Skip to content

Releases: AngBan2x/epk-dashboard

v4.0.0-rc.36: P12 cerrada y P13 hasta la frontera

Choose a tag to compare

@AngBan2x AngBan2x released this 12 Oct 15:36

v4.0.0-rc.36

Cierra P12 (auditoría) y abre P13 (publicación en redes) hasta su frontera.

P12 — auditoría post-rc.35

Cerrada con el E2E de producción en verde: 136/136 en 40,7 minutos, sin
fallos ni saltados. Era el paso 5 del Flujo Obligatorio.

  • Corrección de compactación. La compactación automática saltaba al ~50 %
    del contexto, y AGENTS.md culpaba a reserved y proponía subirlo a 100000.
    Era falso: space-bunny-free declara limit.output = 524288, exactamente la
    mitad de sus 1.048.576. La salida máxima se descuenta del presupuesto de
    entrada, y de ahí el 50 %. muse-spark-1.3 comparte los mismos 1M y llega al
    87,5 % porque pide 4× menos salida. Se sobrescribe limit.output a 32000.
  • Tests que no podían fallar. scripts/turso-check.ts comparaba de verdad y
    devuelve código de error; admin-catalog dejó de leer el fuente; y hay un
    meta-test que caza it() vacíos y expect(true).
  • Puertas de SQL en runtime. updateTrack y upsertDossier filtran por
    allowlist: el nombre de una columna ya no sale del cuerpo del POST.
  • Capturas de pantalla con guardas. ReleaseTracklistSection era
    inalcanzable (solo se montaba en la rama orphan, que no puede tener hijas) y
    se borró. Y buildReleaseQueue ya no hereda el vídeo de la cabecera.
  • sync-data.yml borrado, no arreglado. Sincronizaba tracks con
    INSERT OR REPLACE omitiendo status, release_id y start_time.
    Arreglarlo lo habría hecho destructivo.
  • Landing en modo oscuro. El hero se capturaba a medias porque el generador
    esperaba 600 ms en vez de a que terminaran las animaciones. No era un bug del
    producto.

P13 — publicación en redes sociales

Hasta la frontera, sin ninguna llamada real a ninguna plataforma.

  • 20 capturas en public/pressplay/ (3,21 MB), servidas por CDN de Vercel, con
    dos guardas que abortan ante una captura en blanco.
  • Las cuatro páginas públicas se capturan sin sesión: la primera versión
    salía con el nombre de una persona real y su contador de notificaciones en una
    imagen destinada a ser pública.
  • social-drafter: redacta borradores y no puede publicar. bash, edit,
    write, task, webfetch y websearch están en deny, y hay un test que lo
    comprueba.
  • Seis adaptadores (bluesky, threads, instagram, facebook, linkedin,
    mastodon) con la capa HTTP inyectada, y una tabla única de límites con fecha
    de verificación.
  • Bug encontrado por los tests: validar de Mastodon comparaba siempre
    contra el 500 de la tabla, así que con una instancia que permite 280 un texto
    de 300 pasaba la validación.
  • Corrección de diseño: AdaptadorSocial no tenía publicar, así que
    crearAdaptador() devolvía la mitad de un adaptador.

Fuera, con su motivo

  • TikTok: gratis, pero sin auditar queda en private viewing mode.
  • X: ya no tiene plan gratuito. 0,20 $/post con URL.

Pendiente declarado

  • La landing y la ficha de release no tienen landmark <main>.
  • admin-catalog.test.ts sigue leyendo el fuente; sus dos aserciones de datos
    son ejecutables y no lo están.
  • Los límites de caracteres de Instagram, Facebook y LinkedIn no están
    publicados en ninguna parte. No se han rellenado de memoria.

Gate: 1657 tests en 84 ficheros, tsc, next lint y build limpios.

v4.0.0-rc.35

v4.0.0-rc.35 Pre-release
Pre-release

Choose a tag to compare

@AngBan2x AngBan2x released this 10 Oct 23:49

v4.0.0-rc.35 — Release completo (reproductor + datos + ficha + admin)

Reproductor consolidado (0b5efa5)

  • AudioPlayer con queue (playsQueue), hasChildren y isQueueTrackActive para álbumes con hijas; etiqueta de fuente honesta (
    esolvePlayingLabel en lib/audio-priority.ts); sin reinicio al pausar. StemsPlayer y LyricsModal borrados.

Datos normalizados (a35fa6f)

  • 83 tracks (�lbum 56, �p 6, single 21);
    elease_type minúsculas (�lbum/�p/single); Vulnicura Strings (
    el-869486ab) marcado como �p por decisión de catálogo. Guard urso-check.ts case-insensitive (lower(release_type)='ep').
  • Previews exactos aplicados en Turso (5 pistas): Heroes ( rk-37bd32a1), Stonemilker ( rk-5b53f1b7), Black Lake ( rk-c30ee43d), Family ( rk-74854e56), Notget ( rk-fd59c7ab). 3 aproximaciones rechazadas documentadas. Dry-run idempotente (0 cambios nuevos).

Ficha de release rediseñada (9e1746f)

  • Letras interactivas por pista (LyricsSectionWrapper + LyricsSection existente, no LyricsModal); ProductionDetailsWrapper en �side debajo de enlaces; navegación prev/
    ext borrada (getReleaseNeighbours eliminado); ests/unit/release-page.test.ts actualizado (Single sobre 2 pistas, no EP).

Admin consolidado (268b573)

  • Pestaña catalog (AdminTab) con filas expandibles (�uildReleaseQueue);
    elease_type y �xternal_links editables (PUT /api/admin/releases); ImageUploader disponible para artistas (kind=profile|banner + �rtistId). ests/unit/admin-catalog.test.ts cubre 4 arreglos reales.

Shows cleanup (parte del bloque datos)

  • .github/workflows/cleanup-shows.yml: cron diario �6:17 UTC con workflow_dispatch; DELETE /api/shows/cleanup con corte 48h exactas (lib/show-cleanup.ts); �nv leído en tiempo de llamada; aviso �est-effort; TEST_ADMIN_PASSWORD pendiente (docs/CONFIG_TODO.md C12).

Documentación

  • docs/AI_LOG.md: referencias a arreglos; docs/PHASE_P3.md: P11 actualizado con referencias exactas (lib/release-page.ts, ests/unit/release-page.test.ts); docs/CONFIG_TODO.md: C12 pendiente; docs/modelos gratuitos disponibles.txt: intacto (384b48f).

Invariantes protegidas

eleaseTypeLabel (lib/release-page.ts) dice la columna (Single), no inventa EP (reverso verificado con test); ests/unit/no-audio-y-enlace-al-artista.test.ts protege el helper; ests/unit/release-page.test.ts cubre 77 casos; ests/unit/admin-catalog.test.ts cubre 4; ests/unit/stems-dead-code.test.ts confirma archivos borrados (StemsPlayer/LyricsModal sin importaciones en todo el repo).

Fotos para los 12 artistas, ficha de release legible y una suite E2E que por fin corre contra produccion

Choose a tag to compare

@AngBan2x AngBan2x released this 09 Oct 06:00

Los 12 artistas del catálogo tienen ya foto de perfil y banner, la ficha de release se rediseñó para que se lea, y —lo que más tiempo llevó— la suite de Playwright podía correr, pero no contra producción: 76 de sus tests eran inútiles fuera de pnpm dev.

Fotos de los artistas (F6)

Los 12 artistas tienen las dos imágenes. scripts/apply-artist-images.ts compara antes de escribir y se niega a pisar un valor revisado sin --force.

  • Siete URLs con el host alias thumb.wikimedia.org canonicalizadas a upload.wikimedia.org. Quedan 0 alias.
  • Björk y David Bowie con perfil y banner, y Pink Floyd, Radiohead y Kraftwerk con perfil. Las tres bandas entran con foto de escenario: en Commons no hay retrato de grupo de una banda. Se eligió mirando las fotos una a una, no por la puntuación del verificador, que ya había aceptado candidatas inservibles con 75.
  • Dos candidatas de Kraftwerk se descartaron por ser retratos de una persona concreta (Pfaffe, Ralf Hütter) por mucho que el título dijera "Kraftwerk".
  • Los avatares se verificaron a 80 px en producción, que es donde se commoda el recorte en círculo.

La ficha de release (F3/F7)

/releases/[id] usa ahora el mismo esqueleto que la ficha de pista: migas de pan, hero con portada 1:1, bloque de hechos y sidebar sticky. El tipo de release se resuelve en un único sitio (releaseTypeLabel), el icono de silencio es uno solo, y "Ver su catálogo" lleva al artista por ancla. AudioPlayer ya resolvía preview → YouTube → estado vacío, así que no hizo falta lógica nueva.

Modo oscuro

Cuatro sitios más con dark: a medias: sugerencias (admin y pública), aprobaciones y su filtro. La comprobación vive en tests/unit/dark-mode-pairing.test.ts.

La suite E2E: podía correr, pero no donde debía

PLAYWRIGHT_BASE_URL=https://epk-dashboard.vercel.app es lo que documentaba AGENTS.md, y no funcionaba: baseURL estaba fijo a localhost:3000 y webServer levantaba next dev siempre. Todos los specs se iban al dev server local en silencio, que además es la configuración que peor funciona (ABI de better-sqlite3, aserto nativo de teardown de V8).

Corregido eso, aparecieron las capas de debajo:

  • networkidle en 43 sitios de 6 specs: contra producción nunca se cumple —hay polling de notificaciones y conexiones vivas—, así que cada page.goto moría a los 60 s. Solo se cumplía en local.
  • Expectativas de hace fases: StemsPlayer se eliminó a propósito en P3.15 y el componente sigue en disco sin usarse; /track/<id> es un shim de 301 a /releases/<id> desde P8 y la mitad de los tests esperaban la URL vieja; / es la landing pública y no una redirección a /dashboard.
  • Specs sin sesión: multimedia.spec.ts no iniciaba sesión, y sin sesión /dashboard no pinta catálogo.
  • Datos del seed local: se buscaba el show "teatro" cuando producción tiene uno en Valencia; se pedía el venue "Teatro Municipal".
  • Un bug de test con efecto de bug de producto: auth-qa ejecutaba en el navegador una función que referenciaba ADMIN_EMAIL, un import de Node (ReferenceError: _credentials is not defined), y phase-n-register-delete rellenaba el formulario antes de la hidratación, así que React lo enviaba como formulario nativo y la página se recargaba. El síntoma era "La contraseña debe tener al menos 8 caracteres" con una contraseña de 12.

Uno sí era producto: approvals-qa pedía 403 cuando un artista intentaba auto-aprobar y recibía 200. Se comprobó en Turso que los releases seguían en draft: resolveSubmitStatus ignora el estado pedido y conserva el vigente, que es el diseño que sustituyó al 403 porque con las 83 filas en approved ningún artista podía guardar ningún release. El test, no el producto, estaba obsoleto.

Correcciones de producto

  • NotificationBell: showSkeleton no exigía que hubiera user, y sin sesión load() ni siquiera se llama, así que el estado de carga no podía resolverse nunca. Es una corrección defensiva: en /dashboard la campana no se pinta sin sesión, así que no se observó un fallo visible; lo que sí era un fallo era el test, que contaba .animate-pulse en el documento entero cuando esa clase la usan tres componentes.
  • qa-cleanup.ts: solo buscaba [QA] entre corchetes, pero los specs titulan sus releases QA P45 Draft <timestamp>. Informaba 0 tracks con dos filas QA ahí dentro.
  • 74 métricas de relleno a NULL, en dry-run y con su error documentado.
  • Vocabulario cerrado de shows: 6 estados elegibles más 2 derivados.

Rotación de credenciales (P7-P10)

Las semillas leen la contraseña del entorno en el punto exacto donde insertan. Se añadió una guía de rotación de credenciales, claves de redes y reescritura de main, con el hallazgo de por qué esa reescritura no funcionaba en Windows: un fichero Zone.Identifier commiteado cuyo : es inválido en NTFS.

Verificación

  • tsc limpio, lint sin warnings, 1259/1259 tests unitarios (67 ficheros), build correcto.
  • E2E contra producción: 135/138 en la última corrida completa de 45 min. El fallo restante era la campana de notificaciones, arreglado y verificado en solitario (3/3); la suite completa no se volvió a pasar después de ese último arreglo.
  • Producers datos: 12 artistas, 0 sin perfil, 0 sin banner, 0 usuarios QA y 0 releases QA tras la limpieza.

Catalogo real, buzon anonimo, una pagina por release y credenciales fuera del codigo

Choose a tag to compare

@AngBan2x AngBan2x released this 02 Oct 07:17

Catalogo real, credenciales fuera del codigo, buzon y una pagina por release

Cierra las diez olas de RC.33 y las cuatro subfases de la Fase P.
1146/1146 tests en 61 ficheros, tsc y lint limpios, build correcto.

Seguridad

  • PATCH /api/tracks/[id] no comprobaba ownership: cualquier usuario
    autenticado podia reescribir la pista de otro. Corregido con el mismo criterio
    de isArtistOwnerOfTrackName que usan el resto de rutas.
  • La credencial de una cuenta real estaba en 41 ficheros (16 scripts, 12 specs
    de Playwright y helpers). Ver P1 abajo.

Una sola pagina por release

/track/[id] pasa a ser un shim de 301. Un single es fila de tracks y
cabecera de release con el mismo id, asi que eran la misma pagina con dos
plantillas: SEO partido, metricas partidas y 535 lineas de ficha sin nada que la
enlace.

Las hijas redirigen al release padre. Una hija cuyo padre ya no existe
(release_id no es foreign key, y la cascada no siempre corre) se renderiza
con aviso, no 404: el contenido existe y es reproducible.

Correccion del bug de duracion: un single sin hijas mostraba 0:00 y
"0 pistas", porque la suma se hacia sobre las hijas. Ademas el filtro de "no hay
dato" no puede ser un isNaN, porque sumDurations(["00:00"]) parsea bien:
"00:00" es el relleno del seed, no un disco de cero segundos.

Fuera las hijas del sitemap, del prev/next y de las notificaciones.

Correo

RESEND_API_KEY esta configurada pero FROM_EMAIL no, porque no hay dominio
verificado. Sin dominio verificado Resend si envia, pero solo desde
onboarding@resend.dev y solo al titular: a otra direccion, 403 garantizado.

Por eso el envio es ahora inyectable y el correo tiene tres estados, no
dos: sent, failed y not_attempted. Sin FROM_EMAIL no hay llamada, y
contarlo como fallo haria que el panel dijera que se perdieron correos que nadie
iba a mandar.

Suite normal con transporte falso (verde siempre). Envio real solo con
RESEND_SMOKE=1, y en un script, no en un spec: gasta cuota real de Resend
(100/mes en el plan gratis) y un fallo de cuota no puede apagar el gate.

Buzon de sugerencias (anonimo, solo admin)

Cinco capas de anti-spam, todas en el servidor: honeypot, tiempo de formulario,
rate limit por IP (10/h), 1 por correo cada 24 h y topes de longitud.

Las cuatro capas de descarte devuelven exactamente el mismo 201: si
distinguieran, un bot solo tendria que recorrerlas en orden. La excepcion es el
429, porque quien comparte IP con media oficina y se bloquea sin explicacion no
tiene forma de saber que tiene que esperar.

El limite duro es el de correo, no el de IP: castiga mas al spam y no castiga
a quien comparte NAT. ip_hash es un HMAC con SESSION_SECRET, no un SHA: el
espacio IPv4 son 2^32 valores y un SHA suelto es la IP con tres pasos de mas.

El aviso al admin es best-effort y va despues de escribir: un 502 de Resend no
puede llevarse por delante el mensaje de alguien que solo queria reportar un
problema.

Catalogo

  • 29 de 74 videos oficiales con video_kind; las 14 coincidencias aproximadas se
    dejaron sin escribir
    a proposito.
  • 5 canales oficiales, incluidos los - Topic.
  • 3 banners de Wikimedia (Pink Floyd, Radiohead, Kraftwerk), revisados a ojo.
    Bowie y Bjork se quedan sin avatar: su composicion no funciona en circulo ni en
    wide, y la inicial con degradado es mejor que un recorte malo.
  • Last.fm real como fallback de metricas; un album hereda los playcounts de sus
    hijas.
  • Carrusel responsive 1/2/3/4.

El patron UC... sobre la descripcion del canal no dio ni uno de los 5: las
descripciones estan en espanol o vacias. Lo que funciono fue la allowlist
explicita.

Accesibilidad

ReleaseTrackList.tsx tenia el mismo aria-label="Reproducir" en todas las
filas, botones de 32 px (por debajo del minimo tactil de 36 px) y sin anillo de
foco. Ahora: 44 px, anillo visible, y etiqueta con numero y titulo.

P1 — las credenciales de test

Regla: contrasena sin default, correo con default solo si no es PII ni secreto.

Si el helper hubiera sido process.env.TEST_ARTIST_PASSWORD ?? "12345678",
funcionaria y dejaria el secreto en el repo: un cambio cosmetico con forma de
arreglo. Contrasenas a "". TEST_ADMIN_EMAIL conserva admin@epk.local
(dominio inventado). TEST_ARTIST_EMAIL tampoco: la unica cuenta de artista
conocida es de una persona real.

playwright.config.ts ahora carga .env.local, porque Playwright no hereda la
carga de Next
y sin eso los 12 specs leian undefined.

Verificacion

Cinco reversiones de arreglo, cinco tests rojos. Los de resolveTrackRoute leen el
SQLite local de verdad, no un doble: prueban que parseTrack expone
release_id. Si esa columna deja de mapearse, el redirect se apaga en silencio y
los tests lo detectan solos.

Pendientes que siguen abiertos

  • scripts/seed-admin.ts sigue con hashSync("admin123"). Es una decision de
    producto, no un olvido: si el admin de produccion se rota, el seed debe leer del
    entorno y fallar sin TEST_ADMIN_PASSWORD.
  • La credencial sigue en el historial de git. Rotarla y reescribir main son
    los dos pasos que quedan, y estan en docs/PLAN_FASE_P.md.
  • deleteUser no borra las sugerencias de ese usuario: decision de privacidad
    pendiente.
  • POST /api/export sigue siendo publico aunque la UI oculte dossier/rider.
  • lib/resend.ts queda huerfano tras P3.
  • 14 videos "parecidos" y los canales - Topic de Bowie y Bjork esperan revision.
  • Las pruebas E2E de Playwright no se ejecutaron: necesitan pnpm dev en :3000.

Edicion de releases sin perdida de datos, cola de album y catalogo con artwork real

Choose a tag to compare

@AngBan2x AngBan2x released this 01 Oct 06:48

name: RC.32 - contrato de edicion, audio, aprobaciones y catalogo real
tagPrefix: v4.0.0-rc
isPrerelease: true
createdAt: 2026-10-01T00:00:00Z

Que cambia

Tres cosas que el usuario puede notar, y cuatro que no, que son las que
importan.

El editor de releases dejo de perder datos y de auto-aprobar

Editar un release sin ser admin ya no funciona: el reenvio de status hacia
que un no-admin se auto-aprobara poniendolo a approved, y el backend lo
aceptaba. El estado de decision se ignora siempre en esa rama.

Editar un release siendo su dueno tampoco funcionaba: tres redirects por un
403. Y cuando se conseguia guardar, se perdian las pistas hijas nuevas y se
borraba el motivo de rechazo del admin. Todo arreglado y verificado.

La lista de correcciones encadenadas esta en el commit 4109794, porque el
quinto bug solo aparecio al arreglar el cuarto.

El reproductor de album

Un preview de 30 s arrastraba el final de capitulo del album y el scrubber
saltaba de pista a mitad. start_time/end_time son milisegundos dentro del
preview en un caso y offsets dentro del video en el otro, y se estaban
tratando como la misma magnitud.

El tracklist de OK Computer pasa a mostrar las 12 duraciones reales (antes
todos a 00:00), y el dashboard monta una cola de las 12 pistas en vez de
anunciar que no hay audio.

Catálogo con caratulas reales

9 filas tenian una foto de Unsplash. Revisadas a ojo, no eran "imágenes
genericas": eran retratos creibles de bandas que no tienen nada que ver, como
Brothers in Arms con un hombre mayor. Sustituidas por artwork real de Cover Art
Archive. unsplash: 0.

Lo que no va a aparecer, y por que

0 videos de YouTube. Se implemento la verificacion de canal oficial — el
canal tiene que enlazar en su descripcion al sitio del artista, con el nombre
coincidente — y los 5 artistas fallan. Radiohead viene con la descripcion
vacia. No se relajo la regla para tener algo que mostrar: eso seria
exactamente el bug que la regla evita.

0 previews de Deezer. Su CDN permite CORS, pero la URL va firmada y caduca
a los 900 s. Se comprobo guardando una y consultandola a la hora: 403. Persistir
esa URL es escribir un enlace muerto.

Correcciones internas

  • Paginacion del catalogo sin orden. getAllTracks() era SELECT * sin
    ORDER BY y encima pagina, asi que una pagina podia repetir o saltar filas.
    El fallo de un test lo destapo; la causa era que la base local de pruebas
    estaba contaminada.
  • Aprobacion en cascada. Aprobar un release deja las hijas en draft, y el
    contador del dashboard no cuadraba con las tarjetas. Ahora es transaccional.
  • 5 timestamps de album corregidos en produccion, con minutos de desviacion
    sembrados, y verificacion para que no vuelvan.
  • lib/artist-promotion.ts usaba el patron de escritura que las propias
    reglas del repo prohibian, y la llamada que creates el perfil no existia: el
    metodo lanzaba siempre. Los 7 artistas ya tienen perfil.
  • /api/sync deshabilitado (410). Era un INSERT OR REPLACE que omitia
    campos, incluido el motivo de rechazo del admin, y su mapeo no includia el
    estado de aprobacion. Ejecutarlo revertia el catalogo entero a draft.
  • Dos definiciones distintas de "hay Turso". Una leia el entorno al
    importar y otra al llamar, y con el entorno cargando tarde las rutas
    respondian 500. Esto habia costado tres despliegues.
  • El video embebido no se podia auditar. La ruta de YouTube no pedia el
    estado del video, y las politicas de la plataforma exigen comprobar la
    marca "hecho para niños" en cada embebido. Ahora el dato esta disponible.

Documentacion

El README afirmaba cosas que eran falsas: el numero de tests (110 -> 800), que
habia una tabla releases (no existe: un release es una fila de tracks con
release_id nulo), y el numero y las rutas protegidas. Anadidas las secciones
que faltaban, entre ellas la de credenciales.

Seguridad

README.md contenia una contrasena real. Sustituida por instrucciones. El
secreto sigue en el historial de git
, asi que hay que rotar la contrasena y
reescribir el historial: eso requiere una accion manual.

Gates

tsc limpio · lint sin avisos · 800/800 tests en 47 ficheros · verificacion
en produccion de /api/sync, /api/tracks, /api/youtube, la cola del
catalogo, el scrubber y los controles del reproductor.

Los tests necesitan --no-file-parallelism. En paralelo, el reciclado de workers
dispara una asertacion nativa de teardown de V8 y el proceso muere. No es un test
rojo: los 800 pasan. Esta documentado con el hallazgo.

S0, audio real, catalogo real y las 15 correcciones

Choose a tag to compare

@AngBan2x AngBan2x released this 30 Sep 19:03

Cierra 15 problemas reportados, 2 huecos estructurales y el flujo de promocion de suscriptor a artista. Ocho agentes en cuatro olas con propiedad exclusiva de ficheros; los commits los hizo el orquestador.

La fuga que no era una fuga

El sintoma era "Error al iniciar reproduccion - archivo no disponible" y la causa documentada era CORS. Medido con curl -I: el CDN de Apple si manda Access-Control-Allow-Origin: *. El arreglo de AI_LOG:1268 resolvia un problema inexistente. Lo que fallaba eran 6 URLs de preview caducadas (404).

Mismo patron que el Helvetica del PDF: un arreglo plausible que se llevaba el credito de un sintoma con otra causa. Lo desmonto un curl de una linea.

El parser que duraba mal

tracks.duration es texto con cinco convenciones conviviendo. El parser estaba duplicado en dos ficheros y destruia solo [m, s], asi que "1:02:03" sumaba 62 s en vez de 3723, y el guard isNaN no salta porque [1,2,3] no tiene NaN. No era hipotetico: lib/youtube.ts emite H:MM:SS y se escribe en duration.

El suscriptor no podia llegar a ser artista

AGENTS.md decia que si; el codigo devolvia 403. Pero aflojar el rol habria sido peor: userHasApprovedContent solo mira track_submissions, asi que un pending en tracks no dispara la promocion. El camino correcto ya existia en servidor (POST /api/submissions, sin chequeo de rol); faltaba la UI.

Seguridad (S0)

  • GET /api/releases?id=X sin user_id hacia SELECT * a secas: devolvia la fila cruda con admin_notes (motivo interno de rechazo) y sin filtro de estado. Un borrador era legible por cualquiera con el id.
  • GET /api/tracks con la misma fuga.
  • /api/dashboard devolvia shows no aprobados a anonimos.
  • ReleaseActions.tsx eran 33 lineas sin ningun chequeo de rol, renderizado siempre en pagina publica.
  • /releases/:id/edit solo comprobaba que el token HMAC fuera valido: cualquier cuenta autenticada entraba.
  • GET /api/dossiers sin sesion ni propiedad.

Datos

  • P3: de 6 a 63 pistas con audio real (previews de iTunes verificados con HTTP antes de escribir).
  • P1: de 0 a 71 filas con caratula real de album.
  • Los 9 padres tenian duration = "00:00": ahora la suma de sus hijas.
  • Corregidas erratas vivas en Turso: "Ashe to Ashes", las comillas literales de "Heroes", Vulnicura Strings marcado EP siendo album.
  • Arreglada una colision de datos en --cleanup que podia dejar padres con 10 hijas huerfanas.
  • release_id, disc_number y track_number al catalogo JSON, con test de forma (no habia ninguno).
  • La ficha tecnica descargaba el catalogo global de los 83 tracks de todos los artistas mientras el copy prometia "el catalogo completo del EPK". Resuelve el artista y el copy dice lo que es.

Descargas

5 opciones -> 3 filas x 3 formatos centrados. El backend ya soportaba las 9 combinaciones; faltaba UI. Container query en vez de breakpoint de viewport, porque vive en una celda de 304-347px.

UI

  • Las tres tarjetas del dashboard envolvian EPKCard en un <a>, conteniendo los botones de like y play. Era el defecto que la propia tarjeta evita y axe-check lo visitaba.
  • 4 releases por pagina con 0 titulos truncados en los 5 breakpoints. El 4 es el dato facil; los 0 truncados son el importante, porque son el motivo del commit bfdda30.
  • /admin/approvals: 16 controles llevaban focus:ring-*, que globals.css NO suprime, asi que cada boton verde, rojo o azul llevaba su anillo mas un contorno global rosa.

Audio

Cola completa con avance automatico en modo YouTube y HTML5. extractAppleMusicId no casaba con ninguna URL real de Apple Music.

Verificado en produccion

admin_notes ausente · duracion 8:21 en vez de 00:00 · 12 artistas (ya no 7) · sin "Editar Release" ni "Aprobado" para anonimo · /api/dossiers 401 · ficha tecnica limpia en 5 anchors, dos ramas · rejilla 4/4/3/2/1 columnas con 0 truncados y 0 interactivos anidados.

tsc limpio, lint sin avisos, 542/542 en 39 ficheros. Cada fix comprobado revirtiendolo.

Sin resolver, a proposito

  • 3 releases sin caratula real: el single no esta en iTunes y poner la del album habria repetido el fallo de Unsplash.
  • 20 pistas sin audio, con motivo documentado (desambiguacion que no se pudo confirmar).
  • lib/artist-promotion.ts:139 usa getDbWrite(): merece su propia ola.
  • lib/turso.ts sigue capturando process.env al importar; su isTursoConfigured() colisiona con el de @/lib/db con semantica distinta.
  • El MCP de Vercel quedo autenticado pero con scope vacio: list_teams devuelve [].

Releases que se ven bien: ficha de album con tracklist y ficha tecnica legible

Choose a tag to compare

@AngBan2x AngBan2x released this 30 Sep 04:44

Releases que se ven bien: ficha de album con tracklist y ficha tecnica legible

La ficha de /track/[id] estaba maquetada para una pista suelta y nunca se habia
mirado con un album, que es justo lo que D2 dejo en la base. Con una captura de
Kid A salieron tres sintomas y una sola causa de fondo.

La ficha de un album

Era la de una pista, aplicada a algo que no tiene letra, ni videoclip, ni
detalles de produccion, ni una cancion en Last.fm, ni audio propio. Seis
tarjetas vacias, un play que no hacia nada, y sin la lista de pistas, que es
justo lo que viene a buscar un periodista a un album.

  • Nueva lista de pistas por disco y numero, con duracion y enlace a cada ficha.
  • Letra, videoclip, enlaces y detalle de produccion se ocultan si no tienen
    nada que mostrar. Cada tarjeta se oculta por sus propios datos, no por ser un
    album: un padre con un enlace real lo seguiria viendo.
  • Sin reproductor, y sin metricas de Last.fm, que miden canciones.
  • Las metricas de streams/likes/listas se sustituyen por pistas, discos y
    duracion total. Antes ponia "0 Streams" en Kid A, que suena a que el album no
    se ha escuchado nunca.
  • El SEO decia "Álbum - 00:00 - 0 streams" en buscadores y previews.

Las 9 portadas estaban rotas

Los 9 releases apuntaban a example.com, el dominio reservado por la RFC que
nunca resuelve. De 19 candidatas de Unsplash, 18 resuelven y 1 da 404: ahora hay
un script que las comprueba y descarta las que no devuelven 200 + image/*, en
vez de asumir. Se verifican antes de sembrar y despues, leyendo la propia base.
Y las portadas muertas ahora caen a un marcador con la inicial, que antes solo
pasaba con la URL vacia.

La ficha tecnica estaba rota, y no por su cuenta

sm:grid-cols-2 responde al viewport, y en la barra lateral de /track/[id] el
componente mide 347px aunque el viewport sea de 1440. Se partia en 2 celdas de
146px, y la fila se come el ancho con badge, dos huecos y el boton: al texto le
quedaban 0px, asi que cada palabra salia en su propia linea. Ahora decide
columnas por container query, con el umbral puesto por medicion, no a ojo.

Verificacion

Un barrido recorre un album y una pista suelta en cinco anchos, en local y en
produccion: 10/10 en ambos. Y se comprobo que el barrido falla al revertir
el arreglo (7 fallos en 1024-1440px, que es donde la barra es estrecha). Un
check que siempre pasa no protege de nada.

Gates: tsc 0, lint 0, vitest 378/378, build OK.

Tambien en esta tanda: MCP de Vercel y Turso, con los endpoints confirmados en
la documentacion oficial y una nota en AGENTS.md sobre lo que sigue siendo
tarea del repo y no de un servidor.

Catalogo multi-track, PDF de prensa y UI legible

Choose a tag to compare

@AngBan2x AngBan2x released this 30 Sep 03:24

Catalogo multi-track, PDF de prensa y UI legible en la pagina de artista

Ola 3 del plan rc29 -> rc31, completa y verificada en produccion.
12 commits. 378 tests (32 ficheros), antes 249.

PDF de prensa (B5) — nuevo

Boton "Dossier completo en PDF" en Descargas para prensa: dossier, rider y
catalogo en un archivo, con portada, navegacion por secciones y pie con
numeracion de pagina.

  • lib/pdf/ genera el PDF con pdfkit, con Noto Serif incrustada en el bundle
    para que los acentos del castellano no se pierdan.
  • Acentos, eñes y simbolos verificados leyendo el texto del PDF, no a ojo.

Catalogos agrupados por release (C2) — el cambio de fondo

Un album padre y un single suelto eran indistinguibles: ambos son
release_id IS NULL y no habia ninguna columna que los separase. Ahora
getArtistCatalog los agrupa con una sola consulta, cada album se lee como
disco (portada + lista de pistas con numeracion por disco) en vez de 11
tarjetas indistinguibles, y la ficha de la pista enlaza a /track/ o
/releases/.

Semilla con nombres reales (D2)

5 artistas, 9 releases y 65 pistas hijas en Turso (74 EPKCards), que es lo que
hace que C2 se pueda ver por fin. Albums con su tracklist real: The Dark Side
of the Moon, The Wall (2 discos), OK Computer, Kid A, Vulnicura Strings, y
cuatro singles con lados B.

Interfaz

  • /artists/[id]: las tarjetas ya se leen (198px -> 294px medido) y el
    catalogo va a 3 columnas, porque con una lista de pistas dentro de cada celda
    los titulos se cortaban ("Another ...").
  • /shows: gradiente de marca, max-w-7xl, design system, stats en <dl>,
    animaciones respetando prefers-reduced-motion y portada de reserva para los
    shows sin flyer (los 2 de produccion no tienen).

Correcciones

  • El header del artista se salia 194px del resto de la pagina.
  • createTrack declaraba status y no lo escribia: todo lo que creaba
    quedaba en draft y era invisible en lo publico. Los 378 tests pasaban con
    la base vacia; lo destapo la semilla con datos de verdad.
  • Las fichas de pista se partian entre dos paginas del PDF, dejando los
    metadatos de una pista huerfanos al principio de la siguiente.
  • El PDF daba 500 en produccion: el constructor de PDFKit carga Helvetica con
    un require dinamico que no llega a la funcion de Vercel. Se evita sin
    depender de ficheros, y sin reglas de tracing a node_modules (que Vercel
    rechaza con pnpm por symlinks).
  • El --cleanup de la semilla borraba solo los padres y dejaba 63 hijas
    huerfanas.

Verificacion

Todo en produccion, no solo en local: el PDF se descarga desde la UI y abre
correcto, la pagina del artista muestra los albums agrupados, y 378 tests mas
typecheck, lint y build en verde.

Pendiente fuera de este alcance: WEBHOOK_SECRET (lo define el usuario) y el
email con Resend (falta FROM_EMAIL y hay cuota agotada).

Tranches 0-4: bugs de cuenta, seguridad, rendimiento, a11y, CI y SEO

Choose a tag to compare

@AngBan2x AngBan2x released this 28 Sep 06:30

Tranches 0-4: bugs que estaban rotos, seguridad, rendimiento, a11y, CI y SEO

Esta tanda no toca el envio de correo: responde a la pregunta de que mas se puede hacer o verificar sin depender de Resend. Salio de tres auditorias de solo lectura (cuenta y ciclo de vida, integraciones y claves, calidad/a11y/rendimiento).

Bugs que estaban rotos en produccion

  • Ajustes de cuenta al 500. app/api/user/settings usaba getDbWrite(), que es el writer SQLite local y efimero: en Vercel (Turso) cambiar el email, cambiar la contrasena y eliminar la cuenta fallaban, y ningun E2E lo detectaba porque todos borran via DELETE /api/auth/me. Reescrito sobre funciones Turso-aware nuevas en lib/db.ts.
  • La UI moria sobre el borrado de cuenta. Prometia 30 dias de gracia sin restauracion ni purga, y deleted_at no bloqueaba ni el login ni la sesion; ademas esas cuentas seguian recibiendo notificaciones y correos. Ahora el login responde 403 ACCOUNT_SUSPENDED, /api/auth/me responde 403 y borra la cookie, el fan-out filtra por deleted_at, y existe recuperacion con email y contrasena dentro de los 30 dias.
  • Contrasena minima unificada a 8, validada en backend (antes /account pedia 6 y el endpoint de cambio aceptaba "a").
  • Paginacion real en la consola de aprobaciones: los botones Anterior/Siguiente eran no-op con // TODO mientras mostraban "Pagina X de Y" al admin.

Seguridad

  • Cuatro endpoints sin autenticacion cerrados: POST /api/sync (ademas ejecutaba DDL), DELETE /api/shows/cleanup, POST /api/tracks/:id/streams y POST /api/webhooks/metrics.
  • Firma HMAC con WEBHOOK_SECRET en los webhooks, con sesion de admin como alternativa.
  • Notificaciones que se perdian en silencio: con 6 de 7 artistas sin cuenta vinculada, aprobar no avisaba a nadie y el admin recibia exito. Ahora la respuesta incluye el estado de la notificacion, el admin ve un aviso rojo cuando el artista no fue notificado, y la consola muestra un chip "Sin cuenta vinculada" y el contador de casos afectados.

Rendimiento

  • N+1 eliminado en /api/dashboard: era un await getLikeCount(track.id) por track en serie, mas cuatro consultas secuenciales. Con Promise.all y una sola consulta de likes, el dashboard autenticado baja a ~150 ms. Era la causa del testTimeout: 30 s.
  • Optimizacion de imagenes activada: los unoptimized eran un workaround porque next.config.js no declaraba remotePatterns. Ahora hay hosts, AVIF/WebP, y lib/image-config.ts decide por URL si una imagen se puede optimizar (las de host desconocido o blob: siguen sin optimizar). Verificado: 9 imagenes por /_next/image y 0 rotas.
  • googleapis eliminado (203 MB) sustituido por fetch contra la misma API y verificado con datos reales. Fuera tambien date-fns, @aws-sdk/client-s3, @types/bcryptjs, @eslint/js y lib/youtube-stats.ts.
  • Visualizador diferido con next/dynamic y waterfall del servidor de /track/[id] resuelto con Promise.all.

Accesibilidad y calidad

  • 23 controles de DossierEditor asociados a su label (el unico incumplimiento duro que quedaba). Verificado: 0 controles sin etiqueta.
  • Anillo de foco global en globals.css para todo control interactivo, con excepcion de los que ya pintan su anillo. Cubre los ~125 botones que no declaraban focus-visible.
  • ui/Button cumple 44px en sus tres tamanos.
  • 0 warnings de ESLint y bug latente del AudioPlayer corregido (la fuente de audio nunca se reseteaba al cambiar de track).
  • Preferencias que si se respetan: new_release_alerts y show_alerts se leen de verdad en el fan-out, con tests.

Verificacion, CI y SEO

  • CI: ahora usa Node 24 (el ABI del binario de better-sqlite3 lo exige) y ejecuta Playwright, que antes no bloqueaba nada.
  • SEO: sitemap.xml con 22 URLs, robots.txt, manifest, metadataBase, OpenGraph y Twitter globales, y generateMetadata + JSON-LD MusicGroup en la pagina de artista.
  • Contraste AA: axe da 0 violaciones criticas o serias en /dashboard, /shows y /artists.
  • Lighthouse contra el build de produccion: /shows 95 de performance y SEO 100; /dashboard 60 de performance y SEO 100, con el LCP bajado de 5.1 s a 4.6 s marcando la portada del primer card como prioritaria. El dashboard sigue siendo la ruta mas pesada y partirla en Server Components queda como decision posterior.
  • E2E en produccion: las 5 suites que faltaban (subscriptions 5/5, notifications 3/3, fanout 1/1, search 2/2, broadcast 1/1) mas matriz 50/50 y 249/249 unit, tsc 0 y build OK.
  • Higiene de datos: 9 tracks, 2 shows, 2 usuarios reales, 0 filas QA. En una tanda previa de esta sesion se eliminaron 13 usuarios de prueba que llevaban semanas en produccion.

Lecciones

  1. Un -replace de PowerShell que no casa devuelve $null y puede escribir archivos a 0 bytes: se perdieron 5 componentes y se recuperaron con git checkout. No escribir codigo fuente con regex en PowerShell.
  2. Una regla CSS dentro de @layer base no llegaba al CSS servido: el anillo de foco global no aparecia hasta sacarla del layer. Comprobar en el navegador que la regla existe en document.styleSheets antes de darla por buena.
  3. Los E2E que verifican el registro fallan de forma esperada al cambiar el modelo de roles: hay que actualizar el contrato del test, no relajar la asercion.

Pendiente de decision (no bloquea, nada de esto requiere Resend)

  1. BLOB_READ_WRITE_TOKEN ausente: subir imagenes da 500 en produccion. SESSION_SECRET tampoco esta en .env.local ni en .env.example.
  2. Aplicar el seed P5.2 del catalogo influyente en produccion.
  3. Backfill de artists.user_id: 6 de 7 artistas siguen sin dueno y no reciben el aviso de aprobacion.
  4. Rate limit distribuido (Upstash/KV): hoy es un Map en memoria, evitable en serverless.
  5. Refactor de /dashboard a Server Components si se quiere atacar el TBT de ~1.1 s.
  6. Export de datos personales / RGPD. La recuperacion de contrasena sigue fuera de alcance porque si depende de correo.

Sigue siendo prerelease: el proyecto no esta terminado.

P5/P6: UI, roles suscriptor a artista, seguridad y a11y

Choose a tag to compare

@AngBan2x AngBan2x released this 28 Sep 02:27

Cierra la tanda P5/P6 reportada con capturas: 13 problemas de UX/navegación/roles más 8 hallazgos de seguridad y 7 de accesibilidad.

UI y navegación

  • Animación real en la expansión de shows, tarjetas más ricas, grid sin anidar, filtros con label asociado y select legible en modo oscuro
  • Link Artistas visible para usuarios autenticados (antes un artista no podía ver otros artistas)
  • Menú del avatar cierra con Escape y clic fuera, sin botón X
  • Carrusel Embla solo en el catálogo del dashboard, navegable con click y teclado
  • Disclaimer de pagos replicado en las tarjetas públicas

Descargas

  • Exportador unificado server-side: dossier, rider, catálogo JSON y catálogo imprimible
  • Se elimina la sección duplicada y se corrige el dato stale (ya no usa el estado viejo)

Roles y aprobaciones

  • El registro público crea solo suscriptores; ya no se puede elegir artista
  • La promoción a artista ocurre al aprobar el primer release o show, con invalidación de sesión
  • submissions = portal del artista; approvals = consola admin con escritor único y motivo mínimo de 10 caracteres validado en backend
  • El suscriptor puede crear contenido y siempre queda en pendiente

Seguridad

  • El dashboard y artistas públicos ya no filtran borradores ni user_id
  • Rate limit en las escrituras más abusables (shows, releases, subscriptions, likes, upload)
  • Corregido un 500 en GET /api/releases?user_id
  • Upload validado por extensión, magic bytes y MIME real
  • Las escrituras de admin ahora notifican y hacen fan-out
  • El token de sesión siempre expira (antes con "recordarme" no expiraba nunca)

Visualizador y a11y

  • FFT 1024 con bandas logarítmicas entre 30 Hz y 18 kHz, ganancia, curva, decay y peak-hold; respeta prefers-reduced-motion
  • LoginModal y modales de aprobación con roles ARIA, Escape, foco atrapado y errores anunciados
  • Modo claro corregido en el reproductor de video, alt descriptivo y fallback en imágenes

Verificación

  • 225/225 unit, tsc 0, build OK
  • E2E en producción: approvals 4/4, subscriber 2/2, shows-transitions 2/2
  • Matriz de producción 50/50 y funcional de exportación 9/9 con los cuatro archivos reales
  • Auditoría de seguridad sin riesgos altos ni críticos de código
  • Datos limpios: 13 usuarios de prueba de fases anteriores eliminados

Pendiente de decisión (no bloquea)

  • FROM_EMAIL sin configurar y pressplay.eu.org pendiente de aprobación: no sale correo a terceros
  • El seed de catálogo influyente (P5.2) está implementado pero no se aplicó en producción
  • 6 de 7 artistas siguen sin user_id, por lo que no reciben notificación de aprobación

Sigue siendo prerelease: el proyecto todavía no está terminado.