# Agent instructions - Sporting Scouter design system

Use this file when generating or refactoring UI for Sporting Scouter products.

## Canonical sources

| Path | Purpose |
|------|---------|
| `packages/config/tailwind/theme.css` (or repo `theme.css`) | Single source of truth for tokens |
| `ui/` (`@sportingscouter/ui`) | Class recipes - `tv()` only, no React in this package |
| `site/` | Published reference at https://design-system.sportingscouter.com |
| `showcase/*.html` | Primitive variant cards |

**Not canonical for new work:** `styles.css`, `_ds_bundle.*`, root `README.md` marketing vocabulary - legacy site brand layer.

## Published machine-readable index

After `site` build:

- `/catalog.json` - pages, areas, titles, descriptions
- `/llms.txt` - short rules + area links
- `/changelog.json` - contents of root `CHANGELOG.md`

## Rules

1. **Semantic tokens only** - no arbitrary Tailwind values, no layer-1 palette utilities.
2. **Blue navigates, black commits** - `--color-primary` vs `--color-cta`.
3. **Forms** - `formLayout()` + `field()` / `fieldParts`; every control has a visible `<label>`.
4. **Authenticated apps** - App Shell with side nav (`appShell()`); do not invent a new dashboard grid.
5. **Composites** - Radix/shadcn behaviour in apps; this repo ships visual classes only for Dialog, Select, Tabs, Toast, etc.
6. **Empty states** - no illustrations; text + CTA (`emptyState()` or `asyncState()` panels).
7. **Density** - `[data-density]` on a surface, not per-component invented scales.
8. **Product shell** - an app that renders the organiser rail imports `@sportingscouter/ui/shell-product.css` after the theme. Every rule is scoped by `appshell--product`, which `appShell()` adds in `sideNav` mode; use the `sideNav()` and `appShell()` slots, not hand-written class names.

## Implementing product UI

When building or changing a screen in a Sporting Scouter **consumer app** (www, business, feedback):

1. Name the surface (review list, form, dialog, empty list, authenticated page).
2. Open that surface in the catalog ([catalog.json](https://design-system.sportingscouter.com/catalog.json) or the matching docs page). Copy its structure snippet - the block that imports `reviewCard()`, `field()`, `appShell()`, and the rest.
3. Fill those slots with data. Keep the recipe's layout, names, and variants.
4. If the catalog has no page for that surface, compose only exports from `@sportingscouter/ui` (`appShell`, `formLayout`, `field`, `button`, `emptyState`, `asyncState`, `stack`) and semantic tokens from `theme.css`.
5. If the snippet has no variant you need, stop. Follow **Designing a new layout** below in this repo. Do not invent the layout in the app.

Before you finish:

- Classes come from recipe functions.
- No arbitrary values and no palette utilities (`p-[13px]`, `bg-[#…]`, `bg-slate-500`).
- Authenticated pages use `appShell()` with side nav.
- Forms use `formLayout()` + `field()`, with a visible `<label>` on every control.
- Read-only stars use `starsFor()` (whole stars). Score bar fill comes from `ratingOf()`.
- Empty and loading states use `emptyState()` or `asyncState()`.
- Demo HTML classes on the reference site (`rv`, `b`, `pg`) stay on the site. Apps call the recipes.

## Designing a new layout

When a UX or product agent needs a screen or pattern that is not in the catalog yet, work **in this design-system repo** - not in a consumer monorepo.

1. **Confirm it is missing.** Search [catalog.json](https://design-system.sportingscouter.com/catalog.json) and the Templates area. If an existing recipe, template, or variant can cover the need, extend that (new variant or slot). Do not add a second dashboard chrome or duplicate a documented surface.
2. **Pick the page type** ([Template authoring](/agents-templates/)):
   - Authenticated product UI (dashboard, queue, settings): `layout: 'app'` in `nav.mjs`. Frame with `appShell()` + `sideNav()`. Do not invent a new dashboard grid (rule 4 above).
   - Public or marketing UI (landing, login): `layout: 'page'`. Use existing templates (e.g. Login, Organiser landing) as references.
3. **Compose with layout primitives first** - `stack`, `layoutPanel`, `section`, `container`, `grid`, `formLayout`, `card`, `emptyState`, `asyncState`. Only add a new `tv()` recipe when the same structure will repeat and cannot be expressed as a variant of an existing export.
4. **Add a recipe when required** - follow `ui/COMPOSITION.md`:
   - One `tv()` module under `ui/src/` with slots, variants, and explicit `defaultVariants`.
   - Semantic tokens only; missing values go in `theme.css`, never as arbitrary Tailwind.
   - Dialog, Select, Tabs, Toast: visual classes on Radix/shadcn parts only - no forked behaviour.
   - Do not contradict closed entries in `ui/GAPS.md` without designer sign-off.
5. **Publish so implementers can copy a snippet:**
   - Entry in `site/scripts/nav.mjs` (slug, title, description; templates use `group: 'templates'`).
   - Body fragment `site/src/pages/<slug>.html` with a **structure snippet** (imports + markup pattern), like the review card page.
   - `npm run build` in `site/` - updates `catalog.json` and `llms.txt`.
   - Do not copy `docs.css` into templates or product apps; it is reference chrome only.
6. **Release:** new recipes, variants, or reference pages are a **minor** semver bump (`CHANGELOG.md`, `ui/package.json`, `templates/ui-react/package.json`, `SITE.version` in `nav.mjs`). Consumer apps pick it up via `ds:sync --ref ui-vX.Y.Z` after the tag is pushed.

## Adding documentation

1. Edit `site/scripts/nav.mjs` (slug, group, area, title, desc).
2. Add `site/src/pages/<slug>.html` fragment.
3. Run `npm run build` in `site/` - updates `catalog.json` and sitemap.

## Gap tracking

Open design questions and implementation status: `ui/GAPS.md`. Do not contradict closed entries without designer sign-off.

## Versioning

Release semver is shared by **`@sportingscouter/ui`** and **`@sportingscouter/ui-react`** (`templates/ui-react/`). Keep these in sync on every release:

| Source | Field | Example |
|--------|--------|---------|
| Root `CHANGELOG.md` | Top `## [x.y.z]` | `3.2.0` |
| `ui/package.json` | `version` | `3.2.0` |
| `templates/ui-react/package.json` | `version` | `3.2.0` |
| `site/scripts/nav.mjs` | `SITE.version` | `v3.2` (display shorthand) |

Product apps do not install from npm. Consumer monorepos copy this repo via their own sync script (often `npm run ds:sync -- --ref <ref>`). Pin releases with git tag **`ui-vX.Y.Z`** (e.g. `ui-v3.2.0`) on the commit that contains the bumped versions and changelog. **Cursor prompt** for updating consumers: `/agents-consumer-update/` (after a site build).

**Semver:** patch - class-string or docs fixes; minor - new recipes, optional variants/slots, reference pages; major - removed/renamed exports, breaking default variants, token renames that break recipes. Note in `CHANGELOG.md` when `theme.css` / `tokens.css` changes require a monorepo sync.

**PRD “v3.1”** in older doc headers is the token-vocabulary era, not the npm package version.

### Release checklist

1. Implement changes in `ui/`, `theme.css`, `site/` as needed.
2. Add or update the top section in `CHANGELOG.md`.
3. Set the same `x.y.z` in `ui/package.json` and `templates/ui-react/package.json`.
4. Set `SITE.version` in `site/scripts/nav.mjs` to `vX.Y`.
5. Run `npm test` in `ui/`, `node --test scripts/release-tag.test.mjs` at repo root, and `npm run build` in `site/`.
6. Merge to **`main`**. GitHub Actions runs **`Release tag`**: reads the top `CHANGELOG.md` version, verifies `ui/` and `ui-react` package.json (and `SITE.version` in `nav.mjs`), then creates and pushes **`ui-vX.Y.Z`** if that tag does not exist yet. Manual fallback: `node scripts/release-tag.mjs --push`.
7. In the consumer monorepo: run sync with `--ref ui-vX.Y.Z`, then commit `chore(ds): sync design system to ui-vX.Y.Z` and run builds/tests. Use the [Update consumer apps](/agents-consumer-update/) prompt in Cursor if you want an agent-guided pass.

