Nav refactoring: - Split nav-data.sx (32 forms) into 6 files: nav-geography, nav-language, nav-applications, nav-etc, nav-tools, nav-tree - Add Tools top-level nav category with SX Tools and Services pages - New services-tools.sx page documenting the rose-ash-services MCP server JS build fixes (fixes 5 Playwright failures): - Wire web/web-signals.sx into JS build (stores, events, resources) - Add cek-try primitive to JS platform (island hydration error handling) - Merge PRIMITIVES into getRenderEnv (island env was missing primitives) - Rename web/signals.sx → web/web-signals.sx to avoid spec/ collision New MCP tools: - sx_trace: step-through CEK evaluation showing lookups, calls, returns - sx_deps: dependency analysis — free symbols + cross-file resolution - sx_build_manifest: show build contents for JS and OCaml targets - sx_harness_eval extended: multi-file loading + setup expressions Deep path bug fix: - Native OCaml list-replace and navigate bypass CEK callback chain - Fixes sx_replace_node and sx_read_subtree corruption on paths 6+ deep Tests: 1478/1478 JS full suite, 91/91 Playwright Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
98 lines
3.7 KiB
Plaintext
98 lines
3.7 KiB
Plaintext
;; ==========================================================================
|
|
;; web/signals.sx — Web platform signal extensions
|
|
;;
|
|
;; Extends the core reactive signal spec (spec/signals.sx) with web-specific
|
|
;; features: marsh scopes (DOM lifecycle), named stores (page-level state),
|
|
;; event bridge (lake→island communication), and async resources.
|
|
;;
|
|
;; These depend on platform primitives:
|
|
;; dom-set-data, dom-get-data, dom-listen, dom-dispatch, event-detail,
|
|
;; promise-then
|
|
;; ==========================================================================
|
|
|
|
|
|
;; --------------------------------------------------------------------------
|
|
;; Marsh scopes — child scopes within islands
|
|
;; --------------------------------------------------------------------------
|
|
|
|
(define with-marsh-scope :effects [mutation io]
|
|
(fn (marsh-el (body-fn :as lambda))
|
|
(let ((disposers (list)))
|
|
(with-island-scope
|
|
(fn (d) (append! disposers d))
|
|
body-fn)
|
|
(dom-set-data marsh-el "sx-marsh-disposers" disposers))))
|
|
|
|
(define dispose-marsh-scope :effects [mutation io]
|
|
(fn (marsh-el)
|
|
(let ((disposers (dom-get-data marsh-el "sx-marsh-disposers")))
|
|
(when disposers
|
|
(for-each (fn ((d :as lambda)) (cek-call d nil)) disposers)
|
|
(dom-set-data marsh-el "sx-marsh-disposers" nil)))))
|
|
|
|
|
|
;; --------------------------------------------------------------------------
|
|
;; Named stores — page-level signal containers
|
|
;; --------------------------------------------------------------------------
|
|
|
|
(define *store-registry* (dict))
|
|
|
|
(define def-store :effects [mutation]
|
|
(fn ((name :as string) (init-fn :as lambda))
|
|
(let ((registry *store-registry*))
|
|
(when (not (has-key? registry name))
|
|
(set! *store-registry* (assoc registry name (cek-call init-fn nil))))
|
|
(get *store-registry* name))))
|
|
|
|
(define use-store :effects []
|
|
(fn ((name :as string))
|
|
(if (has-key? *store-registry* name)
|
|
(get *store-registry* name)
|
|
(error (str "Store not found: " name
|
|
". Call (def-store ...) before (use-store ...).")))))
|
|
|
|
(define clear-stores :effects [mutation]
|
|
(fn ()
|
|
(set! *store-registry* (dict))))
|
|
|
|
|
|
;; --------------------------------------------------------------------------
|
|
;; Event bridge — DOM event communication for lake→island
|
|
;; --------------------------------------------------------------------------
|
|
|
|
(define emit-event :effects [io]
|
|
(fn (el (event-name :as string) detail)
|
|
(dom-dispatch el event-name detail)))
|
|
|
|
(define on-event :effects [io]
|
|
(fn (el (event-name :as string) (handler :as lambda))
|
|
(dom-on el event-name handler)))
|
|
|
|
(define bridge-event :effects [mutation io]
|
|
(fn (el (event-name :as string) (target-signal :as signal) transform-fn)
|
|
(effect (fn ()
|
|
(let ((remove (dom-on el event-name
|
|
(fn (e)
|
|
(let ((detail (event-detail e))
|
|
(new-val (if transform-fn
|
|
(cek-call transform-fn (list detail))
|
|
detail)))
|
|
(reset! target-signal new-val))))))
|
|
remove)))))
|
|
|
|
|
|
;; --------------------------------------------------------------------------
|
|
;; Resource — async signal with loading/resolved/error states
|
|
;; --------------------------------------------------------------------------
|
|
|
|
(define resource :effects [mutation io]
|
|
(fn ((fetch-fn :as lambda))
|
|
(let ((state (signal (dict "loading" true "data" nil "error" nil))))
|
|
(promise-then
|
|
(cek-call fetch-fn nil)
|
|
(fn (data)
|
|
(reset! state (dict "loading" false "data" data "error" nil)))
|
|
(fn (err)
|
|
(reset! state (dict "loading" false "data" nil "error" err))))
|
|
state)))
|