# Spotlight audit — every action, its label, and its change ring

A checklist for testing the "what changed" feature by hand. Fill in the two
**Notes** columns per row; leave a row blank if it behaved as expected.

## How to test one row

The ring never fires on the edit itself. It fires on three things, so each
action is three trials:

1. **Do the action.** Read its label in the history panel (the *Label*
   column below says what the code composes).
2. **Undo** (⌘Z). The ring should light where the thing *stood before the
   undo* — i.e. the edited state — and the camera should bring it into view.
   Then **Redo**: the ring lights where it stood before the redo.
3. **Pick the row** in the history panel. The lens shows that rev, the
   camera goes to the framing you had when you made the edit (recorded at
   seal), the scope opens if it differs, and the ring lights the region.

Write down: the label text as shown; whether a ring appeared; where it
appeared relative to the thing you changed; whether the camera moved
sensibly; and whether the scope opened (for edits made inside a block).

## What the code intends

**Label** = `<Verb> <object>`. It no longer carries ` in <scope>`: §8.1
gives the scope its own line on every history row, read from the row's
recorded `scope_names`, so the label would have said it twice. The verb is
the tool's (`Add`, `Move`, `Rename`, `Retype`,
`Create`, `Resize`, `Modify`, `Edit`) or the palette command's slug
(`delete`, `accent`, `io`, `cut`, `hide-tags`, `show-tags`, `flip-lr`,
`flip-ud`, `lock`, `unlock`, `paste`, `reroute`, `reroute-block`, `nudge`,
`import`), capitalised. The object is the entity named by the ops: `block
“Filter”`, `untitled block`, `pin “clk”`, `route from Filter:out to c:in`,
`text to “first line…”`, `image`, `area “group”`, `route label`, or `N
blocks` / `N shapes` when several. A rename says `… “old” to “new”`, or
`Name block “new”` when there was no old name.

**Ring region** = the union of the footprints of every entity the row's
`touched` list names, read from the document *before* the step, filtered to
the row's recorded scope:

| Entity | Footprint |
|---|---|
| block | its rect |
| pin | one grid cell at its slot anchor on the owner's boundary — so it belongs to the owner's *parent* scope |
| route | the solved polyline's bounds; if none is solved, its endpoints and waypoints |
| route label | its route's footprint |
| text | one cell at its anchor |
| area | its rect |
| image | its rect, rounded to grid |
| the diagram itself (rename), an asset | nothing |

A subject whose scope is not the recorded scope contributes nothing, and a
row whose subjects all fall out produces **no ring at all** — that is the
first thing to watch for on pins and on anything done at the root.

`touched` is capped at 64 names; a bigger selection is truncated and the
ring covers only the first 64.

## The rows

The **Label** column is what the code produces today, taken from
`crates/tools/src/label_audit_tests.rs`: one test per row, each driving the real
setter through the write waist under the verb the real path uses, and
sealing by the same path `App::commit_gesture` seals by. The **Test**
column names it. When a label is corrected, `UPDATE_EXPECT=1 cargo test
--lib label_audit` rewrites the goldens and the diff is the review.

The test scene: blocks **A** and **B** at the root, wired A:X → B:Y by a
route named **clk** carrying one label; an untitled block inside A; a text
box “a note”, an area titled “note”, an image and a bare block **D** at
the root; B wears an icon; Y carries the tag J2.

Fill in the last two columns. `S` marks a row where the code's own rules
predict something odd; the prediction is in the *Expected ring* column.

### Create

| # | Action (how) | Label (actual) | Test | Expected ring | Notes: label | Notes: ring |
|---|---|---|---|---|---|---|
| C1 | New Block tool, drag a rect | `Add block “Block 1”` (inside a block, the same — the row says where) | `c1_new_block`, `c1b_…` | the block's rect | | |
| C2 | Area tool, drag | `Add area “Untitled”` | `c2_new_area` | the area's rect | | |
| C3 | Add Text tool, click | `Add untitled text` (the typed text lands as a separate Edit, N12) | `c3_add_text` | one cell at the anchor | | |
| C4 | Add Image tool (file), place | `Add image` | `c4_add_image` | the image's rect | | |
| C5 | Add Icon tool on a block | `Add block “A”` | `c5_add_icon` | the block's rect | | |
| C6 | Add Port tool on the current block's own edge | `Add pin “Port 1”` | `c6_add_port_on_the_current_block` | S — the port's scope is one level up; expect **no ring** at this level | | |
| C7 | Add a pin to a child block's edge (Resize Block tool, click the edge) | `Resize pin “Port 1”` | `c7_add_pin_on_a_child_blocks_edge` | one cell at the slot anchor | | |
| C8 | Route tool, pin to pin | `Create route from B:Y to A:X` | `c8_create_route_pin_to_pin` | the wire's polyline bounds | | |
| C9 | Route tool ending on a free block edge (mints a pin) | `Create 2 shapes` | `c9_create_route_onto_a_free_edge_mints_a_pin` | S — pin and route; the pin may be filtered by scope | | |
| C10 | Add Route Label tool, click a wire | `Add route label` | `c10_add_route_label` | the wire's polyline bounds | | |
| C11 | Paste a copied selection (a block and a text) | `Paste 3 shapes` (the block's pin travels with it) | `c11_paste_a_copied_selection` | union of everything pasted | | |
| C12 | Paste copied pins onto a block | `Paste pin “X”` | `c12_paste_copied_pins` | S — one scope up from the target block | | |
| C13 | Go Up at the root (Wrap Top) | *not reachable*: the `wrap_top` emitter is unhooked from the UI (`drawing.rs`) | — | — | | |
| C14 | Import a `.json` diagram (menu) or paste an export | `Insert document <name> …` — a verbatim label, not composed; no test | — | the inserted block's rect | | |

### Update — geometry

| # | Action (how) | Label (actual) | Test | Expected ring | Notes: label | Notes: ring |
|---|---|---|---|---|---|---|
| U1 | Move Block (drag) | `Move block “A”` — inside a block: `Move untitled block` | `u1_move_block`, `u1b_…` | the rect *before the step* | | |
| U2 | Move a multi-selection (two blocks and a text) | `Edit 4 shapes` (the Multi Select tool's verb is Edit; the wire between them is counted) | `u2_move_a_multi_selection` | union of all rects | | |
| U3 | Arrow-key nudge of a shape | `Nudge block “A”` | `u3_keyboard_nudge` | the rect | | |
| U4 | Resize Block (drag a corner/edge) | `Resize block “A”` | `u4_resize_block` | the rect | | |
| U5 | Move Pin (drag along the edge) | `Move pin “X”` | `u5_move_pin` | one cell at the slot anchor | | |
| U6 | Move several pins (Multi Pin Select, drag) | `Move 2 pins` | `u6_move_several_pins` | union of the slot cells | | |
| U7 | Arrow-key nudge of pins | `Nudge pin “X”` | `u7_nudge_pins` | slot cells | | |
| U8 | flip-lr (palette) on a block | `Flip-lr pin “X”` (the block is not named; one pin is) | `u8_flip_pins_left_right` | S — the pins' scope is the block's parent | | |
| U9 | flip-ud (palette) on a block | `Flip-ud pin “X”` | `u9_flip_block_vertical` | as U8 | | |
| U10 | Move Title (drag the title) | `Move block “A”` | `u10_move_title` | the block's rect | | |
| U11 | Move Block Type label | `Move block “A”` | `u11_move_block_type_label` | the block's rect | | |
| U12 | Move a wire label along the wire | `Move route label` | `u12_move_wire_label` | the wire's polyline bounds | | |
| U13 | Edit Route (drag a wire segment or corner) | `Modify route from A:X to B:Y` per `describe.rs`'s own test; the drag session is not driven here | — | the wire's polyline bounds | | |
| U14 | reroute (palette) on a wire | `Reroute route “clk” from A:X to B:Y` | `u14_reroute_a_wire` | polyline bounds | | |
| U15 | reroute-block (palette) | `Reroute-block route “clk” from A:X to B:Y` (names one wire, not the block) | `u15_reroute_a_blocks_wires` | S — union of every wire on the block | | |

### Update — names, text, attributes

| # | Action (how) | Label (actual) | Test | Expected ring | Notes: label | Notes: ring |
|---|---|---|---|---|---|---|
| N1 | Rename Title | `Rename block “A” to “Amp”` — first name: `Name block “Mixer”` | `n1_rename_title`, `n1b_…` | the block's rect | | |
| N2 | Rename Block Type (block had none) | `Name block type “Amplifier”` | `n2_rename_block_type` | the block's rect | | |
| N3 | Rename Pin (name field) | `Rename pin “X” to “clk”` | `n3_rename_pin` | S — slot cell, one scope up | | |
| N4 | Pin tag field (pin had none) | `Name pin tag “J1”` | `n4_set_pin_tag` | as N3 | | |
| N5 | Retype Pin (pin had no type) | `Name pin type “bit”` | `n5_retype_pin` | as N3 | | |
| N6 | Click a selected pin's stub (cycle direction) | `Edit pin “X”` | `n6_cycle_pin_direction` | as N3 | | |
| N7 | io (palette) — bulk pin direction | `Io 2 pins` | `n7_set_pin_direction_in_bulk` | as N3 | | |
| N8 | show-tags / hide-tags (palette) | `Show-tags pin “X”` / `Hide-tags pin “Y”` (hide acts on a port, from inside its block) | `n8_show_pin_tags`, `n8b_…` | as N3 | | |
| N9 | accent (palette) | `Accent block “A”` / `Accent route “clk” from A:X to B:Y` / `Accent pin “X”` / `Accent area “note”` / `Accent text “a note”` | `n9_…`, `n9b_…`, `n9c_…`, `n9d_…` | the entity's footprint | | |
| N10 | lock / unlock (palette) | `Lock block “A”` / `Unlock block “A”` | `n10_lock_and_unlock_a_block` | the block's rect | | |
| N11 | Rename Route (double-click a wire label) | `Rename route “clk” to “clock”` | `n11_rename_route` | the wire's polyline bounds | | |
| N12 | Edit Text (two lines typed) | `Edit text to “a longer note with a second line”` (the newline is flattened) | `n12_edit_text` | one cell at the anchor | | |
| N13 | Rename the diagram (title block) | `Rename document …`? — no write-waist setter; not driven here | — | **no ring** | | |

### Delete

| # | Action (how) | Label (actual) | Test | Expected ring | Notes: label | Notes: ring |
|---|---|---|---|---|---|---|
| D1 | Delete a block (no wires) | `Delete block “D”` | `d1_delete_a_bare_block` | the rect where it stood | | |
| D2 | Delete a block with wires on it | `Delete 6 shapes` (block, its pin, its child block and child's pin, the wire, the wire's label) | `d2_delete_a_wired_block` | S — union of the cascade, can span the diagram | | |
| D3 | Delete a port on the current block | `Delete 3 shapes` (port, wire, label) | `d3_delete_a_port_of_the_current_block` | S — one scope up; expect no ring here | | |
| D4 | Delete pins on child blocks | `Delete 4 shapes` (two pins, wire, label) | `d4_delete_pins_on_a_child_block` | slot cells | | |
| D5 | Delete a text / area / image | `Delete text “a note”` / `Delete area “note”` / `Delete image` | `d5_delete_a_text_an_area_and_an_image` | its footprint | | |
| D6 | Delete a block's icon | `Delete block “B”` (the block survives) | `d6_delete_an_icon` | the block's rect | | |
| D7 | Delete a wire (carrying a label) | `Delete 2 shapes` | `d7_delete_a_wire` | polyline bounds | | |
| D8 | Delete a marquee selection (a block and a text) | `Delete 2 shapes` | `d8_delete_a_selection` | union | | |
| D9 | Cut a selection | `Cut block “D”` | `d9_cut_a_selection` | union | | |
| D10 | Cut pins (a wired pin) | `Cut 3 shapes` | `d10_cut_pins` | slot cells | | |
| D11 | Clear a wire label's text (deletes the label) | `Rename 2 shapes` | `d11_clearing_a_wire_label_deletes_it` | polyline bounds | | |
| D12 | Empty a text box (deletes it) | `Edit text “a note”` | `d12_emptying_a_text_box_deletes_it` | one cell | | |

### Steps and history

| # | Action (how) | Label (actual) | Test | Expected ring | Notes: label | Notes: ring |
|---|---|---|---|---|---|---|
| H1 | Undo any of the above | `Undo <original label>` | — | as the original's row (the undo row copies its `touched`) | | |
| H2 | Redo | `Redo <original label>` | — | as H1 | | |
| H3 | Tag a rev in the history panel | `<tag name>` row, kind *tag* | — | **no ring**, no document change | | |
| H4 | Pick an old row after quitting and reopening | the label as saved | — | the ring, the recorded camera and scope from the manifest row | | |
| H5 | An edit made two levels deep, then picked from the root | the row's scope line reads the path | — | the scope should open, then the ring | | |

## What the labels already show, before any hand testing

- **Cascades are counted, not named.** Deleting a wired block says
  `Delete 6 shapes`; deleting one wire with a label says `Delete 2 shapes`;
  clearing a label says `Rename 2 shapes`. The subject the hand chose is
  lost in the count.
- **Palette commands seal under their slug**, capitalised: `Flip-lr`,
  `Flip-ud`, `Io`, `Show-tags`, `Hide-tags`, `Reroute-block`.
- **The verb is the tool's, not the act's.** A pin added with the Resize
  Block tool is `Resize pin`; a group moved with Multi Select is `Edit 4
  shapes`; an emptied text box is `Edit text`, a cleared label is `Rename`.
- **A block field edit names the block.** Adding or deleting an icon is
  `Add block “A”` / `Delete block “B”`; moving a title or type label is
  `Move block “A”`.
- **Three spellings of "new and unnamed":** `Add block “Block 1”`,
  `Add area “Untitled”`, `Add untitled text`.
- **Block-wide pin operations name one pin:** `Flip-lr pin “X”`,
  `Reroute-block route “clk” …`.
- **Endpoint order follows the drag**, so the same wire reads
  `from B:Y to A:X` when drawn from B.
- **The scope is no longer in the label.** It is the row's own second
  line now (§8.1), elided from the left so the leaf survives.
## Things to record once, not per row

- Ring look: colour, thickness, fade time (1.5 s), whether it reads on a
  busy diagram.
- Camera: does the framing zoom too far in for a one-cell pin spot? Too far
  out for a whole-diagram delete?
- Scope changes: is arriving one level in/out disorienting?
