oficina / docs Public

Paragraphs stack like lines, Shift+Enter breaks one, and PDFs have a left margin

Three things that were wrong in how a document renders, found by pasting
a list out of Google Docs and looking at the result.

Paragraph spacing. Docs' Normal text is 0pt before, 0pt after, 1.15 line
spacing, so consecutive paragraphs stack like lines and a visible gap is
an empty paragraph. Ours added 1em after every paragraph, so a pasted
list came out widely spaced with a double gap where the blank line was.
Now margin-block: 0 and line-height 1.4. Worth being precise about what
was NOT wrong: Docs has paragraphs, its Enter makes one, and ours
already matched. The difference was entirely spacing.

The empty-paragraph rule that comes with it is the part that would have
shipped broken. A blank paragraph was visible in the server-rendered
read view only because it carried that margin; with the margin gone,
<p></p> collapses to nothing, so a blank line would still show while
editing — ProseMirror puts a br inside an empty textblock, which is why
it is never :empty there — and vanish from the read view, the PDF and
the Markdown export. Same document, two shapes.

Shift+Enter. hard_break was in the schema and bound to nothing, and the
Rust renderer had no case for it, so a break would have been dropped by
every reader. Now bound and handled in the HTML, the text, the JSON the
exporters read, the PDF and the Markdown, where it is a backslash before
the line ending because that survives trailing-whitespace stripping.
Verified in a real browser: ALPHA, Shift+Enter, BRAVO, Enter, CHARLIE
gives one paragraph holding ALPHABRAVO plus one holding CHARLIE, and the
same shape comes back after a reload through the renderer.

The PDF's left margin. pdf.go drew text at x = ctx.indent, which starts
at zero and is only added to for quotes and lists, while the vertical
cursor correctly started at MarginPt. So body text began at the page
edge and, because wrapping still used the right content width, every
page had a two-inch gutter on the right. MarginPt was correct all along;
nothing applied it horizontally. No test asserted where text starts,
which is why a whole-page offset shipped, and there is one now.

Reviewed by Fable, which verified the p:empty mechanism, all three
x-computation sites, the hard_break name across six representations and
the Markdown form against the libraries' own sources, and found no test
that could not fail against the mistake it guards. Two findings fixed: a
comment asserting Shift+Enter used to fall through to the browser, when
prosemirror-view captures and preventDefaults every Enter so it did
nothing at all; and three PDF spacing constants this change turned into
load-bearing zeroes with nothing pinning them to the stylesheet.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Paul Campbell pushed by paul@keymail.dev 8777e55eaca1c15a5fe227dc2b8beb619f101a36 parent 2392ea3
12 files changed, +706 −43
  • core/src/fragment.rs +25 −0
  • core/tests/fragment.rs +59 −0
  • fixtures/editor-entry.js +46 −0
  • internal/core/testdata/docs_core.wasm binary
  • internal/docs/export_test.go +101 −0
  • internal/docs/static/{editor.0c0322ca56426314.js => editor.26513ec0d7ae331b.js} +11 −11
  • internal/docs/views/show.html +44 −6
  • internal/export/markdown.go +47 −2
  • internal/export/markdown_test.go +86 −0
  • internal/export/pdf.go +78 −9
  • internal/export/pdf_test.go +120 −0
  • internal/export/pdfread_test.go +89 −15

CI passed — run details

core/src/fragment.rs

Loading diff…

core/tests/fragment.rs

Loading diff…

fixtures/editor-entry.js

Loading diff…

internal/core/testdata/docs_core.wasm

Loading diff…

internal/docs/export_test.go

Loading diff…

internal/docs/static/{editor.0c0322ca56426314.js => editor.26513ec0d7ae331b.js}

Loading diff…

internal/docs/views/show.html

Loading diff…

internal/export/markdown.go

Loading diff…

internal/export/markdown_test.go

Loading diff…

internal/export/pdf.go

Loading diff…

internal/export/pdf_test.go

Loading diff…

internal/export/pdfread_test.go

Loading diff…