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

57 lines
4.2 KiB
Markdown

# 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 | secondary | outline | ghost | destructive`, sizes `sm | md | lg | icon`, built-in `loading` spinner, press-squash. **Do not repeat `rounded-xl bg-primary px-4 py-2 ...` strings.** |
| 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
```bash
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
```