Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/nornir

.nornir/scene-design.md

nornir viz — the Urðr-Threads master shell as a DECLARATIVE scene — design

Class: design · Function axis: f0 (the master shell as data) · f1 the shell builder · f2 NavState + render wiring · f3 the SceneHost that DRIVES the panes · f4 responsive + drawer · f5 observability + tests.

Strict: this file states only the shape we run. The scene lives in src/viz/shell_scene.rs; the render wiring + host live in src/viz/app.rs.

f0 — the master shell as DATA

nornir's egui shell (UrdrThreadsApp) is a real master/detail app: a top-bar tab strip, a right "Time machine" inspector, a bottom action-log, and a central active pane. Its master/detail LAYOUT is now described DECLARATIVELY as a [facett_app::scene::Scene] — a menubar + a tree of foldable/draggable/drawer panes

What moved onto the scene (was hand-wired panels):

What deliberately STAYS facett_ui::TabBar (the migration brief permits keeping it as the menubar host): the 28-tab top strip + the workspace/live/🎨-palette pickers and the ℹ About / 🐞 action-log toggles — the stateful chrome that has no clean menu mapping and that the whole existing robot suite drives by label. The scene ADDS an in-window View / Go / Help menubar (facett-menu) with shortcut hints + routes.

f1 — the shell builder (shell_scene.rs)

nornir_shell_scene() (src/viz/shell_scene.rs:80) builds the shell with the Rust builder; the identical value round-trips byte-stable through RON.

root  ─ Split(H, 0.74, DRAGGABLE)              [phone → Tabs under 760pt]
├─ workspace ─ Split(V, 0.82, DRAGGABLE)
│  ├─ panes      (Fixed)    slot:active_pane    — the ACTIVE tab's pane (28 Tab slots)
│  └─ action_log (Foldable 🐞, drawer@760) slot:action_log  — the action trail
└─ detail        (Foldable 🔍)  slot:detail     — the "Time machine" inspector

menubar: View { Toggle Time machine (Ctrl+I), Toggle Action trail (Ctrl+L), Reload (Ctrl+R) }
         Go   { Home (Ctrl+1), Release (Ctrl+2), Test (Ctrl+3), Warehouse (Ctrl+4) }
         Help { About (F1) }
routes:  Ctrl+I → Toggle(detail); button focus-detail → Focus(detail); go.release → Show(panes)

f2 — NavState + render wiring (app.rs)

UrdrThreadsApp holds one nav: NavState (the live fold / split-ratio / route state) seeded in the constructor from nornir_shell_scene(). Each frame, inside the central egui::CentralPanel (src/viz/app.rs:3232), draw_ui:

  1. rebuilds the (cheap, 28-pane) scene;
  2. syncs the action-log drawer fold to the top-bar 🐞 toggle (set_fold(ACTION_LOG_SLOT, !show_action_log));
  3. takes nav OUT (std::mem::replace) so self is free to be the SceneHost, then calls scene::render(&scene, &mut nav, ui, self);
  4. flows a scene-side drawer toggle back to show_action_log, restores nav, and caches the returned render Value in last_scene_render.

f3 — the SceneHost that DRIVES the panes

impl facett_app::scene::SceneHost for UrdrThreadsApp (src/viz/app.rs:3295) maps each slot id to a REAL widget and returns its state:

f4 — responsive + drawer

scene::render picks a Device from the available width. The root master/detail split carries .phone(Layout::Tabs, 760) → under 760pt it collapses to Tabs; the action_log pane carries .drawer(760) → it auto-folds crossing INTO phone-class and restores above it (the desktop-rail → phone-drawer pattern). Both share the PHONE_BREAKPOINT (760pt).

f5 — observability (LAW #6) + tests

state_json()["scene"] (folded in at src/viz/app.rs:1334 via scene_state(), src/viz/app.rs:3840) carries the pane tree, the foldable / drawer / draggable-split sets, the 28 host tab slots, the live folded + split_ratio maps, the menu/route counts, and the last scene::render facts (device, per-pane rects, per-slot state, live ratios + drag-handle separators). So a robot asserts the whole shell without pixels.

Tests