Pedido: poder llevar la comparativa a una sesión abierta con un modelo para que
la use. Eso, y no un informe para imprimir, decide el formato: los ficheros
iguales se resumen en una linea en vez de listarse, los binarios solo se
mencionan, y las diferencias van en diff unificado, que los modelos leen de
forma nativa. Lo que se omite se dice en voz alta — un informe que se deja la
mitad en silencio es peor que ninguno, porque se lee como completo.
Las cabeceras `--- a/ +++ b/` cuestan dos lineas y convierten cada bloque en un
parche de verdad, asi que el modelo puede devolverlo por `git apply` en vez de
reescribir el cambio a mano. Que lo sea de verdad esta comprobado ejecutando
git sobre la salida en 10 escenarios (sin salto final, insercion al principio,
borrado al final...); dos fallos aparecieron asi: interlineaba `-` y `+` en vez
de agrupar, y contaba la linea fantasma que deja el ultimo `\n`.
Implementado en los dos motores (Swift y TypeScript) con la misma semantica, y
verificado que producen el mismo texto **byte a byte** para un mismo caso.
Tambien hay salida JSON, para alimentar una herramienta en vez de una charla.
UI en las dos apps: copiar al portapapeles primero (que es el gesto real), y
guardar/descargar despues. En web, si el portapapeles se niega, se descarga y
se avisa: un fallo silencioso ahi acaba en pegar contenido viejo sin saberlo.
De paso, un bug que esto destapó: el menu contextual de la web se cerraba en
`pointerdown`, quitando los botones antes de que su click llegara — ninguna
accion del menu funcionaba (tampoco "Set as base" ni "Expand all"). El smoke
test nunca habia pulsado una; ahora si.
Closes#9
Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015hEAYuHRKMYz9sSa9zmbzz
A list of NameValuePair entries says the same thing whichever order it's written
in, but the comparison demanded positional equality and flagged a pure reorder as
a difference — which makes it useless on configuration files.
Elements are now reduced to a canonical string: attributes sorted, whitespace
normalised, and siblings grouped by name with *repeated* groups sorted, so their
order stops mattering. Differently named siblings keep their order, since a
sequence of distinct elements can carry meaning.
Comparing canonical strings also avoids matching children pairwise, which would
be quadratic on exactly the long repeated lists this is for — covered by a test
with 400 reversed entries.
Reordering no longer hides real changes: a changed value, a dropped entry and a
collapsed duplicate are all still caught.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UfhDYRLTpGJSKGP1m3LVJN
Archives are detected by their signature, not just their extension, so a ZIP
renamed to anything still opens as a folder — and nesting keeps going, verified
on EAR -> JAR -> renamed ZIP. Entries with clearly non-container extensions are
skipped without inflating, since checking the signature inside an archive means
decompressing, and an EAR holds thousands of .class files.
The comparison now records which mode settled each file, so Automatic shows an
icon per row (byte / text / structural) instead of leaving you to guess when it
went semantic.
Sizes are shown on both sides and highlighted when they disagree, which is often
the fastest hint of what changed.
Two files whose names don't match can now be compared: mark one side, pick the
other, and it becomes the new base in this tab or a new one. Entries living
inside an archive are extracted to a temporary file first, since they have no
path of their own — that's what lets a file inside a JAR be paired with a loose
one.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UfhDYRLTpGJSKGP1m3LVJN
A changed line only said 'changed'. Now the engine classifies it and the view
colours it accordingly: whitespace-only (teal), comment-only (indigo) or real
content (orange), with a legend counting each kind present in the file. Comment
detection uses the syntax inferred from the extension and only claims a comment
change when the code either side of it is identical; an unknown file type claims
nothing, since hiding a real change is worse than showing a cosmetic one.
Within a changed line the differing characters are now highlighted rather than
tinting the whole row, so a 21 -> 18 reads as a one-character edit instead of
looking like a rewritten line.
The tree no longer uses List's outline, which only ever puts a disclosure
triangle on the leading column: rows are flattened with their own expansion
state, so both sides carry a chevron and the tree can be driven from whichever
side you're reading. Expand all / collapse all added to the context menu.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UfhDYRLTpGJSKGP1m3LVJN
"Cotejo" means nothing outside Spanish and is hard to pronounce elsewhere, so the
project is now Kotej: same root, but it reads and sounds the same in English and
Spanish. Renamed throughout — engine, targets, bundle ids and the URL scheme,
which is now kotej://compare.
Icon generated with Codex: two facing panels split by a seam, content lines on
each side and one amber line marking a difference — the tool's job at a glance,
still legible at 16pt.
34 tests still green; kotej:// verified end to end against the demo JARs.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UfhDYRLTpGJSKGP1m3LVJN