# ADR 0003 — SSR-first, htmx-hard, no JSON **Status:** accepted (2026-06-10). The load-bearing front-end principle for demarkus-library. Deliberately strict: we are pushing the hypermedia approach as far as it goes and treating every escape hatch as a tracked concession. ## Decision The application is **hypermedia-driven (HATEOAS)**. The server renders HTML; htmx is the only general interaction layer; there is no JSON API and no client-side application state. This is not "SSR with some htmx" — it is htmx-hard: the default answer to any interaction is "return server-rendered HTML and swap it in." ### The rules (hard) 1. **The server owns all rendering.** Every response is HTML — a full page or a fragment built by `html/template`. The browser receives finished markup, never data to assemble. goldmark→bluemonday→template is the only render path. 2. **No JSON tier, ever.** Handlers return HTML via `c.Render`/`c.HTML`, never `c.JSON`. No REST/JSON projection, no GraphQL, no serialized view models crossing the wire. (Health check returns plain text; that's the lone non-HTML response and it's not application data.) 3. **No client-side state.** Application state lives on the server (session + the world). The DOM is the only client state, and the server is its source of truth. No stores, no client router, no reactivity layer. 4. **Interactivity is `hx-*` attributes.** Navigation (`hx-boost`), search (`hx-get` debounced), version/panel swaps, pagination, forms (`hx-post` → PUBLISH) — all are htmx returning server-rendered fragments into a target. Reach for an attribute before a line of JS. 5. **Self-hosted assets.** htmx is vendored in-repo and served from the Go binary (`/static/htmx.min.js`, pinned 2.0.4) — not a CDN. Single binary, no runtime dependency, no third-party origin, version pinned and reviewable. (Embodied 2026-06-10.) 6. **Fragments are first-class.** Templates are factored so the inner content block can render standalone (for an htmx swap) or inside the page chrome (full load). A fragment request and a full navigation hit the same handler and the same template — no duplicate rendering paths. 7. **Degrade without JS.** Because the server renders every route in full, `hx-boost` navigation and form posts work with JS disabled. htmx is enhancement, never the renderer. Keep it that way: no interaction may be *only* reachable through htmx. ### How far we push it (the experiment) We will attempt the entire app as hypermedia before admitting any island: - **Catalog search (LOOKUP):** debounced `hx-get`, results as an HTML `