diff --git a/frontend/src/lib/components/sessions/SessionWrapper.svelte b/frontend/src/lib/components/sessions/SessionWrapper.svelte index bedc7c9167..2869bb383a 100644 --- a/frontend/src/lib/components/sessions/SessionWrapper.svelte +++ b/frontend/src/lib/components/sessions/SessionWrapper.svelte @@ -106,8 +106,10 @@ setContext('aiChatManager', runtime.manager) } - // Reactive session reference (mutations to summary/target propagate via the $state proxy) - const session = $derived(sessionState.sessions.find((s) => s.id === sessionId)) + // Reactive session reference (mutations to summary/target propagate via the $state proxy). + // Falls back to the mounted session once it is deleted: the layout's header reads this + // through PageHeaderContent, outside the page's tree, once more before the wrapper unmounts. + const session = $derived(sessionState.sessions.find((s) => s.id === sessionId) ?? initialSession) // Only a session that has never sent opens as the starter block. Read off the record, not // the transcript: a cold session's history is still loading from storage when it mounts, diff --git a/frontend/src/routes/(root)/(logged)/sessions/+page.svelte b/frontend/src/routes/(root)/(logged)/sessions/+page.svelte index f0b866b373..a34a97c45b 100644 --- a/frontend/src/routes/(root)/(logged)/sessions/+page.svelte +++ b/frontend/src/routes/(root)/(logged)/sessions/+page.svelte @@ -94,11 +94,7 @@ tabsToReload, viewerTabsToReload } from '$lib/components/sessions/previewReload' - import { - isPreviewListPath, - barePath, - agentPagePath - } from '$lib/components/sessions/previewPaths' + import { isPreviewListPath, barePath, agentPagePath } from '$lib/components/sessions/previewPaths' import { listWorkspaceAgents } from '$lib/components/sessions/workspaceAgents' import { leafKeyFor, @@ -268,6 +264,15 @@ .map((r) => sessionState.sessions.find((s) => s.id === r.sessionId)) .filter((s): s is NonNullable => s != null) ) + // The preview hosts take their runtime from the item rather than a lookup by id: a + // removed session's hosts are torn down after its runtime has left the map, and their + // teardowns (an editor unregistering from the runtime) still read the prop. + const warmHosts = $derived( + listRuntimes().flatMap((runtime) => { + const session = sessionState.sessions.find((s) => s.id === runtime.sessionId) + return session ? [{ session, runtime }] : [] + }) + ) // Chat columns stay mounted (stacked, visibility-toggled) so switching back // keeps scroll position, MRU-capped like preview tabs: every mounted column @@ -1272,9 +1277,8 @@
{#if previewTabHostComponent.current} {@const TabHost = previewTabHostComponent.current} - {#each warmSessions as s (s.id)} - {@const rt = getRuntime(s.id)} - {@const tabs = rt?.previewTabs} + {#each warmHosts as { session: s, runtime: rt } (s.id)} + {@const tabs = rt.previewTabs} {#each tabs?.tabs ?? [] as tab (tab.id)}