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
This commit is contained in:
parent
0e4c58d68d
commit
adf9611d10
|
|
@ -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 1–10 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
|
||||
|
|
|
|||
|
|
@ -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#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 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 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