Scope the split workspace layout to one tab (#873) - #874
Merged
SawyerHood merged 1 commit intoJul 27, 2026
Conversation
The main content area renders the split layout, which lived in a localStorage-backed atom whose `storage`-event subscription fires in *other* tabs. Selecting a thread in one tab therefore pushed that pane into every other tab, while each tab's sidebar and URL (route-derived, per-tab) stayed put — the split-brain in issue #873. `createTabScopedStorage` persists to sessionStorage (per tab, survives that tab's reload) with a localStorage fallback so a newly opened tab still starts from the most recent arrangement, and deliberately has no `storage` subscription. The split layout and maximized pane now use it; genuine preferences (theme, sidebar collapse, model defaults) keep syncing across tabs as before. Tests: new regression tests in SplitThreadArea.test.tsx and atoms.test.ts (all four fail against the unfixed atoms.ts); ThreadRow.test.tsx's reset now clears both stores. Verified end to end with two real Chrome tabs against the dev app: the reported bleed reproduces before the change and is gone after, while per-tab reload, splits, and new-tab seeding all behave. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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 join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Fixes #873.
Cause
The main content area no longer renders "the thread in the URL" — it renders the split layout, which lived in a
localStorage-backed Jotai atom (bb.splitLayout) whose storagesubscribehooks the windowstorageevent. That event only fires in other tabs, so the moment tab 1 selected a thread, every other tab's atom was overwritten with tab 1's pane and repainted.Each tab's sidebar highlight and address bar come from
useRouteState()(react-router, per-tab) and never moved — hence the exact split-brain in the report: main pane = tab 1's thread, sidebar + URL = tab 2's thread.bb.splitLayout.maximizedPaneIdhad identical wiring.Fix
New
createTabScopedStorage()inbrowser-storage.ts:sessionStorage(per tab, survives that tab's reload), falling back tolocalStorageso a newly opened tab still starts from the most recent arrangement;storagesubscription, so one tab never adopts another tab's value mid-session.splitLayoutAtomandmaximizedPaneIdAtomuse it. Genuine user preferences (theme, sidebar collapse, model defaults) keep syncing across tabs as before.Tests
SplitThreadArea.test.tsx— "ignores a layout written by another tab (issue Selected thread persists across tabs #873)": simulates the other tab's localStorage write +storageevent and asserts the pane and URL don't move.atoms.test.ts— layout and maximized pane unaffected by another tab's write; new-tab seeding vs. own-reload restore.atoms.tsand pass with it.ThreadRow.test.tsx's reset now clearssessionStoragetoo (it was leaking layouts between tests once writes hit both stores).turbo lint typecheck test --filter=@bb/app: 0 errors, 283 files / 2083 tests pass.End-to-end verification
Ran the dev stack with three real threads and drove two real Chrome tabs.
Before (fix reverted, same harness) — the reported bug:
After:
Note
A brand-new tab still inherits the last-written arrangement (case 4) — pre-existing "restore your split on open" behavior, and now the only cross-tab path left. If new tabs should instead start from just their URL, dropping the localStorage fallback in
createTabScopedStorageis a two-line change.🤖 Generated with Claude Code