Skip to content

Scope the split workspace layout to one tab (#873) - #874

Merged
SawyerHood merged 1 commit into
mainfrom
bb/investigate-github-issue-cause-thr_fibdvaha3f
Jul 27, 2026
Merged

Scope the split workspace layout to one tab (#873)#874
SawyerHood merged 1 commit into
mainfrom
bb/investigate-github-issue-cause-thr_fibdvaha3f

Conversation

@SawyerHood

Copy link
Copy Markdown
Collaborator

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 storage subscribe hooks the window storage event. 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.maximizedPaneId had identical wiring.

Fix

New createTabScopedStorage() in browser-storage.ts:

  • reads prefer sessionStorage (per tab, survives that tab's reload), falling back to localStorage so a newly opened tab still starts from the most recent arrangement;
  • writes go to both;
  • no storage subscription, so one tab never adopts another tab's value mid-session.

splitLayoutAtom and maximizedPaneIdAtom use 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 + storage event 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.
  • All four new tests were confirmed to fail against the unfixed atoms.ts and pass with it.
  • ThreadRow.test.tsx's reset now clears sessionStorage too (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 tabA: /threads/thr_C  pane "Tab C"
AFTER tabB: /threads/thr_B  pane "Tab C"  sidebar [thr_B]   <- bleed

After:

1 repro  tabA: /threads/thr_C "Tab C" | tabB: /threads/thr_B "Tab B" sidebar [thr_B]
2 reload tabB: restores its own thread, not tab A's
3 split  tabA: 2 panes [thr_C, thr_A] | tabB: still single-pane "Tab B"
4 newtab      : still seeds from the last arrangement

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 createTabScopedStorage is a two-line change.

🤖 Generated with Claude Code

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>
@SawyerHood
SawyerHood merged commit 4a8bf2d into main Jul 27, 2026
10 checks passed
@SawyerHood
SawyerHood deleted the bb/investigate-github-issue-cause-thr_fibdvaha3f branch July 27, 2026 16:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Selected thread persists across tabs

1 participant