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). Docs below use renderPretty / renderPagePretty so the result panel is readable; those are for humans only.

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>