HTML / SSR

ascent-html renders the same UI AST to a string. There is no separate server walker: it mounts into a disposable in-memory DOM with the same Mount engine the browser uses, then serializes the tree. Server and client cannot drift.

Html.render / Html.renderPage emit compact markup (what morph consumes): a fragment, no doctype. Document(title)(head*)(body*) is a whole document. renderDocument mounts that html root as the document element and emits the doctype. renderDocumentPretty is the indented twin. Pretty output is for humans; the newlines would become text nodes if fed back through morph.

Docs below use the pretty entry points so the result panel is readable.

render and renderPage

{
  val ui = E.div(A.className("card"), E.h1("Hello"), E.p("rendered on the server"))
  Html.renderPretty(ui)
}
<div data-ascent="3xmrk9mlrljk" class="card">
  <h1 data-ascent="10rbnu6k8nayd">Hello</h1>
  <p data-ascent="1nye82ifts9sr">rendered on the server</p>
</div>
{
  object Box extends CssClass(S.padding.px(8))
  Html.renderPagePretty(E.div(Box, "styled"))
}
<div data-ascent="1rgt1ud03uic9" class="ascent-docs-HtmlPage-Box-2">styled</div>

.ascent-docs-HtmlPage-Box-2 {
  padding: 8.0px;
}

Reactive snapshots

Reactive boundaries are .get-ted for their current value and rendered inline; one static snapshot, no observers. Event handlers and lifecycle hooks are omitted. Scoped runs once in a fresh Scope, renders, and closes. data-ascent ids match the client Mount stamps when using the same IdMode.

for
  n    <- sq(3)
  html <- Html.renderPretty(E.span(n.map(_.toString)))
yield html
<span data-ascent="1bnfv1c0sne5f">3</span>

Document

Document(title)(head*)(body*) builds the shell: charset, viewport, title, then the head list, then the body list. A script in the body list stays in body. StylePlacement.Aside returns CSS beside the markup. StylePlacement.InlineInHead also appends one style in head when the tree touched CSS. The effect fails with Html.Error when the mounted tree is not a single html element.

Html.renderDocument(
  Document("Todos host")()(E.script(A.src("/host.js"))),
  StylePlacement.Aside,
)
<!DOCTYPE html><html data-ascent="e9ye28zd0mnn" lang="en"><head data-ascent="7icj2gd0vkyv"><meta data-ascent="vy777jlxxf3p" charset="utf-8"><meta data-ascent="pmprh6ir0ph" name="viewport" content="width=device-width, initial-scale=1"><title data-ascent="9v2yyr6bdatc">Todos host</title></head><body data-ascent="1jv3zv936qgjp"><script data-ascent="1qmrjmgmogk85" src="/host.js"></script></body></html>
Html.renderDocumentPretty(
  Document("Todos host")()(E.p("Hello")),
  StylePlacement.Aside,
)
<!DOCTYPE html>
<html data-ascent="cd1m9kilxi4w" lang="en">
  <head data-ascent="7icj2gd0vkyv">
    <meta data-ascent="vy777jlxxf3p" charset="utf-8">
    <meta data-ascent="pmprh6ir0ph" name="viewport" content="width=device-width, initial-scale=1">
    <title data-ascent="9v2yyr6bdatc">Todos host</title>
  </head>
  <body data-ascent="m6j0x39ztdqv">
    <p data-ascent="1x5pvdacje1gx">Hello</p>
  </body>
</html>