AuthClient dev-console + AuthServer deviceFlow via k8s/auth-dev.yaml; otel server accepts its own URL as an extra audience (OTEL_OIDC_AUDIENCE). Vite dev-only device-flow sign-in, scripts/dev-auth.mjs helper, AGENTS.md notes. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
195 lines
11 KiB
Markdown
195 lines
11 KiB
Markdown
# Agent instructions
|
|
|
|
Instructions for AI agents (and contributors) working in this repo. Currently
|
|
covers the `otel-ui` frontend design system, since that's the active area of
|
|
work; add sections here as other parts of the repo get their own conventions.
|
|
|
|
## otel-ui: design system
|
|
|
|
### The rule
|
|
|
|
**One theme, light and dark - that's it.** `otel-ui/src/index.css` defines a
|
|
single set of `--ds-*` tokens with a dark variant (default) and a light
|
|
variant (`:root[data-theme="light"]`). Never introduce a second palette, a
|
|
third theme variant, or a new token namespace alongside `ds-*`. If a screen
|
|
needs a color, it comes from this table or it doesn't exist yet - extend the
|
|
table, don't invent a one-off.
|
|
|
|
The theme toggle lives in `TopBar.tsx`, persists to `localStorage`
|
|
(`otel.ui.theme`), and is applied before first paint by an inline script in
|
|
`index.html` so there's no flash of the wrong theme.
|
|
|
|
The old MD3-style `--color-surface-container-*` / `--color-on-surface` tokens,
|
|
the Material Symbols icon font and the Geist/Inter/JetBrains fonts have been
|
|
**deleted**. Never reintroduce them, raw Tailwind palette colors
|
|
(`slate-*`, `sky-*`, `rose-*`, `white/10`...), gradients, or shadows. Use
|
|
`lucide-react` for icons.
|
|
|
|
Documented exceptions (data-viz only, never chrome): per-service span colors
|
|
in the trace waterfall (`getSpanColor` in `otel-utils.ts`) and the muted
|
|
chart-series hues for metric lines/pods (`POD_COLORS`).
|
|
|
|
### Token reference
|
|
|
|
Source of truth is `otel-ui/src/index.css` (`:root` and `:root[data-theme="light"]`).
|
|
Tailwind utilities are generated as `bg-ds-*`, `text-ds-*`, `border-ds-*`.
|
|
|
|
| Token | Dark | Light | Used for |
|
|
|---|---|---|---|
|
|
| `ds-bg` | `#0D1014` | `#F4F2ED` | page ground |
|
|
| `ds-chrome` | `#0B0E12` | `#FFFFFF` | icon rail, top bar |
|
|
| `ds-surface` | `#14181E` | `#FFFFFF` | cards, panels |
|
|
| `ds-surface-2` | `#191E26` | `#F7F5F0` | inputs, chips, selected row |
|
|
| `ds-fill` | `#1D232B` | `#EDEAE3` | hairlines inside cards, chart bands, bar tracks |
|
|
| `ds-line` | `#242B34` | `#E3E0D8` | card/control borders |
|
|
| `ds-line-strong` | `#2A313B` | `#D6D2C8` | chip borders, neutral badges |
|
|
| `ds-text` | `#E8ECF2` | `#161A1F` | primary text |
|
|
| `ds-text-2` | `#98A3B2` | `#4F5964` | secondary text |
|
|
| `ds-text-3` | `#8B96A6` | `#6B7480` | labels, metadata, muted text - this is the accessibility floor, don't go dimmer |
|
|
| `ds-critical` / `ds-critical-bg` | `#FF6F5E` / `#2B1512` | `#C4402C` / `#FBE9E5` | breached threshold, open errors |
|
|
| `ds-warn` / `ds-warn-bg` | `#F0B24A` / `#2A2011` | `#8A5D0E` / `#F9EFDA` | degraded but not (necessarily) user-visible |
|
|
| `ds-ok` | `#4FCF9A` | `#1F7A56` | healthy, resolved, recovered |
|
|
| `ds-accent` / `ds-accent-bg` | `#7B8CFF` / `#161B33` | `#3B4FC4` / `#E9ECFB` | the one interactive accent - primary buttons, selected segment, links, focus |
|
|
| `ds-on-accent` | `ds-chrome` value | `#FFFFFF` | text on top of `ds-accent` |
|
|
|
|
**Colour is a signal, not decoration.** Chrome, borders and type are neutral.
|
|
`ds-critical` / `ds-warn` / `ds-ok` only appear where a real measurement is
|
|
out of its expected band or a real state changed (open vs resolved). A
|
|
severity pill's color must correspond to an actual state, never picked for
|
|
visual variety. Don't tint cards, headers or buttons "for interest."
|
|
|
|
### Fonts
|
|
|
|
Three families (`otel-ui/index.html` loads them; `index.css` exposes them as
|
|
`--font-display` / `--font-ui` / `--font-data`, used via Tailwind's
|
|
`font-[family-name:var(--font-display)]` arbitrary syntax):
|
|
|
|
- **Bricolage Grotesque** (`--font-display`) - page/card headings, big metric numbers.
|
|
- **Instrument Sans** (`--font-ui`) - all other UI text and body copy. Also the
|
|
new global default (`index.css`'s `@theme` overrides `--font-body`), so
|
|
unmigrated screens already inherit this typeface even before their colors move.
|
|
- **IBM Plex Mono** (`--font-data`) - anything a person would copy or grep:
|
|
ids, `key=value` attribute pairs, timestamps, durations, service names.
|
|
|
|
Never Inter, Roboto, Arial, or a new font stack.
|
|
|
|
### Layout shell (already built - reuse, don't rebuild)
|
|
|
|
- `otel-ui/src/components/layout/IconRail.tsx` - 72px fixed icon rail, global nav, one `aria-label` per icon.
|
|
- `otel-ui/src/components/layout/TopBar.tsx` - 58px fixed top bar: search, time range, theme toggle, auth.
|
|
- `otel-ui/src/components/FilterBar.tsx` - generic filter bar (optional scope segmented control, removable chips, dashed add-filter, an always-visible match readout, clear-all). Reuse this on every screen that lists things instead of building a bespoke filter row.
|
|
- `App.tsx` wires the shell globally and renders the single page-level `<main>`. Screens render inside it - see pitfall #2 below.
|
|
|
|
### Screen pattern
|
|
|
|
Established by `IssuesVisualizer.tsx` (the triage overview) and
|
|
`RumSessionsVisualizer.tsx` (real-user sessions) - follow this shape for the
|
|
remaining screens:
|
|
|
|
- `<FilterBar />` at the top of the screen's content.
|
|
- A main content column (`flex-1`) plus an optional fixed-width aside
|
|
(~340-420px) for supporting stats/panels.
|
|
- Cards: `rounded-[14px] border border-ds-line bg-ds-surface`, **no shadows,
|
|
no gradients** - depth comes from the border and ground color alone.
|
|
- No per-service rainbow badges. `getServicePalette` in `otel-utils.ts` (the
|
|
gradient hash-to-color picker) is legacy: per the original design spec, a
|
|
muted per-service palette is reserved for trace-waterfall span coloring
|
|
only, never for general UI chrome like service badges. Use a neutral
|
|
`border-ds-line-strong text-ds-text-2` badge instead.
|
|
- Real `<button>` / `<a href>` / `<input>` + `<label>` only. Never `onClick`
|
|
on a `div` or `span[role=button]`. `aria-label` on every icon-only control;
|
|
`aria-hidden="true"` on decorative SVG icons (use `lucide-react`, not the
|
|
Material Symbols icon font - see pitfall below).
|
|
|
|
### Pitfalls hit migrating Issues and RUM sessions - avoid repeating these
|
|
|
|
1. **No nested `<button>` elements.** Browsers implicitly close the outer
|
|
`<button>` the moment they hit an inner one, which silently breaks click
|
|
handling on whatever's after it in the DOM (this shipped once, undetected
|
|
until manual testing). If a row/card needs both "click to expand" and
|
|
inner action buttons, make the expandable area its own `<button>` and put
|
|
the action buttons as **siblings**, not children.
|
|
2. **No nested `<main>` landmarks.** `App.tsx` already renders one `<main>`;
|
|
a screen component must not render its own - use a `<div>`.
|
|
3. **`whitespace-nowrap` + `shrink-0` on fixed-height pills/badges.** Without
|
|
them, a longer label silently wraps onto two lines and blows out the
|
|
pill's height inside its flex row - easy to miss until you test with real
|
|
(longer) data or a narrower viewport.
|
|
4. **Never fabricate data.** Before adding a metric, column, or button, check
|
|
whether the field actually exists in the real data model - the backend API
|
|
response, or for RUM specifically, what `static/otel-rum.js` actually
|
|
sends (see the header comment in `otel-ui/src/lib/rum-utils.ts` for a
|
|
worked example of what's real vs. invented). If a design reference shows
|
|
something with no backing data - the RUM mockup invented rage clicks, geo
|
|
location, a "frustration score", and session replay, none of which this
|
|
pipeline captures - drop it or replace it with the closest real,
|
|
honestly-labeled equivalent. Never wire up a control that looks
|
|
functional but isn't backed by anything.
|
|
5. **Screenshots of a hidden Browser preview pane are unreliable** for
|
|
`position: fixed` elements (stale compositor frame, even after a full
|
|
reload). If a screenshot shows something that contradicts
|
|
`getComputedStyle`, trust the computed style, front the tab, and re-check
|
|
before concluding there's a real bug.
|
|
|
|
## Migration status
|
|
|
|
Whole UI is on `ds-*` (one theme, light + dark). The nav strip in `Tabs.tsx`
|
|
is gone - `IconRail` is the only navigation; `Tabs.tsx` just renders the
|
|
active screen. `TelemetryFilters` is built on `FilterBar`. The Material
|
|
Symbols icons were replaced with lucide.
|
|
|
|
Lessons from getting here:
|
|
|
|
- **Merges can silently revert a redesign.** `67ce419` reverted
|
|
`IssuesVisualizer.tsx` to the old styling (a parallel branch had edited the
|
|
same file). After any merge touching `otel-ui/src`, run the greps below -
|
|
they must return nothing.
|
|
- **A stale `otel-ui/dist` or un-redeployed image hides changes.** The Go
|
|
server serves `otel-ui/dist`, else `./dist`, else the legacy root
|
|
`index.html`. Rebuild (`npm --prefix otel-ui run build`) before concluding a
|
|
screen isn't wired.
|
|
- Still to polish (functional, but not yet re-laid-out to the screen
|
|
pattern): `TraceVisualization`, `MetricsVisualizer`, `StructuredLogTerminal`,
|
|
`SearchAndWatcher`, `TrendsVisualizer`, `SetupPanel`. They use `ds-*`
|
|
colors via a mechanical token swap but keep their old layout/type scale, use
|
|
`onClick` on non-buttons in places, and `CommandAndQueryBar` /
|
|
`WaveformHistogram` still sit above every tab except Issues and RUM sessions (`isTriageOverview` in `App.tsx`). Restructure
|
|
them onto `FilterBar` + the card/aside pattern when touching them.
|
|
|
|
### Finding old-pattern usages
|
|
|
|
```sh
|
|
grep -rlE "surface-container|text-on-surface|primary-container|bg-error-container|material-symbols-outlined" otel-ui/src
|
|
grep -rnE "(bg|text|border|ring|from|to|via)-(slate|zinc|gray|sky|cyan|blue|violet|indigo|purple|emerald|teal|rose|red|amber|orange)-[0-9]|text-white|border-white|bg-(white|black)/" otel-ui/src
|
|
```
|
|
|
|
Both must print nothing.
|
|
|
|
### Working order from here
|
|
|
|
Initial pass is done. Take **one view at a time** to the full screen pattern
|
|
(FilterBar + card/aside layout, real buttons, fold or restyle the shared
|
|
chrome for that screen), verify it in the browser in both themes, commit, then
|
|
move to the next. Suggested order: Traces, Logs, Metrics, Search, Trends, Setup.
|
|
|
|
## Local / agent development without login blocking
|
|
|
|
Uses the go-auth device grant (RFC 8628); reference: `https://users.k6n.net/dev-auth.md`.
|
|
Config is CRD-driven (`https://users.k6n.net/crds.md`), see `k8s/auth-dev.yaml`:
|
|
an `AuthClient` `dev-console` with `spec.audience: [https://otel.k6n.net]`
|
|
(audience entries must be absolute URLs - `otel-debug` was rejected and briefly
|
|
made the AuthServer `READY False`), plus `AuthServer.spec.deviceFlow`. The otel
|
|
server accepts that audience via `OTEL_OIDC_AUDIENCE` (`k8s/deployment.yaml`).
|
|
|
|
- **Browser (`npm run dev`)**: `src/dev/device-auth.ts` is imported only under
|
|
`import.meta.env.DEV`. With no session it shows a "Dev sign-in" link + code,
|
|
polls, then reloads. `/dev-auth/*` is proxied by `vite.config.ts` to the
|
|
issuer's `/api/auth/*` (override the issuer with `AUTH_DEV_BASE`).
|
|
- **Terminal / agents**: `node scripts/dev-auth.mjs login|token|whoami|forget`.
|
|
Refresh token lives in `~/.go-auth/dev/` (0700/0600). Use
|
|
`curl -H "Authorization: Bearer $(node scripts/dev-auth.mjs token)" ...`.
|
|
`GO_AUTH_TOKEN` is an explicit-token escape hatch; never store it.
|
|
- Never commit tokens, never persist them in a production build, never log them.
|
|
- Apply with `kubectl apply --dry-run=server --server-side --field-manager=otel-dev-auth -f k8s/auth-dev.yaml`
|
|
first; check `kubectl get authserver -n ecommerce` stays `READY True`.
|