Skip to main content

Module interface

Module interface 

Source
Expand description

The APP SHAPEApp / Interface / Infrastructure, the one runtime shape every workspace_nordisk suite app wears.

Designed 2026-08-02 with Rickard; 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.

Rickard, 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§

ControlKind
What kind of thing a Control is — 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 select takes, what state_json reports, and what a robot driver addresses, so it must survive a rename of the human-facing title.