From d8e3eb94b4d4a8ad17cab292d81f49ab4e029f41 Mon Sep 17 00:00:00 2001 From: JJ Lee Date: Tue, 11 Aug 2026 00:38:16 +0200 Subject: [PATCH] fix(session-header): await session sync before navigation from titlebar flyout MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When switching sessions within the same route (params.id change from the titlebar sessions flyout), the Page component persists and the timeline re-keys immediately. If session data isn't fully cached yet, rows trickle in and the measurement burst overlaps with user interaction — the virtualizer's scroll anchoring snaps the viewport back to the bottom. The dashboard doesn't have this issue because it navigates to a different route pattern, causing a full component remount where messagesReady() naturally gates the timeline from false→true with complete data. Fix: await serverSync().session.sync(id) before calling navigate(). This ensures messages + parts are fully in cache when the timeline mounts, eliminating the trickle-in measurement burst. For already-cached sessions (tab strip pre-sync, previous visits) this resolves instantly — no perceptible delay. Also mirrors the dashboard's ctx.projects.open/touch setup to warm the workspace context. Closes #176 --- .../src/components/session/session-header.tsx | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/packages/app/src/components/session/session-header.tsx b/packages/app/src/components/session/session-header.tsx index 260fc4a71..c05e7d479 100644 --- a/packages/app/src/components/session/session-header.tsx +++ b/packages/app/src/components/session/session-header.tsx @@ -785,9 +785,29 @@ function SessionChatsDropdown() { } } - function openSession(session: Session) { + async function openSession(session: Session) { // Close flyout first so its Portal unmounts cleanly. setOpen(false) + + // Mirror the dashboard's project setup: ensure the directory is registered and + // touched so the workspace context is warm when the session page mounts. + const conn = server.current + if (conn) { + const ctx = globalCtx.ensureServerCtx(conn) + ctx.projects.open(session.directory) + ctx.projects.touch(session.directory) + } + + // Await session sync BEFORE navigating (issue #176). When switching sessions + // within the same route (params.id change), the Page component persists and + // the timeline re-keys immediately. If data isn't fully cached yet, rows + // trickle in and the measurement burst overlaps with user interaction, causing + // the virtualizer's scroll anchoring to snap the viewport back. Awaiting here + // ensures messagesReady() is true with complete data the moment the timeline + // mounts — matching the dashboard path where the fresh route mount naturally + // gates on messagesReady(). For already-cached sessions this resolves instantly. + await serverSync().session.sync(session.id).catch(() => {}) + // Use tabs.select for sessions that already have an open tab (no transition). // For sessions without a tab, navigate directly — DO NOT use tabs.openPath // which calls addSessionTab(startTransition), keeping both old and new UI