Gunnar

nordisk/nornir

.nornir/arch-tab-live.md

<!-- nornir:class: guide -->
# 🏛 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's `defined_in`, with CLI parity (`nornir arch open <symbol>`). Built 2026-06-18.
> Plan: [nordisk_plan.md](nordisk_plan.md). Builds on the existing EPIC ARCH board
> ([`src/viz/arch_tab.rs`](../src/viz/arch_tab.rs)) + the coverage feature's boundary classifier
> ([`src/coverage/mod.rs`](../src/coverage/mod.rs) `classify`).

## 1. The container — one tab, four lenses

The 🏛 Architecture tab no longer renders a single board. It paints a **sub-view selector strip**
([`ArchSubView`](../src/viz/arch_tab.rs)) 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`](../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 trace` twin).
* 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:line`
  still 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)`](../src/coverage/mod.rs) 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:

```jsonc
"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`](../src/arch/mod.rs) — `SymbolLoc`, `resolve_symbol`, `open_in_editor`, `source_window` (the shared bridge) + unit tests.
* [`src/bin/nornir.rs`](../src/bin/nornir.rs) — `ArchOp::Open` + `ArchOpenArgs` + `arch_open` handler.
* [`src/viz/arch_tab.rs`](../src/viz/arch_tab.rs) — `ArchSubView` container, sub-view selector, Live repaint, click-to-code, source-preview panel, Hållpunkter lens, extended `state_json`.
* [`src/viz/app.rs`](../src/viz/app.rs) — Deps lens routed to `draw_dep_graph`, click-to-code source seeding, test injectors.
* `tests/viz_arch_subviews.rs`, `tests/cli_parity.rs` — the tests above.