From a7241f75fca37501bdf33e8ba977022f76865c69 Mon Sep 17 00:00:00 2001 From: nesquena-hermes Date: Fri, 5 Jun 2026 16:52:28 -0700 Subject: [PATCH] =?UTF-8?q?Release=20v0.51.283=20=E2=80=94=20Release=20IY?= =?UTF-8?q?=20(stage-w2=20=E2=80=94=20composer=20queue=20hint=20during=20a?= =?UTF-8?q?uto-compaction=20#3512)=20(#3690)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(composer): surface that messages queue during auto-compaction (#3512, #3079) Co-authored-by: Rod Boev * docs(changelog): v0.51.283 — Release IY (stage-w2) * fix(composer): restore placeholder on ALL compaction-exit paths, not just clearCompressionUi (#3512) Codex+Opus both caught: setCompressionUi(done) and the live-anchored SSE window._compressionUi=null paths bypassed clearCompressionUi, leaving the 'will queue' placeholder stuck after compaction. Factor restore into _restoreCompressionPlaceholder() + call from every compaction-exit path. --------- Co-authored-by: nesquena-hermes <[email protected]> Co-authored-by: Rod Boev --- CHANGELOG.md | 5 ++++ static/i18n.js | 11 ++++++++ static/messages.js | 2 ++ static/ui.js | 33 +++++++++++++++++++++--- tests/test_claude_code_session_import.py | 20 ++++++++++++++ 5 files changed, 67 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 90c8111b..90c9872d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,11 @@ ## [Unreleased] +## [v0.51.283] — 2026-06-05 — Release IY (stage-w2 — composer queue hint during auto-compaction) + +### Fixed +- **The composer now tells you a message will queue during auto-compaction instead of looking dead.** While automatic compression runs, the send button previously went `disabled` with only a "Waiting for compression to finish" tooltip. It now shows a `queue` action with the placeholder + tooltip "Type a message — it will queue and send after compression", so you can type and have it sent automatically when compaction completes. (#3512, @rodboev; closes #3079) + ## [v0.51.282] — 2026-06-05 — Release IX (stage-3544 — surface memory/skill saves in Activity summary) ### Added diff --git a/static/i18n.js b/static/i18n.js index ee6b2fa3..6dd3db32 100644 --- a/static/i18n.js +++ b/static/i18n.js @@ -1300,6 +1300,7 @@ const LOCALES = { composer_stop: 'Stop generation', composer_disabled_clarify: 'Respond to the clarification request', composer_disabled_compression: 'Waiting for compression to finish', + composer_compression_will_queue: 'Type a message — it will queue and send after compression', composer_disabled_empty: 'Type a message to send', composer_mobile_workspace: 'Workspace', composer_mobile_model: 'Model', @@ -2629,6 +2630,7 @@ const LOCALES = { composer_stop: 'Ferma generazione', composer_disabled_clarify: 'Rispondi alla richiesta di chiarimento', composer_disabled_compression: 'In attesa del completamento della compressione', + composer_compression_will_queue: 'Type a message — it will queue and send after compression', composer_disabled_empty: 'Scrivi un messaggio da inviare', composer_mobile_workspace: 'Workspace', composer_mobile_model: 'Modello', @@ -3963,6 +3965,7 @@ const LOCALES = { composer_stop: '生成を停止', composer_disabled_clarify: '確認要求に応答してください', composer_disabled_compression: '圧縮の完了待ち', + composer_compression_will_queue: 'Type a message — it will queue and send after compression', composer_disabled_empty: '送信するメッセージを入力してください', composer_mobile_workspace: 'ワークスペース', composer_mobile_model: 'モデル', @@ -5167,6 +5170,7 @@ const LOCALES = { composer_stop: 'Остановить генерацию', composer_disabled_clarify: 'Ответьте на запрос о разъяснении', composer_disabled_compression: 'Ожидание завершения сжатия', + composer_compression_will_queue: 'Type a message — it will queue and send after compression', composer_disabled_empty: 'Введите сообщение для отправки', composer_mobile_workspace: 'Рабочее пространство', composer_mobile_model: 'Модель', @@ -6421,6 +6425,7 @@ const LOCALES = { composer_stop: 'Detener generación', composer_disabled_clarify: 'Responde a la solicitud de aclaración', composer_disabled_compression: 'Esperando a que finalice la compresión', + composer_compression_will_queue: 'Type a message — it will queue and send after compression', composer_disabled_empty: 'Escribe un mensaje para enviar', composer_mobile_workspace: 'Espacio de trabajo', composer_mobile_model: 'Modelo', @@ -7391,6 +7396,7 @@ const LOCALES = { composer_stop: 'Generierung stoppen', composer_disabled_clarify: 'Auf die Klärungsanfrage antworten', composer_disabled_compression: 'Warte auf Abschluss der Komprimierung', + composer_compression_will_queue: 'Type a message — it will queue and send after compression', composer_disabled_empty: 'Nachricht eingeben zum Senden', composer_mobile_workspace: 'Arbeitsbereich', composer_mobile_model: 'Modell', @@ -8951,6 +8957,7 @@ const LOCALES = { composer_stop: '停止生成', composer_disabled_clarify: '请回复上方的澄清请求', composer_disabled_compression: '等待压缩完成', + composer_compression_will_queue: 'Type a message — it will queue and send after compression', composer_disabled_empty: '请输入消息后发送', composer_mobile_workspace: '工作区', composer_mobile_model: '模型', @@ -10360,6 +10367,7 @@ const LOCALES = { composer_stop: '停止產生', composer_disabled_clarify: '請回覆上方的澄清要求', composer_disabled_compression: '等待壓縮完成', + composer_compression_will_queue: 'Type a message — it will queue and send after compression', composer_disabled_empty: '請輸入訊息後傳送', composer_mobile_workspace: '工作區', composer_mobile_model: '模型', @@ -12748,6 +12756,7 @@ const LOCALES = { composer_stop: '생성 중지', composer_disabled_clarify: '위의 명확화 요청에 응답하세요', composer_disabled_compression: '압축 완료 대기 중', + composer_compression_will_queue: 'Type a message — it will queue and send after compression', composer_disabled_empty: '메시지를 입력하세요', composer_mobile_workspace: '워크스페이스', composer_mobile_model: '모델', @@ -13994,6 +14003,7 @@ const LOCALES = { composer_stop: 'Arrêter la génération', composer_disabled_clarify: 'Répondre à la demande de précisions', composer_disabled_compression: 'En attendant la fin de la compression', + composer_compression_will_queue: 'Type a message — it will queue and send after compression', composer_disabled_empty: 'Tapez un message à envoyer', composer_mobile_workspace: 'Espace de travail', composer_mobile_model: 'Modèle', @@ -15341,6 +15351,7 @@ const LOCALES = { composer_stop: 'Üretimi durdur', composer_disabled_clarify: 'Açıklama isteğine yanıt verin', composer_disabled_compression: 'Sıkıştırmanın bitmesi bekleniyor', + composer_compression_will_queue: 'Type a message — it will queue and send after compression', composer_disabled_empty: 'Göndermek için bir mesaj yazın', composer_mobile_workspace: 'Çalışma alanı', composer_mobile_model: 'Modeli', diff --git a/static/messages.js b/static/messages.js index 643fd62b..5fa0d2e0 100644 --- a/static/messages.js +++ b/static/messages.js @@ -2388,6 +2388,7 @@ function attachLiveStream(activeSid, streamId, uploaded=[], options={}){ // state in global transient UI makes later renderMessages() calls insert // a duplicate Automatic Compression card. window._compressionUi=null; + if(typeof _restoreCompressionPlaceholder==='function') _restoreCompressionPlaceholder(); snapshotLiveTurn(); return; } @@ -2435,6 +2436,7 @@ function attachLiveStream(activeSid, streamId, uploaded=[], options={}){ // completion state as global transient UI, otherwise every subsequent // render projects the same Auto Compression card again. window._compressionUi=null; + if(typeof _restoreCompressionPlaceholder==='function') _restoreCompressionPlaceholder(); snapshotLiveTurn(); } } diff --git a/static/ui.js b/static/ui.js index 20d4df26..0f6ba123 100644 --- a/static/ui.js +++ b/static/ui.js @@ -3834,6 +3834,7 @@ function setComposerStatus(t){ } let _composerLockState=null; +let _compressionPlaceholderSaved=null; function lockComposerForClarify(placeholderText){ const input=$('msg'); @@ -3905,6 +3906,7 @@ function getComposerPrimaryAction(){ if(!isBusy) return hasContent?'send':'disabled'; if(!hasContent){ if(S.activeStreamId&&typeof cancelStream==='function') return 'stop'; + if(compressionRunning) return 'queue'; return 'disabled'; } const explicitAction=_getExplicitBusyCommandAction(msg&&msg.value); @@ -3949,10 +3951,10 @@ function updateSendBtn(){ let _btnTitle; if(action==='disabled'){ const _dmsg=$('msg'); - const _dcompr=typeof isCompressionUiRunning==='function'&&isCompressionUiRunning(); if(_dmsg&&_dmsg.disabled) _btnTitle=_tt('composer_disabled_clarify','Respond to the clarification request'); - else if(_dcompr) _btnTitle=_tt('composer_disabled_compression','Waiting for compression to finish'); else _btnTitle=_tt('composer_disabled_empty','Type a message to send'); + }else if(action==='queue'&&typeof isCompressionUiRunning==='function'&&isCompressionUiRunning()){ + _btnTitle=_tt('composer_compression_will_queue','Type a message — it will queue and send after compression'); }else{ const _tmap={send:'Send message',queue:'Queue message',interrupt:'Interrupt and send',steer:'Steer current response',stop:'Stop generation'}; _btnTitle=_tt('composer_'+action,_tmap[action]||'Send message'); @@ -6182,10 +6184,22 @@ function isCompressionUiRunning(){ const lock=_compressionSessionLock(); return !!((state&&state.phase==='running') || (lock && S.session && lock===S.session.session_id)); } +// Restore the composer placeholder saved when auto-compaction started. Safe to +// call whenever compression leaves the running state, from any path (clear, +// non-running setCompressionUi, or a direct window._compressionUi=null in the +// SSE handler) — it no-ops when nothing was saved. (#3512) +function _restoreCompressionPlaceholder(){ + const _input=$('msg'); + if(_input&&typeof _compressionPlaceholderSaved==='string'){ + _input.placeholder=_compressionPlaceholderSaved; + } + _compressionPlaceholderSaved=null; +} function clearCompressionUi(){ window._compressionUi=null; _clearCompressionElapsedTimer(); _setCompressionSessionLock(null); + _restoreCompressionPlaceholder(); renderCompressionUi(); } function setCompressionUi(state){ @@ -6199,8 +6213,19 @@ function setCompressionUi(state){ } window._compressionUi=nextState; if(nextState.sessionId) _setCompressionSessionLock(nextState.sessionId); - if(nextState.automatic&&nextState.phase==='running') _startCompressionElapsedTimer(); - else _clearCompressionElapsedTimer(); + if(nextState.automatic&&nextState.phase==='running'){ + _startCompressionElapsedTimer(); + const _input=$('msg'); + if(_input&&_compressionPlaceholderSaved===null){ + _compressionPlaceholderSaved=_input.placeholder; + _input.placeholder=typeof t==='function'?t('composer_compression_will_queue')||'Type a message — it will queue and send after compression':'Type a message — it will queue and send after compression'; + } + } else { + _clearCompressionElapsedTimer(); + // Leaving the running state (e.g. setCompressionUi(done)) must restore the + // placeholder too — not only clearCompressionUi(). (#3512 leak fix) + _restoreCompressionPlaceholder(); + } renderCompressionUi(); } function _compressionCardsHtml(state){ diff --git a/tests/test_claude_code_session_import.py b/tests/test_claude_code_session_import.py index 3d78f96c..4e1d294f 100644 --- a/tests/test_claude_code_session_import.py +++ b/tests/test_claude_code_session_import.py @@ -276,3 +276,23 @@ def test_messaging_source_badge_in_sidebar_not_gated_on_is_cli_session(): assert '.session-source-chip[data-source-key="telegram"]' in style_css assert '.session-source-chip[data-source-key="discord"]' in style_css assert '.session-item.cli-session[data-source-key="telegram"]' in style_css + + +def test_compression_queue_discoverability_ux(): + ui_js = (REPO_ROOT / "static" / "ui.js").read_text(encoding="utf-8") + i18n_js = (REPO_ROOT / "static" / "i18n.js").read_text(encoding="utf-8") + + # Old misleading tooltip key must be gone from the compression branch + assert "composer_disabled_compression','Waiting for compression to finish'" not in ui_js + + # New will-queue key must be present in ui.js (tooltip and placeholder) + assert "composer_compression_will_queue" in ui_js + + # getComposerPrimaryAction must return 'queue' for compressionRunning+no-content + assert "if(compressionRunning) return 'queue';" in ui_js + + # clearCompressionUi must restore the placeholder + assert "_compressionPlaceholderSaved" in ui_js + + # New i18n key must appear in all 11 locales (count occurrences) + assert i18n_js.count("composer_compression_will_queue") >= 11