UI
PartialThe console is one renderer over the same views the rest of the platform reads. This page is the renderer / page / dashboard model and the information architecture; the stack and the concrete primitives are the design system.
The renderer contract: ViewResult and the views BFF
Section titled “The renderer contract: ViewResult and the views BFF”The dense-ops layout is an architectural pattern: facet summary over the full set, keyboard chip
filter, tree/list table, click-row detail blade plus a full detail page, the summary staying whole so
click-to-filter is stable. The inventory tier realizes it as the config-driven ListShell and its
primitives (design system); the analytical surfaces will reuse it.
Tooltips, not prose
Section titled “Tooltips, not prose”An operator surface carries no inline explanatory text (ADR-0127): every explainer rides its
label’s (i) tooltip (InfoTip; Eyebrow for section labels), so the pedagogy doctrine is
satisfied by hover and the page itself stays scannable. The standard-editor’s vocabulary
(choices, alternates, accepted types) never leads an operator view, and a system’s body
renders components-first: one card per component with its role as a badge, role-level chrome
only where it says something a badge cannot (a quorum beyond one, a shortfall, an unstaffed
role). The platform-wide sweep of the older pages is tracked in #784. The system zoom is growing
into the workspace #788 defines: facets as ?tab= URL facts (TabRail), the Map tab
rendering the standard’s declared room (ADR-0128) with live occupant state on each marker.
Since ADR-0129 these faces ARE the identity routes’ default, and since #800 they are the
ONLY faces: one way to look, one altitude rule (locations drill, systems open full screen,
components open in blades), editing on each workspace’s Configure tab, and flat tables
surviving as a list-density toggle for bulk work. The classic detail face is retired;
?view=detail is ignored and ?edit=1 lands Configure already editing.
One renderer library, two composition modes
Section titled “One renderer library, two composition modes”Coded pages and dashboards share one view layer
Section titled “Coded pages and dashboards share one view layer”Target design: default views behind coded pages, tracked in #523
A built-in page queries a default view, not a raw resource (the Alarms page reads the
firing-now view, not GET /alarms directly), so the same view backs a dashboard widget unchanged;
composable dashboards are the customization layer over the complete coded console.
Live updates: polling by default
Section titled “Live updates: polling by default”Live data is query polling (a refetch interval; slow-changing config uses a long stale time).
Views and the SSE live relay, tracked in #523; the unit registry is #430
A read can also stream over the view layer (a server-side SSE relay) where latency or fan-out earns it. Config-dependent presentation (a severity level’s id to label and color) resolves client-side from the config view; a sample value converts on read to the operator’s preferred display unit via a future unit registry keyed by the property_type’s canonical unit, so storage stays single-unit.
Which high-frequency surfaces move from polling to the SSE relay, and what latency earns it.
Configuration UIs
Section titled “Configuration UIs”CRUD forms over the typed resource API, one per primitive (components, templates, types, tags,
rules, config, groups, schedules, severity levels, the IAM resources). The type registries each
hold their own page: location types (CRUD; Catalog, under Locations: Types) and secret types
(read-only, at /secret-types by URL with no subrail entry), the former tabbed Types page having split with the
system and component kinds already moved to Standards and Products (build log).
Editing a setting is editing config, an audited mutation, not a
separate prop store (audit).
The rule-authoring surface (ADR-0050); the expression editor is #524, the AI seam ADR-0001
The standout is the rule-authoring page:
- an Expr editor for the predicate or condition, the prepared-input contract surfaced (expressions);
- a live blast-radius preview (which entities a scope selects, which samples a rule would have fired on);
- the AI-suggestion seam (AI): AI may propose a rule pre-filled with provenance; the operator edits and approves (the ordinary audited create). AI never saves a rule itself.
Exploration UIs
Section titled “Exploration UIs”Target design: the exploration surfaces over views, tracked in #523
Coded pages with rich interaction, all reading through views:
- The cascade resolve view (the standout): “why did this value win”, from the cascade resolve output: effective value, winning source, the ordered shadowed bindings it beat.
- Sample history:
lineorheatmapover a time range, stale / unknown surfaced (time). - Alarm drill-down: the alarm, its triggering sample and history, the actions it fired, and the acknowledgement (built today on the component’s Alarms panel, not on a drill-down of its own). Snooze and resolve controls are not part of this: both were refused, with reasons, in ADR-0109.
- Inventory and topology: navigable location / system / component trees,
health (
status-grid) at each level. - Event exploration: the event log by entity / time / category, with the audit trail.
Information architecture
Section titled “Information architecture”Two layers, deliberately decoupled:
- Routes are flat and identity-based. Every entity page is a top-level path (
/systems,/components,/templates,/config); a URL addresses the entity, never its place in the menu, so deep links stay stable however the menu is reorganized. No taxonomy-nested routes, no redirects to maintain. - The sidebar groups those flat routes into clusters for browsing: Home, Fleet (the one canvas, #633), Dashboards, Alarms, Inventory (locations, systems, components, nodes), Values (variables, secrets, config, files), Catalog (a single entry opening the catalog shell, next), Explore, Learn, Admin (users, roles, groups, audit, and the Settings leaf). A cluster is pure presentation, not a destination: rearrangeable and user-customizable without touching a route.
The mode rides the URL too
(ADR-0120 established the rule;
ADR-0132 retargets it):
?edit=1 beside a fleet address lands the workspace’s Configure tab already editing, and beside
an identity blade’s id param (?u=<id>&edit=1) opens that blade editing, both behind the same
<resource>:update permission the Edit button is behind; without it the link lands read-only.
Leaving edit (Cancel or Save) strips the param via history replace, so a refresh mid-edit keeps the
mode while Back never re-enters an edit the operator left. One hook (web/src/lib/editurl.ts) owns
both directions, the create-as-route and row-pencil handoffs are those URLs rather than in-memory
signals, and the name-to-uuid redirect keeps its query string so a name-shaped edit link survives
resolution. Every URL-reachable state is also a state the
docs screenshot pipeline can declare and capture, which is
what retired the one-shot handoffs.
Catalog is one rail entry opening a shell. Clicking Catalog opens a two-column catalog area: a
grouped subrail (Telemetry, Actions, Components, Systems, Locations, Metadata) whose entries
navigate to the registries’ own canonical flat routes (/products, /metrics, …), each real
page rendering in the pane beside it, and an Overview landing at /catalog with one card per
group and live registry counts, the learning-tool answer to “what
is all this”. Subrail and cards derive from one group table judged through the same permission
filter the rail uses, so a gated entry drops from both surfaces at once and a group whose entries
are all gated away disappears with its header; secret types holds no subrail entry at all
(/secret-types stays routed and gated, rendering in the pane). The naming rule
(ADR-0083)
carries into the subrail: a group is named for the fleet noun it serves, an entry keeps the
registry’s own word, and where the registry’s only word is “type” the entry is Types with the
group completing the sentence (Catalog, under Locations: Types). The organizing line the groups
teach: Telemetry is what you receive, Actions is what you send or run; an event is a record of
a happening (caught from the fleet or caused by the platform), never an outbound message, which
is why Events sits in Telemetry while Rules, Commands, and the future Notifications sit in
Actions.
Values is its own top-level group, beside Inventory: values set on fleet entities and resolved down the cascade, a distinct genus from the entities themselves. Config is the CI store (desired configuration, optionally observed back to detect drift and reconcile), distinct from platform Settings (preferences: severity scales, schedules, retention, defaults) and Variables (free interpolated values, no observed side); the full split is config, secrets, and variables.
Inventory holds the fleet entities: locations, systems, components, and nodes, the
collection daemons, monitored and scope-controlled (live, gated on node:read plus ABAC scope), so
a node sits in Inventory, not Admin. Interfaces and tasks are not nav items: an interface is a
panel on a component, a task a panel on a node, facets of the owning entity’s detail page.
Admin is the renamed Settings group: Users, Roles, Groups, Audit, plus the live Settings leaf, the platform-preferences page.
The Home situation room and the Dashboards tier, tracked in #523
Home is distinct from Dashboards. Dashboards monitor the fleet; Home monitors the monitor: config lifecycle (stale templates), control-plane health (rules failing to evaluate, samples dropped with no matching rule), proactive suggestions. A dashboard cannot model that; “Overview” is the default dashboard’s name, not the landing.
The theme is dark-first (the NOC aesthetic) on the brand palette (teal #21CAB9, navy
#080c16), semantic tokens only, no hardcoded colors in components.