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>
20 files changed,
+1363
−27
.amadan/ci.d/10-gate+14 −0core/src/fragment.rs+79 −2core/tests/fragment.rs+153 −0fixtures/align.mjs+150 −0fixtures/align.test.mjs+238 −0fixtures/editor-entry.js+24 −4fixtures/generate-fragment.mjs+25 −1fixtures/toolbar.mjs+88 −0fixtures/vectors/fragment/aligned.binbinaryinternal/core/testdata/docs_core.wasmbinaryinternal/docs/export.go+11 −0internal/docs/export_test.go+188 −0internal/docs/render.go+10 −0internal/docs/static/{editor.26513ec0d7ae331b.js => editor.98a16ab4d0c731c1.js}+13 −13internal/docs/views/show.html+73 −5internal/export/document.go+40 −1internal/export/markdown.go+12 −0internal/export/markdown_test.go+30 −0internal/export/pdf.go+45 −1internal/export/pdf_test.go+170 −0
.amadan/ci.d/10-gateLoading diff…
core/src/fragment.rsLoading diff…
core/tests/fragment.rsLoading diff…
fixtures/align.mjsLoading diff…
fixtures/align.test.mjsLoading diff…
fixtures/editor-entry.jsLoading diff…
fixtures/generate-fragment.mjsLoading diff…
fixtures/toolbar.mjsLoading diff…
fixtures/vectors/fragment/aligned.binLoading diff…
internal/core/testdata/docs_core.wasmLoading diff…
internal/docs/export.goLoading diff…
internal/docs/export_test.goLoading diff…
internal/docs/render.goLoading diff…
internal/docs/static/{editor.26513ec0d7ae331b.js => editor.98a16ab4d0c731c1.js}Loading diff…
internal/docs/views/show.htmlLoading diff…
internal/export/document.goLoading diff…
internal/export/markdown.goLoading diff…
internal/export/markdown_test.goLoading diff…
internal/export/pdf.goLoading diff…
internal/export/pdf_test.goLoading diff…