246 lines
14 KiB
Markdown
246 lines
14 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
|
|
|
|
### Quick start (agents: do this first)
|
|
|
|
```sh
|
|
node scripts/dev-auth.mjs get /api/issues # signs in if needed, then GETs
|
|
```
|
|
|
|
If not signed in it prints `Approve in a browser: https://users.k6n.net/device?user_code=XXXX-XXXX`.
|
|
**Stop and give the human that link**, then wait - the script polls (~10 min
|
|
window) and continues on its own. Refresh token is stored in `~/.go-auth/dev/`
|
|
(0700/0600), so this happens once, not per command. Other commands:
|
|
`login | token | whoami | forget`. For raw curl:
|
|
`curl -H "Authorization: Bearer $(node scripts/dev-auth.mjs token)" https://otel.k6n.net/api/...`.
|
|
Env: `OTEL_URL`, `AUTH_DEV_BASE`, `AUTH_DEVICE_CLIENT_ID` (default `dev-console`),
|
|
`GO_AUTH_TOKEN` (explicit-token escape hatch, never store).
|
|
|
|
Browser (`npm run dev`): with no session a "Dev sign-in" banner shows the same
|
|
link + code and reloads once approved. Code is `src/dev/device-auth.ts`, loaded
|
|
only under `import.meta.env.DEV`; `/dev-auth/*` is proxied by `vite.config.ts`
|
|
to the issuer's `/api/auth/*`.
|
|
|
|
### Gotchas (each cost a failed attempt)
|
|
|
|
- **Request encoding:** `/api/auth/device` and `/api/auth/device/token` take
|
|
**form-encoded** bodies (RFC 8628); only `/api/auth/device/refresh` takes JSON
|
|
(`{"refresh_token": ...}`). JSON to the first two gives a fast `invalid_grant`
|
|
that looks like a denied approval but is a malformed request.
|
|
- **401 `token not issued for this client`:** the otel server rejects the
|
|
token's `aud`. It must contain `otel-debug` or `OTEL_OIDC_AUDIENCE`
|
|
(`https://otel.k6n.net`, set in `k8s/deployment.yaml`). If it 401s, the
|
|
deployed server predates that setting - redeploy with `build.sh`.
|
|
- **Never probe the issuer or auth CRDs with ad-hoc curl/kubectl** unless the
|
|
user has said to; the harness blocks it. The script above is the sanctioned path.
|
|
|
|
### CRD setup (already applied; reference)
|
|
|
|
Reference docs: `https://users.k6n.net/dev-auth.md`, `https://users.k6n.net/crds.md`.
|
|
`k8s/auth-dev.yaml` defines an `AuthClient` `dev-console` (`redirectURIs: []`
|
|
is required even for device-only clients; `spec.audience: [https://otel.k6n.net]`)
|
|
plus `AuthServer.spec.deviceFlow`.
|
|
|
|
- `audience` entries **must be absolute URLs**. `otel-debug` was rejected by the
|
|
operator and flipped the AuthServer to `READY False` until fixed.
|
|
`--dry-run=server` does **not** catch this; after applying, always check
|
|
`kubectl get authserver -n ecommerce` (READY True) and
|
|
`kubectl describe authserver auth -n ecommerce` (events).
|
|
- Apply: `kubectl apply --server-side --field-manager=otel-dev-auth -f k8s/auth-dev.yaml`
|
|
(a non-fatal "spec.issuer: Required value" warning for the partial AuthServer
|
|
apply is expected).
|
|
- Never commit tokens, persist them in a production build, or log them.
|
|
|
|
### Drill-down screens: `MasterDetail` + `ResizablePanes`
|
|
|
|
Navigation here drills list -> detail -> extended detail, so use the shared
|
|
pane components instead of laying out columns by hand.
|
|
|
|
- `components/panes/ResizablePanes.tsx` - generic stack of panes with
|
|
draggable dividers (arrow keys when focused, double-click resets, widths
|
|
persisted per `storageKey`). Fixed panes take a `size`; exactly one pane is
|
|
flexible. Below `lg` panes stack and dividers disappear.
|
|
- `components/MasterDetail.tsx` - list | detail [| extended] on top of it.
|
|
The caller owns selection; `renderDetail(item, drill)` gets
|
|
`drill.openExtended(key)` / `closeExtended()` and `renderExtended(item, key)`
|
|
fills the third pane, which resets when the selection changes.
|
|
`layout="stacked"` gives a full-width table with the detail below.
|
|
- Building blocks: `trace/TraceListItem` (narrow rows), `trace/TraceOverviewRow`
|
|
(table rows), `trace/TraceDetail`, `common/*` (Stat, StatHeader, Segmented,
|
|
DetailField, BackButton). The waterfall takes `selectedSpanId`/`onSelectSpan`
|
|
to drive an external inspector pane.
|
|
- Local UI against a local server: `OTEL_URL=http://localhost:8080 npm run dev`.
|