Bump the frontend-tooling group across 1 directory with 3 updates - #281
Open
dependabot[bot] wants to merge 59 commits into
Open
Bump the frontend-tooling group across 1 directory with 3 updates#281dependabot[bot] wants to merge 59 commits into
dependabot[bot] wants to merge 59 commits into
Conversation
Fuera 28 000 lineas que no usaba nadie: dos copias sin importar de la hoja de estilos de administracion (735 KB), un prefetch de teselas que bajaba a una cache que el service worker no consulta jamas, diez scripts de comprobacion de un solo uso y los informes de versiones que ya no existen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Habia dos verdades sobre en que nodo esta un jugador -la del movil, que avanza sin cobertura, y la del servidor, que solo se entera al sincronizar- y nadie las reconciliaba. De ahi salen los cuatro sintomas de la ruta de campo: nodo completado que manda a repetirlo, marcador que no sube, progreso que aparece y desaparece, y el salto del nodo 5 al 7. - /api/advance distingue por fin ir por DETRAS (un eco de algo ya hecho, se contesta ok) de ir por DELANTE (al movil le faltan avances por subir). Antes se contestaba ok en los dos casos, el movil lo daba por bueno -solo mira status- y el nodo no quedaba anotado en ninguna parte. - El nivel no baja por una respuesta de red. Solo al abrir la aplicacion con la cola vacia, o cuando llega un reseteo desde administracion, que es la unica vez que el servidor puede mandar un nivel mas bajo y tener razon. - Abrir la app ya no pisa el progreso ganado en modo avion. - Los eventos que el servidor rechaza de forma definitiva salen de la cola, y los rechazos se cuentan aparte de los intentos: quedarse sin red no significa que el evento este mal. - Las dos colas dejan de correr a la vez contra el mismo endpoint. Version 4.0.0 y changelog reescrito. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Medido en la Raspberry contra la mision real, antes y despues: paquete offline tras refrescar 1 de 10 nodos con juego -> 10 de 10 arranque con el mapa guardado 22 s de pantalla de carga -> 0,1 s refresco de fondo 205 KB cada 30 s -> 28 KB /api/config 135 KB cada 30 s -> 1,4 KB - El refresco que corre al superar un nodo pedia la partida SIN el paquete offline, y esa respuesta se guardaba como mision del movil: completar un nodo con cobertura dejaba sin juego a todos los siguientes. Sin red, el nodo no tenia ni configuracion del minijuego -la foto del mosaico del botanico vive ahi- ni codigo que aceptar. - /api/game manda una huella del contenido, asi que el movil solo vuelve a bajarse los nodos si cambiaron. - El prefetch del mapa pedia sus 1500 teselas en cada apertura sin mirar si ya estaban. No llegaban a la red -las servia el service worker-, asi que lo unico que hacia el jugador era esperar. - De los 135 KB de /api/config, 134 KB eran las fotos de los catorce jugadores en base64, en un endpoint publico. Ahora van por una URL cacheable y los perfiles completos solo salen por el panel. - Se activa la compresion, que no habia ninguna. - Y cuatro rutas del panel estaban escritas en main.py Y en los routers. Responden las del router, asi que las de main.py eran codigo muerto con toda la pinta de estar vivo: 203 lineas fuera y un test que lo impide. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Las pegatinas de la primera ruta no las lee ningun escaner, y no por mala suerte: se imprimieron con el logo de SAGA encima del codigo. Medido sobre un raster perfecto -el caso mas facil que existe-, un logo que cubra el 30 % del ancho ya deja SAGA_01 y SAGA_02 sin leerse; una foto en el monte, movida y a contraluz, falla mucho antes. Para salvarlas la aplicacion cargaba OpenCV, 11 MB de WebAssembly, y reconocia las pegatinas comparando matrices de modulos en ocho orientaciones. - shared/qrCard.tsx es la unica definicion de tarjeta. Habia cuatro sitios generando el mismo codigo con ajustes distintos. - Nada encima del codigo, negro sobre blanco, zona de silencio de 4 modulos (el generador trae 0) y tamano fisico en milimetros al imprimir. - offline/qrReader.ts: BarcodeDetector nativo donde lo hay, jsQR para el resto. Los dos sin red. - Fuera opencv.js, su worker, los tres modulos de reconocimiento y la puerta del Dockerfile que impedia construir desde un clon limpio. - requirements.txt y package-lock.json fijan por fin las versiones: el contenedor traia FastAPI 0.141.1 y el portatil la 0.138.0. Obliga a reimprimir y volver a pegar SAGA_01 y SAGA_02. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Los bytes ya estaban bien; lo que sobraba eran peticiones. Un jugador le metia a la Raspberry 1920 por hora, y 1440 eran el latido y la tabla de equipo, cada 5 segundos por separado, preguntandose lo mismo. Con trece jugadores, casi 7 peticiones por segundo sostenidas durante tres horas. - El latido devuelve la tabla de equipo, y el movil deja de pedirla aparte. - Con la pantalla apagada no se late ni se piden fotos: en una ruta de tres horas el movil esta casi siempre en el bolsillo. - La configuracion de la mision se guarda cinco minutos en vez de pedirse cada treinta segundos. - Sin cobertura se pinta el ultimo equipo conocido en vez de vaciar el mapa. Medido con la pantalla apagada: 2 peticiones en 54 segundos. Ademas, scripts/crear-mision.sh: cada ruta es un contenedor con su propio directorio de datos. Probado con dos a la vez sin verse la una a la otra, ~60 MB cada una. El aislamiento es por construccion, no por codigo que haya que acertar. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Comprobado contra sagagia.es sin sesion, sin contrasena y sin saber nada: GET /api/field-proofs devolvia las 17 fotos de la ruta con el NOMBRE de quien la hizo, las COORDENADAS exactas y el nodo, y la imagen se descargaba entera desde su URL. El zip con todas, tambien. - Los tres endpoints piden ahora pase de jugador de la mision o sesion de administracion, y la imagen pasa de Cache-Control public a private: con public, Cloudflare la guardaba en su borde y servia sin preguntar. - POST /api/admin/datos-personales cuenta lo que hay y, con confirmacion, borra las fotos -la fila entera, que es donde viven el nombre y las coordenadas- y los rastros GPS. La mision y los tiempos no se tocan. - scripts/borrar-releases.ps1 para las releases que quedan en borrador, que desde fuera no se ven. El permiso que firma un padre cubre hacer la foto, no guardarla dos anos. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Ya mordio una vez: el servidor mandaba level y duplicate en la respuesta del avance, dos campos que decidian si un nodo contaba, y el tipo del movil no los declaraba. Como para TypeScript no existian, nadie los leia y los nodos completados se perdian en silencio. Lo suyo seria generar los tipos del movil a partir del esquema del servidor. No se puede todavia: ningun endpoint declara response_model, asi que el OpenAPI trae las rutas y ninguna forma de respuesta. Ponerlos a todos es un trabajo aparte y grande. Mientras tanto esto llama a los endpoints de verdad y compara los campos que llegan con los declarados. Encontro deriva a la primera, sin buscarla: - /api/advance devolvia level_before en la respuesta "behind" y el tipo no lo declaraba. Un fallo mio de hace una hora. - /api/team devuelve total_nodes y finished_count y el tipo no los tenia. Y finished_count NO LO LEE NADIE: esta para que la pantalla final espere al grupo entero, pero esa parte no llego a conectarse. Queda anotado en el tipo: o se usa, o se quita del servidor. Solo mira el primer nivel; un campo anidado que cambie por dentro no lo caza. Ademas: fuera el flujo release.yml, que era el que iba dejando releases en borrador -nueve, invisibles desde fuera-, y en su lugar un flujo manual que las borra usando el token de Actions, sin necesidad de crear uno personal. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Primera tajada del punto 2. Se empieza por estas porque son las mas independientes: no tocan la partida, ni la sesion, ni la base de datos. Solo buscan un fichero en dos sitios y lo devuelven. De paso, otra ruta fantasma: habia DOS manejadores de /favicon.ico, los dos con el mismo nombre de funcion. Solo respondia el primero -el que registra la ruta gana- y el segundo era codigo muerto con toda la pinta de estar vivo. Ya son cinco duplicadas encontradas en este fichero. main.py: 2270 -> 2161 lineas, 32 -> 20 rutas. Comprobado por COMPORTAMIENTO, no por introspeccion: scripts/ inventario-de-rutas.py pide las 49 rutas del motor y apunta que contesta cada una. Antes y despues del cambio, identicas. La introspeccion no vale aqui: con la version de FastAPI de algunas maquinas, lo que anade include_router no aparece en app.routes aunque el servidor lo sirva. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… y el service worker Segunda tajada del punto 2. Estas ya no son solo ficheros -leen la configuracion de la mision y las fichas de jugador- pero siguen sin tocar la partida de nadie: ninguna cambia el estado del juego. main.py: 2161 -> 2012 lineas, 20 -> 14 rutas. Desde que empezo el punto 2, 2470 -> 2012 y 32 -> 14. Comprobado otra vez por comportamiento con scripts/inventario-de-rutas.py: las 49 rutas contestan exactamente igual que antes de mover nada. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Tercera tajada. Todas sirven la misma pagina -la aplicacion de React- y lo
unico que cambia entre ellas es que sesion dejan puesta al entregarla.
/player/{name} es la unica que hace algo: entrega la pagina y, si ese
jugador existe en la mision, le deja su pase en una cookie. Es el unico
sitio donde se reparte ese pase.
Con esto main.py se queda SIN NINGUNA RUTA: 39 al empezar, 0 ahora. Lo que
queda son 1980 lineas de ayudantes y el ensamblado de la aplicacion.
Las 49 rutas contestan exactamente igual que antes de mover nada, medido
las tres veces con scripts/inventario-de-rutas.py.
Queda la parte gorda del punto 2: los routers siguen haciendo import main
dentro de sus funciones para esquivar el import circular, 77 simbolos de
superficie. Eso va por grupos: configuracion, almacenamiento, sesiones,
jugadores.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
minigames/registry.ts declaraba cuatro juegos cuyo componente no jugaba nada: pintaba un recuadro en INGLES que decia "This minigame is resolved through the family-native runtime host". Un mensaje de desarrollador. Y era alcanzable. InteractionSheet elegia entre tres ramas: el motor de familias, ese registro, o el aviso de "nodo sin juego configurado". La rama del relleno se dispara cuando el nodo tiene tipo pero no fuente de minijuego -resolvedRuntime nulo y el tipo entre los cuatro-, y entonces al jugador le sale ese texto en medio del monte en vez de un juego. Fuera el registro, fuera MinigameHost -que solo existia para pintarlo- y fuera la rama. Si no hay juego que resolver, ahora sale el aviso honesto que ya existia. Y de paso minigames/types.ts, que se quedaba sin nadie. Queda un solo registro: core/resolver.ts con FamilyRuntimeHost. Las 49 rutas siguen contestando igual. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Lo unico que pedia vertical era orientation: portrait en el manifiesto, y eso solo lo respeta Android con la aplicacion instalada desde la pantalla de inicio. En iOS no existe, y en un navegador normal tampoco: quien jugara con el movil desbloqueado veia los minijuegos girados y descuadrados, porque todos estan pensados para vertical -el laberinto de inclinacion mide el eje corto y el mosaico reparte la foto en columnas-. Se hacen las dos cosas que se pueden hacer: pedir el bloqueo por API donde exista, y donde no, tapar la pantalla y pedir que giren el movil. El aviso va en ScreenFrame, asi que tapa tambien la camara, los minijuegos y las hojas, que es donde el horizontal mas descuadra. No sale en pantallas grandes: un portatil en horizontal es un uso legitimo. Y se mira tambien el lado corto, porque con el teclado abierto la altura se desploma y la ventana parece apaisada sin que nadie haya girado nada. Se escucha por cuatro sitios -resize, orientationchange, screen.orientation y matchMedia- porque ninguno vale en todos los moviles. Comprobado que hay entornos donde la ventana cambia de tamano y no se emite resize: si solo se escuchara ese, el aviso se quedaria puesto encima del juego, que es peor que no tenerlo. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
El nombre de la cache del shell llevaba la version dentro y el servidor se la reescribia en cada despliegue. La intencion era buena -que el jugador reciba lo nuevo- pero el efecto era el contrario: al activarse, el service worker borraba la cache anterior en el mismo instante en que estrenaba la nueva, vacia. Con red no se nota, porque se vuelve a bajar todo. Sin red si: quien abra la aplicacion en el aparcamiento el dia despues de un despliegue se queda literalmente sin nada, con la anterior ya borrada y la nueva sin llenar. En un juego que existe para funcionar sin cobertura, ese es el peor fallo posible, y llevaba ahi todo este tiempo. El nombre es fijo ahora. Los ficheros de la aplicacion llevan su hash en la URL, asi que dos versiones conviven en la misma cache sin pisarse y no hace falta vaciarla para estrenar. Lo que quede de las caches viejas se copia antes de borrarlo, y si la copia falla no se borra nada: mejor gastar unos megas de mas que dejar a alguien sin juego en el monte. Comprobado en produccion: la cache versionada desaparecio, la fija conserva las 23 entradas y las 1183 teselas siguen intactas. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Habia dos almacenes vaciandose contra el mismo endpoint: los eventos fisicos -escaneos, codigos a mano, objetos recogidos- en localStorage, y los nodos completados en IndexedDB. Dos almacenes son dos verdades sobre lo que falta por subir, y de ahi salieron los nodos que se repetian. Ahora todo se encola en IndexedDB. Cambios que trae: - ORDEN. La clave de IndexedDB empieza por usuario y TIPO antes que por la fecha. Mientras la cola solo llevaba nodos completados daba igual, pero al meter tambien escaneos y mochila un evento posterior de otro tipo se colaba delante. Se ordena por fecha, que es como paso de verdad. - CANDADO. La sincronizacion la llaman el ciclo de refresco, el reintento del avance y la vuelta de la cobertura, a veces los tres a la vez. Sin candado se mandaban colas solapadas y el nivel que salia dependia de cual contestase antes. - ESPERA CRECIENTE. Con cobertura intermitente, reintentar cada ciclo contra una red que no va gasta bateria y no consigue nada. Tras fallar se espera, doblando hasta un minuto, y se reinicia en cuanto el servidor contesta. - MIGRACION. Lo que un jugador tuviera en la cola vieja se pasa a la nueva antes de la primera sincronizacion. Sin eso, quien estuviera a mitad de ruta con escaneos sin subir los perderia al actualizar. localFirst.ts pasa de 599 a 475 lineas: se queda con el estado de sincronizacion que se pinta, la partida guardada y las fotos.⚠️ La migracion no se ha podido comprobar de punta a punta en el navegador: el ciclo se salta con la pestana oculta -es la optimizacion de bateria- y el panel de pruebas no se puede mantener visible. Queda cubierta por tests/test_unha_soa_cola.py, que comprueba que se muda ANTES de sincronizar. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
En el monte cada peticion que no hace falta es bateria, es red ocupada y es una oportunidad mas de que algo se quede a medias. En la Raspberry, cada lectura de mas se multiplica por trece moviles. - La mochila se subia entera en CADA vuelta del ciclo, cambiara o no. Una mochila cambia al recoger o al forjar, un punado de veces en toda la ruta; el resto eran 120 peticiones por hora y por movil para contarle al servidor lo que ya sabia. Ahora se compara con lo ultimo subido. Se fuerza al validar un nodo que exige objeto, porque ahi el servidor valida con esa mochila. Y solo se da por subida si el servidor contesta que si: con un fallo se reintenta, que es justo lo que hace falta al forjar sin cobertura. - El latido leia la tabla ENTERA de posiciones para tirar el resultado. Trece moviles cada cinco segundos son 9360 lecturas completas por hora para nada. - Y los fallos del servidor dejan de disfrazarse de falta de cobertura. Un 500, un pase caducado y el monte sin antena caian todos en el mismo sitio y el jugador leia siempre "sin conexion". Asi se escondio un error de backend durante una partida entera: en el movil todo iba bien y en el servidor no existia. El nodo se sigue guardando igual -eso es lo que deja seguir jugando- pero ahora se dice cual de las tres cosas paso, y queda en la consola. Medido: 2,0 -> 1,9 MB por jugador y hora. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Constantes de directorios, el servidor de estaticos, la pagina del jugador y la etiqueta de version vivian sueltos entre la logica del juego. No tienen nada que ver con la partida: son rutas de ficheros. Los usan los tres routers que sirven paginas y estaticos. main.py: 1980 -> 1890 lineas. Quedan reexportados desde aqui porque los routers todavia los piden por main mientras se rompe el ciclo, pero ya son cinco simbolos menos de los 77 de superficie. De paso, la pagina de "falta compilar el frontend" pasa a estar en castellano como el resto. Las 49 rutas contestan igual. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Tres formas de quedarse encallado, que en el monte no se distinguen de "la aplicacion no va": - GPS. La precision alli suele ser de 30 a 80 metros y a veces no llega ninguna posicion: bajo pinar, en una vaguada, con el movil frio. El nodo se quedaba en "LOCALIZANDO..." para siempre y no habia forma de entrar. Es un candidato claro a lo de "el botanico no me dejaba entrar". Ahora, pasados 45 segundos sin posicion, el nodo se abre igual: el GPS es la puerta, pero la prueba de verdad es el reto de dentro, y ese no se supera desde el sofa. Dejar a alguien plantado es peor que dejarle entrar un poco antes de tiempo. - Configuracion. Un nodo la trae en dos sitios, config y minigame.config, y no llevan lo mismo. El boton de abrir miraba una y la comprobacion del envio la otra, asi que podian discrepar sobre el mismo nodo. Ahora hay una sola funcion que decide cual gana, y manda la del minijuego, que es la que se le entrega al jugador. - El codigo de respaldo sin nodo activo. Se apuntaba y el mensaje decia que se sincronizaria al volver la red. Es mentira, y de las caras: el servidor solo hace progresar con un nodo completado y ahi no se sabe cual seria, asi que el jugador se quedaba tranquilo esperando algo que no iba a pasar en vez de volver a intentarlo. Ahora dice lo que hay. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
No habia nada. Nadie miraba si el jugador se iba de la aplicacion, buscaba la respuesta y volvia. Con un mosaico o un Simon delante, salir y volver era gratis. Lo que de verdad quita la ventaja no es el tiempo: es REINICIAR el reto. Al volver, React rearma el juego entero -patron nuevo, piezas revueltas otra vez- y lo que hubiera memorizado fuera ya no vale. Los 30 segundos por salida son el recargo, y van como penalizacion, no dentro del reloj del nodo: el servidor guarda cada cosa en su sitio y meterlo dentro lo contaria dos veces. Se avisa en pantalla, porque un patron que se reinicia solo y sin explicacion parece un fallo de la aplicacion. Y queda constancia como evento aparte: en el marcador solo se ve tiempo de mas, y eso no distingue a quien tardo de quien salio cuatro veces. Detalles que importan: - Solo se vigila mientras hay un minijuego delante. En un coleccionable o en un nodo de camara no hay nada que memorizar fuera, y penalizar por mirar el mapa seria castigar el uso normal. - Las salidas de menos de segundo y medio no cuentan: bajar la persiana de notificaciones o que se apague la pantalla no es hacer trampa. - Se escucha tambien pagehide, que es lo que dispara iOS al cambiar de aplicacion, donde visibilitychange no siempre llega.⚠️ Esto NO distingue una trampa de una llamada entrante, y nadie puede: el navegador solo dice que la pagina dejo de estar visible. Por eso la penalizacion es moderada en vez de intentar adivinar intenciones. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
El servidor lo mandaba en la tabla de equipo desde hacia tiempo. La pantalla final NO lo usaba: lo cuenta ella misma a partir de esa misma lista de perfiles. Dos formas de contar lo mismo acaban dando numeros distintos el dia que una de las dos cambia, y ademas era un campo que el tipo del movil ni declaraba: justo el patron que perdio nodos completados. Lo encontro el test de contrato el dia que se escribio. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…elicada Cuarta y quinta tajada, mas un fallo mio que salio al probar en Android. NODOS -> backend/app/runtime/mision.py. Leerlos, validarlos y prepararlos para el jugador es el corazon del juego y lo usan los routers en seis sitios; quitarlo de en medio del resto es de lo que mas despeja. MOCHILA -> backend/app/runtime/mochila.py. Y aqui lo importante no es mover: es que esa cuenta estaba enterrada, sin una sola prueba, y decide si el nodo final deja entrar o no. La mochila NO se guarda como una lista: se reconstruye sumando los eventos -recogido, gastado- y contrastandolos con la copia que sube el movil. Ninguna fuente sobra: los eventos cubren lo que se recoge en un nodo, la copia cubre lo que se forja en la mesa de trabajo, que pasa entero en el telefono y no deja evento. Y se toma el MAYOR de los dos, no la suma, porque un objeto que aparece en ambos es el mismo objeto: sumarlos abriria nodos que no tocan. Diez pruebas nuevas fijan cada caso. EL FALLO MIO: al meter el contador de espera del GPS puse un useRef DESPUES de los returns tempranos de carga y error. Eso cambia el numero de hooks entre renders y React tira la aplicacion entera: error 310, y el jugador ve la pantalla de fallo en vez del juego. Estuvo desplegado un rato. Lo encontro la prueba en Android emulado, no la de escritorio. El hook sube con los demas y queda escrito en el codigo por que. main.py: 1890 -> 1738 lineas. Tests: 217 -> 227. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
PlayerApp.tsx hace a la vez el GPS, la sincronizacion, la camara, la interfaz y el arranque de los minijuegos. En un fichero asi no se ve donde acaban los hooks, y eso ya costo una caida en produccion hoy mismo: un useRef despues de un return temprano tiro la aplicacion entera. Primera tajada: las fotos. Eran cuatro estados, un ciclo de 15 segundos, un escuchador de eventos y tres funciones repartidos por el fichero. Juntos se lee de un vistazo lo que hacen, que no es obvio: llevan DOS listas. Las del servidor y las que van de camino. Se pintan juntas porque sin las pendientes el jugador hace una foto en el monte, no la ve por ningun lado hasta que vuelve la cobertura y da por hecho que ha fallado. Y en cuanto una sube hay que quitarla de pendientes en el acto o queda contada dos veces. Comprobado en Android emulado: arranca, juega, sin errores, y las 49 rutas contestan igual. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Estaban repartidas por PlayerApp.tsx como cuentas sueltas entre la interfaz y los ciclos de red, sin una sola prueba. Son las que deciden quien puede abrir un nodo, y en campo se equivocan de las dos maneras: si son estrictas, quien esta encima del nodo no entra; si son laxas, se abre desde el coche. El dato que manda todo esto, medido en Cotorredondo: bajo arbolado la precision anda por los 30-80 metros. Cualquier regla pensada para una calle de ciudad falla alli. De ahi salen los tres numeros que ahora estan fijados por pruebas: - El limite de precision no baja de 60 m y sube con el radio del nodo. Un limite fijo de 45 m descartaba la posicion ENTERA y el HUD se quedaba sin distancia o congelado en el ultimo valor bueno. - El margen que se perdona al comprobar el radio tiene tope de 35 m: sin descontarlo no se entra nunca con 60 m de error, y sin tope se abriria un nodo desde doscientos metros. - Sin dato de precision se acepta la posicion. El navegador no siempre lo da, y descartar por no saber deja al jugador sin posicion cuando si la tiene. Ademas se distingue una posicion VIEJA de una IMPRECISA: la primera sigue sirviendo para pintar el mapa, la segunda es que el chip esta buscando. Decirle "sin GPS" a alguien que esta bajo un pinar es mentirle. La regla del margen estaba escrita tres veces en PlayerApp. Ahora es una, y un test falla si vuelve a aparecer suelta. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
docs/continuar.md: donde esta el proyecto, como se despliega, las trampas conocidas -el import circular, los hooks, la fuente de verdad, la config en dos sitios- y lo que falta por orden. Escrito para que no haga falta nada mas. docs/auditar.md: como buscar los fallos que quedan. No es una lista de sitios: son los siete patrones que YA han mordido en este proyecto, para buscarlos donde todavia no se ha mirado. Con lo que mas importa arriba: medir antes de afirmar, y decir lo que no se puede demostrar. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Tercera tajada. Dos estados, dos funciones de peticion y un efecto de comprobacion, sueltos entre la interfaz y los ciclos de red. Juntos se lee de un vistazo la decision que costo una tarde: los dos permisos van POR SEPARADO. Iban juntos, se pedian de golpe y solo se daban por buenos si los dos salian bien, asi que conceder el movimiento no quitaba la fila porque la camara habia fallado, y encima saltaba un aviso diciendo que faltaba la camara cuando lo que acababas de conceder era el movimiento. Y se ve tambien lo otro: el permiso de movimiento solo existe en iOS. En Android no hay nada que pedir, asi que se da por concedido y se avisa al mapa; sin eso la brujula no arrancaba nunca en Android, esperando un permiso que ese sistema no pide. PlayerApp: 3154 -> 3086 lineas. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
handleSubmitCode eran 341 lineas en medio de PlayerApp.tsx: decide si un nodo cuenta, y no tenia una sola prueba porque no habia por donde cogerla. Ahora vive en player/avance/. decisiones.ts son cuentas y textos, sin red ni React: cuanto suma el reloj, que objeto entrega un nodo, si la culpa es del servidor o del monte. enviarCodigo.ts es el orden de la operacion, con lo de React inyectado en un entorno. En PlayerApp quedan 50 lineas de cableado. Mismo comportamiento a proposito: mismos textos, mismo orden de efectos, mismos numeros. 20 pruebas nuevas, comprobado que fallan las 20 sin el cambio. Fijan lo que ya mordio en campo: que el tiempo nunca reste, que el marcador suba antes de preguntar al servidor, que "behind" no se de por bueno, que un 500 no se cuente como falta de cobertura, y que sin nodo activo el mensaje no prometa una sincronizacion que no va a ocurrir. PlayerApp: 3086 -> 2793 lineas. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Medido con el grafo de imports desde main.tsx, siguiendo tambien los import() a demanda y los new URL(): 13 de los 146 ficheros de frontend/src no los alcanzaba nadie. De cada uno se comprobo ademas que ningun sitio del repositorio nombra ninguno de sus simbolos. El peor era signalHunt/RuntimeScreen.tsx, 898 lineas. El propio FamilyRuntimeHost dice que ese minijuego se elimino y que la familia se juega como checkpoint, y medido contra la mision real 6 de los 10 nodos son de esa familia. Quien fuese a arreglar esos nodos ahi no habria cambiado nada. Tambien sequenceCode/RuntimeScreen.tsx (751), sustituido por SimonRuntimeScreen; el gameCatalog del jugador, un segundo catalogo de juegos con cuatro alias del mismo array que nadie lee; y collectibleRules.ts, del que se midio en la mision real que required_collectibles y collectible_rewards salen en 0 nodos. Se quedan a proposito, porque no son basura sino funciones desenchufadas: shared/versionGuard.ts (vixiarVersion no la llama nadie, asi que la proteccion contra jugar con una version vieja NO esta activa) y shared/fechas.ts (leerMarcaDeTiempo, el parseo que aguanta los seis decimales de Safari). frontend/src: 146 -> 135 ficheros, 3454 lineas menos. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
La rama de GitHub habia divergido: 15 commits que reescribian el trabajo de
aqui en cuatro aplastados y anadian cosas nuevas encima. Se trae el contenido,
no la historia, porque fusionarla habria arrastrado 85 MB de una distribucion
de Node y resucitado los 11 ficheros muertos que se acababan de borrar.
Lo que entra, comprobado uno por uno:
- Estetica de los minijuegos: toca los tres RuntimeScreen que de verdad se
juegan. Los otros dos que habia en esa rama son los ya borrados por muertos.
- Temas dinamicos y mobile-themes.css.
- Ahorro de bateria: el latido pasa de 5 s a 30 s y deja de mandarse en cada
lectura del GPS. Comprobado que la posicion no se pierde -heartbeatPositionRef
se reasigna en cada render- y que HEARTBEAT_STALE_SECONDS son 180, asi que a
30 s nadie pasa a stale. El timeout de getCurrentPosition sube a 15 s, que
bajo arbolado es mas realista.
- Espera de 15 s tras un fallo de red antes de reintentar. Dos cosas quedan
apuntadas y sin tocar: nextAllowedSyncTime es una variable de modulo comun a
todos los usuarios del dispositivo, y al entrar en espera devuelve el
snapshot sin actualizar sync_status.
- Selector de tema y VALID_PLAYER_THEMES en {glass, flame-red}. Medido contra
la mision real antes de aceptarlo: player_theme = 'glass', que sigue valiendo.
Lo que NO entra: bajar vite de 8 a 5 -la imagen que corre en produccion se
construyo con vite 8 dentro de node:20-slim, o sea que construye; el problema
de rolldown era del Node 20.11 de Windows-, el frontend/dist committeado -el
Dockerfile lo reconstruye y lo pisa, asi que nunca es lo que se sirve- y los
85 MB de Node.
Y con los temas venia un fallo de verdad. Se aplicaban asi:
document.body.className = `theme-${config.player_theme}`
Eso no anade una clase: sustituye TODAS las del body. Y el body no es solo del
tema -el escaner de QR pone saga-qr-scanner-open, y de esa clase cuelga la
regla que esconde la barra de abajo mientras se enfoca la pegatina-. Ademas
estaba en el cuerpo del componente, o sea que corria en CADA render, y
PlayerApp se repinta con cada lectura del GPS.
Asi que abrir el escaner y dar dos pasos devolvia la barra de abajo ENCIMA del
visor, tragandose los toques. Medido: 5 de los 10 nodos se completan leyendo
un QR.
Ahora shared/tema.ts quita solo las clases que empiezan por theme- y anade la
suya, y el tema de arranque se pone al cargar el modulo: una vez, antes de que
React pinte, sin parpadeo y sin repetirse. Con 5 pruebas, 4 de ellas en rojo
antes del cambio, y una que barre todo frontend/src para que nadie vuelva a
asignar document.body.className entero.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
La 4.1.0 no es ni la 4.0.0 de aqui ni la 4.0.4 de GitHub: es las dos cosas. Se pone en los tres sitios a la vez -VERSION, package.json y package-lock- que es lo que npm ci comprueba. Y .gitignore para frontend/dist y node-v*, porque lo construido lo genera el Dockerfile y tener ademas una copia en el repositorio son dos verdades sobre lo mismo -la del repositorio no es nunca la que se sirve-, y una distribucion de Node dentro del repo son 85 MB que paga cada clon para siempre. Comprobado despues con un clon limpio: 4,7 MB en vez de ~90. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Dos cosas que parecian funcionar y no hacian nada. EL STORE. usePlayerStore declaraba la partida entera -status, payload, config, errorMessage- ademas del GPS y de que paneles estan abiertos. Medido: status 0 lecturas, payload 0, config 0, errorMessage 0. setGamePayload no lo llamaba nadie, asi que esos cuatro campos se quedaban en su valor inicial para siempre, pareciendo datos. La partida de verdad es el useState<LoadState> de PlayerApp. Ya habia mordido, y estaba escrito en CraftingPanel: la mesa de trabajo leia getState().payload, encontraba null, y le decia "No hay recetas" a un jugador que llevaba los ingredientes en la mochila. Alli quedaba ademas un respaldo `?? getState().payload?.stages ?? []` que siempre devolvia []: no era un respaldo, era ruido. saveInventorySnapshot vivia dentro de setGamePayload, o sea que tampoco se ejecutaba nunca. Se queda lo que si cruza varias pantallas: el GPS (6 lecturas) y los paneles (4 lecturas, 25 escrituras). LOS TEMAS. mobile-themes.css estaba en el repositorio y no lo importaba nadie. Vite solo empaqueta el CSS que alguien importa, asi que nunca entro en el bundle: el dist construido tenia CERO reglas theme-*. El movil recibia class="theme-glass" y no habia ni una regla que casara. Tres commits de temas, un selector en el panel y una lista de temas recortada, y ni un pixel cambio en ningun telefono. Ademas apuntaba a sitios que no existen. Medido: .player-panel 0 usos, .login-panel 0, .saga-mobile-shell 0. Y button[type=button][style*=backgroundColor] no puede casar nunca, porque el DOM serializa el estilo como background-color. Lo que si existe y lleva la cara del juego es .saga-glass-panel: 8 componentes, minijuegos incluidos. Ahora el tema le da sus variables, asi que se nota de verdad y no solo en el fondo. Todo bajo body[class*="theme-"], que solo ponen la entrada y el jugador: el panel de administracion conserva su aspecto. Y sin un solo !important, porque la especificidad ya gana. Los temas del CSS y VALID_PLAYER_THEMES del servidor son ahora la misma lista y hay una prueba que las compara: dos listas de temas divergen siempre. De paso, fuera de mobile-shell.css lo que no usa ningun componente: .saga-premium-button, .saga-pulse-glow, .saga-slide-up-enter y sus keyframes. 9 pruebas nuevas, 7 fallaban antes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…na y los acentos
LA GUARDA DE LOS HOOKS ESTABA DESCONECTADA. El fallo mas caro de este frontend
fue meter un hook por debajo de un return temprano en PlayerApp.tsx: React tira
la aplicacion entera con el error 310. Paso, y llego a produccion. La regla que
lo caza, react-hooks/rules-of-hooks, estaba en "warn", y eslint termina con
codigo 0 cuando solo hay avisos: el workflow de lint NO podia fallar nunca por
eso.
Medido en la Pi antes de subirla: 275 avisos en total y 0 de esta regla. O sea
que ponerla en error no obligaba a arreglar nada, solo dejaba la trampa armada.
Las demas se quedan en warn a proposito -no-unused-vars tiene 134 y
exhaustive-deps 33-, porque un lint que falla por 275 cosas se acaba
desactivando y entonces no queda ninguna guarda.
LA LINTERNA FALLABA EN SILENCIO. El escaner de QR ensena un boton de linterna
cuando el navegador dice que la camara tiene una. Al pulsarlo se pedia
applyConstraints({torch}) dentro de un try con un catch VACIO: si el movil
decia que si y luego no podia, no pasaba absolutamente nada. Ni luz, ni
mensaje, ni el boton cambiando -setTorchOn solo corre si la llamada sale bien-.
De noche, delante de una pegatina, era darle a un boton que pone "OFF" y no
hace nada. Y no es un rincon raro: 5 de los 10 nodos se completan leyendo un
QR, y la ruta se camina hasta el atardecer. Ahora se avisa y se retira el
boton: prometer una linterna que no funciona es peor que no ofrecerla.
EL TEXTO HABIA PERDIDO LOS ACENTOS. Alguna herramienta edito PlayerHud.tsx
convirtiendo lo no-ASCII en interrogantes. En la funcion que esconde los
controles del mapa al abrir un panel quedaron cuatro comparaciones IDENTICAS
-eran cuatro simbolos distintos- y dos etiquetas con el acento comido, que no
casan con nada: los botones del mapa con tilde no se escondian y se quedaban
encima del panel.
Comprobado que es dano real y no cosa del terminal -los bytes son 0x3F- y que
viene del commit raiz, asi que los simbolos originales no estan en ninguna
parte de la historia. Se arregla sin adivinar: se quitan los acentos antes de
comparar, con escapes ASCII en el regex a proposito, y los simbolos se
reconocen por su FORMA en vez de por una lista que ya no existe.
Barrido de todo el repositorio antes de tocar: 20 lineas sospechosas, 18 falsos
positivos de parametros de URL y solo esas 2 rotas de verdad. El gallego de la
mision esta intacto. La prueba nueva barre el repositorio entero.
11 pruebas nuevas, 8 fallaban antes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Cada foto se bajaba dentro de un try con un console.warn en el catch, y se
continuaba igual. Si fallaban ocho de diez, el ZIP se generaba con dos y el
jugador leia "Descarga de ZIP completada".
Eso es de lo peor que puede hacer este programa: dar por buena una copia
incompleta de las pruebas de alguien. El jugador borra el movil confiando en
que las tiene.
Ahora se cuentan las que fallan -tambien las que vienen sin url, que antes se
saltaban en silencio, y las respuestas con codigo de error, que antes pasaban
por buenas- y solo se dice "completada" si no falta ninguna. Si falta alguna,
se dice cuantas de cuantas.
Y la segunda parte, medida sobre el build real: jszip sale como un trozo aparte
-jszip.min-*.js, 96 KB, 28 KB comprimido- porque se pide con await
import('jszip'). El precache offline solo guarda los scripts que ya estan en la
pagina: pwaShell.ts los busca con querySelectorAll('script[src]'). Asi que ese
trozo NO esta en el movil y sin cobertura la descarga falla en la primera
linea.
Se deja asi a proposito -cargarlo siempre son 28 KB comprimidos de mas para
todos, y esto se hace en casa con wifi, no en el monte- pero ahora se dice:
"Hace falta conexion para armarlo" en vez de "Error al crear ZIP", que no le
decia a nadie que hacer.
4 pruebas nuevas, 3 fallaban antes. Una de ellas fija que el precache sigue
cogiendo solo lo que esta en la pagina, porque si eso cambiara la nota de
arriba dejaria de ser cierta.
305 verdes, tsc limpio.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
dependabot
Bot
force-pushed
the
dependabot/npm_and_yarn/frontend/frontend-tooling-5dd385fff7
branch
from
August 13, 2026 21:00
4969ada to
05fecb0
Compare
…parche
Medido en el navegador sobre produccion, que es lo que faltaba:
body.className = "theme-flame-red" <- bien
--theme-bg = "#2f0a0a" <- bien
getComputedStyle(body).backgroundColor = "rgb(2, 6, 23)" <- azul marino
O sea: la clase puesta, la variable resuelta, y el fondo sin cambiar. La regla
que ganaba, encontrada preguntandole al navegador que reglas casaban:
html, body, #root { ... background: rgb(2, 6, 23) !important; }
Sale de globalPlayerEdgeFix, un bloque que ScreenFrame inyecta en un <style>.
Con !important gana a cualquier especificidad, asi que el tema no podia pintar
el fondo por mucho que la variable estuviera bien. Y el color se metia
reemplazando texto dentro del bloque por una prop cuyo valor por defecto era
ese mismo azul, y a la que nadie le pasaba otra cosa.
Ese es el motivo entero de que se viera "un parche de unos botones": lo mas
grande de la pantalla -el fondo y el mapa- no lo tocaba nadie.
Ahora el fondo global, el marco de la pantalla y el fondo del mapa salen de
var(--theme-bg) y var(--theme-surface), y cada tema define la suya. Los
!important se quedan, que estan para ganarle a Leaflet y al navegador, pero lo
que ponen ya viene del tema. Fuera la prop themeColor y el reemplazo de
cadenas.
5 pruebas nuevas, las 5 fallaban antes. 310 verdes, tsc limpio.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
El repositorio es publico. La guarda del propio proyecto lo dice en su cabecera: el motor es publico; la ruta, los nombres de quien juega y los codigos de los nodos, no. Llevaba dias saltando con 41 avisos en 15 ficheros. Dos tratos distintos, porque no es lo mismo: - NOMBRES DE PERSONAS, en cuatro ficheros de prueba y en un script: pasan a nombres inventados de plantas del monte gallego. Las pruebas se leen igual de bien y dejan de nombrar a nadie. - EL TOPONIMO, en comentarios, en el CHANGELOG y en los docs: se generaliza. No se pierde nada, porque lo que importa de esos comentarios es "medido en el monte bajo arbolado", no en cual. Y lo mas serio, que estaba a la vista de cualquiera que abriese la aplicacion: el texto de portada del motor decia cuantos nodos tiene la ruta y como se llama el monte, en tres idiomas. Eso es de la mision -viene de config.story_text-, no del motor. Un motor que se descarga de un repositorio publico no puede llevar dentro la ruta de nadie. Ahora dice que es esto y que hace falta, sin nombrar sitio ni contar nodos. La guarda pasa: "Repository privacy guard passed." Y hay una prueba que la ejecuta en cada vuelta. Hacia falta porque la lista de palabras vive en .saga-privacidad-local.txt, que esta fuera de git: en CI la comprobacion se salta entera y siempre pasa, asi que la guarda solo decia algo si alguien la corria a mano. Otra prueba comprueba que esa lista NO entre nunca al repositorio, porque meterla seria filtrarla igual. 313 verdes, tsc limpio. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…adie Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
dependabot
Bot
force-pushed
the
dependabot/npm_and_yarn/frontend/frontend-tooling-5dd385fff7
branch
from
August 14, 2026 08:09
05fecb0 to
d7f6675
Compare
Encontrado en el banco de ensayo, en una carga limpia:
body.className = "theme-glass" con la mision puesta en flame-red
El efecto que aplica el tema leia getCachedPublicConfig(), o sea la copia
guardada en el movil. Un jugador que abre la aplicacion por primera vez no
tiene esa copia todavia, asi que se quedaba con el tema de respaldo aunque la
mision dijera otro, y solo se corregia en una carga posterior.
Es la razon de que el tema pareciera no funcionar al mirarlo: la primera vez
nunca era el bueno.
Ahora manda state.config, que es lo que acaba de traer el servidor, y la copia
guardada queda de respaldo para abrir sin cobertura. LoginApp ya lo hacia bien;
el fallo era solo de PlayerApp.
1 prueba nueva. 314 verdes, tsc limpio.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Encontrado caminando el banco de ensayo, que es de lo que se trataba. La primera vez que un movil abre la mision, la pantalla avisa: "Primera vez: se guarda el mapa. Tarda unos minutos". Durante toda esa descarga el estado no es 'ready', y el efecto que pone el tema esperaba a 'ready'. Medido: al 77% de las teselas el cuerpo seguia en theme-glass con la mision puesta en flame-red. O sea que la primera apertura -la unica que un jugador hace en el monte, y la mas larga- se pasaba entera con el tema equivocado. La configuracion llega mucho antes que las teselas. El tema se pone ahi. Y de paso, mas reglas de CSS declaradas dos veces: - .saga-build-info--floating estaba SEIS veces en su hoja, cuatro de ellas para esconderla, en bloques numerados #235b a #235e. #235c es copia exacta de #235b y #235e de #235d, y la consulta de 900 px se traga entera a la de 760. Cuatro intentos de tapar lo mismo, porque desde fuera no se ve si el anterior funciono. Queda uno. - El azul del fondo estaba escrito a mano en la carcasa mientras el tema intentaba pintarlo desde otro sitio. Ahora sale de --theme-bg, con el azul de siempre como valor por defecto por si la hoja de temas no carga. Una prueba nueva barre las cuatro hojas del jugador y no deja repetir ningun selector con el mismo cuerpo. 5 pruebas nuevas. 318 verdes, tsc limpio. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
CORRECCION DE ALGO QUE CASI AFIRMO MAL. Al ver que el precache se corta en seco a 1500 teselas y que el detalle de los nodos -zoom 18- se anade EL ULTIMO, reproduje la cuenta y me salio que no entraba ni una: "llegas al nodo y el mapa ampliado esta en blanco". Lo medi contra el banco de ensayo, con la ruta real y la implementacion de verdad, y era mentira. La cache tiene 1161 teselas de las 1500 del tope, y 152 son de zoom 18. El detalle de los nodos se guarda entero. Mi reproduccion sobrecontaba los zooms bajos. Casi repito el fallo que este proyecto ya tiene documentado: dar por buena una causa sin medirla contra lo real. Lo que si es cierto y se arregla: va al 77% del presupuesto y el detalle es el ultimo de la cola. Una ruta con mas nodos, o mas larga, llega al tope y pierde justo esa capa EN SILENCIO -addTile devuelve sin decir nada y el resumen cuenta las que pidio, no las que hacian falta-. El panel de "antes de salir" seguiria diciendo que el mapa esta listo. Ahora el resumen lleva recortado, descartadas y detalle_de_nodos, y el aviso final no anuncia "Mapa listo" si se corto: dice "Mapa guardado, sin todo el detalle" y cuantas no caben. Que se corte puede ser razonable; que no se sepa, no. 4 pruebas nuevas. 322 verdes, tsc limpio. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
dependabot
Bot
force-pushed
the
dependabot/npm_and_yarn/frontend/frontend-tooling-5dd385fff7
branch
from
August 14, 2026 08:54
d7f6675 to
de250c9
Compare
…escrito
Encontrado CAMINANDO la ruta en el banco de ensayo, que es lo que llevaba seis
versiones sin poder hacerse.
El codigo de respaldo es la salida de emergencia: alguien atascado en un
minijuego, de pie en el monte, escribe el codigo impreso y sigue. Cuesta dos
minutos de penalizacion, asi que no se usa por gusto.
Probado con un codigo equivocado, mirando el DOM mientras pasaba:
- el nodo NO avanza bien
- sale "Codigo no aceptado. Intentalo de nuevo." bien
- la casilla se cierra y se vacia mal
onSubmitCode devuelve si el nodo llego a superarse, y el escaner de QR ya mira
ese valor para no cantar victoria en falso. Esta casilla lo ignoraba y cerraba
pasara lo que pasara.
Lo mas probable en un codigo escrito a mano es una errata. Cerrar tira lo
tecleado y obliga a reabrir y reescribirlo entero, de noche y con el movil en
una mano. Ahora solo se cierra si se acepto.
De paso queda comprobado, jugando y no leyendo el fuente:
- el avance de nodo funciona de punta a punta: Parking 1/10 -> Campo de Tiro
2/10 -> Mirador do Vixia 3/10;
- el nodo coleccionable da gemas_antiguas x2, que es la cantidad de la
receta, no un duplicado;
- el minijuego del laberinto carga con el payload REAL y ensena su texto;
- el mensaje de rechazo que escribi en avance/decisiones.ts es exactamente el
que ve el jugador.
3 pruebas nuevas. 325 verdes, tsc limpio.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Medido en el navegador sobre el banco, sumando el area de cada elemento verde que se ve de verdad: saga-offline-grid-tile 28 elementos 1.816.696 px2 <- 99% del verde tilt-primary (borde) 1 14.183 px2 saga-mission-node-pin 2 2.426 px2 saga-avatar-pin (brillo) 1 2.095 px2 Por eso cambiar botones y tintes no cambiaba nada: lo que ocupa la pantalla es el mapa, y el mapa iba verde por su cuenta. La rejilla de fondo, la linea de la ruta, el brillo del marcador del jugador y los controles. Ahora salen del tema. En MapSurface solo se han cambiado VALORES DE COLOR: ni una linea de logica, que ese fichero esta en la lista de no tocar. La linea de la ruta se tine desde el CSS y no desde Leaflet, porque Leaflet escribe el color como atributo del SVG y ahi var() no resuelve. La clase si la pone, asi que basta con una regla. Lo que NO cambia, a proposito: los pines de nodo. Verde = superado, azul = el que toca ahora, rojo = pendiente. Es una escala con significado, y ahi el rojo ya quiere decir otra cosa: pintar de rojo lo superado seria mentir justo donde el jugador mira para saber por donde va. De paso: al meter el estilo nuevo puse un backtick dentro de un template literal y rompi la compilacion. Lo caza tsc, no yo. 5 pruebas nuevas. 330 verdes, tsc limpio. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…o escrito Medido en el banco: el area verde de la pantalla pasa de ~1.840.000 px2 a 2.624, un 99,86% menos. Lo que queda son los pines de nodo, que son una escala con significado y se dejan a proposito. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
dependabot
Bot
force-pushed
the
dependabot/npm_and_yarn/frontend/frontend-tooling-5dd385fff7
branch
from
August 14, 2026 20:46
de250c9 to
4d844d3
Compare
Borradas 14 posiciones guardadas en produccion. Pero borrar era la mitad: se
habrian vuelto a llenar solas.
Medido antes de borrarlas, sin imprimir ni una coordenada: de las 14, NUEVE
estaban a mas de 3 km de la ruta -35, 55, 69, 70 km-, y una de hacia poco mas
de un dia, con la ruta jugada hacia una semana. No eran posiciones de juego:
eran casas y trabajos de gente que abrio la aplicacion para mirar la
clasificacion.
La causa: el latido solo miraba si la partida estaba cargada.
if (state.status !== 'ready') return
...
intervalId = window.setInterval(publishHeartbeat, 30000)
Nada comprobaba si la mision ya habia terminado, asi que cada 30 segundos se
mandaba la posicion de alguien que ya no estaba jugando.
Ahora, con la mision terminada, el latido sigue yendo -es lo que trae la tabla
del grupo y alimenta la clasificacion- pero sin coordenadas.
Contra los datos de personas, lo que protege de verdad no es el permiso
firmado, que cubre hacer la foto y no guardarla dos anos, sino no tener lo que
no hace falta.
Las fotos de campo NO se han tocado: son las pruebas de la partida.
3 pruebas nuevas. 333 verdes, tsc limpio.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Habia dos hojas ensenando lo mismo con distinta cara. TeamSheet (165 lineas) daba presencia y estado de GPS de cada jugador: existia para saber quien esta conectado y donde. RankingSheet (377) da nivel, tiempo total y quien ha terminado, que es el juego. Comprobado antes de quitar nada, contra la mision real: la clasificacion cuadra sola. En los 14 jugadores el total de la tabla es exactamente la suma de los tiempos por nodo mas las penalizaciones. Cero descuadres y cero nodos fuera de rango. Lo que se queda es lo que funciona. Fuera el boton, las funciones de abrir y cerrar, el estado del store y el componente. El latido sigue trayendo la tabla del grupo, que es de donde sale la clasificacion. NO se tocan los marcadores de companeros en el mapa: eso es jugabilidad de una gymkhana de grupo y no un icono, asi que esa decision no la tomo yo. De paso, la guarda de privacidad se gano el sueldo otra vez: cazo que el fichero estaba borrado del disco pero no del indice de git. 5 pruebas nuevas. 338 verdes, tsc limpio. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Tenias razon con lo de que seguia siendo el mismo diseño. Hasta ahora el tema cambiaba fondo, acentos y tintes, pero la cara del juego -esquinas muy redondeadas y desenfoque fuerte- era identica en los dos. Elegir "fuego" se veia como un parche de color encima del mismo diseño. Medido en el banco, ordenando por area visible: saga-glass-panel radio 24px blur(22px) 66.597 px2 tarjetas de permisos 16px blur(8px) 33.104 px2 barra del HUD 28px blur(24px) 13.282 px2 Todas con el estilo EN LINEA, que es por lo que una regla de CSS no las tocaba. Habia 161 borderRadius y 35 backdropFilter escritos a mano. Ahora la forma sale del tema: --theme-radius-panel 24px -> 3px --theme-radius-card 16px -> 2px --theme-radius-pill 999px -> 4px --theme-blur blur(22px) -> none --theme-border-w 1px -> 2px Y fuego tiene rasgos propios que cristal no tiene: los paneles llevan una esquina cortada -clip-path, como una placa de metal-, una linea de brasa arriba y los titulos en versalitas separadas. Todo bajo body.theme-flame-red, asi que CRISTAL NO SE ENTERA: sigue siendo el tema de siempre, que era el requisito. 82 formas convertidas en las cinco superficies que mas se ven. Quedan las de los minijuegos y los paneles pequeños, que van despues. 6 pruebas nuevas. Una compara las dos listas y falla si los dos temas tienen la misma forma: es la que impide que esto vuelva a ser un cambio de color. 343 verdes, tsc limpio. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
En el banco, con el tema de fuego puesto, las variables ya estaban vivas -panel 3px, blur none, corte 16px- y ocho superficies eran angulares. Pero la MAS GRANDE de todas seguia redondeada: map-surface radio 28px 912.407 px2 Que es la pantalla entera. Con eso redondeado, todo lo demas daba igual. Convertidas 72 formas y 34 desenfoques mas, en el mapa y en quince pantallas: el escaner de QR, la mochila, la mesa de trabajo, la clasificacion, las hojas, la camara, el visor de fotos y los avisos. Sumado a lo anterior: 154 radios y 34 desenfoques que estaban escritos a mano ahora salen del tema. Cristal se queda exactamente como estaba -sus variables valen lo que valian- y fuego es otro diseño. 343 verdes, tsc limpio. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…padeo "Hizo como un parpadeo pero se antepuso el otro tema". Tenia dos causas, las dos de orden. 1. El servidor entregaba index.html tal cual, con el <body> sin clase. La pantalla de carga -el anillo y la barra de "Primera vez: se guarda el mapa"- se pinta ANTES de que exista configuracion ninguna, asi que salia siempre con los colores por defecto dijera lo que dijera la mision. Por eso la carga seguia azul y verde por mucho tema que se pusiera. 2. LoginApp y PlayerApp llamaban LOS DOS a aplicarTema al cargar el modulo, y App.tsx los importa a los dos de golpe con un import normal. En la pagina del jugador se ejecutaban las dos llamadas: sin configuracion guardada las dos ponian el respaldo, y solo despues llegaba el tema de verdad. Eso es lo que se veia cambiar. El sitio donde se arregla de una vez es el HTML. Ahora react_index_or_missing lee el tema de la mision -que el servidor conoce, la configuracion es suya- y lo pone en el body antes de mandar la pagina. No queda ningun instante sin tema, ni en la carga. Se quitan las dos llamadas de arranque: ya no hacen falta y eran las que se pisaban. Los efectos que reaccionan a la configuracion se quedan, que son los que atienden un cambio de tema sin recargar. Si algo falla al leer la configuracion se devuelve cadena vacia y la pagina sale sin clase: los valores por defecto del CSS son los de siempre. 3 pruebas nuevas. 346 verdes, tsc limpio. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
dependabot
Bot
force-pushed
the
dependabot/npm_and_yarn/frontend/frontend-tooling-5dd385fff7
branch
from
August 15, 2026 19:40
4d844d3 to
8e43ce9
Compare
En la captura se veia claro: los botones ENTRAR ya salian rojos -el acento del tema llegaba- pero la tarjeta seguia verde. El motivo es que la entrada no usaba el verde de marca (#22c55e), que ya estaba convertido, sino verdes grises propios escritos solo ahi: #253530, #34433e, #25322e y rgba(78,92,90,...). Por eso mis busquedas del verde de marca no los cazaron. Ahora salen del tema: el fondo grande de --theme-bg y --theme-surface, el halo de --theme-tint, las dos tarjetas de --saga-glass-bg y los bordes de --theme-border-w y --saga-glass-border. El velo sobre la foto pasa a negro neutro, que no tiñe de ningun color. Se quedan verdes a proposito el punto y el aviso del cofre offline: ahi verde significa "listo", no es color de marca. 346 verdes, tsc limpio. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Comprobado en el banco antes de subirlo: 0 elementos verdes, fondo rgb(47,10,10), tarjetas de 3-4 px sin desenfoque y el boton ENTRAR en rojo brasa. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Bumps the frontend-tooling group with 2 updates in the /frontend directory: [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) and [eslint](https://github.com/eslint/eslint). Updates `@typescript-eslint/eslint-plugin` from 8.66.0 to 8.67.0 - [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases) - [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/CHANGELOG.md) - [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.67.0/packages/eslint-plugin) Updates `@typescript-eslint/parser` from 8.66.0 to 8.67.0 - [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases) - [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/parser/CHANGELOG.md) - [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.67.0/packages/parser) Updates `eslint` from 8.57.1 to 10.8.1 - [Release notes](https://github.com/eslint/eslint/releases) - [Commits](eslint/eslint@v8.57.1...v10.8.1) --- updated-dependencies: - dependency-name: "@typescript-eslint/eslint-plugin" dependency-version: 8.67.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: frontend-tooling - dependency-name: "@typescript-eslint/parser" dependency-version: 8.67.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: frontend-tooling - dependency-name: eslint dependency-version: 10.8.1 dependency-type: direct:development update-type: version-update:semver-major dependency-group: frontend-tooling ... Signed-off-by: dependabot[bot] <support@github.com>
dependabot
Bot
force-pushed
the
dependabot/npm_and_yarn/frontend/frontend-tooling-5dd385fff7
branch
from
August 15, 2026 20:11
8e43ce9 to
c4e330c
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Bumps the frontend-tooling group with 2 updates in the /frontend directory: @typescript-eslint/eslint-plugin and eslint.
Updates
@typescript-eslint/eslint-pluginfrom 8.66.0 to 8.67.0Release notes
Sourced from @typescript-eslint/eslint-plugin's releases.
Changelog
Sourced from @typescript-eslint/eslint-plugin's changelog.
Commits
20a261fchore(release): publish 8.67.06dfe4d0chore(eslint-plugin-internal): [plugin-test-formatting] enforce zero-indentat...3b155bbchore: use typescript 7 for typechecking (#12601)Updates
@typescript-eslint/parserfrom 8.66.0 to 8.67.0Release notes
Sourced from @typescript-eslint/parser's releases.
Changelog
Sourced from @typescript-eslint/parser's changelog.
Commits
20a261fchore(release): publish 8.67.03b155bbchore: use typescript 7 for typechecking (#12601)Updates
eslintfrom 8.57.1 to 10.8.1Release notes
Sourced from eslint's releases.
... (truncated)
Commits
c049dc310.8.1a3f7826Build: changelog update for 10.8.118eb0a7fix: prevent ASI hazard inno-unused-labelsautofix (#21173)0a14800chore: update github/codeql-action action to v4.37.4 (#21196)7d0cbf8docs: Update README05adcb1test: fix failing ecosystem test foreslint-plugin-unicorn(#21191)5611035test: add error locations info tono-void(#21185)ee47333ci: bump github/codeql-action from 4 to 4.37.3 (#21176)f131c03chore: improve ecosystem test failure reporting (#20937)0a05812docs: add missing backticks tono-duplicate-imports.js(#21183)