docs(folding): Stage 2 framing rev 4 — address review round 3

Route command-time visible-line maps through each operation's target
window, while retaining the acting frontend as the projection-policy
owner. Model nested and crossing folds as merged hidden components so
row and byte clamps always resolve to one actually visible head.

Also key projection on the negotiated render selection and correct the
unmerged status of the separate Stage 1 housekeeping PR.
This commit is contained in:
Levi Neuwirth 2026-07-23 17:25:55 -04:00
parent 8160d660a0
commit 4222ffae6e
1 changed files with 120 additions and 56 deletions

View File

@ -1,6 +1,6 @@
# Folding Stage 2 — grid (daemon-rendered) collapse — framing (Arc 6) # Folding Stage 2 — grid (daemon-rendered) collapse — framing (Arc 6)
**Revision 3 — 2026-07-23. Status: DRAFT for review (rounds 12 findings **Revision 4 — 2026-07-23. Status: DRAFT for review (rounds 13 findings
addressed).** Parent architecture (`docs/folding-framing.md`, rev 5) is APPROVED addressed).** Parent architecture (`docs/folding-framing.md`, rev 5) is APPROVED
and Stage 1 (the headless fold engine) is MERGED as **#142** (canonical `main` @ and Stage 1 (the headless fold engine) is MERGED as **#142** (canonical `main` @
`c49a8c7`). This doc reframes **Stage 2** in detail off that base, per the `c49a8c7`). This doc reframes **Stage 2** in detail off that base, per the
@ -68,7 +68,8 @@ parent's §8/§14. Numbering continues the parent's `Q#FD…` scheme from `Q#FD1
normalizes to the visible head**, then steps to the preceding/following visible normalizes to the visible head**, then steps to the preceding/following visible
line (§7). line (§7).
- **#142 housekeeping** (retire the active-work.md folding lane + refresh handoff - **#142 housekeeping** (retire the active-work.md folding lane + refresh handoff
§1) is a **separate docs PR**, kept out of `folding-tui` (landed as PR #147). §1) is a **separate docs PR**, kept out of `folding-tui` (PR #147, ready for
merge but not yet landed).
### Round 2 (rev 2 → rev 3) ### Round 2 (rev 2 → rev 3)
@ -94,7 +95,7 @@ parent's §8/§14. Numbering continues the parent's `Q#FD…` scheme from `Q#FD1
rendered window**, keyed on that window's `buffer_id` + its `TextView` rendered window**, keyed on that window's `buffer_id` + its `TextView`
line-offsets + `view_top`; peer presence derives/receives the **recipient line-offsets + `view_top`; peer presence derives/receives the **recipient
window's** map (§3.2). New acceptance: a split with two **different** buffers, window's** map (§3.2). New acceptance: a split with two **different** buffers,
only one folded — no active-buffer/map leakage (§11 acceptance 13). only one folded — no active-buffer/map leakage (§11 acceptance 14).
- **F3 (moderate) — hidden positions need column projection, not only row - **F3 (moderate) — hidden positions need column projection, not only row
clamping.** Rev 2 clamped a hidden caret/peer cursor to `visible_head_of`'s row clamping.** Rev 2 clamped a hidden caret/peer cursor to `visible_head_of`'s row
but left the column unspecified, which could paint a hidden point at an arbitrary but left the column unspecified, which could paint a hidden point at an arbitrary
@ -108,6 +109,36 @@ parent's §8/§14. Numbering continues the parent's `Q#FD…` scheme from `Q#FD1
- **Nits.** Fixed the `Viewport<'a>` typo; stated the map build cost honestly as - **Nits.** Fixed the `Viewport<'a>` typo; stated the map build cost honestly as
O(folds) with a byte→line lookup per fold (B4). O(folds) with a byte→line lookup per fold (B4).
### Round 3 (rev 3 → rev 4)
- **F1 (major) — command-time maps follow the operation's target window.** Rev 3
said every command/event map came from the active window, but wheel events call
`scroll_window(win_id, …)` for the pane under the pointer **without activating
it** (`src/editor.rs:1919/2264`). In a split, scrolling inactive buffer B could
therefore derive folded buffer A's map. Rev 4 separates the two axes: the
**acting frontend** supplies the Q#FD21 projection-policy gate; the
**operation's target window** supplies `buffer_id` / line offsets / `view_top`
for the map. Motion, paging, and auto-scroll target the active window; click
inverse and wheel target their explicit `win_id`. Acceptance 14 now pins an
inactive-pane wheel with different fold state.
- **F2 (moderate) — byte projection must handle crossing overlaps, not only strict
nesting.** Stage 1's `FoldStore::insert` permits arbitrary normalized ranges and
rejects no crossing overlap. With A hiding lines 13 and B headed on line 2 and
hiding 35, a point on line 5 is directly inside only B; B's `range.start` is
nevertheless hidden by A. Rev 3's "outermost fold containing the original
point" could therefore return a hidden position. Rev 4 makes the derived map's
unit a **merged hidden component** retaining its one visible `head_line` and
exact `head_position`; both `visible_head_of` and `visible_position_of` resolve
through that component. This is equivalent to repeatedly projecting a hidden
fold head until visible and covers nesting, shared heads, and crossing overlap.
Acceptance 6 adds the crossing shape.
- **F3 (minor) — PR #147 is ready, not landed.** Corrected the two premature
"landed" claims; the housekeeping remains separate and unmerged.
- **Nit — selected render kind, not an advertisement.** Q#FD21 now keys on
`session_state.negotiated_capabilities.semantic_render`, the same attach-time
boolean that selects `RenderState` vs `SemanticRenderState`; LOCAL is explicitly
grid-projecting.
## 1. What Stage 2 ships ## 1. What Stage 2 ships
The grid TUI is **daemon-rendered**: the daemon walks a buffer's source lines and The grid TUI is **daemon-rendered**: the daemon walks a buffer's source lines and
@ -274,7 +305,10 @@ frontend's layout; distinct windows carry distinct `window.buffer_id` /
show two different buffers with only one folded. The after-frame presence pass show two different buffers with only one folded. The after-frame presence pass
likewise iterates recipient windows by `buffer_id` (`src/overlay_paint.rs:124`). likewise iterates recipient windows by `buffer_id` (`src/overlay_paint.rs:124`).
Any per-frame-singleton fold map therefore leaks; the map must be **per rendered Any per-frame-singleton fold map therefore leaks; the map must be **per rendered
window** (F2). Terminal windows never fold (Q#FD9) and are skipped. window** (F2). Command/event targeting matters too: wheel scrolling receives an
explicit `win_id` and does not activate that pane, so its map belongs to the
target window, not necessarily the active one (round-3 F1). Terminal windows
never fold (Q#FD9) and are skipped.
## 3. The shared visible-line map primitive (Q#FD12) ## 3. The shared visible-line map primitive (Q#FD12)
@ -291,24 +325,36 @@ derived, never stored.
### 3.1 Queries ### 3.1 Queries
Folds may nest; the derivation unions their hidden intervals into a sorted, Folds may nest, share heads, or cross through arbitrary data-API ranges. The
non-overlapping hidden-line set `H` (a line is hidden regardless of which fold owns derivation unions overlapping or adjacent hidden intervals into sorted,
it). The map answers, in **line space**: non-overlapping **hidden components**. Each component
`C = [first_hidden, last_hidden]` retains:
- `head_line = first_hidden - 1`, the one visible line immediately before `C`;
- `head_position`, the exact end-of-content byte of `head_line` (the
`range.start` that begins the earliest participating hidden interval).
Adjacent intervals merge because the later fold's head is hidden by the earlier
interval; keeping them separate would preserve a head that cannot render. The
union of the components is the hidden-line set `H` (a line is hidden regardless
of which fold owns it). The map answers:
- `is_hidden(line) -> bool``line ∈ H`. - `is_hidden(line) -> bool``line ∈ H`.
- **`visible_head_of(line) -> line`** — for a hidden `line`, the head **line** of - **`visible_head_of(line) -> line`** — for a hidden `line`, its component's
the **outermost** enclosing fold (resolve to the enclosing fold's head; if that `head_line`; for a visible `line`, itself. This is the recursively resolved
head is itself hidden, recurse; heads strictly decrease, so it terminates on the **outermost visible head**, including when a crossing fold's own head is hidden
one visible head). For a visible `line`, itself. **Row-only** clamps use this: by an earlier fold. **Row-only** clamps use this: diagnostic signs, the
diagnostic signs, the relative-number cursor anchor, and the `view_top` backward relative-number cursor anchor, and the `view_top` backward clamp. (Rev 1's
clamp. (Rev 1's innermost `head_of` is removed — F1.) innermost `head_of` is removed — round-1 F1.)
- **`visible_position_of(pos) -> Position` (F3)** — for a byte `pos` on a hidden - **`visible_position_of(pos) -> Position` (round-2 F3, round-3 F2)** — for a byte
line, the **outermost** enclosing fold's `range.start` (the end of the visible `pos` on a hidden line, its component's exact `head_position`; for a visible
head line — exactly where Stage 1 moves point on fold-at-cursor). For a visible `pos`, itself. This is equivalent to projecting to a containing fold's
`pos`, itself. **Position** clamps — which carry a column, not just a row — use `range.start` and repeating while that head is hidden, so it cannot return
this: the local caret, peer cursors, and selection **endpoints**. It resolves to another hidden position under crossing overlap. **Position** clamps — which
`visible_head_of(line(pos))`'s line at the head's end-of-content column, so a carry a column, not just a row — use this: the local caret, peer cursors, and
hidden point never paints at an arbitrary column on the head. selection **endpoints**. A hidden point therefore lands at
`visible_head_of(line(pos))`'s end-of-content column, never at an arbitrary
column on the head.
- `next_visible(line)` / `prev_visible(line)` — the next/previous visible line, - `next_visible(line)` / `prev_visible(line)` — the next/previous visible line,
skipping whole folds (for the render walk and vertical motion). skipping whole folds (for the render walk and vertical motion).
- `visible_between(a, b) -> isize` — signed count of visible lines from `a` to `b` - `visible_between(a, b) -> isize` — signed count of visible lines from `a` to `b`
@ -346,13 +392,15 @@ window's `buffer_id`, its `TextView` line-offsets, and (for the render walk) its
(`src/overlay_paint.rs`, site 10) iterates recipient windows by `buffer_id`; for (`src/overlay_paint.rs`, site 10) iterates recipient windows by `buffer_id`; for
each it derives/receives **that recipient window's** map and clamps peer cursors each it derives/receives **that recipient window's** map and clamps peer cursors
(`visible_position_of`) / projects peer selection endpoints through it. (`visible_position_of`) / projects peer selection endpoints through it.
- **Command / event — per acting window, gated on the frontend (F1).** Click - **Command / event — per target window, gated on the acting frontend (round-3
inverse, auto-scroll, paging, wheel, and vertical motion (sites 1113, in F1).** Click inverse, auto-scroll, paging, wheel, and vertical motion (sites
`EditorCore` / `EditorState`) build a short-lived instance from 1113, in `EditorCore` / `EditorState`) build a short-lived instance from
`state.fold_registry` + the **active window's** buffer at call time — **only when `state.fold_registry` + the **operation's target window** at call time — **only
the acting frontend's `fold_projection_active` is set** (Q#FD21); otherwise they when the acting frontend's `fold_projection_active` is set** (Q#FD21).
keep raw source-line behavior. "The map is already built" (rev 1) was false — these Motion, paging, and auto-scroll target the active window; click inverse and
run outside any frame. wheel use their explicit `win_id` (wheel does not activate the pane under the
pointer). Otherwise they keep raw source-line behavior. "The map is already
built" (rev 1) was false — these run outside any frame.
`view_top` **stays a source-line index** (Q#FD12, **Bet B5**), only ever set via `view_top` **stays a source-line index** (Q#FD12, **Bet B5**), only ever set via
`clamp_view_top` so it never rests on a hidden line — preserving the saveplace `clamp_view_top` so it never rests on a hidden line — preserving the saveplace
@ -414,16 +462,19 @@ semantic/GPU session never enters, so they are **already grid-only**. The
**command/event-time** reckoning below (motion, paging, wheel, click, auto-scroll) **command/event-time** reckoning below (motion, paging, wheel, click, auto-scroll)
runs in **shared** `EditorCore`/`EditorState` code, so each such site is **gated on runs in **shared** `EditorCore`/`EditorState` code, so each such site is **gated on
the acting frontend's `fold_projection_active`** (a `FrontendView` flag, the acting frontend's `fold_projection_active`** (a `FrontendView` flag,
`src/editor_core.rs:240`, set at attach from the frontend's advertised render kind: `src/editor_core.rs:240`, set at attach from
grid ⇒ true in Stage 2; semantic/GPU ⇒ false until Stage 3, cleared at detach). `session_state.negotiated_capabilities.semantic_render`, the same selected-render
bit that allocates `RenderState` vs `SemanticRenderState`: grid ⇒ true in Stage 2;
semantic/GPU ⇒ false until Stage 3; LOCAL ⇒ true; cleared with the view at detach).
When the flag is false the site keeps its current raw-line behavior — so a GPU When the flag is false the site keeps its current raw-line behavior — so a GPU
session on the same shared-fold buffer never skips a source line it still displays. session on the same shared-fold buffer never skips a source line it still displays.
**Caret clamp (Q#FD16, F3).** Caret grid position (`src/editor.rs:3033-3044`): if **Caret clamp (Q#FD16, F3).** Caret grid position (`src/editor.rs:3033-3044`): if
the logical cursor's line `is_hidden`, render at **`visible_position_of(cursor)`** the logical cursor's line `is_hidden`, render at **`visible_position_of(cursor)`**
— the outermost fold's `range.start`, i.e. the visible head row at the head's — the hidden component's `head_position`, i.e. the visible head row at the head's
end-of-content column (not an arbitrary column, F3). Satisfies the parent's end-of-content column (not an arbitrary or still-hidden column, F3). Satisfies
per-cursor render-time invariant (Q#FD3), including the shared-store case. the parent's per-cursor render-time invariant (Q#FD3), including the shared-store
case.
**Local selection (Q#FD16, F2/F3).** `paint_local_selection` (`src/editor.rs:3241`): **Local selection (Q#FD16, F2/F3).** `paint_local_selection` (`src/editor.rs:3241`):
each selection **endpoint** on a hidden line projects via `visible_position_of`; each selection **endpoint** on a hidden line projects via `visible_position_of`;
@ -436,13 +487,15 @@ same way and hidden interiors drop. The map is the **recipient window's** map
(§3.2), built for that window's buffer. (§3.2), built for that window's buffer.
**Click inverse (Q#FD16, Q#FD21).** `activate_and_position` (`src/editor.rs:2233`): **Click inverse (Q#FD16, Q#FD21).** `activate_and_position` (`src/editor.rs:2233`):
on a fold-projecting frontend, grid row `k` maps to the `k`-th visible line, so a on a fold-projecting frontend, build the map from the explicit target `win_id`;
click never lands on a hidden line. grid row `k` maps to that window's `k`-th visible line, so a click never lands on
a hidden line.
**Viewport / scroll (Q#FD18, Q#FD21).** On a fold-projecting frontend: `view_top` **Viewport / scroll (Q#FD18, Q#FD21).** On a fold-projecting frontend: `view_top`
is set only via `clamp_view_top` (backward to the head for a hidden candidate, F1); is set only via `clamp_view_top` (backward to the head for a hidden candidate, F1);
paging (`move_page_down/up`), wheel (`scroll_window`), and the auto-scroll-to-cursor the active window's paging (`move_page_down/up`) and auto-scroll-to-cursor clamp
clamp (`src/editor.rs:2866`) advance and bound by **visible** lines (`src/editor.rs:2866`), plus wheel `scroll_window(win_id)` on its explicit target
window, advance and bound by **visible** lines
(`next_visible`/`prev_visible`, `visible_between`); `move_to_line`/goto-line targets (`next_visible`/`prev_visible`, `visible_between`); `move_to_line`/goto-line targets
a raw line and, if hidden, clamps `view_top` so the target's **head** is visible (no a raw line and, if hidden, clamps `view_top` so the target's **head** is visible (no
auto-unfold — deferred search-reveal, §9); the **mode-line scroll indicator** auto-unfold — deferred search-reveal, §9); the **mode-line scroll indicator**
@ -455,7 +508,7 @@ fold-projecting frontend, `next-line`/`prev-line` (and arrows) step to the adjac
**visible** line (`next_visible`/`prev_visible`), so a collapsed region is one **visible** line (`next_visible`/`prev_visible`), so a collapsed region is one
motion step and the cursor never rests hidden. **If motion begins from a hidden motion step and the cursor never rests hidden. **If motion begins from a hidden
logical cursor** (a shared fold or a goto-line into a fold), it **first normalizes logical cursor** (a shared fold or a goto-line into a fold), it **first normalizes
to `visible_head_of(cursor)`**, then steps. The render-time caret clamp (Q#FD16) to `visible_position_of(cursor)`**, then steps. The render-time caret clamp (Q#FD16)
backstops the pre-motion shared-fold frame. On a non-fold-projecting frontend these backstops the pre-motion shared-fold frame. On a non-fold-projecting frontend these
retain raw-line motion. All reuse the command-time per-window map (§3.2). retain raw-line motion. All reuse the command-time per-window map (§3.2).
@ -524,11 +577,12 @@ Each widened behavior is **bite-verified** (a test failing without the widening)
- **B7** Threading `Option<&'a VisibleLineMap>` on a lifetime-bearing `Viewport<'a>` - **B7** Threading `Option<&'a VisibleLineMap>` on a lifetime-bearing `Viewport<'a>`
keeps `Viewport: Copy`. FALSIFIABLE if a `View` impl or construction site cannot keeps `Viewport: Copy`. FALSIFIABLE if a `View` impl or construction site cannot
satisfy the lifetime; fallback is to settle `Viewport` as non-`Copy` explicitly. satisfy the lifetime; fallback is to settle `Viewport` as non-`Copy` explicitly.
- **B8** A `fold_projection_active` flag on `FrontendView`, set from the frontend's - **B8** A `fold_projection_active` flag on `FrontendView`, set from
advertised render kind at attach, cleanly scopes command-time visible-line `session_state.negotiated_capabilities.semantic_render` in the same attach
reckoning to grid frontends. FALSIFIABLE if the render kind is not known at transaction that selects the grid vs semantic render state (and initialized
`register_frontend_view` time; fallback is to derive it from the daemon's true for LOCAL), cleanly scopes command-time visible-line reckoning to grid
grid-vs-semantic session map at dispatch. frontends. Every non-daemon `FrontendView` construction must choose the
projection explicitly; it is never inferred from `FrontendId`.
## 11. Acceptance — Stage 2 ## 11. Acceptance — Stage 2
@ -561,6 +615,10 @@ the precedents), sized for macOS startup + long temp-path width (handoff §5).
projects its endpoints and paints on the visible head + visible tail rows, projects its endpoints and paints on the visible head + visible tail rows,
nothing on hidden rows. A peer cursor on a hidden line clamps the same way; peer nothing on hidden rows. A peer cursor on a hidden line clamps the same way; peer
selection endpoints project, interiors drop. A click never selects a hidden line. selection endpoints project, interiors drop. A click never selects a hidden line.
Repeat the caret/peer/endpoint assertions with **crossing folds** (A hides
lines 13; B's head is hidden on line 2 and B hides through line 5): a point on
line 5 resolves to A's visible head position, never B's hidden
`range.start`.
7. **Ordinary overlays across a fold (F2).** A style/syntax span and a search wash 7. **Ordinary overlays across a fold (F2).** A style/syntax span and a search wash
on lines straddling a fold paint only on the visible rows, correctly aligned; on lines straddling a fold paint only on the visible rows, correctly aligned;
the completion popup anchored below a fold lands on the right visible row. the completion popup anchored below a fold lands on the right visible row.
@ -594,7 +652,9 @@ the precedents), sized for macOS startup + long temp-path width (handoff §5).
14. **Split of different buffers, one folded (F2).** A vertical split shows buffer A 14. **Split of different buffers, one folded (F2).** A vertical split shows buffer A
(with a fold) and buffer B (no fold). A's window collapses; **B's window is (with a fold) and buffer B (no fold). A's window collapses; **B's window is
byte-for-byte identical to the no-fold baseline** (no active-buffer/map leakage); byte-for-byte identical to the no-fold baseline** (no active-buffer/map leakage);
peer presence painted into B's window uses B's map. peer presence painted into B's window uses B's map. Keep folded A active and
wheel-scroll over inactive B: focus stays on A, while B's `view_top` and
cursor advance through **B's** raw-visible lines using B's map, never A's.
## 12. Gates (Stage 2) ## 12. Gates (Stage 2)
@ -613,8 +673,8 @@ failure as a regression (handoff §3).
- **Q#FD12** One shared visible-line map **primitive** (derivation + queries from - **Q#FD12** One shared visible-line map **primitive** (derivation + queries from
`folds()` + line offsets), instantiated as short-lived instances **per rendered `folds()` + line offsets), instantiated as short-lived instances **per rendered
window and per phase** (render via `Option<&'a VisibleLineMap>` on `Viewport<'a>` window and per phase** (render via `Option<&'a VisibleLineMap>` on `Viewport<'a>`
preserving `Copy`; after-frame per recipient window; command-time per active preserving `Copy`; after-frame per recipient window; command-time per operation
window), never a per-frame singleton (F2), home usable from `EditorCore`. target window), never a per-frame singleton (F2), home usable from `EditorCore`.
`view_top` stays a source-line index, set only via `clamp_view_top`. Every `view_top` stays a source-line index, set only via `clamp_view_top`. Every
consumer in the §2.2 census routes through it. (§3) consumer in the §2.2 census routes through it. (§3)
- **Q#FD13** Collapse in `TextView::render` (row `r``r`-th visible line); head - **Q#FD13** Collapse in `TextView::render` (row `r``r`-th visible line); head
@ -624,9 +684,10 @@ failure as a regression (handoff §3).
of the cursor; gutter width unchanged. (§5) of the cursor; gutter width unchanged. (§5)
- **Q#FD15** A diagnostic on a hidden line clamps to `visible_head_of` (outermost - **Q#FD15** A diagnostic on a hidden line clamps to `visible_head_of` (outermost
visible head), most-severe merge; not dropped. (§6) visible head), most-severe merge; not dropped. (§6)
- **Q#FD16** Render-time **position** clamp via `visible_position_of` (the outermost - **Q#FD16** Render-time **position** clamp via `visible_position_of` (the merged
fold's `range.start` — head row **and** head end-of-content column, F3) for the hidden component's visible `head_position` — head row **and** head
caret, peer cursors, and selection endpoints; hidden selection interiors drop; end-of-content column, including crossing overlaps) for the caret, peer cursors,
and selection endpoints; hidden selection interiors drop;
diagnostics and the relative-number anchor use the row-only `visible_head_of`; diagnostics and the relative-number anchor use the row-only `visible_head_of`;
click inverse maps grid rows to visible lines. (§7) click inverse maps grid rows to visible lines. (§7)
- **Q#FD17** *(ruled: include)* Vertical line-motion steps by visible lines; motion - **Q#FD17** *(ruled: include)* Vertical line-motion steps by visible lines; motion
@ -644,21 +705,24 @@ failure as a regression (handoff §3).
ellipsis only; on ⇒ col-0 sign cell on the head row with diagnostic priority. A ellipsis only; on ⇒ col-0 sign cell on the head row with diagnostic priority. A
dedicated fold column (unconditional marker + layout change) is deferred. (§5) dedicated fold column (unconditional marker + layout change) is deferred. (§5)
- **Q#FD21** Fold projection is **per-frontend**: a `fold_projection_active` flag on - **Q#FD21** Fold projection is **per-frontend**: a `fold_projection_active` flag on
`FrontendView`, set at attach from the advertised render kind (grid ⇒ true in `FrontendView`, set at attach from the negotiated selected-render bit (grid ⇒
Stage 2; semantic/GPU ⇒ false until Stage 3), cleared at detach. All true in Stage 2; semantic/GPU ⇒ false until Stage 3; LOCAL ⇒ true), cleared at
detach. All
command/event-time visible-line reckoning (motion, paging, wheel, click, command/event-time visible-line reckoning (motion, paging, wheel, click,
auto-scroll) is gated on the acting frontend's flag; render-time clamps are auto-scroll) is gated on the acting frontend's flag, while the map derives from
already grid-path-only. Keeps a simultaneous unfolded GPU session's cursor from the operation's target window (active for motion/paging/auto-scroll; explicit
skipping lines it still displays. (§7, §2.7) `win_id` for click/wheel); render-time clamps are already grid-path-only. Keeps
a simultaneous unfolded GPU session's cursor from skipping lines it still
displays without leaking another pane's fold map. (§7, §2.7)
## 14. Branch and PR plan ## 14. Branch and PR plan
Branch **`folding-tui`**, worktree `../pmacs-folding-tui`, off canonical `main` @ Branch **`folding-tui`**, worktree `../pmacs-folding-tui`, off canonical `main` @
`c49a8c7` (folding Stage 1 / #142 merged). This framing is the opening commits (rev `c49a8c7` (folding Stage 1 / #142 merged). This framing is the opening commits (rev
1 → rev 3); Stage 2 implements on this same branch and opens as the second folding 1 → rev 4); Stage 2 implements on this same branch and opens as the second folding
PR. One feature, one branch, one PR — Stage 3 (GPU) is a separate branch/PR off the PR. One feature, one branch, one PR — Stage 3 (GPU) is a separate branch/PR off the
resulting main. resulting main.
Housekeeping from #142 (retire the `docs/active-work.md` folding lane + refresh Housekeeping from #142 (retire the `docs/active-work.md` folding lane + refresh
`docs/agent-handoff.md` §1) **landed as the separate docs PR #147**, kept out of `docs/agent-handoff.md` §1) is **ready as the separate, unmerged docs PR #147**,
`folding-tui` (ruled). kept out of `folding-tui` (ruled).