Skip to content

Bump the frontend-tooling group across 1 directory with 3 updates - #281

Open
dependabot[bot] wants to merge 59 commits into
mainfrom
dependabot/npm_and_yarn/frontend/frontend-tooling-5dd385fff7
Open

Bump the frontend-tooling group across 1 directory with 3 updates#281
dependabot[bot] wants to merge 59 commits into
mainfrom
dependabot/npm_and_yarn/frontend/frontend-tooling-5dd385fff7

Conversation

@dependabot

@dependabot dependabot Bot commented on behalf of github Aug 13, 2026

Copy link
Copy Markdown
Contributor

Bumps the frontend-tooling group with 2 updates in the /frontend directory: @typescript-eslint/eslint-plugin and eslint.

Updates @typescript-eslint/eslint-plugin from 8.66.0 to 8.67.0

Release notes

Sourced from @​typescript-eslint/eslint-plugin's releases.

v8.67.0

8.67.0 (2026-08-10)

🚀 Features

  • typescript-eslint: export basic globs for using tseslint (#12105)

❤️ Thank You

See GitHub Releases for more information.

You can read about our versioning strategy and releases on our website.

Changelog

Sourced from @​typescript-eslint/eslint-plugin's changelog.

8.67.0 (2026-08-10)

This was a version bump only for eslint-plugin to align it with other projects, there were no code changes.

See GitHub Releases for more information.

You can read about our versioning strategy and releases on our website.

Commits
  • 20a261f chore(release): publish 8.67.0
  • 6dfe4d0 chore(eslint-plugin-internal): [plugin-test-formatting] enforce zero-indentat...
  • 3b155bb chore: use typescript 7 for typechecking (#12601)
  • See full diff in compare view

Updates @typescript-eslint/parser from 8.66.0 to 8.67.0

Release notes

Sourced from @​typescript-eslint/parser's releases.

v8.67.0

8.67.0 (2026-08-10)

🚀 Features

  • typescript-eslint: export basic globs for using tseslint (#12105)

❤️ Thank You

See GitHub Releases for more information.

You can read about our versioning strategy and releases on our website.

Changelog

Sourced from @​typescript-eslint/parser's changelog.

8.67.0 (2026-08-10)

This was a version bump only for parser to align it with other projects, there were no code changes.

See GitHub Releases for more information.

You can read about our versioning strategy and releases on our website.

Commits

Updates eslint from 8.57.1 to 10.8.1

Release notes

Sourced from eslint's releases.

v10.8.1

Bug Fixes

  • 18eb0a7 fix: prevent ASI hazard in no-unused-labels autofix (#21173) (dongkyu lee)
  • 151ba3f fix: false positives in getter-return and accessor-pairs (#21163) (Grit)
  • 6898df9 fix: ignore meta-property names in id-denylist (#21166) (Pixel)
  • 4d7db66 fix: ignore meta-property names in id-match (#21167) (Pixel)
  • 677214e fix: handle ASI hazards in no-unused-vars removeVar suggestion (#20935) (kuldeep kumar)

Documentation

  • 7d0cbf8 docs: Update README (GitHub Actions Bot)
  • 0a05812 docs: add missing backticks to no-duplicate-imports.js (#21183) (Lee Daeun)
  • 678c90b docs: Update README (GitHub Actions Bot)
  • 8a10424 docs: Update README (GitHub Actions Bot)
  • 69bb948 docs: Update README (GitHub Actions Bot)

Chores

  • 0a14800 chore: update github/codeql-action action to v4.37.4 (#21196) (renovate[bot])
  • 05adcb1 test: fix failing ecosystem test for eslint-plugin-unicorn (#21191) (Lazizbek Ergashev)
  • 5611035 test: add error locations info to no-void (#21185) (Lee Daeun)
  • ee47333 ci: bump github/codeql-action from 4 to 4.37.3 (#21176) (dependabot[bot])
  • f131c03 chore: improve ecosystem test failure reporting (#20937) (crimsonjay0)
  • 1f6edde chore: update ecosystem plugins (#21182) (ESLint Bot)
  • d3266fb chore: unpin webpack dependency (#21172) (Francesco Trotta)
  • 65a6519 chore: add allowScripts field to package.json (#21092) (GiHoon Noh)
  • 22e5256 ci: add triage:no label to Dependabot PRs (#21141) (lumir)
  • 55c9038 ci: bump actions/labeler from 6 to 7 (#21159) (dependabot[bot])
  • 7280e78 chore: update dependency prettier to v3.9.6 (#21162) (renovate[bot])
  • eddbad6 test: fix failing ecosystem test for eslint-plugin-unicorn (#21156) (Francesco Trotta)
  • 60a178d chore: update ecosystem plugins (#21150) (ESLint Bot)
  • f9f61dc test: add error locations to no-unreachable (#21151) (JIYEON)
  • d086293 test: add error locations to no-undef (#21147) (JIYEON)
  • cc01b67 test: add error locations to no-useless-catch (#21144) (devoil)
  • 688e75e chore: add missing backticks in JSDoc (#21143) (Bo Hyun Kim)
  • 7c1e175 test: add error locations to require-await (#21145) (Grit)
  • 588a26d test: add error locations to no-extra-label (#21139) (dongkyu lee)
  • 059aa89 test: add error locations to no-useless-concat (#21140) (dongkyu lee)
  • 5a452a8 test: add error locations to no-const-assign (#21138) (dongkyu lee)

v10.8.0

Features

  • 2fee9bb feat: export ConfigObject from eslint/config (#21082) (sethamus)

Bug Fixes

  • 6b8d2f7 fix: escape reserved characters in rule id in html formatter (#21129) (Francesco Trotta)
  • 9091071 fix: prevent no-unreachable-loop crash when all loop types are ignored (#21116) (Pixel)
  • e23fafe fix: prefer-object-spread add semicolon when adding parenthesis (#21081) (synthex-byte)
  • 20b5ad0 fix: quadratic-time regex in prefer-template (#21096) (Milos Djermanovic)
  • 8b6f6c0 fix: apply ignore configs to computed methods in class-methods-use-this (#21094) (Pixel)
  • b2c608c fix: NewExpression with parenthesized callee in preserve-caught-error (#21083) (Francesco Trotta)

... (truncated)

Commits

odegaard12 and others added 30 commits August 9, 2026 22:17
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>
@dependabot dependabot Bot added dependencies Pull requests that update a dependency file javascript Pull requests that update javascript code labels Aug 13, 2026
odegaard12 and others added 2 commits August 13, 2026 22:56
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
dependabot Bot force-pushed the dependabot/npm_and_yarn/frontend/frontend-tooling-5dd385fff7 branch from 4969ada to 05fecb0 Compare August 13, 2026 21:00
odegaard12 and others added 3 commits August 14, 2026 10:00
…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
dependabot Bot force-pushed the dependabot/npm_and_yarn/frontend/frontend-tooling-5dd385fff7 branch from 05fecb0 to d7f6675 Compare August 14, 2026 08:09
odegaard12 and others added 4 commits August 14, 2026 10:38
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
dependabot Bot force-pushed the dependabot/npm_and_yarn/frontend/frontend-tooling-5dd385fff7 branch from d7f6675 to de250c9 Compare August 14, 2026 08:54
odegaard12 and others added 3 commits August 14, 2026 11:03
…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
dependabot Bot force-pushed the dependabot/npm_and_yarn/frontend/frontend-tooling-5dd385fff7 branch from de250c9 to 4d844d3 Compare August 14, 2026 20:46
odegaard12 and others added 6 commits August 15, 2026 21:09
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
dependabot Bot force-pushed the dependabot/npm_and_yarn/frontend/frontend-tooling-5dd385fff7 branch from 4d844d3 to 8e43ce9 Compare August 15, 2026 19:40
odegaard12 and others added 3 commits August 15, 2026 22:05
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
dependabot Bot force-pushed the dependabot/npm_and_yarn/frontend/frontend-tooling-5dd385fff7 branch from 8e43ce9 to c4e330c Compare August 15, 2026 20:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependencies Pull requests that update a dependency file javascript Pull requests that update javascript code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant