Release v0.51.283 — Release IY (stage-w2 — composer queue hint during auto-compaction #3512) (#3690)

* feat(composer): surface that messages queue during auto-compaction (#3512, #3079)

Co-authored-by: Rod Boev <rod.boev@gmail.com>

* 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 <rod.boev@gmail.com>
This commit is contained in:
nesquena-hermes 2026-06-05 16:52:28 -07:00 committed by GitHub
parent fd45b54c17
commit a7241f75fc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 67 additions and 4 deletions

View file

@ -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

View file

@ -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',

View file

@ -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();
}
}

View file

@ -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){

View file

@ -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