Skip to content

UI work reuses components, ships stories and screenshot baselines

State: open/proposed

  • UI work in the determined suite ports an approved mock before it builds anything new, gives every new or changed component Storybook stories and a screenshot baseline, and shows reviewers before/after and mock/built screenshots.
  • A finished UI change is checked by parallel validation subagents. A large set of independent units runs as a Workflow.
  • The /ui-work skill (solutions/determined/.claude/skills/ui-work/SKILL.md) is the procedure that meets this standard.

A change that adds or alters a UI component or surface meets all eight rules.

  1. Duplication review before building. Search for an existing component or primitive before writing a new one. When an approved mock exists for the surface, porting the mock component’s markup and look comes first; reusing an existing component then applies to its behaviour only (e.g. lazy loading, switching state), never to look. Otherwise reuse it, extend it with a prop or slot, or promote a duplicate to a shared package. The PR body records what was searched and what was decided.
  2. Stories for every new or changed component. Each component has Storybook stories for its empty, loading, error and populated states and its edge cases, in light and dark, and at a narrow width when its layout responds to width. Stories take fixtures, never a live backend.
  3. A screenshot baseline per story. Each story is captured and compared against a committed baseline. A changed baseline is committed in the same PR as the change that moved it.
  4. Before/after screenshots for UI-visible changes. The PR commits before and after images on its branch and embeds them in a PR comment. The committed set and its index follow S-0012-human-reviewable-ui-changes.
  5. Design language. Appearance comes from --d-* tokens, semantics and D* primitives, per S-0010-design-language-consumption. Light and dark render with parity. Platform-specific look values sit behind one token block.
  6. Parallel validation. Once a change is ready, fresh subagents that did not write it review it in parallel: visual review, accessibility and keyboard, story and state coverage, duplication, and — when a mock exists (rule 8) — mock fidelity. They run within the session’s concurrency limit.
  7. Workflows for large sets. Work of more than about six independent units, or fan-out plus verification beyond a handful of agents, runs as a Workflow. Smaller work uses single Agent calls.
  8. Mock fidelity when a mock exists. For a surface with an approved mock, capture mock/built screenshot pairs (same fixture, viewport, theme, density) alongside the before/after set, and run a Mock fidelity validation lens that lists every visible difference with measurements. Every difference is rework unless the spec overrides the mock on purpose, cited by its spec line in the PR. Re-run the lens until it is clean.

The suite composes many experiences from shared packages. A component written twice drifts twice, and the second copy is found only when the two disagree on screen. Stories pin every state a component can show, so a state that renders as nothing is seen before it ships. A baseline turns “does it still look right” into a diff a machine can flag. A reviewer judges a UI change by looking, so the PR carries the pictures.

  • Where to search (rule 1). packages/ts/ui (@determined/ui), packages/ts/app-frame, packages/ts/panes, packages/ts/code-editor, packages/ts/markdown-ui, the shell (solutions/determined/packages/shell), and sibling experiences under solutions/determined/experiences/. docs/LIBRARIES.md lists every shared library. Promotion follows S-0001-co-locate-first-promote-when-shared: keep a first use local, promote on the second.
  • Recording the review (rule 1). One PR section, “Duplication review”: the places searched, the candidates found, and reuse, extend, promote or new for each.
  • Story setup (rule 2). An experience Storybook extends the shared factory in packages/ts/ui/.storybook/factory.ts, as solutions/determined/experiences/fitness/.storybook and solutions/determined/experiences/agent-pants/.storybook do. Stories live in stories/, outside app/, so they never reach a product bundle. The theme global (iframe.html?globals=theme:dark) switches light and dark.
  • Capture (rule 3). The story baseline runner in packages/ts/ui/.storybook/story-shots/ shoots every enrolled story in light and dark, plus a narrow viewport for a story with parameters.screenshot.narrow, and compares each shot with a committed baseline in the package’s .storybook/baselines/. The baselines are Linux PNGs made in the pinned Playwright container, so the runner needs Docker.
    • bun run storybook:check in an enrolled package compares; bun run storybook:baseline rewrites the baselines after an intended change. Build the Storybook first (bun run storybook:build), or use the moon task (moon run exp-polish:storybook-check), which builds and compares.
    • CI runs the check for every enrolled Storybook in the story-shots job.
    • Enrolled: packages/ts/ui and the Polish experience. An experience enrols by adding the two scripts with its story id prefixes and a step pair in the CI job.
    • parameters.screenshot.skip leaves a story out, with the reason as its value. Use it only for a story that cannot render in Storybook, and say so in the PR.
    • For images in docs (an exploration README), capture.ts in the same folder writes PNGs without comparing.
    • Running app surfaces are still captured by the gated tests/ui/<experience>/capture-*.e2e.ts drivers (CAPTURE_SHOTS=1).
  • Before/after (rule 4). Capture “before” from the base branch before the first edit. Commit both sets on the branch, then post a PR comment that embeds each pair with a one-line caption.
  • Validation (rule 6). Validators are read-only and run in parallel with each other, never beside a writer in the same worktree. Each finding goes back to the implementer as rework or into the PR as a known limit.
  • Mock fidelity (rule 8). Save each pair under docs/screenshots/<set>/ as --mock/--built, or compose one image with scripts/compose-screenshot-pair.ts. The lens is a fresh, read-only subagent that compares the pair against the mock: layout, spacing, type, colour, borders, shadows. The PR screenshot comment shows the pairs.
  • Reviewers. A UI PR with no duplication review, a changed component with no story for one of its states, or a visual change with no before/after comment is incomplete.
  • A new StatusPill.vue in one experience when DBadge with an intent already renders the same thing.
  • A component whose only story is the populated state. The empty and error states ship unseen.
  • A story that fetches from a running platform server. It breaks offline and changes as the data changes.
  • A PR that restyles a panel and shows only the after image.
  • Twelve exploration directions run as twelve hand-dispatched agents with no shared verification step.
  • A surface built to spec but compared only against its own prior screenshot, never against the mock it was drawn from.

← Back to Standards