Skip to content

feat(leptos): SSR of the initial document + render-mode-neutral adapter - #9

Merged
juanma-dev merged 1 commit into
mainfrom
feat/leptos-ssr
Jul 31, 2026
Merged

juanma-dev merged 1 commit into
mainfrom
feat/leptos-ssr

Conversation

@juanma-dev

Copy link
Copy Markdown
Owner

Summary

Ships the roadmap's deferred "Server-side rendering of the initial document (Leptos SSR)" item — the recommended v0.6 candidate. Two halves:

  1. taino-edit-leptos is now render-mode neutral. It used to hard-enable leptos/csr, which cargo's feature unification propagated into every downstream app — making SSR apps impossible to build against the adapter. The library now enables no mode; the app picks exactly one of csr/hydrate/ssr (as with every Leptos component library). The browser test-suite keeps csr via a wasm32-scoped dev-dependency.

  2. <TainoEditor> server-renders the initial document. The component's view now carries a one-shot inner_html snapshot of the initial doc, produced by the new host-safe taino_edit_dom::doc_view_html. Under SSR that snapshot is the server payload (content visible + indexable before any wasm); under CSR it improves first paint. The pre-hydration document is deliberately not contenteditable — edits typed before the editor boots would be lost to the mount swap. It is also deliberately non-reactive: after mount, EditorView owns that subtree.

doc_view_html emits exactly the markup EditorView::mount builds, including the trailing <br> caret anchor in empty textblocks (no visual jump on hydration), <span> for to_dom-less nodes, and browser-style void-element emission. TRAILING_BREAK_ATTR/is_textblock moved into the new module and are shared with view.rs, so serializer and renderer cannot drift.

New example: examples/ssr-leptos

Full axum + cargo leptos SSR/hydration demo, in its own cargo workspace (Leptos's csr and ssr/hydrate modes cannot share one feature graph; the root workspace's demos are csr). curl http://127.0.0.1:3000 shows the document in the response HTML.

CI

New ssr-example job: fmt, clippy --features ssr, clippy --lib --target wasm32-unknown-unknown --features hydrate, and the SSR render test.

Tests

  • 6 host unit tests for doc_view_html, incl. byte-parity with Node::to_html for plain content (pins escaping/attr order to core).
  • Headless-Chromium contract test: the serializer's output, parsed by the browser, equals the mounted editor's DOM (innerHTML both sides) for a fixture covering heading attrs, nested marks, escaped text, a void <img> atom, an empty textblock, and a transparent node.
  • Component first-paint test: document visible before the mount effect runs, not editable, and exactly one copy after mount (no duplication).
  • Example SSR test: the server-rendered page embeds doc_view_html(initial_doc) byte-for-byte and contains no contenteditable.
  • All existing suites green: root gates (fmt/clippy/test/doc -D warnings) and the full wasm-bindgen browser matrix for taino-edit-dom + taino-edit-leptos.

Docs

README (SSR section, render-mode note, example listed, stale "v0.5.2 released" fixed), CHANGELOG [Unreleased], ROADMAP (SSR item closed with date; stale "Up next: Release v0.1.0" replaced with the v0.6.0 hand-off + post-v0.6 candidates).

🤖 Generated with Claude Code

Closes the last deferred v0.6 candidate from the roadmap: the Leptos
adapter can now be used in SSR/hydrate apps, and the initial document is
real HTML in the server response.

- taino-edit-leptos no longer force-enables leptos/csr (feature
  unification made downstream SSR apps impossible); the app now picks
  exactly one render mode, like any Leptos component library. Browser
  tests keep csr via a wasm32-scoped dev-dependency.
- New taino_edit_dom::doc_view_html (re-exported by the adapter): a
  host-safe, web-sys-free serializer emitting exactly the markup
  EditorView::mount builds — trailing <br> caret anchors in empty
  textblocks, <span> for to_dom-less nodes, browser-style void elements.
  TRAILING_BREAK_ATTR and is_textblock moved to view_html and shared
  with view.rs so the two can never drift.
- <TainoEditor> now renders the initial doc as a one-shot (deliberately
  non-reactive) inner_html snapshot: the SSR payload and the CSR first
  paint. The pre-hydration document is not contenteditable on purpose —
  edits typed before the editor boots would be lost to the mount swap.
- Contract pinned by tests: 6 host unit tests (incl. byte-parity with
  Node::to_html for plain content), a headless-Chromium parse-equality
  test (serializer output vs mounted DOM), and a first-paint component
  test (doc visible pre-effect, read-only, no duplication after mount).
- examples/ssr-leptos: axum + cargo-leptos SSR/hydration demo in its own
  cargo workspace (leptos csr and ssr/hydrate cannot share a feature
  graph), with a host test asserting the server HTML embeds the
  serialized initial document byte-for-byte. New ssr-example CI job
  gates fmt + clippy(ssr) + clippy(hydrate, wasm32) + the SSR test.
- Docs: README SSR section (+ stale v0.5.2 status fixed), CHANGELOG
  Unreleased entry, ROADMAP SSR item closed and stale v0.1.0 "Up next"
  replaced with the v0.6.0 hand-off.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@juanma-dev
juanma-dev merged commit 273c524 into main Jul 31, 2026
2 checks passed
@juanma-dev
juanma-dev deleted the feat/leptos-ssr branch July 31, 2026 18:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant