CLAUDE.md
Rama main · réplica de lectura del repositorio original.
# Dashglass — notes for coding agents
Lightweight, Prometheus-only dashboarding (the dashboard part of Grafana,
nothing else) with GitOps-first file storage. **Read DESIGN.md before any
architectural change** — it is the source of truth for the data model, the
plugin system and the Bleemeo SaaS convergence plan. This file only covers
what you need to work in the repo.
## Layout
pnpm monorepo (TypeScript, React 19, strict tsconfig) + a Go backend:
- `packages/model` — dashboard schema (Zod, Perses-derived `kind`/`spec`),
deterministic JSON/YAML/TOML serializers, migrations. The file format is
the product.
- `packages/runtime` — dashboard engine: plugin registries (panels, pages,
variable sources), `PrometheusDataSource` interface, query orchestrator,
React providers, `DashboardRenderer`/`DashboardGrid`.
- `packages/datasource-prometheus` — HTTP impl of the interface (native
Prometheus API paths) + `PrometheusLabelValuesVariable` source.
- `packages/panels-builtin` — panel plugins: TimeSeriesChart, StatChart,
GaugeChart, Table, BarChart, HeatmapChart, Markdown (ECharts via
`echarts/core`; Stat/Table/Markdown are plain HTML/SVG) + their
options-form editors (plain HTML `dashglass-field*`
controls). Chart theme tokens live here (`ChartThemeProvider`).
- `packages/promql-editor` — CodeMirror PromQL editor
(`@prometheus-io/codemirror-promql`).
- `packages/grafana-import` — best-effort Grafana JSON → Dashglass dashboard
converter (walker + one migration entry per panel kind; unknown kinds
imported as-is and rendered as unknown panels).
- `packages/ui` — Chakra system (`dashglassSystem`, styles the runtime's
`dashglass-*` class names via semantic tokens) + color mode
(`ColorModeProvider`, light/dark/system, `.dark` root class).
- `apps/dashglass` — OSS app shell: assembles registries, TanStack Query for
management CRUD, SSE → cache invalidation, form-based dashboard editor
(`/d/:name/edit`, react-grid-layout v2) and as-code editor
(`/d/:name/code`).
- `backend/` — Go single binary: embedded SPA, dashboards-directory CRUD
(validating), read-only allowlisted Prometheus proxy on native
`/api/v1/*` paths, fsnotify → SSE, `/api/capabilities`, the grafana.com
download proxy (`/api/grafana-com/{id}`, numeric-id-only to rule out
SSRF, `-disable-grafana-com` for air-gapped installs), and auth
(`internal/auth`: local users/roles/sessions, reverse-proxy headers,
OIDC) driven by `internal/config` (`dashglass.yaml`, `${ENV}` expansion).
- `dashboards/` — sample dashboard files used in dev.
## Commands
```sh
pnpm test # all workspace tests (vitest)
pnpm typecheck # all packages + app (tsc)
cd backend && go test ./... # backend tests
./dashglass lint [paths...] # validate dashboard files (CI); default ./dashboards
./dashglass user add <name> [role] # break-glass local user (admin UI is primary)
pnpm --filter dashglass dev # vite on :3000, proxies /api to :8080
pnpm build-binary # app build → embed → ./dashglass binary
```
Dev backend: `cd backend && go run . -prometheus-url <url>
-dashboards-dir ../dashboards`. Without a real Prometheus, write a fake one
(a ~80-line Node http server answering `query_range`/`labels`/
`label/*/values` with sine data works fine; one existed in a previous
session's scratchpad).
## Hard rules (from DESIGN.md, enforced in reviews)
- **Shared packages expose neither Redux nor TanStack Query in their public
API** (§8). The runtime's query orchestrator is dependency-free; the app
uses TanStack Query; bleemeo-panel will keep Redux. Don't leak either
into `packages/*` exports.
- **Everything extensible is `kind`/`spec`** resolved through a registry.
Plugin specs are opaque to `packages/model` (each plugin validates its
own spec with a forgiving zod fallback). Unknown panel kinds must degrade
gracefully, never crash — that's the OSS-opens-SaaS-dashboard scenario.
- **Serialization must stay deterministic and git-friendly**: stable key
order (`canonical.ts`), no volatile fields, defaults omitted. A one-field
edit must produce a one-line YAML diff — there is a test asserting this;
keep it passing.
- **After changing the Zod schemas in `packages/model`**, run
`pnpm generate-schema` (in that package) to regenerate
`backend/schema/dashboard.schema.json`, and mirror any cross-field check
in `backend/internal/store/validate.go` (`crossFieldIssues`).
- The Go proxy allowlist is **read-only Prometheus endpoints only** — never
expose admin/write paths.
- Chart colors: categorical slots assigned by index in fixed order (the
ordering is the colorblind-safety mechanism), status colors reserved for
thresholds/states and never used as series colors.
- The model, docs, comments and commit messages are in **English**.
## Gotchas
- `pnpm build-binary` overwrites the tracked placeholder
`backend/internal/ui/dist/index.html` with the real build. Restore it
before committing (`git diff` will show it); build assets are gitignored
but the placeholder must stay tracked or `go:embed` breaks fresh clones.
- Workspace packages are consumed as TS source (`exports: ./src/index.ts`),
so compiler options (jsx, DOM lib) live in `tsconfig.base.json`.
- `window.fetch` must be bound when stored (`fetch.bind(globalThis)`) —
already handled in `http-datasource.ts`; same applies to any new client.
- jsdom has no canvas: test ECharts panels through their pure option
builders (`buildTimeSeriesOption`…), not by mounting `<EChart>`.
- End-to-end visual checks work well with the full binary + a fake
Prometheus + headless Chrome screenshots
(`--headless=new --screenshot=… --timeout=15000`); the SSE stream keeps
the page "loading" forever, so a hard `--timeout` is required
(`--virtual-time-budget` hangs).
- jsonschema/v6 (Go): `ErrorKind.LocalizedString` needs a non-nil
`message.Printer`.
- **Kill leftover `./dashglass` processes when a served UI looks stale.** A
binary from a previous session can hold `127.0.0.1:8080` (IPv4) while
docker compose only gets the IPv6 side — the browser then reaches the
old process and curl/localhost may reach the new one. `lsof -nP
-iTCP:8080 -sTCP:LISTEN` shows the split; also check for a stray
`fake-prom` node process. Restart the compose stack after killing so
it rebinds IPv4.
- To screenshot both color modes, drive headless Chrome over CDP
(`--remote-debugging-port`) and call `Emulation.setEmulatedMedia` with a
`prefers-color-scheme` feature — there is no CLI flag for light, and the
host OS preference leaks into `--screenshot` runs. Working scripts from a
past session: `shot.mjs` / `editor-check.mjs` in that session's
scratchpad (rewrite is ~60 lines).
- Code splitting relies on `sideEffects` in the workspace package.json
files. Two traps: `echarts-setup.ts` must stay listed as side-effectful
in `panels-builtin` (its `echarts.use()` gets shaken away otherwise and
charts crash at runtime with "x is not a constructor"), and the
panels-builtin root entry must never statically re-export chart
components or ECharts lands back in the app's main chunk (chart panels
register via `React.lazy`; `PanelChrome` owns the Suspense boundary).
## State / roadmap
Canonical feature status (done / planned / non-goal) lives in **DESIGN.md
§13** — defer to it rather than restating status here, and update it when a
feature lands. Build order and the phased plan: DESIGN.md §11 (steps 1–5
done). The rest of this section is implementation pointers, not status.
CI (`.github/workflows/ci.yml`): tests/typecheck/lint + a Docker smoke test
(`scripts/docker-smoke.sh`, non-root container auth surface). Where things
live: variable + data-link URL interpolation in
`packages/runtime/src/interpolate.ts` (`interpolate`, `interpolateUrl`);
panel-repeat layout reflow in `packages/runtime/src/repeat.ts`; chained
variables and repeat value-pinning in `packages/runtime/src/context.tsx`;
form-editor document ops in `packages/model/src/edit.ts`.
Git integration: `backend/internal/history` (history + commit-on-save +
`sync.go` remote pull/push; credentials via the GIT_ASKPASS `askpass`
subcommand, never on a command line). Runtime admin config (datasource, git
remote): `internal/config/state.go`, persisted in `settings.yaml` next to
`auth.yaml` (0600); anything provisioned by flag/env/file is locked in the
UI. Auth phases (DESIGN §7.4): local users/roles/admin UI, reverse-proxy
headers and OIDC (auth-code + PKCE, groups→role) done; LDAP planned.
Screenshots for docs: `scripts/screenshots/` (fake Prometheus + demo
dashboard + CDP light/dark capture).