🏛 Architecture tab — live, clickable, multi-functionality system diagram (design + guide)
The viz Architecture tab is a CONTAINER with an internal sub-view selector (Deps · Live system · Wiring · Hållpunkter). Every node is clickable into the source (
file:line) via the knowledge graph'sdefined_in, with CLI parity (nornir arch open <symbol>). Built 2026-06-18. Plan: nordisk_plan.md. Builds on the existing EPIC ARCH board (src/viz/arch_tab.rs) + the coverage feature's boundary classifier (src/coverage/mod.rsclassify).
1. The container — one tab, four lenses
The 🏛 Architecture tab no longer renders a single board. It paints a sub-view selector strip (ArchSubView) and dispatches to the active lens — deps is CONSOLIDATED under Arch (the new Arch sub-views are NOT separate top-level tabs):
| sub-view | id | what it draws | source |
| 🔗 Deps | Deps |
the dependency graph (cross-repo + intra-repo) | draw_dep_graph / DepGraphView, painted by app.rs (it owns the timeline) |
| 📡 Live system | Live |
the wiring board, warehouse-driven, repaint-on-change | draw_board + a live toggle + request_repaint_after (mirrors bench_live/coverage_live) |
| 🏛 Wiring | Wiring |
the EPIC ARCH wiring board (arch_graph/arch_svg), static |
the merged ArchGraph from the warehouse |
| 📍 Hållpunkter | Boundaries |
the ui / gRPC / emitter landmark functions as nodes | boundary_landmarks() via coverage::classify (REUSED) |
The active lens + the full lens list are in state_json["architecture"].{sub_view,sub_views} so a robot test enumerates every lens mechanically.
2. Click-to-code — the symbol → file:line bridge
A node's label is a symbol-ish string (a T for a Component, Service.verb for gRPC, a crate::module bucket for a core fn). The one edge a call graph can't give is fn → file, so we resolve the label against the scanned symbol_facts by name:
node click / Hållpunkter chip / `nornir arch open <symbol>`
│
▼
nornir::arch::resolve_symbol(&symbols, query) ← SHARED by the viz tab AND the CLI verb
│ exact name → exact leaf segment → ci → substring (best first, deterministic)
▼
SymbolLoc { symbol, kind, file, line }
│
├─ (i) nornir::arch::open_in_editor(root, file, line) → `code --goto file:line` (best-effort; logs if absent)
├─ (ii) nornir::arch::source_window(root, file, line, 6) → inline source-preview panel in the viz
└─ (iii) `nornir arch open <symbol> [--open] [--all]` → prints `file:line kind symbol` (CLI parity)
- The resolver, the editor launcher, and the source-window reader all live in
src/arch/mod.rs(resolve_symbol/open_in_editor/source_window) so the viz tab and the CLI verb run the same code — no drift. - The clicked node also lights its downstream wiring (the existing
nornir arch tracetwin). - Tables have no symbol, so a table click is select-only (no resolve).
- Remote/thin mode has no checkout → resolution returns nothing; the graph + the printed
file:linestill work, only the inline source preview needs the source.
3. Hållpunkter — boundary landmarks (REUSING the coverage classifier)
The Boundaries lens does NOT reinvent boundary discovery. It calls the coverage feature's nornir::coverage::classify(fn_name, file) on every scanned symbol and keeps the ui / grpc / emitter hits as landmark nodes (kind-mapped to the board legend: ui→Component, grpc→Grpc, emitter→CoreFn). The per-boundary tally + the landmark labels are in state_json["architecture"].boundaries.{ui,grpc,emitter,total,landmarks}.
4. state_json + $NORNIR_VIZ_TRACE (emit LAW #6)
ArchTabState::state_json() exposes, beyond the existing board/coverage keys:
"architecture": {
"sub_view": "Wiring", "sub_views": ["Deps","Live","Wiring","Boundaries"], "live": false,
"boundaries": { "ui": N, "grpc": N, "emitter": N, "total": N, "landmarks": [ … ] },
"last_open": { "query": "ArchTabState", "resolved": "src/viz/arch_tab.rs:3",
"file": "…", "line": 3, "symbol": "ArchTabState",
"candidates": 1, "editor_opened": true|false|null,
"preview_lines": 13, "preview_start": 1 }
}
Trace points ($NORNIR_VIZ_TRACE): architecture.subview (lens switch), architecture.open (IN: query+candidates / END: resolved file:line + editor result), plus the existing architecture.load / architecture.render.
5. Tests (inject-assert + wgpu snapshot)
| layer | file | proves |
| resolver | src/arch/mod.rs resolve_symbol_finds_file_line_by_name_and_leaf |
exact path+line + match priority (name/leaf/gRPC verb) |
| preview | src/arch/mod.rs source_window_reads_context_lines |
the ±N window reads real lines |
| container | tests/viz_arch_subviews.rs arch_container_lists_subviews_and_renders_board |
the four lenses listed + the board counts |
| click-to-code | tests/viz_arch_subviews.rs arch_click_resolves_symbol_to_file_line |
a click resolves to a known file:line + inline preview, via state_json |
| landmarks | tests/viz_arch_subviews.rs arch_boundary_landmarks_cover_ui_grpc_emitter |
ui/grpc/emitter landmarks present + a wgpu golden PNG of the lens |
| CLI parity | tests/cli_parity.rs arch_is_registered_with_open_and_friends / arch_open_accepts_symbol_and_flags |
nornir arch open <symbol> is wired with --open/--all |
Run:
cargo test --features viz --test viz_arch_subviews -- --nocapture
cargo test --features viz --lib arch::
cargo test --test cli_parity arch
6. Files touched
src/arch/mod.rs—SymbolLoc,resolve_symbol,open_in_editor,source_window(the shared bridge) + unit tests.src/bin/nornir.rs—ArchOp::Open+ArchOpenArgs+arch_openhandler.src/viz/arch_tab.rs—ArchSubViewcontainer, sub-view selector, Live repaint, click-to-code, source-preview panel, Hållpunkter lens, extendedstate_json.src/viz/app.rs— Deps lens routed todraw_dep_graph, click-to-code source seeding, test injectors.tests/viz_arch_subviews.rs,tests/cli_parity.rs— the tests above.