oficina / docs Public

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>
Paul Campbell pushed by paul@keymail.dev 2c40c625e0aaa1eb4776acce4b4e84619bf6d9cc parent 94ddd8c
11 files changed, +648 −27
  • fixtures/editor-entry.js +47 −0
  • fixtures/menu.mjs +12 −2
  • fixtures/pages.mjs +226 −0
  • internal/docs/handlers.go +11 −0
  • internal/docs/menu.go +61 −5
  • internal/docs/menu_test.go +18 −2
  • internal/docs/pages_test.go +174 −0
  • internal/docs/render.go +8 −0
  • internal/docs/static/editor.2c000feef616911e.js +14 −0
  • internal/docs/static/editor.573637d22790cda3.js +0 −14
  • internal/docs/views/show.html +77 −4
fixtures/editor-entry.js

Loading diff…

fixtures/menu.mjs

Loading diff…

fixtures/pages.mjs

Loading diff…

internal/docs/handlers.go

Loading diff…

internal/docs/menu.go

Loading diff…

internal/docs/menu_test.go

Loading diff…

internal/docs/pages_test.go

Loading diff…

internal/docs/render.go

Loading diff…

internal/docs/static/editor.2c000feef616911e.js

Loading diff…

internal/docs/static/editor.573637d22790cda3.js

Loading diff…

internal/docs/views/show.html

Loading diff…