Synsema docsENES

Frontend

Synsema serves HTML from the server (SSR) — no imposed framework or CSS. Two paths: render() templates (full design control) and content() (agent-negotiable).

frontend.syn
-- Doc example: render() templates. HTML with { holes }, { each } loops, auto-escaped.
-- Self-contained: writes a tiny template, then renders it (so it runs anywhere, sandboxed).
intent: "doc example: frontend render()"
require file.write("_doctest_card.html")
require file.read("_doctest_card.html")

write_file("_doctest_card.html", "<div class=\"card\"><h2>{ title }</h2><ul>{ each tag in tags }<li>{ tag }</li>{ end }</ul></div>")

print(body of render("_doctest_card.html", {"title": "Synsema", "tags": ["fast", "secure"]}))

test "render fills holes, loops, and HTML-escapes by default (XSS-safe)"
    let html be body of render("_doctest_card.html", {"title": "A <b> tag", "tags": ["x", "y"]})
    assert(contains(html, "<h2>A &lt;b&gt; tag</h2>"))     -- auto-escaped
    assert(contains(html, "<li>x</li><li>y</li>"))         -- { each } loop

render() — free-form templates

render("page.html", data) returns an HTML response; body of render(...) is the string. Templates are HTML with { ... } holes:

route "GET /"
    give render("pages/home.html", {"title": "My App", "items": items})

Render shared partials once at startup: let nav be body of render("partials/nav.html", {}).

content() — agent-negotiable pages

Build a semantic tree once; the runtime serves HTML to humans and Markdown/JSON to agents (by Accept header or a .md/.json suffix) — ideal for docs/blogs an LLM should read.

route "GET /docs/:slug"
    give content(page([heading(1, "Title"), prose("…"), code("let x be 1", "synsema")], {
        "title": "Title", "description": "…", "stylesheet": "/assets/app.css"
    }))

Static assets & client JS

The default is a static mount: static "/assets" from "./static" serves your CSS/JS/images with ETag, Range and gzip. The client is unrestricted — vanilla JS, htmx, or any framework.

Gotcha — declared routes beat static mounts. If your app has wildcard routes (e.g. GET /:lang/:slug), a static mount is never reached, so you serve the assets from a declared route instead. Serve text (css/js/svg) with respond, and binary (images, fonts) with read_file_bytes + binary — plain read_file decodes UTF-8 and would corrupt a PNG:

route "GET /assets/*path"
    let p be "static/" + params.path
    when not file_exists(p)
        give not_found("asset not found")
    when ends_with(p, ".png")
        give binary(read_file_bytes(p), "image/png")   -- byte-exact
    give respond(read_file(p), "text/css; charset=utf-8")

This very docs site is built exactly this way: its wildcard routes (/:lang/:version/:slug) force a declared /assets/*path route, and the Open Graph preview image is served with binary(read_file_bytes(...), "image/png").