f087551454
Adds 7 reusable primitives in src/lib/components/ui/ and migrates ~70 hand-rolled call sites across forms, admin panels, and routes. Tokens unchanged — same Cozy Home palette, just consistently applied. Primitives - Switch: pill toggle, role=switch, terracotta track, cubic-bezier knob - Button: 5 variants × 4 sizes, press-squash, loading spinner, buttonClass() helper for <a> link-as-CTA cases - Checkbox: rounded square with animated check-draw + indeterminate - Select: native <select> with Cozy chevron + matched radius - Slider: gradient track, terracotta-bordered knob, aria-valuetext - Input + Field: documented in CLAUDE.md for future use - 9 buttonClass unit tests Migrations - 23 <input type=checkbox> → <Switch> (boolean settings) - 5 multi-select checkboxes → <Checkbox> (DiscoveryPanel, sys-stats metrics) - ~28 <select> → <Select> - 17 <input type=range> → <Slider> (ThemeCustomizer's hue picker kept custom) - ~25 hand-rolled buttons → <Button> / buttonClass() Surface polish - Admin section wrappers: rounded-lg → rounded-[1.4rem] + shadow-soft (resolves the Phase-5 tradeoff from the Cozy migration memo) - BoardPropertiesPanel: live theme preview swatch showing computed hsl() on a sample button; hue/sat use Slider; bg/cardSize use Select - AppHealthBadge: role=status + aria-live=polite; .status-degraded (slow amber breathing) and .status-offline (single attention flash) now applied - AppForm collapse triggers: rotating chevron + aria-expanded - Empty states for /boards and /apps: inline SVGs using --room-* tokens (peach/sky/sage/butter) instead of generic Lucide icons - Login Remember Me: showcase Switch (first-impression surface) Motion (src/app.css) - New cozy-rise / cozy-rise-stagger for staggered grid reveals (/boards, /apps) - New cozy-expand for accordion sections (healthcheck, integration, wallpaper) - All motion respects prefers-reduced-motion CLAUDE.md - New project guide with a mandatory Frontend reuse table — every primitive documented with "never use raw <input type=checkbox>/<select>/<range>" and "do not repeat rounded-xl bg-primary px-4 py-2 ..." rules Verification - npm run check: 0 errors, 0 warnings, 5831 files - npm test: 301 passing - npm run lint: 0 errors (19 pre-existing warnings unchanged) - npm run build: ✔ done Branch is feat/cozy-polish, ready to PR against master.
4.2 KiB
4.2 KiB
web-app-launcher — project guide for Claude
SvelteKit 2 + Svelte 5 (runes) + Tailwind 4 + Prisma + Vitest. Cozy Home design system (warm cream / dusk, terracotta accent, Fraunces + Figtree, soft shadows). Token contract lives in src/app.css.
Frontend
Basic-component reuse — MANDATORY
When you need any of the following, use the existing primitive from src/lib/components/ui/. Do not hand-roll a new Tailwind class string for a control that already has a primitive.
| Need | Primitive | Why |
|---|---|---|
| Boolean on/off setting | Switch.svelte |
Pill toggle, role="switch", AA contrast, terracotta track when on. Default for any "enable X" / "show Y" / "is default" field. Never use <input type="checkbox"> for booleans. |
| Multi-select item in a list | Checkbox.svelte |
Rounded square with animated check-draw. Only use when the control is truly "pick any number of these," not a single boolean. |
| Dropdown of fixed options | Select.svelte |
Styled chevron, matches Cozy input radius. Wraps native <select>. Do not use raw <select>. |
| Single-line text / number / email / url / password | Input.svelte |
Standard rounded-xl, focus ring, invalid state. Do not repeat the w-full rounded-xl border border-input bg-background px-3 py-2 ... string anywhere. |
| Number in a range (refresh interval, hue, blur, etc.) | Slider.svelte |
Cozy gradient track, terracotta-bordered knob, value tooltip, aria-valuetext. Do not use raw <input type="range">. |
| Action button (submit, save, cancel, link-as-CTA) | Button.svelte |
Variants `primary |
| Label + hint + error wrapper around a control | Field.svelte |
Consolidates <label> + control + <p class="text-xs text-destructive">. |
| Confirm-before-destructive | ConfirmDialog.svelte |
Already exists. Use it. |
| Entity / icon / tag picker | EntityPicker, MultiEntityPicker, IconPickerButton, TagsInput |
Already exist. Reuse. |
Process
- Before writing any form control in a
.sveltefile, scansrc/lib/components/ui/first. If a matching primitive exists, import and use it. - If you find yourself copying a Tailwind class string verbatim from another file, stop: that's the trigger to extract a primitive (or expand an existing one).
- If you genuinely need a new primitive, add it to
src/lib/components/ui/, give it aclass?: stringprop merged viacn(), document it in this table, and migrate at least two call sites in the same PR so it's not dead code. - Tokens (
--primary,--card,--room-*,--shadow-soft, etc.) are defined once insrc/app.css. Never hardcode hex/HSL — read from the token.
Cozy spec quick reminders
- Hero cards:
rounded-[1.4rem]+shadow-[var(--shadow-soft)]. Dense panels:rounded-xl. Neverrounded-lgon a section wrapper. - Headings (
h1,h2,h3) automatically get Fraunces via base layer — no need to addfont-displayunless overriding non-heading text. - Focus uses
focus-visible:ring-2 focus-visible:ring-primary/30— primitives already do this; mirror it on anything hand-rolled. - Motion is gentle and present: prefer
cozy-rise/cozy-expandfromapp.cssover generic Tailwind animations. All motion classes already respectprefers-reduced-motion.
Backend
- Auth: session cookie + optional OAuth. Roles:
admin/ user / guest. Always check role at the route load function, not the component. - Validation: Zod schemas live in
src/lib/utils/validators.ts. Reuse the same schema on client (superForms) and server. - DB: Prisma. Never query the DB directly from a route — go through
src/lib/server/services/*Service.ts.
Testing
- Vitest, Node environment, no DOM (existing pattern). Component tests use the module-scope helpers (e.g.,
buttonClassinButton.svelte) rather than rendering — keep that convention. - Run before committing:
npm run check && npm run lint && npm test && npm run build.
Commands
npm run dev # vite dev on :5181
npm run check # svelte-check (TS + Svelte)
npm run lint # eslint
npm test # vitest run
npm run build # production build