Commit Graph
603 Commits
Author SHA1 Message Date
Steffen Becker 3ae8d27b26 fix: the build runs under a subpath, not only at the domain root
base './' emits relative asset URLs; the manifest, the service-worker
registration and sw.js's precache resolve against their own location;
the four hardcoded /image literals turn relative. Verified headless
under a simulated /edentext/ subpath: boots, types, SW scope correct.
2026-08-17 18:59:48 +02:00
Steffen Becker b37261375a docs: 0.1.0 release date moves to the Pages launch day 2026-08-17 18:59:48 +02:00
Steffen Becker c9ab7d7ab7 docs: public README
Centred logo header, badges, the live-app link, feature overview,
licensing and CLA pointers. README.md leaves .gitignore — it used to
be a private scratch file, now it is the repo's front page.
2026-08-17 18:59:48 +02:00
Steffen Becker 834ffda931 chore: CLA contact address, workflow URL follows the repo rename 2026-08-17 18:59:48 +02:00
Steffen Becker 9d3d120b57 feat: the About dialog pitches the app in two lines
Tagline plus one line of key points in both languages, rendered as a
centred list; the GitHub button points at the real repository.
2026-08-17 18:59:42 +02:00
Steffen Becker 04b2bba5db docs: a block ending at the page bottom can break on the other side
The fit test is a step function on a block chain that agrees with
LibreOffice to well under the harness's millimetre. Measured: a paragraph
whose flow box ends 0.62mm past the content end is pushed over, where the
reference keeps it, and the block behind it then draws its space-before in
full — 14.8mm on every line of that page.
A tolerance on the fit test was tried and reverted, corpus 1074 → 1085:
the error carries no sign, so the slack that fixes one fixture keeps two
lines another one's reference pushes.
2026-08-17 17:35:00 +02:00
Steffen Becker 6c96175182 docs: the frame limitation is wrapping around a free point, not placing one 2026-08-17 13:47:09 +02:00
Steffen Becker 847fe59d3d feat: a frame the text runs through reserves no space at all
Word's in-front-of / behind-text (wp:wrapNone + behindDoc) and ODF's
style:wrap="run-through" both read as a side float, so a near-column-wide
picture reserved its whole height and pushed the text 23-52mm down. They
are a wrap mode of their own now: out of flow at the static position it
was anchored at, z-order from the file (inFront). Both exports carry it
back; tests/unit/wrap-through.test.ts covers the round trip.
The image fixture 72 -> 38 over both formats and 7 pages -> 6, matching
the reference; corpus 1108 -> 1074, no other fixture moved.
2026-08-17 13:45:39 +02:00
Steffen Becker 9c4b768982 fix: a page is the paper's exact height, not the pixel it rounds to
The sheet boxes rounded cm × PX_PER_CM where --user-page-height keeps it
exact, and pageBreaks builds its grid from the rounded value they publish
as --pb-section-page: A4 came out 1123px against 1122.52, so every page
ran 0.127mm long and the text crept down the flow — 1.8mm by the
sixteenth page, 6.0mm by the forty-eighth, against a reference whose
footer sits at the same height on every page.
Corpus 1772 -> 1108, eight fixtures improved, none rose.
2026-08-17 13:27:46 +02:00
Steffen Becker b1b8b9de63 docs: the ODF import yardstick is the default style, and its spacing exception 2026-08-17 09:18:16 +02:00
Steffen Becker b3c52e53fc feat: an ODF paragraph reads the contextual spacing its style declares
style:contextual-spacing drops a paragraph's own spacing towards a neighbour of
the same style; LibreOffice's List styles carry it, and only the docx path read
its w:contextualSpacing counterpart. Unread, every list point kept the 8pt its
style declares and a long document accumulated a page every dozen items.

The editor has no such mode, so the suppressed side becomes an explicit 0. In a
list each item wraps its own paragraph, so the neighbour is the next item's.
2026-08-17 09:17:33 +02:00
Steffen Becker b65920f7b7 fix: a cell or list paragraph is measured against the default style
The ODF importer's yardstick for a block that can carry no style name was the
editor's own fallback, not the default paragraph style — which is what
`p:not([data-style])` re-applies. So a cell paragraph overriding its 115% line
height back to 100% was dropped as "same as the default" and rendered at 115%
anyway: 0.7mm too much on every table row. Its spacing is the one exception,
zeroed by a rule that outranks the style's, for a paragraph but not a heading.

Two fixtures' table rows: pitch 5.8mm against LibreOffice's 5.1, then 5.1.
2026-08-17 09:17:05 +02:00
Steffen Becker 7767a61b95 feat: the references tab groups its buttons the way both word processors do
Captions, index entries and citations each get their own group instead of
riding under the table-of-contents label, which said nothing about them.

The index options menu names the index it edits and lays its ten levels out
as one strip of keys — as rows they were the whole menu.
2026-08-17 00:31:27 +02:00
Steffen Becker a62fa11cc1 feat: the index options menu switches its page numbers
References ▸ Options, where the depth already lives: Word's "Show page numbers"
as a checkbox, over the pageNumbers attr both formats now carry. The menu is no
longer only levels, so its button says Options.
Its node view repainted on the entries alone, so nothing an attr changes ever
reached the rows — leader, title and level styles included. Keyed on the look
as well now, minus the cached entries the pass writes back itself.
2026-08-17 00:09:48 +02:00
Steffen Becker ac5b05e0fe feat: an index the file writes without page numbers keeps its rows text-only
Word's TOC \n and an ODF entry template naming no index-entry-page-number both
say the rows are text alone; we drew leaders and live numbers over them either
way. Carried as the node's own pageNumbers attr, off only — the node view had
the page-less row already, for the bibliography. The exports write the switch
back out: \n over the whole level range, and the ODF template drops the tab
stop with the number, which is the pair LibreOffice's dialog switches together.
2026-08-16 23:49:53 +02:00
Steffen Becker 8a9b391d76 fix: a heading keeps the bold its own run declares
Both importers drop a mark the block's style supplies, and a heading's bold was
taken as given. The registry style that renders it is the file's, though —
re-parented to Standard wherever the file declares a basedOn / parent-style-name
— so nothing supplied what the mark had been deleted for, and a 23pt title line
came out 149.7mm against LibreOffice's 161.7. Gated on that parent now; a
LibreOffice file, which writes fo:font-weight on every Heading_20_N, is
untouched. Parity corpus 1892 → 1889.
2026-08-16 23:12:34 +02:00
Steffen Becker eb3a9f95c1 feat: Word's horizontal line imports as the paragraph's rule
A v:rect flagged o:hr is the line itself, not a shape: its height and
fillcolor become the paragraph's borderBottom (1.5pt/#A0A0A0 where the file
names none), a real w:pBdr keeping precedence — only one line can be drawn.
It was dropped with the "Drawings were removed" warning until now, which the
changelog's gap list did not admit to either.
2026-08-16 22:44:40 +02:00
Steffen Becker 6c731b9233 feat: insert caption is in the picture and table tabs too
References keeps it, as in both products, but that is a tab away from the
object being captioned. The contextual tabs mount their own CaptionDialog —
it reads the caret for category and position, so the copies take no props.
2026-08-16 22:22:29 +02:00
Steffen Becker 53317bf88b i18n: the phonetic guide is called ruby text
Word and LibreOffice hide theirs unless an East Asian language is on; ours is
always in the Insert tab, so the label says what it inserts. The reading field
no longer repeats the dialog's own name.
2026-08-16 22:04:26 +02:00
Steffen Becker 14444b7169 i18n: a button that fills a library says Add, not Insert
Both the AutoText entry and the tab stop go into a list the user inserts from
later; only the cross-reference and ruby dialogs really write to the document.
2026-08-16 21:57:14 +02:00
Steffen Becker ad0df9df05 fix: the logo's stem is visible on every background
An <img> sees neither data-theme nor the document's color, so the ink stem and
its prefers-color-scheme flip left it dark on the dark chrome. One slate stem
instead: 5.1:1 on white, 3.5:1 on the dark surfaces.
2026-08-16 14:46:38 +02:00
Steffen Becker 383436439d i18n: the chromes are named Classic and Modern (beta)
The ribbon is the look users know as classic, the floating island the modern
one; beta because it has no entry point yet for comments, revisions, line
numbering or page design. Mode ids keep naming the shape.
2026-08-16 14:26:30 +02:00
Steffen Becker 2d3395137f style: the status bar follows the chrome above it
Under the ribbon it takes the same --w-* remap .ribbon gives its pickers: the
app's dark neutrals are bluer than Word's, so the two ends of the window did
not match. Classic keeps the themed set. --color-primary is the brand slate
now, so both chromes share one accent.
2026-08-16 14:19:17 +02:00
Steffen Becker b3e5a1c8a9 style: the ribbon takes the logo's colours
--w-accent is the logo's slate instead of Word's blue (5.1:1 on the chrome,
carries white), and the clay marks the contextual tabs — the pair meets where
those sit beside the normal ones. Both defined once from favicon.svg; the
label tone is darkened, the clay itself only 3.3:1 on white.
2026-08-16 14:12:30 +02:00
Steffen Becker 9b14d1654f feat: the style gallery lists heading levels 1–5 until asked for the rest
Both reference products define ten levels but only offer the first five;
visibleStyles() filters styleOrder for the gallery in either chrome, with a
session-only 'show all styles' toggle in the menu. A deeper level a block
already uses stays listed, so the caret always has an active style.
2026-08-16 14:01:30 +02:00
Steffen Becker bc98446198 docs: table-style import re-derives regions and unbakes the region text 2026-08-16 13:43:08 +02:00
Steffen Becker 8f38d15633 test: fuzz round 8 — region attrs and cell overrides on styled tables
styledTable now materializes the region attr (paintTable's shape), keeps
runs heading-safe only in bold regions, and layers direct overrides over
the paint: replaced or cleared cell shading and an own bottom border.
2026-08-16 13:42:36 +02:00
Steffen Becker 6987eda4fc fix: a link keeps its own paint over a baked region color
The editor's a-rule and LibreOffice's Internet Link style both outrank the
region CSS, so neither export lets the baked region color override a
non-plain link's paint: ODT leaves the color off the link run, DOCX paints
the link blue unless the run carries a direct color — the DOCX import
otherwise read its own link back as plain.
2026-08-16 13:42:30 +02:00
Steffen Becker 4c55e9b9f5 fix: imports re-derive a registry table style's regions and unbake its text
Both importers resolve the table's style name + look + grid position through
resolveTableCell: the region attr comes back on each cell, a bold region's
runs convert like headings (boldByDefault) and a run matching the region's
color loses that mark — the style's own text no longer lands as direct
formatting on reopen. A DOCX file carrying its own w:tblStylePr keeps the
baked areas instead; a style name not in the registry stays untouched.
2026-08-16 13:42:24 +02:00
Steffen Becker c473fd5f0f test: fuzz round 7 — indexes, table styles, frame pairs, vertical boxes
The generator now emits tableOfContents nodes of all five families, styled
tables painted through the real resolveTableCell (name + look + materialized
fill/borders), side-by-side topBottom image pairs (wrapAlign), vertical-text
boxes and the Caption paragraph style. normalize learns the index attr
defaults the DOCX TOC field has no switch for.
2026-08-16 13:17:47 +02:00
Steffen Becker 6789377b48 fix: DOCX carries the citation style and an all-off table look
The b:Sources part is also the citation style's only carrier, so a document
whose bibliography index sets a non-default style writes it even with no
source cited. The importer keeps a declared all-off w:tblLook ('') instead
of dropping it back to the default look.
2026-08-16 13:17:47 +02:00
Steffen Becker 6a36b89c18 fix: ODT indexes and shape boxes read back clean
The TOC importer no longer fabricates settings our own export mints: a right
stop at the column's end is the attr's default (null), and an entry template
resolving to the default style names no levelStyles entry. A custom-shape box
keeps its vertical text (the writing-mode read only ran for plain frames),
and a declared all-off tableLook ('') survives instead of reverting to the
default look.
2026-08-16 13:17:38 +02:00
Steffen Becker 4c11a527c9 test: fuzz round 6 — columns, named styles, floating boxes, table extras
The generator now emits columns sections, Title/Subtitle/Quotations
paragraphs (heading-safe runs), floating text boxes with offsets, and
table margins, cell borders, cell padding and formula cells.
2026-08-16 12:50:15 +02:00
Steffen Becker db3fbe32c1 fix: DOCX sections, box offsets and cell margins round clean
- a continuous break that only encodes a columns region no longer
  fabricates a section break or its own header/footer set
- a topBottom text box keeps its x offset (posOffset now written)
- cell margins read back in hundredth-cm, so twips don't show through
- a table margin's zero side stays the default instead of -0
2026-08-16 12:50:10 +02:00
Steffen Becker d32d40be3c fix: named styles and floating boxes read clean through ODT
- what a block's named style supplies (Title's centring, keep-with-next,
  its size as a raised run's percent reference) is direct formatting no more
- a topBottom box keeps its left place instead of gaining a fabricated
  centring; horizontal-pos right reads back as the box's wrapAlign
- a table margin's zero side stays the default instead of an explicit 0
2026-08-16 12:50:04 +02:00
Steffen Becker 5bf470f4b7 test: fuzz round 5 — revisions, fields, citations, char styles, floats
Generator gains insertion/deletion marks, sequence/index/date-time fields,
citations (one record per identifier), builtin character styles (redundant
direct marks dropped — import suppresses them like heading defaults) and
floating images (wrapDist only beside a side wrap). normalize canonicalizes
revision ids/dates like comments, recounts sequence numbers per category,
and drops an auto date field's value and a citation's derived label.
2026-08-16 12:07:22 +02:00
Steffen Becker 205ae72ecf fix: DOCX closes round-5 gaps — XE quotes, linked revisions, box features
An XE field's literal quote is backslash-escaped (Word's own escaping;
import already unescaped it) instead of flattened to an apostrophe. A
w:ins/w:del inside w:hyperlink imports (shared handleRevision closure).
Text boxes carry charStyle (w:rStyle), index entries, citations,
date/time fields and revision marks (w:delText for deletions);
dateTimeFieldParts/xeInstr are shared with the body path.
2026-08-16 12:07:14 +02:00
Steffen Becker afec07579a fix: a deletion's text keeps its formatting through ODT
The deleted runs are hoisted through every serialization pass and spliced
into the tracked-changes registry as styled runs (as LibreOffice stores
them); import converts them back as real inline content. One point marker
per change instead of one per fragment. A deletion's marker also no longer
makes a formula beside it display (aloneInParagraph counts text:change).
applyComments now declares dc:/loext: only when absent — beside a revision
the duplicate xmlns:dc made content.xml invalid.
2026-08-16 12:07:07 +02:00
Steffen Becker 1c5b6d73d5 test: fuzz round 4 — boxes, formulas, comments, bookmarks, ruby, row merges
Generator: text boxes (shape, fill, stroke), inline and display
formulas, comments, bookmarks with cross-references, ruby, rowspan
merges. Display rides a formula owning its line; a stroke width needs
a stroke color; a vertical merge keeps a second column so no row is
all covered cells. Normalize canonicalizes comment ids and dates
(naive local vs UTC are one instant) and trims a formula's latex
(the OMML side keeps the serializer's trailing space on purpose).
2026-08-16 11:22:30 +02:00
Steffen Becker 8e6616c562 feat: resolved comments travel through DOCX (commentsExtended.xml)
Word keeps a comment's resolved state as w15:done in
word/commentsExtended.xml, keyed by the w14:paraId of the comment
body's last paragraph. The docx package knows neither part, so a
post-pack pass stamps the paraId, mints the part with its content
type and relationship, and the importer reads it back.
2026-08-16 11:22:21 +02:00
Steffen Becker 1de86377e5 fix: DOCX text boxes carry paragraph formatting, inline features and marks
The hand-serialized txbxContent emitted only heading style, indent and
alignment: spacing, line height, indents, direction, shading, borders,
links, comment ranges, bookmarks, cross-references, formulas, rubies
and image rotation were all dropped. A bookmark still open at a
paragraph's end lost its runs (missing flush), and the formula/ruby
run regexes could start at a box's drawing run and swallow the box
preamble — they now stay inside one run.
2026-08-16 11:21:59 +02:00
Steffen Becker fdb456ffe6 fix: ODT sentinel passes anchor at the block tag, effects ride past annotations
A text box's anchor paragraph nests its children, so the STY/PBX passes'
scan to the closing tag started at the anchor and stole the first box
child's spec (its indent, direction, shading and borders). Both passes
now match the sentinel right after the opening tag, where it is emitted.
The TEF fallback also accepts bookmark markers and comment annotations
inside a run (tempered to the annotation's first close tag, -end kept
distinct), instead of cutting the effect short at them.
2026-08-16 11:21:20 +02:00
Steffen Becker 1264f9742c fix: dropping a fully covered table row shrinks the spans across it
A row holding only covered cells cannot exist in the editor and was
dropped on ODT import, but every rowspan reaching over it kept its
full count, leaving an inconsistent grid. Spans now shrink by the
dropped rows they covered.
2026-08-16 11:21:13 +02:00
Steffen Becker 6c79ec1197 test: fuzz round 3 — paragraph boxes, flow flags, tab stops, note labels
New in the pool: paragraph shading/rule lines (also in cells and lists), rtl
paragraphs, keepNext/keepLines/widowControl, tab stops, custom bullet chars,
heading levels to 8, image alt/rotation, row heights and custom note labels.
noHyphenation stays out: without the document-wide hyphenate flag the
importers suppress it as meaningless. 1000 sweep seeds clean on both legs.
2026-08-16 10:30:00 +02:00
Steffen Becker 044c59d895 feat: custom note marks travel through DOCX (w:customMarkFollows)
The docx package cannot express a custom footnote/endnote mark, so the anchor
always went out as an automatic reference and the label was lost — and the
importer never read one either, so a Word file's custom mark leaked into the
note's body text. A post-pack pass now rewrites the anchor (flag + literal
run) and the note part's <w:footnoteRef/>; the importer reads the flag, takes
the run's text as the label and strips its repeat off the note body.
2026-08-16 10:30:00 +02:00
Steffen Becker 5dab73ac3a test: fuzz round 2 — cell merges, list types, inline images
colspan merges (grid-consistent), orderedList listStyleType (upper-* only on
nested lists: lower/decimal forms are depth defaults the importers suppress),
and inline PNG images. 300 sweep seeds clean on both legs.
2026-08-16 07:58:24 +02:00
Steffen Becker 5258feb146 fix: sibling nested lists of different formats stop sharing one w:lvl
A DOCX numbering level keeps the first format it saw, so a second nested list
under the same parent with another number format or bullet char silently
rendered as the first one's (an attr-less nested list under an upper-roman
parent came back lower-alpha). The kind-mismatch fork now covers format and
bullet char too, and copies the shallower levels so the importer's cycle walk
still sees the ancestry.
2026-08-16 07:58:24 +02:00
Steffen Becker a7fe334900 test: widen the fuzz generator to effects, links, notes and cell props
New in the pool: underline/strike line styles, super/subscript, caps,
textPosition, links, page breaks, footnotes/endnotes, cell shading and
vertical alignment. normalize() canonicalizes importer-generated note ids and
drops the link mark's default plain:false; FUZZ_SEEDS widens the sweep beyond
the default 50. This is the suite that surfaced the last four export/import
fixes; all 300 sweep seeds now round-trip clean on both legs.
2026-08-16 07:50:22 +02:00
Steffen Becker 1f1176283a fix: a text effect covers its whole run and raises against the block's size
Two applyTextEffects bugs: the fallback span for an effect-only run stopped at
the first element, so everything behind a tab/line-break fell out of the span
and lost the effect — the run is now terminated by an empty sentinel pair and
inline empties ride along. And a raised run (textPosition) was written as a
percentage of the default 12pt even inside a heading, so the importer, which
resolves against the block's real size, read a different height back.
2026-08-16 07:46:43 +02:00
Steffen Becker ed7a40967a fix: an empty block no longer steals the next block's minted style
The whole-block regexes in the sentinel passes (page breaks, paragraph
styles/boxes, text effects, whitespace collapse) matched a self-closing
<text:p/> too — [^>]* happily ends in a slash — and then swallowed everything
up to the next real closing tag, so an empty cell paragraph took the restyle
meant for a later paragraph's sentinel and that paragraph lost it. The
opening-tag pattern now rejects self-closing tags.
2026-08-16 07:46:34 +02:00