Synsema docsENES

Frontend

Synsema sirve HTML desde el servidor (SSR) — sin framework ni CSS impuestos. Dos caminos: render() (control total del diseño) y content() (negociable por agentes).

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() — templates libres

render("page.html", data) devuelve una respuesta HTML; body of render(...) es el string. Los templates son HTML con huecos { ... }:

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

Renderizá los partials compartidos una vez al arrancar: let nav be body of render("partials/nav.html", {}).

content() — páginas negociables por agentes

Construí un árbol semántico una vez; el runtime sirve HTML a humanos y Markdown/JSON a agentes (por header Accept o sufijo .md/.json) — ideal para docs/blogs que un LLM deba leer.

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

Assets estáticos y JS de cliente

Lo default es un mount estático: static "/assets" from "./static" sirve tu CSS/JS/imágenes con ETag, Range y gzip. El cliente no tiene restricciones — JS vanilla, htmx, o cualquier framework.

Ojo — las rutas declaradas ganan sobre los static mounts. Si tu app tiene rutas comodín (p. ej. GET /:lang/:slug), el mount static nunca se alcanza, así que servís los assets desde una ruta declarada. Serví texto (css/js/svg) con respond, y binario (imágenes, fuentes) con read_file_bytes + binary — el read_file a secas decodifica UTF-8 y corrompería un 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-exacto
    give respond(read_file(p), "text/css; charset=utf-8")

Este mismo sitio de docs está hecho exactamente así: sus rutas comodín (/:lang/:version/:slug) obligan a una ruta declarada /assets/*path, y la imagen de preview Open Graph se sirve con binary(read_file_bytes(...), "image/png").