{
"$type": "site.standard.document",
"bskyPostRef": {
"cid": "bafyreifszvw3ly7ursk5wfdscmhtxazzznowi226i3sxwwdthmgfnowmmm",
"uri": "at://did:plc:pi6woz4d47bkuws673w2il2r/app.bsky.feed.post/3mk3grsiefne2"
},
"path": "/t/terminal-top-a-brick-tui-where-every-panel-is-a-nix-file/13970#post_1",
"publishedAt": "2026-04-22T11:31:19.000Z",
"site": "https://discourse.haskell.org",
"tags": [
"https://raspberry.tailb4a955.ts.net/",
"https://gitlab.com/hunorg/terminal-top"
],
"textContent": "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.\n\nHaskell side, briefly:\n\nThe 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, optional `delta` for first-differences).\n\nBrick’s `getContext` + `availWidth` / `availHeight` give width-adaptive tables and sparklines; column alignment is inferred per-column from the data (majority-numeric → right-aligned).\n\nThe fetch layer uses `http-client-tls` with `crypton-connection`, plus an IPv4-only override for one upstream that 404s over IPv6 on some CGNAT paths. `${VAR:-default}` interpolation happens at fetch time, not nix eval, so the domain files stay pure.\n\nState 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.\n\nNix side, briefly:\n\nDomains 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`).\n\nBuilt-in domains:\n\nPicked 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.\n\nTry it in any browser:\n\nRuns on a Raspberry Pi over Tailscale Funnel — the uplink is still a bit flaky at the moment, so give it another try if the first load doesn’t come through:\n\nhttps://raspberry.tailb4a955.ts.net/\n\nOr locally:\n\n`nix run gitlab:hunorg/terminal-top`\n\nSource:\n\nhttps://gitlab.com/hunorg/terminal-top\n\nCurious for feedback on the section vocabulary, whether the JSON-navigation DSL (`path` + `field` + `from` + `where` + `delta`) is expressive enough, and any Brick idiom suggestions from people who’ve shipped larger TUIs.\n\nScreenshot:",
"title": "Terminal-top — a Brick TUI where every panel is a .nix file"
}