Files
go-otel/AGENTS.md
T
2026-09-19 09:58:37 +02:00

11 KiB

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 repo still has a second, older color system - the MD3-style --color-surface-container-* / --color-on-surface / --color-primary-container tokens, also in index.css - left over from before this redesign. That system is dead, not a design choice: it exists only because not every screen has been migrated yet (see the checklist below). Never add new usages of it. Once the checklist is empty, delete that whole legacy @theme block plus the old Geist/Inter/JetBrains/Material-Symbols font <link> entries in index.html, so exactly one design system remains in the codebase.

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 checklist

Converted to ds-* already: App.tsx (shell), IconRail.tsx, TopBar.tsx, FilterBar.tsx, IssuesVisualizer.tsx (Issues / triage overview, tab0), RumSessionsVisualizer.tsx (RUM sessions, tab7).

Everything below still renders with the legacy MD3 dark-only palette (text-on-surface, bg-surface-container-*, bg-primary-container, gradient badges, the Material Symbols icon font) and needs the same treatment. Sizes are current line counts, biggest first:

File Lines Tab / role Notes
TraceVisualization.tsx 1866 Traces & Spans (tab1) Waterfall + span detail; closest existing screen to the design spec's trace-waterfall.html reference
MetricsVisualizer.tsx 1107 Metrics & APM (tab2) Imports GaugeMeter.tsx
StructuredLogTerminal.tsx 582 Live Log Stream (tab3) Uses Material Symbols icons
Tabs.tsx (shell) 513 horizontal tab strip rendered above every screen Gradient active-tab underline, slate colors; decide whether this strip is still needed now that IconRail is the primary nav, or whether it should just lose the gradient/slate styling
SearchAndWatcher.tsx 504 Archive & Trap (tab6)
ServiceGraph.tsx 495 used by TraceVisualization.tsx migrate alongside its caller
WaveformHistogram.tsx 385 shown above every non-Issues/RUM tab's content Uses Material Symbols icons; see the shared-chrome note below
GaugeMeter.tsx 372 used by MetricsVisualizer.tsx migrate alongside its caller
TrendsVisualizer.tsx 323 Historical Trends (tab5, only when ClickHouse stats are configured)
CommandAndQueryBar.tsx 251 shown above every non-Issues/RUM tab's content see the shared-chrome note below
TelemetryFilters.tsx 124 conditionally shown above Tabs.tsx content service multiselect + attribute exclusions; candidate to rebuild on top of FilterBar.tsx instead of restyling in place
SetupPanel.tsx 112 Setup & Agent API (tab4)
PatternsVisualizer.tsx 76 Similarity Patterns (tab8)
ViewErrorBoundary.tsx 62 error fallback UI, wraps every tab's content small, easy pass
RolloutsVisualizer.tsx 48 Cluster Rollouts (tab9)
AuthCallbackView (inside App.tsx) ~40 OIDC callback screen still 100% old tokens even though the rest of App.tsx is migrated

Shared chrome note: CommandAndQueryBar.tsx (anomaly banner, filter status strip, NDJSON export) and WaveformHistogram.tsx (volume/error waveform with click-to-focus) are currently skipped entirely on the Issues tab (isTriageOverview branch in App.tsx) because their old styling would clash with the new FilterBar. When migrating the remaining tabs, decide per screen whether that real functionality gets folded into the screen's own FilterBar/content area (as happened for Issues and RUM sessions), or stays as shared chrome above the remaining tabs, restyled to ds-* tokens.

Finding old-pattern usages

grep -rlE "surface-container|text-on-surface|primary-container|bg-error-container|material-symbols-outlined" otel-ui/src
grep -rlE "from-[a-z]+-[0-9]+ via-[a-z]+-[0-9]+|getServicePalette" otel-ui/src

A file with zero hits and zero ds-* usage either has no meaningful styling of its own (a pure hook/context) or hasn't been looked at yet - check the checklist above.