sxtp: patch + signals primitives (Datastar-borrowed)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 46s
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 46s
Adds two new top-level SXTP message types alongside
request/response/condition/event, modelled on Datastar's
datastar-patch-elements and datastar-patch-signals SSE events:
(patch :target "#x" :mode outer :body (~card)) - DOM fragment
morph. Subsumes HTMX swap modes. Mode is outer (default) |
inner | replace | prepend | append | before | after | remove.
(signals :values {:n 3} :only-if-missing false) - reactive
state patch. nil value removes the signal. only-if-missing
skips existing signals (lazy init).
A server response stream can mix both freely; clients dispatch
by head symbol, ordering preserved. Cleaner than HTMX's
swap-mode-per-trigger because the patch shape is decoupled from
the triggering element/attribute.
Spec at applications/sxtp/spec.sx (patch-fields, signals-fields,
patch-modes, example-patch-stream). Constructors / predicates /
accessors / serialise / parse in lib/host/sxtp.sx. 25 new tests
in lib/host/tests/sxtp.sx (predicates, mode normalisation, fixed
field order, remove-without-body, signals round-trip). Host
conformance 129/129 (was 104/104).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -97,6 +97,42 @@
|
||||
(:body "Any SX value — event payload (optional)")
|
||||
(:time "Number — unix timestamp (optional)"))))
|
||||
|
||||
;; ── patch (DOM fragment patch — borrowed from Datastar) ───────────
|
||||
;; A server-driven instruction to morph a region of the client DOM.
|
||||
;; Subsumes HTMX swap modes; the :body is an SX subtree that the client
|
||||
;; renders to DOM nodes before applying the mode at the target.
|
||||
(define
|
||||
patch-fields
|
||||
(quote
|
||||
((:target "String — CSS selector for the element to patch (required)")
|
||||
(:mode "Symbol — patch mode (optional, default outer)")
|
||||
(:body "SX tree — the new content (omitted for mode remove)")
|
||||
(:transition "Boolean — use a view transition (optional, default false)"))))
|
||||
|
||||
(define
|
||||
patch-modes
|
||||
(quote
|
||||
((outer "Replace the target's outerHTML (default; the morph target)")
|
||||
(inner "Replace the target's innerHTML, preserving the wrapper")
|
||||
(replace "Hard-replace without morphing (no diff, plain swap)")
|
||||
(prepend "Insert the body as the target's first child")
|
||||
(append "Insert the body as the target's last child")
|
||||
(before "Insert the body before the target")
|
||||
(after "Insert the body after the target")
|
||||
(remove "Detach the target; :body MUST be absent"))))
|
||||
|
||||
;; ── signals (reactive state patch — borrowed from Datastar) ──────
|
||||
;; A server-driven update to client-side reactive signals. :values is a
|
||||
;; dict of signal-name -> new-value; setting a value to nil REMOVES the
|
||||
;; signal. With :only-if-missing true, existing signals are not touched
|
||||
;; (use this to lazily initialise signal state without clobbering).
|
||||
(define
|
||||
signals-fields
|
||||
(quote
|
||||
((:values "Dict — signal-name -> new-value (required)")
|
||||
(:only-if-missing
|
||||
"Boolean — only set signals that don't yet exist (optional, default false)"))))
|
||||
|
||||
(define
|
||||
example-navigate
|
||||
(quote
|
||||
@@ -148,6 +184,23 @@
|
||||
:message "No such post"
|
||||
:retry false)))))
|
||||
|
||||
;; A streaming response intermixing patch + signals: the server pushes
|
||||
;; DOM updates AND signal updates over the same channel. The client
|
||||
;; dispatches each message by its head symbol; ordering is preserved.
|
||||
(define
|
||||
example-patch-stream
|
||||
(quote
|
||||
((request :verb subscribe :path "/cart/live" :capabilities (fetch))
|
||||
(response :status ok :stream true)
|
||||
(signals :values {:cart/count 3 :cart/loading false})
|
||||
(patch
|
||||
:target "#cart-mini"
|
||||
:mode outer
|
||||
:body (~cart-mini :count 3 :total 47.50))
|
||||
(patch :target "#flash" :mode inner :body (p "Item added."))
|
||||
(signals :values {:cart/loading true})
|
||||
(patch :target "#cart-loading-spinner" :mode remove))))
|
||||
|
||||
(define
|
||||
example-inspect
|
||||
(quote
|
||||
|
||||
Reference in New Issue
Block a user