From 263f40d9a30bd9cf542270ac1c5966ce8a633a7f Mon Sep 17 00:00:00 2001 From: Levi Neuwirth Date: Fri, 7 Aug 2026 13:38:10 -0400 Subject: [PATCH] =?UTF-8?q?Handoff=20=C2=A72:=20the=20editor=20track=20rea?= =?UTF-8?q?d=20from=20its=20sources,=20not=20inferred?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 Claude-Session: https://claude.ai/code/session_01QjsEnYhm1gPpf6ii2iFxFV --- spec/HANDOFF_2026-08-07.md | 225 ++++++++++++++++++++++++++++--------- 1 file changed, 169 insertions(+), 56 deletions(-) diff --git a/spec/HANDOFF_2026-08-07.md b/spec/HANDOFF_2026-08-07.md index 93c7764..1a8cd9f 100644 --- a/spec/HANDOFF_2026-08-07.md +++ b/spec/HANDOFF_2026-08-07.md @@ -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 A–E), `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 "" + ``` + 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.