Files
rose-ash/lib/host/static.sx
giles dbcbc39ebe host: blog SPA scaffolding (WASM kernel) — server side complete, boost blocked on bundle rebuild
Turn the blog into a SPA using the SX-htmx engine (web/engine.sx) booting the
WASM OCaml kernel (same evaluator as the server) in-browser, with sx-boost
fragment-swapping every link into #content.

Server side DONE + verified:
- lib/host/static.sx: GET /static/** serves shared/static via the file-read
  primitive (ctype by ext, traversal-guarded, 404 on missing). Wired into
  serve.sh (module + route group). Tested: kernel JS + .wasm binary-exact.
- host/blog--page is now the SPA shell: full page = WASM boot scripts +
  sx-boost=#content wrapper + #content; on SX-Request:true returns ONLY the
  inner content fragment for the engine to swap. All 13 handlers thread req.
- docker-compose mounts ./shared/static.
- lib/host/playwright/spa-check.{spec.js,run-spa-check.sh}: boot/boost/swap/back.

Client side: the WASM kernel BOOTS (SxKernel object, data-sx-ready=true, web
stack loads). BLOCKER: the bundled .sxbc throw 'VM: unknown opcode 0' vs this
worktree's kernel -> .sx source fallback -> boot.sx source fails 'Expected
list, got string' -> process-boosted never binds links (boosted 0/N). Fix =
rebuild a consistent WASM bundle (recompile .sxbc against the kernel via
scripts/sx-build-all.sh); the browser wasm target isn't built here yet. See
plans/host-spa.md. Live NOT redeployed (stays on pre-SPA process).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 20:53:06 +00:00

53 lines
2.3 KiB
Plaintext

;; lib/host/static.sx — serve the client kernel + assets so the blog can boot the
;; SX-htmx hypermedia engine (web/engine.sx) and run as a SPA. The native
;; http-listen host reads files with the `file-read` primitive (no perform), so
;; GET /static/** maps to a file under the static root (default "shared/static",
;; resolved against the server cwd — mount ./shared/static there in the container).
;; Depends on lib/dream/types.sx (dream-response/-html-status/-param) + router.
(define host/static-root "shared/static")
(define host/static-use-root! (fn (r) (set! host/static-root r)))
;; content-type by file extension; default to octet-stream.
(define host/static--ctype
(fn (path)
(cond
((ends-with? path ".js") "application/javascript; charset=utf-8")
((ends-with? path ".mjs") "application/javascript; charset=utf-8")
((ends-with? path ".css") "text/css; charset=utf-8")
((ends-with? path ".json") "application/json; charset=utf-8")
((ends-with? path ".map") "application/json; charset=utf-8")
((ends-with? path ".svg") "image/svg+xml")
((ends-with? path ".png") "image/png")
((ends-with? path ".woff2") "font/woff2")
((ends-with? path ".wasm") "application/wasm")
(true "application/octet-stream"))))
;; reject empty, absolute, or traversal paths.
(define host/static--safe?
(fn (rel)
(and (> (len rel) 0)
(not (starts-with? rel "/"))
(not (string-contains? rel "..")))))
;; Serve one asset by its path relative to the static root. file-read THROWS on a
;; missing file, so gate on file-exists? first and return a 404 instead.
(define host/static-serve
(fn (rel)
(if (not (host/static--safe? rel))
(dream-html-status 403 "Forbidden")
(let ((path (str host/static-root "/" rel)))
(if (not (file-exists? path))
(dream-html-status 404 "Not Found")
(dream-response 200
{:content-type (host/static--ctype rel)
:cache-control "public, max-age=3600"}
(file-read path)))))))
;; Route group: GET /static/** -> file under the static root. A plain route LIST
;; (like host/feed-routes); host/serve combines + flattens the groups itself.
(define host/static-routes
(list
(dream-get "/static/**"
(fn (req) (host/static-serve (dream-param req "**"))))))