| adr | 0020 | |
|---|---|---|
| title | Hliðskjálf full-screen top view, NPU rename, and screen-aware TUI | |
| status | accepted | |
| date | 2026-06-30 | |
| supersedes | ||
| superseded_by | ||
| deciders |
|
The dashboard had a process table bound to t ("top"), but no mactop/btop-style
single-host system view. Users watching one host — often over SSH from Termius on
a phone or tablet — wanted per-core CPU, memory, power, accelerator, network, and
disk at a glance, on any screen size.
Two pre-existing constraints shaped the work. The accelerator power metric was
named power.ane (Apple Neural Engine), which is Apple-specific for what is
really a generic NPU other platforms also have. And the wire must stay backward
compatible: a v2.2 dashboard runs against older daemons/hubs in a mixed fleet
(see ADR 0001).
Goals:
- A full-screen single-host "top" view entered with
t, exited withesc/q. - Reuse data already collected (
model.history, per-core values) — render only. - Generic accelerator terminology (
power.npu) without breaking a mixed fleet. - New cross-platform metrics (load, swap, per-core freq) that degrade gracefully.
- Both the top view and the host detail view reflow to the terminal width.
Non-Goals:
- No new binary; the view lives in
heimdall-dashboard. - No fleet aggregation in the top view — it shows the single focused host.
- No TSDB; trends keep using the in-memory ring buffers from ADR 0008.
- Keys:
popens the (renamed) process table;topens the new top view. topviewpackage: a pure, render-only Bubble Tea sub-model. The dashboard constructs it from the focused host's snapshot + history, delegatesUpdate/Viewwhile active, refreshes it on each tick (preserving scroll), resizes it onWindowSizeMsg, and leaves onesc/q. It never touches adapters.Updatereturns an explicit action (ActBack/ActQuit) soqquits the app whileescreturns to the grid.- NPU rename (the data-model decision): the canonical metric key is
power.npu.power.aneis kept as a read alias, normalised topower.npuin one place —domain.HostRegistry.Observe— so any older daemon in the fleet still renders under NPU. The wire (MetricSample) is name+value, so new keys are additive and need no proto change. - New collectors (
swap,load,freq), each returningUnavailable(rendered—) where the platform can't supply it;cpu.freqreads/syscpufreq on Linux and falls back to gopsutil elsewhere. - Screen-aware layout:
topview.layout(width)picks the densest of four tiers (wide grid → medium → narrow → iPhone-portrait key-numbers), and the host detail view scales its gauges/sparklines and reflows its field rows the same way. Every rendered line is clamped to the terminal width.
| Option | Pros | Cons | Why Rejected |
|---|---|---|---|
| Extend the modal system for the top view | reuses modal plumbing | couples to the already-large modal.go; not full-screen |
a dedicated sub-model is cleaner and independently testable |
Separate heimdall-top binary |
isolation | another binary to ship/run; not in-dashboard | users want it inside the dashboard |
Rename power.ane with no alias |
clean break | breaks mixed fleets reading the old key | BC is a hard constraint; alias is cheap |
| Add an NPU/freq proto field | typed wire | proto regen; wire churn for an additive value | name+value wire already carries new keys |
- The
power.anealias is a small permanent normalisation inObserve; it costs one map lookup per metric and must stay until no v2.1-era daemon remains. npu.utilandmem.bwhave no collector yet (they render—everywhere); on Apple Silicon Pro/Max the IOReport channels read ~0 regardless, so this is honest, not a regression. Documented as a follow-up.- The top view refreshes by rebuilding from the latest snapshot each tick; scroll is preserved explicitly. A future per-row selection would need more state.
FinOps: None. No new services or storage; trends remain in bounded RAM.
SRE: The view is render-only and cannot fail collection. New collectors are
failure-isolated per ADR 0003:
an unsupported platform yields Unavailable, never an error that drops the host.
Security: No new privilege. The top view reads existing snapshots; the new collectors are unprivileged (load/swap/freq need no root).
Team: The topview package is small and self-contained; the responsive
pattern mirrors the existing layout(width)/scrollWindow used by the grid.
Ship a dedicated, render-only topview sub-model on t (process table moves to
p), rename the accelerator power metric to the vendor-neutral power.npu while
honouring power.ane as an ingest alias for mixed fleets, add graceful-degrade
load/swap/freq collectors, and make both the top view and the host detail view
reflow to the terminal width so they stay usable down to a phone-sized terminal.
Status: accepted
-
topviewpackage + dashboard wiring (t/p, tick refresh, resize) -
power.ane→power.npualias inHostRegistry.Observe -
swap/load/freqadapters with graceful degradation - Responsive host detail view + width-bound tests
- Real
npu.util/mem.bwcollectors where the platform exposes them - Optional
ProcessRow.User(proto field) to restore a USER column