Removes the 5993-line bootstrapped Python evaluator (sx_ref.py) and all
code that depended on it exclusively. Both bootstrappers (JS + OCaml)
now use a new synchronous OCaml bridge (ocaml_sync.py) to run the
transpiler. JS build produces identical output; OCaml bootstrap produces
byte-identical sx_ref.ml.
Key changes:
- New shared/sx/ocaml_sync.py: sync subprocess bridge to sx_server.exe
- hosts/javascript/bootstrap.py: serialize defines → temp file → OCaml eval
- hosts/ocaml/bootstrap.py: same pattern for OCaml transpiler
- shared/sx/{html,async_eval,resolver,jinja_bridge,handlers,pages,deps,helpers}:
stub or remove sx_ref imports; runtime uses OCaml bridge (SX_USE_OCAML=1)
- sx/sxc/pages: parse defpage/defhandler from AST instead of Python eval
- hosts/ocaml/lib/sx_primitives.ml: append handles non-list 2nd arg per spec
- Deleted: sx_ref.py, async_eval_ref.py, 6 Python test runners, misc ref/ files
Test results: JS 1078/1078, OCaml 1114/1114.
sx_docs SSR has pre-existing rendering issues to investigate separately.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Moved flash-sale, settle-data, search-products/events/posts, and catalog
endpoints from bp/pages/routes.py into sx/sx/handlers/reactive-api.sx.
routes.py now contains only the SSE endpoint (async generators need Python).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
dom.sx and browser.sx are library source (not transpiled into the bundle),
so their functions need explicit PRIMITIVES registration for runtime-eval'd
SX code (islands, data-init scripts). Restore registrations for all dom/
browser functions used at runtime. Revert bootstrap.py transpilation of
dom-lib/browser-lib which overrode native platform implementations that
have essential runtime integration (cekCall wrapping, post-render hooks).
Add Playwright regression test for [object Object] nav link issue.
Replace console-log calls with log-info in init-client.sx.txt.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
steps-to-preview is a pure recursive descent function inside the island's
letrec that builds an SX expression tree from steps[0..target-1].
The preview lake uses it to show partial text (e.g. "the joy of " at step 9).
Still WIP: stepper island doesn't SSR because DOM-only code (effect,
dom-query, dom-create-element) runs in the island body and fails on server.
Need to guard client-only code so SSR can render the pure parts
(code view, counter, preview).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
get-cookie / set-cookie primitives on both server and client:
- JS: reads/writes document.cookie
- OCaml: get-cookie reads from _request_cookies hashtable,
set-cookie is no-op (server sets cookies via HTTP headers)
- Python bridge: _inject_request_cookies_locked() sends
(set-request-cookies {:name "val"}) to kernel before page render
Stepper island (home-stepper.sx):
- Persistence switched from localStorage to cookie (sx-home-stepper)
- freeze-scope/thaw-from-sx mechanism preserved, just different storage
- Server reads cookie → thaw restores step-idx → SSR renders correct step
- Code highlighting: removed imperative code-spans/build-code-dom/
update-code-highlight; replaced with live DOM query that survives morphs
- Removed code-view lake wrapper (now plain reactive DOM)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Code view: SSR now uses same highlighting logic as client update-code-highlight
(bg-amber-100 for current step, font-bold for active, opacity-40 for future).
steps-to-preview: pure function that replays step machine as SX expression
tree — intended for isomorphic preview rendering. Currently working for
simple cases but needs fix for partial step counts (close-loop issue).
Close steps now carry open-attrs/open-spreads for steps-to-preview.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace raw source <pre> with styled spans from build-code-tokens.
Each token gets its colour class, and tokens before the current
step get font-bold text-xs, tokens after get opacity-40.
Home page currently blocked by a separate Map key parse error.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Moved source parsing (sx-parse, split-tag, build-code-tokens) out
of the effect so it runs eagerly during SSR. Only DOM manipulation
(build-code-dom, schedule-idle) stays in the effect.
Lakes now have SSR content:
- code-view: shows source code as preformatted text
- home-preview: shows "the joy of sx" with styled spans
Client hydrates and replaces with interactive version.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The OOB layout delegates to ~shared:layout/oob-sx which needs to
produce sx-swap-oob elements. Without server affinity, the aser
serializes the component call for client-side rendering (matching
production behaviour where the client renders the OOB structure).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The standalone OOB layout was returning nil, so SPA navigation
responses had no OOB swap structure. The header island wasn't
included in responses, so:
- Colour state was lost (island not morphed, signals reset)
- Copyright path wasn't updated (lake not in response)
Now delegates to ~shared:layout/oob-sx which wraps content in
proper OOB sections (filter, aside, menu, main-panel). The header
island with updated :path is included in the content, allowing
the morph to preserve island signals and update lakes.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace AOT adapter compilation with lazy JIT — each named lambda is
compiled to VM bytecode on first call, cached in l_compiled field for
subsequent calls. Compilation failures fall back to CEK gracefully.
VM types (vm_code, vm_upvalue_cell, vm_closure) moved to sx_types.ml
mutual recursion block. Lambda and Component records gain mutable
l_compiled/c_compiled cache fields. jit_compile_lambda in sx_vm.ml
wraps body as (fn (params) body), invokes spec/compiler.sx via CEK,
extracts inner closure from OP_CLOSURE constant.
JIT hooks in both paths:
- vm_call: Lambda calls from compiled VM code
- continue_with_call: Lambda calls from CEK step loop (injected by
bootstrap.py post-processing)
Pre-mark sentinel prevents re-entrancy (compile function itself was
hanging when JIT'd mid-compilation). VM execution errors caught and
fall back to CEK with sentinel marking.
Also: add kbd/samp/var to HTML_TAGS, rebuild sx-browser.js, add page
URL to sx-page-full-py timing log.
Performance: first page 28s (JIT compiles 17 functions), subsequent
pages 0.31s home / 0.71s wittgenstein (was 2.3s). All 1945 tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The geography page function returned nil instead of the index-content
component, and the index layout was missing the standard doc page wrapper.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Aser serialization: aser-call/fragment now return SxExpr instead of String.
serialize/inspect passes SxExpr through unquoted, preventing the double-
escaping (\" → \\\" ) that broke client-side parsing when aser wire format
was output via raw! into <script> tags. Added make-sx-expr + sx-expr-source
primitives to OCaml and JS hosts.
Binary blob protocol: eval, aser, aser-slot, and sx-page-full now send SX
source as length-prefixed blobs instead of escaped strings. Eliminates pipe
desync from concurrent requests and removes all string-escape round-trips
between Python and OCaml.
Bridge safety: re-entrancy guard (_in_io_handler) raises immediately if an
IO handler tries to call the bridge, preventing silent deadlocks.
Fetch error logging: orchestration.sx error callback now logs method + URL
via log-warn. Platform catches (fetchAndRestore, fetchPreload, bindBoostForm)
also log errors instead of silently swallowing them.
Transpiler fixes: makeEnv, scopePeek, scopeEmit, makeSxExpr added as
platform function definitions + transpiler mappings — were referenced in
transpiled code but never defined as JS functions.
Playwright test infrastructure:
- nav() captures JS errors and fails fast with the actual error message
- Checks for [object Object] rendering artifacts
- New tests: delete-row interaction, full page refresh, back button,
direct load with fresh context, code block content verification
- Default base URL changed to localhost:8013 (standalone dev server)
- docker-compose.dev-sx.yml: port 8013 exposed for local testing
- test-sx-build.sh: build + unit tests + Playwright smoke tests
Geography content: index page component written (sx/sx/geography/index.sx)
describing OCaml evaluator, wire formats, rendering pipeline, and topic
links. Wiring blocked by aser-expand-component children passing issue.
Tests: 1080/1080 JS, 952/952 OCaml, 66/66 Playwright
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- send_ok_raw: when SX wire format contains newlines (string literals),
fall back to (ok "...escaped...") instead of (ok-raw ...) to keep
the pipe single-line. Prevents multi-line responses from desyncing
subsequent requests.
- expand-components? flag set in kernel env (not just VM adapter globals)
so aser-list's env-has? check finds it during component expansion.
- SX_STANDALONE: restore no_oauth but generate CSRF via session cookie
so mutation handlers (DELETE etc.) still work without account service.
- Shell statics injection: only inject small values (hashes, URLs) as
kernel vars. Large blobs (CSS, component_defs) use placeholder tokens.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Compiler fixes:
- Upvalue re-lookup returns own position (uv-index), not parent slot
- Spec: cek-call uses (make-env) not (dict) — OCaml Dict≠Env
- Bootstrap post-processes transpiler Dict→Env for cek_call
VM runtime fixes:
- compile_adapter evaluates constant defines (SPECIAL_FORM_NAMES etc.)
via execute_module instead of wrapping as NativeFn closures
- Native primitives: map-indexed, some, every?
- Nil-safe HO forms: map/filter/for-each/some/every? accept nil as empty
- expand-components? set in kernel env (not just VM globals)
- unwrap_env diagnostic: reports actual type received
sx-page-full command:
- Single OCaml call: aser-slot body + render-to-html shell
- Eliminates two pipe round-trips (was: aser-slot→Python→shell render)
- Shell statics (component_defs, CSS, pages_sx) cached in Python,
injected into kernel once, referenced by symbol in per-request command
- Large blobs use placeholder tokens — Python splices post-render,
pipe transfers ~51KB instead of 2MB
Performance (warm):
- Server total: 0.55s (was ~2s)
- aser-slot VM: 0.3s, shell render: 0.01s, pipe: 0.06s
- kwargs computation: 0.000s (cached)
SX_STANDALONE mode for sx_docs dev (skips fragment fetches).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Eliminated double-aser for HTMX requests: build OOB wrapper AST
(~shared:layout/oob-sx :content wrapped_ast) and aser_slot in ONE
pass — same pattern as the full-page path. Halves aser_slot calls.
Added kernel-side timing to stderr:
[aser-slot] eval=3.6s io_flush=0.0s batched=3 result=22235 chars
Results show batch IO works (io_flush=0.0s for 3 highlight calls)
and the bottleneck is pure CEK evaluation time, not IO.
Performance after single-pass fix:
Home: 0.7s eval (was 2.2s total)
Reactive: 3.6s eval (was 6.8s total)
Language: 1.1s eval (was 18.9s total — double-aser eliminated)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Root cause: OcamlBridge._send() used write() without drain().
asyncio.StreamWriter buffers writes — without drain(), multiple
commands accumulate and flush as a batch. The kernel processes
them sequentially, sending responses, but Python only reads one
response per command → pipe desync → "unexpected response" errors.
Fix: _send() is now async, calls drain() after every write.
All 14 callers updated to await.
Playwright tests rewritten:
- test_home_has_header: verifies #logo-opacity visible (was only
checking for "sx" text — never caught missing header)
- test_home_has_nav_children: Geography link must be visible
- test_home_has_main_panel: #main-panel must have child elements
- TestDirectPageLoad: fresh browser.new_context() per test to
avoid stale component hash in localStorage
- _setup_error_capture + _check_no_fatal_errors helpers
_render_to_sx uses aser_slot (not aser) — layout wrappers contain
re-parsed content that needs full expansion capability.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Three fixes completing the aser_slot migration:
1. Single-pass full-page rendering: eval_sx_url builds layout+content
AST and aser_slots it in ONE call — avoids double-aser where
re-parsed content hits "Undefined symbol: title/deref" errors.
2. Pipe desync fix: _inject_helpers_locked runs INSIDE the aser_slot
lock acquisition (not as a separate lock). Prevents interleaved
commands from other coroutines between injection and aser-slot.
3. _render_to_sx uses aser_slot (not aser): layout wrappers like
oob_page_sx contain re-parsed content from earlier aser_slot
calls. Regular aser fails on symbols that were bound during
the earlier expansion. aser_slot handles them correctly.
HTMX path: aser_slot the content, then oob_page_sx wraps it.
Full page path: build (~shared:layout/app-body :content wrapped_ast),
aser_slot in one pass, pass directly to sx_page.
New Playwright tests: test_navigate_geography_to_reactive,
test_direct_load_reactive_page.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Three bugs in aser-expand-component (adapter-sx.sx):
- Keyword values were eval'd (eval-expr can't handle <>, HTML tags);
now asered, matching the aser's rendering capabilities
- Missing default nil binding for unset &key params (caused
"Undefined symbol" errors for optional params like header-rows)
- aserCall string-quoted keyword values that were already serialized
SX — now inlines values starting with "(" directly
Server-affinity annotations for layout/nav shells:
- ~shared:layout/app-body, ~shared:layout/oob-sx — page structure
- ~layouts/nav-sibling-row, ~layouts/nav-children — server-side data
- ~layouts/doc already had :affinity :server
- ~cssx/flush marked :affinity :client (browser-only state)
Navigation fix: restore oob_page_sx wrapper for HTMX responses
so #main-panel section exists for sx-select/sx-swap targeting.
OCaml bridge: lazy page helper injection into kernel via IO proxy
(define name (fn (...) (helper "name" ...))) — enables aser_slot
to evaluate highlight/component-source etc. via coroutine bridge.
Playwright tests: added pageerror listener to test_no_console_errors,
new test_navigate_from_home_to_geography for HTMX nav regression.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
adapter-sx.sx: aser-expand-component expands :affinity :server components
inline during SX wire format serialization. Binds keyword args via
eval-expr, children via aser (handles HTML tags), then asers the body.
ocaml_bridge.py: 10MB readline buffer for large spec responses.
nav-data.sx: evaluator.sx filename fix.
Page rendering stays on Python _eval_slot for now — full OCaml rendering
needs the page shell IO (headers, CSRF, CSS) migrated to OCaml IO bridge.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Client re-evaluates defpage content which calls find-spec — unavailable
on client because all-spec-items (nav-data.sx) isn't sent to browser.
Server rendering works (verified by server-side tests).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- ocaml_bridge: 10MB readline buffer for large spec explorer responses
- nav-data: evaluator.sx filename (was eval.sx, actual spec file is evaluator.sx)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
spec-introspect.sx: pure SX functions that read, parse, and analyze
spec files. No Python. The spec IS data — a macro transforms it into
explorer UI components.
- spec-explore: reads spec file via IO, parses with sx-parse, extracts
sections/defines/effects/params, produces explorer data dict
- spec-form-name/kind/effects/params/source: individual extractors
- spec-group-sections: groups defines into sections
- spec-compute-stats: aggregate effect/define counts
OCaml kernel fixes:
- nth handles strings (character indexing for parser)
- ident-start?, ident-char?, char-numeric?, parse-number: platform
primitives needed by spec/parser.sx when loaded at runtime
- _find_spec_file: searches spec/, web/, shared/sx/ref/ for spec files
83/84 Playwright tests pass. The 1 failure is client-side re-rendering
of the spec explorer (the client evaluates defpage content which calls
find-spec — unavailable on the client).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Major architectural change: page function dispatch and handler execution
now go through the OCaml kernel instead of the Python bootstrapped evaluator.
OCaml integration:
- Page dispatch: bridge.eval() evaluates SX URL expressions (geography, marshes, etc.)
- Handler aser: bridge.aser() serializes handler responses as SX wire format
- _ensure_components loads all .sx files into OCaml kernel (spec, web adapter, handlers)
- defhandler/defpage registered as no-op special forms so handler files load
- helper IO primitive dispatches to Python page helpers + IO handlers
- ok-raw response format for SX wire format (no double-escaping)
- Natural list serialization in eval (no (list ...) wrapper)
- Clean pipe: _read_until_ok always sends io-response on error
SX adapter (aser):
- scope-emit!/scope-peek aliases to avoid CEK special form conflict
- aser-fragment/aser-call: strings starting with "(" pass through unserialized
- Registered cond-scheme?, is-else-clause?, primitive?, get-primitive in kernel
- random-int, parse-int as kernel primitives; json-encode, into via IO bridge
Handler migration:
- All IO calls converted to (helper "name" args...) pattern
- request-arg, request-form, state-get, state-set!, now, component-source etc.
- Fixed bare (effect ...) in island bodies leaking disposer functions as text
- Fixed lower-case → lower, ~search-results → ~examples/search-results
Reactive islands:
- sx-hydrate-islands called after client-side navigation swap
- force-dispose-islands-in for outerHTML swaps (clears hydration markers)
- clear-processed! platform primitive for re-hydration
Content restructuring:
- Design, event bridge, named stores, phase 2 consolidated into reactive overview
- Marshes split into overview + 5 example sub-pages
- Nav links use sx-get/sx-target for client-side navigation
Playwright test suite (sx/tests/test_demos.py):
- 83 tests covering hypermedia demos, reactive islands, marshes, spec explorer
- Server-side rendering, handler interactions, island hydration, navigation
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Each example is now a child nav item linking to its anchor on the
examples page. Event Bridge and Named Stores are sections within
Examples (they have live demos there), not separate pages.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Remove "Overview" nav link (index.sx IS the summary)
- Rename "Demo" → "Examples" in nav and page title
- Remove "Plan" and "Phase 2" from nav (all items done — status table remains in overview)
- Add "Marshes" to nav (was missing, content already existed)
- Add live event bridge demo island (data-sx-emit → signal via on-event)
- Add event bridge section (#14) to examples page
- Keep "demo" route as alias for backward compat
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Evaluator: data-first higher-order forms — ho-swap-args auto-detects
(map coll fn) vs (map fn coll), both work. Threading + HO: (-> data
(map fn)) dispatches through CEK HO machinery via quoted-value splice.
17 new tests in test-cek-advanced.sx.
Fix plan pages: add mother-language, isolated-evaluator, rust-wasm-host
to page-functions.sx plan() — were in defpage but missing from URL router.
Aser error handling: pages.py now catches EvalError separately, renders
visible error banner instead of silently sending empty content. All
except blocks include traceback in logs.
Scope primitives: register collect!/collected/clear-collected!/emitted/
emit!/context in shared/sx/primitives.py so hand-written _aser can
resolve them (fixes ~cssx/flush expansion failure).
New test file: shared/sx/tests/test_aser_errors.py — 19 pytest tests
for error propagation through all aser control flow forms.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
test-continuations-advanced.sx (41 tests):
multi-shot continuations, composition, provide/context basics,
provide across shift, scope/emit basics, scope across shift
test-render-advanced.sx (27 tests):
nested components, dynamic content, list patterns,
component patterns, special elements
Bugs found and documented:
- case in render context returns DOM object (CEK dispatches case
before HTML adapter sees it — use cond instead for render)
- context not visible in shift body (correct: shift body runs
outside the reset/provide boundary)
- Multiple shifts consume reset (correct: each shift needs its own
reset)
Python runner: skip test-continuations-advanced.sx without --full.
JS 815/815 standard, 938/938 full, Python 706/706.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
provide/context and scope/emit!/emitted now use CEK continuation
frames instead of an imperative global stack. Scope state is part
of the continuation — captured by shift, restored by k invocation.
New frame types:
- ProvideFrame: holds name + value, consumed when body completes
- ScopeAccFrame: holds name + mutable emitted list
New CEK special forms:
- context: walks kont for nearest ProvideFrame, returns value
- emit!: walks kont for nearest ScopeAccFrame, appends to emitted
- emitted: walks kont for nearest ScopeAccFrame, returns list
Kont walkers: kont-find-provide, kont-find-scope-acc
This fixes the last 2 test failures:
- provide survives resume: scope captured by shift, restored by k
- scope and emit across shift: accumulator preserved in continuation
JS Full: 870/870 (100%)
JS Standard: 747/747 (100%)
Python: 679/679 (100%)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Continuations are now multi-shot — k can be invoked multiple times.
Each invocation runs the captured frames via nested cek-run and
returns the result to the caller's continuation.
Fix: continue-with-call runs ONLY the captured delimited frames
(not rest-kont), so the continuation terminates and returns rather
than escaping to the outer program.
Fixed 4 continuation tests:
- shift with multiple invokes: (list (k 10) (k 20)) → (11 21)
- k returned from reset: continuation callable after escaping
- invoke k multiple times: same k reusable
- k in data structure: store in list, retrieve, invoke
Remaining 2 failures: scope/provide across shift boundaries.
These need scope state tracked in frames (not imperative push/pop).
JS 747/747, Full 868/870, Python 679/679.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Higher-order forms (map, filter, reduce, some, every?, for-each,
map-indexed) now evaluate their arguments via CEK frames instead
of nested trampoline(eval-expr(...)) calls.
Added HoSetupFrame — staged evaluation of HO form arguments.
When all args are evaluated, ho-setup-dispatch sets up the
iteration frame. This keeps a single linear CEK continuation
chain instead of spawning nested CEK instances.
14 nested eval-expr calls eliminated (39 → 25 remaining).
The remaining 25 are in delegate functions (sf-letrec, sf-scope,
parse-keyword-args, qq-expand, etc.) called infrequently.
All tests unchanged: JS 747/747, Full 864/870, Python 679/679.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Dedicated page documenting and demonstrating content-addressed
computation. How it works, why it matters, the path to IPFS.
Live demo: counter + name widget with CID generation, history,
and restore-from-CID input.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Hash frozen SX to a content identifier (djb2 → hex). Same state
always produces the same CID. Store by CID, retrieve by CID.
- content-hash: djb2 hash of SX text → hex string
- content-put/get: in-memory content store
- freeze-to-cid: freeze scope → store → return CID
- thaw-from-cid: look up CID → thaw signals
- char-code-at / to-hex primitives for both platforms
- Live demo: counter + name widget, content-address button,
CID display, restore from CID input, CID history
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- freeze-scope "home-stepper" captures step-idx signal
- Each step/back saves to localStorage via freeze-to-sx
- On mount, restores from localStorage via thaw-from-sx
- Invalid state resets to default (step 9)
- Clear preview lake before replay to prevent duplicates
- Register local-storage-get/set/remove as primitives
- Arrows 3x bigger
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace raw CEK state serialization with named freeze scopes.
A freeze scope collects signals registered within it. On freeze,
signal values are serialized to SX. On thaw, values are restored.
- freeze-scope: scoped effect delimiter for signal collection
- freeze-signal: register a signal with a name in the current scope
- cek-freeze-scope / cek-thaw-scope: freeze/thaw by scope name
- freeze-to-sx / thaw-from-sx: full SX text round-trip
- cek-freeze-all / cek-thaw-all: batch operations
Also: register boolean?, symbol?, keyword? predicates in both
Python and JS platforms with proper var aliases.
Demo: counter + name input with Freeze/Thaw buttons.
Frozen SX: {:name "demo" :signals {:count 5 :name "world"}}
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Documents and demonstrates serializable CEK state. Type an expression,
step to any point, click Freeze to see the frozen SX. Click Thaw to
resume from the frozen state and get the result.
- New page at /sx/(geography.(cek.freeze))
- Nav entry under CEK Machine
- Interactive island demo with step/run/freeze/thaw buttons
- Documentation: the idea, freeze format, thaw/resume, what it enables
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When a tag's open step is evaluated, both its opening and closing
brackets go big+bold together. Previously close ) had the close
step index so it stayed faint until much later.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Code view uses a lake with imperative DOM spans. Each token has its
base syntax colour class stored. On each step, update-code-highlight
iterates all spans and sets class based on step-idx: evaluated tokens
go bold, current step gets violet bg, future stays normal.
No reactive re-rendering of the code view — direct DOM class updates.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Each token span independently reacts to step-idx via deref-as-shift
- Colours match highlight.py: sky for HTML tags, rose for components,
emerald for strings, violet for keywords, amber for numbers
- Current step bold+violet bg, completed steps dimmed
- No closing paren on separate line
- Fix bare nil → NIL in eventDetail and domGetData
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace header source view with interactive CEK render stepper.
Auto-parses on mount, step forward/back through DOM construction
with CSSX styling. Uses lake for preview persistence.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Cache the style element reference in _cssx-style-el so flush-cssx-to-dom
never creates more than one. Previous code called dom-query on every
flush, which could miss the element during rapid successive calls,
creating duplicates.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>