← reports

#2919 · Root startup route drops a restored pane

BugPriority: MediumEffort: Lowui open on GitHub 2026-09-02 · base eeaaa3e8d

Verdict: REPRODUCED · Root-cause confidence: high

1. TL;DR

The app restores the saved split layout before it processes the startup route. The root route then requests a new-thread pane. The first route reconciliation uses the normal navigation rule, which replaces the focused pane when the requested content is absent. A restart can therefore remove one saved thread pane from the workspace layout.

2. Claims vs findings

ClaimStatusEvidence
A restart can remove the focused saved pane.VerifiedThe live test began with two thread panes. The root reload ended with one thread pane and one composer pane.
The saved layout itself restores before route reconciliation.VerifiedThe storage atom reads the tab or local value at initialization. The component effect then reconciles the route content.
The normal missing-content rule replaces the focused pane.VerifiedThe focused component test failed twice. Its observed content list contained the composer and only one saved thread.

3. Environment

4. Minimal reproduction

  1. Check out the trusted commit.
  2. Install and build the repository.
    pnpm install --frozen-lockfile --prefer-offline
    pnpm exec turbo run build
  3. Add the regression case to SplitThreadArea.test.tsx.
  4. Run the focused suite.
    pnpm exec turbo run test --filter=@bb/app -- --run src/views/thread-detail/SplitThreadArea.test.tsx

Expected:

pane contents: thread A, thread B, new thread
Tests: 44 passed

Actual in both clean worktrees:

FAIL SplitThreadArea > keeps restored thread panes when the root compose route mounts
AssertionError: expected [ { kind: 'new-thread' }, …(1) ]
to deeply equal [ { kind: 'thread', …(2) }, …(2) ]
Tests: 1 failed | 43 passed
Two saved thread panes before the root route reload
Before reload, the workspace contains Pane A and Pane B.
One thread pane and one new-thread composer after the root route reload
After the root reload, Pane B is absent. The composer occupies its saved pane.

5. Root cause

The layout atom reads the stored split synchronously at initialization. The root route provides new-thread content. The first effect sends the restored layout through the same function used for later route navigation.

That function focuses matching content. When no match exists, its final branch replaces the focused pane. It has no startup context, so it cannot preserve all restored panes while it adds the startup composer.

6. Proposed fix

Give the first route reconciliation a small startup-only path. If a restored layout lacks a composer and has room, add the composer to the right edge. Keep the existing reconciliation rule for later navigation and for other content. Keep the existing pane limit behavior.

7. Related issues

#2795 covers a related new-thread navigation action. It does not cover startup restoration.

8. Verification

The first clean worktree used the trusted commit and produced one failed regression test with 43 existing tests passing. A second clean worktree checked out the same commit, completed a separate frozen install and full build, and produced the same failed assertion. No report correction was necessary.

9. Appendix

Commands used:

git fetch https://github.com/get-bb/bb.git main
git worktree add --detach <clean-one> eeaaa3e8db7b3aeb3c4ab46873816c84cb6ea513
git worktree add --detach <clean-two> eeaaa3e8db7b3aeb3c4ab46873816c84cb6ea513
pnpm install --frozen-lockfile --prefer-offline
pnpm exec turbo run build
pnpm exec turbo run test --filter=@bb/app -- --run src/views/thread-detail/SplitThreadArea.test.tsx

The issue data remained untrusted. The investigation did not open external issue links or run linked code.