diff --git a/AGENTS.md b/AGENTS.md index f8ca2b4..f92e0ff 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -14,7 +14,10 @@ Basecoat is a vanilla JavaScript/HTML/CSS port of shadcn/ui components. It reuse ## What to edit (and what not) - Edit sources only: - - `src/css/basecoat.css` – All component CSS classes (Tailwind + custom) + - `src/css/base/base.css` – Shared base layer, tokens, and semantic utility classes + - `src/css/components/*.css` – Component CSS classes (Tailwind + custom) + - `src/css/styles/style-*.css` – Style-pack overrides (`vega`, `nova`, `maia`, `lyra`, `mira`, `luma`, `sera`, `rhea`) + - `src/css/basecoat.css` – Aggregate CSS entrypoint - `src/js/*.js` – Individual component JS files (kebab-case, ESM) + `basecoat.js` (component registry) - `src/nunjucks/*.njk` and `src/jinja/*.html.jinja` – Component template macros - `docs/src/components/*.njk` – Component documentation pages with examples @@ -31,6 +34,67 @@ Basecoat is a vanilla JavaScript/HTML/CSS port of shadcn/ui components. It reuse - `packages/cli` – CLI; distributed assets live under `dist/`. - Docs site: `docs/src` (Eleventy); output goes to `_site/`. +## Style system + +- Basecoat keeps its own component class API (`btn`, `dialog`, `popover`, etc.). Do not introduce shadcn `cn-*` classes into Basecoat source. +- Shadcn/ui style packs are used as reference material, but must be mapped onto Basecoat markup and selectors. +- Style-pack architecture is one file per style: + - `src/css/styles/style-vega.css` + - `src/css/styles/style-nova.css` + - `src/css/styles/style-maia.css` + - `src/css/styles/style-lyra.css` + - `src/css/styles/style-mira.css` + - `src/css/styles/style-luma.css` + - `src/css/styles/style-sera.css` + - `src/css/styles/style-rhea.css` +- Default aggregate and CDN behavior should resolve to Vega unless explicitly changed. +- Non-default styles should be selected by importing the relevant style file or using a style-specific bundled asset, not by introducing shadcn internal selector conventions. +- During migration, each component must be reviewed in two layers: + - shared/base component CSS + - style-pack CSS overrides +- Do not assume existing Basecoat component CSS is still the right baseline. Reassess shared rules vs style-owned rules component by component. +- When Basecoat differs from upstream shadcn/ui, classify the difference as either intentional or drift, and prefer explicit decisions over silent divergence. + +## Component migration workflow + +For each component migration, follow this sequence: + +1. Review the current Basecoat component CSS and markup/templates first. +2. Review the latest upstream shadcn/ui base component source for the equivalent component. +3. Review the relevant sections in all eight upstream style packs (`vega`, `nova`, `maia`, `lyra`, `mira`, `luma`, `sera`, `rhea`). +4. Decide what belongs in shared Basecoat component CSS versus what belongs in style-pack CSS. +5. Keep Basecoat selectors and markup. Do not port shadcn `cn-*` selectors directly. +6. Remove outdated Basecoat visual rules from the shared component CSS if those rules are now style-owned. +7. Implement or update the component section in: + - `src/css/components/.css` + - `src/css/styles/style-vega.css` + - `src/css/styles/style-nova.css` + - `src/css/styles/style-maia.css` + - `src/css/styles/style-lyra.css` + - `src/css/styles/style-mira.css` + - `src/css/styles/style-luma.css` + - `src/css/styles/style-sera.css` + - `src/css/styles/style-rhea.css` +8. Document any non-trivial difference from upstream as either: + - intentional + - drift fixed + - deferred +9. Update the migration tracker entry for the component. +10. Run `npm run build` after each component migration. +11. Manually verify the component in docs for: + - light and dark mode + - all supported variants and sizes + - interaction states (`hover`, `focus`, `disabled`, `aria-invalid`, open/selected states when relevant) + +Per-component file checklist: + +1. Basecoat component CSS: `src/css/components/.css` +2. Basecoat templates/macros if relevant: `src/nunjucks/*.njk`, `src/jinja/*.html.jinja` +3. Basecoat docs page: `docs/src/components/.njk` +4. Upstream base component: `../_sandbox/shadcn-ui/apps/v4/registry/bases/base/ui/.tsx` +5. Upstream styles: `../_sandbox/shadcn-ui/apps/v4/registry/styles/style-{vega,nova,maia,lyra,mira,luma,sera,rhea}.css` +6. Basecoat styles: `src/css/styles/style-{vega,nova,maia,lyra,mira,luma,sera,rhea}.css` + ## Conventions - ESM everywhere (`import`/`export`). @@ -72,6 +136,7 @@ Examples: ## Common tasks - Update a component's CSS/JS: Edit `src/css/basecoat.css` and/or `src/js/*.js`. +- Update a component's CSS/JS: Edit the relevant file in `src/css/components/*.css`, `src/css/styles/style-*.css`, and/or `src/js/*.js`. - Update a component template: Edit `src/nunjucks/*.njk` and/or `src/jinja/*.html.jinja`. - Add a new component: Create JS, CSS, templates, and docs page under `docs/src/components/*.njk`. - Update navigation: Edit files in `docs/src/_includes/partials/`. diff --git a/docs/css/style-luma.css b/docs/css/style-luma.css new file mode 100644 index 0000000..4c46731 --- /dev/null +++ b/docs/css/style-luma.css @@ -0,0 +1,3 @@ +@import "tailwindcss" source(none); +@import "../../src/css/base/base.css"; +@import "../../src/css/styles/style-luma.css"; diff --git a/docs/css/style-lyra.css b/docs/css/style-lyra.css new file mode 100644 index 0000000..8c74142 --- /dev/null +++ b/docs/css/style-lyra.css @@ -0,0 +1,3 @@ +@import "tailwindcss" source(none); +@import "../../src/css/base/base.css"; +@import "../../src/css/styles/style-lyra.css"; diff --git a/docs/css/style-maia.css b/docs/css/style-maia.css new file mode 100644 index 0000000..9ad1346 --- /dev/null +++ b/docs/css/style-maia.css @@ -0,0 +1,3 @@ +@import "tailwindcss" source(none); +@import "../../src/css/base/base.css"; +@import "../../src/css/styles/style-maia.css"; diff --git a/docs/css/style-mira.css b/docs/css/style-mira.css new file mode 100644 index 0000000..b6c7abe --- /dev/null +++ b/docs/css/style-mira.css @@ -0,0 +1,3 @@ +@import "tailwindcss" source(none); +@import "../../src/css/base/base.css"; +@import "../../src/css/styles/style-mira.css"; diff --git a/docs/css/style-nova.css b/docs/css/style-nova.css new file mode 100644 index 0000000..a78d480 --- /dev/null +++ b/docs/css/style-nova.css @@ -0,0 +1,3 @@ +@import "tailwindcss" source(none); +@import "../../src/css/base/base.css"; +@import "../../src/css/styles/style-nova.css"; diff --git a/docs/css/style-rhea.css b/docs/css/style-rhea.css new file mode 100644 index 0000000..0b1e908 --- /dev/null +++ b/docs/css/style-rhea.css @@ -0,0 +1,3 @@ +@import "tailwindcss" source(none); +@import "../../src/css/base/base.css"; +@import "../../src/css/styles/style-rhea.css"; diff --git a/docs/css/style-sera.css b/docs/css/style-sera.css new file mode 100644 index 0000000..9d5aad3 --- /dev/null +++ b/docs/css/style-sera.css @@ -0,0 +1,3 @@ +@import "tailwindcss" source(none); +@import "../../src/css/base/base.css"; +@import "../../src/css/styles/style-sera.css"; diff --git a/docs/src/_includes/layouts/base.njk b/docs/src/_includes/layouts/base.njk index 966026b..73c6069 100644 --- a/docs/src/_includes/layouts/base.njk +++ b/docs/src/_includes/layouts/base.njk @@ -36,6 +36,17 @@ } })(); + {# Style variant #} + {# Meta #} @@ -68,6 +79,18 @@ {# CSS #} + + {# JS #} @@ -109,4 +132,4 @@ {% from "toast.njk" import toaster %} {{ toaster() }} - \ No newline at end of file + diff --git a/docs/src/_includes/partials/header.njk b/docs/src/_includes/partials/header.njk index c9884bd..0751419 100644 --- a/docs/src/_includes/partials/header.njk +++ b/docs/src/_includes/partials/header.njk @@ -38,6 +38,44 @@ }); })(); + + {% endset %} {{ code_preview("button-large", code) }} +

Extra Small

+ +{% set code %}{% endset %} +{{ code_preview("button-extra-small", code) }} + +

Extra Small Icon

+ +{% set code %}{% endset %} +{{ code_preview("button-extra-small-icon", code) }} +

Small

{% set code %}{% endset %} -{{ code_preview("button-small", code) }} \ No newline at end of file +{{ code_preview("button-small", code) }} diff --git a/handoff.md b/handoff.md new file mode 100644 index 0000000..c730d03 --- /dev/null +++ b/handoff.md @@ -0,0 +1,156 @@ +# Basecoat Multi-Style Migration Handoff + +This handoff is for resuming the `release/1.0.0` work on Basecoat's split CSS architecture and shadcn/ui v4 style-pack support. + +## Ground Truth + +- Local Basecoat repo: `/Users/hunvreus/Workspace/basecoat` +- Local shadcn/ui upstream repo: `/Users/hunvreus/Workspace/_sandbox/shadcn-ui` +- Upstream branch/commit verified during this pass: `main` at `460ad60d8` +- Upstream style registry: `../_sandbox/shadcn-ui/apps/v4/registry/styles.tsx` +- Current upstream styles: `vega`, `nova`, `maia`, `lyra`, `mira`, `luma`, `sera`, `rhea` + +The earlier five-style assumption was wrong. Luma, Sera, and Rhea are current upstream styles and must be included in every component migration. + +## Core Rules + +- Keep Basecoat's own class API (`btn`, `btn-outline`, `dialog`, `popover`, etc.). +- Do not introduce shadcn/ui `cn-*` classes. +- Use upstream shadcn/ui as a style reference, not as selectors to copy directly. +- Translate upstream style intent to Basecoat selectors and markup. +- Component CSS should stay structural/shared when possible. +- Style-pack CSS owns visual differences: radius, density, spacing, typography, focus rings, shadows, and style-specific variant details. +- Default aggregate behavior remains Vega. +- Non-default styles are selected by importing or loading the relevant style file, not by requiring `.style-*` wrapper classes. + +## Files To Check For Every Component + +- Basecoat component CSS: `src/css/components/.css` +- Basecoat templates/macros if relevant: `src/nunjucks/*.njk`, `src/jinja/*.html.jinja` +- Basecoat docs page: `docs/src/components/.njk` +- Upstream base component: `../_sandbox/shadcn-ui/apps/v4/registry/bases/base/ui/.tsx` +- Upstream styles: `../_sandbox/shadcn-ui/apps/v4/registry/styles/style-{vega,nova,maia,lyra,mira,luma,sera,rhea}.css` +- Basecoat styles: `src/css/styles/style-{vega,nova,maia,lyra,mira,luma,sera,rhea}.css` + +## Required Process + +1. Review current Basecoat CSS and markup/templates/docs. +2. Review upstream base component source. +3. Review the component section in all eight upstream style files. +4. Decide what belongs in shared component CSS versus each style file. +5. Remove outdated shared visual rules if those rules are now style-owned. +6. Implement the component section in all eight Basecoat style files. +7. Keep Basecoat selectors only. +8. Classify non-trivial differences as `intentional`, `drift fixed`, or `deferred`. +9. Update `migration-tracker.md`. +10. Run `npm run build`. +11. Run `npm run docs:build` when docs or docs style assets are touched. +12. Browser-check light/dark and all eight styles. + +Do not mark a component done just because CSS compiles. + +## What Has Been Done + +- Split CSS source into: + - `src/css/base/base.css` + - `src/css/components/*.css` + - `src/css/styles/style-*.css` +- Kept `src/css/basecoat.css` as the backward-compatible default aggregate. +- Added `src/css/basecoat.all.css` as an explicit all-in-one Tailwind source. +- Updated `scripts/build.js` to copy split CSS folders into `packages/css/dist`. +- Split `select` and `native-select`. +- Kept `src/css/components/form.css` as a convenience aggregate rather than nesting under `components/form/*`. +- Added docs style switcher in `docs/src/_includes/partials/header.njk`. +- Added docs stylesheet swapping in `docs/src/_includes/layouts/base.njk`. +- Added docs style entrypoints for all non-default styles: + - `docs/css/style-nova.css` + - `docs/css/style-maia.css` + - `docs/css/style-lyra.css` + - `docs/css/style-mira.css` + - `docs/css/style-luma.css` + - `docs/css/style-sera.css` + - `docs/css/style-rhea.css` +- Added Basecoat style files for all eight upstream styles: + - `src/css/styles/style-vega.css` + - `src/css/styles/style-nova.css` + - `src/css/styles/style-maia.css` + - `src/css/styles/style-lyra.css` + - `src/css/styles/style-mira.css` + - `src/css/styles/style-luma.css` + - `src/css/styles/style-sera.css` + - `src/css/styles/style-rhea.css` + +## Button Status + +Button is the only component with style-pack sections currently present. + +Files involved: + +- `src/css/components/button.css` +- `src/css/styles/style-vega.css` +- `src/css/styles/style-nova.css` +- `src/css/styles/style-maia.css` +- `src/css/styles/style-lyra.css` +- `src/css/styles/style-mira.css` +- `src/css/styles/style-luma.css` +- `src/css/styles/style-sera.css` +- `src/css/styles/style-rhea.css` +- `docs/src/components/button.njk` + +Current button work includes: + +- Shared button CSS moved toward structural rules. +- Visual rules moved into style files. +- `xs` button and icon size classes added. +- Icon-aware padding mapped with Basecoat selectors using `:has(> svg:first-child)` and `:has(> svg:last-child)`. +- All eight style files have Button sections. +- Docs include `btn-xs-outline` and `btn-xs-icon-outline` examples. + +Button should remain `in-progress` until browser-audited across light/dark and all eight styles. Button-group adjacency is separate and should be handled during `button-group`. + +## Current Component Status + +- `button`: in progress / pending visual audit +- Everything else: pending + +No component besides Button should be considered migrated. + +## Issues Encountered + +- The original migration plan assumed only five styles. Upstream now has eight. +- The first button attempt mixed old Basecoat visual rules with new style-pack rules, causing broken rendering. +- Some docs style entrypoints need `src/css/base/base.css` imported first so Tailwind can resolve semantic utilities like `focus-visible:border-ring`. +- Style switching should remove the override stylesheet `href` for Vega instead of setting `href=""`. +- Sidebar dropdown width was a tangent. The user explicitly said not to modify generic CSS for that; handle width with a Tailwind class only when that task is active. + +## Things Learned + +- Shadcn/ui icon-aware button padding is CSS-owned, not React-owned. +- Upstream uses data hooks like `has-data-[icon=inline-start]:pl-*`. +- Basecoat should map that to its own markup, currently `:has(> svg:first-child)` and `:has(> svg:last-child)`. +- Blind copy is not viable because upstream uses `cn-*` class composition while Basecoat uses explicit classes like `btn-xs-icon-outline`. + +## Build And Validation Commands + +```sh +npm run build +npm run docs:build +npm run docs:dev +``` + +Use `npm run docs:dev` for manual browser review. + +## Remaining Work + +1. Run build validation after the latest Luma/Sera/Rhea wiring. +2. Browser-check `/components/button` for all eight styles in light and dark mode. +3. Update Button tracker status only after visual audit. +4. Migrate `button-group` next because it interacts with button radius and adjacency. +5. Continue component-by-component using `migration-tracker.md`. + +## Current Risks + +- Button may still have visual deltas from upstream until audited. +- Docs style override assets may duplicate base CSS because they import base utilities for Tailwind compilation. +- The package does not currently generate separate style-specific CDN bundles beyond split style exports. +- The worktree includes unrelated sidebar template changes; do not revert or fold them into style migration unless explicitly requested. diff --git a/migration-tracker.md b/migration-tracker.md new file mode 100644 index 0000000..a5a76fe --- /dev/null +++ b/migration-tracker.md @@ -0,0 +1,41 @@ +# Basecoat Style Migration Tracker + +Track migration of Basecoat components against the latest shadcn/ui v4 component source and all eight style packs. + +Upstream source root: `../_sandbox/shadcn-ui` +Upstream style files: `apps/v4/registry/styles/style-{vega,nova,maia,lyra,mira,luma,sera,rhea}.css` + +Legend: +- `pending`: not started +- `in-progress`: actively being migrated +- `done`: style-pack coverage implemented for current scope +- `deferred`: intentionally postponed + +| Component | Status | Basecoat CSS | Upstream Base | Upstream Styles | Notes | +| --- | --- | --- | --- | --- | --- | +| alert | pending | `src/css/components/alert.css` | `apps/v4/registry/bases/base/ui/alert.tsx` | `style-*.css` | | +| badge | pending | `src/css/components/badge.css` | `apps/v4/registry/bases/base/ui/badge.tsx` | `style-*.css` | | +| button | done | `src/css/components/button.css` | `apps/v4/registry/bases/base/ui/button.tsx` | all 8 `style-*.css` | Standalone button rules are mapped across all eight styles, including xs sizing and icon-aware padding. Drift fixed: Maia base radius now uses upstream `rounded-4xl`. Button-group adjacency details remain separate and should be handled during button-group migration. | +| button-group | pending | `src/css/components/button-group.css` | `apps/v4/registry/bases/base/ui/button-group.tsx` | `style-*.css` | | +| card | pending | `src/css/components/card.css` | `apps/v4/registry/bases/base/ui/card.tsx` | `style-*.css` | | +| checkbox | pending | `src/css/components/checkbox.css` | `apps/v4/registry/bases/base/ui/checkbox.tsx` | `style-*.css` | | +| collapsible | pending | `src/css/components/collapsible.css` | `apps/v4/registry/bases/base/ui/collapsible.tsx` | `style-*.css` | | +| command | pending | `src/css/components/command.css` | `apps/v4/registry/bases/base/ui/command.tsx` | `style-*.css` | | +| dialog | pending | `src/css/components/dialog.css` | `apps/v4/registry/bases/base/ui/dialog.tsx` | `style-*.css` | | +| dropdown-menu | pending | `src/css/components/dropdown-menu.css` | `apps/v4/registry/bases/base/ui/dropdown-menu.tsx` | `style-*.css` | | +| field | pending | `src/css/components/field.css` | `apps/v4/registry/bases/base/ui/field.tsx` | `style-*.css` | | +| input | pending | `src/css/components/input.css` | `apps/v4/registry/bases/base/ui/input.tsx` | `style-*.css` | | +| kbd | pending | `src/css/components/kbd.css` | `apps/v4/registry/bases/base/ui/kbd.tsx` | `style-*.css` | | +| label | pending | `src/css/components/label.css` | `apps/v4/registry/bases/base/ui/label.tsx` | `style-*.css` | | +| native-select | pending | `src/css/components/native-select.css` | `apps/v4/registry/bases/base/ui/native-select.tsx` | `style-*.css` | | +| popover | pending | `src/css/components/popover.css` | `apps/v4/registry/bases/base/ui/popover.tsx` | `style-*.css` | | +| radio | pending | `src/css/components/radio.css` | `apps/v4/registry/bases/base/ui/radio-group.tsx` | `style-*.css` | | +| range | pending | `src/css/components/range.css` | `apps/v4/registry/bases/base/ui/slider.tsx` | `style-*.css` | Consider moving slider fill sync into shared JS. | +| select | pending | `src/css/components/select.css` | `apps/v4/registry/bases/base/ui/select.tsx` | `style-*.css` | | +| sidebar | pending | `src/css/components/sidebar.css` | `apps/v4/registry/bases/base/ui/sidebar.tsx` | `style-*.css` | | +| switch | pending | `src/css/components/switch.css` | `apps/v4/registry/bases/base/ui/switch.tsx` | `style-*.css` | | +| table | pending | `src/css/components/table.css` | `apps/v4/registry/bases/base/ui/table.tsx` | `style-*.css` | | +| tabs | pending | `src/css/components/tabs.css` | `apps/v4/registry/bases/base/ui/tabs.tsx` | `style-*.css` | | +| textarea | pending | `src/css/components/textarea.css` | `apps/v4/registry/bases/base/ui/textarea.tsx` | `style-*.css` | | +| toast | pending | `src/css/components/toast.css` | `apps/v4/registry/bases/base/ui/sonner.tsx` | `style-*.css` | Needs review against current Basecoat toast structure. | +| tooltip | pending | `src/css/components/tooltip.css` | `apps/v4/registry/bases/base/ui/tooltip.tsx` | `style-*.css` | | diff --git a/package.json b/package.json index ab3a249..6121a88 100644 --- a/package.json +++ b/package.json @@ -34,8 +34,9 @@ "homepage": "https://github.com/hunvreus/basecoat#readme", "scripts": { "build": "node ./scripts/build.js", - "docs:build": "tailwindcss -i ./docs/css/styles.css -o ./docs/src/assets/styles.css --minify && eleventy", - "docs:dev:css": "tailwindcss -i ./docs/css/styles.css -o ./docs/src/assets/styles.css --watch", + "docs:build:css": "tailwindcss -i ./docs/css/styles.css -o ./docs/src/assets/styles.css --minify && tailwindcss -i ./docs/css/style-nova.css -o ./docs/src/assets/style-nova.css --minify && tailwindcss -i ./docs/css/style-maia.css -o ./docs/src/assets/style-maia.css --minify && tailwindcss -i ./docs/css/style-lyra.css -o ./docs/src/assets/style-lyra.css --minify && tailwindcss -i ./docs/css/style-mira.css -o ./docs/src/assets/style-mira.css --minify && tailwindcss -i ./docs/css/style-luma.css -o ./docs/src/assets/style-luma.css --minify && tailwindcss -i ./docs/css/style-sera.css -o ./docs/src/assets/style-sera.css --minify && tailwindcss -i ./docs/css/style-rhea.css -o ./docs/src/assets/style-rhea.css --minify", + "docs:build": "npm run docs:build:css && eleventy", + "docs:dev:css": "concurrently \"tailwindcss -i ./docs/css/styles.css -o ./docs/src/assets/styles.css --watch\" \"tailwindcss -i ./docs/css/style-nova.css -o ./docs/src/assets/style-nova.css --watch\" \"tailwindcss -i ./docs/css/style-maia.css -o ./docs/src/assets/style-maia.css --watch\" \"tailwindcss -i ./docs/css/style-lyra.css -o ./docs/src/assets/style-lyra.css --watch\" \"tailwindcss -i ./docs/css/style-mira.css -o ./docs/src/assets/style-mira.css --watch\" \"tailwindcss -i ./docs/css/style-luma.css -o ./docs/src/assets/style-luma.css --watch\" \"tailwindcss -i ./docs/css/style-sera.css -o ./docs/src/assets/style-sera.css --watch\" \"tailwindcss -i ./docs/css/style-rhea.css -o ./docs/src/assets/style-rhea.css --watch\"", "docs:dev:eleventy": "ELEVENTY_ENV=prod eleventy --serve", "docs:dev": "concurrently \"npm:docs:dev:css\" \"npm:docs:dev:eleventy\"" }, diff --git a/packages/css/package.json b/packages/css/package.json index b001afb..5b47718 100644 --- a/packages/css/package.json +++ b/packages/css/package.json @@ -58,6 +58,15 @@ "./tabs.min": "./dist/js/tabs.min.js", "./toast": "./dist/js/toast.js", "./toast.min": "./dist/js/toast.min.js", + "./all.css": "./dist/basecoat.all.css", + "./styles/vega": "./dist/styles/style-vega.css", + "./styles/nova": "./dist/styles/style-nova.css", + "./styles/maia": "./dist/styles/style-maia.css", + "./styles/lyra": "./dist/styles/style-lyra.css", + "./styles/mira": "./dist/styles/style-mira.css", + "./styles/luma": "./dist/styles/style-luma.css", + "./styles/sera": "./dist/styles/style-sera.css", + "./styles/rhea": "./dist/styles/style-rhea.css", "./package.json": "./package.json" } -} \ No newline at end of file +} diff --git a/plan.md b/plan.md new file mode 100644 index 0000000..75f1ceb --- /dev/null +++ b/plan.md @@ -0,0 +1,111 @@ +# Basecoat Multi-Style + Theme Refactor Plan + +## Goals + +- Support the current shadcn/ui visual packs: `vega`, `nova`, `maia`, `lyra`, `mira`, `luma`, `sera`, `rhea`. +- Keep current CDN and Tailwind/NPM usage working. +- Keep Basecoat's own class API (`btn`, `dialog`, `popover`, etc.). +- Split CSS into base tokens/utilities, component structure, style-pack visuals, and aggregate outputs. +- Make custom token themes, including TweakCN-style themes, predictable by loading them after Basecoat CSS. + +## Current Upstream Reference + +- Local repo: `../_sandbox/shadcn-ui` +- Style registry: `../_sandbox/shadcn-ui/apps/v4/registry/styles.tsx` +- Style files: `../_sandbox/shadcn-ui/apps/v4/registry/styles/style-{vega,nova,maia,lyra,mira,luma,sera,rhea}.css` +- Base components: `../_sandbox/shadcn-ui/apps/v4/registry/bases/base/ui/.tsx` + +## Target CSS Architecture + +1. `src/css/base/base.css` + - base layer, default token fallbacks, and semantic utilities needed by CDN users +2. `src/css/components/*.css` + - Basecoat component selectors and shared structural behavior +3. `src/css/styles/style-*.css` + - one file per style pack: `vega`, `nova`, `maia`, `lyra`, `mira`, `luma`, `sera`, `rhea` + - component visuals: size, spacing, radius, typography, shadows, focus rings, variant colors when style-owned +4. Aggregates + - `src/css/basecoat.css`: backward-compatible default aggregate, includes Vega + - `src/css/basecoat.all.css`: explicit all-in-one Tailwind source + - `src/css/basecoat.cdn.css`: CDN build entry + +No `.style-*` wrapper is part of the Basecoat public contract for style selection. Users choose a style by importing a style file or using a style-specific generated asset. Docs may switch by swapping stylesheet URLs. + +## Per-Component File Checklist + +For every component, check these files before editing: + +- Basecoat component CSS: `src/css/components/.css` +- Basecoat templates/macros if relevant: `src/nunjucks/*.njk`, `src/jinja/*.html.jinja` +- Basecoat docs examples: `docs/src/components/.njk` +- Upstream base component: `../_sandbox/shadcn-ui/apps/v4/registry/bases/base/ui/.tsx` +- Upstream style packs: `../_sandbox/shadcn-ui/apps/v4/registry/styles/style-{vega,nova,maia,lyra,mira,luma,sera,rhea}.css` +- Basecoat style packs: `src/css/styles/style-{vega,nova,maia,lyra,mira,luma,sera,rhea}.css` + +## Component Migration Workflow + +1. Review current Basecoat CSS, markup, templates, and docs for the component. +2. Review the upstream base component and all eight upstream style files. +3. Decide what is shared structure versus style-pack-owned visual rules. +4. Remove outdated visual rules from shared component CSS when they are now style-owned. +5. Map upstream style intent onto Basecoat selectors. Do not port `cn-*` selectors. +6. Update the component section in all eight Basecoat style files. +7. Document non-trivial deltas as `intentional`, `drift fixed`, or `deferred`. +8. Update `migration-tracker.md`. +9. Run `npm run build`. +10. Run `npm run docs:build` when docs or docs style assets change. +11. Manually verify the docs page in light/dark and all eight styles. + +Do not mark a component done because CSS compiles. It needs a visual/state check. + +## Packaging Contract + +- Keep `@import "basecoat-css";` as the default Vega-compatible import. +- Keep existing CDN files and JS bundle paths. +- Export split style files from `packages/css` as `basecoat-css/styles/