From 54e88b71efd9a6fd9d880b6e90d844a875808662 Mon Sep 17 00:00:00 2001 From: Adam Malczewski Date: Mon, 22 Jun 2026 00:36:31 +0900 Subject: feat(tabs): cross-device tab sync via conversation lifecycle MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Consume the conversation lifecycle handoff (wire@0.10.0, transport-contract@0.14.0). Re-pinned file: deps + re-mirrored .dispatch/*.reference.md. - fetchOpenConversations() on connect: GET /conversations?status=active,idle restores the tab bar across devices (merges with localStorage — opens new tabs, removes closed ones, updates titles from backend) - conversation.statusChanged WS handler: closed → removeTabLocally (no re-POST); active → open tab + spinner; idle → update status map - conversation.compacted WS handler: dispose stale store + cache, reload history from server - TabBar shows a spinner on active conversations (statusFor prop) - closeTab refactored to use removeTabLocally (extracted cleanup) - conformance guards + WS adapter tests cover all 3 new WsServerMessage types 686 tests green. --- src/features/tabs/ui/TabBar.svelte | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'src/features') diff --git a/src/features/tabs/ui/TabBar.svelte b/src/features/tabs/ui/TabBar.svelte index 812a663..9d224b9 100644 --- a/src/features/tabs/ui/TabBar.svelte +++ b/src/features/tabs/ui/TabBar.svelte @@ -5,12 +5,15 @@ let { tabs, activeConversationId, + statusFor, onSelect, onClose, onNewDraft, }: { tabs: readonly Tab[]; activeConversationId: string | null; + /** Returns the conversation's lifecycle status, or undefined when unknown. */ + statusFor?: (conversationId: string) => string | undefined; onSelect: (conversationId: string) => void; onClose: (conversationId: string) => void; onNewDraft: () => void; @@ -84,6 +87,9 @@ {handles.get(tab.conversationId) ?? tab.conversationId} {tab.title} + {#if statusFor?.(tab.conversationId) === "active"} + + {/if}