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