// Main column-stack workspace (Finder-style). // // Layout: [column 0 (section)] [column 1] … [column N] [text editor (pinned)]. // The text-editor pane is always rendered last and absorbs the remaining // horizontal space; the column stack on its left is horizontally scrollable // so deep chains stay reachable on narrow viewports. // // Each column has a drag-to-resize handle on its right edge. Each column also // gets `index` as a prop so its inner click handlers can call `pushFrom(index, …)` // to drill in (truncating any deeper columns first). "use client"; import { useCallback, useEffect, useRef } from "react"; import { TextCanvasPane } from "./sections/TextCanvasPane"; import { ModelPane } from "./sections/ModelPane"; import { ConceptsPane } from "./sections/ConceptsPane"; import { RequirementsPane } from "./sections/RequirementsPane"; import { FindingsPane } from "./sections/FindingsPane"; import { TermColumn, BlockColumn, AssociationColumn, ConstraintColumn, RequirementColumn, FindingColumn, } from "./columns/EntityColumns"; import { useOpenPanes, PANE_MIN_WIDTH, PANE_MAX_WIDTH, columnKey, type Column, } from "../../lib/workspace/openPanesStore"; import type { FixtureData } from "../../lib/fixtures/aristotle"; interface MainWorkspaceProps { data: FixtureData; projectId: string; } export function MainWorkspace({ data, projectId }: MainWorkspaceProps) { const { columns, closeFrom, widthFor, setWidth } = useOpenPanes(); return (
{columns.map((col, index) => ( setWidth(col, w)} onClose={() => closeFrom(index)} /> ))}
); } interface ResizableColumnProps { column: Column; index: number; projectId: string; width: number; onResize: (w: number) => void; onClose: () => void; } function ResizableColumn({ column, index, projectId, width, onResize, onClose }: ResizableColumnProps) { const startXRef = useRef(0); const startWidthRef = useRef(0); const draggingRef = useRef(false); const onPointerMove = useCallback( (e: PointerEvent) => { if (!draggingRef.current) return; const dx = e.clientX - startXRef.current; const next = Math.max(PANE_MIN_WIDTH, Math.min(PANE_MAX_WIDTH, startWidthRef.current + dx)); onResize(next); }, [onResize] ); const onPointerUp = useCallback(() => { if (!draggingRef.current) return; draggingRef.current = false; document.body.style.cursor = ""; document.body.style.userSelect = ""; }, []); useEffect(() => { const move = (e: PointerEvent) => onPointerMove(e); const up = () => onPointerUp(); window.addEventListener("pointermove", move); window.addEventListener("pointerup", up); return () => { window.removeEventListener("pointermove", move); window.removeEventListener("pointerup", up); }; }, [onPointerMove, onPointerUp]); const onHandleDown = useCallback( (e: React.PointerEvent) => { e.preventDefault(); draggingRef.current = true; startXRef.current = e.clientX; startWidthRef.current = width; document.body.style.cursor = "col-resize"; document.body.style.userSelect = "none"; }, [width] ); return (
); } interface ColumnRendererProps { column: Column; index: number; projectId: string; onClose: () => void; } function ColumnRenderer({ column, index, projectId, onClose }: ColumnRendererProps) { switch (column.kind) { case "section": switch (column.id) { case "concepts": return ; case "model": return ; case "requirements": return ; case "assumptions": return ; case "risks": return ; case "inconsistencies": return ; } case "term": return ; case "block": return ; case "association": return ; case "constraint": return ; case "requirement": return ; case "finding": return ( ); } }