Compare commits

...

1 Commits

Author SHA1 Message Date
Levi Neuwirth adf9611d10
docs: the GUI arc framing, revision 3 approved --- and its Stage 0 lane
Opens the GUI arc. The framing and the lane are this branch's FIRST
commit, which is both the arc's own rule for every PR in it and the
reason this branch exists today rather than after #227: the document
spent two review rounds as an untracked file in one worktree, and
uncommitted work does not travel between machines.

Revision 3 closed two blocking findings from the second round.

The closure comparison is falsifiable now. Revision 2 ordered its
grades but never said how a cell acquires one, and its "normalize to
the head grade" rule mis-graded exactly the rows it was written for:
journey steps 7 and 11 both read "works but undiscoverable" and would
both have graded `Works`, with the undiscoverability demoted to prose
that cannot block closure --- while discoverability IS the substance of
steps 4, 7 and 11. Grades now have observable criteria, a compound step
takes the minimum over declared subclaims, and the table carries three
columns (local TUI, attached TUI, GPU) compared against the local TUI.

Half B's dependency graph was inverted. Viewport facts are ABOUT a
window, and whether a semantic window is a daemon projection or a
frontend-local object is what the model stage decides --- so the facts
cannot be designed first. Revision 2 also had a framing-only stage
handing the sidebar geometry no stage shipped. The order is now model
framing, then the viewport/identity substrate, then splits plus
IMPLEMENTED side geometry, then the sidebar.

Q#GA4 through Q#GA12 are ruled and closed.

Four claims this revision inherited were wrong and are corrected with
their sources rather than quietly dropped: the daemon has always
emitted `InstanceSignal::Bell` (daemon.rs:1373), so Bell is
consumer-only; the GPU NEVER consumes daemon `view_top`, which the
backlog states in as many words; Q#DS9 scopes save AND restore, so
Stage 4b owns both; and the protocol ceiling is v23, not v22 --- #228
raised it after #221, and revisions 2 and 3 both read the range at the
audit anchor without re-reading it afterwards.

The three-column claim is narrowed to what it can support. The attached
TUI handshakes `semantic_render: false` and never consumes the
SemanticFrame family, so a shared GPU/attached gap cannot implicate the
semantic wire. The columns separate local from daemon-attached, and
attached-grid from semantic; source tracing still assigns ownership.

Q#GA6's citation is corrected to the one site that matters: local and
attached document events converge on the document handler, whose only
wheel arms are Up/Down at editor.rs:3189. The sites revision 3 cited
were terminal-content paths.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016bqGA6s9tTUFzYpbeW3tai
2026-08-10 21:32:14 +02:00
2 changed files with 1003 additions and 0 deletions

View File

@ -210,6 +210,52 @@ hazard in a shape that looks committed. **A documented error message
that never appears is worse than no documentation**, because the reader
waits for a signal that is not coming.
## The GUI arc — Stage 0 branch OPEN, PARKED behind #227, no PR yet
**Written at the branch's first commit**, with the framing, which is
what this arc's own §5 requires of every PR in it. The standing
correction from #171 and #215 was missed at #224 and #225; this lane
exists to stop the streak rather than to note it again.
- **Branch `gui-arc-stage0`**, base `githubsucks/main` @ `0e4c58d` (the
#231 merge). **`githubsucks/gui-arc-stage0` is the authoritative
tip** — the ref, not a SHA. Recover with
`git fetch githubsucks && git checkout gui-arc-stage0`.
- **Framing `docs/gui-arc-framing.md`, revision 3, APPROVED
2026-08-10** after two review rounds (two blocking findings each
round, closed). It is **also the framing for Stage 0 itself**, which
is docs-only; Stages 110 each require their own framing before their
branch.
- **PARKED, deliberately.** Nothing further happens on this branch
until **#227 (git Stage 1) is finished and merged**. #227's ref is 72
`main` commits behind and touches `COHERENCE.md`,
`docs/active-work.md` and `builtin/runtime/listview.lua` — the three
files Stage 0's absorption rewrites. Sequencing it first avoids
compounding exactly the conflicts Stage 0 exists to retire. The
branch exists now, ahead of that work, **only so the approved framing
is portable**: uncommitted work does not travel between machines, and
the framing spent its whole review as an untracked file in one
worktree.
- **Scope when it resumes (docs only, no `src/`):** the absorption pass
enumerated in the framing's §5 — five stale lanes, the
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:** none applicable — Stage 0 changes no code. The gate
suite for its PR is `cargo fmt --check`, `git diff --check`, and
nothing else it can meaningfully run.
## `scripts/gate` — PR #225 OPEN (build tooling)
**PR #225** — https://github.com/levineuwirth/pmacs/pull/225. Written

957
docs/gui-arc-framing.md Normal file
View File

@ -0,0 +1,957 @@
# 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#GA4Q#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)`** — F1F12 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 are not hypothetical — they are the two rows
`COHERENCE.md` §2 carries today:
- **Step 7** — "Symbol: **works but undiscoverable**". Subclaims
*reachable* / *discoverable*: `Works` and `Missing`. Cell grade
**`Missing`**.
- **Step 11** — "**Works but undiscoverable**". Same shape, same
result.
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 05): 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 610): 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 110 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.