practice entry points: sidebar filter, practice draft, graph picker

- Sidebar: the three-way kind filter (Teaching/All/Practice, persisted
  in localStorage) above the search; two new-session doors (Teaching →
  /chat/new, Practice → /chat/new?mode=practice); practice rows carry
  the dumbbell badge and 'Practicing…' busy meta.
- Draft: ?mode=practice swaps the copy (title, empty state, 'What do
  you want to practice?') and startDraftSession posts kind:'practice'.
- Graph entry (home composer): teach|practice switch on the stats row's
  left edge (StatsBar leadingSlot); practice mode shows the propositions
  chip that opens the chain table as a picker (its floating bar becomes
  'Practice these' — no delete fence); the picks seed the opening prompt
  ('Propositions from the map:…') while the title stays the typed text.
This commit is contained in:
amosblomqvist
2026-10-04 22:01:42 +03:00
parent 59bf5bf219
commit e76e42f764
9 changed files with 290 additions and 33 deletions
+27 -2
View File
@@ -944,7 +944,18 @@ export function GraphChromeRow({
);
}
export function GraphView() {
export function GraphView({
chainSelectOpen = false,
onChainSelected,
onChainSelectClose,
}: {
/** PRACTICE SELECT (owner, 2026-10-04): the home composer's practice
* chip opens the chain table as a proposition picker — this prop
* drives it; the confirm hands the picked nodes to the caller. */
chainSelectOpen?: boolean;
onChainSelected?: (nodes: { id: string; name: string; statement: string }[]) => void;
onChainSelectClose?: () => void;
}) {
const containerRef = useRef<HTMLDivElement | null>(null);
const canvasRef = useRef<HTMLCanvasElement | null>(null);
const cardRef = useRef<HTMLDivElement | null>(null);
@@ -1048,6 +1059,12 @@ export function GraphView() {
// The chain table overlay (chains-overlay plan, 2026-09-29) —
// component-local: a browsing surface, not graph state.
const [chainsOpen, setChainsOpen] = useState(false);
// PRACTICE SELECT (2026-10-04): the parent's chip drives the select
// mode; closing (either the picker's confirm or its X) reports back so
// the chip's open state and the table's never disagree.
useEffect(() => {
if (chainSelectOpen) setChainsOpen(true);
}, [chainSelectOpen]);
useEffect(() => {
let cancelled = false;
fetch("/api/graph")
@@ -1678,7 +1695,15 @@ export function GraphView() {
chains={snapshot?.chains ?? []}
sessions={snapshot?.sessions ?? {}}
fileSources={snapshot?.fileSources ?? {}}
onClose={() => setChainsOpen(false)}
onClose={() => {
setChainsOpen(false);
onChainSelectClose?.();
}}
selectMode={chainSelectOpen}
onConfirmSelection={(picked) => {
onChainSelected?.(picked.map((n) => ({ id: n.id, name: n.name, statement: n.statement })));
onChainSelectClose?.();
}}
/> </div>
);
}