Historial de cambios

GitHub

Cada cambio relevante, versión a versión. El mismo archivo se publica en el repositorio y está traducido a los 19 idiomas.

[1.267.1] — 2026-11-04

Corregido

  • Reparación del tren de releases (los gates de CI de v1.267.0) — la cadena de conteos de tests en PROJECT-CONTEXT ya no se rompe en la cabecera de v1.267.0 (el formato de flecha que matchea el eval grader), los prompts de QA se reorganizan (un prompt vivo por release, v1.266.0 archivado) y la cobertura por archivo se eleva por encima del ratchet 98/90: el renderizador de salario de applicantpro pierde un fallthrough inalcanzable (salaryRangeOf devuelve, de forma demostrable, undefined, {min}, {max} o {min,max}) con las ramas unilaterales ≥/≤ ahora fijadas a través del flujo de fetch, y la suite board-response-cache fija el bypass de no-store, el 304 suministrado por el llamador contra un board sin guardar y la expulsión en MAX_ENTRIES.
  • Pruebas: el recuento en vivo es 7395 (las suites de la ola v1.266.0/1.267.0 llevan más specs de lo que las notas anteriores seguían — la cadena se re-ancla al nivel medido: 7392 tras la ola, + 3 pruebas de gate en este parche); cobertura 98.51 línea / 90.35 rama.

Notas

  • La etiqueta v1.267.0 se deja apuntando al commit rojo del merge — no se re-apunta (el servidor rechaza etiquetas re-apuntadas); v1.267.1 es el vehículo de despliegue.

  • Reparación en parche del tren v1.267.0: gates de CI en verde, el árbol de código no cambió en lo sustancial.

[1.267.0] — 2026-11-04

Añadido

  • Cuatro nuevas fuentes de escaneo — GermanTechJobs, Darwinbox, Keka, ApplicantPro/isolved (registro 125 → 129 = 124 EN + 5 RU · 124 adapters) — la segunda ola de parent-sync contra career-ops @ 7d8feb6d (87 commits upstream). GermanTechJobs (#6346): RSS de todo el tablero en germantechjobs.de/rss (~626 elementos en una sola petición); el empleador es el último segmento @ del título — un título sin él se descarta, nunca se archiva bajo el tablero; selección solo mediante provider: explícito. Darwinbox (#6416): JSON alljobs sin autenticación detrás de <tenant>.darwinbox.in, el id de empresa sale de la URL de carreras, recorrido paginado por POST (50/página, max_pages 20/200) con el alto por página corta leído del propio recuento de filas de la fuente. Keka (#6199): dos peticiones — una shell de carreras con redirect:'manual' cuyo script nombra el UUID del feed (un tenant desconocido redirige 302 a TenantNotFound.html y se lee como un 404 con nombre), y después todo el inventario abierto en un único array embed-jobs. ApplicantPro / isolved (#6252): el domainId numérico del bootstrap y después {success, data:{jobs, jobCount}}; jobCount > rows avisa que la lista está incompleta; el opt-in applicantpro: { fetchDetails: true } enriquece desde JSON-LD con pausa de 250 ms.
  • Reutilización de respuestas de tablero vía ETag (#6105) — dentro de un mismo escaneo, una respuesta de tablero obtenida en los últimos 15 minutos se reutiliza tal cual y las más antiguas se revalidan con If-None-Match (el 304 conserva el cuerpo); la clave de caché cubre método + URL + cabeceras de credenciales + cuerpo, las lecturas solapadas se pliegan sobre la promesa en vuelo, y CAREER_OPS_HTTP_CACHE=0 / _TTL_MS lo anulan. En memoria por ejecución de escaneo (web-ui no tiene contrato de caché en disco) — desviación registrada.

Corregido

  • Fechas en cuatro tableros (#6274) — remoteok, remotive, workingnomads e IBM emiten ahora fecha de publicación, así que el filtro «Posted within» funciona allí; el timestamp naive de remotive se normaliza a UTC (su bug de edad por máquina estaba vivo), y el _source de IBM realmente pide dcdate.
  • Una clave mal escrita de portals.yml se rechaza (#6107) — el escaneo EN lanza ahora un error con nombre (archivo + clave + sugerencia de la clave más cercana, Levenshtein ≤ 2) antes de cualquier petición, en lugar de ignorar en silencio titel_filter:.
  • getro sigue recorriendo más allá de un pin destacado antiguo (#6406) — una oferta fijada caducada se sigue descartando, pero ya no corta la paginación; el recorrido termina en una página corta real.

Notas

  • Deliberadamente no hecho: el filtro de vistos del escáner RU no distingue idioma y su ruta de parseo de portals no pasa por la puerta de claves mal escritas (hand-offs registrados en la spec de la ola — ningún tablero RU emite aún filas por idioma); remoteok conserva su cadena de fecha con offset en lugar de normalizarla a UTC (su forma viva es determinista). Tests 7096 → 7243; Playwright 173 pass / 0 fail / 1 opt-in skip; registro 129 = 124 EN + 5 RU · 124 adapters (todas las puertas de recuento regeneradas desde el registro vivo).

[1.266.0] — 2026-11-01

Añadido

  • Las versiones por idioma de Personio dejan de comerse entre sí (CAR-223: parent #4789 + #5044 + #5285) — la pila aplazada, ejecutada como release propio. Lado de escaneo (#4789): scan_history.dedup_include_language: true (strict opt-in — con OFF es byte-idéntico) hace que las versiones por idioma de una misma oferta sobrevivan al dedup vía la maquinaria de variantes del historial de vistos (languageFormsFromUrl / recordUrlVariant / isUnseenLanguageVersion); la clave canónica elimina los parámetros language/lang/locale. preferred_languages (#5285): ordena las filas de versiones de una oferta con la preferida primero (estable, orden de proveedor en empates) — independiente del interruptor (con OFF se conserva la versión preferida, con ON se conservan todas en ese orden), con un port validatePreferredLanguages de validate-portals. Fan-out de personio (#5044): la página de carreras se pide sin el force ?language=en (se honra el ?language=xx propio del tenant) y emite una fila por versión de idioma con URLs language + ?language=<code>; dedupIncludeLanguage añade peticiones por idioma (tope 5, con pausa); los fallbacks /xml y job-link sin cambios. recruitee gana la misma emisión language + preferred_languages (opts o entrada). Los helpers compartidos viven en server/lib/language-preferences.mjs.

Notas

  • Hand-offs registrados, deliberadamente no hecho: el filtro de vistos del escáner RU no distingue idioma y ningún tablero RU emite aún filas por idioma; el espejo cliente url-key.js y el cableado de validación de la ruta de portals siguen abiertos. Tests 7096 → 7203; registro sin cambios 125 = 120 EN + 5 RU · 120 adapters.

[1.265.0] — 2026-11-01

Añadido

  • Dos nuevas fuentes de escaneo — Apple y Bulldogjob (registro 123 → 125 = 120 EN + 5 RU · 120 adapters) — ola de parent-sync contra career-ops @ fe857ecf. Apple (jobs.apple.com, #5496 + #6196): el sitio es una app React Router SSR sin una API JSON utilizable, así que la fuente lee el blob de hidratación de la ruta de búsqueda ordinaria (window.__staticRouterHydrationData); el descubrimiento lo define la entrada (apple.queries con orden por relevancia + ejes apple.teams, sin valores por defecto — una entrada sin ninguno lanza error), las páginas se acotan con max_pages (2 por defecto, techo 50) con pausa de 200 ms, cada segmento de URL se codifica a prueba de surrogates y una oferta marcada PIPE por Apple lleva en el snippet la nota «puede ser un talent pipeline» — nada se filtra por ello. Bulldogjob (bulldogjob.pl, #6253): el tablero polaco tampoco tiene API JSON — los listados llevan __NEXT_DATA__ (50/página, totalCount); el careers_url de la entrada es la búsqueda construida en el sitio (los filtros viven en la ruta), cada petición lleva un /page,N explícito y el recorrido se detiene en una página corta, el total declarado o max_pages (25 por defecto, techo 100) con etiqueta de truncamiento en tableros parciales.
  • Rework de la paginación de IBM (#6273) — el recorrido fijo de 600 registros pasa a max_pages (20 por defecto, techo 300, override por entrada) con pausa de 200 ms entre páginas y reintento por página vía fetchJsonWithRetry; una primera página fallida lanza error, una página posterior fallida conserva lo recogido y nombra la página, y solo una página completa en el techo propio de la entrada avisa «raise max_pages».

Corregido

  • justjoin fecha las ofertas por lastPublishedAt (#6254) — el publishedAt de la API de candidatos avanza cada vez que el empleador refresca la oferta, así que la mayor parte del tablero llevaba la fecha de hoy y max_posting_age_days dejaba pasar ofertas de semanas; ahora gana la fecha de publicación anterior, con fallback cuando falta o no parsea.
  • jobvite pliega las filas que comparten un posting URL (#6127) — un feed público devuelve una fila <job> por ciudad para un único detail URL; el parser conservaba la primera ciudad y contaba el resto como duplicados. Ahora los hermanos aportan solo localizaciones, unidas con · en orden de feed, se mantienen el primer título/fecha y «remote» se reevalúa sobre la localización unida.
  • Liveness: un rung para UltiPro (#6090) — UKG no tiene endpoint JSON por oferta y responde 200 en las cerradas, así que las 57 ofertas UltiPro cerradas leían uncertain. La página de detalle incrusta el registro: OpportunityIsClosed true/false → expired/active, el aviso «opportunity unavailable» → expired, cualquier otra cosa queda para el navegador. El banner de Built In «Sorry, this job was removed» es hard-expired (#6336) — la página conserva el JD completo y el control APPLY; ahora gana el banner, y el badge hermano «Job removed» sin «sorry» no coincide.

Notas

  • Deliberadamente no hecho: la mitad parent-side del fix de jobvite sobre el compilador de localizaciones de scan.mjs (web-ui documenta el compilador USPS como no-port) y el campo note del padre para la pista PIPE de Apple (el job shape de web-ui no tiene note; viaja en snippet). Tests 7096 → 7135 (+39); Playwright 173 pass / 0 fail / 1 opt-in skip; coverage gate ≥ 98/89; registro 125 = 120 EN + 5 RU · 120 adapters (todas las puertas de recuento regeneradas desde el registro vivo).

[1.264.3] — 2026-11-01

Corregido

  • Ola de endurecimiento de rate-limit (LOW nº 1–3 de REVIEW-2026-10-31-v1.264.1) — writeRateLimit() (express-rate-limit@7.5.1, 60 req/min route×IP, 429 JSON + Retry-After) cubre ahora los 47 endpoints de mutación (antes 2): 9 guardas de llmRateLimit mal acotadas se sustituyen (/api/config — ¡reescritura de .env!, PUT /api/batch, /api/batch/merge, outcome, followup/seed, assessments, stats/snapshot, mock-interview/save, networking/save), 3 se apilan antes del bucket de LLM en las rutas más caras (/api/evaluate, /api/deep, /api/auto-pipeline) y 33 registros desnudos se conectan (editores CV/profile/modes, todas las escrituras del tracker, pipeline POST/restore/DELETE, jds, 7× /api/run/* con una sola edición del bucle, posting-guard, Playwright-PDF inline, portals toggle/health/track, discover, reports, export/docx, two-pager, career-plan, memory y las tres rutas DELETE). /api/apply-helper sigue en allowlist sin límite (cómputo local puro, fijado); 16 rutas puramente LLM conservan el bucket condicional de LLM por diseño. Un tripwire de completitud (tests/rate-limit-coverage.test.mjs, red-first con 27 desnudos) hace fallar el CI ante cualquier futura registro de mutación sin throttling.
  • TRUST_PROXY opt-in + validaciones rearmadas (LOW nº 1–2) — app.set('trust proxy', N) acepta un hop-count del env (nunca boolean true — cifraría con el extremo izquierdo del XFF controlado por el atacante); sin valor por defecto, loopback conserva la degeneración documentada con clave por socket. validate: false pasa a un writeValidateConfig() dirigido: solo se desactiva xForwardedForHeader (el XFF de Caddy se ignora por diseño sin trust proxy); las demás validaciones — incluida trustProxy — vuelven a ser ruidosas, así que una futura configuración errónea trust proxy: true se manifestará como ERR_ERL_PERMISSIVE_TRUST_PROXY y no en silencio. El keyGenerator personalizado se eliminó deliberadamente: express-rate-limit ata sus validaciones de proxy al generador por defecto, y el aislamiento por ruta no cambia (un MemoryStore por llamada de fábrica).
  • La confirmación de copia de cvstart.org dura 2 s (CAR-141) — el estado «Скопировано ✓» se mantiene ahora 2 segundos según el criterio de aceptación (antes 1,5 s), con pin red-first en tests/site-copy-feedback.test.mjs. Cierra el último criterio de CAR-141 reparable por agente; la regrabación del vídeo demo queda aplazada por decisión del propietario.

Cambiado

  • La ayuda dice 19 idiomas (×19) — las frases de §19 «Localizing the app» y §32 «Run it from OpenWorker» ya no van dos localizaciones por detrás: cada bundle dice ahora 19, con தமிழ் y Tiếng Việt añadidos a cada enumeración en su propio estilo (ar lo hace con la cifra escrita «التسع عشرة»), y el resto «cả-17» del §32 de vi.md también queda corregido. Las puertas de estructura (33 H2 / 125+ H3) se mantienen byte-estables.
  • Higiene de estado — AGENTS.md indica v1.264.2 / 7082 tests (antes v1.86.x / 1566); PLAN.md marca las entregas 1–7 como Shipped y su párrafo del tren dice docs ×19; el comentario de StatsBar.astro describe lo que facts.adapters really contiene (123 = total de fuentes, nombre histórico de la clave conservado); la cabecera de i18n.js dice 19 idiomas; el QA prompt v1.264.2 lleva la cobertura real (98.45/90.05) y la aritmética de Playwright (176 = 174 harness + 1 opt-in skip + 2 suites a nivel de script).

Notas

  • Deliberadamente no hecho: la ola de parent-sync según la port-list registrada sigue en BACKLOG — el fork debe sincronizarse primero past upstream a9476866 (7 de los 10 commits de la lista aún no están en el árbol local del padre; IBM/UltiPro/Bulldogjob/apple con alcance S–M, personio language-dedup aplazado a su propia entrega). La regrabación del vídeo demo de CAR-141 queda aplazada conscientemente por el propietario («видео не надо пока», 2026-11-01).
  • Tests 7082 → 7096 (+9 tripwire de cobertura + 5 trust-proxy); Playwright 173 pass / 0 fail / 1 opt-in skip (+2 pines de chevron dentro de casos existentes); coverage 98.46 line / 90.10 branch; evals 14/0/1skip; registro sin cambios 123 = 118 EN + 5 RU · 118 adapters.

[1.264.2] — 2026-11-01

Corregido

  • Las cabeceras de grupo de la barra lateral se suman al contrato de truncamiento (CAR-219) — las etiquetas de los toggles de grupo (.nav-v2-toggle > span) ahora truncan con elipsis y llevan un tooltip localizado a través del mismo mecanismo syncLabelTitles() que los elementos de navegación de CAR-203; el chevron nunca se encoge, el toggle sigue siendo clicable y el RTL se mantiene. Pin rojo primero en tests/nav-v2.test.mjs (+1 → 7082 tests).

Cambiado

  • El texto de cvstart.org se envía en los 19 idiomas (CAR-220) — meta.helpDesc, helpTeaser.c1.desc y faq.a2 están traducidos en cada locale del sitio (en v1.264.1 eran solo EN por diseño); GitHub Pages ya sirve la copia mejorada en cvstart.org.
  • Higiene del backlog y de la revisión (CAR-221/222) — los seis puntos «port when merged upstream» quedan cerrados: los siete PRs upstream están fusionados, cuatro puntos ya estaban portados y dos son del lado del padre. Aterriza docs/reviews/REVIEW-2026-10-31-v1.264.1.md (0 HIGH / 0 MED, tres seguimientos LOW registrados en BACKLOG) y queda registrada la lista de puertos del parent-sync (6 PORT / 3 VERIFY / el hueco de la fuente apple; cola upstream de 118 commits @ VERSION 1.38.0).

Notas

  • La corrección del sello-espejo del changelog (92a05fcf) viaja en este tag: cada espejo paginado de cvstart.org lleva ahora la versión del release que lo generó, así que el grader site-build-after-fanout por fin ejercita su lógica real en lugar de saltarse la prueba.
  • Deliberadamente no hecho: limitar los POSTs mutantes restantes (LOW №3 de la revisión) y el acoplamiento de buckets con trust-proxy — quedan registrados en BACKLOG para la próxima ola de endurecimiento. CAR-141 (vídeo de demo) sigue siendo del propietario.
  • Tests 7081 → 7082 (pin CAR-219); Playwright 176; registro sin cambios 123 = 118 EN + 5 RU · 118 adapters.

[1.264.1] — 2026-10-31

Corregido

  • Las píldoras de la barra lateral truncan con elegancia las etiquetas localizadas largas (CAR-203) — el span de la etiqueta de navegación es ahora un ciudadano flex de pleno derecho (min-width: 0 + overflow: hidden + text-overflow: ellipsis + nowrap en v2-nav.css), y syncLabelTitles() en nav-v2.js refleja la etiqueta localizada en el tooltip a través de la misma clave data-i18n (cero claves i18n nuevas) — la larga «Профинансированные компании» vuelve a ser legible al pasar el ratón en lugar de quedar recortada en seco por el overflow: hidden de la fila. El objetivo táctil de 44 px, el pintado del estado activo y el espejo RTL quedan intactos; pin rojo primero en tests/nav-v2.test.mjs.
  • Un único techo de evaluación de 480 s para todas las locales (CAR-218) — el remote-QA de v1.264.0 captó evaluaciones transitorias 502 · 300 s · 0 ch en es y zh-CN (la repetición puntual pasó, pero el muro era real). EVAL_TIMEOUT_MS es ahora 480_000 y evalTimeoutMs(lang) lo devuelve incondicionalmente — GEMINI_EVAL_TIMEOUT_MS y el auto-pipeline lo heredan. El presupuesto de tokens por locale de CAR-174 (16384 / 32768) sigue siendo la única división por locale.

Cambiado

  • La FAQ de cvstart.org lleva los recuentos exactos de sources (CAR-217) — {sources} job-board sources: {sourcesEn} international boards plus {sourcesRu} Russian ones se sustituye desde facts.json, generado a partir del registro vivo (123 = 118 EN + 5 RU), así que los números jamás pueden derivar; las locales que aún mantienen la redacción antigua ~{adapters} siguen redondeando vía adaptersRounded. Las mejoras del texto EN vienen de regalo (meta.helpDesc, helpTeaser.c1.desc, faq.a2 nombran el menú de comandos, el pipeline agrupado con undo y el tracker kanban).

Notas

  • Deliberadamente no hecho: las mejoras de texto i18n del sitio (meta.helpDesc / helpTeaser.c1.desc / faq.a2) siguen siendo solo EN en esta versión — las demás locales conservan una redacción correcta pero más redondeada hasta la siguiente ola de documentación (su faq.a7 gana los placeholders de recuento exacto donde esté traducido). CAR-141 (vídeo de demo) sigue siendo del propietario; la rama sustituida car136/m141 se eliminó con sus ideas registradas en la tarea de Linear (recuperables vía el tag archive/car136-m141).
  • Tests 7041 → 7081 (pin CAR-203 + reescrituras del pin CAR-218); Playwright 176; registro sin cambios 123 = 118 EN + 5 RU · 118 adapters.

[1.264.0] — 2026-10-31

Añadido

  • Edición en línea del estado del tracker — el badge de estado de cada fila de #/tracker es un <select> compacto con las etapas canónicas (de GET /api/tracker/stages, nunca una lista blanca hardcodeada). POST /api/tracker/status transmite el set-status.mjs canónico del padre bajo el candado de applications.md (lista blanca de estados vía states.mjs, paso a través de --dry-run/--note, 502 fail-soft con detalle saneado, spawn con array argv). El éxito actualiza la fila in situ (clase de tono + contadores de pestañas, foco conservado); el fallo revierte y muestra un toast. Las filas que el relay no puede direccionar — un estado desconocido editado a mano, un sentinela de backfill # — conservan el badge de solo lectura (correcciones route-review LOW; revisor: sin hallazgos HIGH/MED).
  • Advertencias del servidor visibles en cada página de IA — evaluate, deep, auto-pipeline (página y modal), career-plan, market, cv-studio (humanize + tailor) ahora renderizan el array warnings del servidor (truncamiento de tokens de salida, deriva de idioma, correcciones de fechas) como una banda badge-warn (role="status"); el modal de auto-pipeline ya no pierde las advertencias del frame done. Nueva clave llm.truncatedNotice ×19.
  • Ola de cobertura (+111 tests) — routes/map.mjs 100/94.9, geocode.mjs 96.3/92.9, content.mjs 99/84.9, networking.mjs 96.4/89.1, sources/habr.mjs 100/98.2 (nuevos tests/cov-*-routes.test.mjs ×3, tests/sources-habr.test.mjs); el suelo de ramas se mantiene en ≥ 90 %.
  • Paquete de cierre de Linear CAR-191…216 (docs/reviews/2026-10-30-linear-car191-216-closure-bundle.md) — cierre para el propietario listo para pegar, con SHAs de commit por CAR y comentarios de evidencia.

Corregido

  • Fuente de scan Trudvsem — una página vacía (results: {}, también «pasado del final») ya no mata todo el scan (cero filas, limpio); los sobres de error de la API (HTTP-200 con status: 500) lanzan con el texto de la propia API; la paginación recorre offsets de registro (antes índice de página — páginas duplicadas en silencio, ~52 vacantes en lugar de cientos); el recorrido se detiene antes de la zona de fallo de deep-offset de la API (offset × limit ≥ 10000). Verificado de extremo a extremo en vivo.
  • habr — títulos/empresas se limpian de etiquetas después de decodificar entidades (paridad con hh.mjs): un &lt;script&gt; en el título de una tarjeta ya no puede llegar a las filas del scan como markup vivo; los >> legítimos en títulos sobreviven.
  • PUT /api/profile (ruta en crudo) — un bloque candidate: sin un full_name no vacío se rechaza, en la misma línea que la ruta de fusión de campos (antes persistía full_name: null).
  • Veracidad de los hints — .doc se retira de la lista de aceptación de subida de CV y cv.uploadHint ×19 (el parser nunca lo admitió); config.openaiModelHint ×19 nombra gpt-5 (el servidor mapea a él cualquier id *-codex); config.anthropicModelHint ×19 y el valor por defecto de Anthropic ahora reflejan los niveles del padre — por defecto claude-sonnet-5-5, premium claude-opus-5-5.
  • Enlace a la documentación de Claude Code → code.claude.com (la antigua URL docs.anthropic.com/en/docs/claude-code lanzaba TypeError en la puerta de enlaces).
  • help ×19: la viñeta de densidad de fila ha desaparecido (el toggle salió de la UI en v1.263.0) y 18 familias de spans **bold** rotos quedaron reparadas — más la clase de fuga del renderizador corregida ×19 (segunda línea en blanco tras cada tabla-antes-de-párrafo; el glifo de tubería fuera de las celdas de negrita): 0 ** literales en el render de los 19 bundles de ayuda.

Notas

  • El modelo por defecto de Anthropic cambió a claude-sonnet-5-5 (el nivel estándar del padre). Los despliegues que dependían del antiguo valor por defecto silencioso (claude-sonnet-4-6) se actualizan automáticamente — fija ANTHROPIC_MODEL en .env para evitarlo.
  • Deliberadamente no hecho: los dos detalles LOW de la revisión quedan abiertos a propósito (el select en línea no refleja la degradación movesReady del kanban — el toast honesto en cada fallo es el comportamiento elegido; la rama code: 'timeout' sigue cubierta por el contrato del fake-writer, no por un test de >30 s). CAR-141 (vídeo de demo) sigue siendo del propietario.
  • help ×19 recibió la pasada de prosa post-lanzamiento (viñeta de densidad fuera, clase de fuga del renderizador corregida) en main después de que v1.263.0 saliera — esta versión es la primera etiqueta que la lleva.
  • También en esta etiqueta: la base del chip de evidencia (parsers de informes + insignias de vista), el runner fix-report-links (POST /api/runners/report-links, tarjeta «Reparar» en #/health) y la estimación/confirmación de coste del lote con registro de uso — llegaron a main entre el corte de rama y la etiqueta.

Tests 6905 → 7041 (+136) · Playwright 176 (173 pass / 1 opt-in skip) · cobertura 98.5 % líneas / 90.1 % ramas · registro sin cambios: 123 sources = 118 EN + 5 RU · 118 adapters.

[1.263.0] — 2026-10-30

Añadido

  • Capa de pulido de movimiento — nuevo motion.css (la última hoja de la app): entradas de modal/popover (escala .97 + fade, 130–180 ms), spring-in de toast + estado suave de salida, spring de pulsación del theme-toggle, escala .97 en la pulsación de chip/tab, infraestructura de stagger (pasos de 50 ms, tope 12), fade .page-enter en cambio de ruta (router), transiciones de halo de foco en chips/tabs. Toda regla es segura con prefers-reduced-motion (el movimiento baja a none; la opacidad y el color se mantienen).
  • Corrección táctil — 68 reglas de hover-lift iban sin guardia: los dispositivos táctiles se quedaban con una tarjeta “levantada” atascada tras un tap. Todos los hovers de transform están ahora bajo @media (hover:hover) (neutralizador de motion.css), y el lift de tarjeta se aplica solo a tarjetas genuinamente interactivas (.card:has(a), a.card, button.card, [role=button].card) — nada más de affordance falsa en tarjetas estáticas.
  • A11y: cada control tiene un nombre programático. El nuevo helper A11yDump nombra los 16 volcados de salida LLM de solo lectura; 12 campos solo con placeholder (CV Studio ×6, config ×2, búsqueda de scan, select de score del tracker, nota de outcome, editor de plan, URL de auto, bug report) recibieron aria-labels; la puerta a11y-form-wires ahora lo exige para toda la clase (antes 26 sitios sin nombre, ahora 0 + allowlist con razones documentadas).

Corregido

  • #/scan: la cabecera pegajosa de la tabla ya no pinta sobre la búsqueda de la topbar al hacer scroll por debajo de ella (su z igualaba al de la topbar y el DOM posterior ganaba — ahora derivada de tokens, por debajo); los inputs de “Зарплата от/до” vuelven a tener tamaño normal (una regla flex de .v2-scan-bar .input se colaba en el flex de columna del popover y ponía flex-basis 200 px como ALTURA — ahora con ámbito > + guardia de popover); el botón de alternar densidad se elimina por decisión del propietario (la fontanería se conserva, claves i18n eliminadas ×19).
  • Endurecimiento del deploy workflow (F1–F6): la entrada provider se valida por charset antes de cualquier empalme en shell; los diagnósticos de hermes redactan valores (filtro de claves case-insensitive + máscara de valores + corte a 80 caracteres en los tres sumideros, ?token= también enmascarado); el paso de hermes respeta inputs.app_dir; public_url con puerta https; la guardia anti-downgrade tiene 6 pins ejecutables (sin defaults, orden guardia-antes-de-Deploy, regex estricta de refs).
  • Cola de revisión, rojo primero: el viaje de ida y vuelta de la barra invertida en la importación CSV (reimportar vuelve a deduplicar), strip de <head> de cv-voice 4827 ms → 13 ms, statedYearsMin calculado perezosamente, TOCTOU de captura del posting-guard, wildcard CN de MiniMax, salto de tokens deel, guardias de tamaño del picker de importación antes de leer. Modal de importación: nav-guard armado, input de archivo eliminado al cancelar, lectura de duplicados con guardia, num vía reduce.
  • Lote UX del propietario: ritmo vertical del hero del dashboard (los hijos de nivel inline se pegaban en una sola caja de línea); desplazamiento de −8 px de la fila activa del TOC de ayuda (canal en línea por fila + columna de número); drawer móvil (cierre ✕ visible, reset de scrollTop, el fade fantasma de –paper bajo la guardia .has-hud); la página de docs-assistant ya no desborda con una URL larga en una respuesta (cadena de min-width del grid + overflow-wrap: anywhere); «Недавние» explicado (hint) + borrable; el Save de career-plan se rearma tras una carga fallida; el prefill de URL de evaluate ya no quema una llamada LLM con el placeholder de pegado; el Enter de auto-pipeline respeta la composición IME.
  • Contraste (auditoría WCAG, 296 pares): gradiente del docs-fab 2.43 → 6.4:1, toast.success → par ok-soft (1.75 → AA en oscuro), btn-dark del banner → giro a --ink, variantes claras de scan-logbtn/reposts-badge/fab-chip/close, y bordes de controles de formulario a 3:1 (--cv3-input-line, WCAG 1.4.11 — antes 1.2:1).
  • Barrido visual: el estado vacío del tracker es una tarjeta a todo el ancho (antes se recortaba dentro de la tabla de 760 px a 390); la línea de la consola de scan elipsifica en vez de recortarse a medias; el FAB flotante de chat ya no tapa contenido a 390 (objetivo de 44 px, inset 12); el estado vacío «Application statuses» del dashboard sigue el patrón v2-empty; el hint «?» de stats recibe un divisor + objetivo de 44 px; la pestaña Input de career-plan explica dónde aterriza el resultado; markdown del §1 de ayuda (18 spans de negrita rotos).

Cambiado

  • Azul de acento → rojo carmesí (petición del propietario, coherente con el logo, hue 15) — 5 tokens cv3 × 3 bloques de tema; el texto de la burbuja de chat de docs-assistant cabalga sobre --accent-ink (antes #fff literal, 2.39:1 en oscuro); el lavado de funded cabalga sobre --accent-soft. Danger se queda en hue 25 con sus propios tratamientos.
  • Docs: conteo de la tabla de pruebas del README ×17 (un drift de la era v1.248), §1 de ayuda + 18 spans de markdown, «118» de ayuda fr/it/ta, precios de proveedores CHECKED_ON 2026-10-30, hechos de HANDOFF/CONTEXT/TRACEABILITY.

Notas

  • Deliberadamente no hecho: el rework del barrido de skeletons se queda fuera (el contrato car112 fija el fade actual al pie de la letra); el crossfade del icono del theme-toggle sigue siendo un swap de display (el contrato Playwright del theme-toggle lo observa); la escala de .btn:active no se duplicó (la posee app.css); el stagger de movimiento es solo infraestructura — las teselas del dashboard lo adoptan más adelante; la prosa de help ×19 (mención de densidad, markdown del §1, «118») viaja en el próximo fan-out.
  • Evidencia remote-QA completa: spot de ja en verde (376 s / A–G ✓) sobre v1.262.4; en/it en verde antes, en la era token-dense.

Tests 6869 → 6905 (+36) · Playwright 176 · cobertura 98.4 % líneas / 89.9 % ramas · registro sin cambios: 123 sources = 118 EN + 5 RU · 118 adapters.

[1.262.4] — 2026-10-30

Corregido

  • ja se une a las locales densas en tokens para evaluación (ja → el techo de 32768 tokens + el reloj de 480 s). La re-corrida puntual de 5 locales en prod v1.262.3 separó los cuatro timeouts 502 · 300 s · 0 ch de la noche en transitorios y deterministas: ar (250 s), ko (291 s) y zh-CN (259 s) pasaron sus re-corridas, el drift de lenguaje puntual de ru no se repitió — pero ja chocó dos veces con el techo estándar con cero salida (la corrida completa ×19 40981578549 y la puntual 41012725839). La misma clase de forma que it/pl. Pin rojo primero: evalMaxTokens('ja') = 32768, evalTimeoutMs('ja') = 480_000, las demás locales mantienen 300 s.

Notas

  • Deliberadamente un parche de propósito único: el arreglo de ja es el único cambio que trae esta versión — no empaqueta nada más. ar/ko/zh-CN se quedan en el techo estándar según la evidencia de esta noche (pasan; los fallos de la noche fueron transitorios de carga del proveedor), y el drift de ru fue un flake de generación puntual.

Tests 6868 → 6869 (+1) · Playwright 176 · registro sin cambios: 123 sources = 118 EN + 5 RU · 118 adapters.

[1.262.3] — 2026-10-30

Corregido

  • pl se une a las locales densas en tokens para evaluación (pl → el techo de 32768 tokens + el reloj de 480 s). La remote-QA completa en prod v1.262.1 pilló la evaluación polaca muriendo en el techo estándar — 502 · openrouter · 300 s · 0 ch — y la re-corrida puntual despachada con v1.262.2 lo reprodujo exactamente (corridas 40960568766 y 40968958062): 18/19 locales pasaron A–G, it terminó en 302 s dentro del reloj token-dense de 480 s, y pl chocó dos veces con el muro con cero salida. Un timeout determinista, no un flake — la misma clase de forma que movió a it en v1.262.1. Pin rojo primero: evalMaxTokens('pl') = 32768, evalTimeoutMs('pl') = 480_000, las 18 locales estándar mantienen 300 s.

Notas

  • Deliberadamente un parche de propósito único: el arreglo de pl es el único cambio que trae esta versión — no empaqueta nada más; el contenido de la cola de revisión de v1.262.2 salió por separado.

Tests 6867 → 6868 (+1) · Playwright 176 · registro sin cambios: 123 sources = 118 EN + 5 RU · 118 adapters.

[1.262.2] — 2026-10-30

Corregido

  • Cola de la revisión por oleadas — 7 hallazgos de severidad baja, cada uno rojo primero. El viaje de ida y vuelta de la barra invertida en la importación CSV: una \ en una celda se escribía duplicada y se leía duplicada, así que reimportar el mismo CSV se saltaba el dedup de company+role y podía crear una fila duplicada — el lector ahora desescapa \\ tras el split (el ciclo escritura→lectura es sin pérdidas, la reimportación deduplica). El strip de <head> de cv-voice era cuadrático con etiquetas sin cerrar — 4827 ms → 13 ms en la entrada de 64 KB que la ruta topa. statedYearsMin ejecutaba el parser completo de años para cada oferta de cada fuente aunque years_min no estuviera configurado — ahora es perezoso, los segundos de CPU por escaneo grande desaparecen. Una captura borrada entre readdir y statSync tumbaba el listado del posting-guard (TOCTOU — los archivos desaparecidos se saltan). El default CN de MiniMax solo reconocía api.minimax.cn exacto — cualquier base URL *.minimax.cn recibe ahora el modelo CN. Los tokens $ de Deel que no son refs hex ya no se cuelan en el snippet como literales. El picker de importación comprueba file.size ANTES de file.text() — el tope de servidor de 512 KB respondía 413 solo después de que el navegador hubiera parseado un archivo posiblemente enorme — con un toast localizado import.tooLarge ×19.
  • Endurecimiento del deploy workflow (revisión de workflows F1–F6). provider era la única entrada interpolada en la shell remota sin validación de charset — una ' en el valor rompía las comillas y ejecutaba código arbitrario en prod; ahora se lista en blanco ^[A-Za-z0-9_-]+$ antes de cualquier empalme, y el grep de la guardia de clave ya no puede pasar en falso. El paso de diagnóstico de hermes imprimía journalctl crudo + volcados de units al log público; su filtro se perdía los *_API_KEY= en mayúsculas y no enmascaraba nada — ahora un redact compartido case-insensitive + enmascaramiento de valores + truncado a 80 caracteres en los tres sumideros, y las query strings ?token= también se enmascaran. El paso de hermes ignoraba inputs.app_dir y hardcodeaba /opt/career-ops/src/.env. public_url llegaba a curl sin comprobación https (inyección de opción mediante un - inicial). Y la guardia anti-downgrade — el arreglo del incidente v1.238.0 — no tenía puerta ejecutable: seis pins nuevos en ci-workflows (refs sin defaults, la guardia corre antes de Deploy, regex estricta de refs).
  • Cola del modal de importación (revisión SPA). El modal arma la misma guardia de navegación que el modal de outcome — alejarse de la página ya no lo deja huérfano encima de otra vista. El input de archivo oculto se elimina en cancel, no solo en change — los cierres repetidos ya no filtran inputs a document.body. plan.duplicates se lee por la forma con guardia en la vista previa. La asignación de num del tracker usa reduce en lugar de Math.max(...spread) — el spread alcanza el límite de argumentos alrededor de ~100k filas.

Cambiado

  • Docs: la viñeta «118 EN adapters» de la guía de help ahora lleva el conteo en fr/it/ta (las otras 15 locales ya lo tenían). Los precios de los proveedores en cvstart.org se reverificaron contra las páginas oficiales de precios — los cuatro sin cambios, CHECKED_ON → 2026-10-30. Un barrido de deriva post-release arregló 42 líneas obsoletas de conteo/versión entre README ×19, CONTEXT.md, CLAUDE.md y los manuales SDD (restos de la era 109/104 y de la v1.262.0).

Notas

  • No-acción deliberada: el window.confirm nativo de cv-studio (era CAR-179) se queda — convertirlo a UI.confirm es trabajo posterior, no un ítem de parche; el tooltip de frases hechas <mark title> sigue siendo solo tooltip (suelo de a11y aceptable); remote-qa.yml conserva cancel-in-progress: true — re-despachar sustituye deliberadamente una regresión en curso.
  • remote-QA completo en prod v1.262.1: routes ×19 ✓ · site ×19 ✓ · links ✓ · LLM en vivo 18/19 locales A–G ✓ — pl pilló un único timeout de proveedor 502 · 300 s · 0 ch (no es territorio denso en tokens; la re-corrida puntual viaja con este release).
  • Tests 6841 → 6867 en la rama de release (6864 pass / 0 fail / 3 skip, +26 vs v1.262.1) · Playwright 176 · cobertura 98.42 % líneas / 89.94 % ramas · registro sin cambios: 123 sources = 118 EN + 5 RU · 118 adapters.

[1.262.1] — 2026-10-29

El parche de evaluación en italiano: it se une a las locales densas en tokens (techo de 32768 tokens + reloj de 480 s) después de que la era OpenRouter la pillara cortando el informe justo antes del bloque SCORE_SUMMARY — y la herramienta de cambio de proveedor que hizo posible el diagnóstico llega en el deploy workflow.

Corregido

  • Evaluación en it cortada antes de SCORE_SUMMARY (cola de CAR-190): remote-QA 40928864980 (primera corrida con los créditos recargados) — una evaluación en it corrió 264 s y volvió con 15 045 caracteres y tok ~15635 en el techo de 16 384: bloques A–G intactos, ---SCORE_SUMMARY--- desaparecido. La misma clase de forma que le ganó a ta su techo en v1.261.1 (CAR-174). it entra en TOKEN_DENSE_EVAL_LOCALES (ta, hi → ta, hi, it): se aplican tanto el presupuesto de 32 768 tokens como el reloj de 480 s, y /api/evaluate + auto-pipeline siguen sobre la única fuente de verdad en llm.mjs.
  • Informe en en sin bloques — un flake de generación puntual: en la misma corrida una única evaluación en en volvió con el idioma correcto pero sin ninguno de los bloques A–G; una re-corrida dirigida pasó limpia (21 091 caracteres, A–G ✓). No se añade al conjunto denso en tokens — un flake no es un historial.

Añadido

  • Deploy workflow: mode=hermes y mode=provider (cola operativa de CAR-190): mode=hermes reinicia la pasarela Hermes e imprime un diagnóstico completo (unit file con los secretos limpiados, listeners antes/después, journal de sudo, sondas por puerto, la propia vista de HERMES_BASE_URL de la app, saldo de créditos de OpenRouter); mode=provider cambia LLM_PROVIDER con una guardia de presencia de clave (se niega cuando falta el *_API_KEY del proveedor destino) y reinicia la app — así fue como prod pasó a openrouter cuando los créditos de Hermes se agotaron en pleno vuelo. La guardia de refs del deploy-mode solo se valida para mode=deploy, así que los despachos de recon/verify vuelven a funcionar (el cambio de la era v1.261.3 de inputs requeridos los había silenciado).

Notas

  • No-acción deliberada: en no se añade al conjunto denso en tokens — un informe sin bloques es un flake de generación, no un historial de volumen.
  • No-acción deliberada: la directiva de idioma reforzada de ta no se extiende a it — no se observó deriva de idioma en italiano.
  • No-acción deliberada: grabar el nuevo vídeo demo de UI-v2 sigue siendo cosa del propietario (CAR-141) — la landing ahora incrusta vía youtube-nocookie.com de todos modos.
  • Tests 6841 → 6841 en la rama de release.

[1.262.0] — 2026-10-29

El minor de funciones upstream: el board de empleo Deel se une al escáner (registro 123), el CSV de otro tracker se importa directamente en #/tracker, la verificación de integridad del JD del posting-guard y el chequeo de voz del CV aterrizan como relays/lints, el escáner aprende un filtro de años de experiencia, y los botones de Apply en siete idiomas más dejan de leerse como ausentes — además, el modelo por defecto retirado de MiniMax se reemplaza y los reposts dejan de fabricarse entre fuentes.

Añadido

  • Fuente del board Deel (CAR-184): registro 123 fuentes = 118 EN + 5 RU · 118 adaptadores — autodetecta boards de tenants jobs.deel.com/<slug> (Klarna y otros); el payload flight de Next.js trae todo el inventario en una sola petición (títulos, ubicaciones, modalidad, fechas, descripción), la ruta /api/ prohibida jamás se toca, y un tenant que publica sus carreras en otro lugar responde con una redirección que la fuente rechaza por nombre; la compensación se deja deliberadamente sin mapear (importes sin intervalo de pago serían una adivinanza); gates de recuento ×5 + help §17 ×19.
  • Importación CSV del tracker (CAR-181): POST /api/tracker/import-csv con vista previa de dry-run y un commit bajo lock — entrecomillado RFC-4180, fechas en formatos ISO / día-primero / nombre de mes, etapas de trackers ajenos plegadas hacia los estados canónicos (una etapa desconocida aterriza como Evaluated con una nota, nunca se descarta), dedup por URL de publicación normalizada leída de la celda del enlace markdown o de la columna Notes, límites de 512 KB / 2000 filas; #/tracker gana un botón Import CSV con modal de vista previa, i18n ×19.
  • Relay del posting guard (CAR-182): POST /api/runners/posting-guard — la compuerta determinista y sin LLM de integridad del JD del proyecto padre, resuelta por número de informe mediante la regla de prefijo de las capturas de jds/ (con relleno o sin él, comparada como entero) o una ruta saneada explícita; el hijo recibe solo argv, un techo de 30 s y un contrato fail-soft; una acción «Posting guard» en #/reports/:slug renderiza los hallazgos, i18n ×19.
  • Chequeo de voz del CV (CAR-188): 27 frases hechas de currículum (passionate about, proven track record, leveraged, spearheaded, delve…) más heurísticas de guiones (un rango de años como 2022–2024 no es señal; 20–30% sí lo es) como un POST /api/cv-studio/voice-check de solo lectura y una tarjeta #/cv-studio que resalta las coincidencias exactas; la lista de verificación de Tailor crece hasta seis invariantes; i18n ×19.
  • Filtro de años de experiencia del scan (CAR-180): el parser jd-years (65 formulaciones, nunca descarta cuando no está seguro) lee el mínimo declarado en las descripciones de las ofertas, una nueva opción years_min: de portals salta las ofertas sin cualificación suficiente como skipped_experience (un estado de historial observacional que jamás fija una URL), y yearsMin viaja en el snapshot del scan para una futura UI.
  • Controles de Apply en siete idiomas más (CAR-185/189): vietnamita Nộp đơn ứng tuyển, búlgaro Кандидатствай/Кандидатстване/Външно кандидатстване (el sustantivo anclado al inicio de la etiqueta — los enlaces a artículos de consejos no son Apply), neerlandés Solliciteer/Solliciteren, español ¡Apúntame!/Postularse (etiquetas, no el verbo — los enlaces de FAQ dejan de importar), chino 申请职位/投递 (anclado al control completo) — las ofertas activas de esos boards dejan de leerse como no_apply_control.

Corregido

  • Modelo por defecto de MiniMax (CAR-187): el MiniMax-Text-01 retirado como defecto se reemplaza por MiniMax-M3.1-Flash en api.minimax.io y MiniMax-M3 en api.minimax.cn; la URL base queda anclada al proveedor (un host ajeno al proveedor se rechaza en runtime y la clave jamás llega hasta él); la ayuda de ajustes ×19 nombra los nuevos defectos.
  • Los reposts exigen la misma fuente (CAR-186): un rol publicado en LinkedIn y en el Greenhouse del empleador ya no se reporta como repost — los clústeres ahora se dividen por sourceHost(), que pliega subdominios regionales solo en sitios conocidos (de.linkedin.com ≡ www.linkedin.com, pero ca.example.com queda aparte), pliega los alias de host de Greenhouse en una sola fuente y elimina un punto final suelto.
  • Deriva de idioma en evaluaciones en tamil (CAR-183): una directiva de tamil reforzada vive ahora en la cola volátil del prompt junto al presupuesto de volumen (el hueco de CAR-174) — los informes de remote-QA vuelven a aterrizar en tamil tras la deriva intermitente vista en v1.261.2.

Notas

  • No-acción deliberada: el relay del posting-guard se entrega en fail-soft — el fork todavía no lleva el posting-guard.mjs del proyecto padre (el PR upstream #6146 sigue abierto), así que el endpoint responde {available:false, reason:"script-not-found"} hasta que un sync del padre lo traiga.
  • No-acción deliberada: yearsMin no se expone como chip de UI en #/scan en este release — el campo viaja en el snapshot y el filtro vive solo en la configuración de portals.
  • No-acción deliberada: los cambios de Machine Summary de modes/reports upstream (#6161/#6160/#6157) no se portan — llegan con el proyecto padre desplegado, no como mirrors de web-ui.
  • Tests 6734 → 6841 en la rama de release; la clave de dedup del escáner no se toca (el comportamiento de normalización de URLs no cambió).

[1.261.3] — 2026-10-28

El parche de cierre de diseño: fase 3 del diseño Lovable (densidad + retirada completa del coral), el host-pool del scan (32 empresas, 10 por host), seis correcciones de location en proveedores upstream, la fuente Trakstar Hire (registro 122), undo + guarda de cambios sin guardar en CV Studio — más las correcciones P3 de la regresión final y la paridad de documentación.

Corregido

  • Etiqueta «related» de los clústeres del mapa 3.16:1 → 4.68:1 (ambos temas): el --map-score-mid claro se oscureció a #567b75 (el #5f7f7a sugerido solo medía 4.37 — hacía falta un paso más oscuro); el lado oscuro ya estaba en 4.98:1, intacto. Pin rojo-primero en map-cluster-visual.
  • Anillo de foco del skip-link: el último contorno literal #ff385c → var(--accent-c) — los 78 anillos de foco siguen ahora el token de acento.
  • Topología de acento de .tab-btn.is-active (#584, fase 3): el control mezclaba tres familias de color (tinte coral + texto danger + borde azul); ahora un único par accent-soft.
  • Correcciones de location en proveedores upstream ×5 (#580, CAR-176): flag Fully Remote de workday (no es un lugar), fan-out de telecommuting de workable + estado solo cuando no hay ciudad, coma final de successfactors, frontera de palabra para remote en lever, etiquetas con prefijo Location en icims.
  • http-json: sin reintento en un timeout agotado (#580) — un aborto por deadline es no transitorio en ambos helpers de reintento (upstream midió ~94 s en un board de Ashby con 30 s×reintentos).
  • Paridad de documentación (#587): los últimos contadores de la era 17/18 en HANDOFF/CONVENTIONS/README (uk recibió su sección Локалізація que faltaba; la historia de § extendida hasta §32/§33 — aritmética de 33 H2 verificada).

Añadido

  • Fase 3 del diseño Lovable (#584): tokens de densidad activos — filas de tabla ≈32px, controles 30px, chips 24px (excepción de espaciado WCAG 2.5.8); cada residuo rgba coral movido a pares cv3 (.dash-hero, .badge-bad, halos, .tab-btn.is-active); --ease-out declarado, TOKEN_HANDOFF vacío; contrato .callout--ok/--danger.
  • Fuente Trakstar Hire (#583, CAR-177): registro 122 fuentes = 117 EN + 5 RU · 117 adaptadores — anclado al host jsapi.recruiterbox.com, recorrido por offset con paradas cortas/vacías/repetidas, parciales conservados, etiquetado trakstarTruncated; gates de recuento ×5 + help §17 ×19.
  • Host-pool del scan (#579, CAR-178): hasta 32 empresas en concurrencia, con tope de 10 por host (patrón keyed-limiter de upstream) — snapshot byte a byte idéntico a la ejecución secuencial (gate rojo-primero con mock de tiempos).
  • Undo + guarda de cambios sin guardar en CV Studio (#582, CAR-179): pila de undo acotada (30) sobre los resultados de ejecuciones de IA con Cmd/Ctrl+Z seguro en teclados no QWERTY, guarda de cambios sin guardar al salir de la ruta (dirty por live-probe, confirm/rewind/beforeunload), i18n ×19.
  • Correcciones de proveedores upstream probadas ×17 (parte de CAR-176); el resumen de remote-QA nombra las fuentes de escaneo que fallan + tok ~N por eval (#567, ya en v1.261.2 — la observabilidad se usó para el triage de este release).

Notas

  • El acento es ahora azul (concepto Lovable): acciones primarias/estados activos/anillos de foco siguen --cv3-accent; el rojo queda reservado para danger (.btn-danger, toast.error, texto de danger). El coral sobrevive solo en el asset PNG del logo (decisión del propietario).
  • Densidad: filas de tabla ≈32px / controles 30px / chips 24px — excepción de espaciado WCAG 2.5.8 documentada; el canary de 44px de v1.26.1 se reescribió sobre el contrato de tokens.
  • CodeQL: las 10 alertas abiertas cerradas (4 descartadas como FP con justificación: clases documentadas llmRateLimit/atomicWrite; 6 corregidas en el código fuente).
  • Tests 6655 → 6734 en la rama de release; ningún comportamiento de fuentes de escaneo cambió aparte de la nueva fuente Trakstar.

[1.261.2] — 2026-10-28

El parche de estabilidad + pulido: la caché de geocodificación sobrevive a un crash a mitad de escritura (CAR-173), UI.md renderiza continuaciones de viñetas y listas anidadas, las fugas de claves en el texto renderizado de los archivos de help quedan cerradas ×17, evaluate recibe un techo de tokens por idioma para ta (CAR-174), el chrome RTL se refleja mediante propiedades lógicas (85 hallazgos), las capturas del mapa se reencuadran ×19 y los clústeres del mapa pasan el contraste en oscuro — más la primera fase de la integración de diseño Lovable (hoja pasiva tokens-v3.css), backfill de documentación (revisión de release + specs) y exenciones de cobertura por varianza de nodos de CI.

Corregido

  • Pérdida de la caché de geocodificación por escritura rasgada (CAR-173, #562): ambas rutas de vaciado (insert-flush con debounce, exit-flush) escribían .cache/geocode.json con un writeFileSync desnudo — truncate-then-write — así que un crash, OOM o SIGKILL a mitad de escritura dejaba un archivo rasgado y el JSON.parse del siguiente arranque lanzaba, descartando en silencio toda la caché persistente (semanas de presupuesto de Nominatim), no solo la del día. Ambos puntos de escritura pasan ahora por file-lock.mjs::atomicWrite (temporal en el mismo directorio → rename → unlink en caso de fallo) — el mismo contrato que FULL-R1/FULL-M1 aplicaron a pipeline.md, cv.md, profile.yml, career-plan, la entrada de batch y memory. La maquinaria de vaciado de CAR-142 en sí (insert-flush con debounce, exit-flush, apagado acotado) quedó verificada intacta rojo→verde: los tests (a)/(b) fijan que los inserts llegan al archivo dentro de la ventana GEOCODE_SAVE_MS y que este sigue siendo JSON válido; (c) es una puerta de mecanismo más ejecuciones conductuales de ambas rutas de vaciado que afirman que no queda basura temporal y que el archivo es parseable.
  • Continuación de listas en UI.md (#563): el manejador de listas solo consumía líneas - /* consecutivas, así que una línea de continuación indentada tras una viñeta caía fuera del <ul> emitido y la pasada de párrafos se saltaba el fragmento — la continuación se fugaba como prosa cruda con marcadores **/backticks literales (~95 tramos colgantes por locale en los archivos de help; en.md 95, ru.md 104). Una línea de continuación — espacio en blanco inicial, no un bloque nuevo (encabezado, viñeta, placeholder de fence envuelto en NUL, en blanco) — se une ahora al <li> anterior como texto unido con <br/>, con inline() ejecutado por línea para que los marcadores de énfasis nunca se emparejen a través de la unión; la guarda del placeholder de fence es por primer carácter NUL, no por coincidencia de token desnudo. Las viñetas anidadas -, las listas numeradas, los fences dentro de listas y los encabezados dentro de listas siguen siendo un hueco documentado aparte (próxima ola de docs).
  • Fugas de claves en el texto renderizado de help ×17 (#561): remote-QA encontró 85 claves i18n crudas visibles en el help renderizado (causa raíz probada con repro: el mismo consumo de viñetas de una sola línea de UI.md — una continuación ajustada emitida como texto crudo mantenía sus backticks literales; las secciones de help de v1.260/261 escribían viñetas multilínea). 163 viñetas desplegadas a una sola línea en 17 locales — solo espacios en blanco, ni una palabra traducida tocada (verificado viejo-contra-nuevo). Nueva puerta tests/help-rendered-keys.test.mjs que renderiza cada bundle por la cadena real (stripGithubOnlyBlocks + UI.md cargado en vm) y falla si cualquiera de las 1711 claves aparece en el texto; 17 locales rojo→verde. Las herramientas de repro se envían en el repo: scripts/remote-qa/help-render-scan.mjs (vm) + help-render-crosscheck.mjs (DOM real).
  • El Ctrl+K de remote-QA afirma el contrato de paleta de CAR-122 (#561): prod-regression.mjs espera ahora la paleta de comandos tras Ctrl+K (.nav-v2-palette[role=dialog] + foco en nav-v2-input) O el foco de búsqueda legacy como fallback, en lugar de aceptar silenciosamente cualquiera; el unit tests/remote-qa-ctrl-k.test.mjs fija 6 escenarios sobre un DOM simulado, 0/6 → 6/6 rojo→verde.
  • Encuadre de las capturas del mapa ×19 (#560): informe del propietario sobre images/job-map-en.png — media búsqueda de la topbar + un h1 cortado arriba, una franja gris de la lista lateral en el borde derecho, un clúster de marcadores cortado por el marco cerca de Londres y una línea blanca horizontal sobre el mapa a ~2/3 de altura. capture-map-screenshots.mjs espera ahora el drenaje de la cola de geocodificación (located + noLoc === total, no el contador N/N — las filas Remote están estructuralmente sin ubicación, así que la espera antigua nunca podía completarse), oculta header.topbar y #notif-drawer para la toma, recorta la caja exacta de .job-map-page (ningún panel cortado por el borde del marco), aleja el zoom dos pasos para que ningún clúster quede medio cortado, fija la altura del mapa y toma una captura de viewport plana (captureBeyondViewport remuestrea la capa de tiles compuesta y pinta franjas blancas en los límites de fila de tiles), y alinea el contenedor del mapa a enteros. Las 19 capturas de job-map re-tomadas.
  • Burbujas de clúster del mapa — contraste en tema oscuro + ajuste de etiqueta (#572): barrido del propietario P2/P3 — los anillos de clúster en tema oscuro reposaban sobre un relleno blanco fijo (1,83:1); los anillos toman ahora var(--paper) con dígitos de la paleta de puntuación (medido 4,98:1+, relleno 9,53:1; la banda más baja es matemáticamente incolorable en oscuro y mantiene 6,1:1 mediante una ruta honesta data-dim), y la etiqueta related cabe en su burbuja (ajuste de fuente por rAF, los números nunca se encogen).
  • #/scan cambio de idioma — re-armado de skeletons (seguimiento de #534, agente en vuelo, aterriza antes del tag): seguimiento del fix del propietario en v1.261.0 (#534) — los skeletons re-armados en un repintado por cambio de idioma ya no sobreviven a un refetch completado.
  • Deriva de docs (CAR-139, agente en vuelo, aterriza antes del tag): contadores de README ×19, HANDOFF ×19, hechos de CONVENTIONS resincronizados con el estado vivo del registro/tests.

Añadido

  • Revisión de release + backfill de specs (docs, #559/#558): docs/reviews/REVIEW-2026-10-28-v1.261.1.md (la ola v1.261.0/v1.261.1 revisada contra sus specs) y el backfill de specs SDD para la misma ola — la cola del BACKLOG nombra ahora el hueco restante (tests dedicados para sources/habr.mjs).
  • Barrido profundo RTL (#571): el harness de AR recorre 39 rutas × LTR-vs-RTL (espejo X, desbordamiento, recortes, firmas de text-align) — 85 hallazgos corregidos: el chrome fijo (toast, sidebar, drawer + su × de descarte, docs-FAB, usage-HUD, skip-link) movido a inset-inline-*/border-inline-*, las tablas a text-align: start/end en 17 archivos de vistas + 2 libs, el genérico [dir=rtl] text-align:right retirado (peleaba con el centrado de la columna de estrellas del scan), y el aislamiento de la columna de puntuación de CAR-58 movido a un span ltrNum.
  • Harness de a11y de teclado (#570): tests/playwright-a11y-keyboard.mjs recorre 11 rutas (orden/trampas de Tab, indicadores de foco, overlays con Escape, Enter/Espacio) — 0 defectos de producción; la cobertura de regresión es nueva.
  • Barrido de sin-traducir en i18n (#565): 36 valores idénticos a en traducidos ×9 locales (*.rolePh, claves de config, manifiesto ta) + una guarda de barrido de allowlist cerrada; auditoría i18n del site (cvstart.org) (#568): 20 claves × 18 locales traducidas (todo el bloque demo.*/stats.t.*/showcase.tab* estaba en inglés en todas partes), 1 clave muerta retirada ×19.
  • Observabilidad de remote-QA (CAR-175, #567): el resumen nombra las fuentes de escaneo que fallan (muestras de stderr, redactar-antes-de-truncar) e imprime tok ~N desde el uso del proveedor; la lógica vive en un summary-parts.mjs puro con una puerta de deriva.
  • Listas de marcador anidadas en UI.md (un nivel, #564): las viñetas - se renderizan como un <ul> anidado (seguro frente al placeholder de fence) — los últimos 2 tramos de prosa colgantes por locale desaparecen.
  • Integración de diseño Lovable — fase 1 (#566) / fase 2 (en rama, aterriza antes del tag): public/css/tokens-v3.css porta el concepto Lovable al pie de la letra (superficies oklch 0–3, pares de tinta anotados AA, familia de acento, soft+ink para ok/warn/danger/info/neutral, bandas de puntuación A–G ×2 temas, densidad + foco + sombras); la fase 2 lo activa sobre los nombres de token existentes (acento dividido blue/danger, bandas de puntuación + callouts sobre tokens cv3) con las puertas de contraste/rol/oscuro actualizadas.
  • Techo de tokens por idioma en evaluate (CAR-174, #569): evalMaxTokens(lang) — ta/hi piden 32768 (los tres clientes SDK re-acotaban a 16384; ambas capas corregidas), más un presupuesto de volumen tamil en el prompt (≤5 frases por bloque, ≤900 palabras) — el informe ta del remote-QA de v1.261.1 quedó cortado en el límite de tokens de salida (sin Block B–G + SCORE_SUMMARY).
  • Continuación de fixes reales de CodeQL: la nueva herramienta de render de QA sustituyó el manejo manual de entidades por pasadas de división estructurales + htmlToText({cap: Infinity}); las aserciones de redacción fijan la salida enmascarada exacta; constantes sin uso del harness RTL retiradas.
  • Política de exención de cobertura para la varianza de nodos de CI (habr #558, states #561): sources/habr.mjs (branch 87.23 en la node-matrix de CI vs local ≥89) y states.mjs (CI 86.49 vs local ≥89) se suman a las exenciones en scripts/coverage-baseline.json — los archivos cuya cobertura de branch medida en CI queda estructuralmente por debajo del suelo local por varianza de versión de node quedan exentos en lugar de bajar el trinquete de toda la puerta.

Notas

  • Contrato de Ctrl+K (CAR-122): el acorde abre la paleta de comandos — NO enfoca directamente la caja de búsqueda; la búsqueda se enfoca desde la paleta. La regresión de prod de remote-QA afirma ahora exactamente esto (#561).
  • Descarte de FP de CodeQL ×5: las alertas js/missing-rate-limiting en main esperan el token security_events — acción del operador, no un cambio de código.
  • Aterrizados tras el corte de rama: #561/#562/#563/#564/#565/#566/#567/#569/#570/#571/#572 están todos fusionados en main; los agentes #534-skeleton y CAR-139 se fusionan antes del tag. Las listas de marcador anidadas a más de un nivel y la geometría de vistas densa en inline siguen siendo huecos documentados.
  • Tests 6609 en la rama de release (+15 desde v1.261.1; ~13 más de #561 al fusionar — 2 archivos de puertas nuevos); los únicos tests en rojo antes del fan-out son las puertas de paridad de CHANGELOG ×19 (el fan-out de locales es el siguiente paso del orquestador); no se toca ninguna fuente del escáner (registro 121 = 116 EN + 5 RU · 116 adaptadores).

[1.261.1] — 2026-10-28

El parche de cliente del QA en producción: una corrección P1 de crash en #/two-pager, Ctrl+K funcionando en distribuciones de teclado no QWERTY, la ola móvil de 390px (desbordamiento de docs-assistant, solapamiento de chips del mapa, un harness de 39 rutas), la finalización de CAR-137 (errores específicos, v2empty ×5, hints ×13 + mode pages) y CAR-140 aterrizando capturas v2 reales en todas partes.

Corregido

  • TDZ de two-pager (CAR-170, P1, #554): Cannot access 'previewTimer' before initialization — el editor de etiquetas guardadas disparaba onChange durante la construcción por cada chip restaurado mientras el bucle de construcción quedaba léxicamente por encima de let previewTimer, así que todo el render de #/two-pager crasheaba (sin h1, ×19 locales). El scheduler + el binding viven ahora por encima del bucle; un test conductual ejecuta el módulo real de la vista — el error exacto de producción antes del fix, una página renderizada después — más una puerta de deriva de orden léxico.
  • Ctrl+K en distribuciones de teclado no QWERTY (CAR-172, #554): ambos manejadores de acorde comparaban solo e.key — en una distribución rusa el acorde llega como key='л' code='KeyK' y no hacía nada. Ahora se compara por e.code === 'KeyK' en nav-v2.js y app.js; reproducido en un navegador real con un acorde sintético ‘л’/KeyK. El contrato de CAR-122 se reafirma (ver Notas).
  • Desbordamiento de docs-assistant a 390px (CAR-172, #554): los chips iniciales de CAR-136 eran .btn (nowrap, flex-shrink: 0) — un elemento indivisible de 418px empujaba #/docs-assistant 56px más allá de un viewport de 390px; el patrón de encogimiento de CAR-146 (en v2-llm.css, las puertas CSS de 800 LOC quedan intactas) lo lleva a 0.
  • Solapamiento de chips del mapa a 390px (#552): en un viewport de 390px los chips meta del mapa se solapaban con la fila de CTA (map.css).
  • CAR-137 — errores específicos + v2empty ×5 (#550): cinco fallbacks de error genéricos (‘Error’ / ‘failed’) pasaron a ser claves específicas y accionables — fallo del POST de evaluate, stream de auto-pipeline sin evento terminal, añadido desde scan→pipeline, carga de clústeres de reposts, undo del pipeline; los últimos cinco estados vacíos planos (#/reports, #/usage, #/portals, #/activity, #/dashboard) se suman al patrón v2empty de CAR-124 (explicación + ejemplo etiquetado aria-hidden + acción pre-conectada; 15 claves v2empty.* nuevas, cuatro claves muertas de una línea retiradas) — todo ×19 diccionarios, snapshot regenerado.
  • CAR-137 — HelpHint ×13 + kit de mode-pages (#550): la hint contextual «?» vive ahora en el H1 de cada página restante (pipeline, reports, dashboard, cv, portals, usage, activity, assessments, map, health, docs-assistant, settings) y en las 8 mode pages vía el kit (cada cfg de MODES lleva un hintKey, renderizado a través de HelpHint.title).

Añadido

  • Harness móvil-390 (#552): un barrido de Playwright por cada ruta del SPA (31 canónicas + 8 mode pages = 39 hashes) a 390×844 con un corpus sembrado — comprobación de desbordamiento horizontal por ruta (scrollWidth vs clientWidth, tolerancia de 1px, nombra al infractor más ancho) más una comprobación de impacto en CTA (ninguna barra sticky/drawer/FAB cubre un CTA primario); opt-in, skip elegante sin binario de navegador, sin red.
  • Capturas v2 frescas ×48 (CAR-140 fase A, #551): capturas de pantalla reales de la UI v2 desde un fixture CI-aislado sembrado — dashboard ×19 + job-map ×19 (job-map-ta primera aparición, job-map-vi nueva) + scan/pipeline/tracker/reports × en/ru — que alimentan el README, el help y la landing.
  • Capturas reales en cvstart.org (CAR-140 fase B, #553): los huecos placeholder de CAR-140 desaparecieron — la landing muestra las capturas v2 reales (showcase del job-map + filas de capturas, ×19 locales de la landing).
  • Hints de CAR-137 (#550): 21 claves traducidas nuevas ×19 (help.hint.* ×20 + los hintKey de las mode pages) — cada página se explica ahora in situ.

Notas

  • Contrato de Ctrl+K (CAR-122): el acorde abre la paleta de comandos — NO enfoca directamente la caja de búsqueda; la búsqueda se enfoca desde la paleta. La línea «Ctrl+K фокусит поиск» del checklist de QA de v1.261.0 queda sustituida; el QA prompt de v1.261.1 lleva la comprobación actualizada.
  • Fixes reales de CodeQL (#547): 4 alertas cerradas (la iteración por claves literales en content.mjs hecha estructural, barrido de lint) — aterrizaron post-release en el main de v1.261.0 antes de este parche; listadas aquí por completitud.
  • Tests 6582 (+51); suite de navegador 171–172 pases / 0 fallos / 1 skip opcional; el harness móvil-390 se ejecuta de forma independiente (node tests/playwright-mobile-390.mjs). No se toca ninguna fuente del escáner (registro 121 = 116 EN + 5 RU · 116 adaptadores).

[1.261.0] — 2026-10-28

Ola de revisión completa cerrada (aterrizaron todos los hallazgos HIGH + MEDIUM), la corrección del cambio de idioma reportada por el propietario, correcciones de calidad de la evaluación, fan-out de help ×18, la landing v2 de cvstart.org — más 4 specs SDD/OpenSpec que documentan la ola. Cada corrección aterrizó mediante un fix-forward rojo→verde y una revisión GLM-5.3-Flash con contexto fresco.

Seguridad

  • FULL-R1 — puerta CSRF para acting-GET + escrituras atómicas del pipeline bajo bloqueo (#519): puerta Sec-Fetch-Site/Origin/Referer en /api/stream/*, /api/run/*, /api/geocode (ACTING_GET exportado, puerta de cobertura estática); toda lectura-modificación-escritura de data/pipeline.md / data/last-scan.json bajo withFileLock + el nuevo atomicWrite (temporal en el mismo directorio → rename → unlink en caso de fallo).
  • FULL-R2 — clúster SSRF en las fuentes (#514): rss.mjs con https + rechazo de privadas; ~25 fuentes con fetchImpl desnudo → transportes protegidos; mirrors fijados; validación de redirecciones para habr/hh/geekjob/getmatch; delay(ms, signal) compartido.
  • FULL-R4 — corrupción de prosa en stripDangerousMarkdown (#513/#518): el borrado de esquemas ocurre solo en contextos de enlace/atributo; las entidades se descodifican en una copia de solo detección («JavaScript: React, Node» y «R&D» sobreviven).
  • FULL-M2 — endurecimiento del transporte (#520): safeGet honra timeoutMs junto a la señal del llamador (controlador puente); guardResolvedHost falla CERRADO ante un error de DNS. #525 añade el borrado de cabeceras de credenciales en redirecciones cross-origin, el desacoplamiento del listener del transporte en cada ruta de asentamiento y la corrección de un crash con señal pre-abortada.
  • FULL-M4 — logo/batch (#523): el logo fija los magic bytes en TODAS las rutas (un image/png mentiroso no puede llevar un SVG), acota el content-type a image/* al almacenar, caché privada + nosniff; batch: --parallel acotado a 1–8, --min-score validado, BATCH_BUSY en single-flight, llmRateLimit en las mutaciones de config/batch.

Añadido

  • CAR-147 — techo de evaluación consciente del idioma (#524): evalTimeoutMs(lang) — ta/hi obtienen 480 s en lugar de 300 s (las escrituras índicas tokenizan varios tokens por sílaba; el muro anterior producía 502 + un informe vacío); el cliente de remote-QA espera 540 s.
  • CAR-133 — landing v2 de cvstart.org (#532): hero parallax con puerta de reduced-motion, una demo SIMULADA claramente etiquetada del flujo scan→evaluate→tracker, secciones scroll-reveal con recuentos verificados en vivo, huecos placeholder para las capturas de CAR-140.
  • CAR-135 — help reescrito para la UI v2, fan-out ×18 completo (#533, #540, #541, #542): los 19 archivos de help describen el shell v2 (nav plegable, paleta Cmd/Ctrl+K, skeletons, estados vacíos de ejemplo+acción, chrome del scan v2, pipeline agrupado+undo, reports v2, kanban); las etiquetas de la UI citan sus claves i18n; la frase de las 121 fuentes se genera en vivo desde el registro.
  • Specs (SDD/OpenSpec) (#535): backfill de la wave-two del oct27 + tres specs prospectivas (fan-out de help, hand-offs de escritura, invariantes del cambio de idioma).

Corregido

  • El cambio de idioma ya no deja #/scan en blanco (#534) — reportado por el propietario: el estado de escaneo local de la vista (resultados, chips de facetas) es ahora a nivel de módulo; harness conductual de Playwright con un refetch retrasado 4 s (rojo en la forma antigua).
  • CAR-148/149 — los informes ru con «missing Block G» (#530): el prompt enseñaba tres mapas A–G incoherentes entre sí; esqueleto + lista + ejemplo concuerdan ahora con oferta.md (G = Posting Legitimacy); puerta de deriva conductual.
  • CAR-150 — el undo del pipeline conserva los metadatos de comp (#537): DELETE responde comp, POST acepta un comp opcional con puerta, la vista arrastra {token, comp} por todas las ramas de fallback.
  • FULL-M3 — fuentes muertas informando tableros honestos (#522): getro/mycareersfuture/meituan/mokahr/glints lanzan error ante una page-1 malformada (una API muerta ya no se lee como «saludable, 0 vacantes»); pinnedOverride fija los overrides de company.api de 18 adaptadores.
  • FULL-M5 — una sola cascada de proveedores (#521): routes/llm.mjs pasa por runActiveProvider (+cachePrefix, señal); intercepción del subprocess de Gemini; contratos de respuesta byte-idénticos.
  • FULL-M1 — escrituras de archivos compartidos bajo bloqueo (#526, #539): runTrackedScript + roster TRACKER_MUTATING; los spawns de outcome/followup retienen el bloqueo; tracker.mjs escribe vía atomicWrite; los PUT guardan con bloqueo+atomicidad.
  • FULL-M6 — consolidación de servidor + cliente (#531, #536, #525): dotenv con prototipo nulo + rechazo de __proto__; isPrivateOrLoopback cubre NAT64/6to4/benchmark/reserved; caché mtime en env-config; GC real de slots en file-lock; safeGet con timeout+señal; guarda DNS fail-closed; SSE ×4 → API.postStream; libs de focus/labels/status; clipboard con await; tinte Hired en el dashboard.
  • R3 — deriva de tokens CSS (#527): --accent resuelto (los anillos de foco del mapa estaban muertos en silencio); 11 duraciones fuera de token plegadas; puerta de deriva.
  • R4 — rezagados de la carga (#538): assessments/mode-page/pipeline preview renderizan los skeletons de CAR-112.
  • Lote de CodeQL + CI (#517, fix #539): imports sin usar, la puerta ACTING_GET importa la regex real, slice de lifecycle re-anclado.

Notas

  • A propósito no hecho: el pinning de DNS en el momento de la conexión (requiere undici — fuera de las dependencias, residual documentado); las capturas de CAR-140 (necesitan la UI v2 desplegada); CAR-137/138/141 (próxima pasada de docs); la clase js/missing-rate-limiting de CodeQL es un falso positivo documentado (el limitador propio no recibe crédito).
  • Fan-out de help ×18: la wave-1 de es/de se saltó la nota de intro y el delta de la tarjeta Active Companies (de aterrizó su propia intro; es recibe un seguimiento de una línea).

[1.260.0] — 2026-10-26

Tren de endurecimiento por revisión, ocho fusiones (#491–#498) de las rondas opus-5-5 y AI-review sobre la flota de v1.259.0: normalización salarial, fechas de informe acotadas al preámbulo, robustez del geocodificador, cache-busting del bundle r2+r3 y la corrección del desbordamiento de deep — más dos v2 de interfaz (pipeline, reports). Cada corrección aterrizó mediante un fix-forward rojo→verde.

Corregido

  • Normalización de la comparación salarial (CAR-143 rev-2): los marcadores de periodo se reconocen solo junto a la cifra a la que pertenecen — «6 month contract, $120k» ya no se anualiza ×12, y las formas de periodo RU/ES adosadas («за месяц» junto al número) ganan a las palabras incidentales; la familia de símbolos $/¥ se reconoce pero queda sin mapear salvo que un código ISO explícito comparta la línea (los dólares AUD/CA/SG ya no se facturan como USD, y una forma ¥ RMB recibe un aviso explícito en lugar de una conversión a JPY desviada ~20×); los números desnudos 19xx/20xx son siempre años; las cifras de un rango heredan el periodo de su pareja; el minimum y el target_range del perfil nunca se empalman en un solo rango.
  • Acotado de fixReportDate (CAR-144 rev-2): la corrección de fecha fijada por el servidor se aplica solo al preámbulo anterior al primer encabezado ## (un informe con título H2 recibe un no-op conservador — una fecha desfasada es aceptable, una inventada no); dentro del preámbulo: un candidato único se corrige; si no, un único **Date:** en negrita; si no, nada (dos candidatos en negrita o dos en texto claro no reescriben ninguno). La puerta FIX-5 de etiquetas PDF lee ahora el diccionario ensamblado real — si falta un idioma, lanza error en lugar de pasar en silencio con el fallback inglés.
  • Chip de ubicación de Habr + robustez del geocodificador (CAR-142 rev-2): un chip meta de Habr se convierte en ciudad solo con evidencia estructural (un enlace de filtro de ciudad o un icono de ubicación) — los avisos de reubicación («Возможна релокация») y los tipos de empleo nunca se hacen pasar por ubicaciones; la caché del geocodificador sobrevive ahora a un reinicio (volcado en beforeExit/exit/SIGTERM+SIGINT con re-lanzamiento de la señal, la marca dirty se limpia solo cuando la escritura se ha asentado), el apagado es acotado (latch del bucle de salida, contador de generación, LRU por recencia) y un presupuesto de geocodificación agotado responde 503 + Retry-After en lugar de un 502 genérico.
  • Cache-busting del bundle (CAR-145 r2+r3): el shell cita /js/bundle.js?v=<content-hash> — inmutable si coincide, no-store si no — matando la ventana de 5 minutos del despliegue en la que un shell recién servido se emparejaba con un bundle antiguo cacheado. El hash y el flujo comparten un único descriptor de archivo (sin lecturas rotas); una lectura corta responde null en lugar de un hash de fallback por stat que podía envenenar un despliegue entero; make-bundle escribe la identidad y los compañeros .br/.gz mediante un temporal con punto delante + rename, así que una build eliminada a mitad no puede servir bytes a medio escribir ni filtrar su archivo temporal.
  • Desbordamiento de deep (CAR-146): un brief de investigación guardado cuyo nombre de archivo por sí solo excede el viewport de un teléfono ya no puede empujar #/deep más allá de 390px — la tarjeta se limita a su fila, el título se ajusta con salto de línea y la fecha permanece en una sola línea; el barrido de viewport estrecho recorre ahora #/deep con un fixture sembrado demasiado largo (el único hallazgo de las dos ejecuciones de remote-QA, cerrado).
  • Endurecimiento del pipeline v2 (revisión de CAR-116): los borrados confirman y se pueden deshacer (con un texto de deshacer honesto), sin fabricación de la empresa anfitriona en las tarjetas, ids de fila FNV-1a, recuentos masivos verdaderos y una guarda de reentrada en las operaciones masivas.

Añadido

  • Pipeline v2 (CAR-116): tarjetas enriquecidas, una vista agrupada y descarte masivo con deshacer; 17 claves pipe.* nuevas llegaron traducidas en los 19 diccionarios.
  • Reports v2 (CAR-118): búsqueda en vivo con filtros por banda de puntuación/legitimidad/empresa/fecha, encabezados ordenables (aria-sort), una vista de tarjetas persistente con botones de acción por banda, un histograma de puntuaciones con clic-para-filtrar y el renderizado unificado «Company — Role» / «x.x/5» (window.ReportOps); 31 claves rep2.* llegaron traducidas en los 19 diccionarios.

Notas

  • Cada corrección pasó una ronda de revisión con evidencia rojo→verde; los dos hallazgos HIGH de AI-review sobre el primer bundle r2 (hash de lectura corta, atomicidad de los compañeros) quedan cerrados por r3, y las rondas de CAR-143/CAR-144/CAR-142 reverificaron cada una los hallazgos abiertos de su predecesora.
  • A propósito no hecho: la vista de clústeres del mapa sigue siendo CAR-125 (en cola); la restauración en el servidor de los metadatos del pipeline es un ticket aparte; sin correcciones retroactivas para el contenido guardado bajo los estados pre-rev-2 de v1.259.0.
  • Tests 5988 (+165); suite de navegador 152 pases / 0 fallos / 1 skip opcional. No se toca ninguna fuente del escáner (registro 121 = 116 EN + 5 RU · 116 adaptadores); las 48 claves i18n nuevas aterrizaron con su fan-out ×19 dentro de los propios PR de funcionalidad.

[1.259.0] — 2026-10-25

Segundo tren de la flota de agentes, seis correcciones: las fechas de los informes las fija el servidor, el geocodificador del mapa cachea y lee ciudades rusas (RU), la ayuda ×18 cita las etiquetas de botón localizadas, las frases de la interfaz dejan de mezclar idiomas, las comparaciones salariales se calculan en el servidor — y el bundle de JS se distribuye precomprimido (carga en frío 3.1 MB → 598 KB).

Corregido

  • Fechas de informe inventadas (CAR-101): el prompt de evaluación fija ahora la fecha de hoy del lado del servidor («el campo Date: DEBE ser exactamente — cópiala verbatim; nunca la inventes»), y fixReportDate reescribe una fecha desviada a hoy en cada punto de aceptación (rama Anthropic, subproceso Gemini, cola compatible con OpenAI, /api/deep, auto-pipeline) con un aviso + registro de actividad — una corrección, nunca un rechazo. 349/789 informes en producción llevaban fechas inventadas; los nuevos ya no pueden.
  • Geocodificación del mapa (CAR-105): las cadenas de ciudad en cirílico de las filas de hh.ru/Habr llegan ahora al geocodificador; una caché acotada y persistente (data/geocode-cache.json, withFileLock) hace que una ciudad ya resuelta nunca vuelva a golpear al proveedor; las llamadas de geocodificación llevan presupuesto por petición, así que el mapa se llena progresivamente en lugar de bloquearse 13.9 s.
  • UI en idiomas mezclados (CAR-108): 23 fugas con clave corregidas en 17 de 19 diccionarios (85 valores) — «все matching», «только remote», «Открыть Scan», el embudo «Evaluated → Applied…» y más pasaron a ser frases localizadas completas que reutilizan el vocabulario de producto de cada idioma. i18n-no-latin-leaks ampliado con la tabla de fragmentos + la sonda del embudo (rojo→verde).
  • Inversión del veredicto por divisa (CAR-109): la comparación salarial se calcula en el servidor en comp-compare.mjs y se incrusta de forma canónica (Candidate minimum: … per YEAR (raw: …) frente a JD offer: …); las formas mensuales ×12, k/к/тыс/萬 y con coma decimal se normalizan antes de comparar; una conversión incierta no puede por sí sola invertir el veredicto (el caso zh-TW «€80–100k ниже минимума» ha muerto). El modelo nunca multiplica.
  • Etiquetas de botón en la ayuda ×18 (CAR-107): los paquetes de ayuda localizados citaban etiquetas en inglés («⚡ Run live», «Save», «Scan now») que la interfaz ya no muestra — 263 sustituciones en 18 idiomas, mapeadas desde los fuentes de las vistas; el docs-assistant hereda la corrección.

Añadido

  • Bundle precomprimido + carga en frío cacheable (CAR-104): make-bundle.mjs emite bundle.js.br (brotli q11) + .gz en el momento del release; un middleware acotado al bundle negocia br > gz con Vary: Accept-Encoding, public, max-age=300, must-revalidate + ETag/304; con guarda de frescura (un compañero nunca se sirve más antiguo que el bundle de identidad), las peticiones directas de los artefactos devuelven 404, CSP byte a byte idéntica. Carga en frío: 3.1 MB → ~0.6 MB por el cable (−80%); las visitas repetidas dentro de los 5 minutos transfieren ≈0 bytes.

Notas

  • Todas las correcciones llegaron de la flota de agentes con evidencia rojo→verde (13 + 28 + 1 + 30 + 7 casos nuevos en sus suites) y pasaron rondas de revisión (CodeQL #579/#580/#584 corregidos en código — URLs stub parseadas vía new URL().host, una cadena muerta del prompt eliminada).
  • Las cifras del backfill de puntuaciones de v1.258.0 están ahora registradas en CAR-129 (updated: 22, reportScoreless: 159).
  • A propósito no hecho: sin corrección retroactiva de fechas para los 349 informes históricos (un backfill sería una operación de ops aprobada por el propietario), sin vista de clústeres en el mapa (CAR-125 sigue en cola detrás de esta corrección de geocodificación).
  • Tests 5823 (+74). No se toca ninguna fuente del escáner (registro 121 = 116 EN + 5 RU · 116 adaptadores); sin claves i18n (CAR-108 edita solo los valores de las claves existentes).

[1.258.0] — 2026-10-25

Seis correcciones de la flota de agentes en un solo tren: el contador de empresas de #/scan deja de mezclar universos, la investigación a fondo exige fuentes, los errores de formulario iluminan el campo, los estados de carga ganan skeletons y un h1 instantáneo, y las evaluaciones cachean su prefijo de prompt de 37k tokens.

Corregido

  • Contador «Active companies» de #/scan (CAR-100): el numerador contaba empresas distintas entre TODAS las filas de resultados (1554 en producción — solo hh.ru aporta cientos) mientras el denominador contaba las empresas rastreadas con soporte de API (~90) — dos universos en una sola fracción. Emparejamiento (a): el numerador cuenta ahora las empresas entre las filas que el propio escaneo de empresas rastreadas con soporte de API obtuvo (se emparejan por _entry ?? company; las filas de empleadores de hh.ru/telegram quedan fuera); el fallback previo al escaneo se movió al mismo universo (podía llegar a mostrar «104/90»).
  • Fabricación en la investigación a fondo (CAR-110): el prompt de deep lleva ahora un contrato estricto — toda afirmación fáctica termina con [source: domain.com] (si no puedes nombrar la fuente → no enuncies el hecho; inventar un marcador es peor que escribir «unknown»), los hechos desconocidos se escriben unknown, y el informe DEBE terminar con una sección ## Data freshness. Anclas de frescura: la fecha UTC de hoy + una url de empresa opcional en POST /api/deep (acotada, nunca se recupera del lado del servidor).
  • Los errores de formulario resaltan el campo (CAR-130): un POST fallido marca el primer campo del formulario con aria-invalid + .is-error y le da el foco (WCAG 3.3.1); se limpia al editar. La pista de error de red es consciente del despliegue — en loopback se conserva la pista del comando de desarrollo; un origen desplegado recibe «el servidor no responde» en lugar del consejo bash web-ui/bin/start.md (el banner de conexión llevaba el mismo hardcode).
  • Estados de carga (CAR-112): #/map y #/tracker renderizan su h1 al instante (el objetivo de foco del router solo existía cuando ya habían llegado los datos) y un helper compartido de skeleton (Skeleton.rows, filas shimmer aria-hidden dentro de una región role="status" que reutiliza common.loading) sustituye el texto pelado «Cargando…» en tracker, resultados del scan y el contenedor del mapa; prefers-reduced-motion detiene el pulso.
  • Caché del prompt de evaluación (CAR-111): buildEvaluationPromptParts divide el prompt de ~37k tokens en un prefijo estable byte a byte (CV + perfil + archivos de modos + instrucciones) y la cola del JD; la ruta Anthropic envía [prefix + cache_control: ephemeral] + [JD] — el prefijo se factura a la tarifa de lectura de caché (≈10× más barato) en cada llamada posterior a la primera. Un prefijo ausente o que no coincide hace fail-open hacia la petición antigua idéntica byte a byte; la cascada compatible con OpenAI queda intacta (allí la caché es del lado del servidor).

Notas

  • Las seis correcciones llegaron de la flota de agentes con evidencia rojo→verde (5 + 7 + 7 + 7 + 8 casos nuevos) y rondas de revisión; las primeras alertas CodeQL de CAR-111 (#579/#580) se corrigieron en código — el stub del test parsea el host de la URL en lugar de comparar subcadenas, y se eliminó una cadena muerta con el prompt completo.
  • El backfill de puntuaciones se ejecutó en producción (endpoint de v1.256.0, con aprobación del propietario): updated: 22, missingReport: 0, reportScoreless: 159 — 159 filas sin puntuación apuntan a informes cuyos encabezados son anteriores al bloque de máquina o llevan formatos de puntuación no parseables; solo recuperables mediante re-evaluación o una pasada de parser más amplia (anotado en CAR-129).
  • A propósito no hecho: sin cambio de nulos-al-final en la columna de commute (rareza preexistente de Infinity en descendente), sin re-tarificación retroactiva de los registros de uso antiguos, sin resaltado al estilo .is-error para los formularios de filtros del scan (CAR-130 acotado al helper compartido de submit de mode-page + la pista de api.js).
  • Tests 5749 (+36). No se toca ninguna fuente del escáner (registro 121 = 116 EN + 5 RU · 116 adaptadores); sin claves i18n (la pista de CAR-130 reutiliza conn.down; la región de skeleton reutiliza common.loading).

[1.257.0] — 2026-10-25

Dos correcciones de la flota de agentes: el asistente de ayuda integrado entiende devanagari y tamil, y la página de uso muestra lo que Hermes cuesta de verdad.

Corregido

  • Recuperación del asistente de ayuda para hi/ta (CAR-103): la clase de palabra del tokenizador de palabras clave de docs-assistant era [\p{L}\p{N}\u30fc]+ — sin marcas de combinación. Las matras del devanagari (ा ै ्…) y los signos vocálicos del tamil (ே ை ்…) partían las palabras en fragmentos; la regla de longitud descartaba los fragmentos cortos (una consulta en hindi coincidía con 0 secciones) y las palabras más largas quedaban decapitadas en basura que colisionaba con secciones no relacionadas. La clase conserva ahora \p{M}; la ruta de bigramas CJK queda intacta. hi «मैं स्कैन कैसे चलाऊं?» y ta «ஸ்கேன் எப்படி இயக்குவது?» recuperan las secciones correctas de los paquetes incluidos.
  • Coste de uso para Hermes (CAR-106): normalizeUsage() colapsaba el uso del proveedor a {in, out} y descartaba en silencio el cost reportado por la pasarela — #/usage mostraba $0.0000 sobre 9.5 M de tokens de Hermes (su fila en la tabla de precios es 0/0). La forma del registro de uso gana un cost? opcional y retrocompatible; el agregador prefiere el coste reportado por el proveedor en cada registro y solo recurre a la estimación de la tabla de precios cuando falta; los valores basura o negativos se descartan, nunca se fabrican.

Notas

  • Ambas correcciones llegaron de la flota de agentes con evidencia rojo→verde (3 + 5 casos nuevos; 14/14 y 10/10 en verde; vecinos 78/78 y 61/61). Los primeros PR (#470/#471) llevaban una base de worktree obsoleta que entraba en conflicto con el v1.256.0 ya fusionado por squash — se sustituyeron por cherry-picks limpios (#472/#473).
  • A propósito no hecho: sin re-tarificación retroactiva de las líneas históricas de data/llm-usage.jsonl (no llevan campo de coste; solo se benefician los registros nuevos).
  • Tests 5713 (+8: docs-assistant ×3, usage ×5). No se toca ninguna fuente del escáner (registro 121 = 116 EN + 5 RU · 116 adaptadores); sin claves i18n.

[1.256.0] — 2026-10-25

Calidad de los datos del tracker: el rol nunca se archiva como empresa, un tablero de ofertas nunca se archiva como empleador, las filas sin puntuación dejan de aparecer primero — y un endpoint one-shot rellena (backfill) las puntuaciones que faltan a partir de los propios informes.

Añadido

  • Relleno de puntuaciones (backfill, CAR-129): POST /api/tracker/backfill-scores rellena la celda Score de las filas cuyo informe enlazado lleva una puntuación parseable (181 filas evaluadas tenían informe pero ninguna puntuación). Las filas ya puntuadas, los centinelas explícitos (N/A), las filas sin informe y los informes sin puntuación no se tocan nunca; solo se reescribe la celda Score, bajo el file lock; la forma de la puntuación se valida en el punto de escritura y se normaliza a la convención X.X/5 del tracker; la ejecución es idempotente y devuelve {ok, updated, missingReport, reportScoreless}.

Corregido

  • El rol nunca es la empresa (CAR-128): guessCompanyRole trataba el lado IZQUIERDO de una línea de encabezado con guion como la empresa, pero los tableros escriben ambos órdenes — «Senior Frontend Engineer - Grafana Labs» archivaba el rol COMO la empresa (13 filas: Grafana, CircleCI…). Una heurística de palabras clave de rol con límites de palabra elige ahora el lado del rol, y un tipo de empleo nunca se convierte en el empleador («Senior Engineer - Remote (EU)» no archiva nada en lugar de «Remote (EU)»).
  • Un tablero de ofertas nunca es el empleador (CAR-127): el fallback de slug de dominio archivaba las filas de hh.ru como «hh» (120 filas). Los slugs hh/habr se unen a la lista de no-empleadores; la inferencia de empleador a mitad de frase («в компании X», «X ищет») se ejecuta ahora también para los hosts de tableros de ofertas (hh.ru/kz/uz/by, habr.com) — donde el texto de la página nombra al empleador real. Causa raíz encontrada por el camino: los \b/\w ASCII de esos patrones de inferencia nunca coincidieron con el cirílico en absoluto — muertos en silencio desde v1.248.4; los sustituyen lookbehinds Unicode y clases \p{L}.
  • Las filas sin puntuación se ordenan al final (CAR-129): la ordenación por puntuación del tracker usaba un centinela -1, así que con la ordenación ascendente por defecto cada fila sin puntuación aparecía antes que todas las puntuadas. Los nulos ahora se ordenan al final en ambas direcciones (el FILTRO de puntuación conserva su -1 — «sin puntuación no pasa un filtro ≥4» es la semántica correcta ahí).

Notas

  • Las filas históricas NO se migran con esta versión: las correcciones del parser se aplican a las evaluaciones nuevas; las puntuaciones se recuperan vía el endpoint de backfill, mientras que las 120 celdas de empleador «hh» necesitan re-evaluación, no una reescritura.
  • Se incorpora una ronda de revisión opus-5-5: el patrón sin anclar «X ищет» se descartó antes del envío (cualquier secuencia con inicial mayúscula antes del verbo se habría archivado como empleador — el invariante de no fabricación), se añadieron anclas «В…» a inicio de frase, y el escritor del backfill re-verifica la celda objetivo (existen filas # duplicadas tras ediciones manuales), conserva el número de columnas de una fila corta malformada a mano y se detiene en la primera línea fuera de la tabla. El punto dentro de un nombre con punto («Сбер. IT») es indistinguible de un punto de frase para la ancla «в компании» — se archiva el prefijo conservador («Сбер»); el patrón anclado al verbo captura la forma completa.
  • A propósito no hecho: sin extracción del comparador de tracker.js a una lib testeable con unitarios (la puerta estática sobre el fuente se queda; anotado en CAR-129 como seguimiento), sin cambio de nulos-al-final en la columna de commute (rareza preexistente de Infinity en descendente), sin migración de datos para las filas «hh».
  • Tests 5705 (+10: 5 de identidad en auto-pipeline-jd-gate, 2 de CAR-128, la puerta de ordenación de CAR-129, 4 de backfill incl. el pin de ruta hostil — menos los casos v1.248.x sustituidos). No se toca ninguna fuente del escáner (registro 121 = 116 EN + 5 RU · 116 adaptadores); sin claves i18n.

[1.255.2] — 2026-10-24

Tres correcciones visibles para el usuario procedentes de la regresión en vivo y de un informe de caída en producción: el crash de renderizado de #/scan, la comprobación de idioma vietnamita ciega y la pérdida recurrente del bloque de resumen en zh-TW.

Corregido

  • Crash de #/scan (CAR-99): la regeneración de las puertas de conteo desde v1.253.0 escribió el literal FALLBACK_SOURCES con una doble coma final en cada entrada — 120 huecos de elisión en el array. new Set(FALLBACK_SOURCES.map((s) => s.value)) en la vista de scan contenía entonces undefined, y el id.length del clasificador de empresas lanzaba TypeError: Cannot read properties of undefined (reading 'length') en cada render de página (capturado por logbuf en producción). Los huecos se han eliminado y una nueva puerta (4 specs, verificada en rojo) ejecuta el archivo y falla ante cualquier hueco, entrada inservible o undefined en el Set de IDs de adaptadores — la puerta de drift solo textual no podía ver esta clase.
  • Comprobación de idioma vi ciega (CAR-97): el vietnamita es escritura latina y no tenía entrada en la tabla de palabras de parada de la QA en vivo, así que las «palabras de parada de vi» contaban aciertos en inglés y una respuesta en vietnamita se leía como report not in vi (regresión en vivo: vi 2, en 2). El vietnamita ya tiene su propia tabla de diez palabras funcionales — un informe puramente vietnamita pasa, y uno en inglés sigue fallando.
  • zh-TW perdía el bloque SCORE_SUMMARY (CAR-98): el recordatorio final de idioma solo decía que el formato de máquina no debe traducirse, nunca que el bloque de resumen debe existir — un informe corto en zh-TW (5,983 caracteres) llevaba los bloques A–G pero omitía el bloque por completo (una recurrencia de la clase v1.239.2). La directiva inicial y el recordatorio final ahora afirman que el bloque es obligatorio y que sin él el informe no es válido.

Notas

  • Esta versión no cambia a propósito ninguna fuente del escáner, el recuento del registro (121 = 116 EN + 5 RU · 116 adaptadores) ni el cableado de proveedores — es solo de correcciones, así que las puertas de conteo quedan intactas.
  • Tests 5695 (+9: car99-fallback-holes ×4, car97-vi-lang-check ×4, el invariante del prompt de CAR-98 ×1). Navegador 143/143. La re-verificación en vivo de vi/zh-TW viaja con la regresión posterior al despliegue.

[1.255.1] — 2026-10-24

Dos rondas de correcciones tras revisión: la revisión Opus del idioma vietnamita (9 hallazgos) y la segunda pasada sobre las correcciones CAR-91/92 (5 must-fix) — ambas cerradas con tests rojo→verde.

Corregido

  • Ronda de revisión del locale vi (seguimiento de CAR-88): el glifo hangul (신) que se coló en project.subtitle / project.roleHint queda corregido (độ khớp tín hiệu), con una puerta que falla ante cualquier glifo CJK/Kana en un valor de vi que el diccionario EN no lleve; las seis URLs markdown con espacios en docs/help/vi.md (what-is career-ops, apply for a job, set up playwright y tres más) llevan guiones hasta los slugs reales de las guías, con una puerta sin-espacios-en-URLs.
  • Límites de los encabezados de evaluación (CAR-92 r2 H1): &, ., /, ' son ahora caracteres de palabra en el límite izquierdo de la letra de bloque — ## Q&A, ## Plan B, ## Ph.D. requirement, ## E-commerce ya no se registran como bloques A/B/D/E; el terminador pelado de fin de línea solo se acepta tras un token de palabra de bloque (Block/блок/블록/…); el - ASCII salió de las clases de terminador escrito para que los encabezados E-commerce/ई-मेल sobrevivan.
  • Informes numerados en ucraniano/ruso (CAR-92 r2 H2): el detector de la secuencia А Б В Г Д Є Ж es ahora un helper compartido; cuando encuentra 3+ letras de la secuencia en un nivel de encabezado, ese nivel se convierte en el nivel de referencia del cortador — las subsecciones con letra ### A. ya no truncan el informe en el bloque Б.
  • Ciclo de vida de las filas arrastradas (CAR-92 r2 M1): las filas que una fuente fallida arrastra al siguiente escaneo se sellan con carriedSince, se descartan tras 30 días o 5 arrastres consecutivos, y se añaden después de las filas frescas en lugar de re-acumularse en silencio para siempre.
  • Denominador de la síntesis Gemini + legitimacy (CAR-92 r2 M2) y la ruta de body-score del subprocess (M3): leídos del archivo de hallazgos, corregidos con tests rojos.
  • Puerta de deriva del bundle de carga en frío actualizada para el diccionario vi (90 script tags / 87 bundlable) y su binding de test sin usar eliminado (CodeQL 572).

Notas

  • La redacción del recuento de adaptadores del banner «Latest» de README.vi que la revisión señaló vive en el banner Anterior y ya replica el README EN byte a byte — se deja intacta para mantener la paridad EN↔vi (documentado en el cuerpo del commit).
  • Tests 5686 (+26: eval-validate-opus-r2, scanner-carryover-r2, aserciones de la puerta vi). Registro sin cambios en 121 fuentes (116 EN + 5 RU) · 116 adaptadores.

[1.255.0] — 2026-10-24

El vietnamita (Tiếng Việt) se une como el 19.º idioma de la interfaz — un diccionario de 1525 claves con paridad completa con el EN, detección de vi/vi-VN, el paquete de ayuda completo, réplicas de README y CHANGELOG, integración en la landing de cvstart.org y la 🇻🇳 en el selector de idioma.

Añadido

  • Vietnamita (vi) — 1525/1525 claves con paridad completa con en; detect() acepta vi y vi-VN; el vietnamita es LTR (nunca se añade a RTL_LANGS). «Pipeline» sigue siendo «Pipeline» — un préstamo técnico estándar, en la lista blanca.
  • docs/help/vi.md completo (33 H2 / 125 H3), más las réplicas README.vi.md y CHANGELOG.vi.md, y las cadenas de andamiaje en vietnamita en prompts.mjs (LOCALE_NAMES + SCAFFOLD_STRINGS) para que la salida LLM en vietnamita funcione de fábrica.
  • Landing de cvstart.org en vietnamita — site/src/i18n/vi.json, la entrada de locale en el sitemap y una captura dashboard-vi.png; el selector de idioma gana la 🇻🇳 (19 posiciones).
  • Puertas de enumeración 18 → 19 — LANGS, sondas de detect, auditoría i18n, paridad de changelog, bloque de traducciones del README, barrido de locales — más el tests/i18n-vi-locale.test.mjs rojo-primero (139 líneas).

Notas

  • La landing de cvstart.org gana el vi — una desviación deliberada del precedente de ta (esa versión mantuvo la landing en 17 a propósito); documentado en la especificación de la versión.
  • vi es de escritura latina, así que la puerta no-latin-leaks no aplica (custodia los idiomas no latinos); el vocabulario técnico latino es la norma.
  • Suelo de pruebas: 5634 → 5642 (1 omitida = la prueba opcional de captura de maquetación del navegador, sin cambios).

career-ops-ui v1.267.1 · page 1/9 · mirror of the project changelog for cvstart.org