Merge pull request #236 from levineuwirth/gui-arc-stage0
docs(gui-arc): Stage 0 — the standard sees the GUI
This commit is contained in:
commit
f8ad3e758a
426
COHERENCE.md
426
COHERENCE.md
|
|
@ -98,18 +98,18 @@ remain open to them.
|
||||||
| 2 | Golden product journey | **Runs end to end, thin at 11–12** | **Step 1 now works — v1.1.0 ships binaries (#211)**, so the journey no longer begins with a source build. `pmacs .` opens the directory (1a); the interface introduces itself (1b-3); a missing language server says so (1b-2, #204); a build is bound and prefilled (1b-1, #203). **Steps 11 (background-work ownership, §9) and 12 (session restore) are the remaining thin end** |
|
| 2 | Golden product journey | **Runs end to end, thin at 11–12** | **Step 1 now works — v1.1.0 ships binaries (#211)**, so the journey no longer begins with a source build. `pmacs .` opens the directory (1a); the interface introduces itself (1b-3); a missing language server says so (1b-2, #204); a build is bound and prefilled (1b-1, #203). **Steps 11 (background-work ownership, §9) and 12 (session restore) are the remaining thin end** |
|
||||||
| 3 | Zero-configuration state | **Partial** | Defaults genuinely strong; missing-tool failure is silent, not graceful |
|
| 3 | Zero-configuration state | **Partial** | Defaults genuinely strong; missing-tool failure is silent, not graceful |
|
||||||
| 4 | Progressive disclosure | **Inverted** | The advanced level is real; the beginner level is the missing one |
|
| 4 | Progressive disclosure | **Inverted** | The advanced level is real; the beginner level is the missing one |
|
||||||
| 5 | Unified discoverability | **Partial** | Discovery Stage 1: eleven `help.*` commands (describe key/mode/hook/buffer/command/setting, where-is, list commands/keybindings/settings, apropos) over the existing registries, indexed by `M-x help`. Commands, keys, modes, hooks and settings are now reachable, and `*workers*` already was (`M-x editor.list-workers`); **packages have no comparable surface** (§13), and workers still lack owner/purpose/hierarchy and any indicator (§9). `Command` still has no title/category/flags, M-x rows are still bare names, and the Rust help layer is still orphaned |
|
| 5 | Unified discoverability | **Partial** | Discovery Stage 1: eleven `help.*` commands (describe key/mode/hook/buffer/command/setting, where-is, list commands/keybindings/settings, apropos) over the existing registries, indexed by `M-x help`. Commands, keys, modes, hooks and settings are now reachable, and `*workers*` already was (`M-x editor.list-workers`); **packages have no comparable surface** (§13), and workers carry a required **purpose** and a statusline **activity indicator** since #232, but still lack an accountable **owner** and any **hierarchy** (§9). `Command` still has no title/category/flags, and the Rust help layer is still orphaned. M-x rows carry descriptions since #228 (protocol v23) |
|
||||||
| 6 | Interaction islands | **Weak, and growing** | Six hardcoded key-interception shadows; no transient-keymap mechanism exists |
|
| 6 | Interaction islands | **Weak, and growing** | Six hardcoded key-interception shadows; no transient-keymap mechanism exists |
|
||||||
| 7 | First-class workspaces | **Missing (conventions only)** | Marker walk + four independent consumers; no workspace object |
|
| 7 | First-class workspaces | **Missing (conventions only)** | Marker walk + four independent consumers; no workspace object |
|
||||||
| 8 | Execution locations | **Missing (architecture ready)** | SSH attach works; "location" is not a value anywhere |
|
| 8 | Execution locations | **Missing (architecture ready)** | SSH attach works; "location" is not a value anywhere |
|
||||||
| 9 | Worker ownership | **Mechanism without identity** | Cancellation solid; no owner/purpose/hierarchy; four disjoint activity views |
|
| 9 | Worker ownership | **Mechanism with purpose, without ownership** | Cancellation solid; **purpose required on every job and process, and a statusline activity indicator, since #232**; still **no accountable owner and no hierarchy**; four disjoint activity views |
|
||||||
| 10 | Extension trust classes | **Missing (one class)** | Shared Lua state, `__index = _G`; MCP is the one out-of-process seam |
|
| 10 | Extension trust classes | **Missing (one class)** | Shared Lua state, `__index = _G`; MCP is the one out-of-process seam |
|
||||||
| 11 | Config layering + provenance | **Partial (foundation only)** | Typed registry is right; 5 settings live in it; no value provenance |
|
| 11 | Config layering + provenance | **Partial (foundation only)** | Typed registry is right; 5 settings live in it; no value provenance |
|
||||||
| 12 | Profiles | **Missing** | One hardcoded default keymap; not a named concept |
|
| 12 | Profiles | **Missing** | One hardcoded default keymap; not a named concept |
|
||||||
| 13 | Package lifecycle UX | **Resolution without lifecycle** | Mature resolver/lockfile; init-only install; no uninstall/disable/search |
|
| 13 | Package lifecycle UX | **Resolution without lifecycle** | Mature resolver/lockfile; init-only install; no uninstall/disable/search |
|
||||||
| 14 | Workbench primitives | **Partial (best trajectory)** | Listview is a real primitive but only **4** call sites, all LSP panels (`*lsp*` added post-audit by #204); buffer-list and search re-implement it; **the bottom panel is COMPLETE — both frontends, and Stage 3 flipped the adopter default so omission means the panel**. **Tree is implemented (◐) with the LSP outline as its one adopter; the remaining consumers, including dired's `i`, have not adopted** |
|
| 14 | Workbench primitives | **Partial (best trajectory)** | Listview is a real primitive with executable call sites in **two** modules — `lsp.lua` (**4**) and **`git.lua` (1, `*git-status*`, #227)** — so it is **no longer LSP-only**; *(counted excluding comment mentions, which an earlier `grep -c` included)*; buffer-list and search re-implement it; **the bottom panel is COMPLETE — both frontends, and Stage 3 flipped the adopter default so omission means the panel**. **Tree is implemented (◐) with the LSP outline as its one adopter; the remaining consumers, including dired's `i`, have not adopted** |
|
||||||
| 15 | Contextual affordances | **Weak** | Right-click menu only; code actions apply first-blindly; no git integration at all |
|
| 15 | Contextual affordances | **Weak** | Right-click menu only; code actions apply first-blindly; **Git integration reaches status and diff (#227, Stage 1) and no further** — §15's ground truth is authoritative, and this row previously said "no git integration at all" |
|
||||||
| 16 | Semantic frontend | **Strong** | v6..=v21 schema support; production attach remains v20 during the dark panel slice; degradation practiced |
|
| 16 | Semantic frontend | **Architectural: Strong · Product: Weak** | v6..=v23 schema support; production attach remains v20 during the dark panel slice; degradation practiced. **The two subgrades and the product criteria live in §16** — the row points there rather than carrying a grade of its own, so the GUI-as-a-product half cannot hide inside an architectural `Strong` |
|
||||||
| 17 | Distribution | **Partial** | **v1.1.0 ships prebuilt Linux/macOS binaries on tag** (#211) with checksums and a stated glibc floor. No channels, in-place update, rollback, signing, or package-manager distribution |
|
| 17 | Distribution | **Partial** | **v1.1.0 ships prebuilt Linux/macOS binaries on tag** (#211) with checksums and a stated glibc floor. No channels, in-place update, rollback, signing, or package-manager distribution |
|
||||||
| 18 | Onboarding | **Partial** | Journey Stage 1b-3: an unconfigured launch greets in `*scratch*` naming `M-x` and four real bindings, and `M-x help` renders a cheat sheet. Still no tutorial and `C-h` still deletes a word — deliberately, see §18 |
|
| 18 | Onboarding | **Partial** | Journey Stage 1b-3: an unconfigured launch greets in `*scratch*` naming `M-x` and four real bindings, and `M-x help` renders a cheat sheet. Still no tutorial and `C-h` still deletes a word — deliberately, see §18 |
|
||||||
| 19 | Coherence acceptance tests | **Started** | `tests/journey_acceptance.rs` carries 45 pins over steps 2, 3, 4, 5, 6 and 9 — the ratchet is real and stages add rows to it. The other five §19 scenarios (workspace lifecycle, worker ownership, config provenance, package lifecycle, extension isolation) are still unwritten |
|
| 19 | Coherence acceptance tests | **Started** | `tests/journey_acceptance.rs` carries 45 pins over steps 2, 3, 4, 5, 6 and 9 — the ratchet is real and stages add rows to it. The other five §19 scenarios (workspace lifecycle, worker ownership, config provenance, package lifecycle, extension isolation) are still unwritten |
|
||||||
|
|
@ -356,7 +356,200 @@ This journey should become a release gate. New architectural work should
|
||||||
be evaluated partly by whether it improves, preserves, or complicates
|
be evaluated partly by whether it improves, preserves, or complicates
|
||||||
the journey.
|
the journey.
|
||||||
|
|
||||||
### Ground truth: the journey today
|
### 2a. Required subclaims per step (the GUI arc, Stage 0)
|
||||||
|
|
||||||
|
**A grade whose subclaims are unstated is not falsifiable.** The
|
||||||
|
per-frontend table below grades each (step, frontend) cell as the
|
||||||
|
**minimum over that step's required subclaims** — never the first word
|
||||||
|
of a prose verdict. This section is that list, published so a reader can
|
||||||
|
disagree with a grade by pointing at a subclaim rather than at taste.
|
||||||
|
|
||||||
|
Two rules decide what belongs here. **Discoverability is a subclaim
|
||||||
|
wherever it is the step's substance**, not an annotation on it — steps
|
||||||
|
4, 7, 8, 9 and 11 are graded on whether a capability is *findable*, because
|
||||||
|
"present but nobody can find it" is the failure those steps exist to
|
||||||
|
catch. And **any defect cited as the reason a cell is below `Works` must be a
|
||||||
|
subclaim**: an annotation cannot absorb a failing one. *Narrowed from
|
||||||
|
"anything named as a defect", which swept in limitations this section
|
||||||
|
explicitly excludes — no package manager, no tutorial — and so
|
||||||
|
contradicted its own exclusions.*
|
||||||
|
|
||||||
|
| # | Step | Required subclaims |
|
||||||
|
|---|---|---|
|
||||||
|
| 1 | Install | (a) a documented install path completes on a clean machine; (b) the installed binary launches |
|
||||||
|
| 2 | Launch unconfigured | (a) starts with no user config; (b) presents a usable editing surface; (c) raises no error the user must dismiss |
|
||||||
|
| 3 | Open real project | (a) a file opens from the CLI; (b) a file opens from inside the editor; (c) a directory opens and is browsable; (d) the project root is established |
|
||||||
|
| 4 | Understand interface | (a) the mode line reports buffer state; (b) a help surface exists; (c) **discoverable** — the route to it is advertised in-product, not only known |
|
||||||
|
| 5 | Edit | (a) text edits apply and render; (b) undo/redo; (c) selection, kill and yank; (d) **the full contents of an overlong line are reachable**, by wrapping or horizontal navigation |
|
||||||
|
| 6 | Language intelligence | (a) a server attaches for a supported language; (b) diagnostics surface; (c) completion, hover and go-to-definition are available; (d) styling reflects the server; (e) **failure is visible and actionable** — both a startup failure and a *later* crash say so, rather than intelligence silently ceasing |
|
||||||
|
| 7 | Find symbol / file | (a) open a file by path; (b) browse to a file without knowing its path; (c) find a symbol; (d) **discoverable** — the file, browse and symbol routes are **advertised in-product**, not merely bound |
|
||||||
|
| 8 | Open terminal | (a) a terminal opens; (b) input and output round-trip; (c) it can be closed or killed; (d) **discoverable** — reachable by an advertised binding |
|
||||||
|
| 9 | Build / test | (a) a build or test command runs **in the opened project's context**; (b) its output is captured; (c) the output is navigable; (d) **discoverable** — §2's own criteria say "Build or test command discoverable" (line above), which is what `C-c c` was bound to satisfy |
|
||||||
|
| 10 | Inspect error | (a) errors are enumerated; (b) navigation reaches the site; (c) the site is marked in the buffer |
|
||||||
|
| 11 | Understand what background work is running | (a) work in flight is **visible without asking**; (b) a detailed view is reachable that names each substantial background work item's **purpose and accountable owner — not merely its kind or dispatcher** — **across jobs, processes, servers and terminals**; (c) **discoverable** — reachable by an advertised binding; (d) cancellable |
|
||||||
|
| 12 | Close + restore | (a) closing is clean and loses no data; (b) per-file state (cursor, scroll) restores; (c) the open-buffer set and window layout restore |
|
||||||
|
|
||||||
|
**Aggregation inside a subclaim, stated because it decided two cells.**
|
||||||
|
A subclaim that names a conjunction — "completion, hover and
|
||||||
|
go-to-definition"; "a startup failure *and* a later crash" — is graded
|
||||||
|
by the same minimum rule as the step: **an absent member makes the
|
||||||
|
subclaim `Missing`, not `Partial`.** Degradation is for a member that is
|
||||||
|
present and qualified; absence is absence at every level. Steps 6 and 11
|
||||||
|
are `Missing` on this rule, and an earlier draft graded both `Partial`
|
||||||
|
by treating a conjunction as one degradable atom — which would have made
|
||||||
|
the ordinal mean something different at the subclaim level than at the
|
||||||
|
step level, and is the reading the framing's own Step 7 worked example
|
||||||
|
already rejects.
|
||||||
|
|
||||||
|
**3(c) requires a browsable directory SURFACE, not dired.** Dired is
|
||||||
|
today's evidence for it, not its definition — so a frontend inherits
|
||||||
|
whatever browsing it actually has, which is precisely what the
|
||||||
|
per-frontend columns exist to expose. A subclaim naming the
|
||||||
|
implementation would have graded the mechanism instead of the journey.
|
||||||
|
|
||||||
|
**Deliberately NOT subclaims.** Performance, aesthetics, and parity with
|
||||||
|
other editors: each is real, and each is graded elsewhere or nowhere.
|
||||||
|
**5(d) is the boundary case and is deliberately a correctness claim**:
|
||||||
|
unreachable text is the long-lines arc's defect, while its 100 MB timing
|
||||||
|
witness stays a separate performance gate. "Large files stay usable" was
|
||||||
|
in an earlier draft of this row and was performance wearing a
|
||||||
|
correctness coat.
|
||||||
|
Folding them in here would make every cell a judgement about taste and
|
||||||
|
the ordinal would stop meaning anything. **Frontend-specific
|
||||||
|
limitations are also not subclaims** — they are what the three columns
|
||||||
|
*measure*, so encoding them here would beg the question.
|
||||||
|
|
||||||
|
**Step 12(c) is the worked example of why subclaims are per step and
|
||||||
|
not per frontend.** It is a required subclaim everywhere, and Q#DS9
|
||||||
|
makes it structurally impossible on any daemon-backed frontend — so the
|
||||||
|
GPU cell fails it by construction rather than by omission. That is a
|
||||||
|
finding the old single-verdict table could not express, because "Partial"
|
||||||
|
was doing the work of both "some of this is missing" and "some of this
|
||||||
|
cannot exist yet".
|
||||||
|
|
||||||
|
### 2b. The per-frontend journey table
|
||||||
|
|
||||||
|
Each cell is the **minimum over §2a's required subclaims**, with the
|
||||||
|
binding subclaim named. Columns: **local TUI** (`pmacs .`), **attached
|
||||||
|
TUI** (`pmacs --attach`), **GPU**. **The bar is GPU ≥ local TUI**; the
|
||||||
|
attached column is evidence, separating a daemon-boundary gap from a
|
||||||
|
frontend-local one.
|
||||||
|
|
||||||
|
Anchors: `COHERENCE.md` §2's ground-truth rows, the GUI arc audit at
|
||||||
|
`4bc55e8` (`docs/gui-arc-framing.md` §2), Q#DS9
|
||||||
|
(`docs/desktop-save-framing.md`), and `builtin/runtime/welcome.lua`.
|
||||||
|
|
||||||
|
**What counts as "advertised in-product", stated because an earlier
|
||||||
|
draft of this paragraph got it wrong and mis-graded two steps.** The
|
||||||
|
welcome advertises **both** a key table (`C-x C-f`, `C-c t`, `C-c c`,
|
||||||
|
`C-x b`) **and prose** — its first line names `M-x` and `M-x help`
|
||||||
|
(`welcome.lua:61`). Advertisement is therefore **transitive through the
|
||||||
|
help graph**: a command reachable from an advertised route counts as
|
||||||
|
advertised, which is how `help.list-keybindings` carries browse and
|
||||||
|
symbol (step 7) and how `help.list-commands` carries
|
||||||
|
`editor.list-workers` (step 11). Reading only the four-entry key table
|
||||||
|
is what produced the earlier `Partial` at steps 4 and 7.
|
||||||
|
|
||||||
|
**A binding is a stronger claim than a route, and 11(c) asks for the
|
||||||
|
binding.** That is why step 11 still fails while step 7 passes: both are
|
||||||
|
reachable through the help graph, but **no binding reaches
|
||||||
|
`editor.list-workers`**. `*workers*` is not binding-free — it carries a
|
||||||
|
buffer-local `C-c C-k` for `workers.cancel-at-point`
|
||||||
|
(`async.lua`; `docs/keybindings.md:282`) — and that binding lives
|
||||||
|
*inside* the view, so it cannot help anyone find it. The absent thing is
|
||||||
|
specifically **a binding that opens it**.
|
||||||
|
|
||||||
|
| # | Step | local TUI | attached TUI | GPU | binding subclaim |
|
||||||
|
|---|---|---|---|---|---|
|
||||||
|
| 1 | Install | Works | Works | **Works** | — release builds, ships and *verifies* `pmacs-gpu` (`release.yml:149,168,195`) |
|
||||||
|
| 2 | Launch unconfigured | Works | Works | Works | — |
|
||||||
|
| 3 | Open real project | Works | Works | Works | 3(c) satisfied by a browsable surface, not by dired specifically |
|
||||||
|
| 4 | Understand interface | Works | Works | Works | 4(c) satisfied — the welcome's **first line** says "M-x runs any command; **M-x help lists the keys**" (`welcome.lua:61`). An earlier draft graded this Partial by reading only the four-entry key table and ignoring the prose above it |
|
||||||
|
| 5 | Edit | Works | Works | **Partial** | 5(a) on GPU — **no IME, no `set_ime_allowed`**, so composed/CJK input is impossible; Latin editing is fine. Local/attached satisfy 5(d) by wrap and by QoL Stage 5 horizontal scroll |
|
||||||
|
| 6 | Language intelligence | **Missing** | **Missing** | **Missing** | 6(e) — startup failure surfaces; a **later crash does not**, and an absent member of a conjunction makes the subclaim **Missing**, not degraded (§2a's aggregation rule). `server_is_live` is consulted at five sites and every one *skips* work rather than reporting. GPU additionally degrades 6(d) — single-authority semantic styling vs the grid's `merge_styles` — but the grade is already floored by 6(e) |
|
||||||
|
| 7 | Find symbol / file | Works | Works | Works | 7(d) satisfied **under the published "advertised in-product" wording**: the advertised `M-x help` route reaches `help.list-keybindings`, which lists **every registered binding** (`help.lua`; asserted over every sequence at `discovery_acceptance.rs:208`), so browse and symbol are advertised transitively. *If the intent is direct advertisement only, 7(d) must say so — and the framing's own worked example then forces **Missing**, never Partial* |
|
||||||
|
| 8 | Open terminal | Works | Works | Works | 8(c) satisfied by the **global** `M-x buffer.kill-this` (`default.lua:1209`); killing a terminal buffer prunes the session and reaps the process (`vterm_stage1_acceptance.rs:336`). 8(c) never required a terminal-*specific* command. 8(d) passes on the welcome's `C-c t` |
|
||||||
|
| 9 | Build / test | Works | Works | Works | 9(d) passes on the welcome's `C-c c` entry, not on the bare binding; 9(a) runs in the detected project's context |
|
||||||
|
| 10 | Inspect error | Works | Works | Works | all of 10(a–c) hold. *Being gated on step 6 or 9 is a **dependency, not a subclaim**, and §2a's membership rule forbids an annotation from lowering a grade — an earlier draft let it do exactly that. Grading the dependency would require adding it as a subclaim first* |
|
||||||
|
| 11 | Understand background work | **Missing** | **Missing** | **Missing** | 11(c) — **no binding opens `editor.list-workers`**, so a keybinding listing cannot lead anyone to it: the subclaim is **absent**, not degraded, and it floors the cell. *The view is not binding-free — `C-c C-k` runs `workers.cancel-at-point` buffer-locally inside it (`docs/keybindings.md:282`) — but a binding that only works once you are already there cannot satisfy a discoverability subclaim.* 11(b) fails too — #232 gives **purpose**, never an accountable **owner**, and the planes stay disjoint (jobs in `*workers*`, processes in `pmacs.process.list`, servers in `*lsp*`, terminals **nowhere**). 11(a) passes on the activity indicator |
|
||||||
|
| 12 | Close + restore | **Partial** | **Missing** | **Missing** | 12(c) — local restores nothing beyond per-file state (desktop-save is opt-in); on **both daemon-backed frontends it is a structural no-op** (Q#DS9), so they fail it **by construction** |
|
||||||
|
|
||||||
|
**Closure condition 2 currently FAILS, with KNOWN failures at two
|
||||||
|
steps and the exact set pending verification.** GPU is below local TUI
|
||||||
|
at **step 5** (Partial vs Works) and **step 12** (Missing vs Partial),
|
||||||
|
and every *graded* cell elsewhere is equal — but GPU 3(c) and 9 rest on
|
||||||
|
inference (below), so a third failure cannot be ruled out until they are
|
||||||
|
verified. "Exactly two" would be a stronger claim than this table's own
|
||||||
|
evidence supports, and an earlier draft made it. GPU 6(c) is inferred
|
||||||
|
too, but cannot change step 6, which is already floored at `Missing` by
|
||||||
|
6(e) on all three frontends. That is the whole of
|
||||||
|
the GPU's journey deficit as this table measures it — a narrower result
|
||||||
|
than "the GUI feels behind", and a falsifiable one. It survived a review
|
||||||
|
round that corrected six of the twelve rows, which is some evidence it
|
||||||
|
is a property of the tree rather than of the grader.
|
||||||
|
|
||||||
|
**The journey's two worst steps are frontend-INDEPENDENT**, and that is
|
||||||
|
the table's other finding. Steps 6 and 11 grade `Missing` in all three
|
||||||
|
columns: language intelligence dies silently after a server crash, and
|
||||||
|
**no binding opens** the background-work view — a route exists
|
||||||
|
(`M-x help` → `help.list-commands` → `editor.list-workers`), and a
|
||||||
|
buffer-local `C-c C-k` exists *within* the view, but nothing bound leads
|
||||||
|
to it. That is why 11(c) asks for a binding rather than a route, and why
|
||||||
|
the binding it asks for is one that **reaches** the view. Neither is GUI work,
|
||||||
|
neither is closed by this arc, and both were previously carried as
|
||||||
|
`Partial` — which is how they stayed off the critical path.
|
||||||
|
|
||||||
|
**The attached column earns its place at those two rows, and they point
|
||||||
|
opposite ways.** Step 12 fails on attached TUI *and* GPU, so it is a
|
||||||
|
**daemon-boundary** gap that no GPU work can close — Q#DS9 and the
|
||||||
|
workspace object own it, which is why Stage 4b is P2-gated. Step 5
|
||||||
|
fails on GPU *alone*, so it is **frontend-local** — Stage 1d's IME work
|
||||||
|
closes it. A single merged TUI column would have shown two identical
|
||||||
|
red cells and no way to tell those apart.
|
||||||
|
|
||||||
|
**Inference flags — and when they must be closed (user ruling,
|
||||||
|
2026-08-11).** These do **not** block the Stage 0 docs PR. They **must**
|
||||||
|
be verified **before this table is first enforced as a release gate**.
|
||||||
|
**3(c) and 9 can change the deficit set**; **6(c) cannot change step 6's
|
||||||
|
grade today** — 6(e) already floors it at `Missing` on all three
|
||||||
|
frontends — but still needs evidentiary closure rather than standing
|
||||||
|
indefinitely as an assumption.
|
||||||
|
|
||||||
|
GPU 3(c) is inferred **in both halves** —
|
||||||
|
rendering and browsing interaction — as are GPU 9 (compile output
|
||||||
|
renders like any other daemon buffer) and GPU 6(c) (hover reaches the
|
||||||
|
echo area as on the grid). All must be verified before this table is
|
||||||
|
used as a release gate.
|
||||||
|
|
||||||
|
*An earlier draft called 3(c)'s rendering half "verified" on
|
||||||
|
`gpu_invocation_acceptance.rs:706`. That test runs
|
||||||
|
`--headless-managed-probe`, which connects, receives and DECODES a
|
||||||
|
`BufferSnapshot` into text; it never constructs GPU render state and
|
||||||
|
never calls `render_offscreen` (`pmacs-gpu/src/main.rs:1065`). It
|
||||||
|
witnesses delivery and decoding — which is real, and is not rendering.
|
||||||
|
Retracted rather than softened, because "verified" was the word doing
|
||||||
|
the damage.*
|
||||||
|
|
||||||
|
### Ground truth: the journey today — EVIDENCE ONLY; §2b OWNS THE GRADES
|
||||||
|
|
||||||
|
**The verdict column below is the SUPERSEDED single-verdict model.**
|
||||||
|
§2b's per-frontend table is authoritative for every grade, and where the
|
||||||
|
two disagree — steps 4, 6, 7, 10 and 11 — **§2b is correct and this
|
||||||
|
table's verdict is historical.** It disagrees because it grades by a
|
||||||
|
prose string rather than by the minimum over declared subclaims, which
|
||||||
|
is the reading §2a and §2b exist to replace.
|
||||||
|
|
||||||
|
**Kept rather than deleted, and kept rather than re-synced.** Its value
|
||||||
|
is the per-step *evidence* — what exists, what was measured, which PR
|
||||||
|
changed it — which §2b's cells cite and do not restate. Re-syncing its
|
||||||
|
verdicts would restore a second source of truth for grades and
|
||||||
|
guarantee this drift recurs; marking it removes the second source
|
||||||
|
instead.
|
||||||
|
|
||||||
|
**A historical grade may stand here. A false statement about the tree
|
||||||
|
may not** — the two are different, and only the first is what "kept for
|
||||||
|
its evidence" licenses. Step 8's "no close/kill command" was the latter
|
||||||
|
and is corrected in place below.
|
||||||
|
|
||||||
**Grade: reaches step 5; thin from step 6 on.** Was **broken at step 3**
|
**Grade: reaches step 5; thin from step 6 on.** Was **broken at step 3**
|
||||||
at audit time:
|
at audit time:
|
||||||
|
|
@ -395,11 +588,11 @@ Full verdict table:
|
||||||
| 4 | Understand interface | **Partial** | Mode line gives name/modified/L:C/scroll + mode/LSP/terminal segments. Journey Stage 1b-3 adds a welcome in `*scratch*` and `M-x help`; **still Partial** because `C-h` deletes a word (deliberately — §18) and there is no tutorial |
|
| 4 | Understand interface | **Partial** | Mode line gives name/modified/L:C/scroll + mode/LSP/terminal segments. Journey Stage 1b-3 adds a welcome in `*scratch*` and `M-x help`; **still Partial** because `C-h` deletes a word (deliberately — §18) and there is no tutorial |
|
||||||
| 5 | Edit | **Works** | Full CUA + Emacs keymap in 161 lines (`builtin/keymaps/default.lua`); isearch, query-replace, kill ring, undo/redo, auto-indent/pair/comment, atomic save. Genuinely excellent zero-config |
|
| 5 | Edit | **Works** | Full CUA + Emacs keymap in 161 lines (`builtin/keymaps/default.lua`); isearch, query-replace, kill ring, undo/redo, auto-indent/pair/comment, atomic save. Genuinely excellent zero-config |
|
||||||
| 6 | Language intelligence | **Partial** | Rust grammar bundled and auto-attaches; rust-analyzer preconfigured (`builtin/runtime/lsp.lua`). **Journey Stage 1b-2 (#204) ended the silence** for a server that fails to *start*: the status line names the command, language and errno once per `(language, root, command)`; the modeline reads `LSP:!` instead of nothing; and `M-x lsp.status` renders `*lsp*` over the `status_buffer_text()` renderer that had existed since M4.8 with no caller. **Still Partial**, for a reason unaffected by that landing: a server that starts and then *crashes* is still unsurfaced — `LspEventKind::Crashed` is pushed and no builtin subscriber handles it |
|
| 6 | Language intelligence | **Partial** | Rust grammar bundled and auto-attaches; rust-analyzer preconfigured (`builtin/runtime/lsp.lua`). **Journey Stage 1b-2 (#204) ended the silence** for a server that fails to *start*: the status line names the command, language and errno once per `(language, root, command)`; the modeline reads `LSP:!` instead of nothing; and `M-x lsp.status` renders `*lsp*` over the `status_buffer_text()` renderer that had existed since M4.8 with no caller. **Still Partial**, for a reason unaffected by that landing: a server that starts and then *crashes* is still unsurfaced — `LspEventKind::Crashed` is pushed and no builtin subscriber handles it |
|
||||||
| 7 | Find symbol / file | **File: fixed (open by path merged #162; browsing #165). Symbol: works but undiscoverable** | No find-file/dired/picker existed at audit. Now `C-x C-f` opens a known path and `C-x d` / `C-x C-j` browse (flat listing, `dired` mode keymap); `M-.`/`M-?`/`C-c o` still bound but advertised nowhere and server-gated; no workspace-symbol command; `pmacs.index.*` has no UI |
|
| 7 | Find symbol / file | **File: fixed (open by path merged #162; browsing #165). Symbol: works but undiscoverable** | No find-file/dired/picker existed at audit. Now `C-x C-f` opens a known path and `C-x d` / `C-x C-j` browse (flat listing, `dired` mode keymap); `M-.`/`M-?`/`C-c o` are bound and server-gated. **The "advertised nowhere" claim previously recorded here was false** under the transitive-help contract (§2b): being bound, they are named by `help.list-keybindings`, which the welcome's advertised `M-x help` reaches — which is why §2b grades this step `Works`. Corrected in place as a statement about the tree, while the historical verdict at the left stands. Still true: no workspace-symbol command, and `pmacs.index.*` has no UI |
|
||||||
| 8 | Open terminal | **Works** | Full PTY with scrollback + modeline segment, bound to `C-c t` and configurable through three registered settings (`terminal.default-profile`, `terminal.scrollback-rows`, `terminal.escape-key`) plus named `pmacs.terminal.profiles` (PR #173), and searchable through `M-x terminal.copy-mode` / `C-c C-t`, which materializes the retained scrollback into an ordinary read-only buffer (Stage 2). Named limitations: `C-c t` is unreachable from *inside* a terminal window, where `C-c` is consumed as the escape — `M-x terminal` still works there; and there is still **no close/kill command**, which is the remaining half of this step's discoverability gap. *Was broken outright on the GPU frontend until the double terminal-layout sync was fixed: the child took a `SIGWINCH` storm at tick cadence, so typing into it was impossible while output still flowed.* |
|
| 8 | Open terminal | **Works** | Full PTY with scrollback + modeline segment, bound to `C-c t` and configurable through three registered settings (`terminal.default-profile`, `terminal.scrollback-rows`, `terminal.escape-key`) plus named `pmacs.terminal.profiles` (PR #173), and searchable through `M-x terminal.copy-mode` / `C-c C-t`, which materializes the retained scrollback into an ordinary read-only buffer (Stage 2). Named limitations: `C-c t` is unreachable from *inside* a terminal window, where `C-c` is consumed as the escape — `M-x terminal` still works there; and `C-c t` is not re-advertised there. **The "no close/kill command" claim previously recorded here was false**: `M-x buffer.kill-this` is global (`builtin/commands/default.lua:1209`) and killing a terminal buffer prunes the session and reaps the owned process (`tests/vterm_stage1_acceptance.rs:336`). Corrected in place because it is a statement about the tree, not a historical grade. *Was broken outright on the GPU frontend until the double terminal-layout sync was fixed: the child took a `SIGWINCH` storm at tick cadence, so typing into it was impossible while output still flowed.* |
|
||||||
| 9 | Build / test | **Works** | Journey Stage 1b-1 (#203): `C-c c` runs `compile.run`, and the first prompt is prefilled from the detected project kind (`pmacs.compile.defaults`, seeded `rust = "cargo build"`, extensible from `init.lua`) via `ProjectKind::Rust` — **not** `Cargo`, see §24. The prompt **captures** its directory rather than re-resolving at accept time, so the command it offers and the directory it runs in cannot drift while the minibuffer waits. Still defaults cwd to the detected project root and parses Rust `-->` errors. Named limitation: after `pmacs <dir>` the active buffer is dired's and pathless, so the cwd falls back to the process cwd — §8's execution-location model owns that, and the degradation stays coherent (no suggestion is offered for a directory with no detected Cargo project) |
|
| 9 | Build / test | **Works** | Journey Stage 1b-1 (#203): `C-c c` runs `compile.run`, and the first prompt is prefilled from the detected project kind (`pmacs.compile.defaults`, seeded `rust = "cargo build"`, extensible from `init.lua`) via `ProjectKind::Rust` — **not** `Cargo`, see §24. The prompt **captures** its directory rather than re-resolving at accept time, so the command it offers and the directory it runs in cannot drift while the minibuffer waits. Still defaults cwd to the detected project root and parses Rust `-->` errors. Named limitation: after `pmacs <dir>` the active buffer is dired's and pathless, so the cwd falls back to the process cwd — §8's execution-location model owns that, and the degradation stays coherent (no suggestion is offered for a directory with no detected Cargo project) |
|
||||||
| 10 | Inspect error | **Partial (good once reached)** | `E:n W:n` modeline counts, underlines, `M-g n/p` + ``C-x ` `` walking a unified compile/grep/diag source, message echo, `RET` visits. Gated entirely on step 6 or 9 succeeding first |
|
| 10 | Inspect error | **Partial (good once reached)** | `E:n W:n` modeline counts, underlines, `M-g n/p` + ``C-x ` `` walking a unified compile/grep/diag source, message echo, `RET` visits. Gated entirely on step 6 or 9 succeeding first |
|
||||||
| 11 | See background work | **Works but undiscoverable** | `*workers*` view via `M-x editor.list-workers`; `C-c C-k` cancel-at-point. No keybinding, no statusline spinner/progress indicator anywhere (§9) |
|
| 11 | See background work | **Partial** | **Statusline activity indicator since #232** — in-flight count plus the oldest job's purpose, absent entirely when idle, through `ui.activity-indicator`. `*workers*` view via `M-x editor.list-workers`, **with no binding that OPENS it**; `C-c C-k` cancel-at-point works buffer-locally once inside. *Was "Works but undiscoverable — no statusline spinner/progress indicator anywhere"; #232 shipped exactly that indicator on 2026-08-09 and the row went stale the same day* |
|
||||||
| 12 | Close + restore | **Partial** | Per-file cursor+scroll (saveplace), recent files, minibuffer history, autosave recovery all restore zero-config. Open-buffer set and window layout do **not**: desktop-save is opt-in (`pmacs.session.desktop_mode(true)`) *and* a documented no-op under a daemon (`src/desktop.rs:323-326`, `:353-356`, Q#DS9) |
|
| 12 | Close + restore | **Partial** | Per-file cursor+scroll (saveplace), recent files, minibuffer history, autosave recovery all restore zero-config. Open-buffer set and window layout do **not**: desktop-save is opt-in (`pmacs.session.desktop_mode(true)`) *and* a documented no-op under a daemon (`src/desktop.rs:323-326`, `:353-356`, Q#DS9) |
|
||||||
|
|
||||||
A journey observation worth keeping verbatim from the audit:
|
A journey observation worth keeping verbatim from the audit:
|
||||||
|
|
@ -499,19 +692,32 @@ level is the one missing. Audited level-by-level:
|
||||||
build actions, menus, missing-tool guidance):
|
build actions, menus, missing-tool guidance):
|
||||||
|
|
||||||
- files ✓ since #162 / #165 (`C-x C-f` opens a path, `C-x d` browses;
|
- files ✓ since #162 / #165 (`C-x C-f` opens a path, `C-x d` browses;
|
||||||
neither is advertised anywhere but the keymap) · buffers ✓ (`C-x
|
**both are advertised** — `C-x C-f` directly in the welcome's key
|
||||||
b`, `*buffer-list*`) · search ✓ (`C-s`/`C-r`/`C-M-s`; project.search
|
table, `C-x d` transitively through `M-x help` →
|
||||||
is M-x-only) · diagnostics ✓ once a server runs · terminal ✓ but
|
`help.list-keybindings`) · buffers ✓ (`C-x b`, `*buffer-list*`) ·
|
||||||
M-x-only · build ✓ but M-x-only with empty prompt · menus △
|
search ✓ (`C-s`/`C-r`/`C-M-s`; project.search is M-x-only) ·
|
||||||
|
diagnostics ✓ once a server runs · terminal ✓ **and advertised**
|
||||||
|
(`C-c t`, in the welcome) · build ✓ **and advertised** (`C-c c`, in
|
||||||
|
the welcome; the prompt is **prefilled from the detected project
|
||||||
|
kind**, not empty) · menus △
|
||||||
(right-click only, 11 items) · missing-tool guidance ✗ (§1.2).
|
(right-click only, 11 items) · missing-tool guidance ✗ (§1.2).
|
||||||
|
|
||||||
|
*Three claims in this bullet were false and are corrected together:
|
||||||
|
"neither is advertised anywhere but the keymap", "terminal ✓ but
|
||||||
|
M-x-only", and "build ✓ but M-x-only with empty prompt". All three
|
||||||
|
were answered by the welcome buffer and by `C-c t`/`C-c c` — the very
|
||||||
|
fixes the paragraph after §2's audit quote already records as
|
||||||
|
landed. They survived because a level-by-level inventory was never
|
||||||
|
re-run against them.*
|
||||||
|
|
||||||
**Intermediate** (should discover: palette, keybinding search, workspace
|
**Intermediate** (should discover: palette, keybinding search, workspace
|
||||||
settings, profiles, package management, task definitions,
|
settings, profiles, package management, task definitions,
|
||||||
frontend/language settings):
|
frontend/language settings):
|
||||||
|
|
||||||
- palette △ (`M-x` fuzzy over bare names — the rows are still bare, §5) ·
|
- palette △ (`M-x` fuzzy over bare names — the rows are still bare, §5) ·
|
||||||
keybinding search ✓ (`help.list-keybindings`, `help.where-is`,
|
keybinding search ✓ (`help.list-keybindings`, `help.where-is`,
|
||||||
`help.describe-key`; still no key to reach them) · workspace settings
|
`help.describe-key`; **advertised through the welcome's `M-x help`
|
||||||
|
route, but no DIRECT binding reaches them**) · workspace settings
|
||||||
✗ (no workspace scope, §11) · profiles ✗ (§12) · package management ✗
|
✗ (no workspace scope, §11) · profiles ✗ (§12) · package management ✗
|
||||||
in-session (§13) · task definitions ✗ · frontend customization △
|
in-session (§13) · task definitions ✗ · frontend customization △
|
||||||
(themes, `pmacs.gpu.set_font`, statusline providers — all Lua-only) ·
|
(themes, `pmacs.gpu.set_font`, statusline providers — all Lua-only) ·
|
||||||
|
|
@ -652,10 +858,11 @@ the sharpest instance of §1.1.**
|
||||||
function passed as `CompletionSource::Custom`.
|
function passed as `CompletionSource::Custom`.
|
||||||
**What remains missing here:** a discovery surface for **packages**
|
**What remains missing here:** a discovery surface for **packages**
|
||||||
(§13) — `*workers*` already has one, reachable by
|
(§13) — `*workers*` already has one, reachable by
|
||||||
`M-x editor.list-workers`, though §9's ownership model and activity
|
`M-x editor.list-workers`, and §9's activity indicator now exists
|
||||||
indicator are still absent — and **no key reaches any of this**: the
|
(#232) while its **ownership model remains absent** — and **no DIRECT binding reaches any of
|
||||||
family is `M-x`-only by design until the help-prefix decision is taken
|
this**, though the family is advertised: the welcome names `M-x help`,
|
||||||
(see the prefix bullet below).
|
which indexes it. The family stays unbound by design until the
|
||||||
|
help-prefix decision is taken (see the prefix bullet below).
|
||||||
*Completion is assistance, not validation:* `resolve_accepted_value`
|
*Completion is assistance, not validation:* `resolve_accepted_value`
|
||||||
returns the literal typed text when no candidate is selected, so a
|
returns the literal typed text when no candidate is selected, so a
|
||||||
typo still reaches the handler; refusing a non-candidate is unbuilt
|
typo still reaches the handler; refusing a non-candidate is unbuilt
|
||||||
|
|
@ -977,14 +1184,23 @@ so non-pool work (LSP requests, MCP) appears uniformly; one shared
|
||||||
`*workers*` view (`src/workers_buffer.rs`, opened by `M-x
|
`*workers*` view (`src/workers_buffer.rs`, opened by `M-x
|
||||||
editor.list-workers`, auto-refreshing, `C-c C-k` cancel-at-point).
|
editor.list-workers`, auto-refreshing, `C-c C-k` cancel-at-point).
|
||||||
|
|
||||||
**The identity layer is absent:**
|
**The identity layer is PARTLY built — purpose landed, ownership did not:**
|
||||||
|
|
||||||
- `PendingJob` (`src/async_runtime.rs:365-392`) carries `{cancel,
|
- `PendingJob` (`src/async_runtime.rs`) carries `{cancel, state,
|
||||||
state, supersede_key, stream_buffer, max_batch, kind,
|
supersede_key, stream_buffer, max_batch, kind, dispatched_at,
|
||||||
dispatched_at}`. **No owner. No purpose string. No
|
**purpose**}`. Since #232 the **purpose is required** — `JobSpec` has
|
||||||
workspace/buffer association. No parent.** The one buffer link that
|
no `Default`, so a dispatcher that supplies none **does not compile**
|
||||||
exists (parse job → buffer) lives in a `SyntaxCoordinator` side map,
|
(`src/async_runtime.rs:429`), which makes presence a type obligation
|
||||||
invisible to the workers view.
|
rather than a convention. **Still absent: an accountable owner, any
|
||||||
|
workspace/buffer association, and any parent.** The one buffer link
|
||||||
|
that exists (parse job → buffer) lives in a `SyntaxCoordinator` side
|
||||||
|
map, invisible to the workers view.
|
||||||
|
|
||||||
|
The distinction that survives #232: a purpose says what a job is
|
||||||
|
**doing**; ownership says **who asked**. Populated from static
|
||||||
|
per-subsystem constants, an `owner` field would have been an *origin*
|
||||||
|
and would have misattributed third-party work to a builtin — which is
|
||||||
|
why #232 shipped none rather than a plausible one.
|
||||||
- `JobKind` is a **closed 12-variant enum** (Sleep, ComputeSum, EmitN,
|
- `JobKind` is a **closed 12-variant enum** (Sleep, ComputeSum, EmitN,
|
||||||
Grep, Parse, FsReadDir, FsStat, FsRename, FsChmod, FsRemove,
|
Grep, Parse, FsReadDir, FsStat, FsRename, FsChmod, FsRemove,
|
||||||
McpRequest, LspRequest). `pmacs.workers.register` funnels Lua jobs
|
McpRequest, LspRequest). `pmacs.workers.register` funnels Lua jobs
|
||||||
|
|
@ -1002,26 +1218,43 @@ editor.list-workers`, auto-refreshing, `C-c C-k` cancel-at-point).
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| Async jobs | `*workers*` | processes, servers, terminals |
|
| Async jobs | `*workers*` | processes, servers, terminals |
|
||||||
| OS processes | `pmacs.process.list` (no buffer view exists) | **filters to `LineOriented` only — terminal PTYs are invisible**; `spawn_terminal` bypasses the public path entirely |
|
| OS processes | `pmacs.process.list` (no buffer view exists) | **filters to `LineOriented` only — terminal PTYs are invisible**; `spawn_terminal` bypasses the public path entirely |
|
||||||
| LSP servers | `*lsp*` status text | **no builtin command opens it**; LSP sets `RestartPolicy::Never` on the supervisor and runs its own restart logic |
|
| LSP servers | `*lsp*` status text | opened by **`M-x lsp.status`** (`builtin/runtime/lsp.lua:3533`), but by no binding; LSP sets `RestartPolicy::Never` on the supervisor and runs its own restart logic |
|
||||||
| Terminals | private id set drained after the supervisor tick | user-visible in none of the above |
|
| Terminals | private id set drained after the supervisor tick | user-visible in none of the above |
|
||||||
|
|
||||||
A terminal PTY appears in **no** user-visible activity view. An LSP
|
A terminal PTY appears in **no** user-visible activity view. An LSP
|
||||||
server appears in `*lsp*` (unreachable) and `list()`; its requests
|
server appears in `*lsp*` (unreachable) and `list()`; its requests
|
||||||
appear in `*workers*`; nothing joins them.
|
appear in `*workers*`; nothing joins them.
|
||||||
- **No progress indicator exists anywhere** — no statusline spinner,
|
- **A progress indicator now exists (#232, 2026-08-09)** — a statusline
|
||||||
no busy count (grep for progress/spinner/busy in `src/statusline.rs`
|
provider rendering the in-flight count and the **oldest** job's
|
||||||
is empty). "Visible asynchronous work" (§3) is currently false unless
|
purpose, absent when idle, gated by `ui.activity-indicator`. So
|
||||||
the user knows to run `M-x editor.list-workers`.
|
"visible asynchronous work" (§3) is **no longer false**: activity is
|
||||||
- `ProcessSpec.label` is the nearest thing to attribution: caller-
|
visible without knowing to run `M-x editor.list-workers`, though **no
|
||||||
supplied, unvalidated convention (`lsp:{name}`, terminal buffer
|
binding opens that view** — `C-c C-k` is bound inside it, which cannot
|
||||||
name).
|
help anyone find it.
|
||||||
|
|
||||||
|
**The indicator's first act was to expose three months of invisible
|
||||||
|
LSP file-watcher polling (issue #233)** — §9's own argument,
|
||||||
|
demonstrated.
|
||||||
|
- **`purpose` is now required** on every job and process (#232), through
|
||||||
|
a single allocation funnel with no `Default`, so the compiler proves
|
||||||
|
every caller supplies one. That is a real per-job description where
|
||||||
|
there was none.
|
||||||
|
- **Attribution is still missing, and that is what §9 grades.** A
|
||||||
|
purpose says what a job is *doing*; it does not say **who asked** —
|
||||||
|
no owner, no parent, no package. `ProcessSpec.label` remains a
|
||||||
|
caller-supplied, unvalidated convention (`lsp:{name}`, terminal
|
||||||
|
buffer name). **This section's grade is left untouched pending a
|
||||||
|
re-audit**: the mechanism-without-identity finding is *partly*
|
||||||
|
answered, and moving a grade is an audit act, not a documentation
|
||||||
|
correction.
|
||||||
|
|
||||||
The audit's conclusion, worth preserving verbatim: *because identity is
|
The audit's conclusion, worth preserving verbatim: *because identity is
|
||||||
missing, scoped cancellation has nothing to scope over and a unified
|
missing, scoped cancellation has nothing to scope over and a unified
|
||||||
activity view has nothing to group by — the four views exist precisely
|
activity view has nothing to group by — the four views exist precisely
|
||||||
because there is no common key to merge them on.* Owner/purpose/parent
|
because there is no common key to merge them on.* **Purpose** landed with #232;
|
||||||
fields on the job and process specs are the prerequisite; the unified
|
**owner and parent** fields on the job and process specs remain the
|
||||||
view and the ownership tree fall out of them.
|
prerequisite, and the unified view and the ownership tree fall out of
|
||||||
|
them.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -1490,10 +1723,73 @@ facto privileged implementation.
|
||||||
|
|
||||||
### Ground truth
|
### Ground truth
|
||||||
|
|
||||||
**Grade: strong — the healthiest concern in this document, and most of
|
**This concern carries TWO subgrades, because it asks two different
|
||||||
its asks are already practiced.**
|
questions and one answer was hiding the other.**
|
||||||
|
|
||||||
- Versioned protocol schema `SUPPORTED=[6..=21]` with deliberate
|
| subgrade | grade | what it measures |
|
||||||
|
|---|---|---|
|
||||||
|
| **Architectural** | **Strong** | the protocol, its versioning discipline, capability negotiation, degradation, and the absence of a privileged frontend |
|
||||||
|
| **Product** | **Weak** | whether the graphical frontend is a workbench a user would choose — graded against §2b's per-frontend journey table and §3.1's blocker list |
|
||||||
|
|
||||||
|
**Why the split exists.** "Productize the semantic frontend" was graded
|
||||||
|
`Strong` on architectural evidence alone, and that grade was true and
|
||||||
|
was answering the wrong question: the protocol is excellent *and* the
|
||||||
|
GUI is not yet a daily driver. A single cell could not hold both, so the
|
||||||
|
product half was invisible — which is how a frontend the reporter would
|
||||||
|
not choose sat inside the healthiest concern in this document.
|
||||||
|
|
||||||
|
**Product subgrade criteria** (each falsifiable, none aspirational):
|
||||||
|
|
||||||
|
1. **GPU ≥ local TUI at every journey step** under §2b. **Currently
|
||||||
|
FAILS at two known steps** — 5 (no IME) and 12 (restore is a
|
||||||
|
structural no-op) — with the exact set pending verification of §2b's
|
||||||
|
inferred cells.
|
||||||
|
2. **The daily-driver blocker list is empty.** **Currently nine open**
|
||||||
|
(`docs/gui-arc-framing.md` §3.1): Escape quits, IME absent,
|
||||||
|
`translate_key` holes, sub-line/horizontal scroll, no DPI, folding
|
||||||
|
dead on the GPU, no session restore, no reconnect, the one-window
|
||||||
|
ceiling.
|
||||||
|
3. **Every surviving divergence is declared**, not accidental.
|
||||||
|
**Not yet assessed** — §3.2's register is seeded empty, so this
|
||||||
|
criterion has no finding either way and cannot lift or lower the
|
||||||
|
grade.
|
||||||
|
|
||||||
|
**How the grade is derived, so it is a grading rule and not an
|
||||||
|
adjective.** The three criteria above are *closure* conditions; on their
|
||||||
|
own they distinguish only "closed" from "not closed", which is not a
|
||||||
|
grade. The product subgrade reads:
|
||||||
|
|
||||||
|
| grade | condition |
|
||||||
|
|---|---|
|
||||||
|
| **Strong** | all three criteria pass |
|
||||||
|
| **Partial** | usable, criterion 1 **passes**, and not all three criteria pass |
|
||||||
|
| **Weak** | usable and criterion 1 **fails** at one or more steps |
|
||||||
|
| **Missing** | no usable graphical frontend |
|
||||||
|
|
||||||
|
**The rule is TOTAL by construction** — every usable state is covered by
|
||||||
|
exactly one of `Partial`/`Weak`/`Strong`, since criterion 1 either
|
||||||
|
passes or fails and `Strong` is the all-pass case. An earlier draft
|
||||||
|
required a non-empty blocker list for `Weak` and named blockers in
|
||||||
|
`Partial`, which left a real state ungraded: criterion 1 passing with an
|
||||||
|
empty blocker list but criterion 3 failing or unassessed. Criterion 1 is
|
||||||
|
the discriminator because it is the only one that measures the GPU
|
||||||
|
*against the TUI*; the others measure it against its own goals.
|
||||||
|
|
||||||
|
**Current: Weak** — criterion 1 fails at two known steps. (Nine
|
||||||
|
blockers are open including the one-window ceiling; under the total rule
|
||||||
|
they no longer *derive* the grade, but they are why it is not close to
|
||||||
|
`Strong`.) The distinction that
|
||||||
|
matters is between `Weak` and `Partial`: the GPU is not merely
|
||||||
|
*unfinished*, it is **behind a frontend that ships in the same binary**,
|
||||||
|
and that is what the ceiling and the two failing steps say.
|
||||||
|
|
||||||
|
**What moves it.** The GUI arc, and nothing else — it exists to satisfy
|
||||||
|
exactly these three. The product subgrade is what the arc closes
|
||||||
|
against, and it should be re-graded when the arc closes, not before.
|
||||||
|
|
||||||
|
**Architectural ground truth follows; it is unchanged and still strong.**
|
||||||
|
|
||||||
|
- Versioned protocol schema `SUPPORTED=[6..=23]` with deliberate
|
||||||
encoding-breaking bumps, both-frontends support required per bump,
|
encoding-breaking bumps, both-frontends support required per bump,
|
||||||
and byte-pin discipline for appended variants (handoff §4). The v21
|
and byte-pin discipline for appended variants (handoff §4). The v21
|
||||||
bottom-panel family landed with Stage 2B-1 (#184) and is **live in
|
bottom-panel family landed with Stage 2B-1 (#184) and is **live in
|
||||||
|
|
@ -1615,7 +1911,10 @@ across the whole command family, not an oversight.
|
||||||
|
|
||||||
Note the dependency: five of the ten onboarding steps above currently
|
Note the dependency: five of the ten onboarding steps above currently
|
||||||
lead somewhere broken or invisible (find a file — the mechanism is fixed
|
lead somewhere broken or invisible (find a file — the mechanism is fixed
|
||||||
since #162/#165 but is advertised nowhere except the keymap; inspect a
|
since #162/#165 and IS advertised — `C-x C-f` sits in the welcome's key
|
||||||
|
table directly, and every bound command is reachable through `M-x help`;
|
||||||
|
this clause read "advertised nowhere except the keymap" and was false at
|
||||||
|
both levels, found while correcting step 7's row above; inspect a
|
||||||
diagnostic — silent-failure risk; view workers — undiscoverable;
|
diagnostic — silent-failure risk; view workers — undiscoverable;
|
||||||
setting provenance — unanswerable). Onboarding is correctly sequenced
|
setting provenance — unanswerable). Onboarding is correctly sequenced
|
||||||
*after* the P1/P4 fixes, but the cheap floor — a welcome buffer in
|
*after* the P1/P4 fixes, but the cheap floor — a welcome buffer in
|
||||||
|
|
@ -1701,6 +2000,47 @@ documented no-op under a daemon. Journey Stage 1 closing is what moves
|
||||||
this priority's own work to done — what is left is other priorities'
|
this priority's own work to done — what is left is other priorities'
|
||||||
work showing up in the journey.
|
work showing up in the journey.
|
||||||
|
|
||||||
|
### The GUI arc — placement (Q#GA5)
|
||||||
|
|
||||||
|
**Half A slots after Priority 1**, whose own work is complete (§20 P1),
|
||||||
|
which is why this block sits here rather than lower down — in a
|
||||||
|
*Recommended Priority Order*, position IS the schedule, and an earlier
|
||||||
|
draft placed it after Priority 5 while the prose said P1.
|
||||||
|
|
||||||
|
**How it interleaves with P2–P5.** Half A is independent of them and
|
||||||
|
runs alongside: it adds no workspace object, no extension-ownership
|
||||||
|
model, no discovery surface. The interaction is at **P2 only**, through
|
||||||
|
the gate below. P3, P4 and P5 neither block it nor are blocked by it,
|
||||||
|
and P5's workbench-primitive adoption is *helped* by it — Stage 9's
|
||||||
|
sidebar is a tree-primitive consumer.
|
||||||
|
|
||||||
|
It is the product half of §16, and it is what the §16 product subgrade
|
||||||
|
is graded against.
|
||||||
|
|
||||||
|
**Reaching Stage 4b is a P2 START GATE.** Stages 4b (session save and
|
||||||
|
restore) and 9 (project/files sidebar) are **workspace-owned**: a
|
||||||
|
session and a sidebar root are both P2's objects, and inventing local
|
||||||
|
conventions for them is precisely the "fifth independent root
|
||||||
|
convention" §7 warns against. So when the arc reaches Stage 4b, **P2
|
||||||
|
starts**, and **no later GUI stage begins** until P2 has an approved
|
||||||
|
framing and an opened lane. Non-gated GUI work may interleave freely
|
||||||
|
after that while the object lands.
|
||||||
|
|
||||||
|
**The gate is on STARTING P2, not on finishing it** — so the arc is
|
||||||
|
never blocked on work nobody has begun, and it cannot outrun the model
|
||||||
|
it depends on. Without the gate, every non-gated stage could finish
|
||||||
|
first and leave P2 as a terminal closure blocker.
|
||||||
|
|
||||||
|
**Arc naming (Q#GA4).** This arc is **"the GUI arc"**, deliberately a
|
||||||
|
name and not a number. The roadmap's **"Arc 8 — GPU structural parity"
|
||||||
|
label is RETIRED** here; its scope is this arc's Half B. **"Arc 8" now
|
||||||
|
unambiguously means the Lean 4 arc** (`docs/agent-handoff.md` §1a,
|
||||||
|
stages 1–4b landed), which is the numbering that stays. Landed framing
|
||||||
|
documents that say "Arc 8 adjacent" of viewport facts or splits
|
||||||
|
(`editing-conveniences-framing.md`, `bottom-panel-framing.md`) keep
|
||||||
|
their historical text — they are dated records, and this is the
|
||||||
|
authoritative disambiguation for anything written from here on.
|
||||||
|
|
||||||
### Priority 2: Make workspace and location explicit
|
### Priority 2: Make workspace and location explicit
|
||||||
|
|
||||||
Otherwise project, LSP, remote, task, and persistence accumulate
|
Otherwise project, LSP, remote, task, and persistence accumulate
|
||||||
|
|
@ -1803,8 +2143,10 @@ implementation — this list is direction, not commitment):
|
||||||
handle + derived `dispatch_idle`, then migrate shadows one per PR.
|
handle + derived `dispatch_idle`, then migrate shadows one per PR.
|
||||||
4. **Extension ownership** (P3): `hook.remove`, owner-carrying
|
4. **Extension ownership** (P3): `hook.remove`, owner-carrying
|
||||||
registrations, attribution-by-default.
|
registrations, attribution-by-default.
|
||||||
5. **Worker identity** (§9): owner/purpose/parent on jobs and
|
5. **Worker identity** (§9): **owner and parent** on jobs and
|
||||||
processes, join the four planes, statusline activity indicator.
|
processes, and joining the four planes. *Purpose and the statusline
|
||||||
|
activity indicator shipped in #232 (Stage 1); what remains is the
|
||||||
|
ownership half.*
|
||||||
6. **Workspace entity** (P2): the object, then location values.
|
6. **Workspace entity** (P2): the object, then location values.
|
||||||
7. **Config provenance + adoption** (P6).
|
7. **Config provenance + adoption** (P6).
|
||||||
8. **Package lifecycle** (P7, after 4).
|
8. **Package lifecycle** (P7, after 4).
|
||||||
|
|
|
||||||
|
|
@ -136,7 +136,8 @@ lesson, §1 for the two framings).
|
||||||
are identical on every machine. Remote names are otherwise
|
are identical on every machine. Remote names are otherwise
|
||||||
machine-local: `origin` may name this canonical URL, a release mirror,
|
machine-local: `origin` may name this canonical URL, a release mirror,
|
||||||
or something else, and therefore has no authority by name alone.
|
or something else, and therefore has no authority by name alone.
|
||||||
- Canonical base at this snapshot: **`githubsucks/main` @ `122b8e8`** —
|
- Canonical base at this snapshot: **`githubsucks/main` @ `e67ad07`** —
|
||||||
|
the file-watcher arc absorption, atop `122b8e8`
|
||||||
the file-watcher D3 **#235** (closes issue #233), atop `b867f64`
|
the file-watcher D3 **#235** (closes issue #233), atop `b867f64`
|
||||||
git integration Stage 1 **#227**, atop `ae84d58` the LSP file-watcher
|
git integration Stage 1 **#227**, atop `ae84d58` the LSP file-watcher
|
||||||
fix **#234**, atop `0e4c58d` destination capture **#231**, `3cc1b85`
|
fix **#234**, atop `0e4c58d` destination capture **#231**, `3cc1b85`
|
||||||
|
|
@ -160,9 +161,10 @@ lesson, §1 for the two framings).
|
||||||
#206, Journey Stage 1b-3 #205, 1b-2 #204 and 1b-1 #203, the
|
#206, Journey Stage 1b-3 #205, 1b-2 #204 and 1b-1 #203, the
|
||||||
reap-ledger diagnostic #202, the isolation framing #201, the
|
reap-ledger diagnostic #202, the isolation framing #201, the
|
||||||
process-signal diagnostic #200 and the ledger absorption #199.
|
process-signal diagnostic #200 and the ledger absorption #199.
|
||||||
**Protocol schema support is `v6..=v22`; the production server-first
|
**Protocol schema support is `v6..=v23`; the production server-first
|
||||||
`Hello` still advertises v20** — two different facts, and #184 landed
|
`Hello` still advertises v20** — two different facts, and #184 landed
|
||||||
only the first. The upper bound moved to **v22 at #221**, which added
|
only the first. The upper bound moved to **v23 at #228**
|
||||||
|
(`MinibufferPromptRows`), after **v22 at #221**, which added
|
||||||
`InstanceMessage::LineWrapFacts`; `ADVERTISED_PROTOCOL_VERSION` did
|
`InstanceMessage::LineWrapFacts`; `ADVERTISED_PROTOCOL_VERSION` did
|
||||||
not move and must not be edited to chase it. Verified against
|
not move and must not be edited to chase it. Verified against
|
||||||
`pmacs-protocol/src/message.rs`, not carried forward: this line said
|
`pmacs-protocol/src/message.rs`, not carried forward: this line said
|
||||||
|
|
@ -171,7 +173,7 @@ lesson, §1 for the two framings).
|
||||||
where they describe a stage as it landed** — only this
|
where they describe a stage as it landed** — only this
|
||||||
current-state paragraph tracks the live range.
|
current-state paragraph tracks the live range.
|
||||||
**The recovery floor advances with the base**, so the check below
|
**The recovery floor advances with the base**, so the check below
|
||||||
now requires `9a26ac8` or newer; a tree at `db1bbe9` no longer
|
now requires **`e67ad07`** or newer; a tree at `db1bbe9` no longer
|
||||||
passes — it would lack the entire QoL arc, which this file and the
|
passes — it would lack the entire QoL arc, which this file and the
|
||||||
handoff both describe as complete. That is deliberate — a check accepting an older commit than
|
handoff both describe as complete. That is deliberate — a check accepting an older commit than
|
||||||
the declared base passes on a tree the rest of this file does not
|
the declared base passes on a tree the rest of this file does not
|
||||||
|
|
@ -211,16 +213,21 @@ git worktree list
|
||||||
git status --short --branch
|
git status --short --branch
|
||||||
```
|
```
|
||||||
|
|
||||||
The `git log` command must expose `9a26ac8` — the base named above — or a
|
The `git log` command must expose `e67ad07` — the base named above — or a
|
||||||
newer intentional main. Keep this threshold and the canonical-base line in
|
newer intentional main. Keep this threshold and the canonical-base line in
|
||||||
step: a recovery check that accepts an older commit than the base it
|
step: a recovery check that accepts an older commit than the base it
|
||||||
declares canonical will pass on a tree the rest of this file does not
|
declares canonical will pass on a tree the rest of this file does not
|
||||||
describe.
|
describe.
|
||||||
If it does not, stop and repair the remote/fetch configuration.
|
If it does not, stop and repair the remote/fetch configuration.
|
||||||
|
|
||||||
**This path was exercised, not asserted, at this snapshot** — re-run
|
**LAST EXERCISED AT `9a26ac8`, 2026-08-08** — re-run from an empty
|
||||||
from an empty directory on 2026-08-08 when the base advanced to
|
directory rather than having its SHA swapped. **The advance to
|
||||||
`9a26ac8`, rather than having its SHA swapped. That distinction is the
|
`e67ad07` (2026-08-11) has NOT been re-exercised**, and by this
|
||||||
|
paragraph's own argument that is exactly when the exercise matters, so
|
||||||
|
it is recorded as an outstanding obligation rather than left to read as
|
||||||
|
though it had been done. The floor above and the check below are in
|
||||||
|
step at `e67ad07`; what is unverified is the *procedure* against that
|
||||||
|
base, not the SHA. That distinction is the
|
||||||
whole point of this paragraph: advancing the base is exactly when the
|
whole point of this paragraph: advancing the base is exactly when the
|
||||||
recovery commands are most likely to have rotted, and a swapped SHA
|
recovery commands are most likely to have rotted, and a swapped SHA
|
||||||
reads identically to a verified one. `git clone` the canonical URL, add
|
reads identically to a verified one. `git clone` the canonical URL, add
|
||||||
|
|
@ -243,60 +250,67 @@ hazard in a shape that looks committed. **A documented error message
|
||||||
that never appears is worse than no documentation**, because the reader
|
that never appears is worse than no documentation**, because the reader
|
||||||
waits for a signal that is not coming.
|
waits for a signal that is not coming.
|
||||||
|
|
||||||
## `scripts/gate` — PR #225 OPEN (build tooling)
|
## The GUI arc — Stage 0 branch OPEN, absorption COMPLETE, ready for PR
|
||||||
|
|
||||||
**PR #225** — https://github.com/levineuwirth/pmacs/pull/225. Written
|
**Written at the branch's first commit**, with the framing, which is
|
||||||
**after** the PR existed, again, and again because review asked. Two
|
what this arc's own §5 requires of every PR in it. The standing
|
||||||
lanes in a row have now been added late; the correction from #171 and
|
correction from #171 and #215 was missed at #224 and #225; this lane
|
||||||
#215 is not sticking, and recording that is more useful than a
|
exists to stop the streak rather than to note it again.
|
||||||
back-dated block that pretends it did.
|
|
||||||
|
|
||||||
- **Branch `gate-script`**, base `githubsucks/main` @ `b833b13` (the
|
**The park is discharged**: #227 merged as `b867f64`, and the
|
||||||
#224 merge). **`githubsucks/gate-script` is the authoritative tip** —
|
file-watcher arc (#233) closed via #234 and #235. Rebased onto
|
||||||
the ref, not a SHA. Recover with
|
`e67ad07`; the single framing commit replayed with no conflict.
|
||||||
`git fetch githubsucks && git checkout gate-script`.
|
|
||||||
- **Framing `docs/gate-script-framing.md`**, approved at revision 4
|
|
||||||
after four review rounds; revision 5 records two safety defects found
|
|
||||||
against the implementation, not the design.
|
|
||||||
- **Scope:** `scripts/gate` (per-worktree `CARGO_TARGET_DIR`, five
|
|
||||||
ambient roots, durable per-gate logs, the fixed gate suite),
|
|
||||||
`tests/gate_script_acceptance.rs`, and a handoff §3 rewrite pointing
|
|
||||||
at the script while §3 keeps policy and acceptance-suite selection.
|
|
||||||
No `src/`, no crate, no manifest, no protocol.
|
|
||||||
- **Verification:** 15 acceptance tests over the no-gates paths, each
|
|
||||||
isolated by `PMACS_GATE_TARGET_ROOT`. Mutation-tested; the two prune
|
|
||||||
guards are redundant by design and only fail the test when **both**
|
|
||||||
are removed, which is recorded in the test itself. Observed real runs
|
|
||||||
confirm failed-gate naming, log paths, ambient creation and reaping,
|
|
||||||
and distinct log directories per run.
|
|
||||||
|
|
||||||
**GATE STATUS — R8 RESOLVED.** This lane was blocked because
|
**Stage 0's absorption scope was RE-DERIVED from the tree rather than
|
||||||
`scripts/gate` exited 1 on a clean tree: **R8** failed `m4_acceptance`
|
taken from this lane's own earlier text, and the earlier text was
|
||||||
and therefore the sweep. That was never a footnote — #225 is the lane
|
wrong in the optimistic direction.** `add0ba1` absorbed **#227 and
|
||||||
that makes the gate suite authoritative, and a tool shipping with its
|
#234 only**; a reading of its −532-line diff as "half of Stage 0's
|
||||||
own gate red teaches the opposite of what it exists to teach.
|
absorption" was too generous. Five stale lanes remain below, and two
|
||||||
|
`COHERENCE.md` corrections had not been made at all. What is done here
|
||||||
|
is listed at the commit that does it, not promised here.
|
||||||
|
|
||||||
**R8 was fixed and retired in #226** (`dcb852e`), which bounded the LSP
|
- **Branch `gui-arc-stage0`**, base `githubsucks/main` @ `e67ad07` (the
|
||||||
fixture's project detection. This branch is rebased onto it.
|
file-watcher arc absorption) after the 2026-08-11 rebase; it was
|
||||||
|
branched at `0e4c58d`. **`githubsucks/gui-arc-stage0` is the
|
||||||
**RE-GATED 2026-08-09: `scripts/gate` exits 0.** All nine gates green in
|
authoritative tip** — the ref, not a SHA. Recover with
|
||||||
one command — fmt, clippy, `--lib`, `--lib --features crdt`, both named
|
`git fetch githubsucks && git checkout gui-arc-stage0`.
|
||||||
acceptance suites, `m4_acceptance`, `-p pmacs-gpu`, and the full
|
- **Framing `docs/gui-arc-framing.md`, revision 3, APPROVED
|
||||||
workspace sweep. That is #225's own acceptance criterion, and it is the
|
2026-08-10** after two review rounds (two blocking findings each
|
||||||
first time the tool has passed the suite it exists to run.
|
round, closed). It is **also the framing for Stage 0 itself**, which
|
||||||
|
is docs-only; Stages 1–10 each require their own framing before their
|
||||||
**Rebase resolution, per the standing rule that #226's R8 documentation
|
branch.
|
||||||
wins.** Three conflicts, all in R8 text this branch had written while
|
- **The park is over and the work is DONE.** The branch was parked at
|
||||||
the row was still an open investigation: two in
|
its first commit until #227 merged, because #227 was 72 `main` commits
|
||||||
`docs/ci-red-signatures.md` (both resolved to #226's retired row, this
|
behind and touched the three files Stage 0's absorption rewrites.
|
||||||
branch's pre-fix copy dropped), and the framing-doc pair
|
#227 merged (`b867f64`), #233's arc closed (#234, #235), this branch
|
||||||
(`e71e1bd` added it, `7cfba73` removed it — both **skipped**, since they
|
rebased onto `e67ad07`, and the absorption ran. **Sixteen commits
|
||||||
are net-zero here and `main` owns the file authoritatively; replaying
|
above the base as of `30e2209`; ready for its PR.** *(Anchored to a
|
||||||
the second would have deleted `main`'s copy). Two now-stale lanes were
|
SHA deliberately: a bare running count goes stale on the next commit,
|
||||||
also removed: this branch's "R8 NEEDS A LANE" investigation block, and
|
which is the staleness class this lane exists to retire.)*
|
||||||
#226's own lane, which Rule 4 retires now that it has merged — its
|
- **What landed (docs only, no `src/`):** the absorption pass
|
||||||
durable facts are in the retired registry row and the handoff §6
|
enumerated in the framing's §5 — five stale lanes, the
|
||||||
census.
|
authority/recovery anchor, `COHERENCE.md`'s `v6..=v21` → `v6..=v23`,
|
||||||
|
the U4 correction and the U9 rewrite in `docs/ci-red-signatures.md`,
|
||||||
|
the stale right-click backlog line, and journey step 11's verdict
|
||||||
|
(falsified by #232) — then the per-frontend journey table, the §16
|
||||||
|
product subgrade the scorecard will point at, §20 placement, the
|
||||||
|
handoff cross-reference, and the "Arc 8" retirement.
|
||||||
|
- **Two absorption items that are NOT simple deletions**, recorded here
|
||||||
|
because getting them wrong is silent: **#228's lane** must lose only
|
||||||
|
its PR-specific block, while the standing **Discovery lane (P4)** is
|
||||||
|
rewritten to "Stage 2 merged; later discovery work remains" —
|
||||||
|
predicate evaluation, command metadata, help unification and the
|
||||||
|
prefix decision are all still open. And **`v6..=v23` must not sweep
|
||||||
|
away the same row's "production attach remains v20"**, which is
|
||||||
|
correct (`ADVERTISED_PROTOCOL_VERSION` is 20).
|
||||||
|
- **Verification (2026-08-11, committed branch tip): FULL PRE-PR SUITE
|
||||||
|
PASS.** Stage 0 changes no code, so there is no touched acceptance
|
||||||
|
suite; that does **not** exempt a docs-only PR from the standing gates
|
||||||
|
in `AGENTS.md`. Ran `cargo fmt --check`; `cargo clippy --workspace
|
||||||
|
--all-targets -- -D warnings` as its own step; `cargo test --lib`;
|
||||||
|
`cargo test --lib --features crdt`; `cargo test --test m4_acceptance --
|
||||||
|
--skip basedpyright`; `PMACS_REQUIRE_GPU=1 cargo test -p pmacs-gpu`;
|
||||||
|
and `git diff --check`.
|
||||||
|
|
||||||
## Git integration — STAGE 1 MERGED as #227; Stage 2 must be scheduled alone
|
## Git integration — STAGE 1 MERGED as #227; Stage 2 must be scheduled alone
|
||||||
|
|
||||||
|
|
@ -332,401 +346,6 @@ serialization rule on wire changes. No branch, no framing yet.
|
||||||
fails the day that job gains a macOS leg, the same way `g6_2` did
|
fails the day that job gains a macOS leg, the same way `g6_2` did
|
||||||
(handoff §1: macOS cannot hold a non-UTF-8 filename).
|
(handoff §1: macOS cannot hold a non-UTF-8 filename).
|
||||||
|
|
||||||
## Destination capture (Q#JR14 generalization) — PR #231 OPEN, revision 9, cleared to merge
|
|
||||||
|
|
||||||
**PR #231** — https://github.com/levineuwirth/pmacs/pull/231. #227
|
|
||||||
blocks on this lane.
|
|
||||||
|
|
||||||
The mechanism landed at `0efc8c0`; review found a correctness blocker;
|
|
||||||
`ca72461` implemented **revision 7**, which review then **also**
|
|
||||||
rejected; `469d5c8` replaced it with **revision 8** and its §3
|
|
||||||
enumeration is **performed and recorded in the framing**; review then
|
|
||||||
found a hole in revision 8's guard **scope** and the commit below closes
|
|
||||||
it as **revision 9**.
|
|
||||||
|
|
||||||
**The macOS red that blocked this lane, and how it was cleared.** Both
|
|
||||||
CI attempts at `4654b94` failed `a_pty_resize_blanks_the_host_before_repainting`
|
|
||||||
on `Test (macos-latest / luajit)`. A control experiment was run at the
|
|
||||||
exact base commit `0190102`: **five valid observations, all green on
|
|
||||||
both macOS flavours**, against the branch's 0/2 — 1/C(7,2) = 4.8% under
|
|
||||||
an equal-rate model. That implicates the branch statistically. **The
|
|
||||||
diff exonerates it mechanically**: grepping this lane's entire `src/`
|
|
||||||
diff for `full_grid|resize|resync|Geometry|reconcile_panel_layout`
|
|
||||||
matches an **import line and nothing else**, and
|
|
||||||
`full_grid_resync_acceptance` (191 lines) has no panel, side-window,
|
|
||||||
dedication, display or directory surface at all. Merged on that reading,
|
|
||||||
with the equal-rate model itself in doubt — see the U4 row, and note a
|
|
||||||
sixth base attempt reddened on a *third, unrelated* macOS selector
|
|
||||||
(U8), which is what a background platform failure rate looks like.
|
|
||||||
|
|
||||||
**The original blocker:** the panel profile skipped checks 2–4 on the
|
|
||||||
claim that a panel result never touches a document window. **Panel
|
|
||||||
placement falls back to an ordinary document window** when the frontend
|
|
||||||
is not panel-capable or its side slot is dedicated, so a `"panel"`
|
|
||||||
commit could replace a **newer** document with every stale-intent guard
|
|
||||||
skipped. Reproduced in review.
|
|
||||||
|
|
||||||
**Four designs, two rejected outright and one corrected — the sequence
|
|
||||||
is the part worth not re-learning:**
|
|
||||||
|
|
||||||
1. **Revision 6 — predict at preflight.** Rejected: the `await` refusal
|
|
||||||
stops concurrent interleaving, not the body, which is arbitrary
|
|
||||||
synchronous Lua and can create the fallback itself.
|
|
||||||
2. **Revision 7 — enforce at the placement boundary.** Implemented at
|
|
||||||
`ca72461`, then rejected: `docs/agent-handoff.md:748` requires
|
|
||||||
`commit_to` to preflight **before** the callback, because
|
|
||||||
"validating at display time is four mutations too late". A body has
|
|
||||||
already created buffers, handles and paint by then, so a
|
|
||||||
placement-time refusal is a partial commit with an error return.
|
|
||||||
3. **Revision 8 — keep the preflight, REFUSE the scope-invalidating
|
|
||||||
mutation.** The shape the tree implements. Same as `Handle:await`
|
|
||||||
being refused inside a commit scope: the fallback never comes into
|
|
||||||
existence, and refusal stays mutation-free on `(false, reason)`.
|
|
||||||
4. **Revision 9 — make the refusal hold for the WHOLE body.** Not a new
|
|
||||||
shape; a correction to revision 8's scope. A nested `commit_to`
|
|
||||||
**replaced** the enclosing contract and restored it afterwards, so
|
|
||||||
an outer `"panel"` commit's restriction went out of force for the
|
|
||||||
inner body's extent: nested `"document"` commit → callback dedicates
|
|
||||||
the side slot, unrefused → outer commit resumes, falls back,
|
|
||||||
overwrites a newer document. Reproduced in review. Contracts now
|
|
||||||
**compose** — the core holds a stack, `commit_to` pushes and pops
|
|
||||||
rather than swapping, and the guard consults every contract in force,
|
|
||||||
so the strictest active restriction wins. Nesting itself is **not**
|
|
||||||
forbidden: only the mutation is refused, so a nested commit that
|
|
||||||
touches no dedication runs exactly as before. Detecting the
|
|
||||||
dedication when the outer commit resumed was not available — that is
|
|
||||||
a late refusal, which is what revision 7 was rejected for.
|
|
||||||
|
|
||||||
**WHAT REVISION 9 DID *NOT* INVALIDATE — read this before re-opening the
|
|
||||||
enumeration.** The write-site enumeration below survived intact: every
|
|
||||||
site is real, every one is still guarded, and review of the nesting
|
|
||||||
defect found no missing route. What was wrong was the *surrounding*
|
|
||||||
claim — that the guard was in force for the whole outer body. A complete
|
|
||||||
list of write sites is not a complete argument until the guard's extent
|
|
||||||
is stated too. The acceptance suite now drives the same rows at **two
|
|
||||||
depths**, directly and through a nested `commit_to`.
|
|
||||||
|
|
||||||
**THE ENUMERATION IS THE LOAD-BEARING PART, AND IT IS CLOSED AS AN
|
|
||||||
ENUMERATION OF WRITE SITES — for a structural reason, not because
|
|
||||||
inspection ran out of ideas.** Full working in the framing §3; the short
|
|
||||||
form:
|
|
||||||
|
|
||||||
- **Only two pieces of state can matter**, because `resolve_placement`
|
|
||||||
reaches `Ordinary` from a side request through exactly two branches:
|
|
||||||
`panel_capable`, and the one side window's `dedicated`.
|
|
||||||
- **`panel_capable` is unreachable from a body.** It is written only
|
|
||||||
where a `FrontendView` is constructed, and nothing in
|
|
||||||
`src/lua_bindings/` constructs, registers or unregisters one —
|
|
||||||
`register_frontend_view` has callers only in `daemon.rs` and core
|
|
||||||
unit tests.
|
|
||||||
- **Eight writes to `dedicated` exist** (`rg 'params\.dedicated\s*='
|
|
||||||
src/`); **four are reachable and a fifth is guarded defensively** —
|
|
||||||
`apply_placement`'s `Side` created / replacing / non-replacing arms
|
|
||||||
and `set_params` are the reachable four, and `quit_window`'s
|
|
||||||
`QuitAction::Restore` is the fifth, proved unreachable below and
|
|
||||||
guarded anyway. **All five are guarded**, which is the count that
|
|
||||||
matters; listing four under the word "five" is what an earlier version
|
|
||||||
of this bullet did. Two `Ordinary` arms are harmless (their target is
|
|
||||||
never a side window; one only ever clears the flag) and one is a unit
|
|
||||||
test.
|
|
||||||
- **The guards are sited where the property converges, not per caller.**
|
|
||||||
All three `Side` arms are reached through `apply_placement`, which has
|
|
||||||
**exactly one caller** — so one guard in `display_buffer` covers every
|
|
||||||
request-driven dedication, including spellings that do not exist yet.
|
|
||||||
`set_params` is a genuinely separate write and is guarded separately;
|
|
||||||
dedication does **not** converge before the field itself, and that is
|
|
||||||
stated rather than papered over.
|
|
||||||
- **Closing the side window is NOT a route**, checked rather than
|
|
||||||
assumed: with no side leaf `side_window_for` returns `None` and
|
|
||||||
placement **creates** a fresh panel instead of falling back. Hiding is
|
|
||||||
likewise irrelevant — `panel_hidden` is not consulted by placement.
|
|
||||||
- **`quit_window`'s `QuitAction::Restore { dedicated: true }` is
|
|
||||||
UNREACHABLE**, and this was the surprise. `Restore` is stored only on
|
|
||||||
a *replacing* side placement, and a dedicated slot can never be the
|
|
||||||
target of one. Guarded anyway, labelled defensive, because its
|
|
||||||
unreachability is emergent from two rules in another function.
|
|
||||||
- **What this does not rule out:** the enumeration is closed over the
|
|
||||||
current tree, not future edits. `params.dedicated` is a public field,
|
|
||||||
so nothing but the acceptance rows would catch a new direct writer.
|
|
||||||
|
|
||||||
**Also closed:** an invalid-UTF-8 profile (`string.char(255)`) reached
|
|
||||||
`to_str()` and surfaced mlua's generic conversion error instead of the
|
|
||||||
documented message naming the accepted values — the same reachability
|
|
||||||
class as revision 5's `Option<String>` defect, one layer down. The
|
|
||||||
comparison is on bytes now.
|
|
||||||
|
|
||||||
**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 9.**
|
|
||||||
Revisions 1–5 were approved over four review rounds; revisions 6–9 are
|
|
||||||
corrections carrying the blocker above, and **revision 8's design as
|
|
||||||
scoped by revision 9 is what the tree implements**. Revisions 6 and 7
|
|
||||||
are described in that document as the record of why *not* those;
|
|
||||||
neither is in the tree and neither should be restored from it.
|
|
||||||
- **Implemented in four commits.** `779bb02` is the mechanism
|
|
||||||
(`pmacs.window.capture_destination()`, the `ViewDestination` rename,
|
|
||||||
the profile argument); `d5a6170` is
|
|
||||||
`tests/destination_capture_acceptance.rs`; `469d5c8` is the
|
|
||||||
revision-8 panel-profile correction plus the invalid-UTF-8 hole;
|
|
||||||
`394fa43` is revision 9's contract stack and the commit below adds its
|
|
||||||
cross-frontend pin. **15 pins**, and both preservation suites pass
|
|
||||||
**unchanged** (journey 47, dired 31) — §7's stop signal not firing
|
|
||||||
rather than being suppressed.
|
|
||||||
- **HOW THE PANEL PROFILE IS ENFORCED, in one sentence so no earlier
|
|
||||||
revision gets reinstated by someone reading only that document:** the
|
|
||||||
preflight stays exactly where it was, and the mutations that would
|
|
||||||
invalidate it are **refused at the attempt**.
|
|
||||||
- `EditorCore::panel_commit_dedication_refusal` is the one rule. It
|
|
||||||
fires while **any** `"panel"` `CommitContract` for this frontend is
|
|
||||||
in force — every contract on the stack, not the innermost — and is
|
|
||||||
consulted from `display_buffer` (before `apply_placement`, so a
|
|
||||||
refused attempt mutates nothing), `pmacs.window.set_params` (before
|
|
||||||
its borrow, so `fixed_rows` in the same table is not applied
|
|
||||||
either), and `quit_window`.
|
|
||||||
- **This is the same shape as `Handle:await` being refused inside a
|
|
||||||
commit scope**, and for the identical reason: something that would
|
|
||||||
invalidate the scope's guarantee is rejected outright rather than
|
|
||||||
predicted around or caught late.
|
|
||||||
- The contract (`CommitContract { destination, profile }`) rides on
|
|
||||||
the core in a **stack**, pushed and popped by the **same**
|
|
||||||
`ScopedFrontendGuard` that scopes the frontend, so a `"panel"`
|
|
||||||
profile can never outlive the body that declared it. The field is
|
|
||||||
private to the crate — Lua cannot claim a profile for a placement it
|
|
||||||
did not commit to.
|
|
||||||
- **A stack, not a slot, and the distinction is revision 9 (above).**
|
|
||||||
The frontend override and the ambient frontend are *substitutions*,
|
|
||||||
so a nested scope rightly replaces them; a contract is a
|
|
||||||
*restriction*, and replacing one suspends it. The guard stores a
|
|
||||||
depth and truncates back to it, so an inner exit removes exactly the
|
|
||||||
contract it added and leaves every enclosing one in force.
|
|
||||||
- **Matching is per FRONTEND as well as per profile, and that is a
|
|
||||||
deliberate exception with its own positive pin.** A nested commit for
|
|
||||||
a different frontend may dedicate *its* side slot: `resolve_placement`
|
|
||||||
consults only the requesting frontend's `panel_capable` and its own
|
|
||||||
one side window, so nothing done to B can change where A's side
|
|
||||||
request lands. Pinned by
|
|
||||||
`a_nested_commit_for_another_frontend_may_dedicate_its_own_slot`,
|
|
||||||
which is the file's only row asserting that something is **allowed**
|
|
||||||
— every other asserts a refusal, and an exception only the doc
|
|
||||||
comment knows about is one review round from being simplified out.
|
|
||||||
- **Prohibiting nested `commit_to` was the other candidate and was
|
|
||||||
rejected.** It closes the hole by forbidding a construction no rule
|
|
||||||
objects to — `commit_to` is public Lua API for saying where a
|
|
||||||
continuation's result belongs, and a body committing to a second
|
|
||||||
destination (a diff beside a status panel) is where #227's adoption
|
|
||||||
is heading. Only the restriction needed preserving. **No Lua in the
|
|
||||||
tree nests today** — `builtin/runtime/dired.lua` is the only
|
|
||||||
`commit_to` consumer and it does not — so this is a decision about
|
|
||||||
the API's future rather than about a live consumer, which is why it
|
|
||||||
is recorded rather than left implicit.
|
|
||||||
- **`panel_placement_can_fall_back` remains the preflight**, unchanged
|
|
||||||
in role: it measures whether this frontend places side requests in
|
|
||||||
the panel *right now*. With the invalidating mutations refused, that
|
|
||||||
measurement stays true for the life of the body, which is what makes
|
|
||||||
it a guarantee rather than a forecast.
|
|
||||||
- The four document checks live once, in
|
|
||||||
`EditorCore::document_destination_refusal`.
|
|
||||||
- **Three deliberate limits**, each a different decision rather than a
|
|
||||||
stricter version of this one: the **document profile is untouched**
|
|
||||||
(constraining its body would newly refuse dired's own documented
|
|
||||||
panel path — a preservation-suite stop signal); **dedicating a
|
|
||||||
document window is still allowed** (it cannot change which of
|
|
||||||
panel-or-document a side request resolves to); and **falling back is
|
|
||||||
still allowed** — a frontend that cannot render a panel degrades
|
|
||||||
gracefully exactly as today, because this refuses the mutation that
|
|
||||||
*manufactures* a fallback, never the fallback itself.
|
|
||||||
- **Mutation-checked per guard, and the pattern is the evidence the rows
|
|
||||||
are independent rather than one assertion repeated.** Deleting the
|
|
||||||
`display_buffer` guard fails the three `display{side, dedicated}` rows
|
|
||||||
— verified **individually**, by rotating each to the front of the
|
|
||||||
table, since the first failure otherwise masks the rest. Deleting the
|
|
||||||
`set_params` guard fails only that row and leaves the display rows
|
|
||||||
passing. Both leave every other test in the file green.
|
|
||||||
- **Audit: nothing else relied on "a panel never touches a document".**
|
|
||||||
Four doc sites repeated the claim (`ViewDestination`'s own doc twice,
|
|
||||||
`capture_view_destination`, `ViewDestinationLua`) and were corrected;
|
|
||||||
no other code depended on it. Dired — the only Lua `commit_to`
|
|
||||||
consumer — takes the **two-argument document profile**, so all four
|
|
||||||
checks already applied to it, and it separately documents and accepts
|
|
||||||
the side-slot fallback (`builtin/runtime/dired.lua`).
|
|
||||||
`compile.lua`'s `already_in_panel` queries live state rather than
|
|
||||||
assuming, and the terminal adopter's rollback keys off
|
|
||||||
`DisplayOutcome::created_side`, already false on a fallback.
|
|
||||||
- **TWO FRAMING CLAIMS THE TREE DID NOT MATCH.** Neither changed a
|
|
||||||
decision; both are recorded because the framing says "counted, not
|
|
||||||
estimated" and a reader will check.
|
|
||||||
1. **The rename was 11 references across 5 files, not 8 across 4.**
|
|
||||||
`src/daemon.rs:1804` also calls the capture (the attaching
|
|
||||||
frontend's directory open), and `editor.rs` holds six references
|
|
||||||
rather than the counted total. Mechanical either way.
|
|
||||||
2. **Q#DC-4's "a frontend with no document window" is a DEFENSIVE
|
|
||||||
branch, not a routine one.** The obvious spelling — a frontend
|
|
||||||
showing only a bottom panel — is asserted impossible: Q#BP6 says a
|
|
||||||
layout always retains at least one non-side window, and
|
|
||||||
`EditorCore::non_side_target` carries a `debug_assert!` that fires
|
|
||||||
under `cargo test` when one does. So with Q#BP6 held a *registered*
|
|
||||||
frontend always has a live document window. The decision still
|
|
||||||
stands (capture stays total; an adopter with nowhere to land gets a
|
|
||||||
refusal naming that rather than permission to fall back to ambient
|
|
||||||
state), and the two Q#DC-4 pins drive the reachable spelling of the
|
|
||||||
same condition — a layout whose document window has gone while the
|
|
||||||
view remains. **#227 should not expect to hit this refusal**; it is
|
|
||||||
insurance, not a path.
|
|
||||||
- **Mutation-tested, since a matrix of deliberate omissions is exactly
|
|
||||||
what passes vacuously.** Retyping the profile to `Option<String>`
|
|
||||||
fails the table and boolean rows with mlua's conversion error (the
|
|
||||||
number row survives — Lua coerces it — which is why the closed set is
|
|
||||||
witnessed by more than one non-string). Applying all four checks in
|
|
||||||
both profiles fails the panel column; applying only check 1 in both
|
|
||||||
fails the document column. Defaulting an omitted profile to `"panel"`
|
|
||||||
fails **`journey_acceptance`'s two preservation pins**, which is the
|
|
||||||
contract claim being executable rather than asserted. Dropping the
|
|
||||||
frontend scope for the panel profile fails the survives-a-switch pin's
|
|
||||||
panel row; dropping the no-document-window arm fails the Q#DC-4 pair.
|
|
||||||
|
|
||||||
**Revision 8's four, each isolating a different way to get it wrong** —
|
|
||||||
and the pattern of *which* rows survive each is the evidence the parts
|
|
||||||
are independent rather than redundant:
|
|
||||||
1. delete the `panel_commit_dedication_refusal` call from
|
|
||||||
`display_buffer` → the three `display{side, dedicated}` rows fail,
|
|
||||||
**verified individually** by rotating each to the front of the
|
|
||||||
table so the first failure cannot mask the rest. Every other test
|
|
||||||
passes — which is exactly the hole an implementation guarding only
|
|
||||||
`set_params` would ship.
|
|
||||||
2. delete it from `set_params` → **only** that row fails; the three
|
|
||||||
display rows still pass.
|
|
||||||
3. delete the `panel_placement_can_fall_back` arm from
|
|
||||||
`commit_destination_refusal` → **only** the two pre-established
|
|
||||||
fallback rows fail, which is the preflight half.
|
|
||||||
4. make `panel_placement_can_fall_back` unconditionally `true` (the
|
|
||||||
"widen the predicate" non-fix) → the really-lands-in-the-panel pin,
|
|
||||||
the Q#DC-4 panel pin and the matrix's three panel rows all fail.
|
|
||||||
That is the two profiles collapsing into one, made visible — the
|
|
||||||
named fallback design, showing up as a test diff rather than
|
|
||||||
silently.
|
|
||||||
|
|
||||||
And reverting the byte comparison to `to_str()?` fails the
|
|
||||||
`invalid utf-8` row with mlua's conversion error, on content.
|
|
||||||
|
|
||||||
**Revision 9's two, each isolating a different half of the rule:**
|
|
||||||
1. restore `panel_commit_dedication_refusal` to reading only the
|
|
||||||
innermost contract (`.last()`, which is exactly revision 8's
|
|
||||||
swapped slot) → **only**
|
|
||||||
`a_nested_commit_cannot_mask_an_outer_panel_restriction` fails.
|
|
||||||
Note the ordinary-nesting pin deliberately survives this — it
|
|
||||||
exists to fail the *other* candidate fix (prohibit nesting), so the
|
|
||||||
two are a pair rather than one test written twice.
|
|
||||||
2. delete `&& contract.destination.frontend == fid` from the same
|
|
||||||
scan, making any outer `"panel"` contract **globally** restrictive
|
|
||||||
→ **only**
|
|
||||||
`a_nested_commit_for_another_frontend_may_dedicate_its_own_slot`
|
|
||||||
fails. Both single-frontend nesting tests pass under it, which is
|
|
||||||
the evidence they are independent of the frontend match rather than
|
|
||||||
merely looking so; the cross-frontend exception had no pin at all
|
|
||||||
before this row, since every other test in the file drives one
|
|
||||||
frontend.
|
|
||||||
|
|
||||||
Both were run across all three acceptance suites and the lib: in each
|
|
||||||
case `journey_acceptance` (47), `dired_acceptance` (31) and
|
|
||||||
`cargo test --lib` (1920) stay green, along with every other pin in
|
|
||||||
this file.
|
|
||||||
|
|
||||||
**The counts above are journey 47 / dired 31**, matching the bullet
|
|
||||||
further up. The mutation paragraph committed at `394fa43` had them
|
|
||||||
**reversed** in both the ledger and that commit's message; the ledger
|
|
||||||
is corrected here and the message is left as written, since rewriting
|
|
||||||
a pushed commit is worse than a footnote. A reader following that SHA
|
|
||||||
should take these numbers, not those.
|
|
||||||
- **The public API #227 adopts against (Q#DC-5), pinned so it is a
|
|
||||||
contract rather than an intention:**
|
|
||||||
`pmacs.window.commit_to(dest, body [, profile])`. Profile is an
|
|
||||||
optional trailing argument typed **`mlua::Value`, not
|
|
||||||
`Option<String>`** — with `Option<String>` mlua rejects a number or
|
|
||||||
table during argument *conversion*, before the closure runs, making
|
|
||||||
the promised "accepted values are…" message unreachable. That is the
|
|
||||||
same trap the existing binding documents for `dest`. Validated in the
|
|
||||||
body against a **closed** set — `"document"` and
|
|
||||||
`"panel"`. **Omitted means `"document"`**, so every existing
|
|
||||||
two-argument caller keeps all four preflight checks *by definition of
|
|
||||||
the signature*, which is what makes `journey_acceptance` passing
|
|
||||||
untouched a consequence rather than a hope. An unrecognized or
|
|
||||||
non-string profile **errors**, naming the accepted values — a silent
|
|
||||||
fallback would hand a caller different checks than it asked for,
|
|
||||||
which is the exact failure the parameterization exists to prevent.
|
|
||||||
Git's mapping is settled here too: `*git-status*` → panel,
|
|
||||||
`*git-diff*` → document. Revision 2 took three findings: Q#DC-2's parameterization was
|
|
||||||
incomplete (a panel depends on **none** of checks 2–4, not just check
|
|
||||||
3, so the question now carries a full preflight matrix with every
|
|
||||||
omission testable); `tests/journey_acceptance.rs` joins dired as a
|
|
||||||
**preservation suite and stop signal**, since it holds the
|
|
||||||
`commit_to` scope, forged-userdata, preflight and restoration pins
|
|
||||||
this lane generalizes; and the **coherence-impact section was missing
|
|
||||||
entirely**, which `CLAUDE.md` and `COHERENCE.md` §25 both require.
|
|
||||||
- **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 existed but was not Lua-reachable** until `779bb02`.
|
|
||||||
`pmacs.window.commit_to` took 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 could say where its result belongs. Line numbers are
|
|
||||||
the pre-lane ones, kept because they are what the finding was written
|
|
||||||
against.
|
|
||||||
- **Scope:** a Lua-reachable capture, a generic rename
|
|
||||||
(`DirectoryDestination` → `ViewDestination`; the framing counted 8
|
|
||||||
references across 4 files, the tree held **11 across 5** — see the
|
|
||||||
finding above), 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, *when the placement really is a panel*,
|
|
||||||
irrelevant to the first. One shape over-refuses the panel or
|
|
||||||
under-checks the document.
|
|
||||||
|
|
||||||
**DO NOT READ THE OLDER FORM OF THIS BULLET, WHICH SAID "the panel
|
|
||||||
never touches the captured window's buffer".** That is the claim
|
|
||||||
revisions 6–8 invalidate: panel placement **falls back** to an
|
|
||||||
ordinary document window when the frontend is not panel-capable or
|
|
||||||
its side slot is dedicated. The relaxation is conditional, and the
|
|
||||||
mutations that could make it fall back are refused inside a
|
|
||||||
panel-profile commit (revision 8) rather than predicted at preflight
|
|
||||||
(revision 6) or caught at placement (revision 7, which would refuse
|
|
||||||
after the callback had already mutated).
|
|
||||||
- **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, as the executable line rather than a description:**
|
|
||||||
|
|
||||||
```
|
|
||||||
scripts/gate --acceptance destination_capture_acceptance \
|
|
||||||
--acceptance journey_acceptance \
|
|
||||||
--acceptance dired_acceptance
|
|
||||||
```
|
|
||||||
|
|
||||||
`--acceptance` is repeatable, so there is no reason for this ledger
|
|
||||||
to say "plus dired's" and leave the reader to reconstruct it.
|
|
||||||
**`journey_acceptance` and `dired_acceptance` are preservation suites
|
|
||||||
and a STOP SIGNAL**: they carry the `commit_to` scope,
|
|
||||||
forged-userdata, preflight and restoration pins this lane
|
|
||||||
generalizes, and if either needs editing, the change altered Journey
|
|
||||||
Stage 1a's semantics rather than closing a gap in them. No
|
|
||||||
`--protocol` — core and Lua bindings only.
|
|
||||||
|
|
||||||
## Worker identity Stage 1 (§9) — MERGED as #232 (`3cc1b85`)
|
## Worker identity Stage 1 (§9) — MERGED as #232 (`3cc1b85`)
|
||||||
|
|
||||||
**Written with the lane's first commit**, per the standing correction
|
**Written with the lane's first commit**, per the standing correction
|
||||||
|
|
@ -1004,258 +623,6 @@ the authoritative tip** — the ref, not a SHA. Recover with
|
||||||
each grew a required `purpose` field, and `pmacs.process.spawn`
|
each grew a required `purpose` field, and `pmacs.process.spawn`
|
||||||
requires `purpose` in its spec table.
|
requires `purpose` in its spec table.
|
||||||
|
|
||||||
## Discovery Stage 2 — PR #228 OPEN, **MERGE-BLOCKED**
|
|
||||||
|
|
||||||
**PR #228** — https://github.com/levineuwirth/pmacs/pull/228. Opened
|
|
||||||
2026-08-09 at `2d298dd`. **Open for review, not for merge.**
|
|
||||||
|
|
||||||
**The block is a gate-integrity problem, not backlog hygiene.** This
|
|
||||||
lane's gate is `scripts/gate --protocol`, which promises the CRDT
|
|
||||||
workspace sweep. That sweep's documented precondition is
|
|
||||||
`cargo build --workspace --no-default-features --features luajit,crdt`
|
|
||||||
(handoff §5), and **the script does not run it** — confirmed by reading
|
|
||||||
its plan emitter. On a fresh per-worktree target directory the sweep
|
|
||||||
fails on twelve `gpu_invocation_acceptance` tests missing the
|
|
||||||
`pmacs-gpu` binary, so a `--protocol` result can be decided by the
|
|
||||||
state of the build directory rather than by the diff.
|
|
||||||
|
|
||||||
Latent until #225 gave each worktree its own target dir — a shared one
|
|
||||||
usually already had `pmacs-gpu` built, satisfying the precondition by
|
|
||||||
accident. It surfaced on this branch's first gate run.
|
|
||||||
|
|
||||||
**Unblocking requires both:** the `scripts/gate` repair, in its own
|
|
||||||
narrow framing and its own PR (explicitly **not** folded into this
|
|
||||||
feature branch), and then a **fresh-target rerun of this branch's
|
|
||||||
protocol gate** under the repaired script.
|
|
||||||
|
|
||||||
**Written with the lane's first commit**, per the standing correction
|
|
||||||
from #171 and #215.
|
|
||||||
|
|
||||||
**Branch `discovery-stage2`**, base `githubsucks/main` @ `4bc55e8`
|
|
||||||
(the #225 merge). **`githubsucks/discovery-stage2` is the authoritative
|
|
||||||
tip** — the ref, not a SHA. Recover with
|
|
||||||
`git fetch githubsucks && git checkout discovery-stage2`.
|
|
||||||
|
|
||||||
- **Framing `docs/discovery-stage2-framing.md`, revision 3, APPROVED
|
|
||||||
2026-08-09** after three review rounds. Each round found the previous
|
|
||||||
one reasoning about a mechanism instead of reading it — an in-place
|
|
||||||
field change that postcard cannot make compatible, a TUI that never
|
|
||||||
reads the message at all, a round-trip test that freezes nothing, a
|
|
||||||
cache hazard the per-peer render state makes impossible, and a
|
|
||||||
clipping rule unachievable at narrow widths.
|
|
||||||
Scope: `COHERENCE.md` §5's "M-x rows are still bare names".
|
|
||||||
Descriptions already exist on `Command` and are already rendered by
|
|
||||||
`help.list-commands`; they are missing at the one moment they would
|
|
||||||
change a decision.
|
|
||||||
- **PROTOCOL BUMP v22 → v23, and this lane HOLDS THE BUMP SLOT.**
|
|
||||||
Additive: a new `MinibufferPromptRows` variant **appended** to the
|
|
||||||
enum, with `MinibufferPrompt` **frozen** for v12–v22. An in-place
|
|
||||||
field change is a wire break — postcard encodes positionally, and
|
|
||||||
that variant is sent to every peer `>= 12` (`src/daemon.rs:1472`).
|
|
||||||
- **Git Stage 2 (gutter markers) also needs a bump and must wait for
|
|
||||||
this to land.** Git Stage 1 is no-wire and runs beside it.
|
|
||||||
- **Two halves, only one of which is wire work.** `pmacs-gpu` renders
|
|
||||||
the new variant. **The grid TUI never reads `MinibufferPrompt` at
|
|
||||||
all** — it paints from `core.minibuffer` and renders
|
|
||||||
`format!(" [{cand}]")` (`src/editor.rs:5484`), so its half is a
|
|
||||||
local formatting change reading the registry directly. A multi-row
|
|
||||||
TUI chooser is explicitly NOT this lane.
|
|
||||||
- **Gates:** `scripts/gate --protocol --acceptance
|
|
||||||
discovery_stage2_acceptance --acceptance m9_6_acceptance --acceptance
|
|
||||||
m9_7_acceptance --acceptance m9_8_acceptance` — the strengthened
|
|
||||||
two-configuration sweep, which is what `--protocol` exists for. The
|
|
||||||
three m9 suites are named because the PR #228 review round measured
|
|
||||||
them as this change's blast radius (see the description-clip bullet);
|
|
||||||
their continued passing is on the record rather than assumed.
|
|
||||||
**`--protocol` does NOT run its own documented precondition**
|
|
||||||
(`cargo build --workspace --no-default-features --features
|
|
||||||
luajit,crdt`, handoff §5) — run it by hand first or twelve
|
|
||||||
`gpu_invocation_acceptance` tests fail on a missing `pmacs-gpu`
|
|
||||||
binary. That omission is the `gate-protocol-build` lane's, not this
|
|
||||||
one's.
|
|
||||||
- **IMPLEMENTED.** `PROTOCOL_VERSION` is 23,
|
|
||||||
`ADVERTISED_PROTOCOL_VERSION` is untouched at 20. New suite
|
|
||||||
`tests/discovery_stage2_acceptance.rs`; the daemon half is
|
|
||||||
`crdt`-gated (a semantic session is necessarily a text replica) and
|
|
||||||
runs one daemon serving a v22 and a v23 session simultaneously.
|
|
||||||
- **Multi-line descriptions are clipped AT THE SURFACE, and
|
|
||||||
registration-level rejection was investigated and REJECTED ON
|
|
||||||
EVIDENCE — do not re-propose it.** PR #228 review found the real
|
|
||||||
hazard: the GPU dropdown derives its height, visible window and
|
|
||||||
highlight offset from `rows.len()` (one logical row per candidate),
|
|
||||||
so a detail carrying a line break misaligns every row below it; the
|
|
||||||
TUI writes into a single-row band. The obvious fix — reject CR/LF in
|
|
||||||
`CommandRegistry::define` — was implemented and measured, and it
|
|
||||||
**fails 36 tests across `m9_6`/`m9_7`/`m9_8`**, because MCP tool
|
|
||||||
registration renders a whole schema block into `description`
|
|
||||||
(`tests/fixtures/pmacs-mcp-tools/init.lua:272`,
|
|
||||||
`table.concat(lines, "\n")`, used at `:496`) and
|
|
||||||
**`tests/m9_6_acceptance.rs:583-598` asserts four separate lines of
|
|
||||||
it** — tool text, `Arguments:`, and two per-argument lines. No
|
|
||||||
single-line rendering satisfies those assertions, so a registry guard
|
|
||||||
could only go green by deleting a shipped acceptance criterion.
|
|
||||||
The one-line constraint belongs to the surfaces that have it:
|
|
||||||
`Command::description_first_line` clips, both single-row consumers
|
|
||||||
call it, and the full text still reaches `describe-command` /
|
|
||||||
`help.list-commands` untouched. Precedent already in-tree — the same
|
|
||||||
MCP fixture clips a tool RESULT to its first line because *"a
|
|
||||||
multi-line set_status would corrupt the row layout"* (`:277-285`).
|
|
||||||
**A startup census is not a corpus census**: booting an
|
|
||||||
`EditorState` and scanning all 180 registered descriptions found zero
|
|
||||||
offenders, because MCP registers at RUNTIME and builds the string by
|
|
||||||
concatenation — invisible to both that census and a grep for literals.
|
|
||||||
The workspace sweep is what caught it.
|
|
||||||
- **The freeze is enforced by LITERAL byte fixtures**, not a round-trip
|
|
||||||
— `minibuffer_prompt_v12_wire_bytes_are_frozen` in `src/protocol.rs`,
|
|
||||||
the first such fixture in this repo. Bite-verified: reordering two
|
|
||||||
fields of `MinibufferPrompt` leaves
|
|
||||||
`minibuffer_prompt_round_trips_through_postcard` **passing** and fails
|
|
||||||
the fixture, which is exactly the hazard a round-trip cannot see.
|
|
||||||
- **Version assertions updated (five, each read before editing):**
|
|
||||||
`src/protocol.rs` — the `PROTOCOL_VERSION == 22` tripwire (renamed
|
|
||||||
`protocol_version_is_twenty_three_for_minibuffer_prompt_rows`) and
|
|
||||||
`supported_protocol_versions_resume_ladder_on_v6_floor`'s
|
|
||||||
accepted/rejected ranges; `tests/statusline_segments_acceptance.rs`
|
|
||||||
(version + supported range + the `!supported` ceiling);
|
|
||||||
`tests/bottom_panel_stage2b_gpu_acceptance.rs`;
|
|
||||||
`tests/vterm_stage3_acceptance.rs`. **No `ADVERTISED_PROTOCOL_VERSION`
|
|
||||||
assertion fired**, which is the pin doing its job.
|
|
||||||
- **No cross-version cache test, deliberately** (framing §3.2/§6):
|
|
||||||
`SemanticRenderState::for_peer` bakes the negotiated version in at
|
|
||||||
attach and is dropped at detach, so a cache cannot span two versions.
|
|
||||||
A test for an impossible condition passes forever while teaching the
|
|
||||||
next reader that the hazard is real.
|
|
||||||
|
|
||||||
## LSP LaTeX coverage — IMPLEMENTED, gates green, no PR yet
|
|
||||||
|
|
||||||
**Written with the lane's first commit**, per the standing correction
|
|
||||||
from #171 and #215.
|
|
||||||
|
|
||||||
**Branch `lsp-latex-coverage`**, base `githubsucks/main` @ `4bc55e8`
|
|
||||||
(the #225 merge). **`githubsucks/lsp-latex-coverage` is the
|
|
||||||
authoritative tip** — the ref, not a SHA. Recover with
|
|
||||||
`git fetch githubsucks && git checkout lsp-latex-coverage`.
|
|
||||||
|
|
||||||
- **Framing `docs/lsp-language-coverage-framing.md`, revision 3 —
|
|
||||||
IMPLEMENTATION AUTHORIZED 2026-08-09**, after a summary of its four
|
|
||||||
corrections rather than a findings round on the document itself.
|
|
||||||
Recorded that way deliberately: the §3 `.texlabroot` verification
|
|
||||||
caveat was live and binding, and was step zero of the work rather
|
|
||||||
than a footnote it could be read past. **It is now discharged — see
|
|
||||||
below.** **Revision 1 was UNTRACKED on `main` in one checkout** and
|
|
||||||
therefore did not travel; committing it here is the fix.
|
|
||||||
- **Scope: one `pmacs.lsp.config.latex` entry plus its root resolver.**
|
|
||||||
`texlab` 5.25.1 is installed and unused; a `.tex` buffer highlights
|
|
||||||
correctly and offers no completion, diagnostics, or go-to-definition.
|
|
||||||
- **Revision 2 found Slice 1 is SMALLER than revision 1 framed.** The
|
|
||||||
proposed `.tex`/`.latex`/`.sty`/`.cls` filetype mappings are
|
|
||||||
redundant: the grammar already carries exactly those extensions
|
|
||||||
(`src/syntax.rs:1111`), grammar-extension detection sits **ahead** of
|
|
||||||
the LSP filetype map in the precedence chain
|
|
||||||
(`docs/latex-grammar-math-substrate-framing.md:166-171`), and
|
|
||||||
`lsp.lua:267-270` calls that map "mainly the LSP-only fallback". The
|
|
||||||
two systems cannot disagree, because the grammar's extension list is
|
|
||||||
what drives detection.
|
|
||||||
- **Two other corrections.** `haskell-language-server` **is** installed
|
|
||||||
on this machine — revision 1 said it was not, which was the whole
|
|
||||||
basis of its Slice 1 / Slice 2 split. And Q#LX3's deferral argument
|
|
||||||
read `COHERENCE.md:1669` ("first slice in flight") when `:124` and
|
|
||||||
`:867` both record multi-root affinity as **merged (#161)**; that
|
|
||||||
line contradicts the same document twice and wants a separate fix.
|
|
||||||
- **Q#LX2 (the LaTeX root) is answered.** An upward marker walk through
|
|
||||||
`config.latex.root`, which already accepts a resolver function
|
|
||||||
(`lsp.lua:543`), falling back to the file's own directory.
|
|
||||||
**`.git` is deliberately excluded**: a repo root is the wrong answer
|
|
||||||
for LaTeX, and it is the one place copying the other fourteen
|
|
||||||
entries' instinct is actively wrong.
|
|
||||||
- **STEP ZERO IS DISCHARGED — §3's `.texlabroot` caveat, by
|
|
||||||
observation.** Marker 1 **ships**, and the framing's premise for it
|
|
||||||
was corrected in the process.
|
|
||||||
- **`.texlabroot` is a real texlab marker.** texlab v5.25.1's
|
|
||||||
`crates/distro/src/language.rs` maps `.texlabroot`/`texlabroot` →
|
|
||||||
Root, `Tectonic.toml` → Tectonic, `.latexmkrc`/`latexmkrc` →
|
|
||||||
Latexmkrc; `ProjectRoot::walk_and_find`
|
|
||||||
(`crates/base-db/src/deps/root.rs`) walks ancestors testing all
|
|
||||||
three, innermost wins. The shipped marker set is **texlab's own**,
|
|
||||||
including the bare `texlabroot`/`latexmkrc` spellings the framing
|
|
||||||
did not list.
|
|
||||||
- **But texlab cannot apply that walk to fix a root pmacs gets
|
|
||||||
wrong**, which is the correction that matters. Each arm searches
|
|
||||||
`workspace.iter()` — documents ALREADY LOADED — and the workspace
|
|
||||||
comes from the folders the CLIENT supplies. Hand-driven LSP
|
|
||||||
sessions confirmed it: with `rootUri` at a `chapters/`
|
|
||||||
subdirectory, no marker above it (`.texlabroot` included) widened
|
|
||||||
texlab's view and its dependency graph never reached the parent
|
|
||||||
document; with `rootUri` at the marker directory the parent
|
|
||||||
resolved, marker present or not. **texlab honours the root it is
|
|
||||||
handed and never corrects a too-narrow one**, so what
|
|
||||||
`config.latex.root` returns *is* the project scope. That makes the
|
|
||||||
resolver the whole value of the lane rather than a nicety.
|
|
||||||
- **`args = {}` is also observed**, not assumed: bare `texlab`
|
|
||||||
answers `initialize` with `TexLab 5.25.1` over stdio, so the `run`
|
|
||||||
subcommand is not needed.
|
|
||||||
- **§3 said the wrong thing and has been corrected — `b5eaf27` IS
|
|
||||||
revision 3.** It framed marker 1 as conditional on texlab honouring
|
|
||||||
the `.texlabroot` *file*, when the operative fact is that texlab
|
|
||||||
honours the *client-supplied root* and never widens it. The caveat
|
|
||||||
was discharged by observation, and revision 3 records what that
|
|
||||||
established. Nothing about §3 is outstanding.
|
|
||||||
- **`.git` exclusion needed more than omitting it from the list.**
|
|
||||||
`project_root_for` falls through to `pmacs.project.detect` when a
|
|
||||||
resolver returns nil, and **that** walk includes `.git` — so a
|
|
||||||
resolver declining on a markerless file would hand texlab the
|
|
||||||
repository root by the back door. The resolver therefore never
|
|
||||||
declines for a file that has a directory. Pinned end to end through
|
|
||||||
attach, with the same fixture asserting the shared detector really
|
|
||||||
would have answered the repo root.
|
|
||||||
- **Commit `a9ef37f`** — `builtin/runtime/lsp.lua` plus
|
|
||||||
`tests/lsp_latex_acceptance.rs` (14 tests, one per §6 bullet plus the
|
|
||||||
boundary and decline cases). No `settings`/`init_options` (Q#LX1); no
|
|
||||||
filetype mappings (§2, asserted both ways).
|
|
||||||
- **Gates: ALL GREEN** via
|
|
||||||
`./scripts/gate --acceptance lsp_latex_acceptance` — fmt, clippy,
|
|
||||||
lib, lib-crdt, the new suite, m4, gpu, the workspace sweep (115
|
|
||||||
suites, zero failures), diff-check. No `--protocol` — a config entry,
|
|
||||||
no wire.
|
|
||||||
- **Seven mutations each fail the suite**: resolver declining on no
|
|
||||||
marker (6 tests), no marker walk (4), a redundant `filetypes.tex`
|
|
||||||
(1), boundary ignored (1), `io.open` truthiness so a directory counts
|
|
||||||
as a marker (1), marker set narrowed (4), command renamed with
|
|
||||||
opinionated settings added (1).
|
|
||||||
- **The boundary has now been the interesting part twice, and the
|
|
||||||
second time it was a real defect (fixed in review).** First it was
|
|
||||||
hermeticity — every fixture sets `set_search_boundary` at its own
|
|
||||||
tempdir because R8's shape (a stray `latexmkrc` above the tempdir)
|
|
||||||
would make the markerless assertions pass while testing nothing.
|
|
||||||
Then review found `latex_within_boundary` answering a PATH question
|
|
||||||
with string arithmetic: `dir:sub(1, #boundary + 1) == boundary .. "/"`
|
|
||||||
compares against `"//"` when the boundary is `/`, which no canonical
|
|
||||||
path matches, so a root boundary judged **every** ancestor out of
|
|
||||||
bounds, ran no marker walk at all, and gave each chapter of a thesis
|
|
||||||
its own server — the lane's headline behaviour silently off, with
|
|
||||||
every shipped test still green because each one clamps to a tempdir.
|
|
||||||
The same trap sat at the other end (`/` was never a walk candidate,
|
|
||||||
and `/paper.tex` sliced to an empty directory and declined into the
|
|
||||||
`.git`-aware detector). Now segment comparison throughout: the root
|
|
||||||
is a boundary with zero segments, contained by construction rather
|
|
||||||
than by a special case. Pinned by an ATTACH-level test under a `/`
|
|
||||||
boundary — two chapters, one server, marker root — and the
|
|
||||||
hermeticity property asserts **both** directions, since "stops at the
|
|
||||||
boundary" is also satisfied by a walk that never runs. Suite is 16
|
|
||||||
tests. **A reader
|
|
||||||
deciding whether to trust this resolver should read it as: the marker
|
|
||||||
set and the `.git` exclusion were settled by observation and are
|
|
||||||
solid; the boundary arithmetic around them was not, and is the place
|
|
||||||
to look first if roots come back wrong.**
|
|
||||||
- **Trap for the next agent in this worktree:** this machine exports a
|
|
||||||
shared `CARGO_TARGET_DIR`, so a bare `cargo test` compiles against a
|
|
||||||
sibling worktree's artifacts and fails with errors from code that is
|
|
||||||
not in this tree. Use `scripts/gate`, or
|
|
||||||
`CARGO_TARGET_DIR="$(./scripts/gate --print-target-dir)"` for ad-hoc
|
|
||||||
runs. `scripts/gate`'s own header documents this; the failure looks
|
|
||||||
like a broken branch, which is why it is recorded here.
|
|
||||||
- **No PR opened**, by instruction.
|
|
||||||
|
|
||||||
## `scripts/gate --protocol` build step — **MERGED as #229** (`7cf4653`)
|
## `scripts/gate --protocol` build step — **MERGED as #229** (`7cf4653`)
|
||||||
|
|
||||||
**MERGED as PR #229** — https://github.com/levineuwirth/pmacs/pull/229,
|
**MERGED as PR #229** — https://github.com/levineuwirth/pmacs/pull/229,
|
||||||
|
|
@ -1465,41 +832,6 @@ authoritative tip** — the ref, not a SHA. Recover with
|
||||||
sweep step each fail the suite.
|
sweep step each fail the suite.
|
||||||
|
|
||||||
|
|
||||||
## QoL arc retirement — PR #224 OPEN (docs only)
|
|
||||||
|
|
||||||
**PR #224** — https://github.com/levineuwirth/pmacs/pull/224. Written
|
|
||||||
**after** the PR existed rather than with the lane's first commit —
|
|
||||||
which is the standing correction from #171 and #215 being missed again,
|
|
||||||
and it took review asking. Recorded that way rather than quietly
|
|
||||||
back-dated: this file requires a lane for **every open PR**, including
|
|
||||||
the PR that retires other lanes.
|
|
||||||
|
|
||||||
- **Branch `retire-long-lines-lane`**, base `githubsucks/main` @
|
|
||||||
`9a26ac8` (the #223 merge). **`githubsucks/retire-long-lines-lane` is
|
|
||||||
the authoritative tip** — the ref, not a SHA, since any edit to this
|
|
||||||
block advances past whatever SHA it records. Recover with
|
|
||||||
`git fetch githubsucks && git checkout retire-long-lines-lane`.
|
|
||||||
- **Docs only.** Two files, `docs/active-work.md` and
|
|
||||||
`docs/agent-handoff.md`. No `src/`, no crate, no manifest, no test
|
|
||||||
changes.
|
|
||||||
- **Scope:** Rule 4 for the QoL arc, closed at #223. Remove the three
|
|
||||||
merged lane blocks (long lines, Stage 1 #219, Stage 2 #220) **after**
|
|
||||||
re-homing their durable residue to the handoff; advance the handoff's
|
|
||||||
date and `main` anchor and this file's canonical-base record and
|
|
||||||
recovery floor; add capability-aware keymap resolution as a named
|
|
||||||
handoff §6 backlog item — cross-cutting, **not started**, needs its
|
|
||||||
own framing.
|
|
||||||
- **Verification:** `git diff --check` clean. **The recovery path was
|
|
||||||
re-exercised, not SHA-swapped** — fresh clone into an empty
|
|
||||||
directory, `githubsucks` alias, `--prune` fetch, `9a26ac8` confirmed
|
|
||||||
an ancestor of `githubsucks/main`, worktree recovered with the
|
|
||||||
three-argument form; all four steps clean. Swept for dangling
|
|
||||||
references to the removed lanes and their branches. The full gate
|
|
||||||
suite is **not** re-run for a change that cannot reach it, and that
|
|
||||||
is stated rather than left as a gap.
|
|
||||||
- **Retire this block in the next absorption after #224 merges.** It
|
|
||||||
describes a docs PR; once merged there is nothing volatile left.
|
|
||||||
|
|
||||||
## Docs absorption after #217 — MERGED as #218 (2026-08-06 09:59Z)
|
## Docs absorption after #217 — MERGED as #218 (2026-08-06 09:59Z)
|
||||||
|
|
||||||
**PR #218** — https://github.com/levineuwirth/pmacs/pull/218. **This
|
**PR #218** — https://github.com/levineuwirth/pmacs/pull/218. **This
|
||||||
|
|
@ -1849,40 +1181,45 @@ wants, not a continuation of a plan.**
|
||||||
optional external tools; `/bin/sh`, `stty`, git and tar are documented
|
optional external tools; `/bin/sh`, `stty`, git and tar are documented
|
||||||
and never checked. That is §18 onboarding work.
|
and never checked. That is §18 onboarding work.
|
||||||
|
|
||||||
## Discovery lane (P4) — STAGE 1 MERGED (#207); STAGE 2 IS NEXT
|
## Discovery lane (P4) — STAGES 1 AND 2 MERGED (#207, #228); LATER WORK REMAINS
|
||||||
|
|
||||||
**Rewritten, not removed.** Rule 4 removes a lane when its ARC is done;
|
**Rewritten, not removed, and the PR-specific Stage 2 block above it is
|
||||||
this arc is not — Stage 1 built the command surface and everything that
|
retired into this one.** Rule 4 removes a lane when its ARC is done;
|
||||||
needs a Rust change is still ahead. Stage 1's durable facts are in
|
this arc is not. **Deleting this lane on the strength of one merged
|
||||||
`docs/agent-handoff.md` §1.
|
stage would have dropped four named pieces of open work** — predicate
|
||||||
|
evaluation, command metadata, help unification, and the prefix
|
||||||
|
decision — which is exactly why the coalesce is spelled out rather than
|
||||||
|
left to judgement. Durable facts from both stages are in
|
||||||
|
`docs/agent-handoff.md` (§1 for Stage 1; the v23 frozen-variant lesson
|
||||||
|
in §4 for Stage 2).
|
||||||
|
|
||||||
- **Landed:** eleven `help.*` commands over the existing registries,
|
- **Landed, Stage 1:** eleven `help.*` commands over the existing
|
||||||
indexed by `M-x help`, with `editor.describe-command` /
|
registries, indexed by `M-x help`, with `editor.describe-command` /
|
||||||
`editor.describe-setting` kept as forwarders. No Rust, no protocol
|
`editor.describe-setting` kept as forwarders. No Rust, no protocol
|
||||||
change. §5 moved substrate-without-surface → **Partial**.
|
change. §5 moved substrate-without-surface → **Partial**.
|
||||||
- **Stage 2 candidates, in rough dependency order:**
|
- **Landed, Stage 2 (#228, `0857bf4`):** M-x rows carry descriptions
|
||||||
1. **Richer M-x rows** — a **protocol change**:
|
over **protocol v23** — `MinibufferPromptRows` appended while
|
||||||
`MinibufferPrompt.candidates` is `Vec<String>`, while
|
`MinibufferPrompt` stays frozen and still sent for `12..=22`, gated as
|
||||||
`CompletionPopupRow` already carries `kind`/`detail`, so the wire
|
a range on both sides so exactly one variant reaches any peer.
|
||||||
pattern is solved and the bump is the work.
|
- **Still open, and the reason this lane survives:**
|
||||||
2. **`Command` gains title / category / aliases / flags /
|
1. **`Command` gains title / category / aliases / flags /
|
||||||
arg-schema** — a Rust type change across ~147 definition sites.
|
arg-schema** — a Rust type change across ~147 definition sites.
|
||||||
MCP currently works around the missing schema by stuffing rendered
|
MCP currently works around the missing schema by stuffing rendered
|
||||||
JSON into the description string.
|
JSON into the description string.
|
||||||
3. **Predicate evaluation.** `Command.predicate` is read at
|
2. **Predicate evaluation.** `Command.predicate` is read at
|
||||||
`src/help.rs:76` and one test, and **evaluated nowhere**. Starting
|
`src/help.rs:76` and one test, and **evaluated nowhere**. Starting
|
||||||
to evaluate it makes commands stop being invocable, so it needs its
|
to evaluate it makes commands stop being invocable, so it needs its
|
||||||
own decision about what "unavailable" means at each call site —
|
own decision about what "unavailable" means at each call site —
|
||||||
M-x, dispatch, menu. `discovery_acceptance`'s `d9` pins today's
|
M-x, dispatch, menu. `discovery_acceptance`'s `d9` pins today's
|
||||||
behaviour with a *raising* predicate, so that stage must change the
|
behaviour with a *raising* predicate, so that stage must change the
|
||||||
pin knowingly.
|
pin knowingly.
|
||||||
4. **Help-layer unification.** `src/help.rs` is still orphaned. Stage 1
|
3. **Help-layer unification.** `src/help.rs` is still orphaned. Stage 1
|
||||||
funnels every command through `pmacs.editor._show_help` and renders
|
funnels every command through `pmacs.editor._show_help` and renders
|
||||||
via named per-subject functions, so the work is enumerated:
|
via named per-subject functions, so the work is enumerated:
|
||||||
replace the four subjects `src/help.rs` covers (key, mode, hook,
|
replace the four subjects `src/help.rs` covers (key, mode, hook,
|
||||||
buffer) and **write three new Rust renderers** for settings, lists
|
buffer) and **write three new Rust renderers** for settings, lists
|
||||||
and apropos.
|
and apropos.
|
||||||
5. **The help prefix.** Deliberately untouched by Stage 1 — the
|
4. **The help prefix.** Deliberately untouched by Stage 1 — the
|
||||||
decision is one for the whole family, and `C-h` is **not** free
|
decision is one for the whole family, and `C-h` is **not** free
|
||||||
(non-kitty terminals cannot disambiguate Ctrl+Backspace from
|
(non-kitty terminals cannot disambiguate Ctrl+Backspace from
|
||||||
Ctrl+H; both produce byte 0x08). `F1` / `C-c ?` / a rebind are the
|
Ctrl+H; both produce byte 0x08). `F1` / `C-c ?` / a rebind are the
|
||||||
|
|
|
||||||
|
|
@ -1178,6 +1178,10 @@ someone forgot.
|
||||||
it — the reverted seam failed only the consumed-plan check, not the
|
it — the reverted seam failed only the consumed-plan check, not the
|
||||||
content assertion. That is what the consumed-plan check is for.
|
content assertion. That is what the consumed-plan check is for.
|
||||||
- **Lean 4 arc (Arc 8) — stages 1, 2, 3a, 3b, 4a, 4b ALL LANDED**
|
- **Lean 4 arc (Arc 8) — stages 1, 2, 3a, 3b, 4a, 4b ALL LANDED**
|
||||||
|
*(**"Arc 8" means THIS arc and only this one.** The roadmap's
|
||||||
|
colliding "Arc 8 — GPU structural parity" label is retired: that scope
|
||||||
|
is Half B of **the GUI arc**, which is a name and not a number —
|
||||||
|
`COHERENCE.md` §20, Q#GA4.)*
|
||||||
(`docs/lean4-mode-framing.md`; #160, #161, #167, #170, #179, #181). pmacs edits Lean 4: `arborium-lean` highlighting, a
|
(`docs/lean4-mode-framing.md`; #160, #161, #167, #170, #179, #181). pmacs edits Lean 4: `arborium-lean` highlighting, a
|
||||||
`lean4` major mode, `⟨⟩ ⦃⦄ ⟮⟯` pairs, and a `lake serve` language
|
`lean4` major mode, `⟨⟩ ⦃⦄ ⟮⟯` pairs, and a `lake serve` language
|
||||||
server with a Lake-aware outermost root, a lazy toolchain probe, a
|
server with a Lake-aware outermost root, a lazy toolchain probe, a
|
||||||
|
|
@ -2747,9 +2751,30 @@ nothing; and the CLOSE message must use the same variant family as the
|
||||||
OPEN, or a session closed by the other family's clear leaves its surface
|
OPEN, or a session closed by the other family's clear leaves its surface
|
||||||
on screen forever.
|
on screen forever.
|
||||||
|
|
||||||
**Fake LSP** (`src/bin/pmacs_fake_lsp.rs`) modes: `fullonly`,
|
**LaTeX is served by `texlab`, and its root is NOT the repository root**
|
||||||
`rangeonly`, `rangeonly16` (UTF-16 + fail-closed bounds validation),
|
(#230). `pmacs.lsp.config.latex` resolves the document root by an upward
|
||||||
`sighelp`. Use these for capability-matrix tests, not real servers.
|
marker walk — `.texlabroot`/`texlabroot` are texlab's own markers,
|
||||||
|
verified in its `crates/distro/src/language.rs` rather than assumed —
|
||||||
|
and **`.git` is deliberately excluded from that walk**: a repository
|
||||||
|
root is the wrong answer for a multi-file document, which is the whole
|
||||||
|
reason the resolver exists. `config.latex.root` already accepted a
|
||||||
|
resolver function, so this added no new mechanism.
|
||||||
|
|
||||||
|
**Fake LSP** (`src/bin/pmacs_fake_lsp.rs`) modes — enumerated from the
|
||||||
|
binary, because this list had gone stale and a stale mode list is how a
|
||||||
|
test ends up covering the shape next to the defect. Capability matrix:
|
||||||
|
`fullonly`, `rangeonly`, `rangeonly16` (UTF-16 + fail-closed bounds
|
||||||
|
validation), `sighelp`, `prepare`, `preprefuse`, `rename`,
|
||||||
|
`inlaybounds`, `inlayrefresh`, `semantictokensrefresh`,
|
||||||
|
`applyeditplan`, `resourceops`, `posecho`, `defenv`, `wsconfig`,
|
||||||
|
`rooturi`, `leanprogress`. Failure shapes: `crash`, `error`, `garbage`,
|
||||||
|
`silent`. **File watchers (issue #233)**: `filewatch` (RelativePattern
|
||||||
|
`**/*.txt`), `filewatchabs` (plain-string ABSOLUTE glob),
|
||||||
|
`filewatchflat` (RelativePattern with no leading `**/`),
|
||||||
|
`filewatchbare` (bare relative string — the P1 regression guard),
|
||||||
|
`filewatchrereg` (same id twice, no unregister), `filewatchjoin`,
|
||||||
|
`filewatchretire`. Use these for capability-matrix tests, not real
|
||||||
|
servers.
|
||||||
|
|
||||||
## 5. Hard-won ops lessons
|
## 5. Hard-won ops lessons
|
||||||
|
|
||||||
|
|
@ -3170,6 +3195,20 @@ on screen forever.
|
||||||
|
|
||||||
## 6. Named deferrals (the standing backlog, consolidated)
|
## 6. Named deferrals (the standing backlog, consolidated)
|
||||||
|
|
||||||
|
**The GUI arc — OPEN, framing approved 2026-08-10, Stage 0 READY FOR PR (not yet opened).**
|
||||||
|
`docs/gui-arc-framing.md` is the arc-level frame and is also Stage 0's
|
||||||
|
own framing; Stages 1–10 each need their own before their branch. It
|
||||||
|
opened from a daily-driver report: the TUI is daily-drivable, the GUI is
|
||||||
|
not. **`COHERENCE.md` §16 now carries a PRODUCT subgrade (Weak) beside
|
||||||
|
its architectural one (Strong)**, and the arc is what closes it. §20
|
||||||
|
places Half A after Priority 1, and **reaching Stage 4b is a P2 START
|
||||||
|
GATE** — no later GUI stage begins until P2 has an approved framing and
|
||||||
|
an opened lane. The name is deliberate: **"Arc 8" means the Lean 4 arc
|
||||||
|
and nothing else** (Q#GA4). Several standing-backlog items below are
|
||||||
|
sequenced by this arc rather than deferred — §2.5 of the framing maps
|
||||||
|
every one to a stage or leaves it here explicitly.
|
||||||
|
|
||||||
|
|
||||||
**Fixture project-detection census — NOT STARTED, follow-on from R8.**
|
**Fixture project-detection census — NOT STARTED, follow-on from R8.**
|
||||||
`tests/m4_acceptance.rs` alone constructs state through
|
`tests/m4_acceptance.rs` alone constructs state through
|
||||||
`EditorState::new_with_roots` **113 times**, and an unknown number of
|
`EditorState::new_with_roots` **113 times**, and an unknown number of
|
||||||
|
|
|
||||||
|
|
@ -801,7 +801,15 @@ only compare a red run against a *different* run. Here both selectors
|
||||||
ran green **inside the same gate invocation**, minutes earlier, on the
|
ran green **inside the same gate invocation**, minutes earlier, on the
|
||||||
same tree and machine — `03-lib` (1928 passed, 0 failed) and
|
same tree and machine — `03-lib` (1928 passed, 0 failed) and
|
||||||
`04-lib-crdt` (2113 passed, 0 failed) — and then failed in `11-sweep`.
|
`04-lib-crdt` (2113 passed, 0 failed) — and then failed in `11-sweep`.
|
||||||
Whatever this is, it is not the tree.
|
So this is **not deterministic on this tree; causation and any rate
|
||||||
|
effect are unresolved.**
|
||||||
|
|
||||||
|
*The original wording here was "whatever this is, it is not the tree",
|
||||||
|
which this file's own rerun rule forbids: a same-tree green establishes
|
||||||
|
**intermittence only**, and a tree can raise an intermittent failure
|
||||||
|
**rate** without making it deterministic. Same-tree greens cannot
|
||||||
|
exonerate the tree. Corrected rather than deleted, because the wrong
|
||||||
|
claim is the one a later reader would otherwise reach for.*
|
||||||
|
|
||||||
| field | value |
|
| field | value |
|
||||||
|---|---|
|
|---|---|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,973 @@
|
||||||
|
# The GUI arc — framing
|
||||||
|
|
||||||
|
**Status: revision 3 — APPROVED 2026-08-10.** Approved on its design;
|
||||||
|
the seven accuracy and process edits requested with the approval are
|
||||||
|
folded in, and no further review round is required before the Stage 0
|
||||||
|
branch.
|
||||||
|
|
||||||
|
**This document is itself the framing for Stage 0**, which is docs-only.
|
||||||
|
Revision 3's opening previously said that *every* stage gets its own
|
||||||
|
framing while §10 proceeded straight from this document into Stage 0 —
|
||||||
|
the two could not both be true. The rule, stated exactly:
|
||||||
|
|
||||||
|
- **Stage 0 is framed by this document.** No separate framing round; it
|
||||||
|
ships documentation only, and its scope is enumerated in §5.
|
||||||
|
- **Stages 1 through 10 each require their own framing**, approved
|
||||||
|
before that stage's branch, as the arc-level contract-ownership rule
|
||||||
|
demands. This document owns ordering, gates and the arc-level bar —
|
||||||
|
never a stage's acceptance criteria.
|
||||||
|
|
||||||
|
**Revision 3 answers the second review round (two blocking findings,
|
||||||
|
seven required corrections, and nine rulings that close Q#GA4–Q#GA12).**
|
||||||
|
|
||||||
|
- **The closure comparison is falsifiable now** (blocking №1). Revision
|
||||||
|
2's ordinal ordered grades but never said how to *assign* one, and
|
||||||
|
"normalize to the head grade" silently mis-graded compound rows: a
|
||||||
|
step reading "Works but undiscoverable" took `Works` from its first
|
||||||
|
word while the failing half became a non-blocking annotation — even
|
||||||
|
though **discoverability is the substance of steps 4, 7 and 11**, not
|
||||||
|
a qualifier on them. §3.3 now defines observable criteria for each
|
||||||
|
grade, grades a compound step by its **weakest required subclaim**,
|
||||||
|
and separates **local TUI / attached TUI / GPU** into three columns.
|
||||||
|
The comparison is GPU against the **local TUI** — the canonical
|
||||||
|
`pmacs .` journey — with the attached TUI retained as evidence,
|
||||||
|
because it is what distinguishes a semantic-wire gap from a
|
||||||
|
frontend-local one.
|
||||||
|
- **Half B's dependency graph was inverted, and is re-ordered**
|
||||||
|
(blocking №2). Viewport facts cannot be designed before the
|
||||||
|
multi-window model decides whether semantic windows are daemon
|
||||||
|
projections or frontend-local objects, because that decision
|
||||||
|
determines **the identity a viewport fact is *about***. And a
|
||||||
|
framing-only stage cannot hand the sidebar geometry it consumes. Half
|
||||||
|
B is now 6 model framing → 7 viewport/window-identity substrate
|
||||||
|
(with smooth scroll and the scrollbar) → 8 splits **plus implemented**
|
||||||
|
non-bottom side geometry → 9 sidebar riding Stage 8 → 10 tabs.
|
||||||
|
- **"The GPU consumes daemon `view_top`" was backwards.** The backlog
|
||||||
|
says the opposite in as many words — "the GPU **never** consumes
|
||||||
|
daemon `view_top`" (`docs/side-quest-backlog.md:147`). Corrected.
|
||||||
|
- **Stage 4b owns save *and* restore as a pair.** Q#DS9 scopes both to
|
||||||
|
local mode and makes **both** no-ops under a daemon, enforced in Rust
|
||||||
|
(`desktop-save-framing.md:222`). Revision 2's Q#GA10 claimed only the
|
||||||
|
restore *trigger* remained open; snapshot ownership, save timing and
|
||||||
|
multi-frontend arbitration remain open too.
|
||||||
|
- **The Bell audit was wrong.** The daemon already emits
|
||||||
|
`InstanceSignal::Bell` (`src/daemon.rs:1373`), at the audit anchor
|
||||||
|
`4bc55e8` as well; `src/frontend.rs:349` consumes it and `pmacs-gpu`
|
||||||
|
does not. Bell is **consumer-only** work for Stage 1c, with no
|
||||||
|
producer question to answer.
|
||||||
|
- **Stage 0 no longer contradicts the portability rule.** Revision 2
|
||||||
|
put the absorption PR *before* committing this framing, which leaves
|
||||||
|
the approved framing living only in one worktree. The approved
|
||||||
|
framing and the Stage 0 lane are now the branch's **first commit**;
|
||||||
|
if synchronization stays a separate PR, that PR carries the framing
|
||||||
|
first.
|
||||||
|
- **No orphan scorecard row.** A GUI-product grade needs criteria and
|
||||||
|
ground truth under `COHERENCE.md` §16 — a distinct **product**
|
||||||
|
subgrade beside the architectural one — before the scorecard can
|
||||||
|
point at it.
|
||||||
|
- **§2.5 stops overclaiming.** It said the arc sequences the whole GPU
|
||||||
|
backlog while items had neither stage nor deferral. Every item is now
|
||||||
|
mapped or explicitly left standing, in a table.
|
||||||
|
- **Q#GA8's temporary island is withdrawn** — ruled against, so this
|
||||||
|
arc adds **no** off-path hardcode at all (§7).
|
||||||
|
- **Reconnect attribution covers the silent cases** (§7). EOF and crash
|
||||||
|
may deliver no `Goodbye` at all; "the daemon's stated reason" alone
|
||||||
|
would have been unsatisfiable exactly when it matters.
|
||||||
|
|
||||||
|
**One correction this round found on its own**, not raised in review:
|
||||||
|
§3's condition 1 listed the Q#GA3 goals as "Stages 5, 6 and 10" while
|
||||||
|
§5 marked **four** stages as Q#GA3 goals — the sidebar was missing from
|
||||||
|
the closure sentence. Fixed, and re-checked against the renumbering.
|
||||||
|
|
||||||
|
**Origin.** A daily-driver report, 2026-08-09: *the TUI is a suitable
|
||||||
|
daily driver; the GUI feels behind similar editors, enough that the
|
||||||
|
reporter would default to a different editor before using pmacs' GUI.*
|
||||||
|
This is the same shape that opened the QoL arc (one daily-driver
|
||||||
|
report → an arc that closed when the report's complaints were answered
|
||||||
|
on both frontends), at a larger scale — so it gets a standard and an
|
||||||
|
arc-level frame before any lane, not a framing per gap.
|
||||||
|
|
||||||
|
**Ground truth below was established 2026-08-09** by a three-lane audit
|
||||||
|
(GPU frontend inventory, TUI/grid inventory, documentation sweep) at
|
||||||
|
`main` @ `4bc55e8`, with the §2.2 producer matrix re-verified against
|
||||||
|
`src/semantic_render.rs` at the same anchor. Per `COHERENCE.md`'s
|
||||||
|
citation convention: symbols first, `file:line` second; line numbers
|
||||||
|
drift, symbols are authoritative.
|
||||||
|
|
||||||
|
Three arc-shaping questions were put to the user and ruled on
|
||||||
|
2026-08-09; they are recorded as resolved, not open:
|
||||||
|
|
||||||
|
- **Q#GA1 — RESOLVED: one arc, two halves.** Half A is maturity
|
||||||
|
(input, parity, chrome, robustness, hover); Half B is structure
|
||||||
|
(viewport facts, splits/multi-window, side surfaces, and the
|
||||||
|
presentation stages that depend on them). The arc closes only when
|
||||||
|
**both** halves land (§3).
|
||||||
|
- **Q#GA2 — RESOLVED: the closure bar is journey parity plus an empty
|
||||||
|
blocker list**, now stated as a conjunction over the whole stage map
|
||||||
|
(§3).
|
||||||
|
- **Q#GA3 — RESOLVED: all four GUI-native affordances are goals, none
|
||||||
|
is a non-goal.** Hover/signature popups, smooth scroll + scrollbar,
|
||||||
|
and a project/files sidebar are in-scope goals. **Tabs/tabline is a
|
||||||
|
declared goal at deliberately low priority, sequenced last** — the
|
||||||
|
user's ruling verbatim: it is not a non-goal and "would be nice down
|
||||||
|
the line," with care required. What the care means is Q#GA12 plus
|
||||||
|
the anti-patterns pinned at Stage 10.
|
||||||
|
|
||||||
|
Coherence sections this framing serves: §2 (the journey, which Stage 0
|
||||||
|
makes frontend-graded), §3 (the recommended default surface for the
|
||||||
|
graphical frontend), §6 (interaction islands — see §7's accounting),
|
||||||
|
**§7 (first-class workspaces — two stages are gated on it, §5.1)**,
|
||||||
|
§14 (workbench primitives — the sidebar is tree-primitive adoption),
|
||||||
|
§16 (the semantic-frontend grade this arc completes the product half
|
||||||
|
of), §20 (priority order — placement is Q#GA5).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Why an arc: the diagnosis
|
||||||
|
|
||||||
|
**The GPU frontend is a rendering showcase over a single buffer, not a
|
||||||
|
workbench.** It is genuinely ahead of the TUI on rendering richness —
|
||||||
|
a real minibuffer candidate dropdown where the grid has only an inline
|
||||||
|
`[candidate]` suffix, a minimap, inline math, pixel-precise squiggles,
|
||||||
|
correct grapheme shaping where the grid drops combining marks in body
|
||||||
|
text — and behind on the three things that make an editor a daily
|
||||||
|
driver. Those three are the arc's spine:
|
||||||
|
|
||||||
|
1. **The one-window ceiling.** `State` holds exactly one `buffer` and
|
||||||
|
one `current_buffer_id`; the daemon's entire per-frontend split
|
||||||
|
layout (`Layout::compute`, `core.views`) is invisible to a semantic
|
||||||
|
session. The bottom panel band (`PanelBand`) is a hand-built special
|
||||||
|
case of "a second region." Everything spatial queues behind the
|
||||||
|
general version: splits, side windows beyond `Side::Bottom`, the
|
||||||
|
project/files surface `COHERENCE.md` §3 names, per-window status
|
||||||
|
bands. The July roadmap called this "the largest unscoped design
|
||||||
|
problem" and it still is.
|
||||||
|
2. **The GUI cannot be driven like a GUI.** `keymap_stack::Scope` has
|
||||||
|
no frontend identity and `FrontendEvent` has no command-invocation
|
||||||
|
variant, so a GPU-only binding cannot exist (the reason #220 shipped
|
||||||
|
zoom as unbound commands — handoff §6's capability-aware keymap
|
||||||
|
item). Beneath that, `translate_key` and the winit handler consume
|
||||||
|
a narrow slice of desktop input; the rest lands in `_ => {}` (§2.3).
|
||||||
|
3. **The monolith has no test seam for input.** `pmacs-gpu/src/main.rs`
|
||||||
|
is ~11.7k production lines in one file; the
|
||||||
|
`gpu-terminal-input` framing already recorded that GPU key routing
|
||||||
|
is untestable because `App::window_event`'s logic is inline "with no
|
||||||
|
extractable seam," and called the refactor "a real refactor
|
||||||
|
[belonging] to its own lane." An input arc that skips the seam ships
|
||||||
|
blind.
|
||||||
|
|
||||||
|
**Why the standard never caught this drifting.** `COHERENCE.md` §16
|
||||||
|
grades the *semantic protocol* — degradation practiced, capability
|
||||||
|
negotiation, versioning — and that grade (Strong) is earned. But **no
|
||||||
|
scorecard row measures the GUI as a product**, and **the golden journey
|
||||||
|
has only ever been graded on the TUI**. "Semantic frontend: Strong" and
|
||||||
|
"I'd use a different editor before the GUI" stayed simultaneously true
|
||||||
|
because the standard only measured the first. This is §1.1's
|
||||||
|
substrate-without-surface at frontend scale: the substrate is the
|
||||||
|
protocol and the daemon's facts; the missing surface is the GPU
|
||||||
|
consumers of them (§2.2 below shows which halves exist). Stage 0
|
||||||
|
closes the measurement gap so it cannot reopen.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Ground truth (audited 2026-08-09)
|
||||||
|
|
||||||
|
### 2.1 What the GPU has
|
||||||
|
|
||||||
|
Code area with syntax/LSP styling, gutter (Off/Absolute/Relative/
|
||||||
|
Hybrid) with diagnostic signs, minimap with click/scrub, one bottom
|
||||||
|
status band (statusline segments validated and themed), bottom panel
|
||||||
|
band with divider drag, minibuffer with a 10-row candidate dropdown,
|
||||||
|
in-buffer completion popup with kind glyphs, right-click context menu,
|
||||||
|
isearch band UI, diagnostic squiggles (dedicated pipeline), selection
|
||||||
|
and search washes, peer presence (cursors + selections), inline math,
|
||||||
|
terminal mode, optimistic CRDT editing with unconfirmed-edit
|
||||||
|
journaling. Mouse: click, drag, double/triple-click, wheel (line-
|
||||||
|
quantized), edge auto-scroll, panel and divider gestures, minimap
|
||||||
|
scrub. Clipboard both directions via `arboard`.
|
||||||
|
|
||||||
|
### 2.2 Wire-capability matrix: produced vs consumed
|
||||||
|
|
||||||
|
The GPU's live-loop catch-all is one `_ => None` (`main.rs:5211`);
|
||||||
|
`FoldState` and `BlockAdornments` appear in `pmacs-gpu` **only** in a
|
||||||
|
debug-name helper. But "the GPU ignores it" means different work
|
||||||
|
depending on whether a producer exists — revision 1 conflated these,
|
||||||
|
and three items hid producer scope. Producer column verified against
|
||||||
|
`src/semantic_render.rs` at `4bc55e8`:
|
||||||
|
|
||||||
|
| Capability | Produced? | GPU consumes? | Work required |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `FoldState` | **Yes** (`semantic_render.rs:1881`) | No | **Consumer-only** — Stage 3a |
|
||||||
|
| `InlineAdornments` | Only `(AtOffset, Text)`, from the inlay-hint store (`semantic_render.rs:1849`) | Exactly that same subset | **No live gap today.** Other placements/content are producer *and* consumer work; not claimed by this arc |
|
||||||
|
| `BlockAdornments` | **No** — a producer test asserts none is emitted (`semantic_render.rs:4543`) | No | Producer + consumer; Stage 3a's framing decides whether GPU folding renders from `FoldState` alone or needs placeholders |
|
||||||
|
| `ResourceOffer` / `AdornmentContent::Resource` | **No** | No | Producer + consumer; stays deferred (§6) |
|
||||||
|
| `InstanceSignal::Title` | **No producer found** in `src/` | No | **Not needed for a dynamic window title** — `StatusFacts` already carries the buffer name, so Stage 1c titles the window frontend-locally; a `Title` producer (e.g. terminal-set titles) is separate, unclaimed work |
|
||||||
|
| `InstanceSignal::Bell` | **Yes** (`daemon.rs:1373`, present at `4bc55e8`) | No — `frontend.rs:349` is the grid consumer; `pmacs-gpu` has no arm | **Consumer-only** — Stage 1c. *Revision 2 recorded "no producer found" and gave Stage 1c a producer question to answer; the producer was there the whole time, and the audit had searched the semantic-render path rather than the daemon's signal path.* |
|
||||||
|
| `Goodbye(reason)` post-handshake | Yes | Bootstrap only; live-loop reason discarded | **Consumer-only** — Stage 1c |
|
||||||
|
| `CompletionPopup.prefix_len`/`total` | Yes (on the wire) | Stored under `#[allow(dead_code)]`, unrendered | **Consumer-only** — minibuffer/completion refinement |
|
||||||
|
|
||||||
|
### 2.3 Input gaps (verified in-session, not carried from docs)
|
||||||
|
|
||||||
|
- **Escape quits the entire application** when no intercept/popup is
|
||||||
|
active (`main.rs:2769`; the comment says "otherwise it stays the
|
||||||
|
local quit"). No data is lost — the daemon holds state — but it
|
||||||
|
reads as a crash to anyone with Escape reflexes.
|
||||||
|
- `translate_key` produces **no `ProtocolKey::F(u8)`** — F1–F12 are
|
||||||
|
unbindable in the GUI though the protocol carries them. `BackTab`,
|
||||||
|
`Menu` also unmapped; `Key::Dead(_) → None` (dead keys silently
|
||||||
|
dropped); multi-codepoint `Key::Character` truncated to its first
|
||||||
|
char.
|
||||||
|
- **No `WindowEvent::Ime`, no `set_ime_allowed`** — CJK/compose input
|
||||||
|
is unusable. Undocumented anywhere before this audit.
|
||||||
|
- **No `ScaleFactorChanged` arm; `scale: 1.0` hardcoded** — HiDPI is
|
||||||
|
wrong (also recorded as a pre-existing gap in
|
||||||
|
`gpu-set-font-framing.md`).
|
||||||
|
- Sub-line wheel deltas are rounded then discarded with **no residual
|
||||||
|
accumulator** — precise-pixel trackpad scroll under ~½ line height
|
||||||
|
does nothing. Horizontal wheel x is discarded although
|
||||||
|
`code_scroll_left` exists; `MouseKind::ScrollLeft/Right` are never
|
||||||
|
emitted. Ctrl+wheel is ignored.
|
||||||
|
- No middle-click paste, no `DroppedFile`, no I-beam cursor over text,
|
||||||
|
minibuffer dropdown not clickable (audit F-007).
|
||||||
|
- **`FrontendEvent::FocusGained`/`FocusLost`/`Detach` are never
|
||||||
|
sent** — no `Focused` arm; `CloseRequested` exits without `Detach`,
|
||||||
|
so the daemon learns of departure by socket EOF.
|
||||||
|
|
||||||
|
### 2.4 Structure, robustness, chrome
|
||||||
|
|
||||||
|
- One document window forever (§1 cause 1). Daemon splits invisible.
|
||||||
|
- No auto-reconnect; the reconnect banner is an attach-TUI-only seam
|
||||||
|
(`Frontend::draw_status_overlay`). F-008 in
|
||||||
|
`gpu-attach-robustness-framing.md`.
|
||||||
|
- **Session restore is structurally never**: desktop save/restore
|
||||||
|
early-returns in Rust under a daemon (Q#DS9), and the GPU is always
|
||||||
|
semantic — so a GPU session can never restore. Journey step 12's
|
||||||
|
thin end, at its thinnest on this frontend.
|
||||||
|
- Chrome theming is half-applied: `MENU_BG`, completion popup
|
||||||
|
background, `MINIMAP_BG`, `CARET_COLOR`, `WINDOW_BG_RGBA`, and the
|
||||||
|
peer-presence palette are hardcoded constants; custom themes
|
||||||
|
fracture in the GUI. (The TUI's completion popup and menu are also
|
||||||
|
unthemed `Indexed` constants — the pair should be fixed together,
|
||||||
|
per no-privileged-frontend.)
|
||||||
|
- Cursor: fixed 2px bar, fixed color, no blink, no styles.
|
||||||
|
- Word wrap regressed at #221: the GPU had cosmic-text
|
||||||
|
`WordOrGlyph` since it existed and now gets `Wrap::Glyph`; the
|
||||||
|
long-lines framing already names `ui.line-wrap = "word"` as the
|
||||||
|
clean additive third value.
|
||||||
|
- LSP styling diverges by model: the grid **merges** LSP tokens over
|
||||||
|
tree-sitter (`LspStyleView`/`merge_styles`); the semantic wire is
|
||||||
|
single-authority — GUI highlighting is strictly poorer in
|
||||||
|
mixed-authority languages.
|
||||||
|
- `HoverView` and `SignatureView` exist in the core, **built and never
|
||||||
|
attached anywhere** (§1.1 dark matter) — relevant to Stage 5.
|
||||||
|
|
||||||
|
### 2.5 Already-recorded backlog: mapped or explicitly left standing
|
||||||
|
|
||||||
|
Revision 2 said this arc "sequences" `docs/side-quest-backlog.md`
|
||||||
|
§"GPU frontend mechanics (non-theme)" without restating it, which
|
||||||
|
claimed coverage it did not have — several items had no stage *and* no
|
||||||
|
deferral, and a reader checking whether the arc covered their complaint
|
||||||
|
had nothing to check. **Every item in that section is below. An item is
|
||||||
|
either mapped to a stage or explicitly left in the standing backlog;
|
||||||
|
there is no third state.** Handoff §6's capability-aware keymap item
|
||||||
|
and the folding framings' Stage 3 obligations are absorbed **by
|
||||||
|
reference** — each keeps its own framing.
|
||||||
|
|
||||||
|
| Backlog item | Disposition |
|
||||||
|
|---|---|
|
||||||
|
| Command/minibuffer chord forwarding; Meta/Super chords | Stage 1a |
|
||||||
|
| Rebindable local `Ctrl-V`/`Escape` | Escape half → Stage 1a; `Ctrl-V` half → Stage 2 (it is a keymap-vocabulary question, not an input-plumbing one) |
|
||||||
|
| Middle-click paste | Stage 1b |
|
||||||
|
| Right-click context menu | **Already shipped** (§2.1) — the backlog item is stale and Stage 0 retires the line |
|
||||||
|
| Frontend-local provisional selection | **Standing backlog** — a selection-ownership question, not a GUI-maturity gap |
|
||||||
|
| Minibuffer `i/total` hint (= `CompletionPopup.prefix_len`/`total`) | Stage 3d |
|
||||||
|
| Clickable minibuffer dropdown rows (audit F-007) | Stage 3d |
|
||||||
|
| Multibyte-exact band caret; nav highlight-wrap bug | Stage 3d |
|
||||||
|
| Telescope-style preview pane; candidate kind/doc annotations; unify TUI inline vs GPU dropdown | **Standing backlog** — the unification is a cross-frontend convergence design, and the other two ride it |
|
||||||
|
| Scrollbar scroll; pixel-smooth sub-line scroll | Stage 7 (the *discard* bug is Stage 1b; the smooth **model** needs Stage 7's facts) |
|
||||||
|
| Horizontal scroll / soft-wrap | wheel → Stage 1b; wrap → Stage 3b |
|
||||||
|
| Auto-reconnect + "reconnecting…" banner | Stage 4a |
|
||||||
|
| `AttachRequest.initial_size` cell-grid assumption | Stage 1c, with DPI — the assumption is only visible once scale is real |
|
||||||
|
| Capability renegotiation (relaunch daemon `--features crdt`) | **Standing backlog** — daemon lifecycle, not frontend maturity |
|
||||||
|
| Peer caret glyph + name label; own-vs-peer cursor merge; `SelectionSnapshot` vs `Decorations::Selection`; background-kind decorations painted | **Standing backlog** — collaboration/decoration rendering; no journey step and no §3.1 blocker depends on them |
|
||||||
|
| Inline adornment placements beyond `AtOffset` | **Standing backlog** — producer *and* consumer scope (§2.2) |
|
||||||
|
| Glyphon full-buffer `prepare` ceiling; `Renderer` sub-struct extraction | **Standing backlog** — perf and refactor; the `main.rs` split's first slice is Stage 1-pre and claims no more |
|
||||||
|
| Golden-PNG comparison harness | **Deferred by §8**, with its condition stated there |
|
||||||
|
|
||||||
|
Three gaps from §2.3/§2.4 are not in that backlog section and are
|
||||||
|
mapped here so they cannot fall through: **`DroppedFile`** → Stage 1b;
|
||||||
|
**cursor blink and styles** → Stage 3c (§7 registers the knob, and
|
||||||
|
Stage 3c is what ships it — revision 2 named the configuration with no
|
||||||
|
stage behind it); **the LSP merge-vs-single-authority divergence**
|
||||||
|
(§2.4) → **standing backlog**, explicitly, because it is a semantic-wire
|
||||||
|
authority question whose fix belongs to whoever owns multi-server token
|
||||||
|
policy, not to a GUI maturity stage.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Closure criterion (Q#GA2)
|
||||||
|
|
||||||
|
**The arc closes when all three of the following hold; none alone is
|
||||||
|
sufficient:**
|
||||||
|
|
||||||
|
1. **Every stage in §5 has landed** — or has been explicitly re-ruled
|
||||||
|
by the user at the time, with the ruling and its reason recorded in
|
||||||
|
§3.2. There is no stage outside the closure contract: revision 1's
|
||||||
|
"Tail" is dissolved, and the Q#GA3 goals are Stages **5, 7, 9 and
|
||||||
|
10** (hover/signature; smooth scroll + scrollbar; the project/files
|
||||||
|
sidebar; tabs). *Revision 2's sentence said "Stages 5, 6 and 10"
|
||||||
|
while §5 marked four stages as Q#GA3 goals — the sidebar was absent
|
||||||
|
from the closure sentence that is supposed to enumerate them.*
|
||||||
|
2. **The per-frontend journey table shows GPU ≥ local TUI at every
|
||||||
|
step**, under the grading rules in §3.3.
|
||||||
|
3. **The daily-driver blocker list (§3.1) is empty.**
|
||||||
|
|
||||||
|
Divergences that survive must be declared in §3.2, not accidental.
|
||||||
|
|
||||||
|
### 3.1 Blocker list (seed — membership is Q#GA11)
|
||||||
|
|
||||||
|
1. Escape quits the application (§2.3).
|
||||||
|
2. IME absent — CJK/compose input unusable.
|
||||||
|
3. `translate_key` holes: F-keys, BackTab, dead keys, multi-codepoint
|
||||||
|
text.
|
||||||
|
4. Sub-line scroll discard (trackpad feels broken); no horizontal
|
||||||
|
wheel.
|
||||||
|
5. No DPI/scale handling.
|
||||||
|
6. Folding silently dead on the GPU.
|
||||||
|
7. No session restore on the GPU, ever (Q#DS9).
|
||||||
|
8. No reconnect after daemon restart.
|
||||||
|
9. One-window ceiling (graded via the journey table's affected steps
|
||||||
|
rather than as a single line — listed here so the list cannot be
|
||||||
|
emptied while the ceiling stands).
|
||||||
|
|
||||||
|
### 3.2 Declared-divergence and re-ruling register
|
||||||
|
|
||||||
|
Divergences that survive the arc, and any stage the user re-rules out
|
||||||
|
of the closure contract, are recorded here with a reason (the model is
|
||||||
|
#221's honest-divergence ruling on word wrap). Seed: none — entries
|
||||||
|
are added by stage framings or user rulings as they happen.
|
||||||
|
|
||||||
|
### 3.3 How a (step, frontend) cell is graded
|
||||||
|
|
||||||
|
`COHERENCE.md` §2's existing verdicts are compound strings ("Works but
|
||||||
|
undiscoverable", "Partial (good once reached)") and do not order.
|
||||||
|
Revision 2 replaced them with an ordered set but never said how a cell
|
||||||
|
*acquires* a grade, and its normalization rule — take the head grade,
|
||||||
|
demote the rest to annotation — is unsound in the exact case it was
|
||||||
|
written for: **"Works but undiscoverable" would grade `Works`**, and the
|
||||||
|
undiscoverability would become prose that cannot block closure. That
|
||||||
|
inverts the standard, because discoverability is not a qualifier on
|
||||||
|
journey steps 4, 7 and 11 — it *is* their substance.
|
||||||
|
|
||||||
|
**Three columns, not two.** Stage 0 grades each step for **local TUI**
|
||||||
|
(`pmacs .`), **attached TUI** (`pmacs --attach`) and **GPU**
|
||||||
|
separately.
|
||||||
|
|
||||||
|
**The comparison is GPU against the local TUI.** That is the canonical
|
||||||
|
`pmacs .` journey and the frontend the daily-driver report calls
|
||||||
|
suitable, so it is the bar the GUI must meet.
|
||||||
|
|
||||||
|
**The attached TUI column is retained as evidence, not as the bar — and
|
||||||
|
what it is evidence *of* is narrower than revision 3 first claimed.**
|
||||||
|
The attached TUI is a **grid** frontend: it handshakes
|
||||||
|
`semantic_render: false`, and the field's own comment says it "never
|
||||||
|
consumes the SemanticFrame family" (`src/attach.rs`, the
|
||||||
|
`FrontendCapabilities` constructor). So a shared GPU/attached-TUI gap
|
||||||
|
cannot mean "the semantic wire is at fault" — the attached TUI is not
|
||||||
|
on that wire. What the three columns actually separate is:
|
||||||
|
|
||||||
|
- **local vs daemon-attached** behaviour (local TUI against attached
|
||||||
|
TUI), which isolates everything the daemon boundary introduces; and
|
||||||
|
- **attached-grid vs semantic/GPU** behaviour (attached TUI against
|
||||||
|
GPU), which isolates what is specific to semantic rendering.
|
||||||
|
|
||||||
|
**Neither comparison alone establishes producer-versus-consumer
|
||||||
|
ownership.** Reading the columns narrows where to look; **source
|
||||||
|
tracing is what assigns the gap**, exactly as §2.2's matrix had to be
|
||||||
|
verified against `src/semantic_render.rs` rather than inferred from
|
||||||
|
behaviour. A single TUI column would still have merged two distinct
|
||||||
|
diagnoses — that argument survives — but it was never going to hand out
|
||||||
|
owners for free.
|
||||||
|
|
||||||
|
**The grades, by observable criteria.** Each is a test someone else can
|
||||||
|
run and get the same answer:
|
||||||
|
|
||||||
|
> **Broken < Missing < Partial < Works**
|
||||||
|
|
||||||
|
- **Works** — every required subclaim holds with no qualifier, by a
|
||||||
|
route the step's own discoverability subclaim admits.
|
||||||
|
- **Partial** — every required subclaim is *satisfiable*, but at least
|
||||||
|
one is degraded: reachable only by a route the step does not admit
|
||||||
|
(e.g. only by typing an unlisted command), or holding only under a
|
||||||
|
stated precondition.
|
||||||
|
- **Missing** — a required subclaim has **no surface at all**: the
|
||||||
|
action is unavailable and attempting it produces neither effect nor
|
||||||
|
error.
|
||||||
|
- **Broken** — a surface exists and using it produces a **wrong
|
||||||
|
result**, data loss, or an application-level failure. Ranked *below*
|
||||||
|
`Missing` deliberately: an absent feature is honest, while a present
|
||||||
|
one that misleads costs the user work and trust.
|
||||||
|
|
||||||
|
**A compound step is graded by its weakest required subclaim.** Each
|
||||||
|
step in the table declares its required subclaims explicitly; the cell's
|
||||||
|
grade is the **minimum** over them, never the first word of a prose
|
||||||
|
verdict.
|
||||||
|
|
||||||
|
**The worked examples below are HISTORICAL, and `COHERENCE.md` §2b is
|
||||||
|
authoritative for every current grade.** They are kept because they are
|
||||||
|
what motivated this rule, and they are marked because a framing and the
|
||||||
|
standard it serves must not hand a reader two different grades for one
|
||||||
|
step. They quote the **pre-Stage-0** verdict strings, not today's table:
|
||||||
|
|
||||||
|
- **Step 7** — the row then read "Symbol: **works but
|
||||||
|
undiscoverable**". Subclaims *reachable* / *discoverable*: `Works` and
|
||||||
|
`Missing` ⇒ **`Missing`**. **Stage 0 graded it `Works`** on evidence
|
||||||
|
this example did not have: advertisement is transitive through the
|
||||||
|
help graph, and the advertised `M-x help` route reaches
|
||||||
|
`help.list-keybindings`, which names every registered binding.
|
||||||
|
- **Step 11** — the row then read "**Works but undiscoverable**". Same
|
||||||
|
shape, and here the outcome **stands**: Stage 0 also graded it
|
||||||
|
`Missing`, because **no binding opens `editor.list-workers`** for a
|
||||||
|
listing to name. The view does carry a buffer-local `C-c C-k`
|
||||||
|
(`workers.cancel-at-point`), which is reachable only once you are
|
||||||
|
already inside it.
|
||||||
|
|
||||||
|
The rule these examples exist to establish is untouched by either
|
||||||
|
outcome: **the head-grade rule would have graded both `Works`**, and
|
||||||
|
that is what makes it unsound. Step 7 moving on better evidence is the
|
||||||
|
system working; step 11 not moving is the defect surviving contact with
|
||||||
|
it.
|
||||||
|
|
||||||
|
Under revision 2's head-grade rule **both would have graded `Works`**,
|
||||||
|
and the undiscoverability that is the entire finding would have become
|
||||||
|
annotation text with no effect on closure. Two of the journey's twelve
|
||||||
|
steps is not an edge case.
|
||||||
|
|
||||||
|
**Annotations carry only what is not a required subclaim.** They cannot
|
||||||
|
absorb a failing subclaim; if something is load-bearing enough to
|
||||||
|
mention as a defect, it is load-bearing enough to be a subclaim and be
|
||||||
|
graded. Where the two frontends differ only in an annotation, the
|
||||||
|
difference is recorded and does not block closure — that remains true,
|
||||||
|
and is now narrow rather than a loophole.
|
||||||
|
|
||||||
|
**Stage 0 must publish the subclaim list per step**, not just the
|
||||||
|
grades. A grade whose subclaims are unstated is not falsifiable, which
|
||||||
|
is the whole objection this section answers.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Arc structure (Q#GA1)
|
||||||
|
|
||||||
|
**One arc, two halves; the name is "the GUI arc," deliberately a name
|
||||||
|
and not a number.** The roadmap's "Arc 8 — GPU structural parity"
|
||||||
|
label already collides (the Lean 4 framing also claims Arc 8; the
|
||||||
|
collision is recorded in `docs/dired-framing.md`). This arc subsumes
|
||||||
|
roadmap-Arc-8's scope as its Half B; the numeric label retires.
|
||||||
|
|
||||||
|
- **Half A — maturity** (Stages 0–5): the GPU behaves like a competent
|
||||||
|
desktop application over its existing one-window model. No
|
||||||
|
structural redesign; heavy protocol work only where §2.2 shows a
|
||||||
|
producer already exists, or the stage's framing names the producer
|
||||||
|
scope it adds.
|
||||||
|
- **Half B — structure** (Stages 6–10): the multi-window model, then
|
||||||
|
the viewport/window-identity substrate it defines, then splits and
|
||||||
|
side geometry, then the presentation stages that depend on
|
||||||
|
multi-window state (sidebar, tabs). The order is load-bearing — see
|
||||||
|
the note opening Half B in §5.
|
||||||
|
|
||||||
|
Half A ships visible value while Half B's model framing matures; the
|
||||||
|
arc does not close at the end of Half A (§3's condition 1 spans both
|
||||||
|
halves), so the early wins cannot quietly become the whole arc.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Stage map
|
||||||
|
|
||||||
|
**Stages 1–10 each get their own framing before their branch; Stage 0
|
||||||
|
is framed by this document** (see the status block). This document owns
|
||||||
|
the ordering rationale and the arc-level bar, never stage-level
|
||||||
|
acceptance criteria (the contract-ownership rule). **Every PR in this
|
||||||
|
arc opens with its `docs/active-work.md` lane written at the branch's
|
||||||
|
first commit** — the standing correction from #171/#215, missed again
|
||||||
|
at #224 and #225, and adopted here as an arc rule rather than re-hoped.
|
||||||
|
|
||||||
|
### 5.1 The P2 gate (blocking №2's resolution)
|
||||||
|
|
||||||
|
Two stages are **workspace-owned** and carry a hard gate: they may not
|
||||||
|
start before the P2 workspace arc has landed at least the workspace
|
||||||
|
object they consume.
|
||||||
|
|
||||||
|
- **Stage 4b (session save *and* restore)**: "what a session *is*" is
|
||||||
|
the workspace question — Q#DS9 failed precisely because a daemon
|
||||||
|
layout had "nothing principled to attach to" (`COHERENCE.md` §7). A
|
||||||
|
frontend-keyed convention invented here would be a new ownership
|
||||||
|
story P2 then has to unwind; revision 1 called that v1 "plausible",
|
||||||
|
revision 2 withdrew the recommendation, and Q#GA10 is now **ruled**
|
||||||
|
(both surfaces preserved, the save path owned here too). The gate is
|
||||||
|
what makes the ruling implementable: snapshot ownership and
|
||||||
|
multi-frontend arbitration have no answer without P2's object.
|
||||||
|
- **Stage 9 (project/files sidebar)**: a sidebar must show *something
|
||||||
|
rooted*, and §7 warns P2 must start "before a fifth subsystem grows
|
||||||
|
its own root convention — four have already diverged." The sidebar
|
||||||
|
is the fifth if it picks its own root.
|
||||||
|
|
||||||
|
**Reaching Stage 4b is a P2 START GATE, not merely a pause** (Q#GA5
|
||||||
|
ruling, revision 3). Revision 2 let the gated stage stall while
|
||||||
|
everything else proceeded. **It could not have let the arc formally
|
||||||
|
close around P2** — the gated stages are inside the closure contract
|
||||||
|
(§3, condition 1), so closure still blocked on them. What it *would*
|
||||||
|
have allowed is every **non-gated** stage finishing before P2 began,
|
||||||
|
leaving P2 as a **terminal closure blocker**: an arc sitting at 100%
|
||||||
|
of the work it could do, waiting on an arc nobody had started. The rule
|
||||||
|
is stronger:
|
||||||
|
|
||||||
|
1. When the arc reaches Stage 4b, **P2 starts**. That is the trigger.
|
||||||
|
2. **No later GUI stage starts** — gated or not — until P2 has **an
|
||||||
|
approved framing and an opened lane**. Those two are the observable
|
||||||
|
condition; P2 need not have *landed* anything.
|
||||||
|
3. Once P2 has both, **non-gated GUI work may interleave** freely while
|
||||||
|
the workspace object lands. Only the two gated stages (4b, 9) wait
|
||||||
|
on the object itself.
|
||||||
|
|
||||||
|
The gate is on *starting P2*, not on P2's completion, so the arc is
|
||||||
|
never blocked on work nobody has begun — and it cannot outrun the
|
||||||
|
model it depends on. A gated stage never proceeds on a local
|
||||||
|
convention; that was already true and stays true. The arc's
|
||||||
|
`docs/active-work.md` lane records the gate state whenever it is in
|
||||||
|
force.
|
||||||
|
|
||||||
|
### Half A — maturity
|
||||||
|
|
||||||
|
**Stage 0 — the standard sees the GUI (docs only).**
|
||||||
|
|
||||||
|
*The framing goes first, and that reverses revision 2's ordering.*
|
||||||
|
Revision 2 put the absorption PR ahead of committing this document,
|
||||||
|
which contradicts the portability rule it cites elsewhere: an approved
|
||||||
|
framing that lives only in one worktree is one `git clean` from gone
|
||||||
|
and does not travel to another machine. **The approved framing and this
|
||||||
|
arc's `docs/active-work.md` lane are the Stage 0 branch's first
|
||||||
|
commit.** If synchronization stays a separate PR, **that PR carries the
|
||||||
|
framing first** — absorption may precede the rest of Stage 0, never the
|
||||||
|
framing.
|
||||||
|
|
||||||
|
*The absorption pass*, whose scope is now enumerated rather than
|
||||||
|
described (it grew on 2026-08-10 when six lanes merged in one session):
|
||||||
|
|
||||||
|
- **Five stale lanes in `docs/active-work.md`** — #224 and #225 carried
|
||||||
|
as OPEN, #228 as OPEN and MERGE-BLOCKED, LSP LaTeX as "no PR yet"
|
||||||
|
(merged as #230), destination capture as "PR #231 OPEN" (merged as
|
||||||
|
`0e4c58d`). Durable facts into `docs/agent-handoff.md` first, then
|
||||||
|
remove the **PR-specific** block.
|
||||||
|
|
||||||
|
**#228 is the exception, and it must not be retired "per Rule 4" as
|
||||||
|
though Discovery were finished.** Rule 4 removes a lane when its
|
||||||
|
**arc** is done; Discovery's is not. Two entries exist — the
|
||||||
|
PR-specific block and the standing **Discovery lane (P4)**, which
|
||||||
|
already says "Rewritten, not removed" for exactly this reason. Stage
|
||||||
|
0 removes the first after re-homing its facts and **rewrites and
|
||||||
|
coalesces the second** to *"Stage 2 merged; later discovery work
|
||||||
|
remains"*. Still open there: **predicate evaluation**, **command
|
||||||
|
metadata** (title/category/aliases/flags), **help unification**, and
|
||||||
|
**the prefix decision**. Deleting that lane would drop four named
|
||||||
|
pieces of open work on the strength of one merged stage.
|
||||||
|
- **The authority/recovery anchor**, which points at `9a26ac8` while
|
||||||
|
`main` has moved well past the audit anchor `4bc55e8`.
|
||||||
|
- **`COHERENCE.md` §0 row 16 / §16's `v6..=v21` → `v6..=v23`.** The
|
||||||
|
ceiling moved **twice**: #221 took it to v22 for `LineWrapFacts`, and
|
||||||
|
**#228 took it to v23** for `MinibufferPromptRows`
|
||||||
|
(`PROTOCOL_VERSION = 23`, `SUPPORTED_PROTOCOL_VERSIONS = 6..=23`,
|
||||||
|
`pmacs-protocol/src/message.rs:1843`). *Revisions 2 and 3 both said
|
||||||
|
v22, having read the range at the audit anchor and not re-read it
|
||||||
|
after Discovery landed.* The same row's "production attach remains
|
||||||
|
v20" is **still correct** — `ADVERTISED_PROTOCOL_VERSION` is 20 — and
|
||||||
|
must not be swept along with the range.
|
||||||
|
- **The U4 correction and the U9 residue** in
|
||||||
|
`docs/ci-red-signatures.md`. U4's flavour field is not a matching key
|
||||||
|
(the same selector and fragments red on both macOS flavours) and one
|
||||||
|
of its four "occurrences" was a deliberate bite. **U9's text must be
|
||||||
|
fixed, not merely carried**: it says a same-tree green shows the
|
||||||
|
failure "is not the tree," which contradicts this file's own rerun
|
||||||
|
rule — a tree can raise an intermittent failure *rate* without making
|
||||||
|
it deterministic. The replacement claim is **"not deterministic on
|
||||||
|
this tree; causation and rate effect unresolved."**
|
||||||
|
- **The stale backlog line** for the right-click context menu, which
|
||||||
|
ships (§2.5).
|
||||||
|
- **Journey step 11's verdict**, which #232 falsified on 2026-08-10.
|
||||||
|
`COHERENCE.md` §2 still reads "**Works but undiscoverable** … no
|
||||||
|
keybinding, no statusline spinner/progress indicator anywhere (§9)".
|
||||||
|
#232 shipped exactly that indicator — a statusline provider showing
|
||||||
|
an in-flight count and the oldest job's purpose, absent when idle.
|
||||||
|
The row needs regrading under §3.3, and §9's own grade needs
|
||||||
|
re-reading: the mechanism-without-identity finding is partly
|
||||||
|
answered. **Found while grading the journey for this revision, not
|
||||||
|
in review** — which is the argument for §3.3's three columns, since
|
||||||
|
a stale row survives precisely as long as nobody has to assign it a
|
||||||
|
falsifiable grade.
|
||||||
|
|
||||||
|
*Then Stage 0 proper:* add the per-frontend journey verdicts to
|
||||||
|
`COHERENCE.md` §2 under §3.3's grading rules, **including the subclaim
|
||||||
|
list per step**; place the arc in §20 (Q#GA5); cross-reference from
|
||||||
|
`docs/agent-handoff.md` §6; retire the "Arc 8" numbering (Q#GA4). No
|
||||||
|
runtime code.
|
||||||
|
|
||||||
|
**No orphan scorecard row.** Revision 2 proposed adding a GUI-product
|
||||||
|
row to the scorecard with a grade attached ("Weak — renderer ahead,
|
||||||
|
workbench and input behind"). A scorecard row is a pointer to a graded
|
||||||
|
concern, and there is no graded concern for the GUI *as a product*:
|
||||||
|
`COHERENCE.md` §16's grade is architectural. So Stage 0 first
|
||||||
|
establishes **a distinct product subgrade beside the architectural one
|
||||||
|
in §16**, with its own criteria and audited ground truth, and *then*
|
||||||
|
the scorecard points at it. A row whose grade rests on nothing is the
|
||||||
|
thing §16 exists to prevent.
|
||||||
|
|
||||||
|
**Stage 1 — input foundation.**
|
||||||
|
|
||||||
|
- **1-pre: the input seam.** Extract `App::window_event`'s routing
|
||||||
|
into testable functions — the refactor `gpu-terminal-input` already
|
||||||
|
named as its own lane, plus the first slice of the recorded
|
||||||
|
`main.rs` split. This is the stage's first PR because everything
|
||||||
|
after it needs witnesses.
|
||||||
|
- **1a keyboard correctness**: Escape stops being the local quit
|
||||||
|
(round-trips like any key; quitting becomes a command/window
|
||||||
|
affordance — subsumes the backlog's "rebindable local Ctrl-V/Escape"
|
||||||
|
item on the Escape half); `translate_key` completion (`F(u8)`,
|
||||||
|
`BackTab`, `Menu`; dead keys held for 1d rather than dropped).
|
||||||
|
- **1b pointer/scroll correctness**: sub-line residual accumulator;
|
||||||
|
horizontal wheel → `code_scroll_left`; middle-click paste; I-beam
|
||||||
|
cursor over text; `DroppedFile`.
|
||||||
|
|
||||||
|
**Q#GA6 — RULED: land the TUI answer in this same stage; no declared
|
||||||
|
divergence.** The TUI half is smaller than revision 2 implied, and
|
||||||
|
the corrected trace is this: crossterm already delivers
|
||||||
|
`MouseEventKind::ScrollLeft`/`ScrollRight`; `src/protocol.rs` and
|
||||||
|
`pmacs-protocol` already carry them as `MouseKind::ScrollLeft`/
|
||||||
|
`ScrollRight`; and **attached** mouse events already round-trip
|
||||||
|
through `mouse_from_crossterm` / `mouse_to_crossterm`
|
||||||
|
(`src/protocol.rs:712`, `:777`). Local and attached document events
|
||||||
|
**converge on the one document handler, whose only wheel arms are
|
||||||
|
`ScrollUp`/`ScrollDown` at `src/editor.rs:3189`** — that single site
|
||||||
|
is where horizontal wheel is dropped.
|
||||||
|
|
||||||
|
*Revision 3 cited `src/editor.rs:5863-5864` and `:3407` here. Both
|
||||||
|
are **terminal-content** paths — `:3407` matches `TerminalMouseKind`
|
||||||
|
and drives `terminal_manager.scroll_view` — not the document window,
|
||||||
|
so they were the wrong sites for this ruling.*
|
||||||
|
|
||||||
|
So the TUI answer is one handler arm on an event that already
|
||||||
|
arrives, not new plumbing, and QoL Stage 5's reason for excluding
|
||||||
|
explicit-scroll surfaces — keeping the frontends agreeing — is
|
||||||
|
*served* by doing both here rather than traded against.
|
||||||
|
- **1c session/window signals**: `Focused` → `FocusGained`/`FocusLost`;
|
||||||
|
`CloseRequested` sends `Detach` before exit; post-handshake
|
||||||
|
`Goodbye` reason surfaced (consumer-only, §2.2); **window title
|
||||||
|
composed frontend-locally from `StatusFacts`** — no `Title` producer
|
||||||
|
required (§2.2); **`Bell` as a plain consumer** — the producer exists
|
||||||
|
and always did (§2.2), so there is no producer question and no option
|
||||||
|
to drop it; `ScaleFactorChanged`/DPI, and with it the
|
||||||
|
`AttachRequest.initial_size` cell-grid assumption, which is only
|
||||||
|
observable once scale is real.
|
||||||
|
- **1d IME — Q#GA7 RULED: the full preedit overlay, not a commit-string
|
||||||
|
minimum.** The scope is explicitly: commit string; **caret and
|
||||||
|
selection range within the preedit**; **cancellation**; and
|
||||||
|
**focus-loss cleanup** so a dropped composition cannot survive as
|
||||||
|
stale overlay text. Preedit needs a rendering surface, which is why
|
||||||
|
this is not folded into 1a — and why the ruling has real cost, stated
|
||||||
|
rather than discovered later.
|
||||||
|
|
||||||
|
**Stage 2 — capability-aware keymap resolution.**
|
||||||
|
|
||||||
|
**By reference, not absorption**: handoff §6 requires this to be its
|
||||||
|
own framing round and forbids starting it as a half-lane. This arc
|
||||||
|
sequences it here because Stage 1's seam makes its GPU consumers
|
||||||
|
testable, and consumes it for default zoom bindings and every future
|
||||||
|
GPU-native chord. **This arc feeds Stage 2's framing one explicit
|
||||||
|
input question: does the capability-aware vocabulary cover pointer
|
||||||
|
gestures (wheel-with-modifier), or keys only?** Revision 1 assumed
|
||||||
|
the former; nothing yet guarantees it (major №4).
|
||||||
|
|
||||||
|
**Q#GA8 — RULED: wait for Stage 2. The temporary Ctrl+wheel zoom
|
||||||
|
island is not created.** Zoom arrives through this stage's mechanism or
|
||||||
|
not at all. Consequently this arc adds **no** off-path hardcode, and
|
||||||
|
§7's island accounting is now unconditional rather than
|
||||||
|
"at most one" — there is no removal criterion to track because there is
|
||||||
|
nothing to remove.
|
||||||
|
|
||||||
|
**Stage 3 — parity consumers.**
|
||||||
|
|
||||||
|
- **3a folding Stage 3** — consumer-only per §2.2's matrix (the
|
||||||
|
producer exists). The obligations are already enumerated in the
|
||||||
|
folding framings (a `FoldState` consumer, the fold-mirror clear on
|
||||||
|
`BufferSnapshot` (R2-4), `fold_projection` flip, optimistic-edit
|
||||||
|
unfold (R2-3), caret/hit-test fold-awareness). Whether GPU folding
|
||||||
|
needs `BlockAdornments` placeholders — which would add producer
|
||||||
|
scope — is that framing's question. Its ordering precondition
|
||||||
|
(bottom-panel Stage 2's landed band) is satisfied.
|
||||||
|
- **3b word wrap** — `ui.line-wrap = "word"` as the declared third
|
||||||
|
value. Nearly free on the GPU (cosmic-text `WordOrGlyph`).
|
||||||
|
**Q#GA9 — RULED: implement the grid answer with UAX #14; no declared
|
||||||
|
divergence.** The dependency is accepted rather than traded for a
|
||||||
|
§3.2 entry, so both frontends wrap by the same rules.
|
||||||
|
- **3c chrome theming** — `ThemeFacts` adoption for menu, completion
|
||||||
|
popup, minimap, caret, window background, peer palette; the TUI's
|
||||||
|
unthemed popup/menu pair is fixed in the same stage or declared.
|
||||||
|
**Cursor blink and cursor styles ship here** — §7 registers the knob
|
||||||
|
through the config registry and this is the stage behind it (§2.5).
|
||||||
|
- **3d minibuffer/completion refinements** — `prefix_len`/`total`
|
||||||
|
rendered (consumer-only, already on the wire per §2.2); clickable
|
||||||
|
dropdown rows (audit F-007); multibyte-exact band caret; the nav
|
||||||
|
highlight-wrap bug. Grouped as its own sub-stage rather than
|
||||||
|
scattered, because §2.5 showed four backlog items landing in one
|
||||||
|
surface.
|
||||||
|
|
||||||
|
**Stage 4 — robustness.**
|
||||||
|
|
||||||
|
- **4a auto-reconnect** + a reconnecting banner (parity with the
|
||||||
|
attach TUI's seam; F-008). This adds a background reconnect loop and
|
||||||
|
therefore owes §20's background-work attribution — see §7 for the
|
||||||
|
contract its framing must satisfy (owner, lifetime, cancellation,
|
||||||
|
failure attribution).
|
||||||
|
- **4b session save *and* restore for semantic frontends** (Q#DS9) —
|
||||||
|
**P2-gated, §5.1.** The stage owns **both halves as a pair**, which
|
||||||
|
revision 2 got wrong by naming only restore. Q#DS9 scopes v1 to the
|
||||||
|
local `editor::run` path and makes `desktop_mode(true)` auto-save
|
||||||
|
**and** auto-restore no-ops in daemon mode, enforced in Rust by a
|
||||||
|
`DaemonMode` marker that `save_session`/`restore_session` early-return
|
||||||
|
on. So there is no snapshot being written under a daemon today: a
|
||||||
|
restore path alone would have nothing to read, and shipping restore
|
||||||
|
without save would be a stage that cannot work by construction.
|
||||||
|
|
||||||
|
**Q#GA10 — RULED: preserve both surfaces.** Automatic restore on the
|
||||||
|
**first eligible attach** when armed and no explicit target was
|
||||||
|
supplied, **plus** the existing explicit command — not one or the
|
||||||
|
other. What remains open is more than revision 2 admitted when it
|
||||||
|
said only the trigger shape was: **snapshot ownership** (who writes
|
||||||
|
it, keyed how, once the frontend is not the owner), **save timing**
|
||||||
|
(before-quit is a local-mode assumption; a daemon frontend can detach
|
||||||
|
without quitting anything), and **multi-frontend arbitration** (two
|
||||||
|
attached frontends with divergent layouts and one workspace key).
|
||||||
|
All three are decided in this stage's framing, on P2's object.
|
||||||
|
|
||||||
|
**Stage 5 — hover/signature popups** (Q#GA3 goal). The core's
|
||||||
|
never-attached `HoverView`/`SignatureView` are the data-model
|
||||||
|
precedent; the GPU needs a popup surface and a wire decision (ride an
|
||||||
|
existing family vs a new message — its framing decides; hover data
|
||||||
|
currently flows Lua → echo/`*lsp-help*`, so there is **producer scope
|
||||||
|
here by construction**, stated rather than hidden). Independent of
|
||||||
|
Half B; sequenced after Stage 3 so the popup is themed from birth.
|
||||||
|
|
||||||
|
### Half B — structure
|
||||||
|
|
||||||
|
**Revision 3 reorders this half.** Revision 2 ran viewport facts (6)
|
||||||
|
before the multi-window model (7), which is backwards twice over. A
|
||||||
|
viewport fact is *about* something — a window — and whether a semantic
|
||||||
|
window is a **daemon projection** or a **frontend-local object** is
|
||||||
|
exactly what the model stage decides; designing the facts first would
|
||||||
|
fix an identity the model then has to honour or break. Second, revision
|
||||||
|
2's model stage was **framing-only** yet the sidebar was told to "ride
|
||||||
|
Stage 7's geometry": a framing produces no geometry, so Stage 9
|
||||||
|
consumed something no stage shipped. Non-bottom side geometry is now
|
||||||
|
**implemented** in Stage 8.
|
||||||
|
|
||||||
|
**Stage 6 — the multi-window model framing.** The arc's center of
|
||||||
|
gravity and the reason Half B exists: how a daemon layout projects to
|
||||||
|
a semantic frontend (project the per-frontend layout tree vs
|
||||||
|
frontend-local layout over multiple buffer subscriptions — the wire
|
||||||
|
today assumes one document window per semantic session, with the panel
|
||||||
|
band as the only exception). **Its output that everything downstream
|
||||||
|
needs is the window-identity decision**, because that is what a
|
||||||
|
viewport fact, a split, a side slot and a tab all refer to. Framing
|
||||||
|
only; it ships no runtime code, and nothing downstream is told to
|
||||||
|
consume geometry from it.
|
||||||
|
|
||||||
|
**Stage 7 — the viewport/window-identity substrate, and the scroll
|
||||||
|
feel that reads it** (Q#GA3 goal). Viewport facts on the wire, carrying
|
||||||
|
the identity Stage 6 settled — **the GPU never consumes daemon
|
||||||
|
`view_top` today**, which the backlog names as the blocker for recenter
|
||||||
|
and every scroll command (`docs/side-quest-backlog.md:147`; revision 2
|
||||||
|
stated this exactly backwards). **Smooth scroll and the scrollbar live
|
||||||
|
here**, not in a tail: a scrollbar needs authoritative extent and
|
||||||
|
position, and pixel-smooth scrolling changes the scroll model those
|
||||||
|
facts feed.
|
||||||
|
|
||||||
|
**Stage 8 — splits shipped, and side geometry with them**: rendering,
|
||||||
|
input routing, per-window status bands, window-command parity
|
||||||
|
(`C-x 2/3/o/0/1`), **plus implemented side-window geometry beyond
|
||||||
|
`Side::Bottom`**. The geometry is here rather than in Stage 6 because
|
||||||
|
it is code, and because Stage 9 consumes it.
|
||||||
|
|
||||||
|
**Stage 9 — the project/files sidebar** (Q#GA3 goal) —
|
||||||
|
**P2-gated, §5.1.** Tree-primitive adoption (`COHERENCE.md` §14 names
|
||||||
|
project files as a future tree consumer; §3 names the surface). Rides
|
||||||
|
**Stage 8's implemented geometry** and P2's root object.
|
||||||
|
|
||||||
|
**Stage 10 — tabs/tabline** (Q#GA3 goal — last, low priority by
|
||||||
|
ruling). **Q#GA12 — RULED: a deliberate deferral to this stage**,
|
||||||
|
decided after P2 and the multi-window model exist, because both are
|
||||||
|
what make the readings meaningful — the lineage precedents disagree
|
||||||
|
(Emacs `tab-bar-mode` tabs are **window configurations**; tab lines and
|
||||||
|
Doom's centaur-tabs are **buffers**), and a workspace-keyed third
|
||||||
|
reading only becomes available once P2 has landed. Deferring is the
|
||||||
|
ruling, not an absence of one. The constraints hold regardless and are
|
||||||
|
pinned now: tabs present **existing objects** (whichever kind), **never
|
||||||
|
a parallel registry** with unvalidated references — the menu-label
|
||||||
|
mistake is the named anti-pattern — and the surface is **optional and
|
||||||
|
off by default**.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Non-goals and named deferrals
|
||||||
|
|
||||||
|
- **GUI as the default frontend.** Deliberately **not** the closure
|
||||||
|
bar (Q#GA2 chose journey parity). It remains
|
||||||
|
`gpu-initial-target-framing.md`'s deferral, to be *decided* — not
|
||||||
|
assumed — when the arc closes.
|
||||||
|
- **Git integration** (`COHERENCE.md` §15): editor-wide, not
|
||||||
|
GUI-specific; not this arc.
|
||||||
|
- **Settings/preferences GUI**, **native menu bar**, **multiple OS
|
||||||
|
windows**: out of scope; nothing below depends on them.
|
||||||
|
- **`ResourceOffer`/image rendering**: unproduced and unconsumed
|
||||||
|
(§2.2); stays deferred unless a stage (sidebar icons, hover docs)
|
||||||
|
pulls it in with a framing that owns both halves.
|
||||||
|
- **Proportional code fonts, ligature/feature toggles, font wire
|
||||||
|
transfer**: `gpu-set-font-framing.md`'s deferrals stand.
|
||||||
|
- **Remote GPU paths, daemon service management**: unchanged.
|
||||||
|
- **Multi-cursor**: pre-existing v0.1 non-goal, unchanged.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Coherence impact (per `CLAUDE.md` / `COHERENCE.md` §20)
|
||||||
|
|
||||||
|
- **Journey steps touched**: **3, 4, 5, 6, 7, 8, 10, 12** — *on the GPU
|
||||||
|
frontend*; Stage 0 makes the journey frontend-graded so the impact is
|
||||||
|
measured per step rather than asserted. Revision 3 listed five; three
|
||||||
|
were missing because the list was carried from revision 1's smaller
|
||||||
|
stage map and never re-derived against the stages this arc actually
|
||||||
|
ships. **Step 3** — `DroppedFile` (Stage 1b) is an open-a-file route.
|
||||||
|
**Step 5** — keyboard correctness, IME, folding, wrapping and
|
||||||
|
scrolling are all editing-surface work (Stages 1a, 1d, 3a, 3b, 7).
|
||||||
|
**Step 6** — completion refinements and hover/signature popups
|
||||||
|
(Stages 3d, 5).
|
||||||
|
- **Interaction islands**: this arc adds **no dispatch shadows** — the
|
||||||
|
count stays at six — and, after Q#GA8's ruling, **no off-path
|
||||||
|
hardcode either**. Revision 2 reserved one temporary island for the
|
||||||
|
Ctrl+wheel zoom intercept under a mandatory removal criterion; the
|
||||||
|
ruling declined it, so the census is untouched by this arc and there
|
||||||
|
is no removal criterion to track. Zoom arrives through Stage 2's
|
||||||
|
mechanism or not at all. A stage that believes it needs a new
|
||||||
|
*shadow*, or a new island, returns to this document first.
|
||||||
|
- **Config registry adoption**: every user-visible knob this arc adds
|
||||||
|
(smooth scroll, scrollbar, cursor blink, tabline toggle, IME
|
||||||
|
behavior if any) registers through the config registry — no new raw
|
||||||
|
Lua-table settings. The minimap's divergent tab width (4 vs the
|
||||||
|
shared 8) stays owned by config-registry Q#CR13, referenced not
|
||||||
|
absorbed.
|
||||||
|
- **Background-work attribution** (moderate №8): Stage 4a's reconnect
|
||||||
|
loop is background work and owes the §20 attribution regardless of
|
||||||
|
§9's unsolved general model. The contract its framing must satisfy:
|
||||||
|
**owner** — the GPU frontend process, scoped to its session, never
|
||||||
|
the daemon; **lifetime/cancellation** — bounded backoff, canceled
|
||||||
|
on user quit and on successful re-attach, never outliving the
|
||||||
|
window; **failure attribution** — every terminal failure surfaces
|
||||||
|
in-window with a reason, and the contract covers the case where the
|
||||||
|
daemon supplies none.
|
||||||
|
|
||||||
|
**The reason requirement is two-sided, because the silent cases are
|
||||||
|
the common ones.** Revision 2 required "the daemon's stated reason",
|
||||||
|
which is unsatisfiable exactly when it matters: a daemon that
|
||||||
|
**crashes or drops the socket delivers no `Goodbye` at all**, and the
|
||||||
|
frontend learns of departure by EOF (§2.3 records that the GPU
|
||||||
|
already loses its peer this way today). So: **use the daemon's reason
|
||||||
|
when one arrives** — which is why Stage 1c's post-handshake
|
||||||
|
`Goodbye`-reason consumer precedes this stage — **and otherwise
|
||||||
|
surface an explicitly locally-classified transport/EOF reason**,
|
||||||
|
labelled as locally inferred rather than reported. A banner that says
|
||||||
|
nothing because the daemon said nothing is the failure this clause
|
||||||
|
exists to prevent. §9's activity-indicator gap is *not* claimed by
|
||||||
|
this arc.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Verification shape
|
||||||
|
|
||||||
|
- **What already exists is used, not rebuilt** (major №5): the real
|
||||||
|
offscreen `render_to_view` composition harness, the readback path,
|
||||||
|
the smoke tests, and the required-GPU CI job are **landed**. Stages
|
||||||
|
3c, 8 and any pixel-visible change add pixel assertions against
|
||||||
|
that harness immediately. What is deferred from
|
||||||
|
`gpu-golden-harness-framing.md` is only **golden-PNG comparison and
|
||||||
|
the case gallery**; a stage adopts those if image diffing beats
|
||||||
|
direct assertions for its witnesses, with that framing.
|
||||||
|
- **The a37 problem is confronted, not inherited.** Real-GPU
|
||||||
|
end-to-end tests compile only when `pmacs-gpu` is built, return
|
||||||
|
`ok` without running otherwise, and are load-sensitive — the
|
||||||
|
recorded footing hazard. Stage 1-pre's seam exists so input stages
|
||||||
|
are witnessed *without* a display; stages that genuinely need a
|
||||||
|
real frontend say so and name their witness (`PMACS_REQUIRE_GPU`
|
||||||
|
discipline).
|
||||||
|
- **The arc ratchet**: extend `tests/journey_acceptance.rs` with
|
||||||
|
GPU-frontend rows where headlessly drivable; stages add rows, none
|
||||||
|
removes them — same rule as the existing ratchet.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. Rulings — Q#GA4 through Q#GA12, all closed
|
||||||
|
|
||||||
|
**Every arc-level question is ruled as of revision 3.** They are kept
|
||||||
|
here with their answers rather than deleted, because a stage framing
|
||||||
|
that wants to revisit one needs to see what was decided and why it is
|
||||||
|
not open.
|
||||||
|
|
||||||
|
- **Q#GA4 — RULED.** The name is "the GUI arc"; the numeric **Arc 8
|
||||||
|
label retires** at Stage 0, resolving the collision with the Lean 4
|
||||||
|
framing's claim on the same number.
|
||||||
|
- **Q#GA5 — RULED, with a hardening.** Half A slots after P1. Reaching
|
||||||
|
Stage 4b is a **P2 start gate**: no later GUI stage starts until P2
|
||||||
|
has an approved framing and an opened lane, after which non-gated
|
||||||
|
work interleaves freely while the object lands (§5.1). Stronger than
|
||||||
|
the recommendation carried in revision 2, which would have let every
|
||||||
|
**non-gated** stage finish before P2 began — leaving P2 a terminal
|
||||||
|
closure blocker rather than letting the arc close around it.
|
||||||
|
- **Q#GA6 — RULED.** Land the TUI answer in Stage 1b; no declared
|
||||||
|
divergence. The events already arrive and are dropped by the
|
||||||
|
document-window handler (Stage 1b records the sites).
|
||||||
|
- **Q#GA7 — RULED.** Full preedit overlay: commit string, caret and
|
||||||
|
selection range, cancellation, focus-loss cleanup. Not the
|
||||||
|
commit-string minimum.
|
||||||
|
- **Q#GA8 — RULED.** Wait for Stage 2. **No temporary Ctrl+wheel
|
||||||
|
island**, so this arc adds no off-path hardcode (§7).
|
||||||
|
- **Q#GA9 — RULED.** Implement the grid answer with UAX #14. No
|
||||||
|
declared divergence.
|
||||||
|
- **Q#GA10 — RULED.** Preserve **both** surfaces: automatic restore on
|
||||||
|
the first eligible attach when armed and no explicit target was
|
||||||
|
supplied, plus the existing explicit command. Stage 4b owns the
|
||||||
|
paired **save** path too, and three questions remain live inside that
|
||||||
|
stage — snapshot ownership, save timing, multi-frontend arbitration.
|
||||||
|
- **Q#GA11 — RULED.** The §3.1 blocker seed stands **unchanged at nine
|
||||||
|
items**.
|
||||||
|
- **Q#GA12 — RULED as a deliberate deferral** to Stage 10, taken after
|
||||||
|
P2 and the multi-window model exist. The existing-object,
|
||||||
|
no-parallel-registry and optional/off-by-default constraints hold
|
||||||
|
from now, not from Stage 10.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. Sequencing against #227 (git Stage 1)
|
||||||
|
|
||||||
|
Settled with the user on 2026-08-10, and recorded here because it
|
||||||
|
constrains when Stage 0 may start:
|
||||||
|
|
||||||
|
1. **Revision 3 → approval.**
|
||||||
|
2. **The approved framing and the Stage 0 lane are committed and
|
||||||
|
pushed** on the Stage 0 branch, as its first commit. Until that
|
||||||
|
happens this document is not portable and nothing downstream is
|
||||||
|
safe to rely on.
|
||||||
|
3. **#227 is finished and merged** before Stage 0 implementation.
|
||||||
|
4. **Stage 0 rebases and performs the absorption**, which by then
|
||||||
|
includes **#227's own newly merged lane** alongside the five already
|
||||||
|
enumerated.
|
||||||
|
|
||||||
|
The reason #227 goes first rather than riding alongside: its ref is
|
||||||
|
**72 `main` commits behind**, and it touches `COHERENCE.md`,
|
||||||
|
`docs/active-work.md` and `builtin/runtime/listview.lua` — the three
|
||||||
|
files Stage 0's absorption rewrites. Carrying it across the arc would
|
||||||
|
compound exactly the conflicts Stage 0 exists to retire.
|
||||||
Loading…
Reference in New Issue