host: live context — device/locale routed into the render-fold (composition roadmap step 4)

The render context is now the live EXECUTION environment: host/blog--comp-ctx reads device
(mobile/desktop from User-Agent) and locale (from Accept-Language) PURELY from the request
headers — no perform — alongside auth + the graph-query resolver. So the SAME composition
object renders responsively/personalised: `(alt (when (eq "device" "mobile") …) …)` is a
responsive layout, `(when (eq "locale" "fr") …)` a localised variant. The object (its
when-variants) is the definition; the context picks which path renders.

host/blog--device-of / host/blog--locale-of; comp-ctx now (principal req) — post handler
passes req; /compose-demo gains a device-variant block. Reactive/live values plug into the
same context later with no new combinators (the plan's "make the context live" axis).

Verified via focused harness eval (mobile+fr vs desktop+en contexts render M/D variants;
no-req ctx omits device). Tests added.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-30 20:43:06 +00:00
parent 29aa7cd70f
commit 5ead6e73c7
2 changed files with 40 additions and 7 deletions

View File

@@ -539,11 +539,27 @@
(cond
((= rel "is-a") (map host/blog-get (host/blog-instances-of type)))
(else (list))))))
;; the render context for a :body: auth from the principal + the graph-query resolver.
;; live context values, read PURELY from the request headers (no perform) so the SAME
;; object renders responsively/personalised per request — `(alt (when (eq "device" "mobile")
;; …) …)` is a responsive layout, `(when (eq "locale" "fr") …)` a localised variant.
(define host/blog--device-of
(fn (req)
(let ((ua (str (or (dream-header req "user-agent") ""))))
(if (or (contains? ua "Mobile") (contains? ua "Android") (contains? ua "iPhone"))
"mobile" "desktop"))))
(define host/blog--locale-of
(fn (req)
(let ((al (str (or (dream-header req "accept-language") ""))))
(if (>= (len al) 2) (substr al 0 2) "en"))))
;; the render context for a :body: auth from the principal + live device/locale from the
;; request + the graph-query resolver. The context is the EXECUTION environment — the
;; object (its when-variants) is the definition; this picks which path renders.
(define host/blog--comp-ctx
(fn (principal)
(merge (if (nil? principal) {} {"auth" "yes"})
{"query" host/blog--comp-query})))
(fn (principal req)
(merge
(merge (if (nil? principal) {} {"auth" "yes"})
(if (nil? req) {} {"device" (host/blog--device-of req) "locale" (host/blog--locale-of req)}))
{"query" host/blog--comp-query})))
;; Seed a live demo of the composition fold: one object, rendered by host/comp-render, that
;; shows seq + alt(when auth) + row(par) + each — and renders DIFFERENTLY logged-in vs out.
(define host/blog-seed-compose-demo!
@@ -564,6 +580,9 @@
(text "<p>This whole page is <b>one composition object</b>, rendered by the fold — it renders differently depending on context.</p>")
(alt (when (has "auth") (text "<p style=\"color:green\"><b>Members:</b> you are logged in.</p>"))
(else (text "<p style=\"color:#999\"><i>Log in to see the member-only block.</i></p>")))
;; live context: a responsive variant chosen by the request's device (User-Agent).
(alt (when (eq "device" "mobile") (text "<p>📱 <b>Mobile layout</b> (device from the request).</p>"))
(else (text "<p>🖥️ <b>Desktop layout</b> (device from the request).</p>")))
(text "<h3>Two columns (par)</h3>")
(row (text "<div style=\"flex:1;border:1px solid #ccc;padding:0.5em\">Column A</div>")
(text "<div style=\"flex:1;border:1px solid #ccc;padding:0.5em\">Column B</div>"))
@@ -1153,7 +1172,7 @@
;; (host/comp-render) against a context (auth from the principal); else the
;; legacy sx_content path. The SAME object renders differently per context.
(body-html (if (get r :body)
(host/comp-render (get r :body) (host/blog--comp-ctx principal))
(host/comp-render (get r :body) (host/blog--comp-ctx principal req))
(host/blog-render r)))
;; all relation blocks (Related, Tags, Types, Tagged-with-this …)
;; come from iterating the registry — one section, registry-driven.