The ¶ toggle adds a format to each side: bold / italic / underline and a
paragraph style, as LibreOffice's Format… and its Paragraph Styles box.
A format narrows a text search to the runs carrying it, and with no term
the formatting is the search — every such run, or every paragraph in the
style, empty ones included.
A replacement with no text only reformats, which is how a document gets
restyled without retyping it; a heading style switches the node type over
the way setParagraphStyle does, so the extension takes the same sheet.
Both dialogs offer three sort keys and let you pick the type; only the
first key was offered, and the type was always the detected one. A later
key decides only where the ones before tie, and the sort stays stable.
Alphanumeric is the plain collation ("10" before "2") — that is what
choosing it over the detected type is for, so the two collators differ in
`numeric`. Sorted numerically, a cell that is no number sorts last either
way, as an empty cell does in a spreadsheet.
Cite by short name (LibreOffice's own), numbered, APA, MLA or Chicago:
the style sets the citation's label and the row's shape, and rides the
bibliography index node — ODF as the entry template LibreOffice
regenerates its rows from (verified through a re-save), Word as the
sources part's StyleName. Labels are derived and kept true by an
appendTransaction, so adding a citation renumbers the rest.
A section narrower than the reserved sheet now splits the difference
between its insets instead of hanging it all on the right, and every
per-page layer draws from the box's own left — sheet, header/footer, page
border and line numbers. The ribbon's Size menu gains the same per-section
block Orientation already had.
Word's Start at, per section: ODF writes style:page-number on the
paragraph that switches master page, DOCX a w:pgNumType w:start. That
section is no longer exported as a continuous break — probed, LibreOffice
drops the start off a continuous one, and neither word processor honours
it there. The footer counts from the nearest restart at or above the page.
Every recorded change in document order beside the comments pane: author,
date, the text, accept or reject in place. A change split by a paragraph
boundary is one row, so acceptRevision/rejectRevision take an id. The
author colour is a decoration, since a mark cannot know the document's
other authors; it nests inside the mark's span and carries the under/
strikethrough with it. shortcutHint no longer throws on an id nothing
binds, which heading 7-10 had just made reachable.
HTML stops at h6, so 7-10 render as unknown elements editor.css makes
blocks; their built-in styles continue level 6 at 12pt, since LibreOffice
writes these styles with no properties and resolves them from its own
pool. Both importers read a two-digit level again (the style-name regexes
stopped at one digit). Verified through a LibreOffice re-save of both
formats and in the live editor.
LibreOffice's Format > Paragraph > Text Flow checkbox. Only "off"
travels (Word has no per-paragraph "on") and only under a document that
hyphenates, or it would say what is already true. ODF writes
fo:hyphenate="false" into the paragraph's text properties, DOCX gains
w:suppressAutoHyphens through a post-pack pass — the docx package
exposes no option for it. Both survive a LibreOffice re-save.
The manifest registers .odt/.ott/.docx/.dotx as file handlers and a
launchQueue consumer opens what the OS passes, keeping the handle so the
first Save writes that file. A .dotx now reads like an .ott: Word's
importer, but never bound as the file to save over.
Layout's hyphenation, page numbers, line numbers and page decoration, and
Review's accept/reject and comment pane, all passed a label that the icon
variant drops. They are small buttons now, stacked two to a column so the band
keeps its height, with the caret after the label as a row reads.
The group's ↘ launcher is easy to miss, so the footnote dialog also gets a
labelled button in the band, on a cogwheel glyph. Both openers stay: the
corner arrow is where a Word user reaches for it.
The Footnotes group was one labelled button and two bare icons, one of them
the table-of-contents levels glyph. Both insert commands are now labelled big
buttons, and the options move to the group's ↘ launcher, where both word
processors open their footnote dialog. The glyphs draw a page: the note under
its separator at the foot, or the stack whose last page carries the list.
The note-separator, page-decoration and table-line colours were native
<input type=color> or a 20-swatch grid; they now open the app's own picker,
which grew a swatch-only variant (no icon) for a dialog field plus a
disabled state. Its panels move to the top layer: a transformed ancestor is
the containing block for position:fixed, and the floating table toolbar has
one — cell shading opened its palette in the bottom-right corner.
Both were selectable in the dialog and changed nothing — they only rode the
file formats, so an imported document numbered and placed its notes wrong.
noteLabels now counts within a bucket (class, chapter, or the page pageBreaks
publishes through setNoteAnchorPages); a collected footnote leaves position:
absolute and opens its own page, as LibreOffice does. Counting and position
move to the footnote tab, the only place LibreOffice offers them.
The separator's own alignment moves the line alone — probed, notes stay left.
The placements key watched each note's top but not its doc position, so an
edit that shifted the note section without moving a note left the decoration
set naming stale positions — ProseMirror dropped it and every footnote fell
back to its static spot, stacked on top of the next one. The position now
rides the key, resolved once beside the top.
An embedded prefers-color-scheme media query swaps the ink stem and
leaf fills for the app's own dark-theme tokens, so the near-black
stem no longer vanishes on a dark tab bar.
Its picture nests a second w:drawing, and the importer's subtree
search found *that* one's wp:inline first: the box was read as the
bare picture, so every word of the caption in it was dropped —
which is what Word's own Insert Caption writes for a floating
picture. The root is now the drawing's direct child. The export
side gains the caption's SEQ field inside a box, so the running
number stays live there too. Leg 30 covers both.
Two faults on one picture. A block after a float keeps its box over
the float — only its lines move — so the caption on top swallowed
every click meant for the picture; the frame is lifted above the
in-flow boxes. And a caption can only sit under a side-wrapped frame
if it is boxed to it, so its span in the column is measured and
becomes the caption's indent, which also drops it clear of the float.
Measured at 100% and 150%: caption under the picture, its width, in
all four wrap modes.
A picture with "above and below" wrap is a frame centred in the
column while its paragraph carries no alignment at all, so the
caption still went to the left margin. framePlacement reads the
place off the frame: centred, the side it is set against, or its
offset as the caption's indent. The debug dump gains a frames
section — every other one measures the flow downwards, and this
is a sideways question.
It was inserted as a plain paragraph, so a centred or right-aligned
picture got its caption at the left edge of the text column.
LibreOffice frames picture and caption together; a loose paragraph
carries that over as the alignment of the block it captions.
Measured in the live editor: a centred 240px picture at 520…760
now has its caption text at 580…700.
The three dialogs placed themselves, so the ribbon's viewport
coordinates met CSS that hangs the box above its anchor: the
formula popover landed under the band, clipped. Each caller now
owns the placement — `anchored` in the ribbon, like every other
menu there, and a wrapper in the editor that clamps a tall one
into the scroll area instead of letting the overflow cut it.
Two endpoints rather than a box: no text, no fill, and the diagonal of
the frame with `flipV` picking which one. The three kinds share their
preset names, since the heads a file declares are what tell them apart.
ODF gives a line its own element and its heads a named marker, which
needs the draw: prefix declared in styles.xml — undeclared, the file
does not parse at all. Word keeps the frame and flips it.
The Navigator was still listed as missing; only split view is. Formula
recalculation stops the walk at the blocks a table cannot be in, or it
visits every run of the document per keystroke. One dead CSS rule goes,
so svelte-check reports nothing at all.
Nothing here talks to a server at runtime, so a cached shell is the whole
editor. The worker reads the build's hashed asset names out of the entry
document, which precaches without a build step; the document itself stays
network-first, or it would keep naming the assets of the build it was
cached with. Matching ignores Vary: a precached asset is fetched without
the page's Origin, and a server answering "Vary: Origin" never matches it.
Verified in a headless Chromium with the network cut.
The formula rides the cell, as ODF writes it; Word's field syntax is what
is stored, since it is the wider language. `ooow:` in the ODF attribute
value is a namespace prefix — undeclared, LibreOffice reads it as part of
the formula and prints "Expression is faulty".
Sort takes one key and detects the type per cell where both dialogs make
you pick. Number recognition is off by default, as it is in LibreOffice,
and a formula parses a cell's text either way — exactly as there.
The page grid was one repeating cycle, so every page had to be the same
size. It is now a table of height runs the placement pass grows as each
section's first page becomes known, and the sheets are one box per page
instead of a repeating gradient — .paper reserves the widest section's
paper, a narrower one takes the difference as a right inset.
Layout ▸ Orientation offers "this section"; the size stays document-wide
in the ribbon but round-trips per section from a file.
The `docx` package swaps the pair itself for a landscape section, so it
wants the portrait box; handed the swapped one it wrote a portrait page
merely labelled landscape — LibreOffice renders it portrait (measured:
21×29.7cm out of a landscape document).
Per-section paper rides the same fix: an HfSet carries its own format and
orientation, ODF cloning the section's page layout as it already did for
the margins and DOCX writing it into each w:sectPr.
The citation is an inline atom holding the whole record, which is what ODF
writes on text:bibliography-mark — so no database travels beside the file.
The list is a new index family: one row per source, in document order and
without page numbers, as LibreOffice prints one.
DOCX cites by tag and keeps the sources in a custom-XML part the docx
package knows nothing about, so the post-pack pass mints the part, its
properties, both relationships and the content type itself.
Classic chrome already had the logo as an About-dialog trigger; the
ribbon had none. A small favicon icon after the language picker opens
the same dialog, giving both chromes the same affordance.
Two asymmetric blades on a ground line, in the slate/clay brand
colors, replace the old page-lines icon. The old favicon is kept as
favicon-old.svg. The About dialog shows it above the wordmark.
References ▸ Index entry marks a place with the term the index should
list — under a key of its own where it belongs elsewhere — and the Index
in the same menu lists each term once, sorted case-insensitively, with
every page it was marked on.
Both formats write a place, not a range, so the entry is an inline atom:
ODF text:alphabetical-index-mark plus text:alphabetical-index, Word XE
plus INDEX. LibreOffice reads our file back unchanged. The bibliography,
the other half of that gap, is still open.
F5 (LibreOffice's key) opens an outline of the document's headings:
click one to jump, and move it up or down or promote/demote its level,
each carrying everything under the heading. Below it the tables,
pictures and bookmarks LibreOffice's Navigator also lists.
A move swaps with the adjacent chapter at the same level and stops at a
heading above it, so a subheading never reparents itself. The pane docks
right beside the comments one — the classic chrome's Tools chip owns the
left edge.
A .ott/.dotx is the built document with one label changed — ODF's
package media type, Word's content type for the document part — so the
picker offers both and the chosen extension decides which exporter runs.
Opening or saving also records the file in a recent list. The name is in
localStorage so the menu draws at once; the handle that reopens it is in
IndexedDB beside it. A file that has moved, or whose permission the
browser drops on reload, is reported and dropped from the list.
A handful of photos is the whole ~5 MB quota, and past it the document
stopped saving. The autosaved JSON now keeps an `idb:<key>` per picture
and the bytes live in their own store; the key is a sampled content
hash, so a picture used twice is stored once and each save sweeps what
the document no longer names.
loadDocument is async as a result and Editor.svelte awaits it. Where
IndexedDB is unavailable the data-URIs stay inline exactly as before.
A list inside a box was flattened to literal bullet/number runs and a
picture was dropped: the box XML is a hand-built string, and neither had
the package parts it needs.
applyTextBoxesDocx already holds the whole unzipped package, so it mints
them — a media entry with its relationship, and an abstractNum/num pair
above the package's own ids, the paragraphs carrying a sentinel until
those ids exist. Verified through LibreOffice and back through our own
importer, nesting, marker symbols and start values intact.
Twelve presets beside the three rectangular ones. Each outline is one
polygon in utils/shapes.ts, given once: the node view draws it as an SVG
path, the ODF export scales the same points into draw:enhanced-path, and
Word gets the preset's name and draws its own geometry.
A gallery in the box toolbar and the ribbon renders its tiles from that
same table, so a new shape needs no icon. Verified through LibreOffice
from both formats; a preset we can't draw still imports as a warning.
Two marks carry a revision with its author and date: an insertion is
marked text, a deletion is text kept and struck through. Consecutive
typing joins one change; accept/reject work at the cursor or on all.
ODF splits the pair — text:change-start/-end around an insertion, a
text:change where a deletion was, its text in the tracked-changes
registry. DOCX uses w:ins/w:del with w:delText. Both round-trip, and
accepting every change gives the document an unrecorded edit would.
Continuous or per page, every nth line, empty lines counted or not —
LibreOffice's Tools ▸ Line Numbering, round-tripping as its
text:linenumbering-configuration and Word's w:lnNumType. CSS exposes no line
boxes, so the layer Range-selects each block and takes the distinct tops of
its client rects as its lines; LibreOffice renders the exported file with the
numbers in the same places.
LibreOffice's Format ▸ Page Style ▸ Area/Borders and its Format ▸ Watermark,
all three round-tripping. Probed placements that are not guessable: ODF reads
a watermark only out of the master page's header (and only with xmlns:draw
declared), Word's w:sectPr wants pgBorders after pgMar or it loses the header
references, and the VML shapetype has to travel or LibreOffice cannot resolve
it. On screen the watermark is an SVG textLength, since a fontwork shape
stretches its text to its box rather than setting a font size.
The same generated index the table of contents is, over the captions of one
category instead of the headings — an `index` attr picks the family, so the
node view, the page numbers and the leader dots are shared. Round-trips as
ODF text:illustration-index / text:table-index, found by the counter's name
the way LibreOffice finds them, and Word's TOC \c "Figure" field.
A caption is a paragraph in the Caption style holding a sequence field —
one counter per category, in document order, renumbered as the document is
edited and on load (a file's cached numbers are unreadable wherever the
format is not arabic). Round-trips as an ODF text:sequence with
LibreOffice's own ooow:Illustration+1 formula and a Word SEQ field.
Caption style values probed off LibreOffice: 10pt italic, 0.212cm around.
The block's own direction, set in the Paragraph dialog where LibreOffice
keeps it, so a quoted Hebrew or Arabic paragraph turns inside a left-to-right
document. Rides ODF style:writing-mode / Word w:bidi through cells and list
items as well as top-level blocks; the direction the page already has is
inheritance, so neither importer stamps it on every block.
The structural header, beside the styling one: pageBreaks grows the
between-rows spacer by the first row's height and draws a copy of that
row — colgroup included, so the columns line up — at the foot of the
gap, which is the top of the continuation. Round-trips as ODF
table:table-header-rows and Word's w:tblHeader; LibreOffice keeps both.
Word's "don't allow row to break" needs nothing: a row that fits a page
is already an atomic leaf here.
The comment is a mark carrying its own author, date, body and resolved
flag — the bookmark pattern, so no second store and no schema node. The
pane lists them in document order and jumps to the text each marks.
ODF gets office:annotation + office:annotation-end and LibreOffice's
loext:resolved, DOCX the range pair plus word/comments.xml; a point
comment, which LibreOffice writes without a range, attaches to the run
before it. Ctrl+M no longer strips a comment, a bookmark or a link.
Layout > Page numbers: 1 / i / I / a / A and a first-page number, both
round-tripping. ODF has no document-level start — probed, LibreOffice
puts style:page-number on the first paragraph's own style — so the
export mints one there and the page layout carries style:num-format;
Word takes both in the first section's w:pgNumType. The page *count*
field stays decimal, as the one both of them write does.