# 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 `
`. 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: - `` 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 `