diff --git a/docs/active-work.md b/docs/active-work.md index 59783a1..2ebc4b8 100644 --- a/docs/active-work.md +++ b/docs/active-work.md @@ -265,6 +265,51 @@ also removed: this branch's "R8 NEEDS A LANE" investigation block, and durable facts are in the retired registry row and the handoff §6 census. +## Destination capture (Q#JR14 generalization) — BRANCHED, framing in review + +**Written with the lane's first commit**, per the standing correction +from #171 and #215. + +**Branch `destination-capture`**, base `githubsucks/main` @ `4bc55e8` +(the #225 merge). **`githubsucks/destination-capture` is the +authoritative tip** — the ref, not a SHA. Recover with +`git fetch githubsucks && git checkout destination-capture`. + +- **Framing `docs/destination-capture-framing.md`, revision 1**, in + review. +- **A PREREQUISITE LANE. PR #227 (git Stage 1) blocks on it.** #227's + P1a review finding is why it exists: git's async completions mutate + and display UI without capturing the initiating frontend + (`builtin/runtime/git.lua:609`, `:854`), so a result surfaces in + whichever frontend is active when git exits. +- **The mechanism exists but is not Lua-reachable.** + `pmacs.window.commit_to` takes a `DirectoryDestinationLua`, which is + **nonconstructible from Lua** by design + (`src/lua_bindings/mod.rs:4256`) and minted only inside the + `path.open-directory` listener dispatch (`src/editor.rs:1311`) from a + `pub(crate)` capture (`:1241`). So no async Lua continuation outside + a directory open can say where its result belongs. +- **Scope:** a Lua-reachable capture, a generic rename + (`DirectoryDestination` → `ViewDestination`, 8 references across 4 + files — counted, not estimated), and the preflight question below. + **No adopter**: git's adoption is #227's work after this lands, since + a prerequisite that converts its own first consumer cannot be + reviewed separately from it. +- **The substantive question (Q#DC-2)** is that git's two continuations + differ in kind. `*git-status*` goes to the **bottom panel** + (`listview.open` defaults `display` to `"panel"`, + `builtin/runtime/listview.lua:550`); `*git-diff*` replaces a + **document** window. `commit_to`'s stale-intent check (Q#JR14c) is + right for the second and wrong for the first — the panel never + touches the captured window's buffer, so refusing on its change is a + refusal unrelated to what the continuation does. One shape + over-refuses the panel or under-checks the document. +- **Stop signal recorded in the framing:** if any existing dired test + needs editing, the generalization changed Journey Stage 1a's + semantics, and that is cause to stop rather than to adjust the test. +- **Gates:** `scripts/gate --acceptance ` plus dired's. + No `--protocol` — core and Lua bindings only. + ## LSP LaTeX coverage — IMPLEMENTED, gates green, no PR yet **Written with the lane's first commit**, per the standing correction @@ -603,6 +648,7 @@ authoritative tip** — the ref, not a SHA. Recover with — added in the second round — a **rename of either** the build or the sweep step each fail the suite. ||||||| parent of 72bbb96 (docs: LSP LaTeX coverage framing revision 2, on a branch at last) +||||||| parent of ac1d6cc (docs: frame a general destination capture (revision 1)) ## QoL arc retirement — PR #224 OPEN (docs only) diff --git a/docs/destination-capture-framing.md b/docs/destination-capture-framing.md new file mode 100644 index 0000000..10e32fa --- /dev/null +++ b/docs/destination-capture-framing.md @@ -0,0 +1,198 @@ +# A destination capture any async continuation can use + +**Status: framing pass, revision 1. Pre-implementation. Awaiting +approval.** + +**A prerequisite lane. PR #227 (git Stage 1) blocks on it**, and its +P1a review finding is the reason this exists. + +--- + +## 1. Why, and why as its own lane + +PR #227's review found that git's async completions mutate and display +UI without capturing the initiating frontend +(`builtin/runtime/git.lua:609`, `:854`), so a result can surface in +whichever frontend happens to be active when git exits. Run +`git.status` in frontend A, let frontend B become active, and A's panel +opens in B. + +**The finding named the right mechanism.** `pmacs.window.commit_to` +exists for exactly this continuation boundary: Journey Stage 1a's +Q#JR14 built it because "the listing settles a tick or more later, and +by then the ambient frontend, selected window, and active buffer may +all name something else" (`src/editor.rs:1238-1240`). + +**But it is not reachable from Lua outside one path**, which is why +this is a lane and not a line in #227: + +- `commit_to` takes a `DirectoryDestinationLua`, **nonconstructible + from Lua** by deliberate design (`src/lua_bindings/mod.rs:4256`) — + userdata with no constructor and no setters, so a caller cannot + fabricate a plausible triple. +- The only site that mints one is inside the `path.open-directory` + listener dispatch (`src/editor.rs:1311`), from + `capture_directory_destination`, which is `pub(crate)` + (`src/editor.rs:1241`). + +So any async Lua continuation that is **not** a directory open has no +way to say where its result belongs. Git is the first to need it; it +will not be the last. + +Landing this inside #227 would put new Lua API surface, over another +lane's merged mechanism, inside a feature branch — the same folding +that was declined for the `scripts/gate` repair, for the same reason. + +## 2. Ground truth + +- **The captured data is already generic.** + `DirectoryDestination { frontend, window, buffer }` + (`src/editor_core.rs:159-166`) contains nothing directory-specific. + Only its **name** and its **capture site** are. +- **The blast radius of a rename is small**: 8 references across 4 + files (`editor_core.rs`, `editor.rs`, `lua_bindings/mod.rs`, + `lua_bindings/window_panel.rs`). Checked, not estimated. +- **`commit_to`'s preflight is four checks** + (`src/lua_bindings/window_panel.rs:488-525`), in order: the + requesting frontend still has a layout; the destination window is + still live in it; **the window still shows the captured buffer** + (Q#JR14c stale intent); and the window is not dedicated (Q#JR14f). +- **`Handle:await` refuses inside a commit scope** + (`builtin/runtime/async.lua:87-90`) — yielding would restore the + scope while the coroutine is still parked. Any adopter awaits + *before* committing, as dired does. +- **Git's two continuations do not have the same shape**, and this is + the finding that shapes the design: + - `*git-status*` goes through `listview.open`, which resolves + `display` with a **`"panel"`** default + (`builtin/runtime/listview.lua:550`). It lands in the bottom + panel, **not** in a document window. + - `*git-diff*` calls `pmacs.window.display(buf, { select = true })` + — the **document** target, deliberately, "so the status panel it + was invoked from stays visible beside it" + (`builtin/runtime/git.lua:852-854`). + +## 3. The tension this lane has to resolve + +`DirectoryDestination.buffer` exists for one purpose, stated at its +definition: *"what that window held at capture time, so **stale intent +loses to the user**"* — a user who replaced the buffer while work was +in flight is newer information than the request. + +**That predicate is right for a document replacement and wrong for a +panel.** The git status panel does not replace the captured window's +buffer; it opens in the bottom panel beside it. Refusing to show it +because the user switched files in the document window would be a +refusal with no relationship to what the continuation actually does — +the panel case would inherit a check about a window it never touches. + +Meanwhile the diff case *is* a document replacement, and wants exactly +the dired semantics. + +So a single one-size destination either **over-refuses** the panel case +or **under-checks** the document case. Q#DC-2 is where that gets +decided, and it is the substance of this lane. + +## 4. The change, in outline + +- **A Lua-reachable capture**, returning the same nonconstructible + userdata for the *current* frontend and its document window. +- **Generic naming.** `DirectoryDestination` becomes something that + does not lie about a git panel; `capture_directory_destination` and + the userdata type follow. 8 references (§2). +- **The directory path keeps behaving exactly as it does today** — this + lane generalizes the capture, it does not change Journey Stage 1a's + semantics. +- **No adopter in this lane.** Git's adoption is #227's, after this + lands. A prerequisite that also converts its first consumer makes the + two impossible to review separately. + +## 5. Open questions + +### Q#DC-1 — what does the capture take as arguments? + +*My vote: **no arguments** — capture the acting frontend and its +document window from the ambient state at call time.* That is what the +existing `capture_directory_destination(frontend, window)` is handed by +its one caller, and a Lua-supplied frontend id would reintroduce the +fabrication hole the userdata design closes. + +### Q#DC-2 — one destination shape, or a panel/document distinction? **(the substantive one)** + +§3 is the problem. Three candidates: + +1. **One shape, all four checks.** Simplest; over-refuses the panel + case, and the refusal reason would be about a window the panel does + not touch. +2. **One shape, preflight parameterized by the continuation** — the + caller declares whether it is replacing the captured window's + buffer, and the stale-intent check applies only then. +3. **Two capture kinds**, document and panel, with different preflights. + +*My vote: **(2)***. The four checks are not equally applicable, and +which apply is a property of *what the continuation does*, which only +the caller knows. (3) duplicates the liveness checks that both need; +(1) ships a refusal that will read as a bug the first time a user hits +it. + +**I hold this one loosely.** It is the design decision of the lane, and +(1) has a real argument — a uniform rule is easier to reason about than +a parameterized one, and over-refusal is at least *safe*. + +### Q#DC-3 — what is the type called? + +*My vote: **`ViewDestination`***, with `pmacs.window.capture_destination()` +as the Lua entry point. It names what it is — a place in a view where a +continuation's result belongs — without claiming a directory or a +buffer kind. + +The Q#JR14 doc comments should keep their references intact; a rename +that orphans the rationale is worse than a slightly stale name. + +### Q#DC-4 — is the capture refused when there is no document window? + +`capture_directory_destination` already returns `None` when the +frontend has no document window (`src/editor.rs:1236`). *My vote: +**return `nil`, and require every adopter to handle it***, rather than +inventing a fallback destination. A continuation with nowhere to land +should say so, and #227's adopter should degrade to today's ambient +behaviour with a status message rather than silently guessing. + +## 6. Verification + +- **A captured destination survives a frontend switch**: capture in A, + make B active, commit, and assert the result lands in **A**. This is + P1a's actual failure and the reason the lane exists — asserting only + that the API returns userdata would pass on a capture that does + nothing. +- **A fabricated destination is still refused** — the existing Q#JR14d + guarantee, re-asserted after the rename so the generalization cannot + quietly open the hole it was built to close. +- **Each preflight refusal is witnessed by its own case**: frontend + gone, window gone, stale buffer, dedicated window — and, under + Q#DC-2's answer, that the stale-buffer refusal does **not** fire for + a continuation that declared it is not replacing that buffer. +- **`nil` when the frontend has no document window** (Q#DC-4). +- **The directory path is unchanged** — dired's existing acceptance + coverage passes untouched. **If any dired test needs editing, the + generalization changed Journey Stage 1a's semantics** and that is a + stop signal, not a fixup. +- **`Handle:await` still refuses inside the scope**, including through + `pmacs.async.yield_to_next_tick` if the worker-identity lane's Q#W-7 + has landed by then; if it has not, this lane does **not** add that + guard — it belongs to that lane and duplicating it would produce a + conflict for no benefit. + +**What this will NOT prove:** that git surfaces in the right frontend — +that is #227's adoption, after this lands. This lane ships the +mechanism and one set of tests for the mechanism. + +## 7. Not in scope + +**Adopting the capture anywhere**, including git (#227 does that) and +including migrating other async continuations that have the same latent +bug — worth an audit, not this lane's work. Changing Journey Stage 1a's +directory semantics. The `commit_to` scope guard for +`yield_to_next_tick` (worker identity Q#W-7). Any protocol change — +this is entirely core + Lua bindings. Panel geometry or placement +policy, which is the bottom-panel arc's.