Terminal-top — a Brick TUI where every panel is a .nix file

Sharing a project I’ve been working on: a Haskell/Brick terminal dashboard where a “domain” (a set of live data sources plus a panel layout) is defined in a single .nix file, not in Haskell code. The platform is generic; the meaning — which URLs, which JSON paths to navigate, which sections to render, what thresholds to colour at — lives in the nix files.

Haskell side, briefly:

The renderer is a small set of section widgets (stat, sparkline, table, articles, legend, groupCount, plus plain headline / caption) that each consume an Aeson.Value through a tiny navigation / aggregation DSL (path, field, from = last|first|max|min|avg|count, where for row filtering, delta for first-differences, scale = log for log-axis sparklines).

Brick’s getContext + availWidth / availHeight give width-adaptive tables and sparklines; column alignment is inferred per-column from the data (majority-numeric → right-aligned).

The fetch layer uses http-client-tls with crypton-connection, plus an IPv4-only override because a few upstreams publish AAAA records that route to unreachable subnets on some networks — the connection stalls past our response timeout before the kernel would fall back to A. ${VAR:-default} interpolation inside URLs and header values happens at fetch time, not nix eval, so the domain files stay pure.

State is EventM + microlens, no TH, no lens dependency. Incremental search (/) is a total event-handler override while typing so other bindings (q, r, Tab) can’t fire mid-keystroke.

Nix side, briefly:

Domains are evaluated at flake build time into a single JSON blob; the wrapper sets TERMINAL_TOP_DOMAINS_JSON to that store path, so the binary reads one env var and needs no Nix at runtime. A mkDomain.nix helper validates authored attrsets with path-aware errors (domain 'X' → source [0] → panel [1] 'Y' → section [2] (stat) is missing required key(s): label).

Built-in domains:

Picked for subjects where the source of the number matters more than the chart style: Gaza / West Bank casualties (Tech for Palestine / Gaza MoH / OCHA), Sudan IPC food crisis (FEWS NET), UCDP conflict events (Uppsala University), Climate TRACE emissions, NOAA space weather.

Try it in any browser:

Runs on a Raspberry Pi over Tailscale Funnel:

Or locally:

nix run gitlab:hunorg/terminal-top

Source:

https://gitlab.com/hunorg/terminal-top

Curious for feedback on the section vocabulary, whether the JSON-navigation DSL (path + field + from + where + delta + scale) is expressive enough, and any Brick idiom suggestions from people who’ve shipped larger TUIs.

Screenshot:

6 Likes