Skip to content

v2.11 Search Fix: arregla tres bugs de la búsqueda y amplía los límites (112/112 tests) - #12

Merged
luisfemojica merged 1 commit into
mainfrom
v2.11-search-fix
Aug 9, 2026
Merged

luisfemojica merged 1 commit into
mainfrom
v2.11-search-fix

Conversation

@luisfemojica

Copy link
Copy Markdown
Owner

Resumen

El mantenedor reportó que la búsqueda "a veces dejaba de encontrar" cosas. La auditoría encontró tres bugs encadenados, todos con la misma raíz: applySearch() buscaba nodo de texto por nodo de texto sobre el DOM ya coloreado.

🔴 Bug 1 — La búsqueda se degradaba sola (crítico)

clearSearchMarks() quitaba las marcas reemplazándolas por nodos de texto, pero nunca volvía a fusionar los nodos adyacentes (faltaba normalize()). Cada búsqueda fragmentaba el DOM de forma permanente y las siguientes dejaban de encontrar términos que cruzaran esos cortes.

Reproducción en la app (antes del fix):

Paso Resultado
Buscar Rayuela 1/1 coincidencias ✅
Buscar y — parte el nodo en Ra | y | uela 1/1 coincidencias
Buscar Rayuela otra vez 0 coincidencias ❌

El texto seguía visible en pantalla; la única forma de recuperarlo era volver a pulsar Formatear.

🟠 Bug 2 — Imposible buscar términos que cruzan colores

En la vista Resaltada el texto está partido en <span> por color. Como la búsqueda solo miraba dentro de cada nodo, cualquier término que abarcara dos colores daba cero aunque estuviera a la vista: <libro → 0, libro id → 0, id="1" → 0 (mientras que libro solo → 4 ✅).

🟡 Bug 3 — Contadores inconsistentes entre vistas

Consecuencia del anterior: <libro daba 0 en vista Resaltada y 2 en vista Texto. Mismo documento, mismo término, dos respuestas.

✅ Solución

applySearch() ahora busca sobre el texto completo de la salida y mapea las posiciones al DOM resaltado:

  1. Aplana el DOM a un string, guardando el offset de cada nodo de texto
  2. Busca las coincidencias sobre ese texto con findRanges() — la misma lógica que usan las vistas Texto/Árbol, así que los contadores coinciden por construcción (bug 3 desaparece estructuralmente, no por parche)
  3. Marca por trozos: una coincidencia que abarca varios spans se envuelve en un <mark> por nodo tocado, y todos los trozos comparten índice de coincidencia para la navegación ▲▼

Extras:

  • normalize() al limpiar las marcas (raíz del bug 1)
  • El outline de la coincidencia activa pasa a box-shadow inset: un borde por trozo la partía con líneas verticales. Verificado que los 5 trozos de <libro id= quedan contiguos (0px de hueco) con un subrayado continuo.

Resultado tras el fix, mismos casos:

Caso Antes Ahora
Rayuela tras 5 búsquedas intermedias 0 ❌ 1/1 ✅
<libro 0 ❌ 1/2 ✅
libro id 0 ❌ 1/2 ✅
id="1" 0 ❌ 1/1 ✅
<titulo>Rayuela 0 ❌ 1/1 ✅
<libro en Resaltada vs Texto 0 vs 2 ❌ 2 vs 2 ✅

📏 Ampliación de límites

El arreglo no cabía en el presupuesto de v2.10 (~2474 de ~2500 líneas, ~97KB de 100KB). Decisión del mantenedor: techo a ~3000 líneas y 120KB duros. Se levanta el modo solo-bugfixes declarado en v2.10, documentado en SCOPE.md, ROADMAP.md, README.md y CLAUDE.md.

De paso corregí referencias obsoletas a "~1500 líneas" que habían quedado desde v2.4 en README, SECURITY y la guía de documentación.

Tests

6 nuevos, uno por bug más navegación y la función pura:

  • Buscar dos veces el mismo término sigue encontrándolo (bug 1)
  • Términos que cruzan el resaltado (bug 2)
  • Una coincidencia partida se marca completa y todos sus trozos se resaltan como activos
  • El contador coincide entre vistas (bug 3)
  • Navegación ▲▼ recorre las coincidencias
  • findRanges: sin solapamiento, insensible a mayúsculas, respeta el tope

112/112 pasando.

Métricas

index.html: ~2510 líneas / ~99KB (nuevos límites: ~3000 / 120KB).

🤖 Generated with Claude Code

La busqueda operaba nodo de texto por nodo de texto sobre el DOM ya
coloreado, lo que causaba tres fallos encadenados:

1. Degradacion progresiva (critico): clearSearchMarks reemplazaba cada
   <mark> por un nodo de texto sin fusionar los adyacentes (faltaba
   normalize()). Cada busqueda fragmentaba el DOM de forma permanente.
   Reproducido: buscar 'Rayuela' -> 1 resultado; buscar 'y'; buscar
   'Rayuela' otra vez -> 0 resultados.
2. Terminos que cruzan colores: '<libro', 'libro id' o 'id="1"' daban 0
   coincidencias aunque estuvieran visibles, porque el texto esta
   partido en spans por color.
3. Contadores inconsistentes: '<libro' daba 0 en vista Resaltada y 2 en
   vista Texto.

Solucion: applySearch aplana el DOM guardando el offset de cada nodo,
busca sobre el texto completo con findRanges() (misma logica que las
vistas Texto/Arbol, asi los contadores coinciden por construccion) y
marca cada coincidencia por trozos, uno por nodo que toque. Los trozos
comparten indice para la navegacion. Ademas normalize() al limpiar y el
outline de la coincidencia activa pasa a box-shadow inset, porque un
borde por trozo la partia con lineas verticales.

Limites ampliados por decision del mantenedor: ~3000 lineas / 120KB
(antes ~2500 / 100KB, que bloqueaban este arreglo). Se levanta el modo
solo-bugfixes de v2.10. De paso se corrigieron referencias obsoletas a
'~1500 lineas' en README, SECURITY y GUIA.

Tests: 6 nuevos (uno por bug, navegacion y findRanges) — 112/112.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@luisfemojica
luisfemojica merged commit 6ba2417 into main Aug 9, 2026
1 check passed
@luisfemojica
luisfemojica deleted the v2.11-search-fix branch August 9, 2026 03:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant