Skip to main content

Module interface

Module interface 

Source
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§

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.