19 Commits
Author SHA1 Message Date
Steffen Becker 055839655e test: the monkey edits both zones, and the runs compare what is drawn
Header and footer go first, each with its own ops, and a variant flag is
flipped while a zone is open. The round trip compares a list's drawn kind and
marker rather than its node type and style name — a list that loses its name to
a formatted marker carries the style's own char — and skips the marks of an
inline atom, which no file keeps. A caret is snapped to a text position, a
swallowed File-menu click is retried, and WebKit's converging ResizeObserver
round is no page error. A new corpus pair brings all six zones through the
import path; the baseline holds it and a re-recorded showcase entry.
2026-09-06 19:07:23 +02:00
Steffen Becker d156482a3f test: LibreOffice, the layout lint and a monkey read the editor's work back
lo-fuzz.test.ts hands every fuzz seed to LibreOffice as .odt and .docx and
compares what comes back, listing the differences LibreOffice makes by design.
test:layout opens the corpus, the showcase and fuzz seeds in the browser, holds
the page count against LibreOffice and checks the lines for overlap, margin
escapes and stranded headings. test:monkey types at random on corpus documents
and asserts the schema, undo to the start, redo, and that both saved formats
read back. browser.mjs now carries the dev server and the settle helper.
2026-09-06 19:07:16 +02:00
Steffen Becker f26de200fe test: the parity harness aligns two documents instead of indexing lines
Comparison stopped at the first divergence on a page, so one displaced line
reported every line behind it and a slipped page hid what followed: a
468-page reference reported 4 differences while hiding 465 pages. The line
streams are aligned instead — nearest resync within 60 lines, contiguous
divergences merged, neighbouring lines off by the same amount collapsed to
a count — and it reports 5766. Reference PDFs are cached by file hash
(corpus 9:00 cold against 5:41 cached), each run prints its counts against
the last, and --quick skips what measured over 50 pages.
2026-09-04 19:30:11 +02:00
Steffen Becker 2dacb1f86b fix: the parity reference keeps the blank pages LibreOffice inserts
Its PDF export drops auto-inserted blank pages unless IsSkipEmptyPages=false is
passed, so a document whose chapters open on a right page was measured against a
reference several sheets short of what LibreOffice lays out: 456 pages against 468
on the largest fixture, 28 against 32 on another. The corpus total rises on the
honest reference — that is drift the old one hid, not new drift.
2026-09-04 19:30:10 +02:00
Steffen Becker 1f4f5ad4b2 test: the parity harness bands a line by overlap, not by its top
A chapter number set far larger than its title shares the title's baseline but
starts a long way above it, so banding by the top alone read one line as two
and stopped the comparison at the first chapter opening. Words carry their
height now, and a word joins the line it overlaps by half the shorter box.
Corpus unchanged at 9.
2026-09-04 19:30:08 +02:00
Steffen Becker ae7bf878d8 fix: the parity harness reads the case a style paints, not the node's
A style's fo:text-transform / w:caps is a CSS transform in the browser, so
Range.getClientRects' text stayed lower case where the reference PDF has real
uppercase — three headings of one fixture reported a lineBreak that was not
one, and comparison stopped at each. The computed textTransform is applied to
the extracted word now. Reports rise where they were hidden behind such a line.
2026-09-04 19:30:05 +02:00
Steffen Becker 889f11cffc test: a committed corpus CI can read
Ten documents the docx lib authors directly, plus the ODT twin
LibreOffice converts each into, now live in tests/corpus/ instead of the
gitignored fixtures dir. corpus.test.ts asserts both formats of a
document read the same and that each survives our export and import
either way. The heading styles gained the outline level Word writes —
without it LibreOffice converts a heading to a paragraph that merely
carries the style, and the two formats disagreed.
2026-08-15 11:54:36 +02:00
Steffen Becker 0da75ed456 refactor: the project is called edentext
Renames the product in the docs, the page title and the DOCX creator, and moves
the whole storage namespace from odf-editor- to edentext-. The prefix is a
per-origin namespace, so it stays the product name rather than a generic one.
2026-08-13 21:43:12 +02:00
Steffen Becker 1a1505071c fix: the parity reference stops depending on which duplicate font it picks
A second copy of a family LibreOffice bundles makes its render
non-deterministic: it resolves the same family to either file per run — the PDF
names them Carlito and Carlito-Regular — and their vertical metrics differ, so a
page led by a large-font heading lands 1.85mm lower on some runs. Measured 5/5
identical once the duplicates were gone, against a 2-of-4 flip before, and one
fixture's count swinging 21↔62 with no code change. README now says to install
only what the local LibreOffice does not already ship.

Also skips the `~$name` lock file a word processor leaves beside a fixture it
has open, which the corpus was picking up as a document.
2026-08-13 18:07:57 +02:00
Steffen Becker f9eeeb25f0 fix: the parity harness reads a word broken mid-word as two words
getClientRects()[0] gave a token that wraps — one wider than its line, or one
carrying a hyphen or a slash — its whole text at its first fragment's position,
where pdftotext reads the two fragments as separate words on separate lines. So
every such break reported a lineBreak the two engines did not actually disagree
on, and hid the case where they break at different characters. Split by the line
each character lands on; the per-character walk only runs on a word with more
than one rect.

Corpus 900 → 849, no page count moved.
2026-08-13 17:21:41 +02:00
Steffen Becker 3301aba30e fix: the parity harness finds Chromium on any platform
playwright-core resolves its own installed browser, so the hand-rolled
~/.cache/ms-playwright scan and its chrome-linux/chrome path can go — that
directory and binary name are Linux-only, and the harness threw before
rendering anything anywhere else. PARITY_CHROME still overrides.
2026-08-13 17:06:50 +02:00
Steffen Becker f055fc4850 docs: every comment back inside the three-line rule
A sweep of the whole tree: 27 blocks ran to four lines or more. Trimmed
to what the code can't say, with two of them split back onto the
function each was really about.
2026-08-11 21:22:13 +00:00
Steffen Becker 860e25d8c8 fix: saving keeps a section's own page margins
Both exports wrote one geometry for the whole document, so a title page's
margins survived import and were lost on the next save. Each ODF section master
now names a page layout of its own — the document's, shifted by the section's
difference, so what the header/footer pass folded into it survives — and every
w:sectPr carries its section's w:pgMar. DOCX import takes the document pair from
the first section, the one .tiptap's padding draws.

The parity harness spread a 458-page document's word list into Math.max and blew
the stack; the longest fixture was never measured.
2026-08-11 19:35:55 +00:00
Steffen Becker ab83ad3c58 test: the parity runner compares a leader as one token, and polls
How many dots fill a tab or index leader is the fill's own business —
both engines end it at the same stop. The settle predicate now polls on a
timer: walking every spacer and index row each frame starved the layout
it was waiting for, and half the runs timed out.
2026-08-10 11:32:40 +00:00
Steffen Becker c26aac1bee fix: the index's leader fills its own gap, at LibreOffice's indents
Every row carried 200 dots — clipped on screen, but the clipboard, the
PDF and any measurement got all of them. Cut each to what its gap holds,
and step the levels by 0.5cm, which is what LibreOffice's Contents styles
use. The parity runner now waits for the page numbers to settle too.
2026-08-10 09:32:49 +00:00
Steffen Becker 4ad743dc28 test: the parity runner waits for the import, not just for a blank page
A 3.5MB fixture takes seconds to parse; until then the editor holds the
empty document, whose layout is stable enough to pass the settle window.
2026-08-10 08:17:40 +00:00
Steffen Becker 2e70e47912 test: the parity runner can reuse a dev server already listening
PARITY_PORT points it at an existing npm run dev instead of starting a
second one on 5199.
2026-08-10 07:38:31 +00:00
Steffen Becker e4b679ad8b test: find whichever Chromium build is installed
The parity runner pinned one build number, so a fresh
`playwright-core install chromium` left it pointing at a directory that
is not there. PARITY_CHROME still overrides.
2026-08-08 12:17:51 +00:00
Steffen Becker 361b14dc17 test: compare the rendered page against LibreOffice
Everything else in tests/ compares documents at the model level; nothing checked
what a page actually looks like. run.mjs renders the same file through soffice
and through the real app in Chromium, reads both back as word boxes, and diffs
page count, line count, line text and line position.

The corpus stays out of the repo: make-fixtures.mjs authors the baseline .docx
files, and real-world documents are whatever you drop into the gitignored
fixtures/ yourself.
2026-08-08 08:35:34 +00:00