# CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## What this is A terminal dashboard (TUI) built with `github.com/grindlemire/go-tui`, showing simulated CPU/memory/disk gauges, network sparklines, and a scrolling event feed. Single binary, no external services. ## Commands ```sh # Regenerate dashboard_gsx.go from dashboard.gsx (required before build if the .go file is missing/stale) go run github.com/grindlemire/go-tui/cmd/tui generate ./... # Build go build ./... # Vet go vet ./... # Run the app go run . ``` There are no test files in this repo yet. ## Architecture ### The .gsx → .go code generation step `dashboard.gsx` is the source of truth for the UI. It's written in `.gsx`, a JSX-like DSL (Tailwind-esque `class` strings, `{expr}` interpolation, `for`/`if` control flow, a `templ` method that compiles to `Render`) provided by the `go-tui` module. **`dashboard_gsx.go` is generated output — do not hand-edit it.** It's git-ignored and carries a `// Code generated by tui generate. DO NOT EDIT.` header. To change the UI: edit `dashboard.gsx`, then run `go run github.com/grindlemire/go-tui/cmd/tui generate ./...` to regenerate `dashboard_gsx.go`. Non-templ Go code (helper funcs, the `dashboardApp` struct, `produceEvents`, etc.) also lives in `dashboard.gsx` — the whole file is the source, not just the template block. The `tui` CLI is wired into `go.mod` as a Go 1.24+ `tool` dependency (`tool github.com/grindlemire/go-tui/cmd/tui`), which is why `go.sum` carries `golang.org/x/tools` etc. even though the app code doesn't import them directly. Don't `go mod tidy` this away — it strips the generator's transitive deps and breaks `go run .../cmd/tui generate`. CI (`.gitea/workflows/build.yml`) regenerates `dashboard_gsx.go` from scratch on every run before building, so the generated file never needs to be committed or kept in sync manually. ### App structure (go-tui conventions) - `main.go` wires up `tui.NewApp(tui.WithRootComponent(Dashboard(eventCh)), tui.WithMouse())` and runs it. A background goroutine (`produceEvents`) pushes random events onto `eventCh`. - `dashboardApp` (defined in `dashboard.gsx`) is the root component. It implements several go-tui interfaces, each independently optional and picked up via interface satisfaction (see the compile-time checks at the bottom of the generated file): - `KeyMap() tui.KeyMap` — key bindings (`q`/Esc to quit, `j`/`k`/arrows to scroll). - `HandleMouse(tui.MouseEvent) bool` — mouse wheel scroll support. - `Watchers() []tui.Watcher` — background subscriptions: a 500ms timer (`updateMetrics`, simulates metric drift) and a channel watch (`addEvent`, consumes `eventCh`). - `Render(*tui.App) *tui.Element` — the generated view tree (from the `templ` block). - `BindApp(*tui.App)` — generated; wires every `*tui.State[T]` field to the app so state changes trigger re-renders. If you override `BindApp` yourself, call the generated `bindAppFields` helper rather than re-listing fields. - All mutable UI state (`cpu`, `mem`, `sparkIn`, `events`, `scrollY`, ...) is `*tui.State[T]`, read via `.Get()` and mutated via `.Set()`. `eventsRef *tui.Ref` gives imperative access to the events panel element (used for `MaxScroll()`/auto-scroll-to-bottom in `addEvent`). - Adding a new stateful field to `dashboardApp` means: add the `tui.State`/`tui.Ref` field, initialize it in `Dashboard(...)`, use it in the `templ` block — the generator regenerates `bindAppFields` and the compile-time interface checks automatically. ### CI `.gitea/workflows/build.yml` runs on push/PR to `main`: checkout → setup-go (version from `go.mod`) → `tui generate` → `go build` → `go vet`.