Skip to content

Fix and simplify side panel splits - #2059

Merged
ymichael merged 6 commits into
mainfrom
bb/investigate-buggy-side-panel-splits-thr_id6scmvsem
Aug 20, 2026
Merged

Fix and simplify side panel splits#2059
ymichael merged 6 commits into
mainfrom
bb/investigate-buggy-side-panel-splits-thr_id6scmvsem

Conversation

@ymichael

@ymichael ymichael commented Aug 20, 2026

Copy link
Copy Markdown
Collaborator

What was wrong

The split right panel rendered the same layout twice: a shared tab-header tree and a separate pane-body tree. The header copy always laid its children out horizontally, so stacked splits detached each tab strip from its pane, and the paired resize handlers could mutate the mirrored tree on the wrong axis. Tab rendering was also split across parallel fileTabs and tabModels representations, while root-compose and plugin fixed tabs used separate adapters, making pane-local selection and content inconsistent.

What changed

  • Render each pane's tab strip, controls, divider, and body as one leaf in a single split tree. Vertical and horizontal splits now resize only their own axis.
  • Preserve Keep split-divider drag state off document.body #2073’s divider performance fix: resize cursor state stays container-local, the iframe drag guard is the final container child, and pointer cancel/unmount restores the temporary flex values without writing inherited styles to document.body.
  • Preserve Preserve persisted file opener source routing #2083’s file-opener routing contract in every unified renderer: persisted params own path and route identity, while the owner record supplies native preview presentation state.
  • Remove the mirrored header tree and its resize-peer synchronization, plus unused sidebar pane maximize/swap/close state.
  • Route thread tabs, root/new-thread tabs, plugin nav fixed tabs, Browser, and Terminal through the same split container and pane-local renderer.
  • Replace the parallel fileTabs/tabModels join with one renderable tab descriptor that owns its model, chrome, actions, content renderer, and layout behavior.
  • Document that navPanel.experimental_fixedTabs can mount once per active visible split pane; the updated contract remains covered by the unpublished plugin SDK 0.4.10 already reserved on main.

There are no host-daemon protocol or other wire changes.

How you verified

  • Reproduced the detached header and cross-axis resize behavior with dev-browser before the fix, then manually verified thread-panel, root-compose, plugin fixed-tab, Browser, Terminal, horizontal, and vertical split combinations after it.
  • Added regression coverage for pane-local tab rendering, split orientation, resizing, persisted-layout reconciliation, plugin fixed tabs, and root-compose content.
  • pnpm exec turbo run typecheck --filter=@bb/app --filter=@get-bb/plugin-sdk
  • pnpm exec turbo run lint --filter=@bb/app --filter=@get-bb/plugin-sdk (0 errors; existing warnings only)
  • node packages/plugin-sdk/scripts/check-npm-version-guard.mjs
  • Post-rebase divider tests: 2 files, 22 tests passed.
  • Post-Preserve persisted file opener source routing #2083 focused app tests: 8 files, 159 tests passed.
  • Plugin SDK tests: 13 files, 105 tests passed.
  • Full app suite: 403 files, 3,097 tests passed, 3 skipped.

Before and after

Thread panel split

Before After
Thread panel split before Thread panel split after

Root/new-thread split

Before After
Root compose split before Root compose split after

Fixes the side-panel split regression introduced by 4e13e3f.

AGENT GENERATED: by GPT-5.6

ymichael added a commit that referenced this pull request Aug 20, 2026
## What was wrong


[`SidebarSplitDivider`](https://github.com/get-bb/bb/blob/6be45053be92f964895038fc374a45e759087479/apps/app/src/components/secondary-panel/SidebarSplitContainer.tsx#L679-L757),
introduced by [#1601](#1601), wrote
both `cursor` and `user-select` to `document.body` at drag start and
cleared them at drag finish. Both properties are inherited, so each
write invalidated styles across the full app tree. That repeats the root
cause measured in [#1992](#1992), where
inherited body/root mutations cost hundreds of milliseconds per
recalculation on a roughly 20k-element long-thread DOM. The divider also
had no unmount cleanup for its native pointer listeners or temporary
flex values.

## What changed

- Keep the active split-divider cursor in local `SidebarSplitContainer`
state instead of mutating `document.body` or the document root.
- Mount the existing `IframeDragGuardOverlay` after the split content,
with `col-resize` or `row-resize` for the active divider. This keeps the
drag captured across iframe/browser-backed pane content without
inserting a guard before a large sibling subtree.
- Continue relying on the divider's existing pointer-down
`preventDefault()` to prevent text selection, matching #1992.
- Cancel the in-flight resize on divider unmount: remove native pointer
listeners, release capture when held, clear drag chrome, and restore
uncommitted flex values.
- Remove the now-unused global body cursor helpers, while preserving the
panel-library takeover used by main.tsx.

This is an app-only change with no host-daemon protocol or other wire
changes. It reuses the existing drag guard and straightforward component
state; it does not introduce another split framework or alter the split
layout model.

## How you verified

The focused regression coverage failed before the production change and
passed after it. Before the fix, the new assertions observed `cursor:
col-resize; user-select: none` on `body` during pointer-down and a stuck
`data-dragging="true"` state after unmount.

- `pnpm exec turbo run test --filter=@bb/app --force --
src/components/secondary-panel/SidebarSplitContainer.test.tsx
src/components/layout/AppLayout.sidebar-resize.test.tsx` — 2 files, 20
tests passed.
- `pnpm exec turbo run build typecheck lint --filter=@bb/app` — build
and typecheck passed; lint passed with 0 errors and 156 existing
warnings.
- `git diff --check` — passed.

Coverage proves that split drag does not change body/document-root
inline styles, pointer-down is default-prevented, the guard mounts after
split content with both row and column cursors, pointer-up/cancel remove
it, and unmount cancels the drag and removes its native listeners.

## Relationship to #1992 and #2059

This applies #1992's measured performance fix to the internal
right-panel divider path that #1992 did not cover. Draft
[#2059](#2059) fixes the separate
mirrored split-tree correctness problem. Its current head (`4aca1e08`)
still contains both body mutations, so it does not supersede this fix.
The changes overlap only in `SidebarSplitContainer.tsx` and its test
because #2059 rewrites that component; if #2059 lands first, the rebase
should keep its single-tree rendering while retaining this PR's
container-local cursor state, final drag guard, and divider cleanup.

> AGENT GENERATED: by GPT-5.6-Sol
@ymichael
ymichael force-pushed the bb/investigate-buggy-side-panel-splits-thr_id6scmvsem branch 4 times, most recently from 3cc4ec9 to f48afb7 Compare August 20, 2026 22:40
@ymichael
ymichael force-pushed the bb/investigate-buggy-side-panel-splits-thr_id6scmvsem branch from f48afb7 to 4567374 Compare August 20, 2026 22:41
@ymichael
ymichael marked this pull request as ready for review August 20, 2026 22:45
@ymichael
ymichael merged commit deb7230 into main Aug 20, 2026
13 checks passed
@ymichael
ymichael deleted the bb/investigate-buggy-side-panel-splits-thr_id6scmvsem branch August 20, 2026 22:59
ymichael added a commit that referenced this pull request Aug 20, 2026
## What was wrong

PR #2088 correctly preserved
`PluginFileOpenerSource.experimental_hostId` through the Docs frontend
and private RPC, but `resolveOpenerFile` consumed it only for
`source.kind === "host"`. Root Compose opens a project-source Markdown
file as `{ kind: "workspace", environmentId: null, projectId,
experimental_hostId }`, so Docs accepted the source identity and then
skipped its only workspace branch, throwing “Docs can open workspace,
host, and thread-storage files only.” The same project-backed file
already works in the PDF opener through the core project content route.

## What changed

Docs now resolves a workspace source with no environment and a project
ID through the project's authoritative local-path source. An explicit
`experimental_hostId` selects that host; omission resolves through
`system.config().primaryHostId`, matching the public opener contract and
core project-workspace routing. The resolved source root and host feed
the existing confined Files SDK read, preview, and optimistic-write
calls. Missing, duplicate, or non-absolute project-source identities
fail closed instead of selecting another machine.

This is the smallest complete fix: one resolver branch plus one server
regression. Environment-backed workspace, absolute host-file, and
thread-storage behavior is unchanged. There is no SDK, CLI, guide,
configuration, or host-daemon wire change, so
`HOST_DAEMON_PROTOCOL_VERSION` is unchanged.

## How you verified

The pre-fix focused regression failed both project-backed cases with the
reported error (2 failed, 31 passed):

- `pnpm exec turbo run test --filter=bb-plugin-simple-notes --
server.test.ts`

After the fix and the final rebase onto `deb723091` / merged #2059:

- The focused server suite passed 32 tests. The regression proves
selected-host read, preview, and optimistic save routing; omitted-host
primary routing even when a different project source is marked default;
and rejection of an unknown selected host before file I/O.
- `pnpm exec turbo run test typecheck build
--filter=bb-plugin-simple-notes --force` passed: 66 tests, Docs build
and typecheck, 7 successful Turbo tasks.
- `git diff --check origin/main...HEAD` passed.

Related to #2088, #2083, and #2059.

> AGENT GENERATED: by GPT-5.6-Sol
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.

1 participant