Stats was the one full-screen view reached by a key of its own rather than by tab, and the interface was less coherent for it. It is now a section sitting third, after Alerts, and behaves like every other one: tab in, tab out, r to refresh. Three things fall out of the move. It auto-refreshes for the first time — the tick handler skips every non-dashboard mode, which is why the overlay never updated while it was open. Its error path no longer forces the queue back into view on a failed fetch, an assumption that only made sense while stats floated above the dashboard. And first-visit loading keys off a statsLoaded flag rather than slice emptiness, because the three empty slices a quiet server returns are a real answer, not a missing one; the loading placeholder is likewise suppressed once something has been drawn, so a background refresh cannot blank the page out from under whoever is reading it. The S key is gone, and with it the ability to peek at statistics from an open incident and land back on it. That round-trip was the only thing statsReturnMode bought, and it was the whole reason stats needed a mode.
4.0 KiB
terdut-tui
TUI client for terdut-server, a Prometheus Alertmanager receiver and incident manager. Requires server v0.4.0+.
Domain model
The server splits alerts from incidents, and this client mirrors it:
- Alert — Alertmanager's record. Firing or resolved, read-only, no workflow state.
- Incident — the work item: triggered → acknowledged → resolved, with an
assignee, snooze, notes and an append-only timeline. Many alerts to one incident,
correlated by Alertmanager's
groupKey.
All user actions target incidents. Two server behaviours the UI has to respect: manual resolve is terminal (hence the confirmation prompt), and snooze is the non-destructive "not now" alternative.
Tech stack
- Go 1.25+
- Bubbletea — TUI framework (strict Elm architecture)
- Lipgloss — styles (all in
internal/tui/styles.go, never inline) - Bubbles — table, textinput, help components
Project layout
main.go CLI entry point: flags, config load, health check, start TUI
internal/api/client.go REST API client — one method per endpoint
internal/config/config.go Config loader (~/.config/terdut-tui/config.yaml)
internal/tui/ Bubbletea UI
model.go Model struct, mode/section constants, Init(), tea.Cmd constructors
update.go Update() — dispatch only, no API calls inline
view.go View() — pure rendering
keys.go keyMap (bubbles/key pattern)
styles.go All lipgloss styles
internal/updater/updater.go Self-update via GitHub Releases
Architecture rules
- Never call API inside
Update()— returntea.Cmdinstead; the runtime runs it async. View()is pure — no side effects, no state mutations.- All state in
Model— no globals. - All styles in
styles.go— never use lipgloss inline inview.go.
Config
Location: ~/.config/terdut-tui/config.yaml
server_url: https://terdut.example.com
api_key: <64-char hex key>
refresh_interval: 30 # seconds, optional, default 30
The API key is a one-time secret generated by terdut-server (POST /api/users/{id}/api-keys).
Running
go run .
go run . --version
go run . --self-update
Building
go build -ldflags="-X main.version=v0.1.0" -o terdut-tui .
Sections
Incidents (the queue, and the default) · Alerts (raw read-only feed) ·
Stats (MTTA/MTTR and alert frequency charts) ·
Archived (archived incidents) · Schedule · Users
Development stages
| Stage | Feature |
|---|---|
| 1 | Scaffold, config, health check, placeholder TUI |
| 2 | Alert dashboard with auto-refresh and stats |
| 3 | Alert detail: acknowledge, comment, statistics charts |
| 4 | On-call schedule calendar view |
| 5 | User management and API key lifecycle |
| 6 | Incidents: queue, timeline, ack/assign/snooze/resolve, MTTA/MTTR |
Memory (GrayMatter)
This project has persistent agent memory via the graymatter MCP tools:
memory_search(agent_id,query) — call at the start of a task when prior context might matter.memory_add(agent_id,text) — call whenever you learn something durable: user preferences, decisions, conventions, gotchas.memory_reflect(action,agent,text/target) — update or forget stale facts. ⚠ takesagent, notagent_id.checkpoint_save/checkpoint_resume(agent_id) — snapshot/restore session state before major refactors or across restarts.
Use a stable agent_id of the form <project>-<role> (e.g. myapp-backend). Store conclusions, not conversation logs. Err on the side of remembering.