Add ui-routes: a routes analog that defaults handler responses
Gather hypermedia routes under ui-routes and an action handler can just end with its live-collection mutation: a handler that returns a value which isn't an http-response is taken as "handled, nothing extra for the actor" and becomes an empty (ui-response). Removes the easy-to-forget trailing (ui-response) from mutate-and-broadcast handlers.
Structural scoping (David's call over a path-prefix middleware): ui-routes is a plain routes-analog handler, so it composes inside routes just like router -- put UI routes under ui-routes and a JSON API / webhook under plain routes/router, which never coerce. #f still falls through (a missing-id mutation returning #f becomes a 404 via with-not-found) and explicit responses pass through untouched.
Lives in (sigil web ui) since it injects ui-response; imports (sigil web routes) and reuses routes (no cycle -- routes does not import ui).
src/sigil/web/ui.sgl | 33 +++++++++++++++++++++++++++++++++
1 file changed, 33 insertions(+)src/sigil/web/ui.sglmodified
(sigil sxml) (sigil http request) (sigil http response) (sigil web routes) (sigil resources)) (export ui-js ui-flash ui-response ui-routes ;; Deprecated aliases (kept through 1.0) sse-morph cache-control: "no-cache") body: (string-join updates ""))) ;;; Combine hypermedia route handlers, defaulting their responses. ;;; ;;; Like `routes` (first non-#f handler wins), but a handler that returns a ;;; value which isn't an HTTP response is taken to mean "handled, nothing ;;; extra for the acting user" and becomes an empty `(ui-response)`. So an ;;; action handler can just end with its live-collection mutation: ;;; ;;; ```scheme ;;; (define (toggle-handler req) ;;; (live-update! todos (item-id req) ;;; (lambda (i) (dict-set i done?: (not (dict-ref i done?:)))))) ;;; ``` ;;; ;;; Gather your hypermedia routes under `ui-routes` and keep other kinds ;;; (a JSON API, a webhook) under plain `routes`/`router`, which never ;;; coerce. `#f` still falls through, and explicit responses pass through ;;; untouched, so `ui-routes` composes inside `routes` just like `router`: ;;; ;;; ```scheme ;;; (routes ;;; (ui-routes (router …ui…) (live-routes todos)) ; forgiving ;;; (router …api…)) ; untouched ;;; ``` (define (ui-routes . handlers) (let ((combined (apply routes handlers))) (lambda (request) (let ((result (combined request))) (if (or (not result) (http-response? result)) result (ui-response)))))) ;; ============================================================ ;; Full-Page Response Helpers