refactor: replace code mode tabs with persistent Browse | Agent split + collapsible terminal
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
This commit is contained in:
@@ -211,31 +211,6 @@ function LayoutsContent({ surfaces, projectId, workspace, activeSurfaceId, onSel
|
|||||||
|
|
||||||
// ── Shared mode tab bar ───────────────────────────────────────────────────────
|
// ── 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 (
|
|
||||||
<div style={{ display: "flex", borderBottom: "1px solid #e8e4dc", background: "#fff", flexShrink: 0, padding: "0 16px" }}>
|
|
||||||
{tabs.map(t => (
|
|
||||||
<button key={t.id} onClick={() => onChange(t.id)} style={{
|
|
||||||
padding: "10px 14px", border: "none", background: "transparent", cursor: "pointer",
|
|
||||||
fontSize: "0.76rem", fontWeight: mode === t.id ? 600 : 450,
|
|
||||||
color: mode === t.id ? "#1a1a1a" : "#a09a90",
|
|
||||||
borderBottom: mode === t.id ? "2px solid #1a1a1a" : "2px solid transparent",
|
|
||||||
fontFamily: "Outfit, sans-serif", whiteSpace: "nowrap",
|
|
||||||
}}>
|
|
||||||
{t.label}
|
|
||||||
</button>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Agent mode ────────────────────────────────────────────────────────────────
|
// ── Agent mode ────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
interface AgentSession {
|
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 (
|
return (
|
||||||
<div style={{ flex: 1, display: "flex", flexDirection: "column", background: "#1a1a1a", overflow: "hidden" }}>
|
<div style={{ flexShrink: 0, borderTop: "1px solid #2d2d2d", background: "#1a1a1a", display: "flex", flexDirection: "column", height: open ? PANEL_HEIGHT : 32, transition: "height 0.2s ease", overflow: "hidden" }}>
|
||||||
<div style={{ flex: 1, display: "flex", alignItems: "center", justifyContent: "center", flexDirection: "column", gap: 14, padding: 40, textAlign: "center" }}>
|
{/* Header / toggle bar */}
|
||||||
<div style={{ width: 52, height: 52, borderRadius: 13, background: "#252526", display: "flex", alignItems: "center", justifyContent: "center", fontSize: "1.3rem" }}>⌨</div>
|
<button
|
||||||
<div>
|
onClick={() => setOpen(o => !o)}
|
||||||
<div style={{ fontSize: "0.88rem", fontWeight: 600, color: "#d4d4d4", marginBottom: 6, fontFamily: "Outfit, sans-serif" }}>Terminal — Phase 4</div>
|
style={{
|
||||||
<div style={{ fontSize: "0.78rem", color: "#6b6560", maxWidth: 300, lineHeight: 1.6, fontFamily: "Outfit, sans-serif" }}>
|
display: "flex", alignItems: "center", gap: 8, padding: "0 14px",
|
||||||
|
height: 32, flexShrink: 0, width: "100%", border: "none",
|
||||||
|
background: "transparent", cursor: "pointer", textAlign: "left",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span style={{ fontSize: "0.58rem", color: "#555", transform: open ? "rotate(180deg)" : "none", transition: "transform 0.2s", display: "inline-block" }}>▲</span>
|
||||||
|
<span style={{ fontSize: "0.68rem", fontWeight: 600, color: "#6b6560", letterSpacing: "0.07em", textTransform: "uppercase", fontFamily: "Outfit, sans-serif" }}>Terminal</span>
|
||||||
|
{appName && <span style={{ fontSize: "0.65rem", color: "#3a3a3a", fontFamily: "IBM Plex Mono, monospace" }}>{appName}</span>}
|
||||||
|
<span style={{ marginLeft: "auto", fontSize: "0.6rem", color: "#3a3a3a", fontFamily: "Outfit, sans-serif" }}>Phase 4</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{/* Body */}
|
||||||
|
{open && (
|
||||||
|
<div style={{ flex: 1, display: "flex", alignItems: "center", justifyContent: "center", flexDirection: "column", gap: 10, padding: "16px 24px", textAlign: "center" }}>
|
||||||
|
<div style={{ fontSize: "0.78rem", color: "#6b6560", lineHeight: 1.6, fontFamily: "Outfit, sans-serif", maxWidth: 340 }}>
|
||||||
{appName
|
{appName
|
||||||
? `A live shell into the ${appName} container via xterm.js + Theia PTY. Coming in Phase 4.`
|
? `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."}
|
: "Select an app from the left, then open a live shell into its container."}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ marginTop: 4, padding: "7px 18px", background: "#252526", color: "#555", borderRadius: 7, fontSize: "0.77rem", fontFamily: "Outfit, sans-serif" }}>Coming in Phase 4</div>
|
)}
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -968,12 +959,6 @@ function BuildHubInner() {
|
|||||||
|
|
||||||
const setSection = (s: string) => router.push(`/${workspace}/project/${projectId}/build?section=${s}`, { scroll: false });
|
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 (
|
return (
|
||||||
<div style={{ display: "flex", height: "100%", fontFamily: "Outfit, sans-serif", overflow: "hidden" }}>
|
<div style={{ display: "flex", height: "100%", fontFamily: "Outfit, sans-serif", overflow: "hidden" }}>
|
||||||
|
|
||||||
@@ -1014,14 +999,38 @@ function BuildHubInner() {
|
|||||||
|
|
||||||
{/* ── Content ── */}
|
{/* ── Content ── */}
|
||||||
<div style={{ flex: 1, display: "flex", flexDirection: "column", overflow: "hidden", minWidth: 0 }}>
|
<div style={{ flex: 1, display: "flex", flexDirection: "column", overflow: "hidden", minWidth: 0 }}>
|
||||||
|
|
||||||
|
{/* Code section — persistent split: Browse (left) | Agent (right), Terminal (bottom) */}
|
||||||
{section === "code" && (
|
{section === "code" && (
|
||||||
<div style={{ flex: 1, display: "flex", flexDirection: "column", overflow: "hidden" }}>
|
<div style={{ flex: 1, display: "flex", flexDirection: "column", overflow: "hidden" }}>
|
||||||
<ModeTabs mode={codeMode} onChange={setCodeMode} />
|
{/* Main split row */}
|
||||||
{codeMode === "browse" && <CodeContent projectId={projectId} appName={activeApp} rootPath={activeRoot} />}
|
<div style={{ flex: 1, display: "flex", overflow: "hidden" }}>
|
||||||
{codeMode === "agent" && <AgentMode projectId={projectId} appName={activeApp} appPath={activeRoot} />}
|
{/* Left: Browse */}
|
||||||
{codeMode === "terminal" && <TerminalMode appName={activeApp} />}
|
<div style={{ flex: 1, display: "flex", flexDirection: "column", overflow: "hidden", borderRight: "1px solid #e8e4dc", minWidth: 0 }}>
|
||||||
|
{/* Browse header */}
|
||||||
|
<div style={{ height: 32, flexShrink: 0, display: "flex", alignItems: "center", padding: "0 14px", borderBottom: "1px solid #e8e4dc", background: "#fff" }}>
|
||||||
|
<span style={{ fontSize: "0.68rem", fontWeight: 700, color: "#a09a90", letterSpacing: "0.07em", textTransform: "uppercase", fontFamily: "Outfit, sans-serif" }}>Browse</span>
|
||||||
|
{activeApp && <span style={{ marginLeft: 8, fontSize: "0.7rem", color: "#b5b0a6", fontFamily: "IBM Plex Mono, monospace" }}>{activeApp}</span>}
|
||||||
|
</div>
|
||||||
|
<CodeContent projectId={projectId} appName={activeApp} rootPath={activeRoot} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Right: Agent */}
|
||||||
|
<div style={{ width: 420, flexShrink: 0, display: "flex", flexDirection: "column", overflow: "hidden", background: "#fff" }}>
|
||||||
|
{/* Agent header */}
|
||||||
|
<div style={{ height: 32, flexShrink: 0, display: "flex", alignItems: "center", padding: "0 14px", borderBottom: "1px solid #e8e4dc" }}>
|
||||||
|
<span style={{ fontSize: "0.68rem", fontWeight: 700, color: "#a09a90", letterSpacing: "0.07em", textTransform: "uppercase", fontFamily: "Outfit, sans-serif" }}>Agent</span>
|
||||||
|
{activeApp && <span style={{ marginLeft: 8, fontSize: "0.7rem", color: "#b5b0a6", fontFamily: "IBM Plex Mono, monospace" }}>{activeApp}</span>}
|
||||||
|
</div>
|
||||||
|
<AgentMode projectId={projectId} appName={activeApp} appPath={activeRoot} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Bottom: Terminal (collapsible) */}
|
||||||
|
<TerminalPanel appName={activeApp} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{section === "layouts" && (
|
{section === "layouts" && (
|
||||||
<LayoutsContent surfaces={surfaces} projectId={projectId} workspace={workspace} activeSurfaceId={activeSurfaceId} onSelectSurface={id => { setActiveSurfaceId(id); navigate({ section: "layouts", surface: id }); }} />
|
<LayoutsContent surfaces={surfaces} projectId={projectId} workspace={workspace} activeSurfaceId={activeSurfaceId} onSelectSurface={id => { setActiveSurfaceId(id); navigate({ section: "layouts", surface: id }); }} />
|
||||||
)}
|
)}
|
||||||
|
|||||||
Reference in New Issue
Block a user