Component-driven workflow

Component-driven workflow

React Cosmos 7.4.1 is the component sandbox for packages/ui primitives.
Decision rationale:
.agents/notes/2026-09-10-component-driven-research.md.
Plan: .agents/plans/2026-09-10-component-driven-cosmos.md.

The rule

A primitive is developed in Cosmos first, then composed into a screen.
The fixture is the contract; the screen is the consumer. Verified
means
: the primitive author has seen and exercised the base state AND
every other state (variants, sizes, error/disabled/loading) in Cosmos
BEFORE screen code lands. A PR that adds a primitive + screen usage in
the same commit but no fixture exercise is a defect — bounce it.

The CVA config is the variant source of truth. Map over its keys in the
fixture; do not copy-paste variant="…" strings. Kitchen-sink research
established that a stale matrix is worse than no matrix; the Cosmos
feedback loop catches the gap on the next run.

Primitive-author vs screen-author

  • Primitive-author (packages/ui/AGENTS.md): write the primitive,
    update or create the fixture, run Cosmos, exercise base + each variant
    • dark mode. Only then commit.
  • Screen-author (apps/web/AGENTS.md): import from @brioso/ui/ui/<name>.
    If the primitive does not exist (or needs a new variant/size), STOP
    and switch to the primitive-author side.

Cosmos setup

  • cd apps/web && bunx cosmos — Vite dev server on localhost:5000
    (or 5001 if taken). The navigation tree lists every fixture under
    src/cosmos/; the controls panel exposes props so you exercise the
    matrix without editing the fixture.
  • New fixture: apps/web/src/cosmos/<Name>.fixture.tsx, Multi-Fixtures
    format (export default { Primary: <…/>, ... }). Import the primitive
    from @brioso/ui/ui/<name> (the package exports subpath) so the sandbox
    exercises the built primitive, not a local copy.
  • Directory-tree decorator (apps/web/src/cosmos/cosmos.decorator.tsx)
    wraps every fixture in the providers it needs (TooltipProvider, theme
    via options.theme). For per-fixture locale, export
    options: { locale, messages } from the fixture (Cosmos Fixture
    Options, PR #1649).

/cosmos/<fixture> route

Dev-only, gated three ways:

  1. sensitive-route-guard (/cosmos is in SENSITIVE_PREFIXES in
    packages/auth-core/src/route-guard.ts) — request-time gate, requires
    WORKER_SHARED_SECRET Bearer.
  2. process.env.NODE_ENV === "production" check in the route’s server
    GET handler (mirrors apps/web/src/routes/dev/login.ts:104).
  3. import.meta.env.DEV check at component render time (tree-shakes the
    body out of production bundles).

The route is in iframe mode — bunx cosmos must be running alongside
vite dev. Excluded from apps/web/scripts/sync-contract.ts (no
public OpenAPI leak).

Adding a primitive

  1. packages/ui/src/ui/<name>.tsx — CVA convention per button.tsx.
  2. package.json#exports — add "./ui/<name>": "./src/ui/<name>.tsx".
  3. apps/web/src/cosmos/<Name>.fixture.tsx — Multi-Fixtures, matrix
    derived from CVA config.
  4. cd apps/web && bunx cosmos — exercise every variant + size + dark
    mode.
  5. Atomic commit per root AGENTS.md rule (one logical change).