Merge gh/main 08ad7b81 into fix/wpxr-empty-record-view

Every write the merged PRs added now names a view, chosen deliberately:

- The keyboard commit names OpenedCell::view, captured when the editor
  opened, as EditTarget does for the mouse (context/code/clients.md).
- RenameRecordCoordinate names no view. Its record must hold cells, so it
  resolves in the store alone: NamedRecord::Stored, as does the rename
  destination records_at_stake adds. named_records replaces cells_named as
  the exhaustive list records_named is read off.
- #252/#265's non_item_refusal moves into Grids::unwritable. The strict view
  lookup (a missing view is RecordGone) and the Grids memo are kept.
- Test fixtures name a view their document has: Default for host() and
  host_on_a_fold(), the records view for the rename pair, and the viewing
  client's own view in browser_round_trip.

Evidence files: both sides kept, every heading of both parents verified
present. Test names: the merge holds every test of both parents.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
This commit is contained in:
Edward Langley
2026-09-24 12:53:03 -07:00
82 changed files with 16935 additions and 1363 deletions
+50
View File
@@ -1192,6 +1192,49 @@ compares **content in order**: the drawn keys rows must equal
injection now fails. What still passes is a bypass that draws exactly what the
builders asked for, in the order they asked — which is harmless by definition.
### tile_select_opens_on_a_regular_category_even_when_a_virtual_holds_row
improvise-52od. `opening_tile`'s rule is "the best-ranked tile", and in every
earlier fixture its top rank, the first Regular tile, was also the first tile on
Row. So the rival `position(|t| t.axis == Axis::Row).unwrap_or(0)`, which needs
no wire field at all, passed the whole workspace. The fixture is a records-shaped
bar: `_Index` on Row, `_Dim` on Column, `_Measure` on None, `Region` on Page and
`Product` on Row. The rule answers 3 (`Region`). The Row rival answers 0
(`_Index`), and a "first Regular on Row" rival answers 4 (`Product`), so all
three are separated. Injected: the Row rival fails this test.
### with_no_regular_category_tile_select_opens_on_measure_not_index
improvise-bqm0. #231's rule, `position(regular).unwrap_or(0)`, fell back to tile
0, which is `_Index`, when a view has no Regular category, which is exactly the
bug #231 fixed. The fixture is `_Index`, `_Dim`, a Label and `_Measure`. The rule
answers 3 (`_Measure`), then 2 (the Label) with `_Measure` removed, and 0 for an
empty bar. Injected: #231's rule answers 0 and fails. The Row rival fails too.
### a_frame_whose_group_level_names_no_category_does_not_deserialize
improvise-e265, ChatGPT Codex's P2 on #250. A `FrameGroup.level` arrives over
the wire, and `AxisFrame::groups` sizes its open-run stack with
`open.resize(g.level, None)`. A frame carrying `level: 4294967295` therefore
drove a multi-gigabyte allocation in the client instead of being refused. At
`e44b1491` the frame deserialized without complaint. The fix checks every level
against `cats.len()` as the frame arrives: `context/code/protocol.md` §3b.
The test serializes a well-formed two-category frame and checks that it reads
back equal. That is the control. It then injects two faults into the JSON, one
at a time: an oversized group level on the **second** marker, and a `GroupEnd`
one level past `cats`. For each fault it asserts two things. The mutated text is
still well-formed JSON. And the refusal names the level ("names no category"),
so the frame is refused for its level and not for its shape. The collapsed,
item-free frame keeps the fixture independent of `FrameEntry::Item`'s shape,
which #275 changes.
Rivals, each run:
- The `try_from` attribute removed: `unwrap_err` panics, because the frame
parses.
- Only the first marker checked (`.next().filter(..)` instead of `.find(..)`):
it fails the same way, because the first marker is in range.
### every_composed_cell_write_names_the_view_the_client_is_on
improvise-wpxr: a `SetCell`/`ClearCell` now names the view whose grid drew the
@@ -1209,3 +1252,10 @@ the Enter commit.
- **Rival** (`clear-cell` naming `"Default"`): fails **only** this test, at the
`clear-cell` assertion. `clear-cell` and `paste` build their command in
`resolve` itself rather than through `typed_cell_command`.
- **After the merge with gh/main `08ad7b81`,** the Enter commit writes the cell
its editor OPENED on (improvise-8xq0), so the test now opens the editor on
`Other`, moves the client to `Later`, and only then commits. The view comes
from `OpenedCell::view`, captured at open.
- **Rival** (the commit naming `state.view.active_view`, the view at
`Enter`), run across the whole workspace: fails **only** this test, with
`["Later"]` against `["Other"]`.