# Flag-day playbook: editor-swap step 12 as a reviewable series

The execution plan for step 12 of `docs/editor-swap-playbook.md` — the
swap itself. D6 already concedes what makes this file different from the
op-emitter playbook: **the series cannot be green mid-way.** One id type
flip propagates through selection, hit-testing, and every tool, and the
legacy document cannot mint the new ids. So the rules change, once,
here:

- The series lands as consecutive commits on the branch; `cargo xtask
  ci` is required **green at the series end** and at nothing before it.
- Every commit is still **reviewable**: it states its intent, its scope,
  and — the red-series discipline — its **residual-red inventory**: the
  compile-error count and the files they sit in, which the next commit
  consumes. A commit that grows the inventory it inherited without
  saying why is a broken commit.
- Sub-agents execute the mechanical fan-outs with the residual-red
  inventory as their acceptance criterion (in place of CI); the session
  lead does the structural commits and reviews everything, as in step
  10. An agent that finds this plan wrong stops and reports.
- No behavior intent anywhere except where an F-decision below says so.
  Everything behavioral was landed and tested in steps 6–11; this series
  re-points wiring.

## What the survey says (2026-08-19)

33 files import the legacy `store::` ids; `ShapeId` appears 449 times,
`LineAnchor` 339, `Deletable` 84. `Drawing` exposes 59 functions (the
step-6 setters plus readers). `App` holds `EditorState { document, path
}` under an `egui::Undoer`, a quiescence snapshot, autosave, and the
`.bwx` container path. Cue resolution (`script/step.rs`) reads the
legacy `schema::Document` projection. The session already ships
`undo/redo/can_undo/can_redo`, `Link { Remote, Local }` is live, every
emitter exists fold-tested in `src/edit/`, and the bridge lowers every
tutorial level.

## Decisions (F1–F8)

- **F1 — ids.** The document-side legacy newtypes die; `blockworx_doc`'s
  `Id<K>` types are *the* ids. `ShapeId` and `Deletable` survive as
  enums re-typed over them. **`LineAnchor` dies** — the port/pin split
  it encoded is gone; its 339 sites collapse to `PinId` (the conversion
  table's row, finally claimed). `WaypointId` dies — waypoints are
  positional in a `Vec<Waypoint>`; `RouteEditSession`'s
  `WaypointSlot::Existing(WaypointId)` re-shapes to a path ordinal.
  `EdgeId` (and the `IdMap` it needs) survives for the presentation
  layer's own minting. *(Corrected 2026-08-19 before 12·1: the legacy
  newtypes cannot be deleted yet — F7 keeps the legacy model compiling
  for the bridge and the tutorial embeds through phase 7, and that
  model uses them internally. So "die" means die from the interface
  layer: `src/store.rs` keeps the legacy ids, re-documented as
  legacy-model-internal and phase-7-doomed, and nothing outside
  `src/document/`, `src/schema/`, and the legacy waist imports them
  after this series. 12·1 addendum: `src/presentation/` also imports
  `LineAnchor` until 12·2's F3 re-key — its accent keys collapse with
  the reads, not the ids.)*
- **F2 — the new Drawing** *(amended twice 2026-08-19, both by user
  review; the record matters, so both stand here)*. The first draft
  took the cache by value — borrow-structure fallout that would have
  paid an O(document) rebuild every frame. The first amendment split
  `doc` and an owned index into two `Drawing` fields — which
  reintroduced, as a representable state, exactly the pairing hazard
  the doc crate's bundled design exists to prevent ("consulting a
  cache built from a *different* document"). The settled shape keeps
  both properties:
  - The doc crate's `DocumentCache` **renames to `IndexedDocument`** —
    it is not a cache at the view level, it is a document with its
    indexes — and splits internally: `IndexedDocument<'a, R> { doc:
    &'a Document<R>, index: &'a DocIndex }` (the view) over
    `DocIndex` (the owned id-maps, no borrow).
  - `DocIndex` persists app-side beside the other derived caches —
    the third instance of the owned, stamp-gated pattern
    (`CachedIndex`, `PinAccents`) — and **its pairing constructor is
    the only way to build the view**: `index.view(doc) ->
    IndexedDocument<'_>` refreshes against the exact document it
    bundles, gated on the document's stamp, so an un-paired view is
    unwritable and the rebuild happens only when a commit lands
    (local submit, foreign delivery, undo/redo — almost never on a
    frame timescale). The stamp: `Document` mints a
    `Generation`-style per-value stamp when a fold produces a new
    value (the legacy `document/model.rs` trick — equal stamps imply
    identical content; safer than encoding session internals like
    `(confirmed rev, pending set)` into the app). `Document::cache()`
    survives for ad-hoc use (tests, emitter unit tests) by building a
    fresh `DocIndex` and viewing it.
  - `Drawing<'a>` holds the view as **one member**: `Drawing {
    indexed: IndexedDocument<'a, Provisional>, path, presentation: &'a mut
    Presentation, sink: &'a mut CommitBuilder }`.
  - The emitters currently take `doc` *and* `cache` — redundant once
    named honestly, since the view carries the document. Their
    signatures narrow to `&IndexedDocument<'_, Provisional>`.
  - Because the rename + split + stamp + signature narrowing is
    doc-crate and emitter work, fully testable, it lands **green
    before the red series** as commit 12·0 below — shrinking 12·2.

  Readers project registers; the waist's setters become one-line calls
  into `src/edit/`'s emitters pushing into `sink`; gesture ends
  (`DragStopped` arms, click commits) seal and `link.submit(...)`. The
  reroute-after-commit cycle inverts: the solver re-materializes from
  the *next* frame's optimistic document (steps 7/8 made that safe —
  geometry lives in the presentation layer, previews are pure).
- **F3 — presentation re-keys and the scoping wrapper collapses**
  *(the module renamed `derived` → `presentation` 2026-08-19, user
  review: the old name said how it is computed, not what it is for).*
  `ScopedRoutes` becomes the flat `HashMap<RouteId, RouteGeometry>` it
  was always going to be once ids are globally unique (recorded at the
  2026-08-19 fix); `PinAccents` re-keys to `(BlockId, PinId)`;
  `TextExtents` to the new `TextId`. `Presentation` itself survives
  unchanged in role.
- **F4 — the scope path.** `BlockPath` re-types over `BlockId`, and the
  document root is `BlockId::NULL` — the same scope vocabulary the
  emitters already speak. `top_id` reads become `title_block().top` /
  parent-NULL roots via the cache.
- **F5 — files at the flag day.** Save paths and autosave die with the
  container (dormant, deleted in phase 7); the log owns persistence.
  Opening an existing `.kdl`/`.bwx` from the CLI **keeps working
  read-only through the bridge** — the dev loop needs to look at
  existing diagrams, and the bridge exists regardless — with the title
  carrying D5's now-true "nothing persisted". This is a courtesy load,
  not a shipped importer; D8's hard break stands and phase 7 makes the
  final format call. (Alternative considered: refuse file arguments
  outright. Rejected for the dev loop's sake; flip at review if the
  courtesy reads as a promise.)
- **F6 — undo.** The `Undoer`, `EditorState`, the quiescence snapshot,
  document autosave, and the history-restore feature all die (Restore
  History was already deferred as subsumed by the log). `Undo`/`Redo`
  call `session.undo()/redo()` through a `Link` passthrough that ships
  `last_submission()` exactly like `submit`; availability reads
  `can_undo/can_redo`.
- **F7 — what stays alive but unplugged.** `src/document/` (the legacy
  model), `src/schema/` + `schema_convert` (the bridge's input and
  oracle), and the KDL parser survive step 12 compiling — the bridge
  and the tutorial-level embeds need them — but nothing in the editor
  reads a legacy `Document` after the series. Phase 7 demolishes.
  `src/edit/`'s and `src/schema/lower.rs`'s `allow(dead_code)` lids
  come off in this series and must not return.
- **F9 — the root is a scope like any other** *(added 2026-08-19 at
  12·2 launch; the read re-point hits it immediately)*. The editor's
  current scope is a `BlockId`, `NULL` at the document root (F4). For
  scope reads to be uniform, `DocIndex` indexes `NULL` as a root
  `BlockIndex` row (its `children` = the `top_level` set; its other
  sets hold any root-owned entities), and the fold's `validate` must
  accept `NULL` owners for scoped entities wherever it would accept a
  real block (verify, adjust, test — bridge-lowered documents always
  carry a real top block, so `NULL` scope content only arises in a
  fresh serverless boot; no bootstrap commit is needed and none is
  wanted). The alternative — auto-submitting a root-block commit on
  empty boot — was rejected: it invents document content the user
  never authored.
- **F8 — golden basis.** The tutorial replay goldens currently compare
  through the legacy serialization. Post-swap the comparison basis is a
  **canonical projection of the new document** (the doc crate's
  `fixtures::projection` grown as needed). Re-cutting those goldens is
  an acceptance step whose diff is reviewed as behavior, not silenced —
  the replayed *gestures* must produce the same diagrams they do today,
  and the review is where that claim is checked cue by cue.

## The series

Commits are numbered 12·0…12·6. 12·0 is an ordinary green commit
(standard rules — CI green, one commit); the red-series rules begin at
12·1. "Red budget" = the residual-red inventory the commit may leave,
named by area; 12·5 must reach zero and 12·6 must be green CI.

### 12·0 — green pre-series: `IndexedDocument` (F2's rename/split/stamp)

**Landed 2026-08-19.** `IndexedDocument` is `#[non_exhaustive]` (read
the fields, never build the literal); `DocIndex::of`/`::view` are the
only doors, `view` rebuilding iff `DocStamp` moved; `Document::cache()`
deleted — it cannot exist under the split. Predict mints (its content
can differ; it runs only when a commit lands). 38 emitter signatures
narrowed; doc-only emitters stay doc-only — an emitter takes exactly
the state it reads. Probe-based rebuild test (sentinel erasure, not a
counter). CI green.

`DocumentCache` renames to `IndexedDocument` and splits into view over
owned `DocIndex` with the pairing constructor; `Document` mints the
per-value stamp; the emitters' `doc`+`cache` parameter pairs narrow to
`&IndexedDocument`. Doc-crate + `src/edit/` only; every existing test
re-points mechanically; `cargo xtask ci` green.

### 12·1 — ids re-type (mechanical; the compiler starts driving)

**Landed 2026-08-19.** 544 red sites over 39 files, every one audited
as a legacy-document read/write (446 id-type mismatches, 54
owner-lookups awaiting 12·2 reads, 36 map-key mismatches, 14
port-vs-pin discriminations that became document reads). Doc crate
green throughout; `Id<K>` gained the compact `Debug` (= `Display`
spelling) the mutation log's `?id` sites rely on. `LineAnchor`
collapse rule: constructors rewrote to the pin id, patterns stay red
as 12·2 reads. `LegacyPinId` alias marks the `BaseShape` signatures
12·2 re-points; `src/script/` had no id positions (cue resolution is
12·5's).

`ShapeId`, `Deletable`, selections, `hit_target`, `spatial`, `nav_tree`,
tool state, and every signature re-type onto `Id<K>`; `LineAnchor`'s
sites collapse to `PinId`; `WaypointId` leaves the edit session for
ordinals; `store.rs` shrinks per F1. No read/write re-pointing yet — the
legacy document references go red where they can no longer type-check
and stay red. **Red budget:** everything document-facing; the commit
message carries the count per file so 12·2/12·3 can consume it.

### 12·2 — the structural core (Drawing, App, path, presentation, F9)

**Landed 2026-08-19 — and re-scoped by measurement.** The agent stopped
and reported rather than half-porting: landing the core exposed 566
read sites the id flip could not see (field/method errors only
reachable once `Drawing` stopped carrying a legacy document), taking
the inventory 544 → 1110. Three unnamed commits were hiding inside
"the reads re-point", split out below as 12·2a–c. What landed and
verified (doc crate green, 111 tests; fmt clean): F9 with `top_level`
folded into the root row and `scope()`/`is_live_block()` separating
the two questions the old field conflated; F4's `BlockPath` over
`BlockId` moved out of the legacy model to `src/path.rs`; F3's re-keys
— the route-scope wrapper collapsed flat, and `PinAccents` collapsed
the legacy pin/port-pin pair into ONE map keyed `(scope, pin)` (both
were always "a stub drawn in scope S takes S's wires' accent"),
recomputed in `chronological` order and gated on `DocStamp`; F2's
`Drawing`/`App` re-shape exactly as specified (the four disjoint field
borrows compile as written; the spatial-index member was kept —
dropping it would have been an unauthorized culling change);
`EditorState` deleted; `pin_shape(PinId)` is the single resolver for
the port-vs-pin discrimination `LineAnchor` used to encode.

### 12·2a — the shape layer ports; grid.rs collapses

`src/shape/` (BaseShape's ~40 methods, block.rs, port.rs, text_box.rs,
comment.rs, image.rs) re-target the doc-crate entities — a port, not a
re-point: pins are separate entities threaded from the scope index,
labels are `Label` namespaces, the icon is one atomic value.
`src/grid.rs`'s legacy-geometry conversion traits collapse into
`src/edit/lower.rs`'s doc-geometry functions (DRY: one conversion
layer), consumers moving with them. `src/render/` follows the shape
layer. **This is the gate: nothing else type-checks until ShapeRef
names doc-crate entities.**

**Landed 2026-08-19.** `ShapeRef` names bundling wrappers (`BlockShape
{ block, pins }`, `PortShape`, `TextShape` with its extent, `Artwork`
unifying image and icon — the same (rect, asset) pair). The mutating
half of the shape layer (`ShapeRefMut`, `apply_resize`, `move_by`,
`add_pin`, …) is GONE, not moved: a mutable shape reference is
unrepresentable over `&Document`, and every such policy already lives
as an emitter. `grid.rs` keeps px policy with zero legacy-type
dependence; the ext traits parked in `src/document/coord.rs` for the
dormant model; `default_port_rect` delegates through an adapter so the
placement formula stays single-copy. Dropped legacy-type tests are
enumerated with their doc-fixture replacements. Gate met: shape,
render, grid, lower, canvas, presentation, document, schema all
zero-error; inventory 1004 → 1283 dedup, the growth again being reads
that only type-checked through the legacy shape layer.

### 12·2b — Drawing's readers; hit-test, spatial, nav, scene

The ~50 `current()` call sites re-shape onto the F9 `Option` (the root
is a scope without a block entity); hit-testing, `spatial`, `nav_tree`,
and the scene passes re-point onto the ported shape layer with
`chronological()` as the draw/hit order.

### 12·2c — the solver and tool reads

The preview funnel (`widget/routing.rs`, `materialize.rs`, the widget
`auto_route.rs`, `movement.rs` reads) re-points: routes read `from`/
`to` and the `waypoints` register; anchor positions from pin entities;
the pure policies reuse `src/edit/geometry.rs`'s (never re-ported).
Tool READ sites across `src/tools/` follow.

**12·2c-i landed 2026-08-19** (the funnel; stopped at a clean boundary
per the discipline — 12·2c-ii below remains). `AutoRoute` is gone from
every live read path: a wire is the doc `Route` plus its `RouteLabel`
entities, bundled as `Wire { route, labels }` (the `BlockShape` twin).
The `FracVal`/`LinearDistance` boundary is two `lower` functions,
exact through `f32` because every stored value originates as one.
One-rule-per-question: the preview readers now delegate to the emitter
policies (`move_blocked`, `group_move_blocked`, `placement`,
`trimmed_approach`-derived ordinals, new `backtracking_ordinals`,
`riding_pins`), with `From<ShapeId> for edit::geometry::Shape` so
preview and commit cannot disagree (outright unification is a
candidate for later). The 12·3 work order is five named write doors:
`route_mut`, `write_waypoints`, `write_geometry`'s write-back (fate
deliberately undecided), `promote_corners_to_waypoints`,
`reanchor_labels` — plus drawing.rs's setter half. Router leg labels
re-keyed `WaypointId` → `PathOrdinal`. New ratification entry: route
hit/crossing order is now `chronological()` (the route half of the
12·2b unification). Inventory 1201 → 972.

### 12·2c-ii — tool reads, app read wiring, the four test files

The ~25 `src/tools/*.rs` READ sites, `app.rs`'s read wiring, and
`render_path_tests` / `closed_router_tests` / `render_bench` /
`drag_abort_tests` re-express. Write arms stay red for 12·3.

**Landed 2026-08-19.** 972 → 517 (12·3: 454, 12·4: 38, 12·5: 25 —
zero unclassified). Three of the four test files ported whole; the
drag-abort suite's purity spelling is now `DocStamp` unchanged + the
gesture sink sealing to `None` (`Document` has no `PartialEq`, and
sealed-empty is the doc-model's honest "nothing changed"). One export
case left red whole (needs 12·3's paste), one golden line waits on
F8. The uuid world retired the route-id collision regression test —
its precondition is structurally unconstructible — replaced by a live
scope-separation assertion. Five additive `Drawing` read helpers;
no accessor gaps. The 12·3 work order is complete: the five write
doors, drawing.rs's 43 setters + finalize_load's materialize,
clipboard.rs wholesale, movement.rs's eleven writes, the wrap-top
test, and every tool commit arm by name.

`EditorState` dies; `App` drives `Drawing` per F2 over the session;
every *reader* re-points (rect/register projections, the 35 `.inner`
render/shape sites, hit-testing, spatial index, nav tree, render
passes); presentation re-keys per F3; the path per F4. Setters are stubbed
red on purpose — reads first, so the diff that changes what the editor
*sees* is separable from the diff that changes what it *does*. **Red
budget:** the waist's setter interiors and every gesture-commit arm.

### 12·3 — writes become emitters; gestures seal and submit

The 59-function waist's setter bodies become `src/edit/` emitter calls
into `sink`; `mutate_and_reroute` becomes gesture-scoped
seal-and-submit through `Link`; `DragStopped`/click arms seal; the
solver's commit-pass entry points re-point to the post-submit document.
The mutation log (`target: "edit"`) moves with the setters — the `waist`
CI gate's floor must survive the series. **Red budget:** undo,
projections, and any straggler the inventory names.

### 12·3 — writes become emitters; gestures seal and submit — LANDED

**Landed 2026-08-19** (517 → 65: 38 undo, 27 projections; zero
unclassified). `App::begin_gesture`/`end_gesture` bracket the sink
(closure form `commit_gesture`); the whole canvas pass rides one
gesture labelled by the active tool, so no tool needed its own;
`dispatch_action` labels from `Action::label()` carrying `CommandId`
names. The solve rider (`routing::solve_rider`) folds ops-so-far onto
a scratch prediction and authors promoted corner lists + label
re-anchors in the same commit — the five write doors resolved
(route_mut/write_waypoints deleted; write_geometry split;
promote pure-return; reanchor as the anchors slice).
`Presentation::refresh_routes` (stamp-gated) replaces finalize_load
and every gesture-end commit pass; foreign commits' wires appear with
no local call. Clipboard is v2 end to end — nothing left for 12·5.
Nine setters mint ids; the waist gate holds at 34 events.

Its review pass found and fixed six real bugs with tests: the
document root refusing a port (REVERSES 12·2b's settled answer — that
settlement conflated "missing block declines" with "root declines";
`owner_scope` now distinguishes them, and AddPort works at a
serverless boot); group move and reroute_block reaching a moved
block's interior wires through the document-wide adjacency; deleted
wires leaking geometry; text-extent and add_named_pin scope slips.
New ratification entries: a route edited through an obstacle
re-solves in the same commit (legacy deferred to the next gesture);
the lock unification reached move_pin and the flips.

### 12·4 — undo re-points; the snapshot machinery dies (F6)

**Landed 2026-08-19** (65 → 22, all 12·5). The Undoer complex, the
quiescence snapshot, autosave, the save paths, ContainerLock ("a lock
serializes writes; there are none, and claiming one is a write"), and
the history/timeline feature died app-side; src/storage stays whole
for phase 7, with the headless convert and F5's courtesy-load readers
kept. Link::undo/redo go through one journal(step) seam whose arms
differ only in ship() — which submit now also uses — verified by
standalone Link tests against the doc crate. summary() became total:
with the local arm speaking, None is unrepresentable, and the title
says "[nothing persisted]" — D5's marker lands here, true at last.
Tutorial mode's interim shape is { session }: nothing parked, because
parking is a move that must mint the level's LocalHost in the same
breath — 12·5's edit. Carried to 12·6's sweep: the orphaned
tools-side timeline surface; --no-write is moot, retired in phase 7.

### 12·5 — projections and the file boundary

Cue/script resolution reads the new document through a thin query layer
(block by title, geometry in cells, pin slots — the fields the step-11
comparison already enumerated); tutorial load lowers through the bridge
and **each level opens as its own session** (a second `LocalHost`
seeded from the lowered log; exit drops it, restart re-welcomes — the
user's session is never touched, and `exit_tutorial`'s document
swap/restore machinery dies instead of being re-pointed; spec §13a's
first near-term item); the clipboard's OS surface speaks v2; export
keeps only what does not read the dying format (enumerated in the
commit message); the CLI file argument follows F5. **Red budget: zero
— the workspace compiles.**

**Landed 2026-08-19 — the red series ends: 22 → 0, workspace-wide
zero errors, linkage proven.** The cue query layer is `CueScope`
beside the cues (Drawing is scope-bound; cues name blocks anywhere);
the bridge returns `Lowered { commits, ids: SourceIds }` so file-id
cue targets ("b1:p1") resolve — the table it always built and threw
away. Tutorials are session-isolated per §13a: enter parks the user's
Link+path, load_level lowers into a fresh LocalHost, exit restores
verbatim, restart re-welcomes. The F5 boundary: open → lower → seed
the boot LocalHost, read-only; an empty path boots an EMPTY session
(the legacy default's invented top block was F9's rejected
alternative, caught and fixed); documents open at `BlockPath::opening`
(inside their top), not the root. Export lost Json/Kdl and the
SVG-embed round-trip (the payload WAS the legacy format); rendering
and selection export stay, the latter folding a v2 paste commit.
The gesture bracket extracted to src/gesture.rs, shared by app and
the scripted driver.

**Carried to 12·6, flagged loudly: B1 —** a gesture cannot read its
own writes (Drawing reads optimistic, which advances at submit), so
create-then-edit flows break (a placed block no longer opens its
title editor; the tutorial replay's sole diff is "Untitled" vs
"CPU"). Tests: 644 pass / 24 fail, classified B1 (structural), B2
(preview/commit parity preconditions), B3 (one lock test). F8
resolution: the golden basis is the id-free projection through the
bridge — the stored goldens stay valid inputs, no re-cut needed, but
regeneration needs a new spelling since nothing serializes the new
document to KDL.

### 12·6 — green: goldens, gates, and the convergence proof

**Landed 2026-08-19 — `cargo xtask ci` exit 0, independently verified.
The series is green.** B1 resolved by deferred resolution: four tools
grew an `Arming` state carrying the minted id, resolving through the
existing constructor on their first frame and falling through in the
SAME frame, with honest fallbacks; one unnamed site found by survey
(resize_block's new-pin click); route_tool correctly left alone (its
pin settles a frame earlier). B2 was ENTIRELY fixture staleness — zero
funnel regressions; several vacuously-passing tests repaired en route,
and one fixture drift (label side) fixed to restore the SVG golden
byte-for-byte. B3 resolved for the landed lock rule (the fixture's
default hid the tag). The sweep: src/edit's dead-code lid is OFF; the
dormant legacy lidded with phase-7 justification; the timeline surface
and other orphans deleted; superseded emitter sugar (create::waypoint,
the per-kind delete wrappers) deleted with coverage moved. The waist
gate re-aimed at what can actually widen — Drawing's borrowed-field
visibility and signature-type greps instead of dead method-name greps
— both proven to bite by perturbation; the edit-event floor covers all
of src/widget at 35 (43 found). F8: the golden IS the projection now
(`*.golden.txt`); the re-cut is provably behavior-neutral (regenerated
byte-identically from the old goldens' own right-hand side). The
convergence proof: for every shipped level, real tools drive one side
against a LocalHost and a second client joins mid-session through the
real welcome path, matching by content_hash and rev. Deferred to step
13: RerouteTarget::Block's two-rules conflict (waist scopes the reroute
fan-out to the current level, the emitter reaches document-wide);
RouteEditSession/waypoint-insertion arithmetic unification; one
now-trivial lazy_edge_drag assertion.

`cargo xtask ci` green: clippy sweep, the `waist` gate re-aimed at the
new setter surface, dead-code lids off (F7), tutorial replay goldens
re-cut onto the F8 projection basis and reviewed cue by cue. Plus the
series' proof obligations:

- tutorial golden replays green over the **lowered** levels;
- the convergence proof: the reconcile suite's two-client shape re-run
  with *this* client driving one side — `script::Session` gestures
  through the real tools against a `LocalHost`, a second `ClientSession`
  fed the host's log, both converging by content hash;
- the drag-abort purity suite (step 8d) still passes — now proving
  aborted gestures submit nothing (the sealed-empty property end to
  end).

## Ratification queue (carried to step 13)

The behavior changes enumerated in steps 10–11's commits land for the
user's decision at step 13, unchanged by this series: lock-guard
unification on `Slot`/`FlipLR` writes vs the ported lock
inconsistencies (tag visibility, direction cycling); the wrap-top
refit; port-slot units; auto-name ordinals; the F5 file boundary; the
F8 golden re-cut diffs. Added by the series itself: the 12·2b
hit-order unification (within a layer the top-painted shape wins a
hit — the legacy took the oldest for blocks/ports/texts but the
newest for icons/comments/images) and the `can_relocate_pins` deltas
(own-scope pins accepted, capacity in cells, locked owners refused —
the last being the queued lock unification seen from the preview
side). And 12·2c's route half of the same: route hit and
crossing-hop order is `chronological()` where the legacy used map
insertion order. From 12·2c-ii: the breadcrumb spells the root as
`""` rather than `"top"` under F9 (parse no longer accepts an omitted
root prefix; displayed paths still round-trip), and the bridge drops a
route whose endpoint pin is absent where the legacy loader kept it
dangling (demo.json carries one).

**How two of these get enforced, not merely decided**
(`docs/type-level-invariants.md`, P7 and P5). The lock-guard unification and
`RerouteTarget::Block`'s two-rules conflict are both queued as decisions, and
both have a construction that makes the decision stick: an `Unlocked(BlockId)`
capability minted in one place and required by every interface-mutating
emitter, so the audit becomes a compile-error list; and a scoped
`IndexedDocument::scope(scope)` view distinct from an explicitly-named
`document_wide()`, so a scope-blind fan-out cannot be the path of least
resistance. Ratifying the rules and then separately deciding how to hold them
is doing the work twice — step 13 should land both halves together.

## After the series (queued, not part of step 12)

- **Commit-driven cue advancement** (spec §13a's second near-term
  item): tutorial step completion becomes a predicate over the
  incoming commit stream — typed ops plus labels — instead of polled
  document-state diffing. Deliberately excluded from the series
  because it changes tutorial *behavior*, and the series carries none;
  it lands as an ordinary green commit once 12·6 proves the swapped
  engine.
- The presence-channel gesture streaming and the tutorial-ghost
  unification stay deferred with spec §13a; nothing in this series
  forecloses them (the preview vocabulary is the message vocabulary).
