refactor: dedupe split-view render, strengthen persistence test

- Extract ConsoleArea helper to eliminate ~60 lines of duplicated layout
  toggle + grid rendering shared between currentLane and !currentLane branches
- Fix persistence test to validate the component's actual write path instead
  of manually re-seeding localStorage (proves writeSplitViewState is called)
- Remove orphaned grid/pane code left by incomplete merge
This commit is contained in:
2026-08-14 13:36:02 +07:00
parent d542fbbf4b
commit 6f22aed47c
2 changed files with 123 additions and 124 deletions
+105 -100
View File
@@ -54,6 +54,97 @@ import type { SplitViewState, SplitLayout } from "../lib/splitViewStorage";
// ── Page ──────────────────────────────────────────────────────────────
function ConsoleArea({
lanes,
selectedLaneId,
splitView,
setLayout,
setPaneLaneId,
binaryStatus,
cwdSuggestions,
activeRuns,
wsConnected,
defaultCwd,
onHasActiveRunChange,
onLaneCreated,
onLaneIdChange,
}: {
lanes: Lane[];
selectedLaneId: number | null;
splitView: SplitViewState;
setLayout: (layout: SplitLayout) => void;
setPaneLaneId: (index: number, id: number) => void;
binaryStatus: { found: boolean; path: string | null } | null;
cwdSuggestions: CwdSuggestion[];
activeRuns: RunListResponse | null;
wsConnected: boolean;
defaultCwd: string;
onHasActiveRunChange: (val: boolean) => void;
onLaneCreated: (lane: Lane) => void;
onLaneIdChange: (id: number | null) => void;
}) {
const { t: tLanes } = useTranslation("lanes");
return (
<>
<div className="flex items-center gap-1.5">
{([1, 2, 4] as const).map((n) => (
<button
key={n}
type="button"
aria-pressed={splitView.layout === n}
onClick={() => setLayout(n)}
className={`rounded border px-2 py-1 text-xs ${
splitView.layout === n
? "border-accent bg-accent/15 text-accent"
: "border-border text-fg-secondary hover:border-border-light"
}`}
>
{tLanes("splitView.paneCount", { count: n })}
</button>
))}
</div>
{splitView.layout === 1 ? (
<LaneConsolePane
lanes={lanes}
laneId={selectedLaneId}
showLaneSelector={false}
onLaneIdChange={onLaneIdChange}
onLaneCreated={onLaneCreated}
binaryStatus={binaryStatus}
cwdSuggestions={cwdSuggestions}
activeRuns={activeRuns}
wsConnected={wsConnected}
defaultCwd={defaultCwd}
onHasActiveRunChange={onHasActiveRunChange}
/>
) : (
<div
className={`grid flex-1 min-h-0 gap-3 ${
splitView.layout === 2 ? "grid-cols-2" : "grid-cols-2 grid-rows-2"
}`}
>
{splitView.paneLaneIds.map((id, i) => (
<LaneConsolePane
key={i}
lanes={lanes}
laneId={id}
showLaneSelector
onLaneIdChange={(newId) => setPaneLaneId(i, newId)}
onLaneCreated={onLaneCreated}
binaryStatus={binaryStatus}
cwdSuggestions={cwdSuggestions}
activeRuns={activeRuns}
wsConnected={wsConnected}
defaultCwd={defaultCwd}
/>
))}
</div>
)}
</>
);
}
export function Workspace() {
const { t: tLanes } = useTranslation("lanes");
const wsConnected = useSyncExternalStore(eventBus.onConnection, () => eventBus.connected);
@@ -552,132 +643,46 @@ export function Workspace() {
</div>
)}
<div className="flex min-h-0 flex-col gap-2 border-t border-border pt-3">
<div className="flex items-center gap-1.5">
{([1, 2, 4] as const).map((n) => (
<button
key={n}
type="button"
aria-pressed={splitView.layout === n}
onClick={() => setLayout(n)}
className={`rounded border px-2 py-1 text-xs ${
splitView.layout === n
? "border-accent bg-accent/15 text-accent"
: "border-border text-fg-secondary hover:border-border-light"
}`}
>
{tLanes("splitView.paneCount", { count: n })}
</button>
))}
</div>
{splitView.layout === 1 ? (
<LaneConsolePane
<ConsoleArea
lanes={lanes}
laneId={selectedLaneId}
showLaneSelector={false}
onLaneIdChange={(id) => setSelectedLaneId(id)}
onLaneCreated={(lane) =>
setLanes((prev) => (prev.some((l) => l.id === lane.id) ? prev : [...prev, lane]))
}
selectedLaneId={selectedLaneId}
splitView={splitView}
setLayout={setLayout}
setPaneLaneId={setPaneLaneId}
binaryStatus={binaryStatus}
cwdSuggestions={cwdSuggestions}
activeRuns={activeRuns}
wsConnected={wsConnected}
defaultCwd={defaultCwd}
onHasActiveRunChange={setPaneHasActiveRun}
/>
) : (
<div
className={`grid flex-1 min-h-0 gap-3 ${
splitView.layout === 2 ? "grid-cols-2" : "grid-cols-2 grid-rows-2"
}`}
>
{splitView.paneLaneIds.map((id, i) => (
<LaneConsolePane
key={i}
lanes={lanes}
laneId={id}
showLaneSelector
onLaneIdChange={(newId) => setPaneLaneId(i, newId)}
onLaneCreated={(lane) =>
setLanes((prev) =>
prev.some((l) => l.id === lane.id) ? prev : [...prev, lane]
)
setLanes((prev) => (prev.some((l) => l.id === lane.id) ? prev : [...prev, lane]))
}
binaryStatus={binaryStatus}
cwdSuggestions={cwdSuggestions}
activeRuns={activeRuns}
wsConnected={wsConnected}
defaultCwd={defaultCwd}
onLaneIdChange={setSelectedLaneId}
/>
))}
</div>
)}
</div>
</section>
)}
{!currentLane && (
<div className="flex min-h-0 flex-col gap-2">
<div className="flex items-center gap-1.5">
{([1, 2, 4] as const).map((n) => (
<button
key={n}
type="button"
aria-pressed={splitView.layout === n}
onClick={() => setLayout(n)}
className={`rounded border px-2 py-1 text-xs ${
splitView.layout === n
? "border-accent bg-accent/15 text-accent"
: "border-border text-fg-secondary hover:border-border-light"
}`}
>
{tLanes("splitView.paneCount", { count: n })}
</button>
))}
</div>
{splitView.layout === 1 ? (
<LaneConsolePane
<ConsoleArea
lanes={lanes}
laneId={selectedLaneId}
showLaneSelector={false}
onLaneIdChange={(id) => setSelectedLaneId(id)}
onLaneCreated={(lane) =>
setLanes((prev) => (prev.some((l) => l.id === lane.id) ? prev : [...prev, lane]))
}
selectedLaneId={selectedLaneId}
splitView={splitView}
setLayout={setLayout}
setPaneLaneId={setPaneLaneId}
binaryStatus={binaryStatus}
cwdSuggestions={cwdSuggestions}
activeRuns={activeRuns}
wsConnected={wsConnected}
defaultCwd={defaultCwd}
onHasActiveRunChange={setPaneHasActiveRun}
/>
) : (
<div
className={`grid flex-1 min-h-0 gap-3 ${
splitView.layout === 2 ? "grid-cols-2" : "grid-cols-2 grid-rows-2"
}`}
>
{splitView.paneLaneIds.map((id, i) => (
<LaneConsolePane
key={i}
lanes={lanes}
laneId={id}
showLaneSelector
onLaneIdChange={(newId) => setPaneLaneId(i, newId)}
onLaneCreated={(lane) =>
setLanes((prev) =>
prev.some((l) => l.id === lane.id) ? prev : [...prev, lane]
)
setLanes((prev) => (prev.some((l) => l.id === lane.id) ? prev : [...prev, lane]))
}
binaryStatus={binaryStatus}
cwdSuggestions={cwdSuggestions}
activeRuns={activeRuns}
wsConnected={wsConnected}
defaultCwd={defaultCwd}
onLaneIdChange={setSelectedLaneId}
/>
))}
</div>
)}
</div>
)}
@@ -671,12 +671,6 @@ describe("split terminal view", () => {
await settle();
unmount();
localStorage.clear();
localStorage.setItem(
"ccam.workspace.splitView",
JSON.stringify({ layout: 2, paneLaneIds: [null, lanesToReturn[1]!.id] })
);
await renderWorkspace();
const persistedSelects = screen.getAllByTestId("pane-lane-select");
expect(persistedSelects).toHaveLength(2);