Fix event-bridge + add client? primitive + header store foundation
- Event-bridge: rewrite island to use document-level addEventListener via effect + host-callback, bypassing broken container-ref + schedule-idle. Also use host-get for event-detail (WASM host handles). - Add client? primitive: false on server (sx_primitives._is_client ref), true in browser (sx_browser.ml sets ref). Enables SSR-safe conditional logic for client-only features like def-store. - Header island: use def-store for idx/shade signals when client? is true, falling back to plain signals on server. Foundation for SPA nav state preservation (store registry persistence still needs work). - Remove unused client? K.eval override from sx-platform.js. 100 passed, 1 skipped (isomorphic nav — store registry resets on SPA nav), 0 failed. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -657,6 +657,8 @@ let make_server_env () =
|
||||
match v with
|
||||
| Thunk (body, closure_env) -> Sx_ref.eval_expr body (Env closure_env)
|
||||
| other -> other);
|
||||
(* client? returns false on server — overridden in browser via K.eval *)
|
||||
ignore (env_bind env "client?" (NativeFn ("client?", fun _ -> Bool false)));
|
||||
env
|
||||
|
||||
|
||||
|
||||
@@ -439,6 +439,9 @@ let api_fn_arity fn_js =
|
||||
let () =
|
||||
let bind name fn = ignore (env_bind global_env name (NativeFn (name, fn))) in
|
||||
|
||||
(* client? returns true in browser — set the ref so the primitive returns true *)
|
||||
Sx_primitives._is_client := true;
|
||||
|
||||
(* --- Evaluation --- *)
|
||||
bind "cek-eval" (fun args ->
|
||||
match args with
|
||||
|
||||
@@ -12,6 +12,7 @@ let _sx_call_fn : (value -> value list -> value) ref =
|
||||
ref (fun _ _ -> raise (Eval_error "sx_call not initialized"))
|
||||
let _sx_trampoline_fn : (value -> value) ref =
|
||||
ref (fun v -> v)
|
||||
let _is_client : bool ref = ref false
|
||||
|
||||
let register name fn = Hashtbl.replace primitives name fn
|
||||
|
||||
@@ -664,6 +665,8 @@ let () =
|
||||
match args with [String msg] -> raise (Eval_error msg)
|
||||
| [a] -> raise (Eval_error (to_string a))
|
||||
| _ -> raise (Eval_error "error: 1 arg"));
|
||||
(* client? — false by default (server); sx_browser.ml sets _is_client := true *)
|
||||
register "client?" (fun _args -> Bool !_is_client);
|
||||
register "apply" (fun args ->
|
||||
let call f a =
|
||||
match f with
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
shared/static/wasm/sx_browser.bc.wasm.assets/sx-2ec218f4.wasm
Normal file
BIN
shared/static/wasm/sx_browser.bc.wasm.assets/sx-2ec218f4.wasm
Normal file
Binary file not shown.
BIN
shared/static/wasm/sx_browser.bc.wasm.assets/sx-31fbd690.wasm
Normal file
BIN
shared/static/wasm/sx_browser.bc.wasm.assets/sx-31fbd690.wasm
Normal file
Binary file not shown.
BIN
shared/static/wasm/sx_browser.bc.wasm.assets/sx-3909a451.wasm
Normal file
BIN
shared/static/wasm/sx_browser.bc.wasm.assets/sx-3909a451.wasm
Normal file
Binary file not shown.
BIN
shared/static/wasm/sx_browser.bc.wasm.assets/sx-3b656442.wasm
Normal file
BIN
shared/static/wasm/sx_browser.bc.wasm.assets/sx-3b656442.wasm
Normal file
Binary file not shown.
BIN
shared/static/wasm/sx_browser.bc.wasm.assets/sx-46414742.wasm
Normal file
BIN
shared/static/wasm/sx_browser.bc.wasm.assets/sx-46414742.wasm
Normal file
Binary file not shown.
BIN
shared/static/wasm/sx_browser.bc.wasm.assets/sx-46cdfe5e.wasm
Normal file
BIN
shared/static/wasm/sx_browser.bc.wasm.assets/sx-46cdfe5e.wasm
Normal file
Binary file not shown.
BIN
shared/static/wasm/sx_browser.bc.wasm.assets/sx-48fa79b9.wasm
Normal file
BIN
shared/static/wasm/sx_browser.bc.wasm.assets/sx-48fa79b9.wasm
Normal file
Binary file not shown.
BIN
shared/static/wasm/sx_browser.bc.wasm.assets/sx-4e2acbf7.wasm
Normal file
BIN
shared/static/wasm/sx_browser.bc.wasm.assets/sx-4e2acbf7.wasm
Normal file
Binary file not shown.
BIN
shared/static/wasm/sx_browser.bc.wasm.assets/sx-777119c1.wasm
Normal file
BIN
shared/static/wasm/sx_browser.bc.wasm.assets/sx-777119c1.wasm
Normal file
Binary file not shown.
BIN
shared/static/wasm/sx_browser.bc.wasm.assets/sx-85155ecd.wasm
Normal file
BIN
shared/static/wasm/sx_browser.bc.wasm.assets/sx-85155ecd.wasm
Normal file
Binary file not shown.
BIN
shared/static/wasm/sx_browser.bc.wasm.assets/sx-951e6734.wasm
Normal file
BIN
shared/static/wasm/sx_browser.bc.wasm.assets/sx-951e6734.wasm
Normal file
Binary file not shown.
BIN
shared/static/wasm/sx_browser.bc.wasm.assets/sx-b3e92dbc.wasm
Normal file
BIN
shared/static/wasm/sx_browser.bc.wasm.assets/sx-b3e92dbc.wasm
Normal file
Binary file not shown.
BIN
shared/static/wasm/sx_browser.bc.wasm.assets/sx-c223a920.wasm
Normal file
BIN
shared/static/wasm/sx_browser.bc.wasm.assets/sx-c223a920.wasm
Normal file
Binary file not shown.
BIN
shared/static/wasm/sx_browser.bc.wasm.assets/sx-c29a668e.wasm
Normal file
BIN
shared/static/wasm/sx_browser.bc.wasm.assets/sx-c29a668e.wasm
Normal file
Binary file not shown.
BIN
shared/static/wasm/sx_browser.bc.wasm.assets/sx-e28ed000.wasm
Normal file
BIN
shared/static/wasm/sx_browser.bc.wasm.assets/sx-e28ed000.wasm
Normal file
Binary file not shown.
BIN
shared/static/wasm/sx_browser.bc.wasm.assets/sx-fc3f3649.wasm
Normal file
BIN
shared/static/wasm/sx_browser.bc.wasm.assets/sx-fc3f3649.wasm
Normal file
Binary file not shown.
@@ -1792,7 +1792,7 @@
|
||||
blake2_js_for_wasm_create: blake2_js_for_wasm_create};
|
||||
}
|
||||
(globalThis))
|
||||
({"link":[["runtime-0db9b496",0],["prelude-d7e4b000",0],["stdlib-23ce0836",[]],["sx-64e6b16e",[2]],["jsoo_runtime-f96b44a8",[2]],["js_of_ocaml-651f6707",[2,4]],["dune__exe__Sx_browser-2ac146e9",[2,3,5]],["std_exit-10fb8830",[2]],["start-80fdb768",0]],"generated":(b=>{var
|
||||
({"link":[["runtime-0db9b496",0],["prelude-d7e4b000",0],["stdlib-23ce0836",[]],["sx-951e6734",[2]],["jsoo_runtime-f96b44a8",[2]],["js_of_ocaml-651f6707",[2,4]],["dune__exe__Sx_browser-6b156118",[2,3,5]],["std_exit-10fb8830",[2]],["start-80fdb768",0]],"generated":(b=>{var
|
||||
c=b,a=b?.module?.export||b;return{"env":{"caml_ba_kind_of_typed_array":()=>{throw new
|
||||
Error("caml_ba_kind_of_typed_array not implemented")},"caml_exn_with_js_backtrace":()=>{throw new
|
||||
Error("caml_exn_with_js_backtrace not implemented")},"caml_int64_create_lo_mi_hi":()=>{throw new
|
||||
|
||||
@@ -17,8 +17,9 @@
|
||||
;; content) flows through the island, around the rocks (reactive signals).
|
||||
(defisland ~layouts/header (&key path)
|
||||
(let ((families (list "violet" "rose" "blue" "emerald" "amber" "cyan" "red" "teal" "pink" "indigo"))
|
||||
(idx (signal 0))
|
||||
(shade (signal 500))
|
||||
(store (if (client?) (def-store "header-color" (fn () {:idx (signal 0) :shade (signal 500)})) nil))
|
||||
(idx (if store (get store "idx") (signal 0)))
|
||||
(shade (if store (get store "shade") (signal 500)))
|
||||
(current-family (computed (fn ()
|
||||
(nth families (mod (deref idx) (len families)))))))
|
||||
(div (~cssx/tw :tokens "block max-w-3xl mx-auto px-4 pt-8 pb-4 text-center")
|
||||
|
||||
@@ -515,16 +515,14 @@
|
||||
|
||||
;; 14. Event bridge — lake→island communication via custom DOM events
|
||||
(defisland ~reactive-islands/index/demo-event-bridge ()
|
||||
(let ((container-ref (dict "current" nil))
|
||||
(messages (signal (list)))
|
||||
(_eff (schedule-idle (fn ()
|
||||
(let ((el (get container-ref "current")))
|
||||
(when el
|
||||
(on-event el "inbox:message"
|
||||
(fn (e)
|
||||
(swap! messages (fn (old)
|
||||
(append old (host-get (event-detail e) "text"))))))))))))
|
||||
(div :ref container-ref
|
||||
(let ((messages (signal (list)))
|
||||
(_eff (effect (fn ()
|
||||
(let ((cb (host-callback
|
||||
(fn (e) (swap! messages (fn (old)
|
||||
(append old (host-get (event-detail e) "text"))))))))
|
||||
(host-call (dom-document) "addEventListener" "inbox:message" cb)
|
||||
(fn () (host-call (dom-document) "removeEventListener" "inbox:message" cb)))))))
|
||||
(div
|
||||
(p :class "text-xs font-semibold text-stone-500 mb-2" "Event Bridge Demo")
|
||||
(p :class "text-sm text-stone-600 mb-2"
|
||||
"The buttons below simulate server-rendered content dispatching events into the island.")
|
||||
|
||||
@@ -232,8 +232,7 @@ test.describe('Reactive island interactions', () => {
|
||||
}
|
||||
});
|
||||
|
||||
test.fixme('event-bridge: sender triggers receiver update', async ({ page }) => {
|
||||
// BUG: on-event handler receives CustomEvent but swap!/signal update doesn't propagate to DOM
|
||||
test('event-bridge: sender triggers receiver update', async ({ page }) => {
|
||||
await page.goto(BASE_URL + '/sx/(geography.(reactive.(examples.event-bridge-demo)))', { waitUntil: 'networkidle' });
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
|
||||
@@ -177,7 +177,7 @@ test.describe('Isomorphic SSR', () => {
|
||||
});
|
||||
|
||||
test.fixme('navigation preserves header island state', async ({ page }) => {
|
||||
// BUG: header island inside #main-panel swap boundary — needs structural layout change or store-based state
|
||||
// BUG: def-store state not persisting — *store-registry* likely reset during SPA nav component reload
|
||||
await page.goto(BASE_URL + '/sx/', { waitUntil: 'networkidle' });
|
||||
|
||||
// Wait for header island to hydrate
|
||||
|
||||
Reference in New Issue
Block a user