Expand description
The APP SHAPE — App / Interface / Infrastructure, the one runtime shape every
workspace_nordisk suite app wears.
Designed 2026-08-02 with the owner; the full argument is in
workspace_nordisk/.nornir/app-shape-design-2026-08-02.md, and its build-time sibling
(four atoms per suite member) is .nornir/SUITE-MODEL.md.
§The bug this exists to make impossible
An app stores a CHOICE and then never lets that choice decide what is drawn under it. The selection is written to a field and read back only where it is displayed — nothing downstream comes FROM it. So a pane offers controls that do not apply to what is selected, and hides ones that do.
The owner, having hit it in two unrelated panes of one binary on one day: “the korp map
2d/3d choice should change settings below … do we see a pattern? infrastructure should
show correct choices/ui”. Both instances were real, and the second was measured: korp’s
chosen_infra had exactly ONE behavioural use in the entire UI — a filter hiding two
tabs — while 37 of 56 production draw_* fns named a backend directly. Choosing an
embedded backend hid two tabs and changed nothing else.
A control that is offered is a promise that it applies. 3-D lighting settings under
a 2-D map is not a missing feature; it is the UI asserting something false about its own
state. Same failure as two enabled flags that disagreed in one Settings pane, and as a
state file reporting a selected region while the select box rendered empty: two pieces
of code work out the same answer separately, somebody has to remember to change both,
and eventually nobody does. The cure is not a philosophy — delete the second copy.
§The shape
trait App { fn infrastructure(&self) -> &dyn Infrastructure; fn interface(&mut self) -> &mut dyn Interface; }Two halves, and that is the whole top level. Interface is recursive: a tab and a view
mode are the same kind of node at different depths, so the controls under a choice ARE
Interface::selected rather than being looked up. [Infra] hands out faces; a face
returning None is why a tab does not exist, which is what replaces a
is_spark_backend()-style predicate with a capability answering for itself.
§Why Interface is a supertrait of Facet, not a new pane contract
Facet already carries title / ui / state_json / update_json / severity /
component. Redeclaring a render here would be a second way to draw a pane — the
twin this whole module exists to prevent (LAW 5). So Interface: Facet adds exactly three
methods and inherits the rest.
All three are defaulted, so impl Interface for MyPane {} is a legal one-liner meaning
“a leaf: I have no sub-choices”. Every addition to Facet this year has been defaulted
for the same reason and that precedent holds here.
There is deliberately no blanket impl<T: Facet> Interface for T: it would make every
facet a leaf and forbid any pane from overriding, which is precisely the interesting
case. Opting in is one line and it is explicit.
Structs§
- Control
- One control, as DATA — never a widget.
Enums§
- Control
Kind - What kind of thing a
Controlis — enough for any renderer to draw it, and nothing about how.
Traits§
- App
- An app: a ui tree and a swappable infra. The whole top level.
- Face
- One backend capability. A face exists only if swapping [
Infra] swaps the implementation. - Infrastructure
- The swappable half of an app — hands out
Faces, and is the only thing that knows which backend answered. - Interface
- A node in the app’s ui tree — recursive, so a tab and a view mode are the same kind of thing at different depths.
Functions§
- selected_
path - Walk to the deepest selected node — the leaf the user is actually looking at.
Type Aliases§
- Key
- A stable machine key for one option — a tab id, a view mode, a layer. Not a label:
this is what
selecttakes, whatstate_jsonreports, and what a robot driver addresses, so it must survive a rename of the human-facing title.