Files
terdut-tui/CLAUDE.md
Niklas Ye 4a579bdbc6
CI / test (pull_request) Successful in 4s
Colour themes, defaulting to gruvbox dark
Every colour was a 256-colour ANSI index hardcoded in styles.go, so changing
the palette meant editing the styles themselves. This puts a semantic token set
between the two: styles name roles, a theme supplies the colours.

internal/theme holds the twelve tokens, the two built-ins (gruvbox-dark, the
new default, and gruvbox-light) and the loader for user themes in
~/.config/terdut-tui/themes/. A user file may 'extends:' a built-in and
override only what it cares about, and may shadow a built-in name to tweak it
in place. Unknown keys, malformed colours and incomplete themes are refused
with a message naming what went wrong.

Colours are truecolor hex now: lipgloss downsamples for 256- and 16-colour
terminals and honours NO_COLOR, so themes carry no fallbacks of their own.
An ANSI index is still accepted for anyone who would rather follow their
terminal's own palette.

The 21 package-level style vars become a Styles struct on the Model, which is
what rule 3 asked for all along; the four free functions in view.go take one as
their first argument. The embedded bubbles components are restyled from the
same tokens — otherwise a theme would leave a pink selected row and grey help
text behind. Note that the table's Cell style deliberately keeps no foreground:
bubbles renders cells before wrapping the row in Selected, so a colour there
cuts the selection highlight short.
2026-08-20 11:06:36 +02:00

111 lines
4.5 KiB
Markdown

# terdut-tui
TUI client for [terdut-server](https://git.ryuvia.com/niklas/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](https://github.com/charmbracelet/bubbletea) — TUI framework (strict Elm architecture)
- [Lipgloss](https://github.com/charmbracelet/lipgloss) — styles (all in `internal/tui/styles.go`, never inline)
- [Bubbles](https://github.com/charmbracelet/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/theme/ Colour themes: semantic tokens, built-ins, user file loader
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 Styles struct — every lipgloss style, built from a theme
internal/updater/updater.go Self-update via GitHub Releases
```
## Architecture rules
1. **Never call API inside `Update()`** — return `tea.Cmd` instead; the runtime runs it async.
2. **`View()` is pure** — no side effects, no state mutations.
3. **All state in `Model`** — no globals.
4. **All styles in `styles.go`** — never use lipgloss inline in `view.go`.
Styles live on `Model.styles`, built once by `newStyles(theme.Theme)`; the
handful of free functions in `view.go` take a `Styles` as their first
argument. No colour literal appears outside `internal/theme`.
## Config
Location: `~/.config/terdut-tui/config.yaml`
```yaml
server_url: https://terdut.example.com
api_key: <64-char hex key>
refresh_interval: 30 # seconds, optional, default 30
theme: gruvbox-dark # optional, default gruvbox-dark
```
Built-in themes are `gruvbox-dark` and `gruvbox-light`; user themes are YAML
files in `~/.config/terdut-tui/themes/`, optionally `extends:`-ing a built-in.
See the README for the token list.
The API key is a one-time secret generated by terdut-server (`POST /api/users/{id}/api-keys`).
## Running
```bash
go run .
go run . --version
go run . --self-update
```
## Building
```bash
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 |
<!-- graymatter:instructions:begin — managed by `graymatter init`; edits inside this block are overwritten -->
## 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. ⚠ takes `agent`, not `agent_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.
<!-- graymatter:instructions:end -->