From 409435ac28e492a42311ba77daf444d67a53af7f Mon Sep 17 00:00:00 2001 From: Sawyer Hood Date: Sat, 18 Jul 2026 15:22:06 -0700 Subject: [PATCH 1/2] Backup dockable terminals and views worktree --- apps/app/src/components/layout/AppLayout.tsx | 37 +- .../plugin/plugin-slot-mounts.test.tsx | 41 +- .../sidebar/SidebarThreadSearchPanel.test.tsx | 31 +- apps/app/src/components/sidebar/ThreadRow.tsx | 16 +- .../sidebar/paneContentSplitIndicator.ts | 16 +- .../sidebar/usePaneContentSplitDrag.ts | 57 +- .../sidebar/useThreadRowSplitDrag.test.tsx | 108 +- .../sidebar/useThreadRowSplitDrag.ts | 81 +- .../workspace-panes/DiffPaneContent.tsx | 90 + .../workspace-panes/TerminalPaneContent.tsx | 72 + apps/app/src/lib/route-paths.ts | 15 + apps/app/src/lib/split-layout/atoms.test.ts | 126 +- apps/app/src/lib/split-layout/atoms.ts | 61 +- .../lib/split-layout/computePaneRects.test.ts | 8 +- apps/app/src/lib/split-layout/ops.test.ts | 461 +++- apps/app/src/lib/split-layout/ops.ts | 525 ++++- .../src/lib/split-layout/persistence.test.ts | 243 ++- apps/app/src/lib/split-layout/persistence.ts | 83 +- apps/app/src/lib/split-layout/types.ts | 41 +- apps/app/src/views/SplitWorkspaceRoute.tsx | 24 + .../src/views/thread-detail/PaneTabStrip.tsx | 574 +++++ .../SplitThreadArea.archive.test.tsx | 20 +- .../SplitThreadArea.parity.test.tsx | 14 +- .../thread-detail/SplitThreadArea.test.tsx | 190 +- .../views/thread-detail/SplitThreadArea.tsx | 472 ++-- .../thread-detail/ThreadDetailHeader.test.tsx | 12 + .../thread-detail/ThreadDetailHeader.tsx | 52 + .../views/thread-detail/ThreadDetailView.tsx | 2 + .../thread-detail/splitPaneCommands.test.ts | 10 +- .../splitThreadNavigation.test.ts | 165 +- .../thread-detail/splitThreadNavigation.ts | 182 +- .../thread-detail/useThreadPaneEntryPoints.ts | 180 ++ bb39-report/bb39-dockable-panes-review.html | 269 +++ bb39-report/qa-001b-direct-thread.jpg | Bin 0 -> 57908 bytes bb39-report/qa-002-beta-replaces-preview.jpg | Bin 0 -> 57837 bytes bb39-report/qa-004-gamma-third-preview.jpg | Bin 0 -> 58978 bytes bb39-report/qa-005-terminal-pane.jpg | Bin 0 -> 43291 bytes bb39-report/qa-006-terminal-interactive.jpg | Bin 0 -> 53615 bytes .../qa-008-terminal-reopened-same-pty.jpg | Bin 0 -> 53615 bytes bb39-report/qa-009-diff-pane.jpg | Bin 0 -> 39876 bytes bb39-report/qa-011-terminal-split-right.jpg | Bin 0 -> 73795 bytes .../qa-013-archive-spares-terminal.jpg | Bin 0 -> 40241 bytes bb39-report/qa-014-flag-off-parity.jpg | Bin 0 -> 49895 bytes bb39-report/report-draft.html | 269 +++ bb39-report/tab-bar-mocks.html | 910 ++++++++ dockable-workspace-prototype.html | 1942 +++++++++++++++++ 46 files changed, 6837 insertions(+), 562 deletions(-) create mode 100644 apps/app/src/components/workspace-panes/DiffPaneContent.tsx create mode 100644 apps/app/src/components/workspace-panes/TerminalPaneContent.tsx create mode 100644 apps/app/src/views/thread-detail/PaneTabStrip.tsx create mode 100644 apps/app/src/views/thread-detail/useThreadPaneEntryPoints.ts create mode 100644 bb39-report/bb39-dockable-panes-review.html create mode 100644 bb39-report/qa-001b-direct-thread.jpg create mode 100644 bb39-report/qa-002-beta-replaces-preview.jpg create mode 100644 bb39-report/qa-004-gamma-third-preview.jpg create mode 100644 bb39-report/qa-005-terminal-pane.jpg create mode 100644 bb39-report/qa-006-terminal-interactive.jpg create mode 100644 bb39-report/qa-008-terminal-reopened-same-pty.jpg create mode 100644 bb39-report/qa-009-diff-pane.jpg create mode 100644 bb39-report/qa-011-terminal-split-right.jpg create mode 100644 bb39-report/qa-013-archive-spares-terminal.jpg create mode 100644 bb39-report/qa-014-flag-off-parity.jpg create mode 100644 bb39-report/report-draft.html create mode 100644 bb39-report/tab-bar-mocks.html create mode 100644 dockable-workspace-prototype.html diff --git a/apps/app/src/components/layout/AppLayout.tsx b/apps/app/src/components/layout/AppLayout.tsx index 1077aba06..802bff969 100644 --- a/apps/app/src/components/layout/AppLayout.tsx +++ b/apps/app/src/components/layout/AppLayout.tsx @@ -63,7 +63,10 @@ import { getThreadRoutePath, isProjectlessProjectId, PLUGIN_PANEL_ROUTE_PATH, + PROJECTLESS_THREAD_DIFF_ROUTE_PATH, SETTINGS_ROUTE_PATH, + TERMINAL_ROUTE_PATH, + THREAD_DIFF_ROUTE_PATH, } from "@/lib/route-paths"; import { useQuickCreateProjectController } from "@/hooks/useQuickCreateProject"; import { IframeDragGuardOverlay } from "@/lib/iframe-drag-guard"; @@ -78,8 +81,11 @@ import { useIsCompactViewport } from "@bb/shared-ui/hooks/use-compact-viewport"; import { useMobileVisualViewportHeight } from "./useMobileVisualViewportHeight"; import { wsManager } from "@/lib/ws"; import { splitLayoutAtom } from "@/lib/split-layout/atoms"; -import { findPaneByThread } from "@/lib/split-layout"; -import { applyThreadOpenToLayout } from "@/views/thread-detail/splitThreadNavigation"; +import { findContentTab } from "@/lib/split-layout"; +import { + applyThreadOpenToLayout, + threadPaneContent, +} from "@/views/thread-detail/splitThreadNavigation"; import { useThreadSplitsEnabled } from "@/hooks/useThreadSplitsEnabled"; import { useAppSettingsRouteMemory } from "@/hooks/useAppSettingsRouteMemory"; @@ -443,19 +449,24 @@ export function AppLayout({ children }: AppLayoutProps) { return; } const current = store.get(splitLayoutAtom); + const thread = { + projectId: signal.projectId, + threadId: signal.threadId, + }; + const content = threadPaneContent(thread); const alreadyOpen = - current !== null && - findPaneByThread(current.root, signal.projectId, signal.threadId) !== - null; + current !== null && findContentTab(current.root, content) !== null; const next = applyThreadOpenToLayout( current, - { projectId: signal.projectId, threadId: signal.threadId }, + thread, isCompactViewport ? "replace" : signal.split, ); if (next !== current) { store.set(splitLayoutAtom, next); } - void navigate(route, alreadyOpen ? { replace: true } : undefined); + if (findContentTab(next.root, content) !== null) { + void navigate(route, alreadyOpen ? { replace: true } : undefined); + } }), [isCompactViewport, navigate, store, threadSplitsEnabled], ); @@ -554,7 +565,17 @@ export function AppLayout({ children }: AppLayoutProps) { const showHeader = !isThreadView && !isRootView && - !(threadSplitsEnabled && pluginPanelMatch !== null); + // Splittable workspace routes render inside SplitThreadArea, where each + // pane's tab strip is the top chrome; the shared AppHeader would sit + // above it as an empty 48px band. + !( + threadSplitsEnabled && + (pluginPanelMatch !== null || + matchPath(TERMINAL_ROUTE_PATH, location.pathname) !== null || + matchPath(PROJECTLESS_THREAD_DIFF_ROUTE_PATH, location.pathname) !== + null || + matchPath(THREAD_DIFF_ROUTE_PATH, location.pathname) !== null) + ); const [desktopInfo] = useState(getBbDesktopInfo); const desktopWindowState = useDesktopWindowState(); const usesDesktopChrome = shouldUseMacosDesktopChrome(desktopInfo); diff --git a/apps/app/src/components/plugin/plugin-slot-mounts.test.tsx b/apps/app/src/components/plugin/plugin-slot-mounts.test.tsx index b7f908517..34500af00 100644 --- a/apps/app/src/components/plugin/plugin-slot-mounts.test.tsx +++ b/apps/app/src/components/plugin/plugin-slot-mounts.test.tsx @@ -56,6 +56,17 @@ import { } from "./PluginPanelActions"; import { splitLayoutAtom } from "@/lib/split-layout/atoms"; import type { PromptDraftState } from "@/lib/prompt-draft"; +import type { PaneContent } from "@/lib/split-layout"; + +function pane(paneId: string, content: PaneContent) { + const tabId = `${paneId}-t1`; + return { + type: "pane" as const, + paneId, + tabs: [{ tabId, content, preview: false }], + activeTabId: tabId, + }; +} function registrationSet( overrides: Partial, @@ -1379,25 +1390,17 @@ describe("PluginNavSidebarItems + PluginPanelView", () => { dir: "row", sizes: [0.5, 0.5], children: [ - { - type: "pane", - paneId: "pane-plugin", - content: { - kind: "plugin-panel", - pluginId: "demo", - panelPath: "board", - subPath: "card/1", - }, - }, - { - type: "pane", - paneId: "pane-thread", - content: { - kind: "thread", - projectId: "proj_test", - threadId: "thr_test", - }, - }, + pane("pane-plugin", { + kind: "plugin-panel", + pluginId: "demo", + panelPath: "board", + subPath: "card/1", + }), + pane("pane-thread", { + kind: "thread", + projectId: "proj_test", + threadId: "thr_test", + }), ], }, }); diff --git a/apps/app/src/components/sidebar/SidebarThreadSearchPanel.test.tsx b/apps/app/src/components/sidebar/SidebarThreadSearchPanel.test.tsx index d5c1a5d2b..4c9c33469 100644 --- a/apps/app/src/components/sidebar/SidebarThreadSearchPanel.test.tsx +++ b/apps/app/src/components/sidebar/SidebarThreadSearchPanel.test.tsx @@ -17,6 +17,7 @@ import { isThreadSearchKeyboardEventTarget } from "./AppSidebar"; import { ProjectListActionButtons } from "./ProjectList"; import { SidebarThreadSearchPanel } from "./SidebarThreadSearchPanel"; import { splitLayoutAtom } from "@/lib/split-layout/atoms"; +import type { PaneContent } from "@/lib/split-layout"; import { getSidebarThreadSearchOptionId, haveSameSidebarThreadSearchNavigationItems, @@ -31,6 +32,16 @@ vi.mock("@/hooks/queries/thread-queries", () => ({ const mockUseThreadSearch = vi.mocked(useThreadSearch); +function pane(paneId: string, content: PaneContent) { + const tabId = `${paneId}-t1`; + return { + type: "pane" as const, + paneId, + tabs: [{ tabId, content, preview: false }], + activeTabId: tabId, + }; +} + function createThreadListEntry({ sectionId = null, id, @@ -424,20 +435,12 @@ describe("ProjectListActionButtons", () => { dir: "row", sizes: [0.5, 0.5], children: [ - { - type: "pane", - paneId: "pane-compose", - content: { kind: "new-thread" }, - }, - { - type: "pane", - paneId: "pane-thread", - content: { - kind: "thread", - projectId: "proj_test", - threadId: "thr_test", - }, - }, + pane("pane-compose", { kind: "new-thread" }), + pane("pane-thread", { + kind: "thread", + projectId: "proj_test", + threadId: "thr_test", + }), ], }, }); diff --git a/apps/app/src/components/sidebar/ThreadRow.tsx b/apps/app/src/components/sidebar/ThreadRow.tsx index 48759e65b..97f0a6195 100644 --- a/apps/app/src/components/sidebar/ThreadRow.tsx +++ b/apps/app/src/components/sidebar/ThreadRow.tsx @@ -464,12 +464,15 @@ function ThreadRowComponent({ { kind: "thread", projectId, threadId: thread.id }, threadSplitsEnabled, ); - const { onPointerDown: onSplitDragPointerDown, openInSplit } = - useThreadRowSplitDrag({ - projectId, - threadId: thread.id, - title: labelTitle, - }); + const { + onPointerDown: onSplitDragPointerDown, + openInSplit, + commitThreadTab, + } = useThreadRowSplitDrag({ + projectId, + threadId: thread.id, + title: labelTitle, + }); // Splits are disabled on compact viewports; the drag hook signals that by // withholding its pointer handler, so gate the click/menu entry points on it. const splitAvailable = onSplitDragPointerDown !== undefined; @@ -571,6 +574,7 @@ function ThreadRowComponent({ to={getThreadRoutePath({ projectId, threadId: thread.id })} data-sidebar-thread-shortcut-target="" data-sidebar-thread-id={thread.id} + onDoubleClick={commitThreadTab} onClick={(event) => { // Selecting a thread/agent row restores its conversation without // disturbing any other thread's collapsed conversation state. diff --git a/apps/app/src/components/sidebar/paneContentSplitIndicator.ts b/apps/app/src/components/sidebar/paneContentSplitIndicator.ts index 0c414e177..f53c9009a 100644 --- a/apps/app/src/components/sidebar/paneContentSplitIndicator.ts +++ b/apps/app/src/components/sidebar/paneContentSplitIndicator.ts @@ -4,8 +4,8 @@ import { useIsCompactViewport } from "@bb/shared-ui/hooks/use-compact-viewport"; import { splitLayoutAtom } from "@/lib/split-layout/atoms"; import { computePaneRects, + contentMatches, countPanes, - findPaneByContent, listPanes, type PaneContent, type PaneRect, @@ -53,12 +53,16 @@ export function usePaneContentSplitIndicator( ) { return NO_INDICATOR; } - const pane = findPaneByContent(layout.root, content); - if (pane === null) { + const panes = listPanes(layout.root); + if ( + !panes.some((pane) => + pane.tabs.some((tab) => contentMatches(tab.content, content)), + ) + ) { return NO_INDICATOR; } const rects = computePaneRects(layout.root); - const miniMap: MiniMapSlot[] = listPanes(layout.root).flatMap((entry) => { + const miniMap: MiniMapSlot[] = panes.flatMap((entry) => { const rect = rects.get(entry.paneId); return rect === undefined ? [] @@ -66,7 +70,9 @@ export function usePaneContentSplitIndicator( { paneId: entry.paneId, rect, - isMe: entry.paneId === pane.paneId, + isMe: entry.tabs.some((tab) => + contentMatches(tab.content, content), + ), isFocused: entry.paneId === layout.focusedPaneId, }, ]; diff --git a/apps/app/src/components/sidebar/usePaneContentSplitDrag.ts b/apps/app/src/components/sidebar/usePaneContentSplitDrag.ts index 9d78e939a..9a86adfe9 100644 --- a/apps/app/src/components/sidebar/usePaneContentSplitDrag.ts +++ b/apps/app/src/components/sidebar/usePaneContentSplitDrag.ts @@ -2,18 +2,15 @@ import { useCallback, type PointerEvent as ReactPointerEvent } from "react"; import { useStore } from "jotai"; import { useNavigate } from "react-router-dom"; import { useIsCompactViewport } from "@bb/shared-ui/hooks/use-compact-viewport"; -import { - getPluginPanelRoutePath, - getRootComposeRoutePath, - getThreadRoutePath, -} from "@/lib/route-paths"; import { splitLayoutAtom } from "@/lib/split-layout/atoms"; import { + activateTab, + commitTab, countPanes, - findPaneByContent, + findContentTab, listPanes, MAX_PANES, - replacePaneContent, + openTab, setFocus, splitPane, type PaneContent, @@ -25,20 +22,11 @@ import { shouldEngageSidebarSplitDrag, type SplitDragFallbackTarget, } from "@/lib/split-drag"; +import { paneContentRoute } from "@/views/thread-detail/splitThreadNavigation"; const SIDEBAR_SELECTOR = '[data-sidebar="sidebar"]'; const MAIN_CONTENT_SELECTOR = "main"; -function routeForContent(content: PaneContent): string { - if (content.kind === "thread") return getThreadRoutePath(content); - if (content.kind === "new-thread") return getRootComposeRoutePath(); - return getPluginPanelRoutePath({ - pluginId: content.pluginId, - path: content.panelPath, - subPath: content.subPath, - }); -} - /** Prototype drag/cmd-click source for non-thread pages. */ export function usePaneContentSplitDrag({ content, @@ -54,21 +42,27 @@ export function usePaneContentSplitDrag({ const isCompact = useIsCompactViewport(); const openInSplit = useCallback(() => { - const route = routeForContent(content); + const route = paneContentRoute(content); const layout = store.get(splitLayoutAtom); if (!enabled || isCompact || layout === null) { navigate(route); return; } - const existing = findPaneByContent(layout.root, content); - const next = + const existing = findContentTab(layout.root, content); + let next = existing !== null - ? setFocus(layout, existing.paneId) + ? activateTab(layout, existing.pane.paneId, existing.tab.tabId) : countPanes(layout.root) >= MAX_PANES - ? replacePaneContent(layout, layout.focusedPaneId, content) + ? openTab(layout, layout.focusedPaneId, content) : splitPane(layout, layout.focusedPaneId, "right", content); + if (existing !== null) { + next = commitTab(next, existing.pane.paneId, existing.tab.tabId); + next = setFocus(next, existing.pane.paneId); + } if (next !== layout) store.set(splitLayoutAtom, next); - navigate(route, existing !== null ? { replace: true } : undefined); + if (findContentTab(next.root, content) !== null) { + navigate(route, existing !== null ? { replace: true } : undefined); + } }, [content, enabled, isCompact, navigate, store]); const onPointerDown = useCallback( @@ -100,23 +94,28 @@ export function usePaneContentSplitDrag({ if (layout === null) return null; return decideThreadDrop({ zone, - threadAlreadyOpen: findPaneByContent(layout.root, content) !== null, + threadAlreadyOpen: findContentTab(layout.root, content) !== null, atMaxPanes: countPanes(layout.root) >= MAX_PANES, }); }, onDrop: (target) => { const layout = store.get(splitLayoutAtom); if (layout === null) return; - const existing = findPaneByContent(layout.root, content); - const next = + const existing = findContentTab(layout.root, content); + let next = existing !== null - ? setFocus(layout, existing.paneId) + ? activateTab(layout, existing.pane.paneId, existing.tab.tabId) : target.zone === "center" - ? replacePaneContent(layout, target.paneId, content) + ? openTab(layout, target.paneId, content) : splitPane(layout, target.paneId, target.zone, content); + if (existing !== null) { + next = commitTab(next, existing.pane.paneId, existing.tab.tabId); + next = setFocus(next, existing.pane.paneId); + } if (next !== layout) store.set(splitLayoutAtom, next); + if (findContentTab(next.root, content) === null) return; navigate( - routeForContent(content), + paneContentRoute(content), existing !== null ? { replace: true } : undefined, ); }, diff --git a/apps/app/src/components/sidebar/useThreadRowSplitDrag.test.tsx b/apps/app/src/components/sidebar/useThreadRowSplitDrag.test.tsx index 1086fb734..ec02fbe12 100644 --- a/apps/app/src/components/sidebar/useThreadRowSplitDrag.test.tsx +++ b/apps/app/src/components/sidebar/useThreadRowSplitDrag.test.tsx @@ -3,17 +3,27 @@ import { beforeEach, describe, expect, it, vi } from "vitest"; import { act, renderHook } from "@testing-library/react"; import { createStore, Provider } from "jotai"; -import type { ReactNode } from "react"; +import type { PointerEvent as ReactPointerEvent, ReactNode } from "react"; import { splitLayoutAtom } from "@/lib/split-layout/atoms"; -import { countPanes, findPaneByThread, listPanes } from "@/lib/split-layout"; +import { + activePaneContent, + countPanes, + findPane, + findPaneByThread, + listPanes, + openTab, +} from "@/lib/split-layout"; import type { LayoutNode, PaneContent, SplitLayout } from "@/lib/split-layout"; +import type { SplitDragConfig } from "@/lib/split-drag"; import { useThreadRowSplitDrag } from "./useThreadRowSplitDrag"; -const { navigateSpy, compactState, experimentState } = vi.hoisted(() => ({ - navigateSpy: vi.fn(), - compactState: { value: false }, - experimentState: { enabled: true }, -})); +const { navigateSpy, compactState, experimentState, splitDragState } = + vi.hoisted(() => ({ + navigateSpy: vi.fn(), + compactState: { value: false }, + experimentState: { enabled: true }, + splitDragState: { config: null as SplitDragConfig | null }, + })); vi.mock("react-router-dom", async (importOriginal) => ({ ...(await importOriginal()), @@ -28,12 +38,27 @@ vi.mock("@/hooks/useThreadSplitsEnabled", () => ({ useThreadSplitsEnabled: () => experimentState.enabled, })); +vi.mock("@/lib/split-drag", async (importOriginal) => ({ + ...(await importOriginal()), + beginSplitDrag: vi.fn( + (_startX: number, _startY: number, config: SplitDragConfig) => { + splitDragState.config = config; + }, + ), +})); + function content(threadId: string): PaneContent { return { kind: "thread", projectId: "p1", threadId }; } function pane(paneId: string, threadId: string): LayoutNode { - return { type: "pane", paneId, content: content(threadId) }; + const tabId = `${paneId}-t1`; + return { + type: "pane", + paneId, + tabs: [{ tabId, content: content(threadId), preview: false }], + activeTabId: tabId, + }; } function singlePane(): SplitLayout { @@ -66,6 +91,14 @@ function eightPanes(): SplitLayout { }; } +function fullSinglePane(): SplitLayout { + let layout = singlePane(); + for (let index = 2; index <= 16; index += 1) { + layout = openTab(layout, "pane-1", content(`t${index}`)); + } + return layout; +} + function renderOpenInSplit(threadId: string, layout: SplitLayout | null) { const store = createStore(); store.set(splitLayoutAtom, layout); @@ -80,14 +113,30 @@ function renderOpenInSplit(threadId: string, layout: SplitLayout | null) { store, getOnPointerDown: () => result.current.onPointerDown, openInSplit: () => act(() => result.current.openInSplit()), + startDrag: () => { + const row = document.createElement("div"); + const pointerEvent = new PointerEvent("pointerdown", { + button: 0, + clientX: 0, + clientY: 0, + }); + Object.defineProperty(pointerEvent, "currentTarget", { value: row }); + act(() => + result.current.onPointerDown?.( + pointerEvent as unknown as ReactPointerEvent, + ), + ); + return splitDragState.config; + }, }; } -describe("useThreadRowSplitDrag — openInSplit (cmd-click / context-menu entry)", () => { +describe("useThreadRowSplitDrag", () => { beforeEach(() => { navigateSpy.mockClear(); compactState.value = false; experimentState.enabled = true; + splitDragState.config = null; }); it("splits the focused pane to the right by default", () => { @@ -105,28 +154,55 @@ describe("useThreadRowSplitDrag — openInSplit (cmd-click / context-menu entry) expect(navigateSpy).toHaveBeenCalledWith("/projects/p1/threads/t9"); }); - it("focuses the existing pane instead of duplicating an already-open thread", () => { - const { store, openInSplit } = renderOpenInSplit("t2", twoPanes()); + it("reveals an existing inactive tab without growing the layout", () => { + let seeded = openTab(twoPanes(), "pane-2", content("t3")); + const beforeTabCount = findPane(seeded.root, "pane-2")!.tabs.length; + const { store, openInSplit } = renderOpenInSplit("t2", seeded); openInSplit(); const layout = store.get(splitLayoutAtom); - expect(countPanes(layout!.root)).toBe(2); // no new pane + const paneTwo = findPane(layout!.root, "pane-2")!; + expect(countPanes(layout!.root)).toBe(2); + expect(paneTwo.tabs).toHaveLength(beforeTabCount); expect(layout!.focusedPaneId).toBe("pane-2"); + expect(activePaneContent(paneTwo)).toEqual(content("t2")); + expect( + paneTwo.tabs.find((tab) => tab.tabId === paneTwo.activeTabId)?.preview, + ).toBe(false); expect(navigateSpy).toHaveBeenCalledWith("/projects/p1/threads/t2", { replace: true, }); }); - it("coerces to a replace of the focused pane at the eight-pane cap", () => { + it("coerces to a committed center tab at the eight-pane cap", () => { const { store, openInSplit } = renderOpenInSplit("t9", eightPanes()); openInSplit(); const layout = store.get(splitLayoutAtom); - expect(countPanes(layout!.root)).toBe(8); // never exceeds the cap + expect(countPanes(layout!.root)).toBe(8); const opened = findPaneByThread(layout!.root, "p1", "t9"); - expect(opened?.paneId).toBe("pane-1"); // replaced the focused pane - expect(findPaneByThread(layout!.root, "p1", "t1")).toBeNull(); + const focused = findPane(layout!.root, "pane-1")!; + expect(opened?.paneId).toBe("pane-1"); + expect(focused.tabs).toHaveLength(2); + expect(activePaneContent(focused)).toEqual(content("t9")); + expect( + focused.tabs.find((tab) => tab.tabId === focused.activeTabId)?.preview, + ).toBe(false); + expect(findPaneByThread(layout!.root, "p1", "t1")?.paneId).toBe("pane-1"); expect(navigateSpy).toHaveBeenCalledWith("/projects/p1/threads/t9"); }); + it("does not navigate when a center drop cannot open into a full sixteen-tab group", () => { + const seeded = fullSinglePane(); + const { store, startDrag } = renderOpenInSplit("t17", seeded); + const config = startDrag(); + expect(config).not.toBeNull(); + + act(() => config?.onDrop({ paneId: "pane-1", zone: "center" })); + + expect(store.get(splitLayoutAtom)).toBe(seeded); + expect(findPaneByThread(seeded.root, "p1", "t17")).toBeNull(); + expect(navigateSpy).not.toHaveBeenCalled(); + }); + it("plain-navigates without touching the layout on compact viewports", () => { compactState.value = true; const seeded = singlePane(); diff --git a/apps/app/src/components/sidebar/useThreadRowSplitDrag.ts b/apps/app/src/components/sidebar/useThreadRowSplitDrag.ts index 626e68a33..f9f98d556 100644 --- a/apps/app/src/components/sidebar/useThreadRowSplitDrag.ts +++ b/apps/app/src/components/sidebar/useThreadRowSplitDrag.ts @@ -6,11 +6,13 @@ import { useThreadSplitsEnabled } from "@/hooks/useThreadSplitsEnabled"; import { getThreadRoutePath } from "@/lib/route-paths"; import { splitLayoutAtom } from "@/lib/split-layout/atoms"; import { + activateTab, + commitTab, countPanes, - findPaneByThread, + findContentTab, listPanes, MAX_PANES, - replacePaneContent, + openTab, setFocus, splitPane, type SplitLayout, @@ -39,9 +41,10 @@ const MAIN_CONTENT_SELECTOR = "main"; * pointer-driven layer. It engages only once the pointer leaves the sidebar * toward the main area (so the existing dnd-kit vertical reorder always wins * inside the sidebar — plan §3), then hit-tests panes: an edge splits, the - * center replaces, and a thread already open focuses its pane instead of - * duplicating. The layout ops enforce the pane cap and no-duplicate invariants; - * this only picks targets. Disabled on compact viewports, where splits are off. + * center opens a committed tab, and a thread already open reveals and commits + * its tab instead of duplicating. The layout ops enforce the pane cap and + * no-duplicate invariants; this only picks targets. Disabled on compact + * viewports, where splits are off. */ export function useThreadRowSplitDrag({ projectId, @@ -52,11 +55,14 @@ export function useThreadRowSplitDrag({ /** * Opens this thread in the split via the second entry point (cmd/ctrl-click, * context-menu "Open in split"), using the SAME placement rules as drag: - * default to a right split, focus the pane if already open, and coerce to - * replace at the pane cap. Falls back to plain navigation on compact - * viewports (splits disabled) and non-thread routes (no layout to split). + * default to a right split, reveal the tab if already open, and coerce to + * opening a tab in the focused pane at the pane cap. Falls back to plain + * navigation on compact viewports (splits disabled) and non-thread routes + * (no layout to split). */ openInSplit: () => void; + /** Commits this thread's preview tab on a sidebar-row double click. */ + commitThreadTab: () => void; } { const store = useStore(); const navigate = useNavigate(); @@ -98,8 +104,7 @@ export function useThreadRowSplitDrag({ } return decideThreadDrop({ zone, - threadAlreadyOpen: - findPaneByThread(layout.root, projectId, threadId) !== null, + threadAlreadyOpen: findContentTab(layout.root, content) !== null, atMaxPanes: countPanes(layout.root) >= MAX_PANES, }); }, @@ -108,19 +113,26 @@ export function useThreadRowSplitDrag({ if (layout === null) { return; } - const existing = findPaneByThread(layout.root, projectId, threadId); - const next = + const existing = findContentTab(layout.root, content); + let next = existing !== null - ? setFocus(layout, existing.paneId) + ? activateTab(layout, existing.pane.paneId, existing.tab.tabId) : target.zone === "center" - ? replacePaneContent(layout, target.paneId, content) + ? openTab(layout, target.paneId, content) : splitPane(layout, target.paneId, target.zone, content); + if (existing !== null) { + next = commitTab(next, existing.pane.paneId, existing.tab.tabId); + next = setFocus(next, existing.pane.paneId); + } if (next !== layout) { store.set(splitLayoutAtom, next); } + if (findContentTab(next.root, content) === null) { + return; + } // The dropped thread now owns the focused pane, so the URL follows it. - // An already-open focus is a replace (no history entry); a split or - // replace pushes like a sidebar click. + // An already-open reveal is a replace (no history entry); a split or + // new tab pushes like a sidebar click. navigate( getThreadRoutePath({ projectId, threadId }), existing !== null ? { replace: true } : undefined, @@ -140,13 +152,18 @@ export function useThreadRowSplitDrag({ navigate(route); return; } - const existing = findPaneByThread(layout.root, projectId, threadId); + const content: PaneContent = { kind: "thread", projectId, threadId }; + const existing = findContentTab(layout.root, content); if (existing !== null) { - const next = setFocus(layout, existing.paneId); + let next = activateTab(layout, existing.pane.paneId, existing.tab.tabId); + next = commitTab(next, existing.pane.paneId, existing.tab.tabId); + next = setFocus(next, existing.pane.paneId); if (next !== layout) { store.set(splitLayoutAtom, next); } - navigate(route, { replace: true }); + if (findContentTab(next.root, content) !== null) { + navigate(route, { replace: true }); + } return; } // Same decision as a drag with a default right-edge target. @@ -155,21 +172,41 @@ export function useThreadRowSplitDrag({ threadAlreadyOpen: false, atMaxPanes: countPanes(layout.root) >= MAX_PANES, }); - const content: PaneContent = { kind: "thread", projectId, threadId }; const next = decision.zone === "center" - ? replacePaneContent(layout, layout.focusedPaneId, content) + ? openTab(layout, layout.focusedPaneId, content) : splitPane(layout, layout.focusedPaneId, "right", content); if (next !== layout) { store.set(splitLayoutAtom, next); } - navigate(route); + if (findContentTab(next.root, content) !== null) { + navigate(route); + } }, [isCompact, navigate, projectId, store, threadId, threadSplitsEnabled]); + const commitThreadTab = useCallback(() => { + const layout = store.get(splitLayoutAtom); + if (layout === null) { + return; + } + const content: PaneContent = { kind: "thread", projectId, threadId }; + const existing = findContentTab(layout.root, content); + if (existing === null) { + return; + } + let next = activateTab(layout, existing.pane.paneId, existing.tab.tabId); + next = commitTab(next, existing.pane.paneId, existing.tab.tabId); + next = setFocus(next, existing.pane.paneId); + if (next !== layout) { + store.set(splitLayoutAtom, next); + } + }, [projectId, store, threadId]); + return { onPointerDown: threadSplitsEnabled && !isCompact ? onPointerDown : undefined, openInSplit, + commitThreadTab, }; } diff --git a/apps/app/src/components/workspace-panes/DiffPaneContent.tsx b/apps/app/src/components/workspace-panes/DiffPaneContent.tsx new file mode 100644 index 000000000..4edf75b12 --- /dev/null +++ b/apps/app/src/components/workspace-panes/DiffPaneContent.tsx @@ -0,0 +1,90 @@ +import { useMemo } from "react"; +import { resolveEnvironmentMergeBaseBranch } from "@bb/domain"; +import { GIT_DIFF_VIEW_BASE_OPTIONS } from "@/components/git-diff/GitDiffCard"; +import { useGitDiffPanelState } from "@/components/secondary-panel/git-diff/useGitDiffPanelState"; +import { GitDiffTabContent } from "@/components/secondary-panel/ThreadSecondaryPanelTabContent"; +import { usePreferredTheme } from "@/hooks/useTheme"; +import { useEnvironment } from "@/hooks/queries/environment-queries"; +import { useThread } from "@/hooks/queries/thread-queries"; + +function DiffPlaceholder({ children }: { children: string }) { + return ( +
+ {children} +
+ ); +} + +export function DiffPaneContent({ + projectId, + threadId, +}: { + projectId: string; + threadId: string; +}) { + const threadQuery = useThread(threadId); + const environmentId = threadQuery.data?.environmentId; + const environmentQuery = useEnvironment(environmentId); + const preferredTheme = usePreferredTheme(); + const defaultMergeBaseBranch = resolveEnvironmentMergeBaseBranch( + environmentQuery.data, + ); + const { gitDiffTarget } = useGitDiffPanelState({ + environmentId: environmentId ?? undefined, + isDiffPanelActive: true, + defaultMergeBaseBranch, + }); + const gitDiffViewOptions = useMemo( + () => ({ + ...GIT_DIFF_VIEW_BASE_OPTIONS, + diffStyle: "unified", + themeType: preferredTheme, + }), + [preferredTheme], + ); + + if ( + threadQuery.data === undefined && + (threadQuery.isLoading || threadQuery.isFetching) + ) { + return Loading diff…; + } + + if ( + threadQuery.data === undefined || + threadQuery.data.projectId !== projectId + ) { + return Diff unavailable; + } + + if (environmentId === null) { + return ( + + Diff unavailable: thread has no environment + + ); + } + + if ( + environmentQuery.data === undefined && + (environmentQuery.isLoading || environmentQuery.isFetching) + ) { + return Loading diff…; + } + + if (environmentQuery.data === undefined) { + return Diff unavailable; + } + + return ( +
+ +
+ ); +} diff --git a/apps/app/src/components/workspace-panes/TerminalPaneContent.tsx b/apps/app/src/components/workspace-panes/TerminalPaneContent.tsx new file mode 100644 index 000000000..adbabd249 --- /dev/null +++ b/apps/app/src/components/workspace-panes/TerminalPaneContent.tsx @@ -0,0 +1,72 @@ +import { Pill } from "@bb/shared-ui/pill"; +import { ThreadTerminalView } from "@/components/thread/terminal/ThreadTerminalView"; +import { useTerminals } from "@/hooks/queries/thread-terminal-queries"; +import type { TerminalPaneTarget } from "@/lib/split-layout"; + +function TerminalPlaceholder({ children }: { children: string }) { + return ( +
+ {children} +
+ ); +} + +function terminalTargetLabel(target: TerminalPaneTarget): string { + switch (target.kind) { + case "thread": + return `Thread ${target.threadId.slice(0, 8)}`; + case "environment": + return target.environmentId; + case "host_path": + return target.cwd; + } +} + +export function TerminalPaneContent({ + terminalId, + target, +}: { + terminalId: string; + target?: TerminalPaneTarget; +}) { + const terminalsQuery = useTerminals(target, { + enabled: target !== undefined, + }); + + if (target === undefined) { + return ( + + Terminal unavailable on this surface + + ); + } + + if ( + terminalsQuery.data === undefined && + (terminalsQuery.isLoading || terminalsQuery.isFetching) + ) { + return null; + } + + const session = terminalsQuery.data?.sessions.find( + (candidate) => candidate.id === terminalId, + ); + if (session === undefined || session.status === "exited") { + return Terminal ended; + } + + return ( +
+
+ + + {terminalTargetLabel(target)} + + +
+
+ +
+
+ ); +} diff --git a/apps/app/src/lib/route-paths.ts b/apps/app/src/lib/route-paths.ts index 6a7984ab2..0164a034d 100644 --- a/apps/app/src/lib/route-paths.ts +++ b/apps/app/src/lib/route-paths.ts @@ -20,6 +20,10 @@ export const THREAD_DETAIL_ROUTE_PATH = "/projects/:projectId/threads/:threadId"; // Trailing splat: the remainder is the panel's `subPath` (empty at the root). export const PLUGIN_PANEL_ROUTE_PATH = "/plugins/:pluginId/:panelPath/*"; +export const TERMINAL_ROUTE_PATH = "/terminals/:terminalId"; +export const PROJECTLESS_THREAD_DIFF_ROUTE_PATH = "/threads/:threadId/diff"; +export const THREAD_DIFF_ROUTE_PATH = + "/projects/:projectId/threads/:threadId/diff"; export interface ThreadRoutePathArgs { projectId: string; @@ -110,6 +114,14 @@ export function getThreadRoutePath(args: ThreadRoutePathArgs): string { : `/projects/${args.projectId}/threads/${args.threadId}`; } +export function getTerminalRoutePath(terminalId: string): string { + return `/terminals/${terminalId}`; +} + +export function getThreadDiffRoutePath(args: ThreadRoutePathArgs): string { + return `${getThreadRoutePath(args)}/diff`; +} + const baseRoutePatterns: readonly string[] = [ APP_ROOT_ROUTE_PATH, AUTH_CALLBACK_ROUTE_PATH, @@ -124,6 +136,9 @@ const baseRoutePatterns: readonly string[] = [ PROJECTLESS_THREAD_DETAIL_ROUTE_PATH, THREAD_DETAIL_ROUTE_PATH, PLUGIN_PANEL_ROUTE_PATH, + TERMINAL_ROUTE_PATH, + PROJECTLESS_THREAD_DIFF_ROUTE_PATH, + THREAD_DIFF_ROUTE_PATH, ]; export const ROUTE_PATTERNS = baseRoutePatterns; diff --git a/apps/app/src/lib/split-layout/atoms.test.ts b/apps/app/src/lib/split-layout/atoms.test.ts index 6412bafbd..f2682ec8d 100644 --- a/apps/app/src/lib/split-layout/atoms.test.ts +++ b/apps/app/src/lib/split-layout/atoms.test.ts @@ -8,16 +8,34 @@ import { MAXIMIZED_PANE_STORAGE_KEY, splitLayoutAtom, } from "./atoms"; -import { countPanes, findPaneByThread, splitPane } from "./ops"; -import type { SplitLayout } from "./types"; +import { + activateTab, + countPanes, + findContentTab, + findPane, + findPaneByThread, + openTab, + splitPane, +} from "./ops"; +import type { PaneContent, PaneNode, SplitLayout } from "./types"; + +function pane(paneId: string, content: PaneContent): PaneNode { + const tabId = `${paneId}-t1`; + return { + type: "pane", + paneId, + tabs: [{ tabId, content, preview: false }], + activeTabId: tabId, + }; +} function singlePane(threadId: string): SplitLayout { return { - root: { - type: "pane", - paneId: "pane-1", - content: { kind: "thread", projectId: "project-1", threadId }, - }, + root: pane("pane-1", { + kind: "thread", + projectId: "project-1", + threadId, + }), focusedPaneId: "pane-1", }; } @@ -112,6 +130,100 @@ describe("closePanesForThreadsAtom", () => { expect(store.get(splitLayoutAtom)).toEqual(layout); }); + it("closes every targeted thread and diff tab while preserving terminals", () => { + const store = createStore(); + let layout = openTab(singlePane("thread-1"), "pane-1", { + kind: "diff", + projectId: "project-1", + threadId: "thread-1", + }); + layout = openTab(layout, "pane-1", { + kind: "terminal", + terminalId: "term-1", + target: { kind: "thread", threadId: "thread-1" }, + }); + layout = openTab(layout, "pane-1", { + kind: "thread", + projectId: "project-1", + threadId: "thread-2", + }); + store.set(splitLayoutAtom, layout); + + const result = store.set(closePanesForThreadsAtom, ["thread-1"]); + const next = store.get(splitLayoutAtom)!; + + expect(result).toEqual({ + removedAny: true, + focusedRoute: { projectId: "project-1", threadId: "thread-2" }, + }); + expect( + findContentTab(next.root, { + kind: "thread", + projectId: "project-1", + threadId: "thread-1", + }), + ).toBeNull(); + expect( + findContentTab(next.root, { + kind: "diff", + projectId: "project-1", + threadId: "thread-1", + }), + ).toBeNull(); + expect( + findContentTab(next.root, { kind: "terminal", terminalId: "term-1" }), + ).not.toBeNull(); + }); + + it("keeps a spared terminal when archiving its thread and reports no focused thread route", () => { + const store = createStore(); + const terminal: PaneContent = { + kind: "terminal", + terminalId: "term-1", + target: { kind: "thread", threadId: "thread-1" }, + }; + const layout = openTab(singlePane("thread-1"), "pane-1", terminal); + store.set(splitLayoutAtom, layout); + + const result = store.set(closePanesForThreadsAtom, ["thread-1"]); + const next = store.get(splitLayoutAtom); + + expect(result).toEqual({ removedAny: true, focusedRoute: null }); + expect(next).not.toBeNull(); + expect(findContentTab(next!.root, terminal)).not.toBeNull(); + expect(findPaneByThread(next!.root, "project-1", "thread-1")).toBeNull(); + }); + + it("collapses groups emptied by tab closure and derives the route from the focused active tab", () => { + const store = createStore(); + let layout = twoPanes(); + layout = openTab(layout, "pane-2", { + kind: "thread", + projectId: "project-2", + threadId: "thread-3", + }); + layout = activateTab( + layout, + "pane-2", + findContentTab(layout.root, { + kind: "thread", + projectId: "project-2", + threadId: "thread-3", + })!.tab.tabId, + ); + store.set(splitLayoutAtom, layout); + + const result = store.set(closePanesForThreadsAtom, ["thread-1"]); + const next = store.get(splitLayoutAtom)!; + + expect(countPanes(next.root)).toBe(1); + expect(findPane(next.root, "pane-1")).toBeNull(); + expect(result.focusedRoute).toEqual({ + projectId: "project-2", + threadId: "thread-3", + }); + }); + it("does nothing when there is no layout or no target threads", () => { const store = createStore(); expect(store.set(closePanesForThreadsAtom, ["thread-1"])).toEqual({ diff --git a/apps/app/src/lib/split-layout/atoms.ts b/apps/app/src/lib/split-layout/atoms.ts index b11c761c3..aebd5bfc2 100644 --- a/apps/app/src/lib/split-layout/atoms.ts +++ b/apps/app/src/lib/split-layout/atoms.ts @@ -6,7 +6,13 @@ import { type SyncStorage, } from "@/lib/browser-storage"; import type { ThreadRoutePathArgs } from "@/lib/route-paths"; -import { findPane, listPanes, removePane } from "./ops"; +import { + activePaneContent, + closeTab, + findPane, + listPanes, + listTabs, +} from "./ops"; import { deserializeSplitLayout, serializeSplitLayout, @@ -88,20 +94,24 @@ export const closePanesForThreadsAtom = atom( return { removedAny: false, focusedRoute: null }; } const targets = new Set(threadIds); + // A tab belongs to a targeted thread when it IS the thread view or a view + // bound to it (its diff). Terminal tabs survive: the PTY resource outlives + // the thread view and closing it is a separate decision. + const isTargetedTab = (content: ReturnType) => + (content.kind === "thread" || content.kind === "diff") && + targets.has(content.threadId); let layout = current; let removedAny = false; for (;;) { - const pane = listPanes(layout.root).find( - (candidate) => - candidate.content.kind === "thread" && - targets.has(candidate.content.threadId), + const location = listTabs(layout.root).find(({ tab }) => + isTargetedTab(tab.content), ); - if (pane === undefined) { + if (location === undefined) { break; } - const next = removePane(layout, pane.paneId); + const next = closeTab(layout, location.pane.paneId, location.tab.tabId); if (next === layout) { - // removePane refuses to remove the last pane. + // closeTab refuses to remove the last pane's last tab. break; } layout = next; @@ -118,26 +128,31 @@ export const closePanesForThreadsAtom = atom( ) { set(maximizedPaneIdAtom, null); } - // The surviving focused pane may still be a targeted thread (recursive - // archive covering every pane). Treat that as "no valid survivor": clear the - // layout so a stale pane isn't left behind, and signal the caller to fall - // back to navigating the window away. - const focused = findPane(layout.root, layout.focusedPaneId); - const survivorRoute = - focused !== null && - focused.content.kind === "thread" && - !targets.has(focused.content.threadId) - ? { - projectId: focused.content.projectId, - threadId: focused.content.threadId, - } - : null; - if (survivorRoute === null) { + // Clear the layout only when NOTHING valid survives (a recursive archive + // covering every view). Non-targeted tabs — a spared terminal, another + // thread — keep the layout alive even when the focused view is still a + // targeted thread; the null route then tells the caller to run its + // pre-split navigate-away while the arrangement persists. + const hasValidSurvivor = listTabs(layout.root).some( + ({ tab }) => !isTargetedTab(tab.content), + ); + if (!hasValidSurvivor) { set(splitLayoutAtom, null); set(maximizedPaneIdAtom, null); return { removedAny: true, focusedRoute: null }; } set(splitLayoutAtom, layout); + const focused = findPane(layout.root, layout.focusedPaneId); + const focusedContent = focused === null ? null : activePaneContent(focused); + const survivorRoute = + focusedContent !== null && + focusedContent.kind === "thread" && + !targets.has(focusedContent.threadId) + ? { + projectId: focusedContent.projectId, + threadId: focusedContent.threadId, + } + : null; return { removedAny: true, focusedRoute: survivorRoute }; }, ); diff --git a/apps/app/src/lib/split-layout/computePaneRects.test.ts b/apps/app/src/lib/split-layout/computePaneRects.test.ts index 4ea271385..3f8f57ae7 100644 --- a/apps/app/src/lib/split-layout/computePaneRects.test.ts +++ b/apps/app/src/lib/split-layout/computePaneRects.test.ts @@ -7,7 +7,13 @@ function content(threadId: string): PaneContent { } function pane(paneId: string): LayoutNode { - return { type: "pane", paneId, content: content(paneId) }; + const tabId = `${paneId}-t1`; + return { + type: "pane", + paneId, + tabs: [{ tabId, content: content(paneId), preview: false }], + activeTabId: tabId, + }; } function expectRect(actual: PaneRect | undefined, expected: PaneRect): void { diff --git a/apps/app/src/lib/split-layout/ops.test.ts b/apps/app/src/lib/split-layout/ops.test.ts index 607948d9b..21a611125 100644 --- a/apps/app/src/lib/split-layout/ops.test.ts +++ b/apps/app/src/lib/split-layout/ops.test.ts @@ -1,13 +1,23 @@ import { describe, expect, it } from "vitest"; import { + MAX_TABS_PER_PANE, MAX_PANES, + activateTab, + activePaneContent, + closeTab, + commitTab, + contentMatches, countPanes, + findContentTab, findPane, findPaneByThread, listPanes, + moveTab, movePane, normalize, + openTab, removePane, + reorderTab, replacePaneContent, resizeSplit, setFocus, @@ -21,13 +31,36 @@ function threadContent(threadId: string, projectId = "project-1"): PaneContent { } function pane(paneId: string, threadId = paneId): PaneNode { - return { type: "pane", paneId, content: threadContent(threadId) }; + const tabId = `${paneId}-t1`; + return { + type: "pane", + paneId, + tabs: [{ tabId, content: threadContent(threadId), preview: false }], + activeTabId: tabId, + }; } function singlePaneLayout(): SplitLayout { return { root: pane("pane-1"), focusedPaneId: "pane-1" }; } +function fourTabLayout(): SplitLayout { + let layout = singlePaneLayout(); + for (let index = 2; index <= 4; index += 1) { + layout = openTab(layout, "pane-1", threadContent(`thread-${index}`)); + } + return layout; +} + +function paneThreadOrder(layout: SplitLayout): string[] { + return findPane(layout.root, "pane-1")!.tabs.map((tab) => { + if (tab.content.kind !== "thread") { + throw new Error("Expected thread-only fixture"); + } + return tab.content.threadId; + }); +} + function layoutAtPaneCount(count: number): SplitLayout { let layout = singlePaneLayout(); for (let index = 2; index <= count; index += 1) { @@ -143,8 +176,10 @@ describe("split layout operations", () => { const swapped = swapPanes(replaced, "pane-1", "pane-2"); expect(replaced.focusedPaneId).toBe("pane-1"); - expect(findPane(swapped.root, "pane-2")?.content).toBe(replacement); - expect(findPane(swapped.root, "pane-1")?.content).toEqual( + expect(activePaneContent(findPane(swapped.root, "pane-2")!)).toBe( + replacement, + ); + expect(activePaneContent(findPane(swapped.root, "pane-1")!)).toEqual( threadContent("thread-2"), ); expect(swapped.focusedPaneId).toBe("pane-2"); @@ -186,7 +221,7 @@ describe("split layout operations", () => { expect(countPanes(moved.root)).toBe(MAX_PANES); expect(after).toBe(before); - expect(after?.content).toBe(before?.content); + expect(after?.tabs).toBe(before?.tabs); expect(moved.focusedPaneId).toBe("pane-8"); expectValidFocus(moved); expect(movePane(moved, "pane-8", "pane-8", "right")).toBe(moved); @@ -281,4 +316,422 @@ describe("split layout operations", () => { expectNormalizedSizes(normalized); expectValidFocus(normalized); }); + + it("opens committed tabs after the active tab and reveals an existing tab", () => { + const first = openTab( + singlePaneLayout(), + "pane-1", + threadContent("thread-2"), + ); + const second = openTab(first, "pane-1", threadContent("thread-3")); + const activated = activateTab(second, "pane-1", "pane-1-t1"); + const inserted = openTab(activated, "pane-1", threadContent("thread-4")); + const revealed = openTab(inserted, "pane-1", threadContent("thread-3")); + const openedPane = findPane(inserted.root, "pane-1")!; + + expect(openedPane.tabs.map((tab) => tab.content)).toEqual([ + threadContent("pane-1"), + threadContent("thread-4"), + threadContent("thread-2"), + threadContent("thread-3"), + ]); + expect(openedPane.tabs.every((tab) => !tab.preview)).toBe(true); + expect(findPane(revealed.root, "pane-1")?.tabs).toHaveLength(4); + expect(activePaneContent(findPane(revealed.root, "pane-1")!)).toEqual( + threadContent("thread-3"), + ); + }); + + it("replaces a preview in place, and commits it when reopened non-preview", () => { + const preview = openTab( + singlePaneLayout(), + "pane-1", + threadContent("preview-1"), + { preview: true }, + ); + const previewPane = findPane(preview.root, "pane-1")!; + const previewId = previewPane.activeTabId; + const replaced = openTab(preview, "pane-1", threadContent("preview-2"), { + preview: true, + }); + const replacedPane = findPane(replaced.root, "pane-1")!; + const committed = openTab(replaced, "pane-1", threadContent("preview-2")); + + expect(replacedPane.tabs).toHaveLength(2); + expect(replacedPane.tabs[1]).toEqual({ + tabId: previewId, + content: threadContent("preview-2"), + preview: true, + }); + expect(findPane(committed.root, "pane-1")?.tabs[1]?.preview).toBe(false); + }); + + it("caps a pane at sixteen tabs while still allowing reveal-existing", () => { + let layout = singlePaneLayout(); + for (let index = 2; index <= MAX_TABS_PER_PANE; index += 1) { + layout = openTab(layout, "pane-1", threadContent(`thread-${index}`)); + } + const capped = openTab( + layout, + "pane-1", + threadContent(`thread-${MAX_TABS_PER_PANE + 1}`), + ); + const revealed = openTab(layout, "pane-1", threadContent("thread-2")); + + expect(capped).toBe(layout); + expect(findPane(revealed.root, "pane-1")?.tabs).toHaveLength( + MAX_TABS_PER_PANE, + ); + expect(activePaneContent(findPane(revealed.root, "pane-1")!)).toEqual( + threadContent("thread-2"), + ); + }); + + it("forces terminal opens committed even when preview is requested", () => { + const terminal: PaneContent = { + kind: "terminal", + terminalId: "term-1", + target: { kind: "thread", threadId: "thread-1" }, + }; + const opened = openTab(singlePaneLayout(), "pane-1", terminal, { + preview: true, + }); + const terminalTab = findContentTab(opened.root, terminal)?.tab; + + expect(terminalTab).toMatchObject({ content: terminal, preview: false }); + }); + + it("activates and commits tabs without changing unrelated tab state", () => { + const preview = openTab( + singlePaneLayout(), + "pane-1", + threadContent("thread-2"), + { preview: true }, + ); + const previewId = findPane(preview.root, "pane-1")!.activeTabId; + const activated = activateTab(preview, "pane-1", "pane-1-t1"); + const committed = commitTab(activated, "pane-1", previewId); + + expect(findPane(activated.root, "pane-1")?.activeTabId).toBe("pane-1-t1"); + expect( + findPane(committed.root, "pane-1")?.tabs.find( + (tab) => tab.tabId === previewId, + )?.preview, + ).toBe(false); + expect(committed.focusedPaneId).toBe("pane-1"); + }); + + it("reorders tabs forward and backward while preserving the active tab", () => { + const layout = fourTabLayout(); + const paneBefore = findPane(layout.root, "pane-1")!; + const activeTabId = paneBefore.activeTabId; + const firstTabId = paneBefore.tabs[0]!.tabId; + const lastTabId = paneBefore.tabs[3]!.tabId; + + const forward = reorderTab(layout, "pane-1", firstTabId, 2); + const backward = reorderTab(layout, "pane-1", lastTabId, 1); + + expect(paneThreadOrder(forward)).toEqual([ + "thread-2", + "thread-3", + "pane-1", + "thread-4", + ]); + expect(paneThreadOrder(backward)).toEqual([ + "pane-1", + "thread-4", + "thread-2", + "thread-3", + ]); + expect(findPane(forward.root, "pane-1")?.activeTabId).toBe(activeTabId); + expect(findPane(backward.root, "pane-1")?.activeTabId).toBe(activeTabId); + }); + + it("clamps reorder destinations beyond both ends", () => { + const layout = fourTabLayout(); + const secondTabId = findPane(layout.root, "pane-1")!.tabs[1]!.tabId; + const thirdTabId = findPane(layout.root, "pane-1")!.tabs[2]!.tabId; + + expect( + paneThreadOrder(reorderTab(layout, "pane-1", secondTabId, 999)), + ).toEqual(["pane-1", "thread-3", "thread-4", "thread-2"]); + expect( + paneThreadOrder(reorderTab(layout, "pane-1", thirdTabId, -999)), + ).toEqual(["thread-3", "pane-1", "thread-2", "thread-4"]); + }); + + it("commits a reordered preview and preserves no-op identity", () => { + const preview = openTab( + singlePaneLayout(), + "pane-1", + threadContent("preview"), + { preview: true }, + ); + const previewTab = findContentTab( + preview.root, + threadContent("preview"), + )!.tab; + const reordered = reorderTab(preview, "pane-1", previewTab.tabId, 0); + + expect(paneThreadOrder(reordered)).toEqual(["preview", "pane-1"]); + expect( + findContentTab(reordered.root, threadContent("preview"))?.tab.preview, + ).toBe(false); + expect(findPane(reordered.root, "pane-1")?.activeTabId).toBe( + previewTab.tabId, + ); + + const committed = fourTabLayout(); + const sameIndexTab = findPane(committed.root, "pane-1")!.tabs[1]!; + expect(reorderTab(committed, "pane-1", sameIndexTab.tabId, 1)).toBe( + committed, + ); + expect(reorderTab(committed, "missing", sameIndexTab.tabId, 0)).toBe( + committed, + ); + expect(reorderTab(committed, "pane-1", "missing", 0)).toBe(committed); + }); + + it("closes tabs with index-neighbor fallback, collapses empty panes, and protects the final tab", () => { + const withThree = openTab( + openTab(singlePaneLayout(), "pane-1", threadContent("thread-2")), + "pane-1", + threadContent("thread-3"), + ); + const middleId = findPane(withThree.root, "pane-1")!.tabs[1]!.tabId; + const activated = activateTab(withThree, "pane-1", middleId); + const closedMiddle = closeTab(activated, "pane-1", middleId); + const paneAfterClose = findPane(closedMiddle.root, "pane-1")!; + + expect(activePaneContent(paneAfterClose)).toEqual( + threadContent("thread-3"), + ); + + const split = splitPane( + closedMiddle, + "pane-1", + "right", + threadContent("thread-4"), + ); + const collapsed = closeTab( + split, + "pane-2", + findPane(split.root, "pane-2")!.activeTabId, + ); + expect(countPanes(collapsed.root)).toBe(1); + + const only = singlePaneLayout(); + expect(closeTab(only, "pane-1", "pane-1-t1")).toBe(only); + }); + + it("moves tabs into groups and side splits with commit and collapse semantics", () => { + const split = splitPane( + openTab(singlePaneLayout(), "pane-1", threadContent("preview"), { + preview: true, + }), + "pane-1", + "right", + threadContent("target"), + ); + const previewTab = findContentTab(split.root, threadContent("preview"))!; + const centered = moveTab(split, "pane-1", previewTab.tab.tabId, { + type: "center", + targetPaneId: "pane-2", + }); + const targetPane = findPane(centered.root, "pane-2")!; + + expect(targetPane.tabs.at(-1)).toMatchObject({ + tabId: previewTab.tab.tabId, + preview: false, + }); + expect(targetPane.activeTabId).toBe(previewTab.tab.tabId); + expect(countPanes(centered.root)).toBe(2); + + const sourceLastTabId = findPane(centered.root, "pane-1")!.activeTabId; + const dissolved = moveTab(centered, "pane-1", sourceLastTabId, { + type: "center", + targetPaneId: "pane-2", + }); + expect(countPanes(dissolved.root)).toBe(1); + expect(findPane(dissolved.root, "pane-1")).toBeNull(); + + const movedSide = moveTab(dissolved, "pane-2", previewTab.tab.tabId, { + type: "side", + targetPaneId: "pane-2", + side: "left", + }); + expect(countPanes(movedSide.root)).toBe(2); + expect( + findContentTab(movedSide.root, threadContent("preview"))?.tab.preview, + ).toBe(false); + }); + + it("handles self drops and pane caps according to whether the source survives", () => { + const preview = openTab( + singlePaneLayout(), + "pane-1", + threadContent("preview"), + { preview: true }, + ); + const previewId = findPane(preview.root, "pane-1")!.activeTabId; + const selfCenter = moveTab(preview, "pane-1", previewId, { + type: "center", + targetPaneId: "pane-1", + }); + expect(findPane(selfCenter.root, "pane-1")?.tabs[1]?.preview).toBe(false); + + const only = singlePaneLayout(); + expect( + moveTab(only, "pane-1", "pane-1-t1", { + type: "side", + targetPaneId: "pane-1", + side: "right", + }), + ).toBe(only); + + const atCap = layoutAtPaneCount(MAX_PANES); + const survivingSource = openTab(atCap, "pane-1", threadContent("extra")); + const extraId = findContentTab( + survivingSource.root, + threadContent("extra"), + )!.tab.tabId; + expect( + moveTab(survivingSource, "pane-1", extraId, { + type: "side", + targetPaneId: "pane-2", + side: "left", + }), + ).toBe(survivingSource); + + const lastTabId = findPane(atCap.root, "pane-8")!.activeTabId; + const paneReplacement = moveTab(atCap, "pane-8", lastTabId, { + type: "side", + targetPaneId: "pane-7", + side: "left", + }); + expect(countPanes(paneReplacement.root)).toBe(MAX_PANES); + expect(findPane(paneReplacement.root, "pane-8")).toBeNull(); + }); + + it("normalizes empty panes, invalid active tabs, and duplicate previews", () => { + const malformed: SplitLayout = { + root: { + type: "split", + dir: "row", + sizes: [0.5, 0.5], + children: [ + { ...pane("pane-1"), tabs: [], activeTabId: "missing" }, + { + ...pane("pane-2"), + activeTabId: "missing", + tabs: [ + { tabId: "a", content: threadContent("a"), preview: true }, + { tabId: "b", content: threadContent("b"), preview: true }, + ], + }, + ], + }, + focusedPaneId: "pane-1", + }; + + const normalized = normalize(malformed); + const survivor = findPane(normalized.root, "pane-2")!; + expect(normalized.root.type).toBe("pane"); + expect(normalized.focusedPaneId).toBe("pane-2"); + expect(survivor.activeTabId).toBe("a"); + expect(survivor.tabs.map((tab) => tab.preview)).toEqual([true, false]); + }); + + it("normalizes terminal tabs to committed state", () => { + const terminal: PaneContent = { + kind: "terminal", + terminalId: "term-1", + target: { kind: "thread", threadId: "thread-1" }, + }; + const malformed: SplitLayout = { + root: { + ...pane("pane-1"), + tabs: [ + { + tabId: "pane-1-t1", + content: terminal, + preview: true, + }, + ], + }, + focusedPaneId: "pane-1", + }; + + const normalized = normalize(malformed); + expect(findContentTab(normalized.root, terminal)?.tab.preview).toBe(false); + }); + + it("trims excess tabs to sixteen without dropping the active tab", () => { + const activeTabId = `tab-${MAX_TABS_PER_PANE + 2}`; + const malformed: SplitLayout = { + root: { + type: "pane", + paneId: "pane-1", + tabs: Array.from({ length: MAX_TABS_PER_PANE + 2 }, (_, index) => ({ + tabId: `tab-${index + 1}`, + content: threadContent(`thread-${index + 1}`), + preview: false, + })), + activeTabId, + }, + focusedPaneId: "pane-1", + }; + + const normalized = normalize(malformed); + const normalizedPane = findPane(normalized.root, "pane-1")!; + expect(normalizedPane.tabs).toHaveLength(MAX_TABS_PER_PANE); + expect(normalizedPane.activeTabId).toBe(activeTabId); + expect(normalizedPane.tabs.at(-1)?.tabId).toBe(activeTabId); + }); + + it("matches view identity by kind, including terminals independent of target", () => { + const terminal = { + kind: "terminal", + terminalId: "term-1", + target: { kind: "thread", threadId: "thread-1" }, + } as const; + const layout = openTab(singlePaneLayout(), "pane-1", terminal); + + expect( + contentMatches(terminal, { + kind: "terminal", + terminalId: "term-1", + target: { kind: "environment", environmentId: "env-1" }, + }), + ).toBe(true); + expect( + findContentTab(layout.root, { kind: "terminal", terminalId: "term-1" }) + ?.tab.content, + ).toEqual(terminal); + expect( + contentMatches(threadContent("same", "p1"), threadContent("same", "p2")), + ).toBe(false); + expect( + contentMatches( + { kind: "diff", projectId: "p1", threadId: "same" }, + { kind: "diff", projectId: "p2", threadId: "same" }, + ), + ).toBe(false); + expect( + contentMatches( + { + kind: "plugin-panel", + pluginId: "notes", + panelPath: "files", + subPath: "a", + }, + { + kind: "plugin-panel", + pluginId: "notes", + panelPath: "files", + subPath: "b", + }, + ), + ).toBe(true); + }); }); diff --git a/apps/app/src/lib/split-layout/ops.ts b/apps/app/src/lib/split-layout/ops.ts index d0492ffac..9a538e138 100644 --- a/apps/app/src/lib/split-layout/ops.ts +++ b/apps/app/src/lib/split-layout/ops.ts @@ -2,6 +2,7 @@ import type { LayoutNode, PaneContent, PaneNode, + PaneTab, SplitLayout, SplitNode, SplitPath, @@ -9,6 +10,10 @@ import type { } from "./types"; export const MAX_PANES = 8; +// A sanity bound, not a UX budget: callers must still handle the no-op (and +// not navigate as if the open succeeded), but the bound is high enough that +// real usage never hits it. +export const MAX_TABS_PER_PANE = 16; const MIN_SIZE = 0.15; const MAX_SIZE = 0.85; @@ -45,48 +50,99 @@ export function findPane(root: LayoutNode, paneId: string): PaneNode | null { return null; } +/** The tab a pane currently renders. Normalized layouts always have a valid + * active tab; the first-tab fallback covers transiently inconsistent input. */ +export function activeTab(pane: PaneNode): PaneTab { + const active = pane.tabs.find((tab) => tab.tabId === pane.activeTabId); + const fallback = pane.tabs[0]; + if (active !== undefined) { + return active; + } + if (fallback === undefined) { + throw new Error("A pane must hold at least one tab"); + } + return fallback; +} + +export function activePaneContent(pane: PaneNode): PaneContent { + return activeTab(pane).content; +} + +export interface TabLocation { + pane: PaneNode; + tab: PaneTab; +} + +export function listTabs(root: LayoutNode): TabLocation[] { + return listPanes(root).flatMap((pane) => + pane.tabs.map((tab) => ({ pane, tab })), + ); +} + +/** + * View identity per content kind: two contents match when opening the second + * should reveal the first instead of creating another view. Threads and diffs + * are one-view-per-thread, terminals one-view-per-terminal, plugin panels one + * view per panel (subpaths are navigation within it), and the compose page is + * a singleton. + */ +export function contentMatches(a: PaneContent, b: PaneContent): boolean { + if (a.kind !== b.kind) { + return false; + } + switch (a.kind) { + case "new-thread": + return true; + case "thread": + return ( + b.kind === "thread" && + a.projectId === b.projectId && + a.threadId === b.threadId + ); + case "plugin-panel": + return ( + b.kind === "plugin-panel" && + a.pluginId === b.pluginId && + a.panelPath === b.panelPath + ); + case "terminal": + return b.kind === "terminal" && a.terminalId === b.terminalId; + case "diff": + return ( + b.kind === "diff" && + a.projectId === b.projectId && + a.threadId === b.threadId + ); + } +} + +/** Finds the tab whose content is the same view as `content`, anywhere in the + * tree, for reveal-existing semantics. */ +export function findContentTab( + root: LayoutNode, + content: PaneContent, +): TabLocation | null { + return ( + listTabs(root).find(({ tab }) => contentMatches(tab.content, content)) ?? + null + ); +} + export function findPaneByThread( root: LayoutNode, projectId: string, threadId: string, ): PaneNode | null { return ( - listPanes(root).find( - (pane) => - pane.content.kind === "thread" && - pane.content.projectId === projectId && - pane.content.threadId === threadId, - ) ?? null + findContentTab(root, { kind: "thread", projectId, threadId })?.pane ?? null ); } -/** Finds the pane representing the same routable page as `content`. Plugin - * subpaths belong to one panel identity, so navigating within a panel updates - * that pane instead of opening duplicates. The compose page is a singleton in - * this prototype, matching its existing shared draft/project state. */ export function findPaneByContent( root: LayoutNode, content: PaneContent, ): PaneNode | null { - return ( - listPanes(root).find((pane) => { - const candidate = pane.content; - if (candidate.kind !== content.kind) return false; - if (content.kind === "new-thread") return true; - if (content.kind === "thread") { - return ( - candidate.kind === "thread" && - candidate.projectId === content.projectId && - candidate.threadId === content.threadId - ); - } - return ( - candidate.kind === "plugin-panel" && - candidate.pluginId === content.pluginId && - candidate.panelPath === content.panelPath - ); - }) ?? null - ); + return findContentTab(root, content)?.pane ?? null; } function replacePaneNode( @@ -105,13 +161,40 @@ function replacePaneNode( }; } -function nextPaneId(root: LayoutNode): string { - const existingIds = new Set(listPanes(root).map((pane) => pane.paneId)); +function nextSequenceId(existingIds: ReadonlySet, prefix: string) { let sequence = 1; - while (existingIds.has(`pane-${sequence}`)) { + while (existingIds.has(`${prefix}-${sequence}`)) { sequence += 1; } - return `pane-${sequence}`; + return `${prefix}-${sequence}`; +} + +function nextPaneId(root: LayoutNode): string { + return nextSequenceId( + new Set(listPanes(root).map((pane) => pane.paneId)), + "pane", + ); +} + +function nextTabId(root: LayoutNode): string { + return nextSequenceId( + new Set(listTabs(root).map(({ tab }) => tab.tabId)), + "tab", + ); +} + +export function createPaneNode( + paneId: string, + tabId: string, + content: PaneContent, + options?: { preview?: boolean }, +): PaneNode { + return { + type: "pane", + paneId, + tabs: [{ tabId, content, preview: options?.preview === true }], + activeTabId: tabId, + }; } function splitDirection(side: SplitSide): SplitNode["dir"] { @@ -179,17 +262,19 @@ export function splitPane( ) { return layout; } - const pane: PaneNode = { - type: "pane", - paneId: nextPaneId(layout.root), + const pane = createPaneNode( + nextPaneId(layout.root), + nextTabId(layout.root), content, - }; + ); return { root: insertPane(layout.root, targetPaneId, side, pane), focusedPaneId: pane.paneId, }; } +/** Replaces the pane's ACTIVE tab content in place: navigation within a view + * (e.g. a thread pane navigating to another thread) rather than a new tab. */ export function replacePaneContent( layout: SplitLayout, paneId: string, @@ -199,12 +284,311 @@ export function replacePaneContent( if (pane === null) { return layout; } + const active = activeTab(pane); + const nextPane: PaneNode = { + ...pane, + tabs: pane.tabs.map((tab) => + tab.tabId === active.tabId ? { ...tab, content } : tab, + ), + activeTabId: active.tabId, + }; + return { + root: replacePaneNode(layout.root, paneId, nextPane), + focusedPaneId: paneId, + }; +} + +export interface OpenTabOptions { + /** Open as the group's preview tab (replacing its current preview). */ + preview?: boolean; +} + +/** + * Opens `content` as a tab in `paneId`. If the same view already exists in the + * target pane it is revealed (activated) instead of duplicated — callers + * wanting tree-wide reveal check {@link findContentTab} first. A preview open + * replaces the group's existing preview tab in its slot; a committed open + * appends after the active tab. + */ +export function openTab( + layout: SplitLayout, + paneId: string, + content: PaneContent, + options?: OpenTabOptions, +): SplitLayout { + const pane = findPane(layout.root, paneId); + if (pane === null) { + return layout; + } + // Stateful views never preview: a preview slot is silently destroyed by the + // next single-click, which must not tear down a PTY view. + const preview = options?.preview === true && content.kind !== "terminal"; + + const existing = pane.tabs.find((tab) => contentMatches(tab.content, content)); + if (existing !== undefined) { + const nextPane: PaneNode = { + ...pane, + // Re-opening a preview tab's view as committed commits it in place. + tabs: pane.tabs.map((tab) => + tab.tabId === existing.tabId && !preview && tab.preview + ? { ...tab, preview: false } + : tab, + ), + activeTabId: existing.tabId, + }; + return { + root: replacePaneNode(layout.root, paneId, nextPane), + focusedPaneId: paneId, + }; + } + + if (preview) { + const previewIndex = pane.tabs.findIndex((tab) => tab.preview); + if (previewIndex !== -1) { + const previewTab = pane.tabs[previewIndex]; + if (previewTab === undefined) { + return layout; + } + const nextPane: PaneNode = { + ...pane, + tabs: pane.tabs.map((tab, index) => + index === previewIndex ? { ...tab, content } : tab, + ), + activeTabId: previewTab.tabId, + }; + return { + root: replacePaneNode(layout.root, paneId, nextPane), + focusedPaneId: paneId, + }; + } + } + + if (pane.tabs.length >= MAX_TABS_PER_PANE) { + return layout; + } + const tabId = nextTabId(layout.root); + const activeIndex = pane.tabs.findIndex( + (tab) => tab.tabId === pane.activeTabId, + ); + const insertionIndex = + activeIndex === -1 ? pane.tabs.length : activeIndex + 1; + const tabs = [...pane.tabs]; + tabs.splice(insertionIndex, 0, { tabId, content, preview }); + const nextPane: PaneNode = { ...pane, tabs, activeTabId: tabId }; + return { + root: replacePaneNode(layout.root, paneId, nextPane), + focusedPaneId: paneId, + }; +} + +export function activateTab( + layout: SplitLayout, + paneId: string, + tabId: string, +): SplitLayout { + const pane = findPane(layout.root, paneId); + if (pane === null || !pane.tabs.some((tab) => tab.tabId === tabId)) { + return layout; + } return { - root: replacePaneNode(layout.root, paneId, { ...pane, content }), + root: replacePaneNode(layout.root, paneId, { ...pane, activeTabId: tabId }), focusedPaneId: paneId, }; } +/** Commits a preview tab (double-click / drag gestures). */ +export function commitTab( + layout: SplitLayout, + paneId: string, + tabId: string, +): SplitLayout { + const pane = findPane(layout.root, paneId); + if (pane === null) { + return layout; + } + const target = pane.tabs.find((tab) => tab.tabId === tabId); + if (target === undefined || !target.preview) { + return layout; + } + const nextPane: PaneNode = { + ...pane, + tabs: pane.tabs.map((tab) => + tab.tabId === tabId ? { ...tab, preview: false } : tab, + ), + }; + return { ...layout, root: replacePaneNode(layout.root, paneId, nextPane) }; +} + +/** + * Closes a tab. Closing a pane's last tab removes the pane (unless it is the + * layout's only pane, which is refused like {@link removePane}). The active + * tab falls to the neighbor that took the closed tab's index. + */ +export function closeTab( + layout: SplitLayout, + paneId: string, + tabId: string, +): SplitLayout { + const pane = findPane(layout.root, paneId); + if (pane === null) { + return layout; + } + const tabIndex = pane.tabs.findIndex((tab) => tab.tabId === tabId); + if (tabIndex === -1) { + return layout; + } + if (pane.tabs.length === 1) { + return removePane(layout, paneId); + } + const tabs = pane.tabs.filter((tab) => tab.tabId !== tabId); + const fallbackTab = tabs[Math.min(tabIndex, tabs.length - 1)]; + const nextPane: PaneNode = { + ...pane, + tabs, + activeTabId: + pane.activeTabId === tabId && fallbackTab !== undefined + ? fallbackTab.tabId + : pane.activeTabId, + }; + return { ...layout, root: replacePaneNode(layout.root, paneId, nextPane) }; +} + +/** + * Reorders a tab within its own group. Dragging is a commit gesture, so the + * moved tab loses any preview state. `toIndex` is clamped to the tab list. + */ +export function reorderTab( + layout: SplitLayout, + paneId: string, + tabId: string, + toIndex: number, +): SplitLayout { + const pane = findPane(layout.root, paneId); + if (pane === null || !Number.isInteger(toIndex)) { + return layout; + } + const fromIndex = pane.tabs.findIndex((tab) => tab.tabId === tabId); + if (fromIndex === -1) { + return layout; + } + const clampedIndex = Math.min(Math.max(toIndex, 0), pane.tabs.length - 1); + const moved = pane.tabs[fromIndex]; + if (moved === undefined) { + return layout; + } + if (clampedIndex === fromIndex && !moved.preview) { + return layout; + } + const tabs = pane.tabs.filter((tab) => tab.tabId !== tabId); + tabs.splice(clampedIndex, 0, { ...moved, preview: false }); + return { + ...layout, + root: replacePaneNode(layout.root, paneId, { ...pane, tabs }), + }; +} + +export type TabDropTarget = + | { type: "center"; targetPaneId: string } + | { type: "side"; targetPaneId: string; side: SplitSide }; + +/** + * Moves a tab between groups (center drop) or out into a new split (side + * drop). Dragging always commits a preview tab. Moving a pane's last tab to a + * side of itself is a no-op; moving it elsewhere dissolves the source pane. + */ +export function moveTab( + layout: SplitLayout, + sourcePaneId: string, + tabId: string, + target: TabDropTarget, +): SplitLayout { + const sourcePane = findPane(layout.root, sourcePaneId); + const tab = sourcePane?.tabs.find((candidate) => candidate.tabId === tabId); + if (sourcePane === undefined || sourcePane === null || tab === undefined) { + return layout; + } + const movedTab: PaneTab = { ...tab, preview: false }; + const sourceEmptiesOut = sourcePane.tabs.length === 1; + + if (target.type === "center") { + if (target.targetPaneId === sourcePaneId) { + // Same-group center drop just commits (reorder is out of scope for v1). + return commitTab(layout, sourcePaneId, tabId); + } + const targetPane = findPane(layout.root, target.targetPaneId); + if ( + targetPane === null || + targetPane.tabs.length >= MAX_TABS_PER_PANE + ) { + return layout; + } + let root = replacePaneNode(layout.root, target.targetPaneId, { + ...targetPane, + tabs: [...targetPane.tabs, movedTab], + activeTabId: movedTab.tabId, + }); + root = withTabRemoved(root, sourcePaneId, tabId); + const collapsed = sourceEmptiesOut + ? dropEmptyPane(root, sourcePaneId) + : root; + if (collapsed === null) { + return layout; + } + return { root: collapsed, focusedPaneId: target.targetPaneId }; + } + + if (target.targetPaneId === sourcePaneId && sourceEmptiesOut) { + return layout; + } + if (!sourceEmptiesOut && countPanes(layout.root) >= MAX_PANES) { + return layout; + } + const newPane: PaneNode = { + type: "pane", + paneId: nextPaneId(layout.root), + tabs: [movedTab], + activeTabId: movedTab.tabId, + }; + let root = withTabRemoved(layout.root, sourcePaneId, tabId); + const collapsed = sourceEmptiesOut ? dropEmptyPane(root, sourcePaneId) : root; + if (collapsed === null) { + return layout; + } + root = insertPane(collapsed, target.targetPaneId, target.side, newPane); + return { root, focusedPaneId: newPane.paneId }; +} + +function withTabRemoved( + root: LayoutNode, + paneId: string, + tabId: string, +): LayoutNode { + const pane = findPane(root, paneId); + if (pane === null) { + return root; + } + const tabIndex = pane.tabs.findIndex((tab) => tab.tabId === tabId); + if (tabIndex === -1) { + return root; + } + const tabs = pane.tabs.filter((tab) => tab.tabId !== tabId); + const fallbackTab = tabs[Math.min(tabIndex, tabs.length - 1)]; + return replacePaneNode(root, paneId, { + ...pane, + tabs, + activeTabId: + pane.activeTabId === tabId && fallbackTab !== undefined + ? fallbackTab.tabId + : pane.activeTabId, + }); +} + +/** Detaches a now-empty pane and collapses the tree; null when it can't. */ +function dropEmptyPane(root: LayoutNode, paneId: string): LayoutNode | null { + const result = detachPane(root, paneId); + return result.detached === null ? root : result.node; +} + interface DetachResult { node: LayoutNode | null; detached: PaneNode | null; @@ -315,12 +699,14 @@ export function swapPanes( } const withFirstSwap = replacePaneNode(layout.root, paneId, { ...pane, - content: targetPane.content, + tabs: targetPane.tabs, + activeTabId: targetPane.activeTabId, }); return { root: replacePaneNode(withFirstSwap, targetPaneId, { ...targetPane, - content: pane.content, + tabs: pane.tabs, + activeTabId: pane.activeTabId, }), focusedPaneId: targetPaneId, }; @@ -464,18 +850,61 @@ export function setFocus(layout: SplitLayout, paneId: string): SplitLayout { return { ...layout, focusedPaneId: paneId }; } -function normalizeNode(node: LayoutNode): LayoutNode { +function normalizePane(pane: PaneNode): PaneNode | null { + if (pane.tabs.length === 0) { + return null; + } + // Terminals are never previews; otherwise at most one preview per group + // (later duplicates commit). + let sawPreview = false; + let tabs = pane.tabs.map((tab) => { + if (!tab.preview) { + return tab; + } + if (tab.content.kind === "terminal" || sawPreview) { + return { ...tab, preview: false }; + } + sawPreview = true; + return tab; + }); + if (tabs.length > MAX_TABS_PER_PANE) { + // Trim from the end, but never drop the active tab. + const activeIndex = tabs.findIndex( + (tab) => tab.tabId === pane.activeTabId, + ); + const kept = tabs.slice(0, MAX_TABS_PER_PANE); + const active = activeIndex >= MAX_TABS_PER_PANE ? tabs[activeIndex] : null; + if (active != null) { + kept[MAX_TABS_PER_PANE - 1] = active; + } + tabs = kept; + } + const activeTabId = tabs.some((tab) => tab.tabId === pane.activeTabId) + ? pane.activeTabId + : (tabs[0]?.tabId ?? pane.activeTabId); + return { ...pane, tabs, activeTabId }; +} + +function normalizeNode(node: LayoutNode): LayoutNode | null { if (node.type === "pane") { - return { ...node }; + return normalizePane(node); + } + const children = node.children + .map(normalizeNode) + .filter((child): child is LayoutNode => child !== null); + if (children.length === 0) { + return null; } - const children = node.children.map(normalizeNode); if (children.length === 1) { - return children[0] ?? node; + return children[0] ?? null; } return { ...node, children, - sizes: normalizeSizes(node.sizes, children.length), + sizes: + children.length === node.children.length + ? normalizeSizes(node.sizes, children.length) + : equalSizes(children.length), }; } @@ -496,7 +925,11 @@ function trimToPaneLimit(root: LayoutNode): LayoutNode { } export function normalize(layout: SplitLayout): SplitLayout { - const root = normalizeNode(trimToPaneLimit(layout.root)); + const normalizedRoot = normalizeNode(layout.root); + if (normalizedRoot === null) { + return layout; + } + const root = trimToPaneLimit(normalizedRoot); const panes = listPanes(root); const focusedPaneId = panes.some( (pane) => pane.paneId === layout.focusedPaneId, diff --git a/apps/app/src/lib/split-layout/persistence.test.ts b/apps/app/src/lib/split-layout/persistence.test.ts index bd796215d..26dcddf39 100644 --- a/apps/app/src/lib/split-layout/persistence.test.ts +++ b/apps/app/src/lib/split-layout/persistence.test.ts @@ -4,7 +4,18 @@ import { serializeSplitLayout, SPLIT_LAYOUT_SCHEMA_VERSION, } from "./persistence"; -import type { SplitLayout } from "./types"; +import { MAX_TABS_PER_PANE } from "./ops"; +import type { PaneContent, PaneNode, SplitLayout } from "./types"; + +function pane(paneId: string, content: PaneContent): PaneNode { + const tabId = `${paneId}-t1`; + return { + type: "pane", + paneId, + tabs: [{ tabId, content, preview: false }], + activeTabId: tabId, + }; +} function layoutWithPaneCount(count: number): SplitLayout { return { @@ -12,15 +23,13 @@ function layoutWithPaneCount(count: number): SplitLayout { type: "split", dir: "row", sizes: Array.from({ length: count }, () => 1 / count), - children: Array.from({ length: count }, (_, index) => ({ - type: "pane" as const, - paneId: `pane-${index + 1}`, - content: { + children: Array.from({ length: count }, (_, index) => + pane(`pane-${index + 1}`, { kind: "thread" as const, projectId: "project-1", threadId: `thread-${index + 1}`, - }, - })), + }), + ), }, focusedPaneId: `pane-${count}`, }; @@ -32,24 +41,16 @@ const layout: SplitLayout = { dir: "row", sizes: [0.4, 0.6], children: [ - { - type: "pane", - paneId: "pane-1", - content: { - kind: "thread", - projectId: "project-1", - threadId: "thread-1", - }, - }, - { - type: "pane", - paneId: "pane-2", - content: { - kind: "thread", - projectId: "project-2", - threadId: "thread-2", - }, - }, + pane("pane-1", { + kind: "thread", + projectId: "project-1", + threadId: "thread-1", + }), + pane("pane-2", { + kind: "thread", + projectId: "project-2", + threadId: "thread-2", + }), ], }, focusedPaneId: "pane-2", @@ -65,7 +66,7 @@ describe("split layout persistence", () => { expect(deserializeSplitLayout(serialized)).toEqual(layout); }); - it("round-trips mixed new-thread and plugin panel content", () => { + it("round-trips tabs, preview state, terminal targets, and mixed content", () => { const mixed: SplitLayout = { root: { type: "split", @@ -73,20 +74,40 @@ describe("split layout persistence", () => { sizes: [0.5, 0.5], children: [ { - type: "pane", - paneId: "pane-1", - content: { kind: "new-thread" }, - }, - { - type: "pane", - paneId: "pane-2", - content: { - kind: "plugin-panel", - pluginId: "notes", - panelPath: "notes", - subPath: "work/today.md", - }, + ...pane("pane-1", { kind: "new-thread" }), + tabs: [ + { + tabId: "pane-1-t1", + content: { kind: "new-thread" }, + preview: false, + }, + { + tabId: "pane-1-t2", + content: { + kind: "terminal", + terminalId: "term-1", + target: { kind: "thread", threadId: "thread-1" }, + }, + preview: false, + }, + { + tabId: "pane-1-t3", + content: { + kind: "diff", + projectId: "project-1", + threadId: "thread-1", + }, + preview: true, + }, + ], + activeTabId: "pane-1-t3", }, + pane("pane-2", { + kind: "plugin-panel", + pluginId: "notes", + panelPath: "notes", + subPath: "work/today.md", + }), ], }, focusedPaneId: "pane-2", @@ -132,4 +153,148 @@ describe("split layout persistence", () => { deserializeSplitLayout(serializeSplitLayout(layoutWithPaneCount(9))), ).toBeNull(); }); + + it("deliberately rejects version-one single-content pane payloads", () => { + expect( + deserializeSplitLayout( + JSON.stringify({ + version: 1, + layout: { + root: { + type: "pane", + paneId: "pane-1", + content: { + kind: "thread", + projectId: "project-1", + threadId: "thread-1", + }, + }, + focusedPaneId: "pane-1", + }, + }), + ), + ).toBeNull(); + }); + + it("rejects invalid tab-group invariants and terminals without targets", () => { + const single = pane("pane-1", { + kind: "thread", + projectId: "project-1", + threadId: "thread-1", + }); + const stored = (root: unknown) => + JSON.stringify({ + version: SPLIT_LAYOUT_SCHEMA_VERSION, + layout: { root, focusedPaneId: "pane-1" }, + }); + + expect( + deserializeSplitLayout(stored({ ...single, activeTabId: "missing" })), + ).toBeNull(); + expect( + deserializeSplitLayout( + stored({ + ...single, + tabs: [ + { ...single.tabs[0], preview: true }, + { + tabId: "pane-1-t2", + content: { kind: "new-thread" }, + preview: true, + }, + ], + }), + ), + ).toBeNull(); + expect(deserializeSplitLayout(stored({ ...single, tabs: [] }))).toBeNull(); + expect( + deserializeSplitLayout( + stored({ + ...single, + tabs: Array.from({ length: MAX_TABS_PER_PANE + 1 }, (_, index) => ({ + tabId: `tab-${index}`, + content: { kind: "new-thread" }, + preview: false, + })), + activeTabId: "tab-0", + }), + ), + ).toBeNull(); + expect( + deserializeSplitLayout( + stored({ + ...single, + tabs: [ + { + tabId: "pane-1-t1", + content: { kind: "terminal", terminalId: "term-1" }, + preview: false, + }, + ], + }), + ), + ).toBeNull(); + expect( + deserializeSplitLayout( + stored({ + ...single, + tabs: [ + { + tabId: "pane-1-t1", + content: { + kind: "terminal", + terminalId: "term-1", + target: { kind: "thread", threadId: "thread-1" }, + }, + preview: true, + }, + ], + }), + ), + ).toBeNull(); + expect( + deserializeSplitLayout( + JSON.stringify({ + version: SPLIT_LAYOUT_SCHEMA_VERSION, + layout: { + root: { + type: "split", + dir: "row", + sizes: [0.5, 0.5], + children: [single, { ...single, paneId: "pane-2" }], + }, + focusedPaneId: "pane-1", + }, + }), + ), + ).toBeNull(); + expect( + deserializeSplitLayout( + JSON.stringify({ + version: SPLIT_LAYOUT_SCHEMA_VERSION, + layout: { + root: { + type: "split", + dir: "row", + sizes: [0.5, 0.5], + children: [ + single, + { + ...single, + tabs: [ + { + ...single.tabs[0], + tabId: "pane-2-t1", + }, + ], + activeTabId: "pane-2-t1", + }, + ], + }, + focusedPaneId: "pane-1", + }, + }), + ), + ).toBeNull(); + }); }); diff --git a/apps/app/src/lib/split-layout/persistence.ts b/apps/app/src/lib/split-layout/persistence.ts index 841c23f9c..3c990c428 100644 --- a/apps/app/src/lib/split-layout/persistence.ts +++ b/apps/app/src/lib/split-layout/persistence.ts @@ -1,8 +1,10 @@ import { z } from "zod"; -import { MAX_PANES, countPanes, listPanes } from "./ops"; +import { MAX_PANES, MAX_TABS_PER_PANE, countPanes, listPanes } from "./ops"; import type { LayoutNode, PaneNode, SplitLayout, SplitNode } from "./types"; -export const SPLIT_LAYOUT_SCHEMA_VERSION = 1; +/** v2: leaves are tab groups. v1 (single-content panes) deserializes to null — + * a deliberate fresh start, matching the reader's "seed from the route". */ +export const SPLIT_LAYOUT_SCHEMA_VERSION = 2; export const SPLIT_LAYOUT_STORAGE_KEY = "bb.splitLayout"; const paneContentSchema = z.discriminatedUnion("kind", [ @@ -22,15 +24,80 @@ const paneContentSchema = z.discriminatedUnion("kind", [ subPath: z.string(), }) .strict(), + z + .object({ + kind: z.literal("terminal"), + terminalId: z.string().min(1), + target: z.discriminatedUnion("kind", [ + z + .object({ kind: z.literal("thread"), threadId: z.string().min(1) }) + .strict(), + z + .object({ + kind: z.literal("environment"), + environmentId: z.string().min(1), + }) + .strict(), + z + .object({ + kind: z.literal("host_path"), + hostId: z.string().min(1), + cwd: z.string().min(1), + }) + .strict(), + ]), + }) + .strict(), + z + .object({ + kind: z.literal("diff"), + projectId: z.string().min(1), + threadId: z.string().min(1), + }) + .strict(), ]); +const paneTabSchema = z + .object({ + tabId: z.string().min(1), + content: paneContentSchema, + preview: z.boolean(), + }) + .strict() + .superRefine((tab, context) => { + if (tab.preview && tab.content.kind === "terminal") { + context.addIssue({ + code: "custom", + message: "Terminal tabs are never previews", + path: ["preview"], + }); + } + }); + const paneNodeSchema: z.ZodType = z .object({ type: z.literal("pane"), paneId: z.string().min(1), - content: paneContentSchema, + tabs: z.array(paneTabSchema).min(1).max(MAX_TABS_PER_PANE), + activeTabId: z.string().min(1), }) - .strict(); + .strict() + .superRefine((pane, context) => { + if (!pane.tabs.some((tab) => tab.tabId === pane.activeTabId)) { + context.addIssue({ + code: "custom", + message: "The active tab must exist in the pane", + path: ["activeTabId"], + }); + } + if (pane.tabs.filter((tab) => tab.preview).length > 1) { + context.addIssue({ + code: "custom", + message: "A pane holds at most one preview tab", + path: ["tabs"], + }); + } + }); const layoutNodeSchema: z.ZodType = z.lazy(() => z.union([paneNodeSchema, splitNodeSchema]), @@ -91,6 +158,14 @@ const splitLayoutSchema: z.ZodType = z path: ["root"], }); } + const tabIds = panes.flatMap((pane) => pane.tabs.map((tab) => tab.tabId)); + if (new Set(tabIds).size !== tabIds.length) { + context.addIssue({ + code: "custom", + message: "Tab IDs must be unique", + path: ["root"], + }); + } }); const storedSplitLayoutSchema = z diff --git a/apps/app/src/lib/split-layout/types.ts b/apps/app/src/lib/split-layout/types.ts index 36923f5f1..a3cda0089 100644 --- a/apps/app/src/lib/split-layout/types.ts +++ b/apps/app/src/lib/split-layout/types.ts @@ -1,3 +1,9 @@ +/** Mirrors the terminal-session target scopes in `@bb/server-contract`. */ +export type TerminalPaneTarget = + | { kind: "thread"; threadId: string } + | { kind: "environment"; environmentId: string } + | { kind: "host_path"; hostId: string; cwd: string }; + export type PaneContent = | { kind: "thread"; @@ -12,12 +18,45 @@ export type PaneContent = pluginId: string; panelPath: string; subPath: string; + } + | { + kind: "terminal"; + terminalId: string; + /** Explicit context binding, fixed at creation: which scope owns the + * PTY. The pane needs it to look up its session (sessions list by + * scope, not id) and to badge its chrome. Absent only on URL-derived + * content (`/terminals/:id` can't carry it) — reveal intents that never + * open a new tab; persisted tabs always carry it. */ + target?: TerminalPaneTarget; + } + | { + kind: "diff"; + projectId: string; + threadId: string; }; +/** + * One view in a tab group. `preview` marks the group's at-most-one preview tab + * (VS Code-style): the next preview open replaces it in place, and commit + * gestures (double-click, drag) clear the flag. Stateful kinds (terminal) + * never carry it. + */ +export interface PaneTab { + tabId: string; + content: PaneContent; + preview: boolean; +} + +/** + * A leaf of the layout tree: a tab group holding one or more views. The pane + * renders its active tab; `paneId` is the stable identity drag/focus/commands + * address, `tabId` addresses a view within it. + */ export interface PaneNode { type: "pane"; paneId: string; - content: PaneContent; + tabs: PaneTab[]; + activeTabId: string; } export interface SplitNode { diff --git a/apps/app/src/views/SplitWorkspaceRoute.tsx b/apps/app/src/views/SplitWorkspaceRoute.tsx index 806101cc9..0194269b6 100644 --- a/apps/app/src/views/SplitWorkspaceRoute.tsx +++ b/apps/app/src/views/SplitWorkspaceRoute.tsx @@ -1,9 +1,13 @@ import { useMemo } from "react"; import { matchPath, Navigate, useLocation } from "react-router-dom"; +import { PERSONAL_PROJECT_ID } from "@bb/domain"; import { APP_ROOT_ROUTE_PATH, LEGACY_PROJECT_COMPOSE_ROUTE_PATH, PLUGIN_PANEL_ROUTE_PATH, + PROJECTLESS_THREAD_DIFF_ROUTE_PATH, + TERMINAL_ROUTE_PATH, + THREAD_DIFF_ROUTE_PATH, } from "@/lib/route-paths"; import type { PaneContent } from "@/lib/split-layout"; import { useRouteState } from "@/hooks/useRouteState"; @@ -27,14 +31,31 @@ export default function SplitWorkspaceRoute() { LEGACY_PROJECT_COMPOSE_ROUTE_PATH, location.pathname, ); + const terminalMatch = matchPath(TERMINAL_ROUTE_PATH, location.pathname); + const projectDiffMatch = matchPath(THREAD_DIFF_ROUTE_PATH, location.pathname); + const projectlessDiffMatch = matchPath( + PROJECTLESS_THREAD_DIFF_ROUTE_PATH, + location.pathname, + ); const pluginId = pluginMatch?.params.pluginId; const panelPath = pluginMatch?.params.panelPath; const pluginSubPath = pluginMatch?.params["*"] ?? ""; + const terminalId = terminalMatch?.params.terminalId; + const diffThreadId = + projectDiffMatch?.params.threadId ?? projectlessDiffMatch?.params.threadId; + const diffProjectId = + projectDiffMatch?.params.projectId ?? PERSONAL_PROJECT_ID; const routeContent = useMemo(() => { if (location.pathname === APP_ROOT_ROUTE_PATH) { return ROOT_COMPOSE_CONTENT; } + if (terminalId) { + return { kind: "terminal", terminalId }; + } + if (diffThreadId) { + return { kind: "diff", projectId: diffProjectId, threadId: diffThreadId }; + } if (isThreadView && projectId && threadId) { return { kind: "thread", projectId, threadId }; } @@ -48,12 +69,15 @@ export default function SplitWorkspaceRoute() { } return null; }, [ + diffProjectId, + diffThreadId, isThreadView, location.pathname, panelPath, pluginId, pluginSubPath, projectId, + terminalId, threadId, ]); diff --git a/apps/app/src/views/thread-detail/PaneTabStrip.tsx b/apps/app/src/views/thread-detail/PaneTabStrip.tsx new file mode 100644 index 000000000..18ace0b86 --- /dev/null +++ b/apps/app/src/views/thread-detail/PaneTabStrip.tsx @@ -0,0 +1,574 @@ +import { cn } from "@bb/shared-ui/lib/utils"; +import { Icon, type IconName } from "@bb/shared-ui/icon"; +import { + Fragment, + useContext, + useEffect, + useRef, + useState, + type MouseEvent as ReactMouseEvent, + type PointerEvent as ReactPointerEvent, +} from "react"; +import { useThread } from "@/hooks/queries/thread-queries"; +import { usePluginSlots } from "@/lib/plugin-slots"; +import { activeTab } from "@/lib/split-layout"; +import type { PaneContent, PaneNode, PaneTab } from "@/lib/split-layout"; +import { HEADER_ICON_BUTTON_CLASS } from "@/components/layout/AppPageHeader"; +import { PluginIcon } from "@/components/plugin/PluginIcon"; +import { SecondaryPanelHostLayoutContext } from "@/components/secondary-panel/SecondaryPanelHostLayoutContext"; +import { + BROWSER_COLLAPSED_HEADER_RESERVE_CLASS, + CHROME_ROW_CLASS, + getBbDesktopInfo, + MACOS_APP_REGION_NO_DRAG_CLASS, + MACOS_COLLAPSED_HEADER_RESERVE_CLASS, + MACOS_WINDOW_DRAG_CLASS, + shouldReserveMacosTrafficLights, + shouldUseMacosDesktopChrome, +} from "@/lib/bb-desktop"; +import { useIsSidebarShowing } from "@/components/ui/sidebar.js"; +import { useIsCompactViewport } from "@bb/shared-ui/hooks/use-compact-viewport"; +import { useDesktopWindowState } from "@/hooks/useDesktopWindowState"; + +// Movement before a tab pointerdown becomes a reorder drag (vs a click). +const TAB_DRAG_ENGAGE_DISTANCE_PX = 5; +// How far outside the strip's box the pointer may wander while still +// reordering; beyond it the gesture hands off to the tear-out/split flow. +const STRIP_TEAR_OUT_MARGIN_PX = 16; + +/** Where a mid-gesture tear-out hand-off starts the split-drag session. */ +export interface TabDragStart { + clientX: number; + clientY: number; + /** The dragged tab element, dimmed by the session while it lives. */ + sourceEl: HTMLElement | null; +} + +export type BeginTabTearOut = ( + tabId: string, + start: TabDragStart, + label: string, +) => void; + +export interface PaneTabStripProps { + pane: PaneNode; + isPaneFocused: boolean; + /** Whether this strip sits on the workspace's top edge — it then doubles as + * the macOS window-drag row, exactly like the pane header row it replaced. */ + isTopRow: boolean; + /** + * True for the workspace's top-right pane: the window panel-toggle overlay + * floats over this strip's right corner, so reserve its footprint while the + * window panel is closed (open, the toggle overlays the panel's own chrome). + */ + reservesWindowPanelToggle?: boolean; + /** + * True for the workspace's structural top-left pane. The strip is the + * titlebar row there, so it — not the in-pane headers below it — clears the + * pinned sidebar trigger and macOS traffic lights, exactly like the page + * header row it replaced. Several top-row strips can be drag regions, but + * only one leaf may own this reserve. + */ + ownsWindowTopLeft?: boolean; + onActivateTab: (tabId: string) => void; + onCommitTab: (tabId: string) => void; + onCloseTab: (tabId: string) => void; + onReorderTab: (tabId: string, toIndex: number) => void; + onBeginTabTearOut: BeginTabTearOut; + /** + * Starts a WHOLE-group move/swap drag from the strip's empty background — + * the only group-drag handle left when the active view suppresses the pane + * header (terminal/diff). Absent on the single-pane surface, where a lone + * group has nothing to reorder. + */ + onBeginGroupDrag?: (event: ReactPointerEvent, label: string) => void; +} + +interface TabDragState { + tabId: string; + /** Insertion index among the OTHER tabs (reorderTab's filtered-list index). */ + insertIndex: number; +} + +/** + * The chrome row above a pane's content: one tab per view in the group, sized + * to the shared 48px chrome-row axis so it carries the old pane header's + * responsibilities (window drag, the reserved panel-toggle corner). The + * "Synthesis" treatment from the BB-39 design board: soft elevated container + * on the active tab, per-kind leading icons, dotted-underline preview state. + * Click activates (and the URL follows), double-click commits a preview tab, + * middle-click or the X closes. Dragging a tab within the strip shows a + * pin-style insertion indicator and reorders on drop; leaving the strip hands + * off to the tear-out/split flow. + */ +export function PaneTabStrip({ + pane, + isPaneFocused, + isTopRow, + reservesWindowPanelToggle = false, + ownsWindowTopLeft = false, + onActivateTab, + onCommitTab, + onCloseTab, + onReorderTab, + onBeginTabTearOut, + onBeginGroupDrag, +}: PaneTabStripProps) { + const stripRef = useRef(null); + const scrollRef = useRef(null); + const { navPanels } = usePluginSlots(); + const isWindowPanelOpen = + useContext(SecondaryPanelHostLayoutContext)?.isOpen === true; + const [desktopInfo] = useState(getBbDesktopInfo); + const isWindowDragRegion = + shouldUseMacosDesktopChrome(desktopInfo) && isTopRow; + const isSidebarShowing = useIsSidebarShowing(); + const isCompactViewport = useIsCompactViewport(); + const desktopWindowState = useDesktopWindowState(); + const reserveMacosTrafficLights = shouldReserveMacosTrafficLights({ + desktopInfo, + windowState: desktopWindowState, + }); + const shouldReserveSidebarTrigger = + ownsWindowTopLeft && (isCompactViewport || !isSidebarShowing); + const [drag, setDrag] = useState(null); + + // "+N" chip: how many tabs sit (mostly) outside the scrollport. + const [overflowCount, setOverflowCount] = useState(0); + useEffect(() => { + const scroller = scrollRef.current; + if (scroller === null) { + return; + } + const update = () => { + const viewport = scroller.getBoundingClientRect(); + let hidden = 0; + for (const el of scroller.querySelectorAll( + "[data-pane-tab-id]", + )) { + const rect = el.getBoundingClientRect(); + const mid = rect.left + rect.width / 2; + if (mid < viewport.left || mid > viewport.right) { + hidden += 1; + } + } + setOverflowCount(hidden); + }; + update(); + scroller.addEventListener("scroll", update); + const observer = + typeof ResizeObserver === "undefined" ? null : new ResizeObserver(update); + observer?.observe(scroller); + return () => { + scroller.removeEventListener("scroll", update); + observer?.disconnect(); + }; + }, [pane.tabs.length]); + + const handleStripPointerDown = (event: ReactPointerEvent) => { + // Only the strip's empty background is the group handle: pointerdowns on + // tabs (and their close buttons) target the tab elements instead. + if ( + event.button !== 0 || + !(event.target instanceof HTMLElement) || + event.target.dataset.stripBg === undefined + ) { + return; + } + onBeginGroupDrag?.( + event, + staticTabLabel(activeTab(pane).content, navPanels), + ); + }; + + // One pointer session per tab drag: within the strip's (slightly inflated) + // box the gesture tracks a pin-style insertion indicator and reorders on + // drop; crossing out hands the same gesture to the split-drag layer already + // engaged. + const beginTabGesture = ( + tab: PaneTab, + label: string, + event: ReactPointerEvent, + ) => { + const strip = stripRef.current; + const tabEl = event.currentTarget; + if (strip === null || !(tabEl instanceof HTMLElement)) { + return; + } + const startX = event.clientX; + const startY = event.clientY; + let engaged = false; + // A drag that never crosses another tab's midpoint drops in place: the + // dragged tab's own index doubles as its filtered-list insertion index. + let lastIndex = Math.max( + 0, + pane.tabs.findIndex((candidate) => candidate.tabId === tab.tabId), + ); + + function cleanup(): void { + window.removeEventListener("pointermove", handleMove); + window.removeEventListener("pointerup", handleUp); + window.removeEventListener("pointercancel", handleCancel); + setDrag(null); + } + + function handleUp(): void { + const wasEngaged = engaged; + const toIndex = lastIndex; + cleanup(); + if (wasEngaged) { + onReorderTab(tab.tabId, toIndex); + } + } + + function handleCancel(): void { + cleanup(); + } + + function handleMove(move: globalThis.PointerEvent): void { + if (strip === null || !(tabEl instanceof HTMLElement)) { + return; + } + if (!engaged) { + if ( + Math.hypot(move.clientX - startX, move.clientY - startY) <= + TAB_DRAG_ENGAGE_DISTANCE_PX + ) { + return; + } + engaged = true; + } + move.preventDefault(); + const rect = strip.getBoundingClientRect(); + const margin = STRIP_TEAR_OUT_MARGIN_PX; + const withinStrip = + move.clientX >= rect.left - margin && + move.clientX <= rect.right + margin && + move.clientY >= rect.top - margin && + move.clientY <= rect.bottom + margin; + if (!withinStrip) { + cleanup(); + onBeginTabTearOut( + tab.tabId, + { clientX: move.clientX, clientY: move.clientY, sourceEl: tabEl }, + label, + ); + return; + } + // Insertion index = how many OTHER tabs the pointer has passed + // (midpoint rule), which is exactly reorderTab's filtered-list index. + let toIndex = 0; + for (const el of strip.querySelectorAll( + "[data-pane-tab-id]", + )) { + if (el.dataset.paneTabId === tab.tabId) { + continue; + } + const tabRect = el.getBoundingClientRect(); + if (tabRect.left + tabRect.width / 2 < move.clientX) { + toIndex += 1; + } + } + lastIndex = toIndex; + setDrag({ tabId: tab.tabId, insertIndex: toIndex }); + } + + window.addEventListener("pointermove", handleMove); + window.addEventListener("pointerup", handleUp); + window.addEventListener("pointercancel", handleCancel); + }; + + // Interleave the insertion indicator at the drop gap: it renders before the + // (insertIndex + 1)-th non-dragged tab, or trailing when past them all. + const items: { tab: PaneTab; indicatorBefore: boolean }[] = []; + let othersSeen = 0; + let indicatorPlaced = false; + for (const tab of pane.tabs) { + const isDragged = drag !== null && drag.tabId === tab.tabId; + let indicatorBefore = false; + if (drag !== null && !isDragged) { + if (othersSeen === drag.insertIndex) { + indicatorBefore = true; + indicatorPlaced = true; + } + othersSeen += 1; + } + items.push({ tab, indicatorBefore }); + } + const indicatorAtEnd = drag !== null && !indicatorPlaced; + + const scrollToEnd = () => { + scrollRef.current?.scrollTo({ + left: scrollRef.current.scrollWidth, + behavior: "smooth", + }); + }; + + return ( +
+
+ {items.map(({ tab, indicatorBefore }) => ( + + {indicatorBefore ? : null} + onActivateTab(tab.tabId)} + onCommit={() => onCommitTab(tab.tabId)} + onClose={() => onCloseTab(tab.tabId)} + onBeginGesture={(event, label) => + beginTabGesture(tab, label, event) + } + /> + + ))} + {indicatorAtEnd ? : null} +
+ {overflowCount > 0 ? ( + + ) : null} + {/* Remaining width is the group-drag / window-drag background. */} +
+ {reservesWindowPanelToggle && !isWindowPanelOpen ? ( + // The floating window panel toggle overlays this corner; reserve its + // stable 28px footprint so overflowing tabs never slide under it. + + ) : null} +
+ ); +} + +/** The mock's pin-style insertion mark: a small dot capping a vertical bar. */ +function DropIndicator() { + return ( + + + + ); +} + +interface PaneTabItemProps { + tab: PaneTab; + isActive: boolean; + isPaneFocused: boolean; + isWindowDragRegion: boolean; + isDragSource: boolean; + onActivate: () => void; + onCommit: () => void; + onClose: () => void; + onBeginGesture: (event: ReactPointerEvent, label: string) => void; +} + +function PaneTabItem(props: PaneTabItemProps) { + if (props.tab.content.kind === "thread") { + return ; + } + return ; +} + +function ThreadTabItem({ + threadId, + ...props +}: PaneTabItemProps & { threadId: string }) { + const { data: thread } = useThread(threadId); + const label = + thread?.title?.trim() || thread?.titleFallback?.trim() || "Thread"; + return ; +} + +function StaticTabItem(props: PaneTabItemProps) { + const { navPanels } = usePluginSlots(); + return ( + + ); +} + +function staticTabLabel( + content: PaneContent, + navPanels: ReturnType["navPanels"], +): string { + switch (content.kind) { + // Thread tabs render through ThreadTabItem; this is only the query-less + // fallback name. + case "thread": + return "Thread"; + case "new-thread": + return "New thread"; + case "terminal": + return "Terminal"; + case "diff": + return "Diff"; + case "plugin-panel": + return ( + navPanels.find( + (candidate) => + candidate.pluginId === content.pluginId && + candidate.path === content.panelPath, + )?.title ?? content.panelPath + ); + } +} + +const KIND_ICONS: Record, IconName> = + { + thread: "MessageSquare", + "new-thread": "Plus", + terminal: "Terminal", + diff: "FileDiff", + }; + +/** The tab's leading 14px kind icon; inherits the tab's text color. */ +function TabKindIcon({ content }: { content: PaneContent }) { + if (content.kind === "plugin-panel") { + // PluginIcon resolves the plugin's branding/contribution icon hint and + // falls back to a generic mark on its own. + return ( + + ); + } + return ( +
@@ -685,7 +858,11 @@ interface SplitTreeProps { ) => void; onNavigateInPane: NavigateInPane; onBeginPaneDrag: BeginPaneDrag; - onPruneStalePane: (paneId: string) => void; + onActivateTab: (paneId: string, tabId: string) => void; + onCommitTab: (paneId: string, tabId: string) => void; + onCloseTab: (paneId: string, tabId: string) => void; + onReorderTab: (paneId: string, tabId: string, toIndex: number) => void; + onBeginTabDrag: BeginPaneTabDrag; } function SplitTree(props: SplitTreeProps) { @@ -711,7 +888,7 @@ function SplitTree(props: SplitTreeProps) { isHiddenByMaximize ? { contentVisibility: "hidden" } : undefined } className={cn( - "relative flex min-h-0 min-w-0 flex-1 overflow-hidden", + "relative flex min-h-0 min-w-0 flex-1 flex-col overflow-hidden", isHiddenByMaximize && "invisible pointer-events-none", isMaximized && "absolute inset-0 z-30", )} @@ -719,33 +896,58 @@ function SplitTree(props: SplitTreeProps) { data-maximized={isMaximized ? "true" : undefined} > {/* Only mounted in split mode, so single panes never pay for the extra - thread subscription (and never prune the last pane). */} - {node.content.kind === "thread" ? ( - props.onPruneStalePane(node.paneId)} - /> - ) : null} - props.onClosePane(node.paneId)} - isMaximized={isMaximized} - onToggleMaximize={() => props.onToggleMaximizePane(node.paneId)} - isBoundedPane + thread subscriptions. One watcher per thread TAB: a stale thread + closes its tab, not the whole group (closeTab still refuses the + layout's last view). */} + {node.tabs.map((tab) => + tab.content.kind === "thread" ? ( + props.onCloseTab(node.paneId, tab.tabId)} + /> + ) : null, + )} + props.onActivateTab(node.paneId, tabId)} + onCommitTab={(tabId) => props.onCommitTab(node.paneId, tabId)} + onCloseTab={(tabId) => props.onCloseTab(node.paneId, tabId)} + onReorderTab={(tabId, toIndex) => + props.onReorderTab(node.paneId, tabId, toIndex) + } + onBeginTabTearOut={(tabId, start, label) => + props.onBeginTabDrag(node.paneId, tabId, start, label) + } + onBeginGroupDrag={(event, label) => + props.onBeginPaneDrag(node.paneId, event, label) + } /> +
+ props.onClosePane(node.paneId)} + isMaximized={isMaximized} + onToggleMaximize={() => props.onToggleMaximizePane(node.paneId)} + isBoundedPane + isTopRow={isMaximized || isTopRow} + ownsWindowTopLeft={false} + onNavigateInPane={props.onNavigateInPane} + onBeginPaneDrag={props.onBeginPaneDrag} + /> +
{/* The inactive-pane scrim lives on an overlay ABOVE the pane's content because styles painted on the pane element itself get covered by children with opaque backgrounds (header scrim, composer). */} @@ -809,7 +1011,6 @@ interface WorkspacePaneContentProps { isFocused: boolean; isSplitPane: boolean; secondaryPanelRegistry: PaneSecondaryPanelRegistry | null; - reservesWindowPanelToggle: boolean; onRequestClose: (() => void) | null; isMaximized: boolean; onToggleMaximize: (() => void) | null; @@ -829,7 +1030,6 @@ function WorkspacePaneContent({ isFocused, isSplitPane, secondaryPanelRegistry, - reservesWindowPanelToggle, onRequestClose, isMaximized, onToggleMaximize, @@ -867,7 +1067,9 @@ function WorkspacePaneContent({ isFocused, isSplitPane, secondaryPanelHost, - reservesWindowPanelToggle, + // The pane's tab strip owns the top-right panel-toggle reservation now; + // in-pane headers below it never share a row with the floating toggle. + reservesWindowPanelToggle: false, onRequestClose, isMaximized, onToggleMaximize, @@ -889,7 +1091,6 @@ function WorkspacePaneContent({ isMaximized, onToggleMaximize, paneId, - reservesWindowPanelToggle, secondaryPanelHost, ], ); @@ -899,7 +1100,6 @@ function WorkspacePaneContent({ ; - } - if (content.kind === "new-thread") { - return ; + switch (content.kind) { + case "thread": + return ; + case "new-thread": + return ; + case "terminal": + return ( + + ); + case "diff": + return ( + + ); + case "plugin-panel": + return ( + + ); } - return ( - - ); } function NonThreadPaneContent({ content, - onRequestClose, beginPaneDrag, isBoundedPane, isTopRow, ownsWindowTopLeft, }: { content: Exclude; - onRequestClose: (() => void) | null; beginPaneDrag?: (event: ReactPointerEvent, label: string) => void; isBoundedPane: boolean; isTopRow: boolean; ownsWindowTopLeft: boolean; }) { const { navPanels } = usePluginSlots(); - const { reservesWindowPanelToggle } = useOptionalPaneContext() ?? { - reservesWindowPanelToggle: false, - }; - const isWindowPanelOpen = - useContext(SecondaryPanelHostLayoutContext)?.isOpen === true; const [desktopInfo] = useState(getBbDesktopInfo); const usesDesktopChrome = shouldUseMacosDesktopChrome(desktopInfo); const panel = @@ -967,40 +1175,29 @@ function NonThreadPaneContent({ candidate.path === content.panelPath, ) : undefined; - const label = panel?.title ?? "New thread"; + const label = + content.kind === "terminal" + ? "Terminal" + : content.kind === "diff" + ? "Diff" + : (panel?.title ?? "New thread"); + // The tab strip is the pane chrome now: the tab owns the title and the + // close/reserve affordances, so bounded panes get no generic title header. + // Only a resolved plugin panel keeps its header row — it carries real + // affordances (panel title/icon and the panel's own header actions). + const showsHeader = panel !== undefined; const handlePointerDown = (event: ReactPointerEvent) => { if (event.button === 0) beginPaneDrag?.(event, label); }; - const actions = ( + const actions = panel ? ( <> - {panel ? ( - - ) : null} + - {onRequestClose ? ( - - ) : null} - {reservesWindowPanelToggle && !isWindowPanelOpen ? ( - // The host's shortcut hint drops below the chrome row; reserve only - // its stable 28px corner button beside these pane actions. With the - // window panel open, the toggle overlays the panel's own chrome - // instead, so the pane actions sit flush at the pane edge. - - ) : null} - ); + ) : null; return (
- {isBoundedPane || panel ? ( + {showsHeader ? ( - {panel ? ( - - ) : ( -

New thread

- )} + {panel ? : null}
} actions={actions} /> ) : null} -
+
{content.kind === "new-thread" ? ( + ) : content.kind === "terminal" ? ( + + ) : content.kind === "diff" ? ( + ) : ( ({ useIsCompactViewport: () => false, })); +// The diff/terminal entry points pull in navigation and mutation machinery +// that this title-rendering test doesn't exercise. +vi.mock("./useThreadPaneEntryPoints", () => ({ + useThreadPaneEntryPoints: () => ({ + openDiff: vi.fn(), + openTerminal: vi.fn(), + isBusy: false, + }), +})); + const PANE_CONTEXT: PaneContextValue = { paneId: "main", isFocused: true, @@ -53,6 +63,8 @@ describe("ThreadDetailHeader", () => { isSecondaryPanelOpen={false} onOpenThreadGitAction={vi.fn()} onToggleSecondaryPanel={vi.fn()} + projectId="proj-1" + threadId="thr-1" threadHeaderGitActions={[]} threadTitle="Review @docs/foo.test.ts with @thread:thr_worker" /> diff --git a/apps/app/src/views/thread-detail/ThreadDetailHeader.tsx b/apps/app/src/views/thread-detail/ThreadDetailHeader.tsx index d0a2b7623..d693d9125 100644 --- a/apps/app/src/views/thread-detail/ThreadDetailHeader.tsx +++ b/apps/app/src/views/thread-detail/ThreadDetailHeader.tsx @@ -27,6 +27,8 @@ import { ThreadTitleMentions } from "@/components/thread/ThreadTitleMentions"; import { SecondaryPanelHostLayoutContext } from "@/components/secondary-panel/SecondaryPanelHostLayoutContext"; import { usePaneContext } from "./PaneContext"; import { PaneMaximizeButton } from "./PaneMaximizeButton"; +import { useThreadSplitsEnabled } from "@/hooks/useThreadSplitsEnabled"; +import { useThreadPaneEntryPoints } from "./useThreadPaneEntryPoints"; const THREAD_HEADER_ACTION_BUTTON_CLASS = COARSE_POINTER_TOOLBAR_ACTION_BUTTON_CLASS; @@ -47,7 +49,9 @@ interface ThreadDetailHeaderProps { onToggleSecondaryPanel: () => void; /** Plugin-contributed thread action buttons (design §4.9); optional. */ pluginActions?: ReactNode; + projectId: string; threadHeaderGitActions: ThreadHeaderGitAction[]; + threadId: string; threadTitle: string; workspaceOpenButton?: ReactNode; } @@ -60,12 +64,15 @@ export function ThreadDetailHeader({ onOpenThreadGitAction, onToggleSecondaryPanel, pluginActions, + projectId, threadHeaderGitActions, + threadId, threadTitle, workspaceOpenButton, }: ThreadDetailHeaderProps) { const [primaryAction, ...secondaryActions] = threadHeaderGitActions; const renderAsDrawer = useIsCompactViewport(); + const threadSplitsEnabled = useThreadSplitsEnabled(); const [desktopInfo] = useState(getBbDesktopInfo); const panelShortcut = useAppCommandShortcut("panel.toggle"); const usesDesktopChrome = shouldUseMacosDesktopChrome(desktopInfo); @@ -177,6 +184,12 @@ export function ThreadDetailHeader({ {primaryAction.label} ) : null} + {threadSplitsEnabled && !renderAsDrawer ? ( + + ) : null} {showRightPanelToggle ? ( @@ -238,3 +251,42 @@ export function ThreadDetailHeader({ /> ); } + +function ThreadPaneEntryPointButtons({ + projectId, + threadId, +}: { + projectId: string; + threadId: string; +}) { + const { openDiff, openTerminal, isBusy } = useThreadPaneEntryPoints({ + projectId, + threadId, + }); + + return ( + <> + + + + ); +} diff --git a/apps/app/src/views/thread-detail/ThreadDetailView.tsx b/apps/app/src/views/thread-detail/ThreadDetailView.tsx index 3240fa333..0e4e39ad6 100644 --- a/apps/app/src/views/thread-detail/ThreadDetailView.tsx +++ b/apps/app/src/views/thread-detail/ThreadDetailView.tsx @@ -2352,7 +2352,9 @@ function ThreadDetailViewInternal(props: ThreadDetailViewInternalProps) { onOpenThreadGitAction={gitActions.threadGitActionDialog.onOpen} onToggleSecondaryPanel={toggleSecondaryPanel} pluginActions={} + projectId={projectId} threadHeaderGitActions={gitActions.threadHeaderGitActions} + threadId={threadId} threadTitle={threadTitle} workspaceOpenButton={workspaceOpenButton} /> diff --git a/apps/app/src/views/thread-detail/splitPaneCommands.test.ts b/apps/app/src/views/thread-detail/splitPaneCommands.test.ts index d22253a76..dc6dea024 100644 --- a/apps/app/src/views/thread-detail/splitPaneCommands.test.ts +++ b/apps/app/src/views/thread-detail/splitPaneCommands.test.ts @@ -6,10 +6,18 @@ import { } from "./splitPaneCommands"; function pane(paneId: string): PaneNode { + const tabId = `${paneId}-t1`; return { type: "pane", paneId, - content: { kind: "thread", projectId: "project-1", threadId: paneId }, + tabs: [ + { + tabId, + content: { kind: "thread", projectId: "project-1", threadId: paneId }, + preview: false, + }, + ], + activeTabId: tabId, }; } diff --git a/apps/app/src/views/thread-detail/splitThreadNavigation.test.ts b/apps/app/src/views/thread-detail/splitThreadNavigation.test.ts index f2033e7e0..916fe8a32 100644 --- a/apps/app/src/views/thread-detail/splitThreadNavigation.test.ts +++ b/apps/app/src/views/thread-detail/splitThreadNavigation.test.ts @@ -1,9 +1,13 @@ import { describe, expect, it } from "vitest"; import { + activePaneContent, + findContentTab, + findPane, findPaneByContent, findPaneByThread, listPanes, MAX_PANES, + setFocus, splitPane, } from "@/lib/split-layout"; import type { SplitLayout } from "@/lib/split-layout"; @@ -32,6 +36,14 @@ function twoPaneLayout(): SplitLayout { ); } +function expectLayout(layout: SplitLayout | null): SplitLayout { + expect(layout).not.toBeNull(); + if (layout === null) { + throw new Error("Expected a split layout"); + } + return layout; +} + function eightPaneLayout(): SplitLayout { let layout = twoPaneLayout(); for (let index = 3; index <= MAX_PANES; index += 1) { @@ -58,7 +70,7 @@ describe("reconcileLayoutForRoute", () => { ); }); - it("replaces only the focused pane's content, preserving the rest of the split", () => { + it("adds a preview tab to the focused group, preserving committed tabs", () => { const before = twoPaneLayout(); const after = reconcileLayoutForRoute(before, { @@ -66,18 +78,45 @@ describe("reconcileLayoutForRoute", () => { threadId: "thread-3", }); - // Layout shape preserved (still two panes); focused pane now shows thread-3. expect(listPanes(after.root)).toHaveLength(2); expect(after.focusedPaneId).toBe("pane-2"); expect(findPaneByThread(after.root, "p1", "thread-3")?.paneId).toBe( "pane-2", ); - // The other pane is untouched. expect(findPaneByThread(after.root, "p1", "thread-1")?.paneId).toBe( "pane-1", ); - // thread-2 was displaced from the focused pane. - expect(findPaneByThread(after.root, "p1", "thread-2")).toBeNull(); + const focused = findPane(after.root, "pane-2")!; + expect(focused.tabs).toHaveLength(2); + expect(focused.tabs[0]?.content).toEqual({ + kind: "thread", + projectId: "p1", + threadId: "thread-2", + }); + expect(focused.tabs[0]?.preview).toBe(false); + expect(focused.tabs[1]?.preview).toBe(true); + }); + + it("replaces the focused group's existing preview in place", () => { + const first = reconcileLayoutForRoute(twoPaneLayout(), { + projectId: "p1", + threadId: "thread-3", + }); + const previewId = findPane(first.root, "pane-2")!.activeTabId; + const second = reconcileLayoutForRoute(first, { + projectId: "p1", + threadId: "thread-4", + }); + const focused = findPane(second.root, "pane-2")!; + + expect(focused.tabs).toHaveLength(2); + expect(focused.activeTabId).toBe(previewId); + expect(activePaneContent(focused)).toEqual({ + kind: "thread", + projectId: "p1", + threadId: "thread-4", + }); + expect(findPaneByThread(second.root, "p1", "thread-3")).toBeNull(); }); it("focuses an existing pane instead of duplicating an already-open thread", () => { @@ -92,6 +131,30 @@ describe("reconcileLayoutForRoute", () => { expect(after.focusedPaneId).toBe("pane-1"); }); + it("activates an existing inactive tab and focuses its group", () => { + const withTab = applyThreadOpenToLayout( + twoPaneLayout(), + { projectId: "p1", threadId: "thread-3" }, + "replace", + ); + const movedFocus = reconcileLayoutForRoute(withTab, { + projectId: "p1", + threadId: "thread-1", + }); + + const after = reconcileLayoutForRoute(movedFocus, { + projectId: "p1", + threadId: "thread-3", + }); + expect(after.focusedPaneId).toBe("pane-2"); + expect(activePaneContent(findPane(after.root, "pane-2")!)).toEqual({ + kind: "thread", + projectId: "p1", + threadId: "thread-3", + }); + expect(findPane(after.root, "pane-2")?.tabs).toHaveLength(2); + }); + it("is a no-op when the route already matches the focused pane", () => { const before = twoPaneLayout(); @@ -110,9 +173,9 @@ describe("mixed page navigation", () => { kind: "new-thread", }); - const after = reconcileLayoutForContent(withCompose, { - kind: "new-thread", - }); + const after = expectLayout( + reconcileLayoutForContent(withCompose, { kind: "new-thread" }), + ); expect(listPanes(after.root)).toHaveLength(3); expect(after.focusedPaneId).toBe( @@ -130,18 +193,62 @@ describe("mixed page navigation", () => { } as const; const before = splitPane(twoPaneLayout(), "pane-1", "bottom", plugin); - const after = reconcileLayoutForContent(before, { - ...plugin, - subPath: "work/today.md", - }); + const after = expectLayout( + reconcileLayoutForContent(before, { + ...plugin, + subPath: "work/today.md", + }), + ); expect(listPanes(after.root)).toHaveLength(3); - expect(findPaneByContent(after.root, plugin)?.content).toEqual({ + expect(findContentTab(after.root, plugin)?.tab.content).toEqual({ ...plugin, subPath: "work/today.md", }); expect(focusedPaneRoute(after)).toBe("/plugins/notes/notes/work/today.md"); }); + + it("leaves a null layout null for an unopened URL-derived terminal", () => { + expect( + reconcileLayoutForContent(null, { + kind: "terminal", + terminalId: "term-1", + }), + ).toBeNull(); + }); + + it("treats an unopened URL-derived terminal as a reveal-only no-op for an existing layout", () => { + const before = twoPaneLayout(); + const after = reconcileLayoutForContent(before, { + kind: "terminal", + terminalId: "term-1", + }); + + expect(after).toBe(before); + }); + + it("reveals an existing terminal by id regardless of target", () => { + const terminal = { + kind: "terminal", + terminalId: "term-1", + target: { kind: "thread", threadId: "thread-1" }, + } as const; + const before = setFocus( + splitPane(twoPaneLayout(), "pane-1", "bottom", terminal), + "pane-2", + ); + const after = expectLayout( + reconcileLayoutForContent(before, { + kind: "terminal", + terminalId: "term-1", + }), + ); + + expect(after.focusedPaneId).toBe( + findPaneByContent(after.root, terminal)?.paneId, + ); + expect(findContentTab(after.root, terminal)?.tab.preview).toBe(false); + }); }); describe("focusedThreadRoute", () => { @@ -166,6 +273,27 @@ describe("focusedThreadRoute", () => { }); describe("applyThreadOpenToLayout", () => { + it("adds a committed tab for a replace intent", () => { + const before = twoPaneLayout(); + const after = applyThreadOpenToLayout( + before, + { projectId: "p2", threadId: "thread-replace" }, + "replace", + ); + const focused = findPane(after.root, "pane-2")!; + + expect(listPanes(after.root)).toHaveLength(2); + expect(focused.tabs).toHaveLength(2); + expect(activePaneContent(focused)).toEqual({ + kind: "thread", + projectId: "p2", + threadId: "thread-replace", + }); + expect( + focused.tabs.find((tab) => tab.tabId === focused.activeTabId)?.preview, + ).toBe(false); + }); + it("splits from the focused pane and focuses the opened thread", () => { const before = twoPaneLayout(); const after = applyThreadOpenToLayout( @@ -193,7 +321,7 @@ describe("applyThreadOpenToLayout", () => { expect(after.focusedPaneId).toBe("pane-1"); }); - it("creates panes five through eight, then replaces the focused pane for a ninth open", () => { + it("creates panes through eight, then coerces a ninth edge open to a committed center tab", () => { const eight = eightPaneLayout(); const focusedPaneId = eight.focusedPaneId; @@ -220,7 +348,14 @@ describe("applyThreadOpenToLayout", () => { expect(findPaneByThread(after.root, "p2", "thread-9")?.paneId).toBe( focusedPaneId, ); - expect(findPaneByThread(after.root, "p1", "thread-8")).toBeNull(); + expect(findPaneByThread(after.root, "p1", "thread-8")?.paneId).toBe( + focusedPaneId, + ); + const focused = findPane(after.root, focusedPaneId)!; + expect(focused.tabs).toHaveLength(2); + expect( + focused.tabs.find((tab) => tab.tabId === focused.activeTabId)?.preview, + ).toBe(false); }); }); diff --git a/apps/app/src/views/thread-detail/splitThreadNavigation.ts b/apps/app/src/views/thread-detail/splitThreadNavigation.ts index ec69b2afb..1df55f2ba 100644 --- a/apps/app/src/views/thread-detail/splitThreadNavigation.ts +++ b/apps/app/src/views/thread-detail/splitThreadNavigation.ts @@ -1,11 +1,15 @@ import type { ThreadRoutePathArgs } from "@/lib/route-paths"; import type { ThreadOpenSplit, ThreadPaneAction } from "@bb/server-contract"; import { + activateTab, + activePaneContent, countPanes, + createPaneNode, + findContentTab, findPane, - findPaneByContent, findPaneByThread, MAX_PANES, + openTab, replacePaneContent, setFocus, splitPane, @@ -15,10 +19,13 @@ import type { PaneContent, SplitLayout } from "@/lib/split-layout"; import { getPluginPanelRoutePath, getRootComposeRoutePath, + getTerminalRoutePath, + getThreadDiffRoutePath, getThreadRoutePath, } from "@/lib/route-paths"; const FIRST_PANE_ID = "pane-1"; +const FIRST_TAB_ID = "tab-1"; export function threadPaneContent(thread: ThreadRoutePathArgs): PaneContent { return { @@ -31,108 +38,115 @@ export function threadPaneContent(thread: ThreadRoutePathArgs): PaneContent { export function createSinglePaneLayout( thread: ThreadRoutePathArgs, ): SplitLayout { - return { - root: { - type: "pane", - paneId: FIRST_PANE_ID, - content: threadPaneContent(thread), - }, - focusedPaneId: FIRST_PANE_ID, - }; + return createSinglePaneContentLayout(threadPaneContent(thread)); } export function createSinglePaneContentLayout( content: PaneContent, ): SplitLayout { return { - root: { type: "pane", paneId: FIRST_PANE_ID, content }, + root: createPaneNode(FIRST_PANE_ID, FIRST_TAB_ID, content), focusedPaneId: FIRST_PANE_ID, }; } export function paneContentRoute(content: PaneContent): string { - if (content.kind === "thread") { - return getThreadRoutePath(content); - } - if (content.kind === "new-thread") { - return getRootComposeRoutePath(); + switch (content.kind) { + case "thread": + return getThreadRoutePath(content); + case "new-thread": + return getRootComposeRoutePath(); + case "terminal": + return getTerminalRoutePath(content.terminalId); + case "diff": + return getThreadDiffRoutePath(content); + case "plugin-panel": + return getPluginPanelRoutePath({ + pluginId: content.pluginId, + path: content.panelPath, + subPath: content.subPath, + }); } - return getPluginPanelRoutePath({ - pluginId: content.pluginId, - path: content.panelPath, - subPath: content.subPath, - }); } -/** Reconciles any splittable page route into the focused pane. */ +/** Stateful views never open as previews: a preview slot gets silently + * replaced by the next single-click, which must not destroy a PTY view. */ +function contentPreviewable(content: PaneContent): boolean { + return content.kind !== "terminal"; +} + +/** + * Reconciles any splittable page route into the layout, per the settled + * docking policy: + * - no layout yet => a single pane holding the route's view; + * - the view already exists as a tab anywhere => reveal it (focus its pane, + * activate its tab), never duplicate; + * - otherwise => open it as the focused group's PREVIEW tab (VS Code-style: + * single-click navigation replaces the group's preview slot in place), + * except stateful kinds which open committed. + * Plugin-panel subpath changes update the existing tab's content (navigation + * within the view). Returns the same reference when nothing changes so the + * driving effect stays idempotent (no history spam, no render loop). + */ export function reconcileLayoutForContent( layout: SplitLayout | null, content: PaneContent, -): SplitLayout { +): SplitLayout | null { + const isRevealOnlyTerminal = + content.kind === "terminal" && content.target === undefined; if (layout === null) { - return createSinglePaneContentLayout(content); + // URL-derived terminal content can't seed a layout either — a targetless + // tab could never locate its session and must not be persisted. + return isRevealOnlyTerminal ? null : createSinglePaneContentLayout(content); } - const existing = findPaneByContent(layout.root, content); + const existing = findContentTab(layout.root, content); if (existing !== null) { - const withRouteState = - existing.content.kind === "plugin-panel" && + const needsSubPathRefresh = + existing.tab.content.kind === "plugin-panel" && content.kind === "plugin-panel" && - existing.content.subPath !== content.subPath - ? replacePaneContent(layout, existing.paneId, content) - : layout; - return withRouteState.focusedPaneId === existing.paneId - ? withRouteState - : setFocus(withRouteState, existing.paneId); + existing.tab.content.subPath !== content.subPath; + let next = layout; + if (existing.pane.activeTabId !== existing.tab.tabId) { + next = activateTab(next, existing.pane.paneId, existing.tab.tabId); + } + if (needsSubPathRefresh) { + next = replacePaneContent(next, existing.pane.paneId, content); + } + return next.focusedPaneId === existing.pane.paneId + ? next + : setFocus(next, existing.pane.paneId); } - return replacePaneContent(layout, layout.focusedPaneId, content); + if (isRevealOnlyTerminal) { + // URL-derived terminal content is a reveal intent only: without its + // context binding the pane couldn't locate its session, so an unopened + // terminal deep link leaves the layout untouched. + return layout; + } + return openTab(layout, layout.focusedPaneId, content, { + preview: contentPreviewable(content), + }); } export function focusedPaneRoute(layout: SplitLayout): string | null { const focused = findPane(layout.root, layout.focusedPaneId); - return focused === null ? null : paneContentRoute(focused.content); + return focused === null ? null : paneContentRoute(activePaneContent(focused)); } /** - * Folds an external route (initial load, sidebar click, deep link) into the - * layout, per the binding navigation policy: - * - no layout yet => a single pane from the route; - * - the route thread is already open in a pane => focus that pane, never - * duplicate it; - * - otherwise => replace the focused pane's content, never dismantling the - * rest of the layout. - * Returns the same reference when nothing changes so the driving effect stays - * idempotent (no history spam, no render loop). + * Folds an external thread route (initial load, sidebar click, deep link) into + * the layout. Threads are reveal-existing (one view per thread); an unopened + * thread opens as the focused group's preview tab. + * Returns the same reference when nothing changes. */ export function reconcileLayoutForRoute( layout: SplitLayout | null, thread: ThreadRoutePathArgs, ): SplitLayout { - if (layout === null) { - return createSinglePaneLayout(thread); - } - const existing = findPaneByThread( - layout.root, - thread.projectId, - thread.threadId, - ); - if (existing !== null) { - return layout.focusedPaneId === existing.paneId - ? layout - : setFocus(layout, existing.paneId); - } - const focused = findPane(layout.root, layout.focusedPaneId); - if ( - focused !== null && - focused.content.kind === "thread" && - focused.content.projectId === thread.projectId && - focused.content.threadId === thread.threadId - ) { - return layout; - } - return replacePaneContent( - layout, - layout.focusedPaneId, - threadPaneContent(thread), + // Thread content never hits the reveal-only terminal guard, so the null + // branch always seeds a layout; the fallback only satisfies the types. + return ( + reconcileLayoutForContent(layout, threadPaneContent(thread)) ?? + createSinglePaneLayout(thread) ); } @@ -143,7 +157,9 @@ function threadOpenSplitZone(split: ThreadOpenSplit): SplitZone { /** * Applies a CLI/SDK thread-open intent to the current layout. This deliberately * routes through the same drop decision as sidebar dragging so already-open - * threads focus, and edge splits at the pane cap coerce to replacement. + * threads focus, and edge splits at the pane cap coerce to replacement. An + * explicit open intent is a commit gesture: the tab opens committed, and a + * center intent adds a tab to the focused group rather than replacing content. */ export function applyThreadOpenToLayout( layout: SplitLayout | null, @@ -164,13 +180,19 @@ export function applyThreadOpenToLayout( atMaxPanes: countPanes(layout.root) >= MAX_PANES, }); if (existing !== null) { - return layout.focusedPaneId === existing.paneId - ? layout - : setFocus(layout, existing.paneId); + const content = threadPaneContent(thread); + const location = findContentTab(layout.root, content); + let next = layout; + if (location !== null && location.pane.activeTabId !== location.tab.tabId) { + next = activateTab(next, location.pane.paneId, location.tab.tabId); + } + return next.focusedPaneId === existing.paneId + ? next + : setFocus(next, existing.paneId); } const content = threadPaneContent(thread); return decision.zone === "center" - ? replacePaneContent(layout, layout.focusedPaneId, content) + ? openTab(layout, layout.focusedPaneId, content) : splitPane(layout, layout.focusedPaneId, decision.zone, content); } @@ -209,18 +231,20 @@ export function applyThreadPaneActionToLayout( } /** - * The focused pane's thread as route args, or null when it isn't a thread pane. - * Drives URL sync: the focused pane owns the address bar. + * The focused pane's thread as route args, or null when its active view isn't + * a thread. Drives URL sync: the focused pane's active tab owns the address + * bar. */ export function focusedThreadRoute( layout: SplitLayout, ): ThreadRoutePathArgs | null { const focused = findPane(layout.root, layout.focusedPaneId); - if (focused === null || focused.content.kind !== "thread") { + const content = focused === null ? null : activePaneContent(focused); + if (content === null || content.kind !== "thread") { return null; } return { - projectId: focused.content.projectId, - threadId: focused.content.threadId, + projectId: content.projectId, + threadId: content.threadId, }; } diff --git a/apps/app/src/views/thread-detail/useThreadPaneEntryPoints.ts b/apps/app/src/views/thread-detail/useThreadPaneEntryPoints.ts new file mode 100644 index 000000000..aa60ccacc --- /dev/null +++ b/apps/app/src/views/thread-detail/useThreadPaneEntryPoints.ts @@ -0,0 +1,180 @@ +import { useCallback, useRef } from "react"; +import { useStore } from "jotai"; +import { useNavigate } from "react-router-dom"; +import { + useCreateThreadTerminal, + useThreadTerminals, +} from "@/hooks/queries/thread-terminal-queries"; +import { splitLayoutAtom } from "@/lib/split-layout/atoms"; +import { + activateTab, + findContentTab, + listTabs, + openTab, + setFocus, + type PaneContent, + type SplitLayout, +} from "@/lib/split-layout"; +import { paneContentRoute } from "./splitThreadNavigation"; + +const TERMINAL_COLS = 100; +const TERMINAL_ROWS = 30; + +interface UseThreadPaneEntryPointsArgs { + projectId: string; + threadId: string; +} + +interface ThreadPaneEntryPoints { + openDiff(): void; + openTerminal(): void; + isBusy?: boolean; +} + +function revealTab( + layout: SplitLayout, + location: NonNullable>, +): SplitLayout { + return setFocus( + activateTab(layout, location.pane.paneId, location.tab.tabId), + location.pane.paneId, + ); +} + +export function useThreadPaneEntryPoints({ + projectId, + threadId, +}: UseThreadPaneEntryPointsArgs): ThreadPaneEntryPoints { + const store = useStore(); + const navigate = useNavigate(); + const terminalsQuery = useThreadTerminals(threadId); + const createTerminal = useCreateThreadTerminal(); + const isCreatingTerminalRef = useRef(false); + const isLoadingTerminals = + terminalsQuery.data === undefined && + (terminalsQuery.isLoading || terminalsQuery.isFetching); + + const applyContent = useCallback( + (content: PaneContent) => { + const layout = store.get(splitLayoutAtom); + if (layout === null) { + return; + } + const threadLocation = findContentTab(layout.root, { + kind: "thread", + projectId, + threadId, + }); + if (threadLocation === null) { + return; + } + + const existing = findContentTab(layout.root, content); + const nextLayout = + existing === null + ? openTab(layout, threadLocation.pane.paneId, content) + : revealTab(layout, existing); + if (nextLayout === layout) { + return; + } + store.set(splitLayoutAtom, nextLayout); + void navigate(paneContentRoute(content), { replace: true }); + }, + [navigate, projectId, store, threadId], + ); + + const openDiff = useCallback(() => { + applyContent({ kind: "diff", projectId, threadId }); + }, [applyContent, projectId, threadId]); + + const openTerminal = useCallback(() => { + const layout = store.get(splitLayoutAtom); + if (layout === null) { + return; + } + const threadLocation = findContentTab(layout.root, { + kind: "thread", + projectId, + threadId, + }); + if (threadLocation === null) { + return; + } + + const existingTerminal = listTabs(layout.root).find( + ({ tab }) => + tab.content.kind === "terminal" && + tab.content.target?.kind === "thread" && + tab.content.target.threadId === threadId, + ); + if (existingTerminal !== undefined) { + const content = existingTerminal.tab.content; + const nextLayout = setFocus( + activateTab( + layout, + existingTerminal.pane.paneId, + existingTerminal.tab.tabId, + ), + existingTerminal.pane.paneId, + ); + store.set(splitLayoutAtom, nextLayout); + void navigate(paneContentRoute(content), { replace: true }); + return; + } + + const runningSession = terminalsQuery.data?.sessions.find( + (session) => session.status === "running", + ); + if (runningSession !== undefined) { + applyContent({ + kind: "terminal", + terminalId: runningSession.id, + target: { kind: "thread", threadId }, + }); + return; + } + + if ( + isLoadingTerminals || + createTerminal.isPending || + isCreatingTerminalRef.current + ) { + return; + } + isCreatingTerminalRef.current = true; + createTerminal.mutate( + { + threadId, + cols: TERMINAL_COLS, + rows: TERMINAL_ROWS, + }, + { + onSuccess: (session) => { + applyContent({ + kind: "terminal", + terminalId: session.id, + target: { kind: "thread", threadId }, + }); + }, + onSettled: () => { + isCreatingTerminalRef.current = false; + }, + }, + ); + }, [ + applyContent, + createTerminal, + isLoadingTerminals, + navigate, + projectId, + store, + terminalsQuery.data?.sessions, + threadId, + ]); + + return { + openDiff, + openTerminal, + isBusy: isLoadingTerminals || createTerminal.isPending, + }; +} diff --git a/bb39-report/bb39-dockable-panes-review.html b/bb39-report/bb39-dockable-panes-review.html new file mode 100644 index 000000000..69d9245ae --- /dev/null +++ b/bb39-report/bb39-dockable-panes-review.html @@ -0,0 +1,269 @@ + + + + + + BB-39 · Dockable Panes Experiment — Product Review + + + +
+
+
Product review · BB-39 / BB-25
+

Dockable panes: terminals, diffs, and threads in one tab-group workspace

+

First working slice of the unified docking model: the split tree's leaves became VS Code-style tab groups, navigation opens preview tabs, and terminals and diffs are first-class draggable panes with thread-header entry points. Built by a four-worker model-routed fan-out, gated by a GPT-5.6 review, and verified live in the dev app for this report.

+
+ Verdict — works end-to-end; 1 P2 found in live QA + Branch bb/dockable-terminals-and-views-thr_9nsm45dtne (uncommitted, base 6dbeb8a20) + Build dev app @ localhost:12325 · threadSplits flag ON + Tests 241 files / 1,624 passing · typecheck clean + Scope experiment behind flag — phases 2+3 of 6 +
+
+ +
+ +
+ +
+

What was built and why

+

BB previously ran two separate pane systems: the recursive split tree (threads and plugin panels only) and the per-thread "secondary panel" tab strip (where terminals, diffs, and files actually lived). BB-25's decision record unifies them: one layout tree whose leaves are tab groups, every surface a tab, resources (threads, PTYs) distinct from views. This experiment ships the first two implementation phases behind the existing experiments.threadSplits flag:

+
+
Tab-group layout engine

Leaves hold ordered tabs with one preview slot; open/close/activate/commit/move ops; localStorage schema v2 (v1 = deliberate fresh start); 8-pane cap, 16-tab sanity bound.

+
Preview tabs

Navigation opens an italic preview that the next single-click replaces in place. Commit by double-click (tab or sidebar row) or by dragging. Stateful views (terminals) never preview.

+
Terminal & diff panes

New descriptors with canonical routes (/terminals/:id, /threads/:id/diff), explicit context binding, xterm mounted straight off the server-relayed session, diff via the production git-diff panel.

+
Thread-header entry points

Diff and Terminal buttons on the thread pane: reveal an open view, else reopen the thread's running terminal, else create one — never a duplicate, never an accidental PTY.

+
+

Deferred by design (later phases): browser panes (BB-40), plugin views API (BB-41), server-side per-project persistence + bb panes agent CLI (BB-42), the registry sidebar and quick switcher (BB-43).

+
+ +
+

The core workflow: click around, commit what matters

+

The defining loop of the design: single-click exploration is free — it never mints permanent tabs — and an explicit gesture locks in what you want to keep.

+
+ QA screenshot: qa-001b-direct-thread.jpg +
Navigation opens a preview. Visiting QA Alpha put it in the tab strip in italics — a preview tab sharing the group with the committed "New thread" view. The thread header keeps its slim chrome; the tab owns the title.
+
+
+ QA screenshot: qa-002-beta-replaces-preview.jpg +
The next click replaces the preview in place. Single-clicking QA Beta in the sidebar swapped the preview slot — still exactly two tabs, no pane spam, URL following along.
+
+
+ QA screenshot: qa-004-gamma-third-preview.jpg +
Commit + preview coexist. Double-clicking QA Beta's sidebar row committed it (upright); QA Gamma then opened as a fresh preview beside it. Three tabs: committed, committed, preview.
+
+
+ +
+

Terminals as first-class panes

+

The load-bearing claim of the whole unification — stateful, server-relayed PTYs living in the layout tree — verified live:

+
+ QA screenshot: qa-005-terminal-pane.jpg +
One click on the header's Terminal button created a PTY bound to the thread, opened it as a committed tab (terminals never preview), and the URL became /terminals/term_cr9va6c5vt. The slim context strip shows the binding: Thread thr_5yi7.
+
+
+ QA screenshot: qa-006-terminal-interactive.jpg +
Fully interactive over the pane's websocket: echo dockable-panes-live-$((6*7))dockable-panes-live-42, executed in the environment's worktree.
+
+
+ QA screenshot: qa-008-terminal-reopened-same-pty.jpg +
The resource outlives its view. After closing the Terminal tab and clicking the entry point again, the same terminal id returned with scrollback intact — the PTY survived the close; the button revealed the existing resource instead of spawning a new one.
+
+
+ +
+

Diff panes

+
+ QA screenshot: qa-009-diff-pane.jpg +
The Diff entry point opened a committed tab at /threads/thr_5yi7…/diff, reusing the production git-diff panel. This environment isn't a git repository, so the empty state is the correct render; populated diff content was not exercised in this pass (the component is the same one the secondary panel ships today).
+
+
+ +
+

Drag, tear-out, and splits

+
+ QA screenshot: qa-011-terminal-split-right.jpg +
Tearing the Terminal tab out to the right edge created a second group — live PTY on the right with scrollback intact, the left group keeping its four tabs (Gamma still a preview). Note the sidebar: thread rows now carry split-indicator minimaps showing where they're docked. Dragging always commits.
+
+
+ +
+

Persistence and lifecycle

+

Reload: after a full page reload, both groups restored from localStorage schema v2, the URL stayed on the focused terminal pane, and the terminal reconnected by id with scrollback (corroborated by DOM query: the earlier command output re-rendered).

+
+ QA screenshot: qa-013-archive-spares-terminal.jpg +
Archiving the thread (via CLI, out-of-band) closed its thread tab but the layout and Terminal tab survived — the exact High-severity review fix, observed live. The PTY itself was ended by server policy (thread archive closes thread-scoped sessions), and the pane degrades honestly to "Terminal ended". The lingering Diff tab in this screenshot is the one defect found — see below.
+
+
+ +
+

Flag-off parity

+
+ QA screenshot: qa-014-flag-off-parity.jpg +
With threadSplits off the thread page renders exactly as production today: no tab strip, no header entry points (verified by accessibility query: zero "Open diff/terminal pane" buttons). The single-pane single-tab path is also DOM-parity-guarded by test.
+
+
+ +
+

Architecture at review depth

+
    +
  • Resources vs views (BB-25 decision record): a tab is a view; threads and terminals are resources that exist unviewed. Per-kind multiplicity: threads reveal-existing (1 view in v1), terminals exactly-one-view (PTY geometry), data views (tasks/diff) cheap multi.
  • +
  • URL-first descriptors: every pane kind has a canonical route and two-way route↔descriptor conversion; the focused pane's active tab owns the address bar. Bare /terminals/:id URLs are reveal-only (they can't carry the context binding) — verified they cannot seed an invalid tab.
  • +
  • Preview is view-layer only, orthogonal to the resource model; one preview slot per group, enforced at open, normalize, and persisted-schema levels for terminals.
  • +
  • Explicit context binding: terminal descriptors carry their target (thread/environment/host-path) fixed at creation, shown in pane chrome.
  • +
  • Fresh start on storage: schema v2; v1 layouts deserialize to null by design (experiment behind flag; old tab state had a 14-day expiry anyway).
  • +
+
+ +
+

How it was built and verified

+

Manager (Fable) implemented the core model and navigation policy; three parallel workers with non-overlapping file ownership built the renderer (Fable), pane components + entry points (GPT-5.6 Sol high), and sidebar adaptations (Sol medium); a fourth migrated tests (Sol high). A GPT-5.6 Sol review gate returned REQUEST CHANGES with 6 findings: 5 fixed in one round (archive sparing terminals, cap-guarded navigation, URL-terminal guard, invariant enforcement, group-drag restoration), 1 declined deliberately (tab insertion stays after-active so a thread's diff/terminal lands beside it). Every fix carries a regression test.

+
    +
  • Typecheck: clean across the app after integration (verified independently by the manager).
  • +
  • Test suite: 241 files / 1,624 tests passing, including migrated split-layout coverage and new regression tests.
  • +
  • Live QA (this report): 13 workflows exercised in the dev app via browser automation with console-error capture — zero console errors observed in any captured run.
  • +
+
+ +
+

Defects, limitations, and environment notes

+

P2 — Archived thread's diff tab lingers on out-of-band archive

+
    +
  1. Open a thread, its Diff pane, and a terminal (any layout).
  2. +
  3. Archive the thread from outside the app (CLI: bb thread archive <id>).
  4. +
  5. Expected: the thread tab and its diff tab close (matching the in-app archive path, which routes through closePanesForThreadsAtom and does close both). Actual: only the thread tab is pruned; the stale-thread watcher doesn't cover thread-bound diff tabs, so an orphaned Diff tab remains (evidence: the lifecycle screenshot above).
  6. +
+

Suggested fix: extend the per-tab stale watcher to thread-bound descriptor kinds, or have DiffPaneContent render an archived-thread state. Not fixed during QA to keep the finding reproducible.

+

Product-policy observation (not a defect)

+

Server policy ends thread-scoped PTYs when their thread is archived, so the spared terminal tab shows "Terminal ended". If terminals should survive archive, that's a server-side lifecycle decision (relevant to the BB-43 registry, where running terminals are sidebar resources).

+

Not exercised in this pass

+
    +
  • Populated diff rendering (QA env isn't a git repo; component is production code reused).
  • +
  • Compact-viewport fallback, dark/custom themes, center-drop tab merge via pointer drag, and strip-background group drag (all unit-tested; drag decide-tables covered by the suite).
  • +
  • CLI thread-open-into-split (applyThreadOpenToLayout is unit-tested; the full bb panes agent surface is BB-42).
  • +
+

Environment / QA residue

+
    +
  • Dev instance data dir: ~/.bb-dev/bb-worktrees-env_rp87eqfyeb-bb-c3596b6d3569; fixtures QA Alpha/Beta remain, QA Gamma archived, one ended terminal session, qa-diff-fixture.txt in its env worktree. Left in place for reproduction.
  • +
  • threadSplits flag left ON in the dev instance for hands-on evaluation; dev server left running.
  • +
  • One tooling quirk (not product): first automation click from the homepage timed out under Playwright's stability wait; all subsequent targeted clicks behaved.
  • +
+
+ +
+

Decisions requested

+
    +
  • Ship gate: is this slice good enough to merge behind the flag (with the P2 filed as a fast-follow), or should the diff-tab pruning land first?
  • +
  • Terminal-on-archive policy: keep server-side PTY teardown on thread archive, or let terminals survive as unbound resources (matters for the BB-43 registry design)?
  • +
  • Insertion order: confirm the declined review finding — committed opens insert after the active tab (keeps a thread's diff/terminal adjacent) rather than appending to the end.
  • +
+
+ +
+

Try it

+
# dev server is already running on this branch
+scripts/bb-dev-app status        # URLs; app at http://localhost:12325
+# flag is already ON; to toggle:
+sqlite3 ~/.bb-dev/bb-worktrees-env_rp87eqfyeb-bb-c3596b6d3569/bb.db \
+  "UPDATE system_experiments SET thread_splits=1 WHERE id='current';"
+

Things to feel: single-click threads (preview, italics) vs double-click (commit); the Diff/Terminal buttons in the thread header; drag a tab to any edge; reload mid-layout; close a terminal tab and reopen it from the button.

+
+ +
+

Coverage summary

+ + + + + + + + + + + + + + + + + + +
WorkflowStatusEvidence
App boot, fixtures visible, console cleanpassqa-000
Navigation opens preview tab (italic) in focused grouppassqa-001b
Preview replaced in place by next single-clickpassqa-002
Sidebar double-click commits; next click adds new previewpassqa-003/004
Terminal entry point creates PTY, committed tab, canonical URLpassqa-005
Terminal interactive I/O through pane websocketpassqa-006
Close tab → PTY survives → reopen reveals same terminal + scrollbackpassqa-007/008 + URL logs
Diff entry point → committed tab at /diff, empty state correctpassqa-009
Tab tear-out drag → edge split, two groups, PTY intactpassqa-010/011
Sidebar split-indicator minimaps reflect docked tabspassqa-011
Reload restores layout (schema v2) + terminal reconnectpassqa-012 + DOM query
External archive prunes thread tab, layout + terminal tab survivepassqa-013
Archived thread's diff tab closes (out-of-band path)fail — P2qa-013
Flag-off parity (no strip, no buttons)passqa-014
Browser panes, plugin views API, registry sidebar, ⌘K, bb panes CLIN/A — later phases (BB-40/41/42/43)
Populated diff render, compact viewport, themes, center-drop mergenot exercised live; unit-tested where applicable
+

Full evidence archive (14 raw screenshots + review artifacts): thread storage bb39/qa/ and bb39/review-report.md.

+
+ +
+
+
BB-39 dockable-panes experiment · reviewed 2026-07-18 · branch bb/dockable-terminals-and-views-thr_9nsm45dtne (base 6dbeb8a20, uncommitted) · dev app localhost:12325
+
+ + diff --git a/bb39-report/qa-001b-direct-thread.jpg b/bb39-report/qa-001b-direct-thread.jpg new file mode 100644 index 0000000000000000000000000000000000000000..d596afd6fdc14055d2879d6e433cd98e21296e20 GIT binary patch literal 57908 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 Z<)-_0x}<-#>h;$uVgFm76Z<~@e*kAeWz_%x literal 0 HcmV?d00001 diff --git a/bb39-report/qa-002-beta-replaces-preview.jpg b/bb39-report/qa-002-beta-replaces-preview.jpg new file mode 100644 index 0000000000000000000000000000000000000000..f57d9b4f2eee08e8cbc4e72611b2442417fca391 GIT binary patch literal 57837 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 K!>h!Ph5rL((aTl< literal 0 HcmV?d00001 diff --git a/bb39-report/qa-004-gamma-third-preview.jpg b/bb39-report/qa-004-gamma-third-preview.jpg new file mode 100644 index 0000000000000000000000000000000000000000..256d0097e26631e542aa2c9f6d4477832fb4018a GIT binary patch literal 58978 zcmeFZ2Ut_hwl}`%-2l?73J6M5dhb=FsdORIqzQ0s;X-^3Pk&d(OG#d+zz}J^$yP?|COoG9-J?>@_oc&6@T5t&RJFTLx%vY3XVK z1Oxz}gMR_I72vie#N{CX=<5U50RSKe2*O&Q01)9n34Zx5i|{YM zl0dSE{!%8A`{h8~4j^OX?CIm_?d*g5NzRzrc%YJa zGnnGd542hkt_`3gCz2z95D{Df2CK05`r(5`tgTuczVP2ndOYNl3}aDJZG% z2h`94gakxHgv3N7B)>F65QzUBAf_XszkE}b^rEpX*%dDavF8cd?TmNEki6n{ zPhL<^GBLBTvhiKz7r1s^LQ+avMpjN;LsLszNB7pOVDfbar+3^!D`+j89BX zP0xIpom)YyuC0ID_`bP?+CTVtcyx?DIsHX0e1HBX7XJM=$^MO8ba=T4iHV7b$$pWG zfY9$3!Rd%eF5e`jS2ZTH^}2XP>^V7uT0(Y3I|YyU9VDaOlQBvrUWpYx)Gwm_D%pQc zuowSVlKmmrU*(zy)B(a@6A>YPl!%D%BSnl)BxEGNBr;pG=DXqoE+9_~W<#asju1pO)shIe?mo06&?C=m2Hl{CiH60Ps)BL{i6CC3kEh z?6Q8o4G!oXY8^LRyH*VtgC*i)8(NajR9s;+7;QIC=Fo-eZ5sJIDkg}jGGWaD)yG}Q z^N)#&wTU*Yo4!^5hmj8yP~2>o#H}n9M{dOif>tJ z^%;>G2oQaj6YT#f8p}nFB3X>Kbv;fwmYZ6@zI(MWuh^7QH}@#Wy}M>yiH%hQCia5- zf!KxXWnox3j56wBdiK^chGS_ubhu^xXz?pGjj*jLE?tWmWg$H_&!zL}yK&A$Ukb`N zP=4KR@l?>e$kfj2QO~^<6tDCXy#RM2o$@yd<&8Qr*V7~4!4F;U9i$;z*n>t-og(Ze zd`6h8Z05GuGV2Pyzgp_f*XK$+er#>X!Gu^_zgHrzUQ2kT+Cu|8+hJTyS>jZ<;A2SZCO6<}c@gqJ z#aEuwbU-iQd-oD1;8FNDG5?MAn9F?WaWwW2{#DN2SnTyhwAj3U%Xo1yRPjQdWNn!} zUrILT*E)TcKJSo=>NT%R{YPYp0TA?$c~s9GiIp`B=OUfNSRdVmDHcQt>Z#1r3@v<) zrl#}2((cQ>2%t{ef2&}N%3awlSs84N5_4wD3`Z#5TGFm((`-7L*krEz#DoJR5_Lpu z#q6XW@er!}AcB=%=d`#_VI|xwVLfM_WSZ`e7C}+pW>h9c2kJ7IS_@S9rkZRIsD1)=g(9#I=lZ+`1F)Tq&*GN?(;c9JtXu|*|YiZqYz}? zVeHSjpw!ZmaThbWo>Wi&5BiUM1FU>W-d8TgOKPn@X@utb+BrIu9@~&{lMfm6%S2lZ3%og~YnL_G032kjX01r;jFOA8V`Hf-!sFofesX9>^k+EJgC5(vC&?&UM3=N}|8Z+kE5lw9_IPZ0AI zsSz(UOmNwZC{ggU1ehK380pmAN{POR$m;0$QRiedaI4v211nn!CJR9EAGmqd4}rb~P> z^r$_2Y3`~fPI;`y);&5C5O2~A<(p%A@D$PPhS=*0zyW}ZoFhD}dTqMtF2Y(>B7;gl z5q$NUM+TdAia+pBF94FTo*c~Yy8NUgSW)^1ezNQY`2@aLIxib;d4}AJP?AUA?cQ`p zsewV;i%f9+=H{j;2|@oyYh$JEg!NJ8eD#@n5qWCuoD4FKC#eaRY=%46kT}3N&{4Gb zaDn>!90T^M=T3%*8uo`>-c?2Ywj1;E|<9Y8w+HFRPjeEF@%PI2~o9J@e+i&oD8~eQP+~ z3x};-uMW|fxZ3!}#lra8KrWZrB37)`C)hh!1wU8*PP8WZ4(009&UiMrX-lpot}^Op zwW&%Kd0WzHkB*MmV)F{LGD<%9ayg~&-U%`ySH9+Pnn{k{TjAz{d{idiJbl`jZ@u7h z;MxFfXh~Ik$rhU2l+evPy>r9V(KJno)gj_XI(h^O%~sDU$TU&qff~DN>2MGVblEM!QuruQGmd|P{xh^x_A2(KPf&ZBENcK236I-sJU>RoA7W(n{V{VEaur?En13Fb9EgRqosB#|W`iv9YqNcIA z!s1Z>%A)p*{v3;#f*JRC5fB_LpYV&-9lutWzP>g{@;mM5FOLdbdM%%tz4= zK@?D5q-nCJbY`XV;>{-Eqkw=%k_+oE^PXC1Gr+Dr^0;o@&?rvtOq4g#a?|zc;^lFu zJPOoq*%|@|e>!7&>+kL52y;|i(MoV%w31!lcktDkZC0ybA(J7S-|kdW!U26`9S(sj zt8l6v#r89(eSAmr=%EDg+E^1{_;2L9laf?4R|nknOg@DTk1zNQDRvecYc-1vcLCyC zu3s((JIWF}aNC0wAYJAQP`hy-91vM-)~V0fh_+!SI}_Wmq+WDxw%@NAr};20tE46^ z_D!{SjN9_pS)-=*;`3vmpTC@uftl7?EtRQ~cT7hto}JW`D=Ush-^MRH8lqqw;IsSLah;+Dbzy2`IU zjFLTfg^{LqTkdsbBSN^33}ceC&!}I?o|zP%y4SYLoM@-=-X($b@yl#*3ib%YoWK!a zQ1 z8&($iS<264OtuPxN-WoP$!Z^acZkvYF&QGBS{7VB=+r@0%ScKmG|RmgJF27?^ERA zQM7YI>~wL$qO%$3G1ET6$Aa^rd-mHWfP(1YNCld|0|#8(Z4uhyrpE+2y?1$aAN`Dm zZE;Y)DREzCkv%0NO_z%$glti#?L>OIn{Ut{RW8 zCNHNSS9J19RSlK*y?vY9);nOJ^f@P3Nj&ukJXr$H?YLYuvWswBlAy=#qB@LNZ%(LuB*?Jnhz)9_GXyGpjBY?2U#ySF+%kC1d-{2Zpr z&MkEEh~SHNFXIpVaP%8v=upa0hYN44Tn*MnU~l( z^e2$NN(z1t&;&l$1fX_H6xf@3D{vBL%e<8I0t<-Ktt736v7DJbpELQwD|CFdIs5Z% zmAWsrX&x>4$(r(_c9)2nE*3*&(N@`Laj?`7>_bV%=SK^fpvAAc*MB%sc9D2ZI&Duk z+x@mG2!i!-0DdO!!EW`HP_e&^exlpvp_&C^LeDN6MzDQrT!u%$sM+=5)<~1XwOswF zwL~htGi$A{^9GqdEF2e`JmVs9hKOm9v#y>ab|MRioc}+`FN|eZ{%8awxvDEJB&4 zNgIj40e6ygkx8ynbx)rSSNQsKNHN!X(lR|BaERp|{$eiDre$CNl;VI@v*$sVx4C$b z+Fh_PvfH1gG0eU}R=20t6BnmXC+-HTibbyKT3$Ux`jb$N^LtCyuQUu)uihr`^)rKi5??igTTJP>_48fpTJsvNK1_;!s z*NZO9A6cM73p!T^qX&igHDIik-{lydj%}NrHobBlvp&x?J1E`bIpEFo;bXY+kpt8? zi48yhuynS1ko$Z40CW(s6sn{&>DwA*BQVN4u`*e+M>&hO>IiZej&z~ame=QX5NS?pB)@_f8`}&tC z+4=4zCG~!R|C=s`;DBXVS3vrF`bbzqXy&UqQ1A|359w0xQI{L?O?pDlQuxng1N5A; zqf8e2x@u*@k1A06v>Z596&*`(BTnp_R^~&hm7_O8B?S3R9WAA=S`)uqjcC_{BZp5 z)e|T>xGHa7+_e04EQ{0oQ(OoPqHe-1fkI6NBZ7aSOoki#&GdPL%6};V=7!w8hjfZa z)SjpbcvuS`@aFi)PGTd?QIrb|EW9DIp_6X+jNa%vHH^B9F5)nwVkLq?UFEaX{tt6ySsd z9A{y^t$VdN08xM)ti*1^mfUebw`nK?0sK7G@q8KwwBYA!RPNtaV##1c3Olk+J+{K0 zd19}}|7aB5tvf$j!k9Q4tFYpL*{9$I&$FFY^hf2&AFT~b$MZ0hNw(&n&HdS#e@>M@ zZ-hU!%70mBTI8G5d^No+G;ctq$u9Ro-htYPsPzw>dJpzj?Oh0;tALGAm7U>zN-Nq& z#o&XEZ7fC^ZA64u^?y;q|C1-Y$G5NsnW}x|Nm_c zgd6{-LL7vQIN()m-B~4GteP70A{=m;-SgNHzv8;N22;kaM+xU>-&=fIiXhefAwD^o zeA2qi9R>&C0OwW)s3GdOH*#(2YB?$vzj>F?ql!Lp7ycPwMaQOAyUCFd`Y8kM?_m*l z^D8ZAoKS^TB5I&b?3LU-03l3z0CIzE8|-SM%QP-~b54 z*U>(Q*pua8MFFDSW1m>PzIGEWU48)MeXF=Q`}GU*KAu;qWue$>kf6a;@Qz77lmoRk zBL)%eY`K8H{&(NU4EdVmYO@trt%$tV#i}djbJp+5eEo_vfF7)X<1y^W4#TK9?T*GB zt4`#moMUO7+X_75@qL$PS`quQFNZk4jtC(ZSBCclM0tU5;Wn~ON5+$qmDah{-ZD27 zzuu?GYHkK=h*R4Pm*{v+thiq5ggcu#OV7~=&FXHZwgWe%*U>Wkx92 zOU#A6<;H;4h!%FGOA{j9HLJb3B_Fj8x+Q`hOB*2WQMUDb?0QFAe~CL=!fYe#?b&Up zGYTB4#H6~iElfDlt#kv$)w~jGv74J|t#qy0Sgxk)dZJLe_XT8V++O3W3oAp5*l}=m zX~^;(17yQ2q~q=vs3__d#J=mXD#>WNxv)32zL#^4b#1o&r)bw3uc}CSg9%@OCgcPU zdXK`-3l)5kWnEZ4eCNSzkOA`$8+Nys$h}!#8bh-dQxpHt$?5qA6FS5vK@V(W#A#^) z53*eow$rY{OS-}&CWUPq?s?oQ?&BcZCCH+&6eyENXQMYQ9QBFizh5iWxIAWP#BAXfB~joUL0^+>5$u1^Je7t(p! z6`K;?5_7m<<@$!Hl*+ptrnx=(SkujPs> zqy?6_Gy$Lgu8;7)<6rzo&waS{V=%vuHd*A3LR^+Si42CuMk9`VT4wV4CCZO-1Yb;O z?6C^$*ameRkjqpyI0jo}0I-4Ai>0UiF6z6xE8H3KP#a>?DMnSW61?Upl43S(>0n^vZKigQ#7;%2Zk(PKj_q>i!8|9z4cfZ{8me0 z#3s$9v~JV>;In6hImFI3ykp$^e9dL3Zg5~-#NH7Z>z%sFzdL=~uZ zDwVc4ut>2dvKDz^;?j#bSqF;7^~HPF6r2>ym|!mPFOitj*q*8nor`$CENH8?oCJHI zC!CX0u-g?ZpO(MeUk2)i#XOtJC}^F01lB6AdcH=-N>6iBCtmO`0tPy8HuvOCP45J=qm(0sYZr)Ga#(k0B{;X!-*@Zu!FPp$Mq zm$@FeXORoD(c-L)M$-J(`M%0di96IqxrNsU+SX`wu9VI^4;re-%kNAtxk5tanFmSj zs=aX?SwziNGpcDkd+#x@Oz^xrUEWf#!{ixEgO)#jff(h8hIuPUw1-1KfcC< zMu(h5St%)Uc`ITEuf8&y=7j93%zG<|{I-Wc0+MDnNDhT+t zGWVrydn+8*mFKkuI4{!#NdBl3tCAiVdvoltH?P924iQ`hceF-43vuBBp*Zu7vI-o} zSeHDXjFm0=M)}5Rn_#beROuTQ6PzqsH;fMA8CS4h_N0Pr=v7V%c~r-9q94j@PZoPf z%-5%F+^emEQ^;wHrV2NIj?iJhPcPffcxVkUh9V3vqP^Sa6tI_2HhFIEH*3Efc$D#( z+uiA`9tjY-J#7g>7ZS^*c9*@h)ISwE(l(hEjiIL*s~O5nzxhHf|An2C zP&nF(_=x}^9xJR#{c++NW(8-~WOxv|c;pxL)(&8gVOe`crjU5kCT|%QX+wlga`EzC zcK4Vt)6=H=T1>4`F8(CP{EbjT3b6VlW_dNw9}f%)+`?RKytInt=iS-;WHw$z(3)-r6@~30w70Ss?Hd>* ztfz)@ESBQBiu}3hUfB6)eJ77T|1HVXC(rc0`rFAg2Rwq8c}oqmlS27NFRD;!Lg$g; zN0H^|!sS3%mwuj9QZeKi#cuxiS~~3QBYN$+Oj#+B?;rMO{oGG1%sG~wsw>eG%hAf= znJlQ|xEeifYE6V2yIZ|&V?B2eB*iqAf&l)&!(5nA%s8`Tn!tb_LO-BmQE7Fb z!3Ta95nH{H*c!{zKQ)=Z_vaGnbmjqN)W=z`f#HVt&~l>`udw1_f-6O+LtUsJTCQ* z<^2(MbWztTxEx6q%u|hBm=u2B`l+SWG;v06V%3W~bL_HKrUkG3l?b^P>f$#{Hj-yN zg9I@*D?ORFPS@*Vpo$Nu=ei3g?8{s0YPh#Wgl))f_FZCq8am;9NgnKH1PaW_4RbEN z9EVuwO1Krx>met0V<6#&>X5DqZ5igJd+S0f-jP-Eu^W6sJ`ZaY#SU8C1I&71j4*pc zGN|sff_jll5Z?@xpF4&cgpjAXwj3aQE5MH0jE1pGETWX5K%w6?Yh(4c3B71S(Kr-|-6rMW;FzhvI~wB{o2P*56KGS$INNsXF$_S%O)VskuQ!tBR94*E;F2mWhVUQUc z(oqowWhaV#OyP39j`92SwS{j4{i}HacwFp2IShtJhtYPPxU@!J_d6tS5fCo?8 zO62}&ZBPxE4{_xIcqm} z5E*wTtBqK*kinHG?+<%S)zin*(+w@PerA(n!F2R%w1zT@Rd=SHj$XGOtoq_1l6-vK z+JE<-4eK7=Rs(5hY5Lt#m)f+*iVTku{jU_t337q6_KW!r(V47|wM*RoT^%vM)Of=G z%e4~!omp5VoL!%?wq#upTYKWAb>V5DEN^CdBvC#?G>lE zUD3~?7e?qH3m(OBm?w8u?=|0k(F)bLe<4GsthyizB!Sjk9@1{hB&r-e{A8DIlHxX` zZS3~sHsP^xed4Z9L^*L+;w@teAlXQVIPGVHKo+SU+Yoyf4)C2UJf#Z(i$HEIt@^c( zP;V|>@PU(ALY8z#bC{kzEF!JCkS_nFN9VbSaOsr}J%#|IH*3D`*!?&F$*W3 z+TiJ%WgO^=6%do_aSIcwEV%pK>E268ic&;HYeLow?DX!Uo?ALh*5tED-wNI!${r;mj4H!YM~IW!w>l#-|$kZBHf?`pkE2TvR3+isnbH0sEZjnqfW4sd!pVQxkl34QHPa5mn zvtS03<}{&)b} z@#muSVKBrhC5-kjOVpMIn=9?f|& zyjSUYri8!oZln4SB?#UkCyWePA!j{cy6QaTFVe=Fb0TkjWc0Pqq(|JM-=NuZ=!flM zPo;Wz)-S<5Vf9Pu`e7{YD{+g9l2HP$dA)K%HB4A2Ki&X{g2@QqXMskjKw*m#qqtwN z77C0ivNYjo=8aK_*t@rX}VpC*60&F(79Ka2?eX09jP1;nh;DjiGCcPfb^-{Sxp zsCB_$>GxB|*3NBViVx#IC8s?e`4T%OnwJXmMp@Wiyh?g_J4HYKdDJ=c$g}$ieG&vG zGbK) z0Vmt5U4BpH$~xnjuf7c^G>7PAN-{-C z?rd~|=PtLF^QRik_{M6v0gt|ZwYg$xb)Jz$;@8Kd^7PR#yUd2O9DQl{g;*L z;7q+m5N-3ihWg5Am6{NPij>h$DQjCFBrjThB~-m8I4BTw@0Hi7o?cRnQxt$1LfK(d zXGE1NJ*t~sM~v!F>v@msJ;dj*P_P5lox}_uv$wJm?>N5ryEgMF-trorq zF`~zi8>UEuvNCjd_wi{;-unehmv}$-KzCotz~tC*L0)~w^3t9M%LQL3Y{6NxisD*2 z%WS|pl!o}Ig&et4GU>@My2(V>&k=v#7bL@a7#3 z=#YG08tIcd_so1gujI4gH{Q|rt_3U4WvNNM(-w+{c(lfSs4jo_p7QvMrBLFnOLeLo zw%@#YUkWUHJQec#I-LQiweHj5k!^OV{mid+lPo#d+6%1jC}bU=)0{_N#L*cp0h zRfWm<hloXkZ?9B&xp9a)t52z3kr_ieUK zXCSA-Fj7HDK}F%Jub^>>WZAqH{9D;vY`J@;XL828v{xg`kUQQq)o=>FEgTbZ)lr1! ztMqGP!uDscD1gjl;oo@9cdf^HUHBeE1XqdEGODa48Ui58Zg|%sSIxA<9%!Us{zGn2 zx5N=scYESD!N+W7@f>1V4|F+W0gvCgqkrL${-d7+lz0xd*8KS_?8tJi*yL{^#$QQC zzs7w068G^hm-~6+c*SnDoA~o0NgP^gF2KAQ5<&M#$groEMoEVlBhT>&fx$RzNe?Rl zbB9e-!;i;tKrG-sW)7bR|J>m}|Jk~#eKd0}uZyQoiK0hI7yebx=`T@-FKP1H&k*1v z16p(?OE@C0Q}8?(`1(SYG?@E8VFQxkrhnT+bvJ=Io`v_s1Dns@(Aaz0mu%HmvyLQnAqamZcd zVtS#gaXzK!^tS-W`U9B*zUNX&lf`bCFPd{NJ<$Pt`LjwvZJFeFFjS4|Y(WdF?_@Kb zZ&hM-H<|b$;+}QFd#@K7rpWw329=Cl-o(#cM{=RDErqt;AFRV}*r@Y0Wpn1*b9^)j z_PzXi`5mn)C!QQUcm|I&RqwqRG)DEoEmnf-ruiL=OHzh3mX3T?&Mm%XXTP@UqW6pk zlGGdQvWXR$T0_H8(z@x!{I|z^IIo;`h~3jBy07Z9KakJbDRzxFQKn2cy3K|@q%C*l zz2!3yO{~l&*jA`JsORpWfO*DRvU0%F9|!La?mw+VvafhXY)tIpBlj%wCaTfZd5+Bu z5ns(aJ5+ruP0sGQZAxLiGd~0?S)pB9E~Cu4;9*<}(@na?hA>~`xVz<+%pz?nh@`vh zEd^KrF0zvDlSSJdJa^B_vH=#`!uJIugDl$t`L0rBs5WU9ebM$^fn5jvsBkJ=IDc(9 zPZDEL$FvgSAEsuJP)F@^`a(1W267(>wV5zXn=>To68`D(ps_wm$G!cIZNjw5L{~RM zmtoKwr%5e4A6GL6akH9f;c|gdxMxq6Nv1m?r5cO% zX*N>!Z%x-qd8-muY86-X*5?omgFc(jj4H9AS&?|eJbAb(fAd&cE_4?zlI-hSu(aSx zKjL_kBs-zhG3g03i{OjH1&Sd&s(`tK1ib`nCuvk<7(D0vA?GDD@(@j6`V1AL@$@-WlVITJZT9Z=vOT2E<9G zNL9=9I|XjB`+5%{45Mm;NN2D&TH_>HFNCZHlt?XU-I!0O@adXn>d#KU>0f-t@#FF8 z0~7qcxLHPnAwyfOh|+Z;VnI62r8`_MLOF?B{>48W>POy8r&Q{UT2(p3^h5{iu#nch zM_VnsK!pRBKS>~b0`-iN?tQ8|SSQ(C|HK?~cD%>>j%<$H=1VW$MGfxqWWuN;Tf2na zEKpj_2oMNuI*e_X$k5iVuAZ!B5q_glrCp>MU&C7_$S9<9&-6|U4sczgOXyRGF^k`Z z5wtL&!@E+su1)#8g#GEhJ1w{_uQcBYiDrE=Y6j0>Fw^vo#7(k-8%s_;PgJcqX`)Px|Dkerso8%H&HgBUE7Samg3|a!R`M`+%5LH&wUBek;!ue6#8o zxyWq6=n%8x%co0_Tz_dFMEYLW?sa+O)PU3srRrQ~MX&k~XTn~7-(^VlcFMl()aGmI^aClsv?1%PD_Q#*oXDHYV-b?@UrV3~Be_|$a z<7R$0Q~x>tPbMp&tiir}+X<_?ZipI8vQ0G-=B;zpI7) z;;c5y+g*F?c!-Z4Yn%6_fzZ8oW}#^H?w||E_-<|v^w|pR`5uO1WiNb@rWqey$N57r zuO+uYH#%0{F-rH6ens!4Gp$i_s5jUbqmG8CndD8T_PebFYU{>#9#cH>c+kDjc5}h1 zf=l<|Tz7eANL0~sYYd#*9`AGn$6>{$B-r;rkZ9wH>T`9q>WMV=h@BetvI=cq&M$B4 z3@VeKh7VGdCzJ6={sg_m63s*8_Qz2Qwut5@GwN0K)ngJFca1HCigVM%X{{@I*}pQ} z=6$%b_C?Nn$X~Q=WcIPM4H1$RtBi+{anX-G@3beaVy+BP6;BGKQPM0fR!;D(lX+GNl5E8?tq!H@u0& zjX%>TfMq3f_1#x%DI0{HA@88Le4_tG8gO!qzk(KNNU9M!79kqW78m*cJB&sozrtGD zxWcWlrYKfv)}s_N|NJ_;&OmD_Q@4G>r+UQ4Ij9l374I2NN#pLMvC>1<#s@BQvUd-P zr6ZWi=ZPi149OW;}r*6J6vq z5vumZETdYMzRsB%{#OWP*_dM*sJZ;hG{%%zAx@nw66rm2a)a&)X*BWS^h1=-G0Xpi$ z_DOVUiu1Z>`KH8i z3Maf99MO>9>B)pDu*`$&Mp^jafdnA@${ZU|Edxc`jA} zJuuN3#59>*RgMa4T@@7QvRhh@?x;%v&uvS6#sMWvZ(p{m@=G4VyURi|ST?RHvL|Ij zv__4ZRDHY$bV_wSY$0AW#)zydrOrlf#upY&50N&TXu3>{0~nyT`OPI^uQN3&=L4(H zb9^l9FG?h6nRH%ou)X>u%X9?83uaNEBJ1^xRHB*k2?)H{ktyCX$v<8u#D#D-()4tm z5%3@`JbKL27HxM^II)+7@#oackIfbB(MLXk(&p3EMJ1wLGg!g$$sZe`p2B5^{K@$y zOH>-8m7g~&R_&_~MOF2RbD&iwWcT;hka0Ljf(km zg5(qaf@R>ToMLaRsDhrq|1*gxWJV9%Q6%Nc0jxT7;>8T%s&d1An_!ivqPLCWswgAo z4hr1eSoU&>s>WKnlSg%Qr>4a4-sGbT?+^N6LJ3w@>SXEk9|@==Dw+6SF*v0iaq_f} zeaBvHM)6wK7Cx>^3rxvucyvtg^USxBn8G&Pc~&S3i>maq&ngw~#Q~%eBkYj4`JQJs zttDvnE+Y%6w<7sjh8*J7=He%Cr;LYD&I`u<&hySbcDFK}i4d)<*zv*aMa)ff`)c4d z_}#I~sNu*!`wyfLrzs(7sp2tNvcX!v_|IZW8oKY2;)1Z~M~gSo+vfstK-?jx^4)3C ziJe5lbjHPIBv>k=x6(Ei31c(t zfk=grd!+*l@lT%7GuZV`h{vi-;^OVotVPhe3QbOI6%H6_S~}h-m{-P};de+ub9j^K zkMduC2L6{GYCbyXO)NOsR~!`Kck%Sjd+9_DO^=g9uX4}`e7x?F5J{)#msCfRk>US zyVx0OS+d34$tJ}Qt`nPP%mqJACHoqy=hoE*c$}X<3S6lfA!`O8@30Cej8vtwo7B70 zsk&%kjFKnYLTFR@eYL9=w=egbQFdNZ5>wok@8YJ1`scfzmd5Ali0mrJGSKx^IH#Ja z-)n@t9Hwf!WqFO7k<@%6sXIU-lL)z%I_@gU(BjijEjQ0CvAR>Pbw#W9_^H6aPyV$0 zGK>^*Bf|IEp zC*8!)!9X>4!85UMN6QM4-xU0ku725J5T!IQ^xm}0yo@I^E9gKC$wD{E%gaZ7>N6pw z%Px+MDZ7(j#lkOS-V$;E+N7*xm(6TTF;|hP5l+o1KbGdZ3spIr4{P$)%B>cJu2qU( zzt$-x0w^NI(^4(*yJh{S>!qbZ^BxbY<9c?4Mb_%#_7^Xy@pWVq>Sw*8*x=xj$pT$b zl0tcg;p3PkYSM)HwYXR(Hn0;76C15{Dc^gtIu%}(OZ71j-Y*<}B4^iiF?bcB4a`@jXGwB{Rq65DB|(M_*F6!JnJ@I^~Jm`V4?#|s6v?&WHyiyqHoPbCO8W&Zs7 z|Cv5vkuZCg*M%zgNoeQXcdMNcncJVlKPUuBU$U$H45K-2MdWolc-~d(YoyV5>n3s5-AncS9KpI8`DWNlcYs7ndQI|L|Oegd_&!rcXIcduRS)=_>8`db#9{sx=X~`wUO7xR! zQ^HXG0Jie9-HpzDZKf<XgF)A@U-WSMjPs$d{hx206cliG$vXQnF4``=5hBvyrGx-f0cvbeum z;UKCflqrcGnFFnK$4A0D%|L~r(RbrD(@oeM`^T?L<~@jfblj+refcTXWPu&QqnYq+ zE>aW@=<$rmq@Te*a_7{LaKRZ5^I)q=^@{Um=4>av^wpXgS>00al?eZq=y2!a)GesQyuYAxAP?@l=lEzR*g3xI(%bJbxCr4FXM!AZCS}7rxap%{pUDK$t2yakK%;$HvY4F1QWeyccG$b-j42`h--U}FXS7k1b)hQ z`WDW~*dL_}G&N7CJU2LzF{$52}&ca+VD$J-qA^<$kTI287&DaA@k=f&>glM3bQ zlrg?*P8-|ca2Oj@etC)Hd+dkpD(70iAl@XaF^qVTvPNAuj;3Su6$qafU&cAy1aNZJ084g+#c_TLUh(OL(h%leJ5^M%eU3Ys(}#_)^r? z_D0!kdb^SU64aUAe6f4Z+r?}G5@Oz>_ppiiNA0m6sZM15*P(0I{hj3d*TeJ!ss68_ zaB>}%ejazdULlCcDQ5%&nRbo&)9cB@-H0XQXY%vh-QBlXl2GgKtR;aW;~ltqk~cBXiOAv;Hp?O+R}-qCMxjY zQFRh-Tf9+`Nr!)g5@E2yQPfY-@>$pj9%9~muKndv>&8piFm0pH2KLbW~#?8`+CK3R0>H zC|BDuXx)ryeC)zn*bVK+S+HAG|A#=O|A@U~M&g1}Zj)YQR}#TLz4Y5S31V}{Cwrh| z;&wUt+o!wK4vse3kDqbOK%TK-@F;hvJ&LHa6(m97!^w7}qoDcGBA#|LNIi1L<5A6B z1-9UL_F%}x!8gk^Emlv5o_+G5jJvW`d}57AS@49lyKLm&9qL7FjaKEe7fBymE=A7_ z+Jw)gg7NXP7U)DU`OruBj!X4LI9+r4oNf+&` z1;XtSoa}d|4uvUuHXpW*eI`6*q&}<-j!=iwE5S-~1q@X0(MpF&in5>^ z4byMWC|G`gQ9#)-?hB^+}ekJZO>w4&!nuz{@@e6U-h zqHEMX_}?Au{O{^3x{Cwg6*!>3^@R550AKdR0r*ze;w^KUQn9r0k;*@f_h3kqd>ATC zd4(LEgab6Wj&T5qr5_Gp&E1;B0UJMH`<_@0#U^|a|0t+_*a^>c!GGn_olk3&PtLe8 z={O((VL}p=Xn3^P4VYE|BJr( z^Zx!*ul=dd|6IrZT)+RcTmH1y{#V(l2Mc5!Sg8qLoia%{o3$HL1c&0gw7_>%mmM$b zLhQ`g4HdY&Lb{(Z+L}^|j3ox8p5Kwmm*(?hYIZ-B!m~IZn*RqILxSH!U5MgPt?^C# z$Zc(F&+g-n;YEv-IpTZ%`JXbdKaAAbsuTOXI&zZYo|)pG04)gNM+hC)DsePqNODT> z*tpDv7Ed#e^1{6q7gy-@V`tm08?p}hdv@!q>miqVmBVZVP|!m6EWX;Re5-}8UhZzi z*2PNdIveY;d6B%atQEsrT5co0pcpPP6j@KSG8fuw?IxP4(}oYsq{|y*Wz|WYKo#I` zPG9VhA`N-`nG(}k0t71OU~VD75m2{MsN%RJfl~8IA0o-PmV8;G3NUkfMc3Cy&5-dv z_^~TK0Hetr&(O={s7HaL9wySr&91~Qve+Qh4*Dvzia2aDqU8aAtRx5u5!>!uY4hGm}d-uy?JbqD_&B zi^fIY1FpXE(1D+6EnVp3xt^aWCPs#TI7i6xNc18pYOzS^dCq<7_K)-JNBMXHj)>28 zF6mB-tF|x4qwnlMn5FCPgaCdiyP-vmS3$I|g)zj1G&N(O3XJ$_KOy4{C^A&tg)%R4bz4qQ~ zueILyrG#^FxP%_{z)!6@N7Qj^`zCf6!2T@Ga7=J2r-E~YE6;p(<=0S`f#|HtFz$V% zSVL22`Yl1T^aW%zWKy-NrtyexHm-Ehz^w+@f7VxO?C5HDlhj8*1`#sTJ}1Ql4FkNx zEy0_S@MANV6I)$bM#!p%Ow8SSO%IEQig3bRYXP<5NBFv3`0b=+!^@ap7*D$qDqpIA zvVq27_2D~`1|;3>%gN7!bTaw+SZ=nQxvp3ATm<;+p*3>c@NPc7z4S`{%mN8`CLx&m zPFyDZWM1l20CKwW(goRKVQ`i#j1m|qA;st*3FF35MlmDpf`6mb;E#GPoZsVn^<|`A zpf>_q<5iJ(R+E;OXW=zD#;+P*)-(n)1xkGBTPe7fvM0A_R}LWO=ssjV{QJ~1DbEL&65kICkH?iLf@D&^pEK`MZY&t7a$j z3lk(A!HuFF!~+xS1ZxIf05Fo3Dg&ppk)lYu3T4q|K2Ca5yJ1Nmq4b8Frr)vB)SaF) z70pCRQC*ZZI*et_x2Zj%JXvZaXGRKZggHWQzX7ham)(GJb+=0oQ`Ltm0$LVKgd|0^ zE&}8R;``BZjG%}9o^%lO_0SGDDPb0U0p%+!EX)1QVj=v%<0)H=x;3F5?nLKwqH8Ir zPX`5u=E>8E>QF@m?HNv|?eOZ2?w0foTHS8p>v0VF54-{Bpdw#sAe-bQr#J4k8C=;W z7bqNe7~rXwy`v>&GF`^d0^&cBKTa9Mo~#`BG)VbYmcJjjB)l(G6n$6v;YQZWr8o~ z-VbYDHtMxj>g%T_1;pSDm)~X+zJ0k#)5vugxKc?=Cfi z3anfb_TpV%$%eB_*zyPR*Wx!kyz}-=)bnV=WVn{Z(K_+1;JAnkWRck|uf1G*kzKPP zJvL`?-kPhLCA~0T$$4}>%!RKuVrP3l7oY8EOkNzFag;BHriw(5jH#dF;$WAO z%!TrCDTu?lSwXd`e%iRUy0+XUk$^?lB1ZFxKmA7(4eZ{q7>7y9(~-vUgj@-lRDkDN z=8}$RP}cCrrKRCjt)zQL?3I0CB13YUMR!S+xr9Vat6ytfGm;m-0%~1o&TMCYiB%>n zB@6G$<;BhE_@Dl%S+l8oO<} z@g)c6MLSew#1YB+$EJY#M4R)GFvSSma={+5d>$0C5k-{gCdXzj$6MM%p@efthX@x5 zbp^#u=A3ynL2DInaC%BM`34?}eL0%F`m)|R-4G38|#s92!#QK!4zLzt`*L<=yNIi8pyn7rjk$it!Z1v9nSgg3M+Ed zNUAXY8K4itXP%`&mYSe}=)f7D#9E>47{J!H-mBdg*1C+Dg?c?P6ckrMZcJ9DQt$4A zl4WJEg^eBF9t0vh`Zv-?R+$4|QVR-X0&^&SQ`iOWZ8|rwLr37V&^RpUD1Zx++ zXbKpQLJ*jj<#fAuj}ju@Bc_5GZ^%B|d;T)3NL#Nn7f8@cNc}u)7B8{KnA;CAs*?=t z#+cu}W`tk;aaa8_80Vogk}2aH%|5dGl5DgDh5a3FjQ-7P8&4uvk5Ny@N+-C zN2UZ>sNzq(&3rem%O~HYomsl^p#?>}Ij{78Z6~tKZo#-VI$tXWrQBKu`wBJNt@758 z2WAqbe-=q)sZk&r+I*B8sDZso1b7p+VtX~*4IEqz2R?61!o}9r8^p;e7P#9y6&BV5 z5$TF}kO8TArQi@7l628t3-a=C%-VU`!6DU^fPa(9Xv~OM*|jI#^8(e)I892?3-V}Y zilJrvOott39e;kY?0rQdvUp)bjC4O@43FRAx(rk|YLDG?M5_z@nyb9pS0(mCo!*;0 zCl}?rOLYn$t!{NU*G|{ieyZVx%hJAby1Wl{AMhWxui&Quyj32}7Q>eZ-Z6C=EbL>ith>hvHYc|lS?pLc7|T=T)hQOV-%T9biu z=I}=wo`U7xZ*DH!BiDAHtya-!6Pa$BjPDyy>ph%Q5!^X& zmGvpjG?C^h-Xce1J3Ib7=X|+5nHuRU7Rxs-2`&O(J%o_RMu~EqR|X3*CmABuIec=f zhB!Iz$*Cz^L=pEKYeQBqqaM8FR9DI7$*NRo zL58p|Tm>U$%@H&GInKv9$V7G`gmjh$)#uf4Kxa$y6`$v$I%q_CChU>oWD{Sp<;3;{ zxothj_^v^xXga-Z>BciVrX;*#-?|0T^;w@IWylv;7G%&4({LF}k?)L+ty%3p?gy?C z4u-pr%a#p7c~}EY8#j<&Y(>g_^cB&(TCwxrZ_Wv=`BS&%z9y)5)3!Te z#8NQmkckr%5hTAq3b}d-XR?}Af? zjPRb8qdfhZ;#)7o6m%^PLqUt=YBKfWuK?+F<9$ISkhqaPhFX@pU4NXMUU!lHvF3$Wd4=%4c|$r;0I&Mk_oP8ZJ4+P z85;T9dr$cHl8;`qCwi)IP&^e53(q5?(73`M7ptgs7w2gG=$;vr*x2k{sFldo?(=qV z&HsqQlOa2jFyTb0kVAc6)XE_3<(^ORJUmg=9C3>9bqy^x$N0ALu0H zwjc+XM4biM7u`}m;^=Rl-p{F(4!K+}25t^z8?SfColr}UZyTiGNr8}E#+YS4Ny0Ov!(TSFs;){=bf7>PMXaE6c*~J^Oyd`TtVm z|IE_*TWqf1d&Z9q_g4m2?3dn$-zmlL!}z~y{IMU5iof^T{@0%WBhLR7&-`mf`>pPa z{d0w;vu>a8FCPqE$Je-JpnQytJZ4_?B@R&%>&prO=&}gH87d-}5iGM;{|&Bx`o%y0 z{6AlRvPj5({rNu|=5M+FU*n1O{pnFZne!*>GOu0#$-4j9#_*ehe>ny$mPo(%u>%fh z{|}jQi;y0g|0|-Ck06*8qRafN*JmLB_?x(y`!bM11hZ)2u=yYV{?o0&TLnl_?dBl- z>sPgLDS*$PMbo|)1^yQI{=Q)5iGzab!wiI^EN)HXw9RwrkWTB5RArCC^6BzbdsZKv zB4*zXri3-0euaxenIav7EfEisWs;w_R^-58&byHVVz;!Bdx>IOmuqnU{RTK9L~tF> z8ZI=N%E2RY_Brk_Q&4(l!_h0bqw*vNj$+K;GCOSrt5D2eV_FopZ{UMAO7=W z{`^=!{(aU@BmR82R5+YpUTl`;D=wV2JL`(o=<99Wb=FV21(LWdjy!pVQo0w(u$`NJ zBqWj0AC{perW-S{H*kpDXObpj_r$UNi^M_Rj4z*MZb9C6u_$PNq^daHY4Na^Yn&U^B93(ehC9&m5gu@!A=| zo2fH`W5gD(h@Ricj%`CswsWv^Mbrn&$4cH?h>>m|e{+Do5&}pHv2xNsc=3SymB;JJ z|B2$$xt6Ur?>x>&IQ>%xq=s=zMO|Ie4A#5sv}I~1%bii`gXH&=r5-PRIaFn}nFsOE zTAPu5F_-|(ynre-MD)IO^1tkii{kz?5G9ijkI(;9cziO@>h>l!!b=gr@mDkZ;~pp#}>L0h>zLJWs&GIv5WIEk?kciFn~T7eN7;gN;%Polp@`C!fzH(T6_1l_p!e*;LD04!MwSQ&1s|Ey8=U;0FS{zxqb za8HIS3O?4lZIt*1$kMFA{n?49eRiv8F8M5xUcx;B7aP`F@Wr*#@)mjK3}v!}Pc!M@ zT&)|uw#sNRpRQ49JbEuTl#6Cps}<|C*+;hP!q07wk1c=2qaCzf9^&x^*-XTrtqx(% zpVAjNRdOw0j`r@bN$HS&R;vc88xU(HYwTNz5*g+@w2@4|e#3^j)y6-}ndy*Y?FxQA zdogY;Q9?yzQXlc~E?!uG0fUN2m;Hb;9a<9L1jnbRyd5%n2bQHhwO{Q4#3k_Q9Z9?! z6vJCp4=zI(q@RzNNbHqt6Y;?Hw>wKUl~#1r^04Ql)Ss8Ik<2DOKnTaqh^AYMHxB4; zTQS?v_Ixy1d^^f}QLE(|y|X;eqd&M2R9BD3S9d2rB*c%u4KpiatJKv~2ofrW)$0n( zRbYlYKJXn#-=C-;HbK@NL3^%ZUhck}U>lt3ShT|n*h$_R?x`qpo|KkzQ)HE+M^c4X zp0vZp8|0Lk7*BX2YT2t#+{Z*TXXqB&LroY9C$GFJQk1tC=c?Aj%I6qv>9=|EHCm4P z0=qsM^TEBfd*^(!i*6J$&DFET2Wb|OM)eBypCGzqA0z0cgWn_OV6*Nh$=cbn@y|on zpEbN~*evrZT3r6Dkc*WZ$wH2$6EspzulfE6$>=N_%D_jlwJdM6Om`{O2erZixksHXy#!_u=shrA))H|!CSZb6-D49e@!iJ2jVs4cq{TNB<5!(P!!l7i-r2vpSG&P^#L z=2~MF=vaMyD@i!Wl7RB|iH!J=Jy`_88Ne6)mS=89pamCCXR+4~xfbjx@US27;aSXL z#~USWV+kSS5lEaMYDVB%&#Y_hC0YPy)jYE&A60W>qRq(QU|#S`R(yI@J9JIe;{zck z$~#^ZUF1Zy?@kZ*25VykZ^uCn4cth!HHnO>eRL>MNWITy1xjFWxrWr4g4|v5z_80O zxjPv((j2vHq~MaPzJ=a?y_w4LQG&SEXP;dzG8=#GZ1eJYj&>|sj z)RQvoqD(4QVcb%((!(zg5V~nXl7c7mrn>Mmrg%yDa99bry}{=E6K%siP{tTaJT#o7 z8R&AvS*pp2)MP2aLkEn zF6$e?ya*$}Wjsunmd5m{g5EqZ6y;6Am2?l#Ow^_@1w%vNg@vGqLsB^$E={|v7l-dFeB$p|a%yMi)OUe9J&>3V`eMS8>T4xT+a<|nrD35YW6cpwc5 z6{?~hdsMj$T%^>)Cc7fGVcVpX+FA0T&+Mh8IW?1edror!l?t>SSPHxo#{!4|kr#KI zZmD->W=%{Tgirp0oFV7LVN6hAm1y7|!oXwDY1n;n-?pYlL`-WT{0O7f<-%9Xq;z9BrNyt)HC zx4gu#Jl8$QHcY$6jx@B!mEbswj3@;V=<+ubyW7MaubWX*;lUxXQFH7XgjZefnYzZ{ z56Gioyu(vB!%!RM`O*nU^G=IkjtcX36XkYDTU(hZb!rg%Rq#dXMEJm7GU6Jm5FlM) z#*>e_q{vElK`Vh+>$SQ(e&%OQZHu|hZrZ+L{+8}_<3K^aA-(H|<+XKwxcdqh;~>uN zvzCZqnmN-%qaleprj+sQbYW@z{Ch3{71Y|u>Jp2m6C4a<*oeo6Y84gguX0uqH#MZ` zSXX@aSo1QT7C^Yxx_NjGUIm5&o+<_6fOd-i8~=2@Szb2K zYYJ`7G}-Q)jpiNFKIG=gAeC3R1F~B!kESuM8H^o7o9&N>D7uKt!3zK_{A`D&7kPv2 zoiUElDJp0QyJC0wu!c3Mc;uQxrJql7@R6A%b*!m!%ej$dy=50f4=GA8dG!i$4SBi| zmjpK7?fM!eBJSJ^|BQon=kj!6UZg^jqv}ZVD%&Lt5=tNd=jlTuE3MgrT(W`nzr5}~ zk94*(y_3p5(?KG@4@IOSpf7K*dseQcg1#W5#AcxcO-)NJ(B27kbavniOl3?eabQZC zps$dj6N%__6#ddG)XNm{0dPz_(`34eW7IyvCuqYI`Fz^xYAO&BI?50-6evzE?$VI^ zBxTPuB}KC&=v{S?_Vr^(U2)abzLwD}tpF;{xFky0;ikL^c8l5QafCMFkoIY}3xTId z1%~yE)`d#~|LdHYBAhO7y$3NzDT8EM(vlPyn8yML{wXI4j?m-weIoX<(!H{KQ>ABm zz|Znz8A)f08!BX&yf%~tn(EOJYrF`(%dpuVU9X&_x4Jzbmrm_Tr*D92(nL_6zbdPm zbl5{Pb2Uk3(}8trw>?juLRcUdfwec`5!G-rJcV8}J7&<1P|HEpDH}lj_<*~FtQ{@N zef^c5r^w{TD*Q#m#U(?z0==DDfl9)YQl=WVPY*QE%M0^W@(Tf!j}91k!6Jq6m1S+_TPPxyB4_U zKenO|r3tIPany+z)th3^YKS4DZy3+1iXS*0k)lJSyKijopCz~gZChDwSfpqd5H>sF zgDW){$S;9O{FathWykR;L4zNnskJm9x=&B+L4_oz~gu zvP*6;PBut|8%nbx9z-WcD2t4tWh~Mk>nTtbeV3uhcu= zpmn5M=^Il*OfVFUVD$9mm=OjFM7k()VbQjsZ??pDFoC@$7{P zn)HfclltQ#79Ls3=5{$>j+Q=q#aQZWG(2ceTo|Hse`-tnfbh897MCj9=#EX=z&VGq zYSR>#Get66a`JWq$a%skD}BnwCe6OejG} zDK8s5(t{u`{dOg1>`p!2Rs?pqb2KGfJ>y?_H&|TW5PX*jn2&5h8Np_!49$s8E!4Fh zU(g@s%T2!dkh`kPm&Uz&-_?a8>4{csiiZkXnl@~*OWkd}DqQJu^qCl#U|uT6P*>sZ zf-dJ1MLXBJ>ZwS9NXHv6YfL-ddC};Po*DD9mB%=E7fsb>K#SMIilF$Xf}j1SF&WzcBK{+C8Fxs!fU8JHG95>scl^NJ z`&>(Uc1&)u0}tZ+mxNxlvGn%)NjvJxygf4_fq8U*ZR=+FUs)cO$eGNDO1dv8&ScSx z+NHPNm^2>DSK5w8G|g54Mad#Hr%y{>JsJ#UYi3ygJhsQit`0h(jW*B*f&8NbNmpjv zpqw9CC~Bc?X+D*DhB?PAT0`6nv*0!{PX5EST$hVcUW86V*m5_y88ycd-$Hyzj@zpp z4+Rh2fTrCb417uY#)xUHC~Q^&X|Dp$hifG+iq5>L(#KS~SVckyI1^SE!cLlwZAdoJ zxhT3!$oqCp{3-rvkI`=NhN(_(>=YVKI610!gKarg=Si)i@!ql`kbXvgEV0FxYQADc zWJ^2iyr`_}2NbeF7Eq>ic=wX@RR`(X2|Zo-VT@1@_bY@pn9r(GHux-{Ey_QA3Ky-5)TpZvb}28}Rxf$~kbBUI zGA1bKVtzEqWkoJdH8yVL#U*1LbdjA;kxRXG=8WpumvRM^_X5=NT~UKH?=|8DV?98< zLrQL^BUYX8(*_Ig7Yf~}^+_jSUzyYX<0LM|#Z}Ni@>_4ScNuXidoBfQDR>BR7Hi7* zgKN?gd~EX^T(^wedXhOy;K954U{05xJiV8_RP=?fX<-U}(s{#4q9|3bLqWHLM~StT zu2(Z`3No2%xkOm6$ypcGyX9`Z#hMHic4{k=&OrQKAzEykK4d#Ich8iAT@4Xq1~vTK zlPYvfH7D=1~#3@d{CSzkBC(XAgshg z-9xB@vK3A`&EwE0*GxFo5zLH?(EFjAlFF`yfi#fD5VR}-|0x$@t!Y^K4L~jV=LCZ< zSJ_#C^{?bc{y+$Tza=;F`;PNJ8HQJJmJU0nIRoR~kXIMzydJ%F$GzM~yA9U`w6xCT zN7;c7E>}e^&+c$DsYKgEs!hX#@K)Xn*UV{eh4QKL+h@9W=OR z((lyi`qOgxV|o0wSnD6&)8H~D|8EW&3kJX}1wkB33a}L|^_y^#{Uuw;@Bg<>(&ClX zYrfmu5ULW6mnke?p2=$C_7Wl_l)6oJ7?MRBAEB087}JFnI}YWqzuspOyy$-M4bZU& zGyMjT6ixgFc!On>pqp0UQ?f%s-crD`V6a#pucN<_n4I3{g!E}Oe++FW{hDJrQ0$av zI~|5swGOL>j>w>=F-~HbzPXJ`Uo(n(u7b7~*WBH_fZ+&aRmAq?mZLzGRv+`@M!(dc z`in}RXV4WhEK)UQO&gj$!5gpAkq|{k`7WiU7wZefz33gDh1uDpzdeka{=>W6&&6$% zG1TFoYP0TkK_bRQJL$xpz?H^KyvZt@M-mNH;J317Q{MAib~dSSUG}OMrfKyh%jDN$ z(Ah4EM?e~a&^4m$T+5dB0>kadx&rn3y}UOIFK%WS97lMJSlkrOy=S|i{ z5Z&S66@`IP&zfROm;%TZE|LWrUyGKnNH@VC`dSWYv?FiNm|s|wGv>^JJPP>aF`3`~ z@)BsNK6p-H;4-rvZBY{}=hX0K#w4%byno@s@+{}u+_xkFws;^ zv_L>cL*%1ki%H5suiSarHg>LuPk5S$Mkr1x6ploM)BS9&EsZT{8z(+aizMO>llP-1 z18FqSP+Q1ESrV^RO^?LXYvG)bt zQ@y|H_(3WA)->;6FsZw@;&3~0h4_w`8=)PtACDh1(ide^Yx3J7NHUI07xc44fxfh% z*~EeF!6-^ZC*e?Zf~I2LBN_U&2w=B2-nCEUDp!aI2oesbo})FwG+1CKz)&cej;lIu zJGma;K^Obif)CV&*~?44|}%2DxK_> z)Ov|zs>$4H70WpL%idj;v=>mmq7;yhX|$%)%RVsXM&G z%!U7jyfny;Xu)OQ!y;Q(3hQYM&eGF#md_Iar2NN1g|lL)o{pt3*Dmk-HPG8E)JnJp z`EsYlDu*@4lP#@Csi;1alpgE{VGp${_ET`?rJ0c?4qC0e*fi>oOx!p@{2ny8mc*A8 z*5v;Bl9&q$kDiV#JS`{7!pwMZU+z&5>1K0%3q6bn(lI8% zRPK*Q<*5T9uPujsrRny;6=lB`Z-OPR3N=yP1T%$*u$bp;&qR6v*^d|WMs(f|#n@=g z_tEdqRoRY+PaSRfigN179mz;RTLbUvR>1gUs30q+;OGJ#+Hc!_{)_Xkn30p|!qEayi$k(9w5WpR}E>a1o?F z8aykb8T6n$5)87g*~BdDdVu1E1Mp~pSKS)!Bsv!Fl02OIJoj``Utiv(m(Y@XsJG%x z|Kma{yFKG_FD2l+m|P|s?biV^mqem_QTS7fT1)G5s9}Jfu#M(J(5fMmiY}j}b zD}I9aW=Dge)K5q{1Vrzr*#)VL)T+8!eMD)|T&l5i(r(=T0yD2=OEKZzd_e5`cvo@C z#yMPgR&$b3FZ9u8)4JM3(Y7Gw>unL{r&5SumsU8cr!V+|PqKoEfG^@oa0 zh}t|gjy!JxFyN{WIrwSif@Ooo;vT-(f_r9gBGL>0T6>C5j$yMtMq=LMVt4qI-%;b& z#6GDO4nL3M>l96@EUC067MQ8jG1Q|kyQ+HMDHKEO6QxLQ+p#`|mIl`<6=j7fLT_%l z+Sh#Wt(R!WGX2%2S6f@Gs!Eio25eV|kCI}rhXuM4S=RBwi#LU$Uuc_2>^YP380LaN-J{^m@F<0KHcFJM zSO|^3vlyZqmhzbM?uSFZAdlo5CAZ3r$LEMj3hP>IYn7S~)PWHkVrl=w@}< zXn`J>ino{0L6JFFa&SUNzXupu|T!&xI-uP?51v5Fk>WjYtdZWGp zO2>S@OqacbcPD-We5E)myFx9yjnKVv)cFR8gmxC)c|)n-(tUX1b3vy|+rUC6OH-QXGqY zhHUX$b6LChr)BbRf=9U2F2=P0#LGZLl3^yV)Ozgc`FSmPdOcxMx1CWy2KAa<;*$W8 z${s4&yE3JZA%K5`5Bp#BnPV@C71xeYT(=fw>MxfB^Sj55{S1d-TdKu$Z&$Vfl{cHB zV_H~s`-sgZ@czVNTp}s@TauZ30Sf&L0TZkdIZ2EF+=7xzW~8GyX+orJ&HHtAwFKx4 zZg2ZcVoiybf(mPd3wkBHy|&hH=lnYxZj1PfGOg7tmn|LX<8Nit@b>$e1u|8X1^cN# zlq%ek(cI?3<>Y*6Zd!arc|NubeFFr-H7yIWzX4Xzz5(p=nD(NQgA!K*+0veT_I22M zw&^RhJSR-19OB&Hk@XW%zV%LzVe2V4Upz&UF=Vde83M-w9Z8&$Vh}!DvkkI z^MsR=;oczchaNou0I-74cDbIGtS$=F)8XP^?^}OO|5Qlq1JY3j4iV+l`)R7}sqd$K zf9G8NUqYy?%e8)n)4`|u%=CZXIo`gE4*~S?Uubgk#v_;o_rH7-v2b@P%VpHwZ8S&h zN?xKIXkQ{=;-X+~&uW`LSI5|GiYQ2xef9{b++7GeZe=3Lt3H7*j*Vj85X45k!G&%$jEkmZ3K&z#k zm<_(>9j{uX>AB^r9J}u?H_4t;USYsUjrA<*Ywp+g=?SAcPY^|6k?eBY`|km&vu~t? zL@#fo0Pg;t8slb3P7_Y4u3CN=*}jY0mu8joBA?3Ti0D^Ddr;&k&G6IMa-g%JRjq`f z*_9y&6Fc4GoW+OxOEa5X4_)tDF2fAmHObJ>>i7n(iQyW)pd9K=y<@NbrnHw5 z&fE4Rnv8GPWZi;P}uo_joN$*3^j$ANx5^%0s{l zq%Zq-4acwL@)%1#8Zb;}Ro;u1&=;mEE-pr*V<9zvf&N$uI6H952Ca!(86~n3B@WV< zRqsq=5pYc^2{Uvl;2U1dny;w+pk}oy=NKFkMatYyMt`IBI%N#;J2mz11zg{|hkyx} zIF_Ejne#c<1sDG(VS4euhLfSTJM!teto8S%0JhML>GDp2K+_;YvlBYGm(jFZW_=5I zEqfLzCiM{LWG{@Og$1}#-cPKnf_x*|-EDk3*>DpP3oYD|uSND^_*YGj}&2XrQ!G+AZZ>9O>{4F8KaTXk@pz*Hq(A=U#j zCZuKna2t+$NNAt&=A41gyCp?_4(S~kuTO2^cyawNzZ7DupA+@L1lF`Vji})%9LS;` z_naQqHo@_d2QiQ?iqn+jVfUs)?`8Ku#JZV9|G}wU2o2utU27xsvsN?(h)a6s#||o` zHMPpdT8|H=Qco@Sv#mVqF|T_mIc0Qvr$uQzpTW8d`khys5xEehOd&X?ouA^G1CI_$ z&)$acn_mgt2i?O*Re`y%i>ihI&h0R-U13^lY;$|Ok3qV!Acj`WdOr`@txEqJ^DY8q zN-JKL=!CFO0RJ(6?R4l^8sbdT3(}rIs16`aSxggUl zX#mWS4$9Dh>TaG~!A+P_c=%Up%!ZHKTk$N9S)~@ZV1d{{?~d``563uDcPfUiNm&T?-vWEJF@z z?U2buGa*GK)}ty59ny85-eY~xrb)1V((x?93kYW0paGXblq0<9EQP7yhGqzZ1wE^8 ztTd(X2awyA0U2^23+sp)I@;JL5eU2pT`;@z>X{}fsFC*3u_Zqg7PD+vIqtO2up~j% zd!E&1lWls@n}|I>vLvb|Lv*;ccG&SjgSc8G6;-}$rsaE6kisX_s@ek)= z{f#2dt4&O*H)$d=prhT6*J=L93eejD9L6g(Y7;gb<<6N|;u_LWmsL#Jw{fHt?tCK# z*A%dZTdFBKQU83m?j~^&L%S^0M?ze)Dnix0X?BE>^!UfoBk=BO$XX;=x$TlM zVNF39(*Q@WYYDG>%9fLzdf94RwtwB~>5%wkm*s~6kTl0Zwwo+E1pTb(Jnn1<7T1mq0 zM<$yTjc!@KjR#%s=5%)?sE+i@{BF_UvJkG2NHR~JAnD2@iy%*}Y%JL3bQ!TwU@4Sd zi(jBpTGNU)s?<2?foEx|5P_h=n5cU2ZJ+~T zIejEyn75V&+o7%@3G5J>)q|!%PThK!ig;>y$t#zognthE8z6@(4UQAE_i;-!@|N}{ zJzDm=WrtT;e7crl^7mUOnwY`FiB`E*!b?SWYF$y*U8B(+>UZVDkE^_uU3amC5$%(R zV!`&;>9q+* z1{%WCXR}$;2LU&OJ?(MEkBn>k9;q=P0b&TF*rqv}n2v&0(fkfXDPVc+V6nF#V`$u_ z|4rF>-Jyj?eTFvO6+xSTr1g`7D7ip7OjK!0q_ULhYdy>UkILF5yf)OB6Fr7LY~x?k2YEQIp!s$PgOM;M{oLw>bfY8>Y3+ z23>NWN(oU0W+kuGJt>b`yLzG17o8hnhAd5EXF=|21iX)RRBg41%@<4`7GTz!rl9~# z+$_#>k4S<=ajm=`C?d`*cPHhA6kl^uN7F~p>XfC@hxcE>t|-1XS4iv|zFAd<&xZ?9 z>e*5egpxVM0!yV}N6suOubXt+UAOoc8TM$YbRI{~!tB_nE}P9de<(>8)O(Uxl&#W_ z5awlbf0iFZg=l8KSSghh-@<`fMNREQCcZuCGoiw4oKjkUqmEUd4 zc^`}ZE>H|8!+kIpYv=Fx>S1jeA;L}z@lCcTQ5h_+*N|$87(~0=*6ZAp=}tJVH;C*| zq)5lp6-wL-DlG2K))zKcbZr5^TA}bPiA`<%67E#e>P|OZyazDUz4(Bbta4*;L!aK( z#M84BL4DVV$L(aLtSkNQju@GzkcBDbYu8AhifRKk%`0nqpmR>AO}c5?{i}@=T>_DA zlqX4h-sqmfM~2C3yN6V6wVmL!NWpP+xezalu97l?!3JaA=#K@K6N_XLPqc;TuQTq7 z(})+=2$&0uK|hfvGyPd*CsM{5I?DF`$`xVn{@R^u~|{NhoXzZ zv%8=At-Z{a{^+~2T_j_Mxa?4o%J2_i2LS3ky4H-6X4DY{9L6QEaS*0XP^O8J81@)y z_pbo4X}Z1PAoLa;K_Qcx%LeWxznElOd1>DN2@p`QX0u5d1j!)jqd*6)t78r2rM^ zmMb}p#J9eTD@kjGEQ(^`h)!UH4>u3o?in=i9ZS_GtxG@L@HM?hH6&M-Il|K`FaKT1 z)9>}me(&Dj)g<;y6@<+|M`u}T-AIC{%N?iU`JH+yXj|Bf-8G#d9i# z%p$xu2lUahMjb2k5tcTCK4dijKk)U6FOK!UDDL`u;yd*z-hDC6%2?%Tka2VM2LyT& z{Ni#!Wc+$kBEfsqGOC(wfEV^Ca1yA$c1Y6%-A3o_yzr{P+Y`hV)uee>!pe+9(Ax=C tO23zc?s)IZhywoUsQmo@P!{>;a?=(41v{DbcdK5%_`tuztJt@>{{uRyLze&m literal 0 HcmV?d00001 diff --git a/bb39-report/qa-005-terminal-pane.jpg b/bb39-report/qa-005-terminal-pane.jpg new file mode 100644 index 0000000000000000000000000000000000000000..a0df9fd51f7c107a2a23c5d3a09cd2203ebbb49a GIT binary patch literal 43291 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 literal 0 HcmV?d00001 diff --git a/bb39-report/qa-006-terminal-interactive.jpg b/bb39-report/qa-006-terminal-interactive.jpg new file mode 100644 index 0000000000000000000000000000000000000000..1c1c48e0c9cae703b408845d7ba1252302b8a8c0 GIT binary patch literal 53615 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 Kt0V05z<&Yk6*brZ literal 0 HcmV?d00001 diff --git a/bb39-report/qa-011-terminal-split-right.jpg b/bb39-report/qa-011-terminal-split-right.jpg new file mode 100644 index 0000000000000000000000000000000000000000..57d4e4e5f734d2d7f7c9927e3bab20559190b893 GIT binary patch literal 73795 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 V^isGc*nae&HoQmw4uTblHvx$llEVN1 literal 0 HcmV?d00001 diff --git a/bb39-report/qa-013-archive-spares-terminal.jpg b/bb39-report/qa-013-archive-spares-terminal.jpg new file mode 100644 index 0000000000000000000000000000000000000000..8334565edd2bd9d6542e0476183309d5a1a38b42 GIT binary patch literal 40241 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 literal 0 HcmV?d00001 diff --git a/bb39-report/qa-014-flag-off-parity.jpg b/bb39-report/qa-014-flag-off-parity.jpg new file mode 100644 index 0000000000000000000000000000000000000000..9139e5c66d2e47f188d51a28269faba97693bedb GIT binary patch literal 49895 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 HVlMm}l~wQi literal 0 HcmV?d00001 diff --git a/bb39-report/report-draft.html b/bb39-report/report-draft.html new file mode 100644 index 000000000..2c67e1c64 --- /dev/null +++ b/bb39-report/report-draft.html @@ -0,0 +1,269 @@ + + + + + + BB-39 · Dockable Panes Experiment — Product Review + + + + + + diff --git a/bb39-report/tab-bar-mocks.html b/bb39-report/tab-bar-mocks.html new file mode 100644 index 000000000..f12b96ad3 --- /dev/null +++ b/bb39-report/tab-bar-mocks.html @@ -0,0 +1,910 @@ + + + + + +BB · Dockable Workspace — Tab Bar Treatments + + + + +
+

Tab bar treatments — dockable workspace

+

Five treatments of the 48px titlebar tab strip, each rendered light + dark with the same tab set: + active thread, terminal, diff, preview thread, truncated plugin tab. Every variant also shows + hover close-reveal, a drag-insertion indicator, and an 8-tab overflow. All grays are mixed from + --canvas/--ink anchors only.

+
BB-39 · design review · 2026-07-18
+
+ + + + +
+

1Quiet underline

+

Linear-style: no container at all. Active earns a 2px underline and full-strength text; preview is italic with a dotted underline. Most faithful to BB's current airiness.

+
+
+
Light
+
+
Default
+
+
+
+
Fix flaky test retries
+
Terminalthr_a1
+
Diff
+
QA Beta
+
BB-26 · Design the unified…
+
+
+
+
Hover on “Terminal” (close reveals; no layout shift)
+
+
+
+
Fix flaky test retries
+
Terminalthr_a1
+
Diff
+
QA Beta
+
+
+
+
Dragging “Diff” — insertion point
+
+
+
+
Fix flaky test retries
+
+
Terminalthr_a1
+
QA Beta
+
+
+
+
Overflow (8 tabs → right fade + count chip)
+
+
+
+
Fix flaky test retries
+
Terminalthr_a1
+
Diff
+
QA Beta
+
BB-26 · Design the unif…
+
Retry queue notes
+
Terminalthr_b7
+
Release checklist
+
+
+3
+
+
+
+
+
+
Dark
+
+
Default
+
+
+
+
Fix flaky test retries
+
Terminalthr_a1
+
Diff
+
QA Beta
+
BB-26 · Design the unified…
+
+
+
+
Hover on “Terminal”
+
+
+
+
Fix flaky test retries
+
Terminalthr_a1
+
QA Beta
+
+
+
+
Drag insertion
+
+
+
+
Fix flaky test retries
+
+
Terminalthr_a1
+
QA Beta
+
+
+
+
Overflow
+
+
+
+
Fix flaky test retries
+
Terminalthr_a1
+
Diff
+
QA Beta
+
BB-26 · Design the unif…
+
Retry queue notes
+
Terminalthr_b7
+
Release checklist
+
+
+3
+
+
+
+
+
+

Strengths: quietest option by far — zero chrome at rest, perfect fit for the titlebar axis. Risks: tab boundaries are invisible, so drag targets and the "this is one object" grouping are weak; italic preview carries the whole state burden and reads poorly with truncated labels. Optimizes for calm over manipulability.

+
+ + +
+

2Soft contained

+

Active tab gets a subtle elevated container (rounded-md, hairline border); inactive is borderless text; preview swaps italics for a dashed hairline border — testing whether shape alone can signal transience.

+
+
+
Light
+
+
Default (note: preview = dashed border, upright text)
+
+
+
+
Fix flaky test retries
+
Terminalthr_a1
+
Diff
+
QA Beta
+
BB-26 · Design the unified…
+
+
+
+
Hover on “Diff”
+
+
+
+
Fix flaky test retries
+
Terminalthr_a1
+
Diff
+
QA Beta
+
+
+
+
Drag insertion
+
+
+
+
Fix flaky test retries
+
Terminalthr_a1
+
+
QA Beta
+
+
+
+
Overflow
+
+
+
+
Fix flaky test retries
+
Terminalthr_a1
+
Diff
+
QA Beta
+
BB-26 · Design the unif…
+
Retry queue notes
+
Terminalthr_b7
+
Release checklist
+
+
+3
+
+
+
+
+
+
Dark
+
+
Default
+
+
+
+
Fix flaky test retries
+
Terminalthr_a1
+
Diff
+
QA Beta
+
BB-26 · Design the unified…
+
+
+
+
Hover on “Diff”
+
+
+
+
Fix flaky test retries
+
Terminalthr_a1
+
Diff
+
QA Beta
+
+
+
+
Drag insertion
+
+
+
+
Fix flaky test retries
+
Terminalthr_a1
+
+
QA Beta
+
+
+
+
Overflow
+
+
+
+
Fix flaky test retries
+
Terminalthr_a1
+
Diff
+
QA Beta
+
BB-26 · Design the unif…
+
Retry queue notes
+
Terminalthr_b7
+
Release checklist
+
+
+3
+
+
+
+
+
+

Strengths: the active container is a strong, calm affordance and matches BB's existing pill language; the dashed preview border successfully replaces italics (labels stay legible when truncated). Risks: dashed borders can read as "drop target" in a drag-heavy UI — the two vocabularies collide. Optimizes for active-tab clarity with minimal added chrome.

+
+ + +
+

3Browser cell

+

Chrome/Arc-style contiguous cells: hairline separators between inactive tabs, and the active cell merges with the content surface below (no bottom border). Strongest "these are siblings you can reorder" semantics.

+
+
+
Light
+
+
Default (active cell fuses with the pane below)
+
+
+
+
+
Fix flaky test retries
+
Terminalthr_a1
+
Diff
+
QA Beta
+
BB-26 · Design the unified…
+
+
+
+
+
+
+
Hover on “Diff”
+
+
+
+
+
Fix flaky test retries
+
Terminalthr_a1
+
Diff
+
QA Beta
+
+
+
+
+
+
Drag insertion
+
+
+
+
+
Fix flaky test retries
+
Terminalthr_a1
+
+
QA Beta
+
+
+
+
+
+
Overflow
+
+
+
+
+
Fix flaky test retries
+
Terminalthr_a1
+
Diff
+
QA Beta
+
BB-26 · Design the unif…
+
Retry queue notes
+
Terminalthr_b7
+
Release checklist
+
+
+3
+
+
+
+
+
+
+
+
Dark
+
+
Default
+
+
+
+
+
Fix flaky test retries
+
Terminalthr_a1
+
Diff
+
QA Beta
+
BB-26 · Design the unified…
+
+
+
+
+
+
+
Hover on “Diff”
+
+
+
+
+
Fix flaky test retries
+
Terminalthr_a1
+
Diff
+
QA Beta
+
+
+
+
+
+
Drag insertion
+
+
+
+
+
Fix flaky test retries
+
Terminalthr_a1
+
+
QA Beta
+
+
+
+
+
+
Overflow
+
+
+
+
+
Fix flaky test retries
+
Terminalthr_a1
+
Diff
+
QA Beta
+
BB-26 · Design the unif…
+
Retry queue notes
+
Terminalthr_b7
+
Release checklist
+
+
+3
+
+
+
+
+
+
+
+

Strengths: unbeatable grouping and drag semantics — cells make reorder targets obvious, and the merged active cell states "you are here" without any accent color. This variant also shows the reserved 28px corner slot (panel toggle). Risks: it's the heaviest chrome of the set — the tinted strip and separators fight BB's airy canvas, and the merge trick requires the strip background to differ from the content background, a real cost across custom palettes. Optimizes for manipulability over quietness.

+
+ + +
+

4Icon-forward

+

Every tab leads with a 14px per-kind glyph; the icon does kind identification, and icon dimming (not italics) marks preview. Terminal and Diff drop their labels entirely when unambiguous — icon-only with a tooltip.

+
+
+
Light
+
+
Default (Terminal & Diff icon-only; badge dropped to the pane context strip)
+
+
+
+
Fix flaky test retries
+
+
+
QA Beta
+
BB-26 · Design the unified…
+
+
+
+
Hover on icon-only Terminal (close replaces nothing — slot reserved)
+
+
+
+
Fix flaky test retries
+
+
QA Beta
+
+
+
+
Drag insertion
+
+
+
+
Fix flaky test retries
+
+
+
QA Beta
+
+
+
+
Overflow (icon-only tabs buy real density)
+
+
+
+
Fix flaky test retries
+
+
+
QA Beta
+
BB-26 · Design the unif…
+
Retry queue notes
+
+
Release checklist
+
+
+3
+
+
+
+
+
+
Dark
+
+
Default
+
+
+
+
Fix flaky test retries
+
+
+
QA Beta
+
BB-26 · Design the unified…
+
+
+
+
Hover on icon-only Terminal
+
+
+
+
Fix flaky test retries
+
+
QA Beta
+
+
+
+
Drag insertion
+
+
+
+
Fix flaky test retries
+
+
+
QA Beta
+
+
+
+
Overflow
+
+
+
+
Fix flaky test retries
+
+
+
QA Beta
+
BB-26 · Design the unif…
+
Retry queue notes
+
+
Release checklist
+
+
+3
+
+
+
+
+
+

Strengths: kind identification becomes instant and pre-attentive; icon-only terminal/diff tabs recover ~100px each and make overflow far less likely. The thr_a1 badge is dropped from the tab here — the pane's context strip is the right home for it; with two terminals open, the tooltip disambiguates. Risks: muted-icon preview is the weakest preview signal of the set (dimmed vs. muted is a two-step gray distinction), and icon-only tabs lose their identity mid-drag. Optimizes for density and scanability.

+
+ + +
+

5Synthesis (recommended)

+

Soft-contained active (from 2) + per-kind icons (from 4) + dotted-underline preview (from 1). Inactive tabs stay bare text+icon so the strip is quiet at rest; the badge moves to the pane context strip.

+
+
+
Light
+
+
Default — preview “QA Beta”: upright text, faint ink, dotted underline
+
+
+
+
Fix flaky test retries
+
Terminal
+
Diff
+
QA Beta
+
BB-26 · Design the unified…
+
+
+
+
+
Hover on “Terminal”
+
+
+
+
Fix flaky test retries
+
Terminal
+
QA Beta
+
+
+
+
Drag insertion
+
+
+
+
Fix flaky test retries
+
Terminal
+
+
QA Beta
+
+
+
+
Overflow
+
+
+
+
Fix flaky test retries
+
Terminal
+
Diff
+
QA Beta
+
BB-26 · Design the unif…
+
Retry queue notes
+
Terminal
+
+
+3
+
+
+
+
+
+
Dark
+
+
Default
+
+
+
+
Fix flaky test retries
+
Terminal
+
Diff
+
QA Beta
+
BB-26 · Design the unified…
+
+
+
+
+
Hover on “Terminal”
+
+
+
+
Fix flaky test retries
+
Terminal
+
QA Beta
+
+
+
+
Drag insertion
+
+
+
+
Fix flaky test retries
+
Terminal
+
+
QA Beta
+
+
+
+
Overflow
+
+
+
+
Fix flaky test retries
+
Terminal
+
Diff
+
QA Beta
+
BB-26 · Design the unif…
+
Retry queue notes
+
Terminal
+
+
+3
+
+
+
+
+
+

Why this synthesis: the elevated container is the one affordance the current bare-text strip most lacks, and it doubles as the drag handle silhouette when a tab tears out. Icons carry kind identification so labels can shorten (or drop, later, for terminal/diff) without ambiguity — but every tab keeps a label at v1 to stay learnable. Preview = faint ink + dotted underline: distinct at a glance, upright and legible when truncated, and it never collides with drag vocabulary the way dashed borders do. Badges (thr_a1) move to the pane context strip — the tab names the surface, the pane names the context. Corner keeps the reserved 28px panel-toggle slot; everything else stays drag region.

+
+ + +
+

ΣComparison

+ + + + + + + + + + + +
VariantKind identificationPreview clarityDensityDrag affordanceQuietness
1 · Quiet underline○○○●○○●●○●○○●●●
2 · Soft contained○○○●●○●●○●●○●●○
3 · Browser cell○○○●○○●○○●●●●○○
4 · Icon-forward●●●●○○●●●●●○●●○
5 · Synthesis ★●●●●●●●●○●●○●●○
+
+ + + diff --git a/dockable-workspace-prototype.html b/dockable-workspace-prototype.html new file mode 100644 index 000000000..931d5e149 --- /dev/null +++ b/dockable-workspace-prototype.html @@ -0,0 +1,1942 @@ + + + + + +BB — Dockable Panes Prototype + + + + +
+
BB — Dockable Panes Prototype
+
/
+
+ +
+ Surface + + +
+
+
+ +
+ +
+
+ +
+
+
+
+
+
+
+ +
+
Click a live pane to reveal · click a view to preview, double-click to keep · Esc to close
+
+
+ + +
+

Things to try

+
    +
  • The sidebar is a registry of live resources — pulsing dots are running threads/PTYs, hollow dots are idle
  • +
  • Click a thread that is already open — its view is revealed with a flash, never duplicated
  • +
  • Unopened thread: single-click = preview (italic tab, one per group, replaced in place); double-click the row or tab label, or drag the tab, to keep it
  • +
  • Close a Terminal tab — it stays in the registry (PTY keeps running); click its row to reopen the same view. “+” / ⌘K create a new terminal resource
  • +
  • Right-click a task row → open in new tab or split right; or drag a task row into the workspace (five drop zones)
  • +
  • Watch tab titles follow navigation — a Tasks pane retitles to “BB-26 · …” on a task, back restores “Tasks”
  • +
  • Open views via ⌘K Panes or each group's +; drag tabs between groups; edges split; splitters resize
  • +
  • Use the thread header's diff / terminal buttons — they open thread-bound views as tabs in the same group, revealing an existing one instead of duplicating
  • +
  • Flip Desktop / Web — browser panes swap to a web placeholder
  • +
+
+ + + + From 27ed48b9223e579beb3acb9bc6f71e5ca85339e3 Mon Sep 17 00:00:00 2001 From: Sawyer Hood Date: Mon, 20 Jul 2026 17:51:28 +0000 Subject: [PATCH 2/2] Support multiple terminals per thread The thread header's Terminal button funneled every thread to a single terminal (reveal open tab, else reopen the running session, else create). The layout, persistence, and server already handle any number of sessions per thread, so the cap was purely this entry point. Click keeps the reveal-or-create behavior; alt-click and a context menu on the button start additional terminals, and the menu lists the thread's running sessions to reveal a specific one. Co-Authored-By: Claude Fable 5 --- .../thread-detail/ThreadDetailHeader.test.tsx | 3 + .../thread-detail/ThreadDetailHeader.tsx | 62 +++++++++--- .../thread-detail/useThreadPaneEntryPoints.ts | 97 ++++++++++++------- 3 files changed, 117 insertions(+), 45 deletions(-) diff --git a/apps/app/src/views/thread-detail/ThreadDetailHeader.test.tsx b/apps/app/src/views/thread-detail/ThreadDetailHeader.test.tsx index 200138228..31d270785 100644 --- a/apps/app/src/views/thread-detail/ThreadDetailHeader.test.tsx +++ b/apps/app/src/views/thread-detail/ThreadDetailHeader.test.tsx @@ -32,6 +32,9 @@ vi.mock("./useThreadPaneEntryPoints", () => ({ useThreadPaneEntryPoints: () => ({ openDiff: vi.fn(), openTerminal: vi.fn(), + newTerminal: vi.fn(), + openTerminalSession: vi.fn(), + runningTerminals: [], isBusy: false, }), })); diff --git a/apps/app/src/views/thread-detail/ThreadDetailHeader.tsx b/apps/app/src/views/thread-detail/ThreadDetailHeader.tsx index d693d9125..538e981b8 100644 --- a/apps/app/src/views/thread-detail/ThreadDetailHeader.tsx +++ b/apps/app/src/views/thread-detail/ThreadDetailHeader.tsx @@ -29,6 +29,13 @@ import { usePaneContext } from "./PaneContext"; import { PaneMaximizeButton } from "./PaneMaximizeButton"; import { useThreadSplitsEnabled } from "@/hooks/useThreadSplitsEnabled"; import { useThreadPaneEntryPoints } from "./useThreadPaneEntryPoints"; +import { + ContextMenu, + ContextMenuContent, + ContextMenuItem, + ContextMenuSeparator, + ContextMenuTrigger, +} from "@bb/shared-ui/context-menu"; const THREAD_HEADER_ACTION_BUTTON_CLASS = COARSE_POINTER_TOOLBAR_ACTION_BUTTON_CLASS; @@ -259,7 +266,14 @@ function ThreadPaneEntryPointButtons({ projectId: string; threadId: string; }) { - const { openDiff, openTerminal, isBusy } = useThreadPaneEntryPoints({ + const { + openDiff, + openTerminal, + newTerminal, + openTerminalSession, + runningTerminals, + isBusy, + } = useThreadPaneEntryPoints({ projectId, threadId, }); @@ -276,17 +290,41 @@ function ThreadPaneEntryPointButtons({ > - + {/* Click reveals the thread's terminal (creating the first); threads can + hold any number, so alt-click and the context menu start more. */} + + + + + + + + New terminal + + {runningTerminals.length > 0 ? : null} + {runningTerminals.map((terminal) => ( + openTerminalSession(terminal.id)} + > + + {terminal.title} + + ))} + + ); } diff --git a/apps/app/src/views/thread-detail/useThreadPaneEntryPoints.ts b/apps/app/src/views/thread-detail/useThreadPaneEntryPoints.ts index aa60ccacc..064d9175e 100644 --- a/apps/app/src/views/thread-detail/useThreadPaneEntryPoints.ts +++ b/apps/app/src/views/thread-detail/useThreadPaneEntryPoints.ts @@ -1,4 +1,4 @@ -import { useCallback, useRef } from "react"; +import { useCallback, useMemo, useRef } from "react"; import { useStore } from "jotai"; import { useNavigate } from "react-router-dom"; import { @@ -27,10 +27,24 @@ interface UseThreadPaneEntryPointsArgs { interface ThreadPaneEntryPoints { openDiff(): void; + /** Reveals the thread's terminal (open tab, else running session), creating + * the first one when none exists. */ openTerminal(): void; + /** Always starts another terminal session for the thread and opens its tab — + * threads can hold any number of terminals. */ + newTerminal(): void; + /** Reveals or opens the tab for one specific running session. */ + openTerminalSession(terminalId: string): void; + /** Running sessions for this thread, for pickers over `openTerminalSession`. */ + runningTerminals: readonly TerminalSessionSummary[]; isBusy?: boolean; } +export interface TerminalSessionSummary { + id: string; + title: string; +} + function revealTab( layout: SplitLayout, location: NonNullable>, @@ -87,6 +101,39 @@ export function useThreadPaneEntryPoints({ applyContent({ kind: "diff", projectId, threadId }); }, [applyContent, projectId, threadId]); + const openTerminalSession = useCallback( + (terminalId: string) => { + applyContent({ + kind: "terminal", + terminalId, + target: { kind: "thread", threadId }, + }); + }, + [applyContent, threadId], + ); + + const newTerminal = useCallback(() => { + if (createTerminal.isPending || isCreatingTerminalRef.current) { + return; + } + isCreatingTerminalRef.current = true; + createTerminal.mutate( + { + threadId, + cols: TERMINAL_COLS, + rows: TERMINAL_ROWS, + }, + { + onSuccess: (session) => { + openTerminalSession(session.id); + }, + onSettled: () => { + isCreatingTerminalRef.current = false; + }, + }, + ); + }, [createTerminal, openTerminalSession, threadId]); + const openTerminal = useCallback(() => { const layout = store.get(splitLayoutAtom); if (layout === null) { @@ -126,55 +173,39 @@ export function useThreadPaneEntryPoints({ (session) => session.status === "running", ); if (runningSession !== undefined) { - applyContent({ - kind: "terminal", - terminalId: runningSession.id, - target: { kind: "thread", threadId }, - }); + openTerminalSession(runningSession.id); return; } - if ( - isLoadingTerminals || - createTerminal.isPending || - isCreatingTerminalRef.current - ) { + if (isLoadingTerminals) { return; } - isCreatingTerminalRef.current = true; - createTerminal.mutate( - { - threadId, - cols: TERMINAL_COLS, - rows: TERMINAL_ROWS, - }, - { - onSuccess: (session) => { - applyContent({ - kind: "terminal", - terminalId: session.id, - target: { kind: "thread", threadId }, - }); - }, - onSettled: () => { - isCreatingTerminalRef.current = false; - }, - }, - ); + newTerminal(); }, [ - applyContent, - createTerminal, isLoadingTerminals, navigate, + newTerminal, + openTerminalSession, projectId, store, terminalsQuery.data?.sessions, threadId, ]); + const runningTerminals = useMemo( + () => + (terminalsQuery.data?.sessions ?? []) + .filter((session) => session.status === "running") + .map((session) => ({ id: session.id, title: session.title })), + [terminalsQuery.data?.sessions], + ); + return { openDiff, openTerminal, + newTerminal, + openTerminalSession, + runningTerminals, isBusy: isLoadingTerminals || createTerminal.isPending, }; }
+
+
Product review · BB-39 / BB-25
+

Dockable panes: terminals, diffs, and threads in one tab-group workspace

+

First working slice of the unified docking model: the split tree's leaves became VS Code-style tab groups, navigation opens preview tabs, and terminals and diffs are first-class draggable panes with thread-header entry points. Built by a four-worker model-routed fan-out, gated by a GPT-5.6 review, and verified live in the dev app for this report.

+
+ Verdict — works end-to-end; 1 P2 found in live QA + Branch bb/dockable-terminals-and-views-thr_9nsm45dtne (uncommitted, base 6dbeb8a20) + Build dev app @ localhost:12325 · threadSplits flag ON + Tests 241 files / 1,624 passing · typecheck clean + Scope experiment behind flag — phases 2+3 of 6 +
+
+ +
+ +
+ +
+

What was built and why

+

BB previously ran two separate pane systems: the recursive split tree (threads and plugin panels only) and the per-thread "secondary panel" tab strip (where terminals, diffs, and files actually lived). BB-25's decision record unifies them: one layout tree whose leaves are tab groups, every surface a tab, resources (threads, PTYs) distinct from views. This experiment ships the first two implementation phases behind the existing experiments.threadSplits flag:

+
+
Tab-group layout engine

Leaves hold ordered tabs with one preview slot; open/close/activate/commit/move ops; localStorage schema v2 (v1 = deliberate fresh start); 8-pane cap, 16-tab sanity bound.

+
Preview tabs

Navigation opens an italic preview that the next single-click replaces in place. Commit by double-click (tab or sidebar row) or by dragging. Stateful views (terminals) never preview.

+
Terminal & diff panes

New descriptors with canonical routes (/terminals/:id, /threads/:id/diff), explicit context binding, xterm mounted straight off the server-relayed session, diff via the production git-diff panel.

+
Thread-header entry points

Diff and Terminal buttons on the thread pane: reveal an open view, else reopen the thread's running terminal, else create one — never a duplicate, never an accidental PTY.

+
+

Deferred by design (later phases): browser panes (BB-40), plugin views API (BB-41), server-side per-project persistence + bb panes agent CLI (BB-42), the registry sidebar and quick switcher (BB-43).

+
+ +
+

The core workflow: click around, commit what matters

+

The defining loop of the design: single-click exploration is free — it never mints permanent tabs — and an explicit gesture locks in what you want to keep.

+
+ QA screenshot: qa-001b-direct-thread.jpg +
Navigation opens a preview. Visiting QA Alpha put it in the tab strip in italics — a preview tab sharing the group with the committed "New thread" view. The thread header keeps its slim chrome; the tab owns the title.
+
+
+ QA screenshot: qa-002-beta-replaces-preview.jpg +
The next click replaces the preview in place. Single-clicking QA Beta in the sidebar swapped the preview slot — still exactly two tabs, no pane spam, URL following along.
+
+
+ QA screenshot: qa-004-gamma-third-preview.jpg +
Commit + preview coexist. Double-clicking QA Beta's sidebar row committed it (upright); QA Gamma then opened as a fresh preview beside it. Three tabs: committed, committed, preview.
+
+
+ +
+

Terminals as first-class panes

+

The load-bearing claim of the whole unification — stateful, server-relayed PTYs living in the layout tree — verified live:

+
+ QA screenshot: qa-005-terminal-pane.jpg +
One click on the header's Terminal button created a PTY bound to the thread, opened it as a committed tab (terminals never preview), and the URL became /terminals/term_cr9va6c5vt. The slim context strip shows the binding: Thread thr_5yi7.
+
+
+ QA screenshot: qa-006-terminal-interactive.jpg +
Fully interactive over the pane's websocket: echo dockable-panes-live-$((6*7))dockable-panes-live-42, executed in the environment's worktree.
+
+
+ QA screenshot: qa-008-terminal-reopened-same-pty.jpg +
The resource outlives its view. After closing the Terminal tab and clicking the entry point again, the same terminal id returned with scrollback intact — the PTY survived the close; the button revealed the existing resource instead of spawning a new one.
+
+
+ +
+

Diff panes

+
+ QA screenshot: qa-009-diff-pane.jpg +
The Diff entry point opened a committed tab at /threads/thr_5yi7…/diff, reusing the production git-diff panel. This environment isn't a git repository, so the empty state is the correct render; populated diff content was not exercised in this pass (the component is the same one the secondary panel ships today).
+
+
+ +
+

Drag, tear-out, and splits

+
+ QA screenshot: qa-011-terminal-split-right.jpg +
Tearing the Terminal tab out to the right edge created a second group — live PTY on the right with scrollback intact, the left group keeping its four tabs (Gamma still a preview). Note the sidebar: thread rows now carry split-indicator minimaps showing where they're docked. Dragging always commits.
+
+
+ +
+

Persistence and lifecycle

+

Reload: after a full page reload, both groups restored from localStorage schema v2, the URL stayed on the focused terminal pane, and the terminal reconnected by id with scrollback (corroborated by DOM query: the earlier command output re-rendered).

+
+ QA screenshot: qa-013-archive-spares-terminal.jpg +
Archiving the thread (via CLI, out-of-band) closed its thread tab but the layout and Terminal tab survived — the exact High-severity review fix, observed live. The PTY itself was ended by server policy (thread archive closes thread-scoped sessions), and the pane degrades honestly to "Terminal ended". The lingering Diff tab in this screenshot is the one defect found — see below.
+
+
+ +
+

Flag-off parity

+
+ QA screenshot: qa-014-flag-off-parity.jpg +
With threadSplits off the thread page renders exactly as production today: no tab strip, no header entry points (verified by accessibility query: zero "Open diff/terminal pane" buttons). The single-pane single-tab path is also DOM-parity-guarded by test.
+
+
+ +
+

Architecture at review depth

+
    +
  • Resources vs views (BB-25 decision record): a tab is a view; threads and terminals are resources that exist unviewed. Per-kind multiplicity: threads reveal-existing (1 view in v1), terminals exactly-one-view (PTY geometry), data views (tasks/diff) cheap multi.
  • +
  • URL-first descriptors: every pane kind has a canonical route and two-way route↔descriptor conversion; the focused pane's active tab owns the address bar. Bare /terminals/:id URLs are reveal-only (they can't carry the context binding) — verified they cannot seed an invalid tab.
  • +
  • Preview is view-layer only, orthogonal to the resource model; one preview slot per group, enforced at open, normalize, and persisted-schema levels for terminals.
  • +
  • Explicit context binding: terminal descriptors carry their target (thread/environment/host-path) fixed at creation, shown in pane chrome.
  • +
  • Fresh start on storage: schema v2; v1 layouts deserialize to null by design (experiment behind flag; old tab state had a 14-day expiry anyway).
  • +
+
+ +
+

How it was built and verified

+

Manager (Fable) implemented the core model and navigation policy; three parallel workers with non-overlapping file ownership built the renderer (Fable), pane components + entry points (GPT-5.6 Sol high), and sidebar adaptations (Sol medium); a fourth migrated tests (Sol high). A GPT-5.6 Sol review gate returned REQUEST CHANGES with 6 findings: 5 fixed in one round (archive sparing terminals, cap-guarded navigation, URL-terminal guard, invariant enforcement, group-drag restoration), 1 declined deliberately (tab insertion stays after-active so a thread's diff/terminal lands beside it). Every fix carries a regression test.

+
    +
  • Typecheck: clean across the app after integration (verified independently by the manager).
  • +
  • Test suite: 241 files / 1,624 tests passing, including migrated split-layout coverage and new regression tests.
  • +
  • Live QA (this report): 13 workflows exercised in the dev app via browser automation with console-error capture — zero console errors observed in any captured run.
  • +
+
+ +
+

Defects, limitations, and environment notes

+

P2 — Archived thread's diff tab lingers on out-of-band archive

+
    +
  1. Open a thread, its Diff pane, and a terminal (any layout).
  2. +
  3. Archive the thread from outside the app (CLI: bb thread archive <id>).
  4. +
  5. Expected: the thread tab and its diff tab close (matching the in-app archive path, which routes through closePanesForThreadsAtom and does close both). Actual: only the thread tab is pruned; the stale-thread watcher doesn't cover thread-bound diff tabs, so an orphaned Diff tab remains (evidence: the lifecycle screenshot above).
  6. +
+

Suggested fix: extend the per-tab stale watcher to thread-bound descriptor kinds, or have DiffPaneContent render an archived-thread state. Not fixed during QA to keep the finding reproducible.

+

Product-policy observation (not a defect)

+

Server policy ends thread-scoped PTYs when their thread is archived, so the spared terminal tab shows "Terminal ended". If terminals should survive archive, that's a server-side lifecycle decision (relevant to the BB-43 registry, where running terminals are sidebar resources).

+

Not exercised in this pass

+
    +
  • Populated diff rendering (QA env isn't a git repo; component is production code reused).
  • +
  • Compact-viewport fallback, dark/custom themes, center-drop tab merge via pointer drag, and strip-background group drag (all unit-tested; drag decide-tables covered by the suite).
  • +
  • CLI thread-open-into-split (applyThreadOpenToLayout is unit-tested; the full bb panes agent surface is BB-42).
  • +
+

Environment / QA residue

+
    +
  • Dev instance data dir: ~/.bb-dev/bb-worktrees-env_rp87eqfyeb-bb-c3596b6d3569; fixtures QA Alpha/Beta remain, QA Gamma archived, one ended terminal session, qa-diff-fixture.txt in its env worktree. Left in place for reproduction.
  • +
  • threadSplits flag left ON in the dev instance for hands-on evaluation; dev server left running.
  • +
  • One tooling quirk (not product): first automation click from the homepage timed out under Playwright's stability wait; all subsequent targeted clicks behaved.
  • +
+
+ +
+

Decisions requested

+
    +
  • Ship gate: is this slice good enough to merge behind the flag (with the P2 filed as a fast-follow), or should the diff-tab pruning land first?
  • +
  • Terminal-on-archive policy: keep server-side PTY teardown on thread archive, or let terminals survive as unbound resources (matters for the BB-43 registry design)?
  • +
  • Insertion order: confirm the declined review finding — committed opens insert after the active tab (keeps a thread's diff/terminal adjacent) rather than appending to the end.
  • +
+
+ +
+

Try it

+
# dev server is already running on this branch
+scripts/bb-dev-app status        # URLs; app at http://localhost:12325
+# flag is already ON; to toggle:
+sqlite3 ~/.bb-dev/bb-worktrees-env_rp87eqfyeb-bb-c3596b6d3569/bb.db \
+  "UPDATE system_experiments SET thread_splits=1 WHERE id='current';"
+

Things to feel: single-click threads (preview, italics) vs double-click (commit); the Diff/Terminal buttons in the thread header; drag a tab to any edge; reload mid-layout; close a terminal tab and reopen it from the button.

+
+ +
+

Coverage summary

+ + + + + + + + + + + + + + + + + + +
WorkflowStatusEvidence
App boot, fixtures visible, console cleanpassqa-000
Navigation opens preview tab (italic) in focused grouppassqa-001b
Preview replaced in place by next single-clickpassqa-002
Sidebar double-click commits; next click adds new previewpassqa-003/004
Terminal entry point creates PTY, committed tab, canonical URLpassqa-005
Terminal interactive I/O through pane websocketpassqa-006
Close tab → PTY survives → reopen reveals same terminal + scrollbackpassqa-007/008 + URL logs
Diff entry point → committed tab at /diff, empty state correctpassqa-009
Tab tear-out drag → edge split, two groups, PTY intactpassqa-010/011
Sidebar split-indicator minimaps reflect docked tabspassqa-011
Reload restores layout (schema v2) + terminal reconnectpassqa-012 + DOM query
External archive prunes thread tab, layout + terminal tab survivepassqa-013
Archived thread's diff tab closes (out-of-band path)fail — P2qa-013
Flag-off parity (no strip, no buttons)passqa-014
Browser panes, plugin views API, registry sidebar, ⌘K, bb panes CLIN/A — later phases (BB-40/41/42/43)
Populated diff render, compact viewport, themes, center-drop mergenot exercised live; unit-tested where applicable
+

Full evidence archive (14 raw screenshots + review artifacts): thread storage bb39/qa/ and bb39/review-report.md.

+
+ +
+
+ +