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

nordisk/facett

.nornir/navgraph-design.md

navgraph — one navigation graph over the whole application, down to the fields

Status: PHASE 1 IMPLEMENTED (2026-09-28), on the v1 look tokens; PHASE 2 built in the scene renderer (2026-09-29, §9 "Phase 2 as built" — proven on a korp-shaped scene, not yet on korp). Owner, 2026-09-28: "show graph of navigation with unique numbering at dev mode" and "yes :) do navgraph". Phases 2–4 (korp, korp's fields, the editor) are not started. Look and feel v2.0 is still not designed; the overlay's colours are v1 palette roles under token names chosen to become v2.0 keys (§9). The original design text below is kept as written; §9 records what phase 1 built, where it differs, and what is left.

Owner, 2026-09-24: "in dev mode draw 1 number on each facett component … try to enumerate also IF possible in the navigation order if that has been declared." Owner, 2026-09-25: "we need a navigation graph over the whole application down to the smallest atom = fields, and numbering 1, 1.1.2.3 for sub trees. Prepare to draw the labels AND the graph/splines between them. … a future UI editor, where I can place out components and get the 'code' for it."

1. What the graph is

A tree of nodes, one per addressable thing on screen. The tree has a spine and a leaf level:

level kind example id who names it
1 App korp host
2 Surface (tab, page, dialog) korp.explore host
3.. Pane (a facet mount) korp.explore.graph.left host (MountId, phase-3 design)
.. Group (form section, toolbar, split) korp.explore.graph.left.filter component or host
leaf Field (text edit, button, checkbox, a painted value) ….filter.name component

Each node carries:

The number is the path of 1-based ranks from the root: 1, 1.2, 1.2.3, 1.2.3.4.

Edges, three kinds, drawn with separate styles:

  1. Contains: parent → child. This is the tree itself.
  2. Next: sibling n → sibling n+1 in the declared order. This is the navigation order (Tab / chord).
  3. Spatial (optional, off by default): the arrow-key neighbour from nav::nearest_in_direction, for checking that arrow navigation agrees with the declared order.

2. How it is collected (zero allocation, one pass)

facett_core::navgraph::NavGraph is a per-frame arena kept in egui's Context memory. It is allocated once and cleared every frame; nodes are indices into a Vec whose capacity is reused.

3. How it is drawn

A top layer is painted after everything else, so dev mode never moves layout. This is the same rule the devid badge follows.

4. How it is used (not only looked at)

5. The future UI editor — what else it needs

The editor's goal: place components and get the "code". The navgraph is its skeleton, because the editor selects, drags and numbers the same nodes. To get from here to there, these pieces are also needed:

  1. A layout document, the source of truth. Today the tree exists only as Rust code (korp's impl Korp). The editor needs it as data: a Layout (ron/toml) made of nodes, each { id, component, props, slot, children }.

    • The navgraph's declared order is the document's child order, so NavOrder comes for free.
    • "Get the code" then means the document plus a generated Rust builder for it (below).
  2. Component registry with a property schema. facet-wrapped's registry already has one Entry per component, with a fixture (demo()) for preview and a DataFactory. The missing piece is a typed props schema per entry (name, type, default, range, doc). That schema is what the editor's inspector renders.

  3. Containers as components. Split, tabs, grid, stack and form each need named slots, so there is somewhere to drop a component. Today these are layout code, not registry entries.

  4. Bindings. Which state path each field reads and writes, and which message an event sends (the Elm loop, elm-design.md). Without this, a placed component is a picture, not an app.

  5. Codegen that round-trips. Layout doc → Rust (the builder calls a hand-written view would make).

    • Deterministic output.
    • The test: doc → code → compile → render → the same navgraph JSON as rendering the doc directly.
    • Edits come back to the doc, never into the generated code.
  6. Editor mode on the same overlay. Hit-testing, selection, drop targets and reordering use the navgraph's rects and scopes. Undo and redo are operations on the document.

Order: 1 → 2 → 3 make placement possible. 4 makes the result work. 5 gives the code. 6 is the UI on top. Phase 1 below builds none of this. It only keeps the node ids and the declared order compatible with §5.1, so the editor never needs a second id scheme.

6. Phases (not scheduled — no code before look and feel v2.0 is designed)

phase where delivers proof
1 facett-core navgraph.rs arena, scope stack, registration hooks (a11y, devid, focus), numbering incl. ~/?, overlay (labels + 3 edge kinds), to_json, chord focus unit tests for numbering and spline paths; a facett-demo kittest: declared order ⇒ no ~/?, chord 1.2 focuses the right node, JSON snapshot
2 korp NavOrder for korp's tabs and panes; panes wired into the deck the korp robot test addresses panes by number
3 korp + facett fields in korp's forms registered the "no ~/?" law green on korp
4+ facett §5 editor pieces, in the §5 order round-trip test in §5.5

7. Decided (owner, 2026-09-25)

  1. The chord is Ctrl+G (then the number, then Enter).
  2. Depth N = 3 (app, surface, pane). Groups and fields show under the hovered or focused node only.
  3. The layout document (§5.1) is ron.

8. Part of look and feel v2.0

The navgraph's chips, splines, depth tints and edge styles are look-and-feel tokens, not literals of their own. They belong in the v2.0 token set beside colour, spacing and motion, so the overlay, the devid badge and a future editor share one visual language. Open for the v2.0 design:

9. Phase 1 as built (2026-09-28)

Where it lives

piece file
arena, numbering, edges, JSON, plugin, keys, overlay facett-core/src/navgraph.rs
overlay colours (v1 roles → v2.0 token names) facett-core/src/look/navtokens.rs
buffered spline sampling (no allocation per edge) facett-core/src/edges.rs spline_points_into
registration: FC-4 atoms facett-core/src/a11y.rs node → navgraph::atom
registration: deck panes facett-core/src/devid.rs badge → navgraph::pane (+ install)
registration: tab strip + active pane facett-core/src/lib.rs FacetDeck::ui / pane_ui
registration: form groups facett-core/src/focus.rs FocusGroup::nav_register
browser half (Ctrl+G default, ?nav=) navgraph::web (wasm only)
demo wiring + declared order facett-demo/src/lib.rs draw_app, demo_nav_order
Streamlit wiring facet-wrapped/src/devmode.rs, facet_dev / facet_navgraph in wasm.rs, loader args.dev, sidebar toggle in py/facett_demo_python/app.py

How it runs

Differences from the design text

Keys

Ctrl+Shift+G cycles off → labels → labels+next → labels+all edges. Ctrl+G, digits and ., Enter focuses the node (a node that is not a widget focuses its first focusable descendant); Escape cancels, Backspace edits. The chord being typed is shown bottom-left.

Proof

Phase 2 as built (2026-09-29, lane A, branch lane-a/navgraph-streamlit @ b7859b9)

korp's tabs and panes are not korp code: korp (and dwarves, and every scene-based app) draws them through facett_app::scene::render, and the scene is a DATA tree. So phase 2 is in the renderer, not in korp, and no host writes a NavOrder for its tabs and panes:

Proof: facett-app/tests/navgraph_scene.rs (a korp-shaped scene: rail + three tabs) — shell 1, rail 1.1, tab set 1.2, strip 1.2.1, headers 1.2.1.1..3, active tab 1.2.2 before and after a tab switch, no ~/? on the spine, Ctrl+G 1.2.1.3 Enter focuses the third header: 3/3. On korp's real scene (korp/src/scene_shell.rs: korp-shell = split[facets, korp-tabs]) this gives korp-shell 1, facets 1.1, korp-tabs 1.2, the tab strip 1.2.1, the active tab 1.2.2 and its panes 1.2.2.n.

Left

10. Ordinary egui (owner requirement, 2026-09-28)

Owner: "navgraph must work on ordinary egui also, of course." The overlay numbers and draws any egui app — an eframe app, korp-ui, dwarves-gui — with no facett component and no hand-written navgraph::field call.

How

Proof

facett-core/tests/navgraph_plain_egui.rs (kittest, plain egui only: a side panel with two buttons and a checkbox, a Window with a three-button row and a TextEdit): every widget is a node with a unique number, the window is its own surface, the button row is one group inside it, every step is ~, the chips reach the AccessKit tree, and Ctrl+G <the text edit's number> Enter focuses the TextEdit without typing the chord's digits into it. In facett-demo the control strip's plain egui widgets are harvested the same way.

Left