Handoff §2: the editor track read from its sources, not inferred

The boundary on the editor/T4 files was lifted for this handoff, so §2
stops being a list of leads and becomes an assessment. Read
PLAN_EDITOR_APP.md, CONTRACT_EDITOR_T4_SPIKE.md revision 6, the spike's
DECISIONS.md, and the spike tree.

Where the spike stands: round 0 complete with iced eliminated, round 1
complete with both surviving candidates passing, round 2 built but not
run — apparatus and candidatekit landed, no round2-evidence directory,
no verdict document. Rounds 3, 3b, 4 and 5 not started.

Two machine-specific facts a hardware change puts at risk, neither
recoverable from the documents alone. The spike's harness refuses to
report PASS unless it finds both a discrete and an integrated Vulkan
adapter, and the integrated one's figures are what decide round 4 — so
a different GPU configuration does not just change the numbers, it
breaks comparability with round 1's completed results. And the
accessibility rounds need two settings that are off by default; without
them AT-SPI enumerates zero applications and the result reads as a
candidate limitation when it is an environment absence.

Checked the one place the threads could collide: the spike path-depends
on layout-ir, core, glyphs, determinism and ops, not on epiphany-bundle,
and no editor crate depends on it either. The no-canonical-base interval
cannot reach T4's measurements. The threads collide only when T1b opens,
since it and P13-S27 both land in bundle.rs — recorded in §4.

§4 now sequences both threads, and the two items that had to happen
while this machine still exists are marked done or assigned.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QjsEnYhm1gPpf6ii2iFxFV
This commit is contained in:
Levi Neuwirth 2026-08-07 13:38:10 -04:00
parent 7909cf09cc
commit 263f40d9a3
1 changed files with 169 additions and 56 deletions

View File

@ -3,16 +3,14 @@
Written at `ada751c`, pushed to `origin/main`. Working tree clean, no stashes, no
unpushed commits on any branch.
**Scope note, stated first because it bounds §2.** This handoff is written from
the **spec / Pass-13 / format-epoch** session. A second session has been
committing to this repository in parallel, and its files were placed explicitly
out of bounds for this one — `spec/PLAN_EDITOR_APP.md`,
`spec/CONTRACT_EDITOR_*.md`, `spec/ANALYSIS_*.md`, `spec/DRAFT_T4_FIXTURE_RECIPE.md`,
the whole `spikes/` tree, `crates/epiphany-editor-gui/**`,
`crates/epiphany-render-svg/**`, `crates/epiphany-glyphs/**`, and the root
`Cargo.toml`. **§2 is therefore not an assessment of that work**; it is the view
from outside the boundary plus what to ask that session for. §0, §1, §3 and §4
are verified.
**Scope.** Written from the **spec / Pass-13 / format-epoch** session. A second
session has been committing to this repository in parallel on the **editor /
T4** track, and its files were out of bounds for this one for most of the work.
**That boundary was lifted on 2026-08-07 for the purpose of this handoff**, so
§2 is now read from the sources rather than inferred — but it is still written
by someone who did not do that work, and it is not a substitute for that
session's own account of its reasoning. Every section is verified against the
tree or the documents; where a claim is second-hand, it says so.
---
@ -40,11 +38,11 @@ Note the root `HANDOFF.md` is a *different, older* document from this one
### 0.2 Local git state that is not on the remote
- **The branch ref `editor-t4-packet2b` does not exist on `origin`.** `origin`
has only `refs/heads/main`. Its *commits* are safe — they are reachable through
the `f33673d` merge — but the branch pointer is local. **If that thread intends
to keep working on that branch, push it before wiping this machine:**
`git push -u origin editor-t4-packet2b`.
- ~~The branch ref `editor-t4-packet2b` exists only locally.~~ **Resolved
2026-08-07** — pushed; `origin` now carries both refs. Recorded because it was
a real exposure: its commits were safe via the `f33673d` merge, but the pointer
was not, and a wiped machine would have lost the branch name the spike's rounds
are organised under.
- **The worktree is local by nature.** Recreate on the other machine with:
`git worktree add .claude/worktrees/editor-t4 editor-t4-packet2b`.
- `.claude/settings.local.json` is untracked — local permissions/settings.
@ -240,44 +238,146 @@ straight through the hole.
---
## §2. The other thread — editor / T4 spike (BOUNDED VIEW ONLY)
## §2. The other thread — the editor track and the T4 toolkit spike
**I could not read any of this thread's files.** What follows is inferred from
shared git history and from notes carried in earlier sessions. Verify all of it
against that session before relying on any of it.
**Boundary lifted 2026-08-07**; this section is now read from the sources, not
inferred. Primary documents: `spec/PLAN_EDITOR_APP.md` (1,024 lines — the plan
and Rulings AE), `spec/CONTRACT_EDITOR_T4_SPIKE.md` (Revision 6, 16 design
pins), `spikes/editor-toolkit/DECISIONS.md` (the running findings log), and the
per-tranche `CONTRACT_EDITOR_T*` set.
Visible in shared history: a `f33673d` merge of `editor-t4-packet2b` ("the T4
spike's Round 2 text packet"), `e2979df` ("Packet 2B: both candidates consume the
frozen text apparatus"), and `694d135` ("Packet 2B apparatus: the neutral
candidatekit and the check-5 oracle"). That reads as a **toolkit-selection spike
running two candidates against a neutral harness** with a frozen text apparatus
and scored checks.
### 2.1 Where the track is
Carried notes (**stale, from before this session**; treat as leads only):
The editor's *model* is built; its shell is a demo. `epiphany-editor-core` is
~6.7k lines with 93 tests behind conformance gate `[7c]`; the hit-test
provenance contract and the SVG renderer are golden-locked;
`epiphany-editor-gui` is 598 lines of self-described demo binary, disposable by
design. The discipline is that every capability lands headless in `editor-core`
first, proven at the IR boundary, before any GUI wiring.
- `spec/PLAN_EDITOR_APP.md` holds the rulings and a tranche ladder T1a → T4, with
all three Ruling-A prerequisites (W1/W2/W3) discharged and **T4 the toolkit
spike** as the next item.
- An `epiphany-editor-core` crate exists with a hit-test contract and an edit
loop, gated by a conformance `[7c]` UI-seam item.
- `epiphany-engrave` + `epiphany-render-svg` carry a renderer-vs-stub scaffold.
- Golden PNGs live in `crates/epiphany-editor-gui/goldens/`, gated in CI by the
`golden-gate` feature.
Ladder: **T1a** (visual golden harness) landed; **T1b** (document layer) is
blocked; **T2** selection v2, **T3** note-entry caret, **T4** the rendering
tranche (open now, via the spike), **T4b** incremental materialization, **T5**
playback.
**Ask that session for**, since none of it is visible from here: the current T4
candidate scores and whether a selection was made; whether Packet 2B closed or
has open rounds; what `spikes/` currently contains and whether any of it is meant
to graduate into `crates/`; and whether anything there depends on the
canonical-base interval in §1.2 — that is the one place the two threads can
collide, and neither side can see the collision from its own side.
**Rulings:** A (direct vector canvas over `ResolvedLayoutIR`) granted;
C (golden harness) granted; D (new `epiphany-editor-app` crate created *at* T4)
granted conditionally; E (clipboard fragment format) granted;
**B (document/persistence) still not granted** — see 2.3.
**One known cross-thread interaction, already handled:** this thread's spec edits
move hardcoded counts in `crates/epiphany-testkit/tests/requirement_labels.rs`
(`CORE_REQUIREMENT_COUNT`, `SUITE_REQUIREMENT_COUNT`, `SUITE_LABEL_COUNT`). Any
`.tex` requirement added by either thread breaks four tests there until those
constants move. It is in no contract's touch table.
All three Ruling-A prerequisites are discharged: W1 IR primitive ownership
(`dd33b34`), W2 the `epiphany-glyphs` seam (`24f8c80`), W3 the text-run
primitive ruling (`f639919`).
---
### 2.2 The T4 spike — exactly where it stands
An elimination experiment producing one deliverable,
`spec/RULING_EDITOR_TOOLKIT.md`, which **does not exist yet**. Candidates:
**C1** modern egui + lyon meshes, **C2** vello behind winit, **C3** iced.
| Round | Subject | State |
|---|---|---|
| 0 | Accessibility route + desk survey | **COMPLETE. C3 (iced) ELIMINATED.** |
| 1 | Compound-path fill correctness | **COMPLETE. C1 PASS, C2 PASS** (27 points × 2 adapters each) |
| 2 | Text (5 checks) | **APPARATUS ONLY.** Packet 2A (fixtures/emitter/differential) and 2B (candidatekit + check-5 oracle) landed; **no candidate results yet** — there is no `round2-evidence/` |
| 3 | Accessibility semantics | not started |
| 3b | Damage correctness (untimed) | not started |
| 4 | Staged latency (ranking) | not started |
| 5 | Foreclosure probes | not started |
**C3's elimination is worth reading before anything else** — it is the
methodological centre of the whole spike. It first reported PASS, and the PASS
was an artefact: `probe-iced` registered a hand-built static AT-SPI tree through
`accesskit_unix`, which takes no window handle and registers from *process*
identity. The tree was decoupled from iced's window, focus and event lifecycle,
and `view()` happened to label its button identically — so the transcript read
as though iced produced it. **Deleting iced from the probe would have produced
the identical readback.** Corrected to FAIL on coordinator review, with the
false positive preserved rather than rewritten. The same shape recurs in Round 1
(C1's first run drew nothing: `egui-wgpu` silently skips primitives whose
texture id is unregistered, so a blank target passed every background point) —
whence the standing rule: *every round must keep at least one assertion that can
only succeed if ink was actually deposited.*
### 2.3 What blocks what
- **T1b is blocked on Ruling B blocker (ii), versioned decode.** Blocker (i)
(graph-state persistence) was resolved 2026-07-24 by
`spec/RULING_GENESIS_PERSISTENCE.md` — the operation set absorbs genesis,
Pass-12 K8 reversed — which is the same genesis-ops track §1 has been
interleaved with. Blocker (ii) needs the migrate-on-read API in
`epiphany-ops`: `decode_envelope` is current-layout-only while the format
requires migrate-on-read, and the "written by the current writer" restriction
is **unenforceable** because minimal stamping means a current writer
legitimately emits major-0, -1 and -2 blocks.
- **T4b's trigger is measured, not guessed.** `benches/editor_pipeline.rs`
found reduce is the only depth-scaling stage and is superlinear (~n^1.4),
breaking the frame budget **between 3,000 and 5,000 edits** — the wall is
~4,500, and note entry mints one operation per note. Reopen is full replay,
so nothing resets that depth until T4b's checkpoint machinery exists.
### 2.4 Two machine-specific facts that a move puts at risk
**These are the reason this section matters for a hardware change, and neither
is recoverable from the documents alone.**
1. **The spike's measurement protocol requires two GPU adapter classes.** Round
1's harness refuses to report PASS unless it finds **one `DiscreteGpu` and
one `IntegratedGpu` Vulkan adapter**, and pin 4 fixes the deciding
configuration across candidates. This machine has a discrete Navi 31 and an
integrated Granite Ridge. **The integrated adapter's figures are the ones
that decide Round 4** — "a budget met on a Navi 31 says nothing about the
machine most users have." If the other box does not present both classes,
Round 1's completed results are not comparable to anything measured there,
and Round 4 cannot be run as specified. Check adapter enumeration *from the
spike's own process* before assuming continuity, and if the hardware differs,
treat it as a contract question rather than re-running quietly on whatever
enumerates.
2. **Accessibility rounds need two settings that are off by default.** On this
machine (sway + AT-SPI2), `Atspi.get_desktop(0)` enumerates **zero**
applications — even with a probe alive, rendering, and connected to the
AT-SPI bus — unless both of these have been set:
```
gsettings set org.gnome.desktop.interface toolkit-accessibility true
gdbus call --session --dest org.a11y.Bus --object-path /org/a11y/bus \
--method org.freedesktop.DBus.Properties.Set org.a11y.Status \
ScreenReaderEnabled "<true>"
```
Without them, Rounds 0 and 3 produce a `NOT RUN` that looks like a candidate
limitation and is not one. The spike's own log flags this for exactly this
situation: *any later round run in a fresh sandbox or session must redo both
steps before trusting a `NOT RUN` verdict on accessibility.*
Also environment-shaped: `a11y-verifier/verify.py` uses `gi.repository.Atspi`
(Python GObject introspection), so the other machine needs that binding, not
just the Rust toolchain.
### 2.5 Frozen-baseline discipline
Spike pin 12 runs every round inside a `git worktree` at a **pinned baseline
commit**, so path dependencies resolve to frozen sources rather than the live
tree — which has had §1's work moving through it continuously. Round 1 is
anchored to root baseline `0a35697d8e48e65d62cd96c19eec2431e414359c`, oracle
commit the same, candidate harness `c20bc93`, with `oracle.json` hashing to
`b3fc017b…`. **If the baseline moves, the affected round is re-run in full**;
partially re-run rounds are not reported. Any round resumed on the other machine
must either reuse that baseline or re-run whole.
### 2.6 Cross-thread collision surface — checked, and clean
The one place the threads could have collided is §1.2's constraint that no
bundle may carry a canonical base. **Verified: it cannot reach the editor
track.** The spike path-depends on `epiphany-layout-ir`, `-core`, `-glyphs`,
`-determinism` and `-ops`**not** on `epiphany-bundle` — and none of
`epiphany-editor-core`, `-engrave`, `-layout-ir` or `-glyphs` depends on
`epiphany-bundle` either. `cargo metadata` resolves in the spike workspace
against the current tree. §1's format-epoch work therefore has no path into
T4's measurements.
The remaining shared surfaces are the ones already named: `.tex` requirement
counts in `testkit/tests/requirement_labels.rs`, and — when T1b eventually
opens — `epiphany-bundle`, which is exactly where §1's rungs live. **T1b and
P13-S27 both land in `bundle.rs` and must not fly together.**
## §3. Reproducing the environment on the other machine
@ -334,34 +434,47 @@ to mutate against.
### 4.1 Before wiping this machine
1. **`git push -u origin editor-t4-packet2b`** — the only ref that exists here
and not on the remote (§0.2). Its commits are safe either way; the pointer is
not.
1. ~~Push `editor-t4-packet2b`~~**DONE 2026-08-07.** `origin` now carries
both `main` and `editor-t4-packet2b`.
2. Copy off the four local-only items: root `HANDOFF.md`,
`.claude/settings.local.json`, and the out-of-repo
`~/.claude/projects/-home-jeans-Repos-active-epiphany/memory/` directory
(§0.1, §0.3). `ALL_DECISIONS_AND_READMES.md` and `out.svg` regenerate.
3. Confirm both sessions have written their handoffs — this one covers §1; the
editor/T4 thread's own is still owed (§2).
3. Have the editor/T4 session add its own account of §2's *reasoning* — this
document reads its artefacts, not its intent. Specifically: whether Round 2's
checks are ready to run, and whether the hardware change in §2.4 changes the
spike's plan.
### 4.2 On arrival
4. `git clone`, then restore the memory directory to the same path, then
`git worktree add .claude/worktrees/editor-t4 editor-t4-packet2b` if that
thread is resuming there.
5. Install the environment in §3, including **`xelatex`** — `pdflatex` fails
outright on `fontspec`.
5. Install the environment in §3, including **`xelatex`** (`pdflatex` fails
outright on `fontspec`) and, for the spike, the `gi.repository.Atspi` Python
binding plus §2.4's two accessibility settings.
5b. **Enumerate GPU adapters from the spike's own process** (§2.4). If the new
machine does not present both a discrete and an integrated Vulkan adapter,
stop and raise it — Round 1's results were taken under that configuration and
Round 4's deciding figures are defined by it.
6. **Reproduce the green baseline in §3.5 before starting new work.** 1570
passing. This thread's whole method depends on a known-green baseline to
mutate against; if the number differs, reconcile that first.
### 4.3 Then, in priority order
7. **Dispatch P13-S27.** The only unblocked item on the critical path, its
**The two threads are independent right now** (§2.6) and can resume in
parallel. They collide only when T1b opens, since it and P13-S27 both land in
`bundle.rs`.
7. **Spec thread — dispatch P13-S27.** The only unblocked item on the critical path, its
contract is complete, and landing it discharges the three inherited
obligations *and* ends the no-canonical-base interval that currently
constrains every other piece of bundle work (§1.2).
8. Then **P13-S16**, dispatchable the moment S27 lands.
9. Take a ruling on **S8** (reject one spelling vs. keep both canonical) whenever
8. **Editor thread — run Round 2's checks**, whose apparatus is built and
whose candidates are wired (§2.2); then Rounds 3, 3b, 4, 5 and the
`RULING_EDITOR_TOOLKIT.md` verdict.
9. Then **P13-S16**, dispatchable the moment S27 lands.
10. Take a ruling on **S8** (reject one spelling vs. keep both canonical) whenever
convenient — it needs a decision, not an implementation, and blocks nothing in
the meantime.