From c6f428e6871203fcf5849fb382cdc2036ea49046 Mon Sep 17 00:00:00 2001 From: Codeman maintainer Date: Sun, 16 Aug 2026 19:18:49 +0200 Subject: [PATCH] review fixes: broadcast session state on working, guard the CodemanSessionOrder global MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The running group sorts on lastSubmitAt, but nothing pushed a session:updated when a turn STARTS — the browser kept whatever stamp it loaded with, so a 30-second-old turn could rank (and read) as an hour-long one. The working handler now rides the same debounced state broadcast idle already uses. And both call sites of window.CodemanSessionOrder now degrade to tab order when the global is missing (iOS Safari's documented stale-cached-JS after a deploy) instead of TypeErroring the whole home screen away. Co-Authored-By: Claude Fable 5 --- src/web/public/home-sessions.js | 5 ++++- src/web/public/mobile-overview.js | 7 ++++++- src/web/session-listener-wiring.ts | 5 +++++ 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/web/public/home-sessions.js b/src/web/public/home-sessions.js index 571bea14..d5d89ab8 100644 --- a/src/web/public/home-sessions.js +++ b/src/web/public/home-sessions.js @@ -216,7 +216,10 @@ Object.assign(CodemanApp.prototype, { }; }); - return window.CodemanSessionOrder.sort(rows); + // Guarded like every other constants.js consumer: a stale cached + // constants.js (iOS Safari serves old JS after a deploy) must degrade to + // tab order, not TypeError the whole home screen away. + return window.CodemanSessionOrder ? window.CodemanSessionOrder.sort(rows) : rows; }, // ═══════════════════════════════════════════════════════════════ diff --git a/src/web/public/mobile-overview.js b/src/web/public/mobile-overview.js index cdb22f05..eef976c3 100644 --- a/src/web/public/mobile-overview.js +++ b/src/web/public/mobile-overview.js @@ -195,7 +195,12 @@ Object.assign(CodemanApp.prototype, { // Order is `CodemanSessionOrder` (constants.js), shared with the desktop // rail: blocked first (longest-blocked at the top), then running // longest-first, then quiet most-recent-first. - const inSection = (states) => window.CodemanSessionOrder.sort(rows.filter((r) => states.includes(r.state))); + // Guarded: a stale cached constants.js (iOS Safari after a deploy) must + // degrade to tab order, not TypeError the overview away. + const inSection = (states) => { + const filtered = rows.filter((r) => states.includes(r.state)); + return window.CodemanSessionOrder ? window.CodemanSessionOrder.sort(filtered) : filtered; + }; // Past = conversations from the unified list that are not currently live. // The endpoint already folds a transcript into its owning session (via the diff --git a/src/web/session-listener-wiring.ts b/src/web/session-listener-wiring.ts index 9244b159..f52a91b8 100644 --- a/src/web/session-listener-wiring.ts +++ b/src/web/session-listener-wiring.ts @@ -224,6 +224,11 @@ export function createSessionListeners(session: Session, deps: SessionListenerDe // re-capture instead). approvalInbox.resolveForSession(session.id, 'resolved_in_terminal', ['idle']); deps.broadcast(SseEvent.SessionWorking, { id: session.id }); + // Full state ride-along: the home screens sort the running group on + // lastSubmitAt, and without this the browser keeps the stamp it loaded + // with (a turn started after page load ranks by the PREVIOUS turn's + // Enter). Debounced, so working-signal flaps cost one broadcast. + deps.broadcastSessionStateDebounced(session.id); const tracker = deps.getRunSummaryTracker(session.id); if (tracker) { tracker.recordWorking();