oficina / docs Public

Paragraphs can be centred and right-aligned

The first block-level formatting in the app, and the first time a
document node carries a value rather than a bare name. That is what
shapes the design.

fragment_html's output is emitted unescaped into the page, which is safe
only because no document bytes have ever reached attribute position —
the inline marks are a closed set of class names precisely to keep it
that way. So alignment is stored as an attribute in the CRDT and mapped
through a closed allowlist to a class that is a literal in the renderer:
center and right get a class, left gets none, and anything else is
dropped. A hostile value never reaches the output because the value is
only ever a key, never a thing that is printed.

That is pinned rather than asserted. The test puts quotes and angle
brackets in the attribute and sweeps the rendered HTML for them; against
a naive implementation it fails with the payload sitting in attribute
position. Byte-identity for documents with no alignment is pinned over
the existing fixtures from two directions. An alignment a future version
invents is dropped by all four allowlists, and — accidentally rather
than by design, but worth keeping — the editor preserves it rather than
destroying it, so a document shared with a newer build survives a round
trip through this one.

In the PDF, alignment is arithmetic rather than a property: each line is
offset within the column the paragraph wraps to, which is margin- and
indent-relative, so a centred line inside a blockquote centres in the
indented column rather than on the page. Markdown has no representation
for alignment and drops it, which the exporter now says out loud.

Reviewed by Fable, which reproduced the hostile-value mutation, broke
the PDF's indent term to confirm the quote test carries its own weight,
and found three things since fixed: a toolbar guard that never ran in
CI while its comment claimed it ran at module load, an assertion that
compared a document to itself and so could not fail, and comments
claiming the pressed state changes weight as well as colour when
font-weight does nothing to an SVG icon. The pressed state now carries a
non-colour cue, which repairs the three older toolbar buttons too.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Paul Campbell pushed by paul@keymail.dev f4bc1559408ebebca7c7bcc07b81f62d38ee266c parent 8777e55
20 files changed, +1363 −27
  • .amadan/ci.d/10-gate +14 −0
  • core/src/fragment.rs +79 −2
  • core/tests/fragment.rs +153 −0
  • fixtures/align.mjs +150 −0
  • fixtures/align.test.mjs +238 −0
  • fixtures/editor-entry.js +24 −4
  • fixtures/generate-fragment.mjs +25 −1
  • fixtures/toolbar.mjs +88 −0
  • fixtures/vectors/fragment/aligned.bin binary
  • internal/core/testdata/docs_core.wasm binary
  • internal/docs/export.go +11 −0
  • internal/docs/export_test.go +188 −0
  • internal/docs/render.go +10 −0
  • internal/docs/static/{editor.26513ec0d7ae331b.js => editor.98a16ab4d0c731c1.js} +13 −13
  • internal/docs/views/show.html +73 −5
  • internal/export/document.go +40 −1
  • internal/export/markdown.go +12 −0
  • internal/export/markdown_test.go +30 −0
  • internal/export/pdf.go +45 −1
  • internal/export/pdf_test.go +170 −0

landed from worktree-align

.amadan/ci.d/10-gate

Loading diff…

core/src/fragment.rs

Loading diff…

core/tests/fragment.rs

Loading diff…

fixtures/align.mjs

Loading diff…

fixtures/align.test.mjs

Loading diff…

fixtures/editor-entry.js

Loading diff…

fixtures/generate-fragment.mjs

Loading diff…

fixtures/toolbar.mjs

Loading diff…

fixtures/vectors/fragment/aligned.bin

Loading diff…

internal/core/testdata/docs_core.wasm

Loading diff…

internal/docs/export.go

Loading diff…

internal/docs/export_test.go

Loading diff…

internal/docs/render.go

Loading diff…

internal/docs/static/{editor.26513ec0d7ae331b.js => editor.98a16ab4d0c731c1.js}

Loading diff…

internal/docs/views/show.html

Loading diff…

internal/export/document.go

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…