From 2e0bc95bb0531473e11404e5df8531d181dd5acc Mon Sep 17 00:00:00 2001 From: Mark Henderson Date: Mon, 9 Mar 2026 14:29:35 -0700 Subject: [PATCH] refactor: replace code mode tabs with persistent Browse | Agent split + collapsible terminal MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Removes the Browse/Agent/Terminal tab switcher from the code section. Browse (file tree + viewer) is now the left pane, Agent chat is a fixed 420px right pane, and Terminal is a collapsible strip at the bottom — all visible simultaneously. Made-with: Cursor --- .../project/[projectId]/build/page.tsx | 103 ++++++++++-------- 1 file changed, 56 insertions(+), 47 deletions(-) diff --git a/app/[workspace]/project/[projectId]/build/page.tsx b/app/[workspace]/project/[projectId]/build/page.tsx index 8fe36f7..1cc0b2c 100644 --- a/app/[workspace]/project/[projectId]/build/page.tsx +++ b/app/[workspace]/project/[projectId]/build/page.tsx @@ -211,31 +211,6 @@ function LayoutsContent({ surfaces, projectId, workspace, activeSurfaceId, onSel // ── Shared mode tab bar ─────────────────────────────────────────────────────── -type CodeMode = "browse" | "agent" | "terminal"; - -function ModeTabs({ mode, onChange }: { mode: CodeMode; onChange: (m: CodeMode) => void }) { - const tabs: { id: CodeMode; label: string }[] = [ - { id: "browse", label: "Browse" }, - { id: "agent", label: "Agent" }, - { id: "terminal", label: "Terminal" }, - ]; - return ( -
- {tabs.map(t => ( - - ))} -
- ); -} - // ── Agent mode ──────────────────────────────────────────────────────────────── interface AgentSession { @@ -800,23 +775,39 @@ function AgentMode({ projectId, appName, appPath }: { projectId: string; appName ); } -// ── Terminal mode (Phase 4 placeholder) ─────────────────────────────────────── +// ── Terminal panel — collapsible bottom strip ───────────────────────────────── + +function TerminalPanel({ appName }: { appName: string }) { + const [open, setOpen] = useState(false); + const PANEL_HEIGHT = 220; -function TerminalMode({ appName }: { appName: string }) { return ( -
-
-
-
-
Terminal — Phase 4
-
+
+ {/* Header / toggle bar */} + + + {/* Body */} + {open && ( +
+
{appName - ? `A live shell into the ${appName} container via xterm.js + Theia PTY. Coming in Phase 4.` - : "Select an app first, then open a live shell into its container."} + ? `Live shell into the ${appName} container via xterm.js + Theia PTY — coming in Phase 4.` + : "Select an app from the left, then open a live shell into its container."}
-
Coming in Phase 4
-
+ )}
); } @@ -968,12 +959,6 @@ function BuildHubInner() { const setSection = (s: string) => router.push(`/${workspace}/project/${projectId}/build?section=${s}`, { scroll: false }); - const codeMode = (searchParams.get("mode") as CodeMode | null) ?? "browse"; - const setCodeMode = (m: CodeMode) => { - const sp = new URLSearchParams({ section: "code", ...(activeApp ? { app: activeApp, root: activeRoot } : {}), mode: m }); - router.push(`/${workspace}/project/${projectId}/build?${sp.toString()}`, { scroll: false }); - }; - return (
@@ -1014,14 +999,38 @@ function BuildHubInner() { {/* ── Content ── */}
+ + {/* Code section — persistent split: Browse (left) | Agent (right), Terminal (bottom) */} {section === "code" && (
- - {codeMode === "browse" && } - {codeMode === "agent" && } - {codeMode === "terminal" && } + {/* Main split row */} +
+ {/* Left: Browse */} +
+ {/* Browse header */} +
+ Browse + {activeApp && {activeApp}} +
+ +
+ + {/* Right: Agent */} +
+ {/* Agent header */} +
+ Agent + {activeApp && {activeApp}} +
+ +
+
+ + {/* Bottom: Terminal (collapsible) */} +
)} + {section === "layouts" && ( { setActiveSurfaceId(id); navigate({ section: "layouts", surface: id }); }} /> )}