Pages: infinite by default, paged when you ask
The last of Paul's named list. Page breaks are drawn as widget decorations over measured block heights, and nothing about them is in the document. That is not an optimisation, it is the only shape that works. A persisted page break would be an edit: it would merge, and two people typing in one document would fight over where page three starts. It is also a category error — the CRDT holds a flat sequence of blocks, and a page is a fact about a piece of paper. So two people at different window widths, zoom levels or with different fonts loaded see different breaks, and that is correct rather than a bug. A COOKIE rather than localStorage, which is the one decision here with a visible consequence: the server reads it and renders data-view on the canvas, so someone who prefers paged gets paged in the first paint instead of watching an infinite document reflow when the deferred bundle runs. localStorage is invisible to the server and would guarantee that flash. Per person, not per document — how you like to read is not something to impose on a collaborator. Measuring is coalesced to one animation frame and re-run on a ResizeObserver, which is what catches the case nobody thinks of: a web font finishing loading and every line reflowing under breaks computed against the fallback. A CLAIM IN show.html WAS CORRECTED RATHER THAN LEFT STANDING. It said a document "paginates the same way on screen and in its own export" because both measure with the same font programs. That was true only while the screen had no pages. pdf.go breaks between LINES and this breaks between BLOCKS, so any page whose last paragraph straddles the boundary differs. Shared fonts make the two agree about where a LINE ends, which is all that paragraph was entitled to say. Screen numbers are orientation; the PDF's are the ones to quote to somebody. A block taller than a page overflows its gap rather than being split — splitting inside a paragraph is line-level layout this app has no engine for, and faking it would put a break where no exporter agrees. Two of my own tests were wrong first. One signed in six times in a loop and hit the sign-in rate limiter, failing for a reason unrelated to what it checked. The other counted every aria-pressed="true" in the page to prove exactly one View item was pressed, and found two — the formatting bar's own toggles. It was measuring the page and reporting on the menu.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
11 files changed,
+648
−27
fixtures/editor-entry.js+47 −0fixtures/menu.mjs+12 −2fixtures/pages.mjs+226 −0internal/docs/handlers.go+11 −0internal/docs/menu.go+61 −5internal/docs/menu_test.go+18 −2internal/docs/pages_test.go+174 −0internal/docs/render.go+8 −0internal/docs/static/editor.2c000feef616911e.js+14 −0internal/docs/static/editor.573637d22790cda3.js+0 −14internal/docs/views/show.html+77 −4
fixtures/editor-entry.jsLoading diff…
fixtures/menu.mjsLoading diff…
fixtures/pages.mjsLoading diff…
internal/docs/handlers.goLoading diff…
internal/docs/menu.goLoading diff…
internal/docs/menu_test.goLoading diff…
internal/docs/pages_test.goLoading diff…
internal/docs/render.goLoading diff…
internal/docs/static/editor.2c000feef616911e.jsLoading diff…
internal/docs/static/editor.573637d22790cda3.jsLoading diff…
internal/docs/views/show.htmlLoading diff…