refactor(tab-manager): make Chrome the sole authority for live browser state#1543
Merged
refactor(tab-manager): make Chrome the sole authority for live browser state#1543
Conversation
…components
Use Empty.Root for both loading and error states in the whenReady gate,
and add {:catch} block so failed seed shows an actionable error instead
of an infinite spinner.
Hoist transaction.origin and deviceId guards to observer callback level since both are consistent across all changed IDs in a single firing. Capture narrowed deviceId as const for async closures.
Remove all Y.Doc/CRDT usage for live tabs and windows. Chrome is now the single source of truth for ephemeral browser data—SvelteMap only, no dual-writes, no observers, no echo detection. - Rewrite browser-state.svelte.ts (690 → 406 lines) - Remove tabsTable, windowsTable, tabGroupsTable from workspace - Remove composite ID types/helpers (TabCompositeId, WindowCompositeId, etc.) - Remove row-converters.ts entirely - Remove 7 Y.Doc-dependent query actions, simplify 8 mutation actions - Move device registration to standalone registerDevice() in workspace.ts - Update all consumers for BrowserTab/BrowserWindow types Saved tabs, bookmarks, chat, and device identity remain Y.Doc-backed.
…lpers JSDoc - Move windows computation from getter body to $derived for caching - Update tab-helpers JSDoc to reflect BrowserTab (tabId: number), remove stale Y.Doc references and string-ID examples
Replace 30-line manual type declarations with two ReturnType inferences. The converter functions are now the single source of truth for both the runtime conversion and the exported type shape.
BrowserTab: remove groupId, openerTabId, status (zero consumers). BrowserWindow: remove incognito, type (zero consumers). tab-helpers: unexport normalizeUrl (internal only), replace trySync with plain try/catch (drops wellcrafted/result import).
# Conflicts: # apps/tab-manager/src/entrypoints/sidepanel/App.svelte # apps/tab-manager/src/lib/state/bookmark-state.svelte.ts # apps/tab-manager/src/lib/state/browser-state.svelte.ts # apps/tab-manager/src/lib/state/saved-tab-state.svelte.ts # apps/tab-manager/src/lib/workspace.ts
Remove all Y.Doc/CRDT dual-writes, observers, composite IDs, row-converters,
echo detection, and authState.status checks. Chrome is now the sole authority
for live browser tab state—894 lines reduced to 361.
- Add BrowserTab/BrowserWindow intersection types (Browser.tabs.Tab & { id: number })
- Add narrowTab/narrowWindow type guards (no object creation)
- Use $derived for windows getter (cached, not recomputed per access)
- Use raw promise for whenReady (no $state wrapper)
- Single set of event handlers (removed merge-artifact duplicates)
Spec: 20260319T170000-browser-state-chrome-authority-v2.md (Phase 1)
Switch all consumer files from workspace Tab/Window types to Chrome-native BrowserTab/BrowserWindow from browser-state. Field renames: .tabId→.id, .windowId→.id (Chrome's native field names). - saved-tab-state: Tab→BrowserTab, .tabId→.id - bookmark-state: Tab→BrowserTab - unified-view-state: .windowId→.id on BrowserWindow accesses - UnifiedTabList: .windowId→.id, .tabId→.id in getKey + template - TabItem: .tabId→.id in derived - items.ts: .tabId→.id, .windowId→.id, title sort null-safety - tab-helpers: TabLike.tabId→TabLike.id - App.svelte: add registerDevice() call in onMount - workspace.ts: add registerDevice() export + device-id imports Spec: 20260319T170000-browser-state-chrome-authority-v2.md (Phase 4)
Remove tabsTable, windowsTable, tabGroupsTable and all associated infrastructure: composite ID types/functions, sentinel constants, tab group color type, internal helpers (nativeTabId, toNativeIds). Remove query actions that read from deleted tables: tabs.search, tabs.list, tabs.findDuplicates, tabs.dedup, tabs.groupByDomain, windows.list, domains.count. Update remaining mutation actions (close, open, activate, save, group, pin, mute, reload) to accept native number IDs instead of composite strings. 1123 lines reduced to 600. Spec: 20260319T170000-browser-state-chrome-authority-v2.md (Phase 2-3, complete)
- TabItem: remove unnecessary $derived(tab.id) wrapper, use tab.id directly; merge duplicate browser-state imports into one - tab-helpers: update JSDoc examples from tabId to id - saved-tab-state, bookmark-state: fix indentation on import closing brace and method declarations (sub-agent artifacts) - items.ts: fix indentation in dedup and group-by-domain handlers
…owWindow to BrowserTab/BrowserWindow constructors
Replace plain `{ id: number }` intersections with branded `TabId` and
`WindowId` types (via wellcrafted/brand) to prevent accidental mixing of
tab and window IDs. Brand constructors reject undefined and TAB_ID_NONE
at the ingestion boundary. Rename narrowTab/narrowWindow to match the
PascalCase dual-namespace convention (type + function share the same name).
Contributor
|
Thank you for your contribution! Before we can merge this PR, we need you to sign our Contributor License Agreement. This is a one-time requirement—it lets us offer commercial licenses for the sync server while you retain full copyright on your code. To sign, please comment on this PR with:
I have read the CLA Document and I hereby sign the CLA You can retrigger this bot by commenting recheck in this Pull Request. Posted by the CLA Assistant Lite bot. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This rewrites the tab manager's live browser state around Chrome as the source of truth and removes the old Y.Doc mirror for tabs, windows, and tab groups.
The old design kept two models of the same ephemeral state alive at once: a reactive SvelteMap for the UI and Y.Doc tables for sync. That made every browser event do double work, forced observers to materialize remote changes back into Chrome, and needed echo-detection heuristics to avoid loops. We don't need that machinery for live browser state. Chrome already owns it.
We now seed from
browser.windows.getAll({ populate: true }), listen to Chrome events, and keep the sidepanel's live state in a singleSvelteMap<number, WindowState>. Y.Doc still backs the data users actually create and expect to persist—saved tabs, bookmarks, chat, tool trust, and device metadata.The shape change is straightforward:
That shift let this PR remove the composite browser-state IDs and the conversion layer that only existed to shuttle Chrome data into Y.Doc.
Concretely, this PR:
browser-state.svelte.tsto use Chrome-native tabs/windows and event-driven updates onlyworkspace.tsrow-converters.tsregisterDevice()init pathA couple of trade-offs are worth calling out. Cross-device live tab mirroring goes away with this design, and any future AI queries over live tabs will need to read from browser state directly instead of workspace tables. That's intentional here: the branch favors a simpler, more honest state model over pretending ephemeral browser state is durable shared data.