You are in versable-builder: a private pnpm monorepo housing the Versable
UI kit (@versable-git/ui), a frontend toolkit, the cross-app design canon,
and a playground app that is both the component gallery and the docs browser
(live at https://versable-builder-playground.vercel.app). Two sibling repos
consume this work: ../walmart-mvp and ../speedway; each has its own
CLAUDE.md, and neither is upgraded until customer-facing work arrives (owner
ruling 2026-08-17). This file is the map; explore from here. A person reading
this repo starts at README.md instead; this file is written for agents.
Ground rules (non-negotiable)#
- Run everything through fnm:
fnm exec --using 24 -- pnpm <cmd>from the repo root. Plainpnpm/noderesolves Homebrew Node 26 and breaks. - Never kill or restart the dev server on :5104 (pm2 process
playground-dev). It hot-reloads. Never runpnpm buildinapps/playgroundwhile it runs (build clobbers.nextand kills it). If it is down, restart with:pm2 restart playground-dev(or, without pm2,cd apps/playground && pnpm dev -p 5104). - Semantic tokens only.
pnpm lint:tokens(eslint) FAILS raw palette classes and bracket-arbitrary colors. Colors come from daisy semantic classes, the status vars (--ok-soft/--ok-lineetc.), shadows from--shadow-*, and runtime data colors ONLY viaaccentStyle(color)+accent-*classes (packages/ui/src/internal/accent.ts). - Don't fight daisyUI (owner rule). Extend it via theme vars and small
CSS in
packages/ui/theme/*.css; never rebuild a control it provides. Custom daisy themes REQUIRE the scale vars (--border,--size-selector,--size-field); they are set, do not remove them. - Verify by running, not by reading. A change is done when the page
renders and the interaction works:
pnpm -r typecheck+pnpm lint:tokenspnpm -r test+ curl the route + drive one interaction (headless browser or fiber-snatcherfsfromapps/playground/:fs navigate <path>,fs click "<text>",fs shoot). Screenshot claims require having looked at the screenshot.
- Commit per logical unit with descriptive messages. While parallel
agents write, use PATH-SCOPED
git add, nevergit add -A. - Numbers in demo data are fictional; anything lifted from speedway reality gets scaled ×π (owner rule).
- Presets are shorthands, never gates (owner ruling 2026-08-14). A
component preset bundles values its plain props can already express, and
explicit props always win over it. A feature reachable ONLY through a
preset is a design defect: build the prop first, then the preset over it.
The pattern lives in
Button(PresetProps+mergePresets) andChip. A bundled field binds only where the component reads it (= preset.x, or a merge path like className's); TypeScript flags a misspelled bundle key but never an unwired one, so wire both ends when adding a preset field.
Repo layout#
| Path | What |
|---|---|
packages/ui/src/ | The kit. One dir per component; index.ts is the barrel = the inventory. internal/ holds shared machinery (ClassNameMap, render-node, markdown-lite, accent, variants). |
packages/ui/theme/ | The styling system: index.css (tokens, two daisy themes, REQUIRED scale vars), components.css (button rules incl. "shade" hovers), fields.css (input surfaces + field dials), accent.css. |
packages/ui/docs/ | The contract doc per component: what it takes, sanctioned and banned combinations, "Before you adopt this", travels with, snippet. Read the one for any component you touch. |
packages/toolkit/ | @versable-git/toolkit 0.1.0, published, framework-free utilities with tests; no app consumes it yet. PROPOSALS.md is where a cross-app helper is filed. |
packages/qsync/ | @versable-git/qsync 0.1.0, published; URL-state sync the playground consumes. |
.github/workflows/ | ci.yml (typecheck, lint:tokens, test, build, the citation ratchet, the do-nots diff), publish-kit.yml (publishes any of the three packages whose version the registry lacks), docs-publish.yml (mkdocs to tech.versable.ai). |
docs/design-language/ | The visual law: 13 docs, each with principles (A), presets (B) and worked cases cited to file:line (C). Start at its README.md. |
docs/app-patterns/ | How a screen is built and written up: data loading, the surface-doc template, the recipes (10-recipe-browsable-list.md, 11-recipe-record-detail.md), starting a new app, validating a UI change. |
docs/DO-NOTS.md | Every Banned rule in one generated page. Check it before you ship; regenerate with node scripts/build-do-nots.mjs after editing a contract doc. |
contract/DO-NOTS.md | The OTHER one, and a different subject. This repo now holds two generated DO-NOTS pages: docs/ is the kit and the screens, contract/ is what a module must do to be one. Different generators, different sources, neither reads the other. Regenerate the contract one with python3 contract/scripts/build-do-nots.py. |
docs/GUIDEBOOK.md | How the layers relate, the pin and upgrade rules, and the retrospective of lapses in the apps. |
docs/SCRIPTURE.md | Why the kit is shaped the way it is: intent, goals, non-goals, self-graded failure modes, success criteria. |
docs/breakdowns/ | Six real screens taken apart layer by layer. |
apps/playground/ | Next app. /components/* gallery (one page per component), /patterns/*, /docs/* the docs browser, /demo/* demo screens including /demo/admin. src/app/showcase-registry.ts is the one route registry; src/lib/docs-registry.ts is the docs allowlist. |
docs/plan/ | Planning docs and the board (TRACKING.md). Owner rulings live in the numbered docs; older plans are under archive/. |
docs/reference/ | The original design mocks. History, not law: the law is docs/design-language/. |
docs/adr/ | Architecture decision records. |
.claude/output/ | Generated studies and reports, gitignored. Never a source of truth; anything the repo must remember goes into a tracked doc. |
Docs: read in this order for any kit or screen work (the governing copy; other docs carry scoped cuts and point here)#
packages/ui/src/index.ts, the barrel. Every runtime export is named there; if a name is not on it, it does not exist.docs/design-language/README.md, then the doc for the trait you are touching (buttons and actions, status language, overlays, typography, and so on).- The component's contract doc in
packages/ui/docs/<component>.md, its Banned combinations and "Before you adopt this" first. - The app-patterns recipe for the screen shape (
10-recipe-browsable-list.mdfor lists,11-recipe-record-detail.mdfor records,05-starting-a-new-app.mdfor a new app). docs/DO-NOTS.mdbefore you call anything done.
For orientation rather than a task: docs/GUIDEBOOK.md (how the layers relate)
and docs/SCRIPTURE.md (why). Section 4 of the guidebook carries the pin and
upgrade rules; read it against docs/plan/74-guidebook-conformance-audit.md,
which records that its pin numbers are stale and that three apps consume the
kit, not two.
Component conventions (match exactly when adding/emitting kit code)#
- Props:
classNamestyles the component's main element;_classNamethe outer wrapper; extra slots via<slot>ClassName(ClassNameMap). - Callbacks are value-first:
onChange(value, item). - Variants/colors/sizes via
Recordlookups of STATIC class strings. - Icons via the registry:
Icon="Search"(keys inicon/icon-for.ts; add new ones there from react-icons, never inline SVGs). - Every component:
skeletonprop; most:loading,tooltip,noAnimate. - DataTable feature config:
features={{ sort:{}, search:{fields}, filters:{registry}, selection:{}, pagination:{pageSize} }}; row menus{label, icon, onSelect, destructive?}.
When this file and CLAUDE.md disagree#
CLAUDE.md governs: it carries the owner's dated rulings and the bar for done,
and is edited first. This file is the layout and the ground rules, and is
brought into line with it.
When stuck / escalation#
Leave a dated line under "Open questions for the owner" in
docs/plan/TRACKING.md describing what blocked you, commit what is green,
never commit red typecheck or lint. Do not invent exceptions to the ground
rules; stop and flag instead.