facett-demo mega matrix (D5)
The showcase app facett-demo shipped with one golden snapshot PNG (tests/snapshot.rs). That proves one themed layout renders to pixels — it does not prove every component behaves. The mega matrix (tests/mega_matrix.rs) is the functional, multi-dimensional sibling: it crosses every component against the data-state and palette axes and asserts the real rendered data of each cell (LAW 1 — never "didn't panic").
The axes
| axis | values | source of truth |
| component | every facet the demo wires (19 Facets in the deck + the menu CommandPalette) = 20 |
the demo's LEGEND (coverage-gated) |
| data state | Populated (real representative data injected) · Empty (clean empty render) = 2 |
DataState::ALL |
| palette | all of facett_core::Theme::ALL (default · sci-fi · nordic-aurora · cyberpunk-neon · amber-crt · deep-space · hugin-noir) = 7 |
Theme::ALL |
Total: 20 × 2 × 7 = 280 cells. Every cell is rendered headless and asserted; the test prints a per-cell pass line and the passed/total tally. (The deck grew from 16 to 19 Facets when syschart, geomap and icebergview were added so the demo is the full robot-drivable showcase — see D5b2 below.)
D5b1 vs D5b2 — the static matrix and the live drive
The mega matrix has two layers, both Facet-coverage-gated against the demo's LEGEND:
- D5b1 — the static matrix (
mega_matrix_component_x_data_x_palette): the component × data × palette sweep above. It renders each component in isolation and asserts its rendered data + palette consumption. (This document's body.) - D5b2 — the robot-UI live drive (
tests/robot_demo.rs): fire up the whole demo headless and drive every button/function, then assert each component'sstate_jsonresponds. It is one matrix line — "does the whole demo actually work when driven?" — emitted asrobot-uiTestResultRows (one per scripted step). See the dedicated section near the end.
What each cell asserts
For each (component, data, palette) cell the matrix injects real values, renders the component headless with the palette pinned active, and asserts:
-
data signal — the component's readable data (
Facet::state_json, or theCommandPalette::state_jsonfor the menu) carries the injected data:Populated→ the count signal is > 0 (rows / nodes / series / fields / points / tiles / cells / items / bars / commands).Empty→ the signal is 0 with no panic and no garbage (a clean empty shell). Grid/table render an empty-but-well-formedRecordBatch(0 rows, real schema), so "empty" is the empty data state, not a malformed one.
The per-component readable signal asserted:
| component | readable signal (state_json field) |
| graph / graph3d / depgraph | nodes |
| pipeline | statuses.{idle,running,done,failed} sum |
| grid | rows |
| table | rows + selected[] (a real injected selection) |
| card | card.fields[] |
| form | dirty (populated = edited/dirty, empty = clean) + valid |
| pop | items[] + done |
| plot / boxplot / violin | series / groups |
| heatmap | cells |
| treemap | tiles |
| candles | bars |
| map | points |
| syschart | nodes[] |
| geomap | points (+ hotspots[]) |
| icebergview | snapshots[] |
| menu | commands[] + hits[] |
-
palette consumption — switching palette must change the palette the component emits in its readable data, not a private default. Proven two ways (a cell passes if either holds for the active palette):
- probe —
facett_core::theme::theme(ui)is the single universal palette read for every custom-painted facet. It records the palette name it hands out into thefacett_core::theme::probethread-local. The testreset()s the probe, renders one component, then readspainted()— a facet that painted with the active palette returnsSome(palette_name); one painting a default returnsSome("default")and fails for the six non-default palettes. - visuals — for facets that theme purely through egui
Visuals(plot / table / pop, and the egui-widget parts of card/form), the activeVisuals::override_text_colormust equal the palette'stext. Different palettes have differenttext, so this discriminates per palette.
- probe —
The menu reads the palette from the context's facett_theme data directly (it has no ui to call theme(ui) on), so its consumption is asserted on the ctx palette it painted from.
The readable surface that was added
The components consumed the active palette at paint time but did not emit which palette they painted with — there was no readable witness for the palette axis. The minimal surface added (LAW 6) is the facett_core::theme::probe thread-local: theme(ui) records the palette it returns, and tests read it back. It is the single universal consumption point, so instrumenting it once gives uniform, per-component palette-consumption evidence with no change to any component's own state_json (and no risk to their existing tests). No other readable surface was needed — every component already exposed its data counts.
The coverage gate (like the MCP one)
coverage_gate_every_demo_component_is_in_the_matrix cross-checks the matrix registry (matrix_keys() = the cell builders' keys + "menu") against the demo's LEGEND (legend_keys()):
- a demo component not in the matrix → RED (
"demo components NOT in the mega matrix (add them): [...]"), - a matrix cell for a component the demo dropped → RED (stale matrix).
So adding a component to facett-demo without adding a matrix cell fails the test — the gate forces the matrix to track the demo, exactly like the MCP tool-coverage gate. coverage_gate_catches_a_fake is the RED proof: it injects a fake LEGEND key and asserts the gate flags it. (Verified live by temporarily adding a ("fakecomp", …) LEGEND entry → the gate panics with demo components NOT in the mega matrix (add them): ["fakecomp"].)
Screenshot proof across every palette (LAW 4)
The single golden in snapshot.rs is kept. The mega matrix adds screenshot_proof_populated_layout_across_every_palette: it renders the representative populated deck (every Facet, real data, glow on) to real pixels via egui_kittest's wgpu backend once per palette and writes tests/snapshots/palette/facett_demo_<palette>.png (7 PNGs), asserting each is the requested size and non-trivial (> 5 KB). The gallery is thus visually proven across all themes, not just one.
D5b2 — the robot-UI live-drive row
Where D5b1 renders each component in isolation, D5b2 (tests/robot_demo.rs) proves the whole demo works when a robot drives it. It is the dynamic complement: one matrix line answering "fire up the entire app and click every button — does it respond?".
How it works. It builds the demo headless with egui_kittest's Harness over the same render path the eframe binary uses (DemoState::draw_app, so the robot drives exactly what a human sees — CLI/GUI parity), then runs a Scenario of (Action → Assert) steps. Each step performs a real AccessKit interaction (click a labelled tab / button / selectable, drag, scroll, press a key) and then asserts a real value read out of the app's state_json (inject-assert LAW). A failed action or assertion is a red cell, never a panic. Every step becomes one robot-ui TestResultRow (aspect = "robot-ui", one run_id per run), so the live drive fills the matrix; the rows are persisted to a JsonFileSink (facett-demo/target/robot-matrix.jsonl), round-tripped, and printed as a grid via nornir_testmatrix::render_matrix.
Every component is driven (a D5b2 coverage gate asserts each LEGEND key appears in some driven step):
| component | what the robot drives | asserted response (state_json) |
| graph / graph3d / depgraph / pipeline | click the tab header | deck.active switched; the facet's nodes present |
| grid / table | click tab + scroll the virtualized view | …/rows == 48 |
| card / form | click tab | card.fields mention Name; form Drew |
| pop / plot | click tab | pop.items mention feed; plot.series mention north |
| boxplot / violin / heatmap / treemap / candles | click tab | tab switched; heatmap.cells == 16 |
| map | click tab | map.points == 48 |
| syschart | click tab + node-select toolbar (node:Ingest, node:Score) |
syschart.selected becomes / toggles off the picked node |
| geomap | click tab + drag-pan + scroll-zoom the canvas | viewport responds, points == 48 held |
| icebergview | click tab + pager (next ▶ / ◀ prev) + time-travel the snapshot picker |
page advances/returns; snapshot current 200 → 100 reloads visible_rows |
| menu | toolbar commands opens it; type-free Escape closes it |
menu.open toggles; menu.hits surface palette.cycle |
The reference run drives 48 robot-ui steps, all green. A sibling fidelity test (robot_runner_records_a_wrong_expectation_as_a_red_cell, mirroring robotui's own reference tests) injects a deliberately-wrong expectation and a missing-widget click and asserts both land as red cells, not panics — so D5b2's pass/fail is genuinely assertion-driven.
On nornir-robotui — the port is a TWIN and its justification expired. This row is the documented nornir-robotui consumer but does not depend on the crate: robot_demo.rs carries an in-crate port of robotui's public API (Observable / RobotSession / Scenario / Step / Action / Assert / run_scenario).
⚠ Corrected 2026-09-01. The reason recorded here — "published robotui 0.1.0 pins egui 0.33 … while facett is egui 0.34, two egui majors cannot share one Harness" — is stale in both halves. Measured: nornir-robotui is 0.3.4 on egui/eframe 0.35; facett is egui 0.35. There is no egui-major split left. The stated re-enable condition ("once robotui ships an egui-0.34 build") can never be met, because robotui went from 0.33 past 0.34 to 0.35 without ever stopping there — a drop-condition that can never fire is the same thing as no drop-condition.
The twin cost a real bug, and it had to be fixed twice. Step::assert was a SETTER (self.assert = assert) rather than an append in both copies, so a chained .assert(a).assert(b) silently kept only the last clause and every earlier one was dead code reporting green. Across 30 nordisk repos that was 135 assertion clauses that had never executed. nornir-robotui fixed it at the source in 0.3.4 (Assert::All(Vec<Assert>); .assert() appends) and korp got the fix by bumping its lock — but a version bump cannot reach a twin, so this file's copy stayed broken and had to be repaired separately on 2026-09-01.
facett's one affected chain is the falkor step: /deck/facets/falkor/node_count == 7 had never run, because /deck/facets/falkor/runner came after it. It now runs, and it passes (verified red-first: injecting a false leading clause makes the step fail with clause 1 of 3; before the fix the same injection was silently discarded and the suite stayed green).
TODO (LAW 5 — reuse, do not twin): delete mod robot and depend on nornir-robotui = "0.3". Five other facett crates (facett-app, -graph, -graphnav, -holger, -brokkr) already take the real dep, so the swap is well-trodden. This bug is the argument: one writer cannot drift from itself.
Headless caveats (recorded honestly).
syschart's own node-select is canvas-position click (not a labelled widget), so the demo adds a label-addressable node-select toolbar the robot drives by label; the canvas click path remains the human/unit-test route.icebergview's snapshot ComboBox popup is not retained in the AccessKit tree across headless frames, so the snapshot switch is driven through the typedselect_snapshotAPI as its own asserted step (the pager buttons are clicked by label); the combo selectables stay a human/unit-test route.- the
menuqueryTextEditis not.labelled_by, sotype_intoon it is not used here (Escape-close + hit-surfacing are driven instead).
Run
cargo test -p facett-demo --test mega_matrix # D5b1: the whole static matrix
cargo test -p facett-demo --test mega_matrix mega_matrix_component -- --nocapture # per-cell table
cargo test -p facett-demo --test robot_demo -- --nocapture # D5b2: the live-drive row + grid
cargo test -p facett-demo # both + the kept golden snapshot
Runs under facett's native cargo test suite, so a nornir test matrix picks it up automatically (no extra wiring).
graph TD
A[facett-demo LEGEND: 20 components] -->|coverage gate| B[mega matrix registry]
B --> C{component × data × palette}
C --> D[populated: signal > 0]
C --> E[empty: signal == 0, clean]
C --> F[palette consumed: probe or visuals]
B --> G[per-palette wgpu screenshots ×7]
A -.add a component, forget the matrix.-> R[RED: gate fails]
A -->|D5b2 robot-UI coverage gate| H[robot_demo: drive the WHOLE app headless]
H --> I[click every tab + every component control]
I --> J[assert each state_json responds → robot-ui rows]
J --> K[fill one matrix line: does the demo actually work?]