proposition pick: persistent lifted selection, live count, honest affordances

Root cause of 'doesn't register': the slice-6 floating-bar edit never
landed (a silent no-match replace) — the Body accepted selectMode but
the bar ignored it, so the picker showed Clear + Delete and nothing
ever confirmed. Found by re-reading; the test suite never pinned the
select-mode bar.

The owner vision instead: the chain table's selection is PERSISTENT
and caller-owned.
- ChainTableOverlay gains controlledSelection/onSelectionChange (every
  check, clear, and delete-prune reports up immediately; uncontrolled
  fallback keeps the delete flow self-contained).
- Select mode's floating bar: Done (closes; the pick already
  registered) — the delete fence never renders while picking.
- GraphView threads the lifted set through, resolving ids → nodes
  against the live snapshot.
- Home: the pick bar above the composer shows 'N propositions
  selected' + Clear in EITHER mode (dashed 'Pick propositions from the
  map' hint only in practice mode with nothing picked); visible hover
  (bg-accent) so the chip reads clickable. Practice sends still seed
  from the pick set.
This commit is contained in:
amosblomqvist
2026-10-04 22:54:17 +03:00
parent 89580d2b93
commit 87f8e1e6f4
4 changed files with 139 additions and 61 deletions
+20 -8
View File
@@ -946,14 +946,18 @@ export function GraphChromeRow({
export function GraphView({
chainSelectOpen = false,
onChainSelected,
practiceSelection,
onPracticeSelectionChange,
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. */
/** PRACTICE SELECT (owner vision, 2026-10-04): the LIFTED, persistent
* pick set — every check/clear in the chain table (however opened)
* reports the full node list up immediately; the composer-side chip
* reflects it live. chainSelectOpen still drives the picker mode
* (Done instead of Delete). */
chainSelectOpen?: boolean;
onChainSelected?: (nodes: { id: string; name: string; statement: string }[]) => void;
practiceSelection?: ReadonlySet<string>;
onPracticeSelectionChange?: (nodes: { id: string; name: string; statement: string }[]) => void;
onChainSelectClose?: () => void;
}) {
const containerRef = useRef<HTMLDivElement | null>(null);
@@ -1700,9 +1704,17 @@ export function GraphView({
onChainSelectClose?.();
}}
selectMode={chainSelectOpen}
onConfirmSelection={(picked) => {
onChainSelected?.(picked.map((n) => ({ id: n.id, name: n.name, statement: n.statement })));
onChainSelectClose?.();
controlledSelection={practiceSelection}
onSelectionChange={(ids) => {
// Resolve ids → nodes against the live snapshot; the
// caller holds the pick set (count + seed).
const props = snapshot?.propositions ?? [];
const picked: { id: string; name: string; statement: string }[] = [];
for (const id of ids) {
const n = props.find((node) => node.id === id);
if (n) picked.push({ id: n.id, name: n.name, statement: n.statement });
}
onPracticeSelectionChange?.(picked);
}}
/> </div>
);