22964f99be
Records mode showed long format: a row per cell, a _Measure column naming which measure it was, and a synthetic "Value" column holding the number. Entering data meant sitting on "Value", and editing the measure column renamed a coordinate rather than a value. Wide format instead: a row is one coordinate tuple — the cells sharing every coordinate but _Measure — and each raw measure gets its own editable column. Two measures on one coordinate are now ONE row. The case matrix came from a Prolog model rather than a read-through, which surfaced the load-bearing consequence: a row now groups several cells, so editing a category column must move all of them. The long-format code moved only the cell under the cursor, which here would leave half a row's measures under the old coordinate and half under the new. Both the plain-records and staged-drill paths implement it. _Dim in the synthetic key now carries the column index rather than a label, because a category and a measure item may share a name and a label could not say which column an edit landed on. GridLayout::record_col resolves it to a RecordCol, and drill pending edits are keyed by (row, RecordCol) — which also frees ApplyAndClearDrill from depending on the layout still being the drill layout. A blank model has no measures, so wide format alone would leave nowhere to type a number and break the onboarding path. A trailing "+" column creates a measure by name; being a schema change, it applies immediately rather than staging inside a drill. Formula targets are not columns — they are computed, so there is nothing to type into them. prune_empty's records branch went with it: the function early-returns in records mode, so that tail could never run. Filed as improvise-2yk in case the early return is itself the bug. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V69PGQfMJuGRmtSWpfVAqn