loadDocumentLanguage falls back to resolveBrowserLocale() instead of a
hardcoded 'en', so a de-* browser starts with the German dictionary. A
saved choice and the document's own fo:language still win.
collect-licenses falls back to the README's License section (only when it
carries a copyright line) for packages without a LICENSE file; hash.js and
isarray now ship their full notice. The About dialog links /licenses.txt,
which was previously reachable only by knowing the URL.
The appearance menu's "Modern (beta)" entry sits behind
import.meta.env.DEV, so a production build offers no way into the
island chrome until it catches up with the ribbon. The chrome itself
and a stored 'modern' pref keep working.
The formula/sort/split popover hangs above the table's top-left corner;
with the table at the top of the page the keep-in-view clamp slid it
down over the table itself. With no room above, the dialog now opens
below the table when that fits the viewport, and only a table filling
the whole window keeps the old clamp.
At the very first position of a table with no block before it, Enter
inserts a paragraph above the table and moves the caret there, as both
word processors do — previously the caret could never get before such
a table. Generic first-child check, so it also covers a table leading
a cell, text box or columns section.
The grid snapped the scroll to the top of the first row on every zoom,
losing the reader's place whenever it sat mid-row and ignoring the
pointer anchor of a trackpad zoom. It now uses the same anchor logic as
a single pane, measured against the grid canvas: the pointer for a
wheel zoom, else the viewport top; firstRow is re-read after the write
so the page cells re-aim immediately. Measured drift < 1px across a
92% -> 194% wheel burst.
Tearing down the focused pane's DOM on a layout switch fires blur
synchronously inside Svelte's commit; TipTap answers with a focus-meta
transaction, and onTransaction's tick write threw state_unsafe_mutation
there — aborting the handler before the other panes got the new state.
The tick now bumps in a microtask, after the flush, before paint.
The pm-pagecount listener sat on the hosts[0] element, which Svelte
replaces when the pane layout changes (multi-page grid, split view).
The editor view moves to the new host, the listener died with the old
one — numPages froze, so no new page ever appeared in the grid.
Listen on the view's own DOM instead, which is only ever re-parented.
Three bare lines said nothing and read the same at any size. It now draws
the box on the text baseline like the floating toolbar's glyph, with the
outlined object the other three wrap icons use.
The tab only showed while the box was node-selected, so it vanished the
moment you clicked in to type — the floating toolbar stayed. It now reads
findTextBox(), the same box that toolbar reads; an image selected inside a
box still shows Picture Format alone. Vertical text was the one control the
tab was missing, now a Text group of its own.
styleName was kept on split, so Enter at the end of a heading left a paragraph
carrying it: drawn like a heading, absent from the navigator's outline, exported
as text:p. It is no longer kept there. setParagraphStyle also missed the name
whenever it switched the node type — a following updateAttributes looks for the
new type in the pre-chain state — so it now rides along with setNode. Levels 2–6
never reached that command at all: the digit class built from MAX_HEADING_LEVEL
(10) read as [0-1], so Heading's own Mod-Alt-N took the key and set a level with
no style; that keymap is dropped, running ahead of every plugin binding.
The fallback download shows a one-time hint about the browser's
ask-where-to-save setting, keeps its anchor in the DOM and the blob
URL alive past the click (Gecko). Failed saves and exports now report
the underlying error, with a clear message when a script blocker
prevented a lazy chunk from loading. A full or blocked localStorage
no longer fails the save when recording the recent-files entry.
public/ is copied verbatim into the build, src/assets/ is not - so the fonts
were deployed without the license texts that OFL-1.1 and Bitstream Vera require
to travel with them. Moving the texts to public/font-licenses/ fixes that with
no build step. The notices covered only Liberation and missed five bundled
dependencies; the commercial license now states what it cannot relicense, the
copyleft German dictionary and thesaurus above all.
The id said 'classic' while the UI called the ribbon Classic and the
island Modern (beta). Type, i18n keys, comments and docs now agree;
a stored legacy 'classic' still selects the island.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
--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.
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.
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.
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.
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.
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.
- 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
- 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
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.