pmacs/builtin/runtime
Levi Neuwirth 49a42ec9dc
feat(listview): the tree primitive — depth, collapse, and identity
COHERENCE.md §14's last missing workbench primitive. Q#TR1-TR4 decided
at review; this implements them.

EXTENDS LISTVIEW rather than adding a treeview (Q#TR1). A separate
primitive would either duplicate ~200 lines of panel discipline —
Q#GB18 handle identity, Q#GB13 `<2>` disambiguation, the read-only
intercept, `prev` capture, the quit chain, generated-buffer writes — or
require extracting them from a shipped primitive first, which is the
riskier change. Rows gain OPTIONAL `depth` and `id`; absent, they behave
exactly as before, which is what keeps the three flat consumers
untouched.

THE OBSERVATION THAT MADE THIS CHEAP: collapse only ever HIDES rows and
never changes a surviving row's depth. Combined with consumers emitting
parents before children in document order, a node's descendants are a
CONTIGUOUS RUN of following rows with greater depth. So collapse is
filtering an existing array, not re-deriving one — the primitive never
calls the consumer to re-render a fold, and pre-rendered indentation
stays correct. That is why `text` remains consumer-supplied (Q#TR4),
which also sidesteps the future conflict with dired's fixed-width
`_layout` column contract.

It is also why a panel with NO `on_refresh` can still fold. The anchor
consumer is exactly that panel: the outline has no refresh at all
(framing §1.5a), so a design requiring the consumer to re-supply rows on
every fold would not have worked for the only consumer that exists.

SELECTION IS RE-SEATED BY ID, NOT BY LINE (Q#TR3). A fold inserts or
removes rows above the cursor, so a line-keyed restore lands on an
unrelated node — the defect `listview.refresh` already had in milder
form. `id` is consumer-supplied and compared by equality; the primitive
never derives one. The outline uses `line:col`, unique per document and
stable across re-render, rather than the `::` parent chain, which
collides on overloads and same-named siblings — precisely where a stale
expansion would reattach to the wrong node.

`has_children` reads the FULL row array rather than the rendered subset.
A collapsed node's children are absent from `line_to_row` by
construction, so asking the rendered view would answer "no" for every
collapsed node and make expanding impossible.

TAB ON A LEAF REPORTS rather than silently doing nothing. The outline's
`g` is already a dead binding — bound, dispatched, no feedback — and
this primitive must not add a second one.

Tests: fold hides ALL descendants while the node and its SIBLING
survive; state and selection survive a re-render; a leaf reports; and a
depthless panel is unchanged by TAB. The fold test is bite-verified —
disabling only the ancestor filter fails it on "descendants hidden".

Verified: fmt, diff-check, clippy with and without crdt, --lib 1896, m4
149, listview 21/21, and the full serialized luajit sweep at 3453
passed / 0 failed. That count reconciles exactly: main is 3450 (Stage
3's 3449 sweep predated its capability-fallback pin) plus these three
tests.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-05 14:31:31 +02:00
..
async.lua feat(journey): open a directory, on one path 2026-07-26 16:39:59 -04:00
autosave.lua feat(config): typed configuration registry with buffer-local scope 2026-07-21 18:29:18 -04:00
comment.lua feat(lean4): editing surface + Stage 1 acceptance (Q#LN5, LN6, LN17) 2026-07-25 09:59:48 -04:00
compile.lua review round 1: the crdt suite Stage 3 missed, and two stale explanations 2026-08-04 12:08:28 +02:00
completion.lua fix(completion): address TUI-validation findings (LSP query gaps, scoping, prefix keys, window scope) 2026-07-07 16:54:14 -04:00
desktop.lua fix(persistence): reliable daemon gate, unarm, per-pane after-load 2026-07-08 22:27:24 -04:00
dired.lua refactor(panel): one shared rule for the adopter `display` vocabulary 2026-08-01 20:25:27 -04:00
editops.lua fix(config): reject wrongly-typed spec fields; make trim-on-save buffer-aware 2026-07-21 18:29:18 -04:00
fold.lua feat(fold): Arc 6 Stage 1 — instance fold engine (headless) 2026-07-23 12:14:00 -04:00
fs.lua wip(stage2a): the Lua half — hooks, LSP subscribers, applier origin 2026-07-29 18:09:03 -04:00
help.lua fix(help): forwarders must work programmatically, not only from M-x 2026-07-31 20:31:00 -04:00
indent.lua fix(edit): PR #109 round 1 — shared search invalidation, daemon anchor clear, bounded indent scan 2026-07-10 15:46:36 -04:00
killring.lua feat(edit): editing-conveniences pack (editops) 2026-07-12 15:33:17 +01:00
lean.lua fix(lsp): close round-six Lean fallback gaps 2026-07-25 22:32:48 -04:00
lean_abbrev.lua feat(lean4): the Unicode input method (Arc 8 Stage 4b) 2026-07-26 16:24:06 -04:00
lean_input.lua fix(lean4): count fan-outs where a claim cannot skip the count 2026-07-26 17:15:24 -04:00
listview.lua feat(listview): the tree primitive — depth, collapse, and identity 2026-08-05 14:31:31 +02:00
lsp.lua feat(listview): the tree primitive — depth, collapse, and identity 2026-08-05 14:31:31 +02:00
mcp.lua M9 ship gate 2026-05-09 15:04:23 -04:00
pair.lua feat(typed-edit): the typed-edit consumer chain (Arc 8 Stage 4a) 2026-07-26 13:00:03 -04:00
recentf.lua fix(persistence): symlink confinement, real test-inertness, view_top restore 2026-07-08 18:21:46 -04:00
saveplace.lua fix(persistence): symlink confinement, real test-inertness, view_top restore 2026-07-08 18:21:46 -04:00
syntax.lua feat(lean4): editing surface + Stage 1 acceptance (Q#LN5, LN6, LN17) 2026-07-25 09:59:48 -04:00
terminal.lua fix(buffer): fan out generated writes, and clear the history that exists 2026-07-26 13:43:22 -04:00
typed_edit.lua fix(typed-edit): close round-8 review on the consumer chain 2026-07-26 13:39:33 -04:00
welcome.lua feat(help): the discovery command family — P4 Stage 1 2026-07-31 19:04:48 -04:00
window.lua feat(window): adopter placement opt-in and the Stage 1 acceptance suite 2026-07-24 14:10:45 -04:00