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.
The minifier strips every legal comment, so the bundle reached users without
the notice MIT and Apache-2.0 require to travel with a copy. collect-licenses
quotes all 97 bundled packages into public/licenses.txt, which deploys like the
font and dictionary licenses do. The About dialog's version now carries the
short commit, so a user of the hosted app can identify the source it was built
from - what AGPL 13 asks for and a fixed 0.1.0 could not answer.
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 signing instructions named a phrase the workflow ignores, and told
contributors to put it in the PR description rather than a comment - following
them left a PR blocked forever. Both files now quote the action's own phrase.
Drops the note that CLA Assistant still had to be set up; it is running.
The setup section still claimed no test suite existed. It now names check,
test, test:lo and test:smoke, and what CI runs of them; the linter note stays,
since none is configured.
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.
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.
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.
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.
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.
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.