feat(ui): emit a standalone stylesheet and sync the DS to Claude Design #153

Merged
ullrich merged 1 commit from design-sync-ui into main 2026-09-13 08:57:21 +00:00
Owner

Imports @trails-cool/ui into a Claude Design project so the design agent builds with our real components and brand instead of generic ones.

Getting there surfaced three problems that are latent independent of the sync — each would bite anyone consuming this package outside the two apps.

1. The package shipped no CSS

Components are styled with Tailwind utilities (bg-accent, text-text-inv, h-9 px-3.5), but src/theme.css holds only the @theme token block. The utilities are generated by each app (apps/*/app/styles.css does @import "tailwindcss" + @import "@trails-cool/ui/theme.css" + @source).

So any consumer outside those apps gets tokens with nothing using them and renders unstyled. Adds a build script emitting dist/ui.css from a new src/build.css that mirrors the apps' composition. dist/ stays gitignored — the stylesheet is regenerated, never committed.

2. Scanning only packages/ui/src was not enough

Tailwind emits only classes it sees used, so the stylesheet contained exactly what these 8 components happen to reference. Any composition on top — a preview, or a screen the design agent generates — silently lost its layout classes.

Caught visually: a stat row rendered as a vertical stack instead of a mono row.

Before flex, gap-4, font-mono, max-w-sm, justify-between, mt-3absent
After all present; 16K → 55K

build.css now also scans the two apps (the vocabulary the product actually uses) and safelists the full semantic token surface via @source inline(...), so the documented families all exist rather than just the used subset — the elevation ramp (text-eg-lo/mid/hi) and border-border-md were documented but absent until this.

3. Fonts did not ship

The build emitted @font-face rules whose url() targets were never copied, and dropped 8 as dead. cfg.extraFonts now points at the @fontsource-variable stylesheets so the real .woff2 binaries are copied — Outfit and Geist Mono render for real instead of falling back to system faces.

Also

"@parcel/watcher": false in allowBuilds@tailwindcss/cli pulls it in and pnpm refuses an unlisted build script. The watcher is only used by --watch, which this build never runs, so false preserves the status quo per the existing convention in that block.

What .design-sync/ holds

Sync inputs, so re-syncs are reproducible and mostly deterministic: config.json, the 8 authored preview sources, NOTES.md (repo gotchas + re-sync risks), and conventions.md — the header prepended to the generated README and read by the design agent. Every class, token and component named in conventions.md was verified against the built artifacts, since a header naming things that do not exist is worse than none.

Verification

  • 8 components, all with authored previews graded good across 20 cells, no floor cards
  • package-validate.mjs exits 0; render check 8/8 clean
  • pnpm typecheck 15/15 · pnpm lint 15/15 · pnpm test 13/13
  • Project: https://claude.ai/design/p/7192cc60-4068-4a5b-a13d-fcc85b2cc6d9

One remaining warning is triaged and recorded in NOTES.md: [FONT_MISSING] "Outfit" fires because --font-sans is "Outfit Variable", "Outfit", … — the variable family ships and is first in the stack; the bare name is only a fallback for machines with the static family installed.

For future re-syncs

dist/ui.css is generated and gitignored, so cfg.buildCmd must run before the converter or a stale stylesheet ships with no error. Recorded under Re-sync risks.

🤖 Generated with Claude Code

https://claude.ai/code/session_01EZvKUP2oYUWYPaCvteKrEN

Imports `@trails-cool/ui` into a [Claude Design](https://claude.ai/design) project so the design agent builds with our real components and brand instead of generic ones. Getting there surfaced three problems that are **latent independent of the sync** — each would bite anyone consuming this package outside the two apps. ### 1. The package shipped no CSS Components are styled with Tailwind utilities (`bg-accent`, `text-text-inv`, `h-9 px-3.5`), but `src/theme.css` holds only the `@theme` token block. The utilities are generated by each app (`apps/*/app/styles.css` does `@import "tailwindcss"` + `@import "@trails-cool/ui/theme.css"` + `@source`). So any consumer outside those apps gets **tokens with nothing using them** and renders unstyled. Adds a `build` script emitting `dist/ui.css` from a new `src/build.css` that mirrors the apps' composition. `dist/` stays gitignored — the stylesheet is regenerated, never committed. ### 2. Scanning only `packages/ui/src` was not enough Tailwind emits only classes it sees used, so the stylesheet contained exactly what these 8 components happen to reference. Any composition on top — a preview, or a screen the design agent generates — silently lost its layout classes. Caught visually: a stat row rendered as a vertical stack instead of a mono row. | | | |---|---| | Before | `flex`, `gap-4`, `font-mono`, `max-w-sm`, `justify-between`, `mt-3` — **absent** | | After | all present; 16K → 55K | `build.css` now also scans the two apps (the vocabulary the product actually uses) and safelists the full semantic token surface via `@source inline(...)`, so the documented families all exist rather than just the used subset — the elevation ramp (`text-eg-lo/mid/hi`) and `border-border-md` were documented but absent until this. ### 3. Fonts did not ship The build emitted `@font-face` rules whose `url()` targets were never copied, and dropped 8 as dead. `cfg.extraFonts` now points at the `@fontsource-variable` stylesheets so the real `.woff2` binaries are copied — Outfit and Geist Mono render for real instead of falling back to system faces. ### Also `"@parcel/watcher": false` in `allowBuilds` — `@tailwindcss/cli` pulls it in and pnpm refuses an unlisted build script. The watcher is only used by `--watch`, which this build never runs, so `false` preserves the status quo per the existing convention in that block. ### What `.design-sync/` holds Sync inputs, so re-syncs are reproducible and mostly deterministic: `config.json`, the 8 authored preview sources, `NOTES.md` (repo gotchas + re-sync risks), and `conventions.md` — the header prepended to the generated README and read by the design agent. **Every class, token and component named in conventions.md was verified against the built artifacts**, since a header naming things that do not exist is worse than none. ### Verification - 8 components, **all with authored previews graded good** across 20 cells, no floor cards - `package-validate.mjs` exits 0; render check **8/8 clean** - `pnpm typecheck` 15/15 · `pnpm lint` 15/15 · `pnpm test` 13/13 - Project: `https://claude.ai/design/p/7192cc60-4068-4a5b-a13d-fcc85b2cc6d9` One remaining warning is triaged and recorded in NOTES.md: `[FONT_MISSING] "Outfit"` fires because `--font-sans` is `"Outfit Variable", "Outfit", …` — the *variable* family ships and is first in the stack; the bare name is only a fallback for machines with the static family installed. ### For future re-syncs `dist/ui.css` is generated and gitignored, so **`cfg.buildCmd` must run before the converter** or a stale stylesheet ships with no error. Recorded under Re-sync risks. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01EZvKUP2oYUWYPaCvteKrEN
feat(ui): emit a standalone stylesheet and sync the DS to Claude Design
Some checks failed
CD Staging / Build & Push Docker Images (pull_request) Has been skipped
CD Staging / Build & Push Docker Images-1 (pull_request) Has been skipped
CD Staging / Deploy Staging (pull_request) Has been skipped
CD Staging / Deploy PR Preview (pull_request) Has been skipped
CI / Dockerfile Package Check (pull_request) Successful in 17s
CI / Security Scan (pull_request) Successful in 58s
CI / Visual Tests (pull_request) Successful in 2m17s
CI / Journal Image Smoke Test (pull_request) Successful in 11m58s
CI / E2E Tests (pull_request) Has been cancelled
CI / Checks (pull_request) Has been cancelled
Cancel superseded CI / Cancel in-flight CI (pull_request) Successful in 10s
CD Staging / Tear Down PR Preview (pull_request) Successful in 34s
33f490b98f
Imports @trails-cool/ui into a Claude Design project so the design agent
builds with our real components and brand instead of generic ones. Getting
there needed three fixes, all of which are latent problems independent of
the sync.

The package shipped no CSS. Components are styled with Tailwind utilities
(bg-accent, text-text-inv, h-9 px-3.5), but src/theme.css holds only the
@theme token block — the utilities are generated by each app. Anything
consuming the package outside those apps therefore gets tokens with nothing
using them and renders unstyled. Adds a build script emitting dist/ui.css
via a new src/build.css that mirrors what apps/*/app/styles.css already do.
dist/ stays gitignored; the stylesheet is regenerated, never committed.

Scanning only packages/ui/src was not enough. Tailwind emits only the classes
it sees used, so the stylesheet contained exactly what these eight components
happen to reference. Any composition on top — a preview, or a screen the
design agent generates — silently lost its layout classes; this showed up as
a stat row rendering as a vertical stack instead of a mono row. build.css now
also scans the two apps (the vocabulary the product actually uses) and
safelists the full semantic token surface, so the documented families all
exist rather than just the used subset.

Fonts did not ship. The build emitted @font-face rules whose url() targets
were never copied, and dropped eight rules as dead. cfg.extraFonts now points
at the @fontsource-variable stylesheets so the real woff2 binaries are copied
— Outfit and Geist Mono render for real rather than falling back.

Also adds "@parcel/watcher": false to allowBuilds. @tailwindcss/cli pulls it
in and pnpm refuses to install an unlisted build script; the watcher is only
used by --watch, which this build never runs.

.design-sync/ carries the sync inputs: config, the authored preview sources,
NOTES.md (repo gotchas and re-sync risks), and conventions.md — the header
prepended to the generated README and read by the design agent. Every class,
token and component named in it was verified against the built artifacts.

Verified: 8 components, all with authored previews graded good across 20
cells, no floor cards; package-validate.mjs exits 0 with the render check
8/8 clean; typecheck, lint and test all pass.

Note for re-syncs: dist/ui.css is generated and gitignored, so cfg.buildCmd
must run before the converter or a stale stylesheet ships silently.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EZvKUP2oYUWYPaCvteKrEN
ullrich deleted branch design-sync-ui 2026-09-13 08:57:29 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
trails-cool/trails!153
No description provided.