Commit Graph

4 Commits

Author SHA1 Message Date
Edward Langley 216b14860d improvise-6qet: the grouped harness spec expects the collapsed group's row
grouped.spec.js asserted a collapsed group contributes no rows (8 data rows).
It now contributes one per Store (10), labelled Upland, and its cells are
not editable. The editable assertion was checked against cell_id without
folds_a_group: it fails with 6 editable cells where 0 are expected.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
2026-09-22 21:31:06 -07:00
Edward Langley 8f3a9768b7 fix: group membership, leading headers and one run rule on AxisFrame
improvise-gd83, improvise-0o6x and improvise-8qlr. One commit because they are
one change to `AxisFrame`/`AxisEntry` — a variant, a field and a method — and
all three move `tests/grid_frame_agreement.rs`, whose two ends must be changed
together or the test stops comparing anything.

improvise-gd83 — AxisEntry carries a group-end boundary
-------------------------------------------------------
`expand_category` emitted a header on ENTERING a group and no boundary on
leaving one, so `group_of` answered with the nearest preceding header rather
than membership, and the TUI painted that span. Copilot was right on PR #23;
this is the filing gotcha 44 said the fix needed.

`AxisEntry::GroupEnd` is the boundary, carried onto the wire as
`FrameEntry::GroupEnd`. Two arms, and a fix can close one and miss the other:

  * within one expansion — Jan[Q1], Feb[Q1], Sep — a `Some -> None` transition;
  * across expansions — `cross_product` expands the inner category once per
    prefix and each call starts with `last_group = None`, so Food/Bob[Family]
    is followed directly by Clothing/Carol with no transition to hook onto.
    That needs a `GroupEnd` at the END of an expansion whose last item was
    grouped.

`GroupEnd` is NOT emitted between two groups: the next header already ends the
previous run, and emitting one there moved `data_row_to_visual_skips_headers`
and its column twin for no gain. Those two are untouched, which is the evidence
the boundary is as narrow as it claims. A trailing marker is trimmed, since the
axis itself ends the last run. Nothing draws a `GroupEnd` and it consumes no
data index.

`THE_MEMBERSHIP_CORRECT_SPANS` and its `assert_ne!` tripwire are gone, and the
value they predicted is now asserted element for element — it was right to the
element, which is worth recording.

improvise-0o6x — a leading group header is drawn
------------------------------------------------
`body_from(idx)` started at the first ITEM, so at the top of an axis a leading
`FrameEntry::Group` was dropped on the way to the screen, in both front ends. A
collapsed group first on its axis therefore had its rows hidden and its header
nowhere: no affordance to get them back.

It is a boundary condition as the ticket suspected, but the boundary is not
`idx == 0`. The windowing rule carries the nearest header ABOVE a scrolled
window for `group_of` and `body_from` discards it — correctly — and a frame
cannot tell that from the top of the axis using `idx` alone, because the
browser's frame is already windowed and always passes `0`. So `AxisFrame`
gained `first`, the absolute data index of its first item, and the rule is
`first + idx == 0`. Without it the browser would draw an extra header row at
every scrolled position that the TUI does not.

RE-PINNED DELIBERATELY, the improvise-q0vb acceptance shape: `GROUPED_FRAME` in
`src/ui/grid.rs` moves by exactly one line — the leading `▼ Co…` now draws
above `East` and everything below shifts down one. The group line, the column
headers, the separator, the data rows and the totals strip are byte-identical,
which is the evidence this is the boundary condition and not a reshuffle.

`harness/tests/grouped.spec.js` asserted TWO group rows against a fixture with
three, naming this ticket on the line and saying the count becoming 3 would be
the record of when it was fixed. It is 3, verified in Chromium. Three
assertions in that file took "the first tbody row" to be a data row; a
`firstDataRow` helper now skips the header, which is the same re-pin.

improvise-8qlr — the run rule lives once
-----------------------------------------
Column-group run coalescing was derived in `src/ui/grid.rs` and again in
`improvise-wasm-client/src/render.rs::col_groups`, held in step by a comment
saying so. `AxisFrame::group_runs`/`group_run_heads` is the one home and both
ends call it. Runs still coalesce by NAME, which is what both ends did before
and is preserved rather than quietly changed.

The TUI keeps one divergence, stated: it labels its first VISIBLE column even
mid-run, or a scrolled grid draws an unlabelled group line.

The agreement test's TUI arm was `group_line.contains("▼ Q1")` — the unpinned
arm the ticket names. It now compares the painted group line cell for cell
against spans derived from the browser's own runs. The test is fixed, not
deleted.

Injected faults, each failing by name
-------------------------------------
  * gd83: delete either `GroupEnd` emission — the two new layout tests fail
    with the leaked group, and the column-header agreement test fails with them.
  * 0o6x: force `body_from`'s match to `false` — `the_whole_frame_is_unchanged`
    and the new leading-header agreement test fail.
  * 8qlr: the ticket's own recipe, forcing the TUI to emit the group label on
    every column. `cargo test -p improvise` was GREEN before this commit and
    now fails with ["▼ Q1","▼ Q1","",...] against ["▼ Q1","","",...].

What the ticket did not predict
-------------------------------
A collapsed leading group has no item to carry its header into a window, so it
reaches the frame only through `axis_frame`'s `pending` queue. The first
version of the windowing change had `GroupEnd` CLEAR `pending` above the
window, which deleted exactly that header — the affordance 0o6x exists to
restore. `GroupEnd` is now queued like a header and clears nothing. Found by
driving the new agreement test in both collapse states; the uncollapsed arm
alone passes against the broken version.

1411 Rust tests pass (baseline 1405, +6). Playwright 28 passed + 3 skipped.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FC2jVn8Au7E9JPkw6bwXXH
2026-09-20 20:40:57 -07:00
Edward Langley f5198c9142 test(harness): gate the grouped reads on a painted head
Self-review under the code-review skill: the three grouped tests read the head
and the row labels in one shot, which cannot wait, while the grid is painted
from the first projection and reframed by the viewport the page then announces.
openGrouped now gates on the head's shape, its first group and the first row's
innermost label with retrying assertions. 15/15 under --repeat-each=3, and all
five injections re-run afterwards.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FC2jVn8Au7E9JPkw6bwXXH
2026-09-20 16:35:47 -07:00
Edward Langley 09ada9c1b5 test(harness): a grouped, multi-level fixture, and the first browser run of anr9
web/demo.improv has no groups and one category per visible axis, deliberately
(gotcha 46), so the multi-level header, the group span, the collapse indicator
and the inline group row -- exactly the code improvise-anr9 added -- had never
been rendered anywhere a test could see (improvise-plcx).

harness/fixtures/grouped.improv is served beside the shipped demo by
harness/build.sh; web/demo.improv is untouched. Two categories on each visible
axis, three row groups with one collapsed, two column groups.

A missing seed is NOT an error -- web/worker.js answers a 404 with an empty
model -- so every assertion is about labels the fixture really carries, never
that a grid exists.

Five injections, five distinct failures, patched in harness/serve so no
rebuild stands between the fault and the run: slash-joining the level tuples;
colSpan = 1 on the group headers; a stray cell on the inline group row;
indicator(false) hard-coded; and the collapse removed from the document.

One thing the ticket did not predict: the row frame's FIRST group header is
never drawn, so the spec asserts two group rows where the document has three,
with improvise-0o6x named on the line.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FC2jVn8Au7E9JPkw6bwXXH
2026-09-20 16:34:24 -07:00