Añade scripts/check-tokens.mjs (npm run lint:tokens, incorporado a
lint) como guardrail: falla si src/**/*.svelte reintroduce valores
arbitrarios fuera de la lista blanca de B.4, rounded-* de fábrica,
pares h-N w-N (deberían ser size-N), espaciado fraccionario en
cuartos, o colores hex/rgba() literales.
Se limpia el repo hasta dejar el script en 0 violaciones: ~40 pares
h-N w-N → size-N (sobre todo iconos, vía script puntual), ~26
espaciados fraccionarios en cuartos redondeados al entero más cercano
(Δ≤1px, p. ej. py-2.75→py-3, gap-1.75→gap-2), radios de fábrica
sueltos (rounded-sm/lg, rounded a secas) migrados a los tokens
correspondientes, y dos tracking-[-0.02em] que coincidían
exactamente con el token tracking-display ya existente pero nunca se
habían migrado. Se retiran las recetas stroke-thin/regular/bold
(creadas en P1, cero consumidores) — decisión tomada, sin bloquear:
no se migra strokeWidth={n} (13 valores, en casi todos los
componentes) a CSS porque no se puede verificar sin Docker que
lucide-svelte respete stroke-width por clase en vez del prop, y una
regresión ahí sería silenciosa en toda la app; el script documenta
este gap explícitamente en vez de fingir cobertura completa.
Detector de huérfanos: 0 componentes sin importadores. Se borra
docs/design-system-refactor-plan.md (plan anterior, superseded por
frontend-refactor-plan.md). web-player/CLAUDE.md gana una sección
"Estilos" con la lista blanca B.4 y los componentes base a reutilizar.
Se recalcula la tabla de línea base (A.4) y se anota en el Registro
(D.1): 61→47 componentes, 44→11 arbitrarios (los 11 restantes en
lista blanca), 13→8 radios distintos, 0 violaciones de lint:tokens.
Verificado con npm run check (0 errores), npm run lint:tokens (0
violaciones) y npm run build. Smoke test de /login sin errores de
consola achacables al cambio (Docker no disponible en esta sesión).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
CoverForm.svelte es el esqueleto compartido (form + enhance + portada
+ mensaje de error + botones) de AlbumForm y PlaylistForm, que quedan
solo con sus campos propios vía snippets `fields` (junto a la
portada) y `extra` (a ancho completo, no está en la spec literal pero
necesario: la descripción del álbum y el selector de visibilidad de
playlist no caben en la columna junto al arte).
ImageDropzone gana `size="hero"` (size-cover-hero + rounded-art-lg) y
un callback `onselect`; la página de subir canción usa ahora este
componente en vez de reimplementar el selector de portada a mano
(envuelto en {#key} para poder resetear tras publicar). Su icono de
posición vacía pasa de 64px a icon-xl (36px), como recomienda el plan.
SegmentedControl.svelte (genérico, N opciones) sustituye el selector
Privada/Pública de PlaylistForm. Chip.svelte sustituye los chips de
filtro de Explorar (selected/count) y los chips de género sugeridos en
"Sin resultados" (href, estilo outline).
@utility field-control en layout.css unifica la cadena de clases
repetida de Input y Textarea.
Verificado con npm run check/build; sin QA visual autenticada (Docker
no disponible en esta sesión).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Migrates remaining one-off colors (bg-white/[N%], border-white/N, hardcoded
shadows/gradients) to the existing --mf-* tokens so light mode reacts
consistently everywhere, including a fix to --color-hover which was
hardcoded instead of pointing at --mf-surface-2. Extracts CollectionHeader
(album/playlist/mix/liked headers) and AccountMenu (TopBar/MobileHeader)
to remove five and two duplicated implementations respectively, and
normalizes inconsistent empty-state copy.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Backend:
- New Likes feature (toggle/get liked tracks) and endpoints
- User follow/unfollow, listening stats, public user profile
- Playlist visibility (public/private) support
- Various auth and DTO/response adjustments to support the above
Frontend (web-player):
- New design system pass: theme tokens/mode (light/dark), glass menu,
tabs bar, player dock/extras/queue components, mix tiles, artist link
- New routes: Liked, public user profile (/u/[id]), likes API proxy
- Search refactor: shared SearchBox component used by TopBar and
MobileHeader (search now reachable on mobile), live results as you
type instead of requiring Enter, dropped the "Buscando…" quick-result
preview
- Removed now-unused components (MobileNav, CoverBadge, MixBentoTile,
PlayPauseOverlay, SuggestionsTable, userDirectory)
- Design handoff reference docs added under web-player/design_handoff_musify_player
and web-player/docs/design-system-refactor-plan.md
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Albums previously had no cover concept at all: no picture upload
flow, no GET /albums/{id}/cover, and AlbumApplicationResponse's
image-key fields were plumbed but never populated. Description,
Pictures and TrackCount already lived on the shared Album base entity
for both local and external albums, so unifying the cover story only
required building the missing local-album picture pipeline.
Backend, cloning PlayList's existing async picture-processing
architecture exactly (MassTransit routing slip + saga), but making
the picture mandatory instead of optional (matching Track's pattern):
- AlbumPictures moves from all-nullable to required fields.
- New AlbumConfiguration (routes/sizes), deliberately without
PlayList's preset-picture fallback since a picture is now required
up front.
- CreateAlbum now takes a mandatory PictureIntentId, validates and
consumes the upload intent, and kicks off the same
copy-to-final -> resize -> upload -> update-entity routing slip
PlayLists use, via a new AlbumProcessingState saga that marks the
album Failed on a picture-processing failure.
- UpdateAlbum takes an optional NewPictureIntentId to replace the
cover later.
- New GET /albums/{id}/cover and POST /albums/upload-picture
endpoints, mirroring the playlist ones.
- New EF migration for the AlbumProcessingState saga table.
Frontend:
- AlbumForm gains a cover dropzone, required on create and optional
on edit (via ImageDropzone's new `required` prop), uploaded through
the same presigned-upload helper playlists use.
- New /api/albums/{id}/cover proxy route.
- PlaylistArt gains a generic coverUrl prop so AlbumHeader and the
album list/detail cards render the real uploaded cover instead of
only the track mosaic fallback.
Delete-time picture cleanup for albums is intentionally left as a
follow-up, matching the existing gap in playlist deletion.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Updates the compose build context, the Dockerfile header, the package
name and the docs. The compose service and container keep the name
frontend/musify-frontend, since nginx routes to http://frontend:3000.
The source files also carry the first prettier pass, so the rename and
the reformat land together for them.