Add scripts/check-tokens.mjs (npm run lint:tokens, wired into lint) as
a guardrail: fails if src/**/*.svelte reintroduces arbitrary values
outside the B.4 allowlist, factory rounded-*, h-N w-N pairs (should
be size-N), quarter-step fractional spacing, or literal hex/rgba()
colors.
Cleans up the repo until the script reports 0 violations: ~40 h-N w-N
pairs -> size-N (mostly icons, via a one-off script), ~26 quarter-step
fractional spacings rounded to the nearest integer (delta <=1px, e.g.
py-2.75->py-3, gap-1.75->gap-2), stray factory radii (rounded-sm/lg,
bare rounded) migrated to the matching tokens, and two
tracking-[-0.02em] that matched the existing tracking-display token
exactly but had never been migrated. Removes the
stroke-thin/regular/bold recipes (added earlier, zero consumers) —
decision made, not blocking: strokeWidth={n} (13 values, across
almost every component) isn't migrated to CSS because there's no way
to verify without Docker that lucide-svelte honors stroke-width by
class instead of the prop, and a regression there would be silent
across the whole app; the script documents this gap explicitly
instead of pretending full coverage.
Orphan detector: 0 components with no importers. Removes
docs/design-system-refactor-plan.md (earlier plan, superseded by
frontend-refactor-plan.md). web-player/CLAUDE.md gains a "Styles"
section with the B.4 allowlist and the base components to reuse.
Recalculates the baseline table (A.4) and logs it: 61->47 components,
44->11 arbitrary values (the remaining 11 on the allowlist), 13->8
distinct radii, 0 lint:tokens violations.
Verified with npm run check (0 errors), npm run lint:tokens (0
violations) and npm run build. Smoke-tested /login with no console
errors attributable to this change.
4.7 KiB
Musify — web-player
Web pública de Musify (carpeta web-player/). Para diseño y recetas de UI usa el skill musify-web.
Stack
SvelteKit 2 (adapter-node) · Svelte 5 runes · Tailwind 4 · TypeScript strict · auth OIDC Zitadel (openid-client + jose).
No hay svelte.config.js (config inline en vite.config.ts) ni tailwind.config.js (config en src/routes/layout.css).
Comandos
npm run dev # servidor de desarrollo (puerto 5173)
npm run build
npm run check # svelte-check — debe salir 0 errores antes de cerrar un cambio
npm run format # prettier --write .
npm run lint # prettier --check . && eslint . && npm run lint:tokens
npm run lint:tokens # scripts/check-tokens.mjs — guardrail del sistema de diseño (ver abajo)
Reglas
- Sin comentarios en el código salvo que sean imprescindibles.
npm run lintynpm run checkdeben salir limpios antes de cerrar un cambio.- Svelte 5 runes siempre:
$props()coninterface Props,$state,$derived,$effect. Nada deexport letni$:.{#each}siempre con clave. - Server-only bajo
$lib/server/; nunca importado desde cliente. - Datos SSR vía
loaden+page.server.ts/+layout.server.ts→ propdata. - Enlaces a endpoints
+server.ts(login/logout) llevandata-sveltekit-reload. - Reutiliza los tokens/recetas del sistema de diseño; extrae a
$lib/components/ui/lo que se repita.
Estilos
El sistema de diseño vive en src/routes/layout.css (tokens @theme + recetas @utility) y src/lib/theme/ (theme.css para neutros estáticos, tokens.ts para los tokens dinámicos por hue de usuario). scripts/check-tokens.mjs (npm run lint:tokens, parte de lint) falla si src/**/*.svelte reintroduce lo que el refactor de docs/frontend-refactor-plan.md (P0–P14) eliminó — léelo antes de añadir un valor arbitrario o un radio/tamaño "de fábrica".
- Radios: siempre
rounded-tag/thumb/control/art/art-lg/panel/panel-lg(orounded-fullpara píldoras/círculos). Nuncarounded-sm/md/lg/xl/2xl/3xlniroundeda secas. - Tamaños cuadrados:
size-*en vez deh-N w-Nrepetido (icon:size-icon-xs/sm/md/lg/xl; portadas:size-cover-xs…hero). - Tipografía: recetas
text-display-1/2/3/4,text-eyebrow,text-body,text-count; tracking contracking-display/tracking-eyebrow, notracking-[…]suelto. - Espaciado: escala de Tailwind en pasos de
.5(evita cuartos como.25/.75; solo aceptables si el token semántico lo pide, p. ej.mb-4.5enSectionHeading). - Colores: siempre
--mf-*/text-*/bg-*/border-*detheme.css/tokens.ts. Nunca hex nirgba()literales en un.svelte(esos literales solo existen como fuente de verdad dentro detheme.css/tokens.ts). style="…"y arbitrarios-[…]: solo la lista blanca de B.4 endocs/frontend-refactor-plan.md(stagger--i, hue de tile, posición de menú, anchos/alturas dinámicos víavar(--mf-*),text-[clamp(…)],grid-cols-[auto_1fr],max-h-[85dvh], paddings envhpara centrado vertical). Todo lo demás, a un token.- Componentes base a reutilizar antes de crear uno nuevo:
Artwork,Avatar,ListRow,MediaIdentity,TrackList,MediaCard,PageHeader,ContextMenu,ConfirmDialog,PlayButton,Slider,Logo,Chip,SegmentedControl,CoverForm. - Gaps conocidos y aceptados (documentados en
scripts/check-tokens.mjs):strokeWidth={n}en iconos no está unificado (13 valores distintos repartidos por casi todos los componentes; migrarlo a las recetasstroke-thin/regular/boldde forma segura requeriría poder verificar visualmente quelucide-svelterespetastroke-widthpor CSS, así que se dejó fuera de esta pasada).
Auth
hooks.server.ts valida la cookie de sesión cifrada (mf_session), refresca el access_token y puebla locals.user / locals.accessToken. Para proteger una ruta, comprueba locals.user en su load y redirige a /login?returnTo=…. Para el backend, envía locals.accessToken como Bearer hacia API_BASE_URL.
Variables de entorno en .env (ver .env.example): ZITADEL_*, AUTH_REDIRECT_URI, AUTH_POST_LOGOUT_URI, SESSION_SECRET, API_BASE_URL.
Backend API
Cliente tipado con openapi-fetch sobre el OpenAPI de Musify.Api. En un load/action server:
import { createApiClient } from '$lib/server/api';
const api = createApiClient({ fetch, accessToken: locals.accessToken ?? undefined });
const { data, error: err } = await api.GET('/tracks');
Devuelve { data, error } (estilo ErrorOr), no lanza. Los tipos viven en src/lib/api/schema.d.ts y se regeneran con npm run gen:api (requiere la API dev levantada en API_BASE_URL, que expone /openapi/v1.json solo en Development).