diff options
| author | Adam Malczewski <[email protected]> | 2026-08-05 14:46:21 +0900 |
|---|---|---|
| committer | Adam Malczewski <[email protected]> | 2026-08-05 14:46:21 +0900 |
| commit | 774519e052fd869952cc5e05aca159e4bf978fa1 (patch) | |
| tree | 2af954316135be8ef689271a807a9bad500ca985 | |
| parent | 42cada78db1e58947074f12dcf7c8e41fc501b40 (diff) | |
| download | dispatch-web-774519e052fd869952cc5e05aca159e4bf978fa1.tar.gz dispatch-web-774519e052fd869952cc5e05aca159e4bf978fa1.zip | |
fix(tabs): reconcile tab workspaceId from authoritative backend data
A summoned conversation's tab can be stamped with the wrong workspace
('default') because the raced conversation.open message precedes the child's
workspace persistence. Add setWorkspaceId to the tabs store and reconcile an
already-open tab's workspace from authoritative sources: the backend
conversation-list refresh and lifecycle status events. This corrects a stale
tab (incl. one persisted to localStorage) once the DB has the right value.
| -rw-r--r-- | src/app/store.svelte.ts | 20 | ||||
| -rw-r--r-- | src/features/tabs/tabs-store.svelte.ts | 5 | ||||
| -rw-r--r-- | src/features/tabs/tabs.ts | 11 |
3 files changed, 35 insertions, 1 deletions
diff --git a/src/app/store.svelte.ts b/src/app/store.svelte.ts index 629e6c6..8997ce0 100644 --- a/src/app/store.svelte.ts +++ b/src/app/store.svelte.ts @@ -1106,8 +1106,15 @@ export function createAppStore(opts?: CreateAppStoreOptions): AppStore { workspaceId: conv.workspaceId, }); } else { - // Already open — update the title from the backend if it differs. + // Already open — reconcile title + workspace from the authoritative + // backend list. A raced `conversation.open` (summon) may have stamped + // the tab with the wrong workspace ("default"); the backend's + // authoritative conversation record is the source of truth, so a + // differing tab workspace is corrected here. tabsStore.setTitle(conv.id, conv.title); + if (tabsStore.tabs.find((t) => t.conversationId === conv.id)?.workspaceId !== conv.workspaceId) { + tabsStore.setWorkspaceId(conv.id, conv.workspaceId); + } } } @@ -1146,6 +1153,17 @@ export function createAppStore(opts?: CreateAppStoreOptions): AppStore { // `queued` = the turn is in flight but waiting for a concurrency slot // (broadcast-only, never persisted — CR-13); the tab shows a ring. conversationStatuses = new Map(conversationStatuses).set(conversationId, status); + // Reconcile the already-open tab's workspace from this authoritative + // lifecycle event (the payload workspace is the conversation's actual + // persisted workspace). A raced `conversation.open` may have stamped the + // wrong workspace ("default") — correct it here. + if ( + chatStores.has(conversationId) && + (tabsStore.tabs.find((t) => t.conversationId === conversationId)?.workspaceId ?? + workspaceId) !== workspaceId + ) { + tabsStore.setWorkspaceId(conversationId, workspaceId); + } // If this is a new active OR queued conversation we don't have a tab for, // open one — so a cross-device turn (incl. one waiting in the concurrency // queue) is visible. `idle` never opens a tab. diff --git a/src/features/tabs/tabs-store.svelte.ts b/src/features/tabs/tabs-store.svelte.ts index d044b1f..acf56fe 100644 --- a/src/features/tabs/tabs-store.svelte.ts +++ b/src/features/tabs/tabs-store.svelte.ts @@ -8,6 +8,7 @@ import { selectTab as reduceSelectTab, setModel as reduceSetModel, setTitle as reduceSetTitle, + setWorkspaceId as reduceSetWorkspaceId, activeTab as selectActiveTab, } from "./tabs"; @@ -28,6 +29,7 @@ export interface TabsStore { closeTab(conversationId: string): void; setModel(conversationId: string, model: string): void; setTitle(conversationId: string, title: string): void; + setWorkspaceId(conversationId: string, workspaceId: string): void; } export function createTabsStore(storage: TabsStorage): TabsStore { @@ -69,5 +71,8 @@ export function createTabsStore(storage: TabsStorage): TabsStore { setTitle(conversationId: string, title: string): void { apply(reduceSetTitle(state, conversationId, title)); }, + setWorkspaceId(conversationId: string, workspaceId: string): void { + apply(reduceSetWorkspaceId(state, conversationId, workspaceId)); + }, }; } diff --git a/src/features/tabs/tabs.ts b/src/features/tabs/tabs.ts index 63cda35..9fc9306 100644 --- a/src/features/tabs/tabs.ts +++ b/src/features/tabs/tabs.ts @@ -82,6 +82,17 @@ export function setTitle(state: TabsState, conversationId: string, title: string return { tabs, activeConversationId: state.activeConversationId }; } +export function setWorkspaceId( + state: TabsState, + conversationId: string, + workspaceId: string, +): TabsState { + const tabs = state.tabs.map((t) => + t.conversationId === conversationId ? { ...t, workspaceId } : t, + ); + return { tabs, activeConversationId: state.activeConversationId }; +} + export function activeTab(state: TabsState): Tab | null { if (state.activeConversationId === null) return null; return state.tabs.find((t) => t.conversationId === state.activeConversationId) ?? null; |
