Files
web-app-launcher/CLAUDE.md
T
alexei.dolgolyov f087551454 feat(ui): cozy polish — primitives, motion, empty states
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.
2026-05-28 14:39:53 +03:00

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

  1. Before writing any form control in a .svelte file, scan src/lib/components/ui/ first. If a matching primitive exists, import and use it.
  2. 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).
  3. If you genuinely need a new primitive, add it to src/lib/components/ui/, give it a class?: string prop merged via cn(), document it in this table, and migrate at least two call sites in the same PR so it's not dead code.
  4. Tokens (--primary, --card, --room-*, --shadow-soft, etc.) are defined once in src/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. Never rounded-lg on a section wrapper.
  • Headings (h1, h2, h3) automatically get Fraunces via base layer — no need to add font-display unless 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-expand from app.css over generic Tailwind animations. All motion classes already respect prefers-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., buttonClass in Button.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