fix: cancel cleanup no longer depends on SSE event (closes #299)

cancelStream() now clears S.activeStreamId, calls setBusy(false),
setStatus(''), and hides the cancel button directly after the cancel
API request completes. Previously cleanup depended on the SSE 'cancel'
event, which never arrived if the connection was already closed —
leaving 'Cancelling...' status and busy spinner stuck indefinitely.

The SSE cancel handler in messages.js still fires when the connection
is alive and performs additional cleanup (adds 'Task cancelled.' message,
clears tool cards). All operations are idempotent.

9 new tests in tests/test_sprint36.py.

Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
This commit is contained in:
nesquena-hermes
2026-04-12 11:08:59 -07:00
committed by GitHub
parent 74a4263056
commit bd3ec45aa9
3 changed files with 187 additions and 3 deletions

View File

@@ -3,9 +3,15 @@ async function cancelStream(){
if(!streamId) return;
try{
await fetch(new URL(`/api/chat/cancel?stream_id=${encodeURIComponent(streamId)}`,location.origin).href,{credentials:'include'});
const btn=$('btnCancel');if(btn)btn.style.display='none';
// Don't set status here - let the SSE cancel event handle UI cleanup
}catch(e){setStatus(t('cancel_failed')+e.message);}
}catch(e){/* cancel request failed — cleanup below still runs */}
// Clear status unconditionally after the cancel request completes.
// The SSE cancel event may also fire and call setBusy(false)/setStatus(''),
// but if the connection is already closed it won't arrive — so we handle
// cleanup here as the guaranteed path.
const btn=$('btnCancel');if(btn)btn.style.display='none';
S.activeStreamId=null;
setBusy(false);
setStatus('');
}
// ── Mobile navigation ──────────────────────────────────────────────────────