Word's w:lvlJc / ODF's fo:text-align on the level: set right, the label grows
into the margin instead of over the text it labels. That is what the built-in
Roman numberings use, and a fixture's numerals collided without it.
Both anchor offsets now render: the x moves the rotor inside the full-width
float, the y the float's top. Chromium puts every line after such a float
below it, so a frame set below the paragraph top is sunk behind the text at
import and the node view measures what those lines already cover.
A formula node stores only its LaTeX source; the MathML on screen, the ODF
formula object and Word's OMML are derived from it, and both importers parse
foreign markup back into that source, so an imported formula stays editable.
Word documents were losing their formulas outright: m:oMath sits beside the w:r
runs in its own namespace, past convertInline's `namespaceURI !== W` guard.
An as-character image is now a line box in getLineRects, so a paragraph of
stacked images breaks between them instead of overflowing the page. Only a
floated image keeps its paragraph atomic, and the leaf's height reaches down
to the lowest float — a float hangs out of the paragraph's box, so
offsetHeight alone let the image walk off the page foot. prevPlacementsKey
damps the ping-pong that overhang causes between two layouts.
Only the top level's own w:ind w:left (ODF: the label-alignment margin)
was read; every level below it was forced to the editor's 1.27cm step.
Both formats store a level's indent absolutely while the editor nests one
step per level, so import now subtracts the level above and the ODF
export adds the running sum back per level (DOCX already accumulated).
Measured: a 0.5cm/-0.6cm pair puts LibreOffice's two levels at 1.77cm and
2.44cm, and the editor draws them at 1.769 and 2.44. Parity corpus
unchanged.
w:tcMar and an ODF cell style's own fo:padding were both dropped: the
table's value (in ODF the first cell's) stood for every cell.
cellPadding is now an attr on tableCell/tableHeader too, measured against
the table's rather than the editor default, so only a real disagreement
carries one. --cell-pad inherits, so the cell's own declaration wins for
its own td and the CSS is unchanged.
Checked against LibreOffice: a 1.2cm cell in a 0.3cm table puts its text
at 332.2pt where the grid column starts at 297.6.
LibreOffice restarts the tab grid on the continuation line; Chromium
hangs the leftover tabs and starts the next line at the margin, 160mm
away in the repro. The run is walked from the pen before it — the one
thing the fix can't move, so the answer holds whether or not the break
is already in place — and a break widget goes where the line runs out.
Measuring this turned up why the two disagreed in the first place: a
file that declares no tab interval falls back to ODF's own 2cm, not to
LibreOffice's UI default of 1.25, so leaving ours implied on export
rendered every tab elsewhere. Both exports declare it now, and both
imports fall back to their format's value rather than to ours.
Only runs of two or more are walked: reading a block's geometry forces
a reflow, and doing it per tab across 48 pages slowed pagination enough
that the parity harness compared a layout still settling.
The left⇥centre⇥right layout every letterhead uses rides on them, and a
zone had none: Word keeps them on the Header style, ODF on the zone
paragraph's, and neither was read or written.
An inactive zone is static generateHTML output no plugin reaches, so it
is measured straight on the DOM. Two bugs surfaced doing it: a segment
its own advance had pushed onto the next line measured as wrapped, and
the alignment then had no way back — it takes the sum of the segment's
client rects now. And the scale came from offsetHeight, which rounds
half a percent away on a zone one line tall.
The default tab interval was fixed at 1.25cm, so a file's own
w:defaultTabStop / style:tab-stop-distance was ignored and the DOCX
export wrote 1.25 back over Word's 1.27. It is a document setting now.
A stop can also carry the character it repeats across its gap. odf-kit
has no leader option and mints one automatic style per option set, so
the leader rides style:type behind a sentinel — matching on position
and alignment alone gave every stop at 6cm the same leader, which
LibreOffice drew back plain as day.
The root file is read at every session start and had grown to 87 KB, so every
session paid for knowledge it did not use. Move the per-directory detail into
nested CLAUDE.md files, which load when a file in that directory is touched,
and the five deep-dive topics into docs/. The root keeps the commands, the hard
rules and an index of where the rest lives.
Content moved verbatim — the probed Word/LibreOffice behaviour, sentinel order
and shared constants are what the code cannot say. CI fails the root over 120
lines, since a prose budget alone is what let it grow.