uiplane — UI-PLANE reachability (LAW 9) — design
LAW 9 — UI-PLANE REACHABILITY. A surface that exists but cannot be navigated to on a reachable plane of the running app = RED. The matrix tracks the current plane, walks a navigation plan to reach each surface's plane, and asserts the surface is present + RAN on its plane — going RED when a plane is an orphan, a transition is missing, or a reached surface never ran.
The failure that birthed it
The mega-matrix tests components in isolation — it renders each surface standalone and greens "the Sverige showcase component can render." But the user runs a running app on which that surface lives on some plane (a tab / dialog / view / app-mode), reachable only by navigating there. The shipped app could not even navigate to the plane those buttons were on (orphaned plane, or the shipped wasm differed). The native green was true and worthless: shipped ≠ tested (LAW 1), and unreachable ≠ covered (LAW 9).
uiplane models the UI as planes + a navigation plan and walks it on the REAL app, so the matrix asserts the thing the user actually does: reach every surface and prove it ran there.
Model (src/uiplane.rs)
UiSurface { id, label, must_run } // a button/slider/pane on a plane
UiPlane { id, label, surfaces: BTreeMap } // a tagged view-state (tab/dialog/view)
Transition{ from, to, action } // a named action that moves plane A→B
UiPlan { planes, transitions, start } // the navigation GRAPH (nodes + edges + root)
UiPlane— a tagged view-state: anid(matched against the driver'scurrent_plane()), a label, and the interactive surfaces declared to live on it.UiSurface::new(id)requires present AND ran;UiSurface::passive(id)is present-only (static chrome that need not fire an emitter). Defaultmust_run = true— pessimism (LAW 7): presume a surface should run.UiPlan— the navigation graph: planes as nodes,Transitions as edges, and astartplane the app boots on (the BFS root).UiPlan::empty()is a headless library with no UI —walkon it is trivially green (nothing to reach), so the primitive is genuinely UI-agnostic.- Pure graph ops (no driver needed):
reachable_planes()(BFS from start),orphan_planes()(declared but unreachable — the Sverige catch),dangling_transitions()(an edge naming an undeclared plane — a plan-authoring bug), andbfs_route()(the ordered driving script:(plane, via-transition-action)per reachable plane, shortest path first).
The driver seam (PlaneDriver)
The seam that lets the SAME UiPlan run against different backends — a native robot-UI (egui_kittest / nornir-robotui) or a deployed-wasm headless browser (LAW 1: drive the SHIPPED artifact). The walk only ever talks to this trait:
pub trait PlaneDriver {
fn current_plane(&mut self) -> String; // from emitted state, never pixels
fn apply(&mut self, action: &str) -> Result<(), String>; // perform a transition
fn surfaces_present(&mut self) -> BTreeSet<String>; // surfaces present on the current plane
fn surfaces_ran(&mut self) -> BTreeSet<String>; // surfaces that FIRED their emitter
}
A driver reads PRESENT/RAN from the app's emitted data (state_json / window.__facett_state()), never from pixels (LAW 6). apply only performs the action; the walk (not the driver) verifies the landing plane equals the transition's to — so a transition that exists in the plan but fails to land (the control is missing in the shipped app) is caught as Unreachable.
MockDriver is the in-memory test double (and the reference shape): a transition table + per-plane present/ran sets. It is how the harness's OWN tests prove the four verdicts without standing up egui.
The walk + the LAW 9 verdict (RobotPlan::walk)
RobotPlan::walk(&plan, &mut driver) -> WalkReport:
-
Compute the BFS route + orphan set + dangling edges.
-
For each route step: if it has a transition action,
applyit and verifycurrent_plane()now equals the target — a mismatch /applyerror marks the plane (and its surfaces) unreachable. -
On a reached plane, read
surfaces_present()+surfaces_ran()and emit oneWalkRowper declared surface:- present && (ran || !must_run) →
ReachableAndRan(GREEN); - present but not ran (and must_run) →
ReachableNotRan(RED — a button that exists but whose handler never fires; ties RAGNARÖK / LAW 2); - absent →
ReachableAbsent(RED — on the plane but the surface isn't there; the wasm-parity catch).
- present && (ran || !must_run) →
-
Every surface on an unreachable / orphan plane →
Unreachable(RED — the Sverige-buttons catch), and the plane is named inunreachable_planes.
Reachability completeness. The walk emits exactly one row per declared surface (report.rows.len() == plan.surface_count()), reachable or not — no surface is silently skipped. WalkReport::is_green() ⟺ no unreachable plane, no dangling edge, and every (plane, surface) row is ReachableAndRan.
Each WalkRow::detail() names what's missing on a RED (the unreachable plane + the need for a transition, the absent surface, or the non-running emitter), so the verdict is actionable.
Composition (does NOT duplicate)
functional_status/ the matrix —WalkReport::emit(component)pushes onefunctional_statusrow per (plane, surface) (aspectuiplane), so LAW 9 lands in the SAME matrix → warehouse path as everything else.to_rows(component)givesTestResultRows directly.utfallsrum— orthogonal: a surface with an outcome space attaches itsOutcomeand records its sweep too. uiplane proves reachable + ran; utfallsrum proves the sample space was swept.- facett's
Navigable— orthogonal:Navigableis the within-plane pan/zoom data struct;UiPlanis the between-plane navigation graph. APlaneDriverusesNavigableto move within a plane if it needs to. nornir-robotui— the nativePlaneDriverwraps itsRobotSession/clicks; this module only defines the seam, the consumer wires the robot to it.
Layering — framework primitive ⟂ nornir's dogfood
- FRAMEWORK (
crates/nornir-testmatrix/uiplane) —UiPlane/UiPlan/PlaneDriver/RobotPlan::walk, GENERIC + UI-AGNOSTIC. No viz/facett/egui types leak in. Works for a headless library (empty plan = trivially green). - NORNIR's APPLICATION (
nornir/src/viz/uiplane_plan.rs) — nornir-viz modelled as aUiPlan(plane perTab, tab-strip click per transition) + the nativeVizPlaneDriverover the liveUrdrThreadsApp+ the wasm driver seam. The worked example that proves the harness. facett-demo's deck (27 tabs + the tab-bar plan) is the second example the facett agent wires later.
The wasm / deployed seam (LAW 1)
src/viz/uiplane_plan.rs::wasm DEFINES (does not implement) the deployed-artifact backend: a BrowserBridge trait over two JS calls the shipped wasm exposes — window.__facett_state() (the state_json) and window.__facett_click(action) (perform a transition) — and a WasmPlaneDriver<B: BrowserBridge> that runs the SAME UiPlan against the LOADED bundle. The present/ran extraction reuses the SAME pane_block_ran logic as native, so native↔wasm verdicts are computed identically — only the transport differs. The facett-demo agent supplies the JS hook + a concrete BrowserBridge (chromedriver / wasm-pack test --headless); the Rust side is ready the moment it lands.