# The console Vite + React + TypeScript + Tailwind, built into `internal/web/dist` and served by the Go binary from an `//go:embed`. There is no separate deployment: the binary *is* the console. ## Day-to-day ```sh make web-check # npx tsc -b --noEmit && npm run build (what CI runs) make web-dev # Vite dev server, proxying /api to 127.0.0.1:8080 make web # production build into internal/web/dist ``` For `make web-dev` to show anything, a `fancy-dns` has to be listening on 127.0.0.1:8080 with the API key the console sends. `deploy/config.example.yaml` binds there, but on port 53; rewrite the ports (or use `make smoke`, which does it for you) before starting it. Two traps that have cost time, both from `AGENTS.md`: - **`tsc -b` replays cached diagnostics** from `node_modules/.tmp/*.tsbuildinfo`. After a large edit `rm -f node_modules/.tmp/*.tsbuildinfo`, or you will read errors that no longer exist and miss ones that do. - **`npm run build` empties `internal/web/dist` and deletes its `.gitkeep`.** Restore it (`git checkout -- internal/web/dist/.gitkeep`). The Go build still works while the directory is empty, so a console rebuild in the middle of someone else's `go build` is not fatal — but the commit is. ## The API contract `src/lib/types.ts` is a **hand-written mirror** of the Go `json` tags in `internal/api` and `internal/app/hooks.go`. `apiFetch` ends in `JSON.parse(text) as T`, so the compiler checks the console against its own declarations and nothing else: a field that loses its `json` tag, or a response a struct never had, shows up as an empty table in a browser and nowhere else. That is not hypothetical — a round of this found the Sources table, the ACL group cards and both Save buttons broken exactly that way. So, when you touch either side: - **Go**: add or keep the `json` tag. `internal/app` has contract tests that assert the exact key set of every admin endpoint; they are the spec, and a rename fails them. - **Console**: change the interface in the same commit. If the server is wrong, fix the server — do not "adapt" in the client, and do not paper a mismatch over with `as any`, an optional field or a `?? 0` default. Those casts are precisely what hides drift. - Never rename a field on one side only. The dashboard, the ServiceMonitor queries and the Grafana panels are consumers of the same JSON. The durable fix is to generate `types.ts` from the Go structs, or to publish an OpenAPI document from the server and generate from that. It is in the plan at the end of `STATUS.md`. ## What is checked `npx tsc -b --noEmit` (strict, with `noUnusedLocals`) and `oxlint` (two rules: `react/rules-of-hooks` and `react/only-export-components`). There is **no test runner** and no component test — the console's correctness rests entirely on its types, which are only as good as the mirror above. Adding vitest and a handful of tests for `lib/format.ts` (the percent/latency/duration formatters, which have already shipped a units bug) is cheap and worth doing.