Keep split-divider drag state off document.body - #2073
Conversation
|
🚨 SLOP COP 🚨 · I am the SlopCop. I am now reviewing this pull request for security, code quality, performance, architecture, and user behavior. I will post one final review after the checks finish. |
f3a4649 to
eec4f4b
Compare
There was a problem hiding this comment.
🚨 SLOP COP 🚨 · review
Plain-English summary: This change keeps a split-panel drag from changing styles across the full page. It uses a local overlay for cursor feedback. It also clears drag listeners and temporary pane sizes when a divider disappears.
I found one small cleanup issue. The change removes the final use of applyResizeCursor and clearResizeCursor, but resizeCursor.ts still exports them. I left an inline comment with the exact cleanup.
The security review found no security or safety issue. The cursor value uses a closed type. The code checks the pointer ID and clears pointer capture, listeners, the overlay, and temporary styles.
The performance review found no issue. The pointer-move path changes only the adjacent flex styles. The overlay mounts after the large pane trees, so it avoids a full subtree style update.
The architecture review supports reuse of IframeDragGuardOverlay. SplitThreadArea has a similar divider, but it has different timeline and resize rules. A shared divider refactor would make this focused change too large.
Verification:
- The focused Turbo test passed all 20 tests.
git diff --checkpassed.- A real browser test created two right-panel panes and dragged their divider.
- During the drag, the overlay showed
cursor-col-resize, both pane sizes changed, and the body and root styles did not change. - After pointer release, the overlay and drag marker disappeared.
I reviewed the requested SHA f3a4649d521b6334bb2658b5489713a800514da5. The branch moved to eec4f4b461655b22049ca64365663e425236350c during the review. Both changed files have identical content at those two SHAs.
eec4f4b to
e3be25d
Compare
## 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 #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 #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-#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 | | --- | --- | |  |  | #### Root/new-thread split | Before | After | | --- | --- | |  |  | Fixes the side-panel split regression introduced by [`4e13e3f`](4e13e3f). > AGENT GENERATED: by GPT-5.6
What was wrong
SidebarSplitDivider, introduced by #1601, wrote bothcursoranduser-selecttodocument.bodyat 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, 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
SidebarSplitContainerstate instead of mutatingdocument.bodyor the document root.IframeDragGuardOverlayafter the split content, withcol-resizeorrow-resizefor the active divider. This keeps the drag captured across iframe/browser-backed pane content without inserting a guard before a large sibling subtree.preventDefault()to prevent text selection, matching Keep sidebar resize off the app-root style path #1992.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: noneonbodyduring pointer-down and a stuckdata-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 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 inSidebarSplitContainer.tsxand 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.