Higher-order forms now step element-by-element through the CEK machine
using dedicated frames instead of delegating to tree-walk ho-map etc.
Each callback invocation goes through continue-with-call, so deref-as-shift
works inside map/filter/reduce callbacks in reactive island contexts.
- cek.sx: rewrite step-ho-* to use CEK frames, add frame handlers in
step-continue for map, filter, reduce, for-each, some, every
- frames.sx: add SomeFrame, EveryFrame, MapIndexedFrame
- test-cek-reactive.sx: add 10 tests for CEK-native HO forms
89 tests pass (20 signal + 43 CEK + 26 CEK reactive).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Completes the invoke→cek-call migration across all spec .sx files:
- adapter-sx.sx: map/filter/for-each in aser wire format
- adapter-dom.sx: island render update-fn
- engine.sx: fetch transform callback
- test-cek-reactive.sx: disposal test
Only async-invoke (adapter-async.sx) remains — separate async pattern.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
All signal operations (computed, effect, batch, etc.) now dispatch
function calls through cek-call, which routes SX lambdas via cek-run
and native callables via apply. This replaces the invoke shim.
Key changes:
- cek.sx: add cek-call (defined before reactive-shift-deref), replace
invoke in subscriber disposal and ReactiveResetFrame handler
- signals.sx: replace all 11 invoke calls with cek-call
- js.sx: fix octal escape in js-quote-string (char-from-code 0)
- platform_js.py: fix JS append to match Python (list concat semantics),
add Continuation type guard in PLATFORM_CEK_JS, add scheduleIdle
safety check, module ordering (cek before signals)
- platform_py.py: fix ident-char regex (remove [ ] from valid chars),
module ordering (cek before signals)
- run_js_sx.py: emit PLATFORM_CEK_JS before transpiled spec files
- page-functions.sx: add cek and provide page functions for SX URLs
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Both plans had nav entries and component files but were missing from
the page-functions.sx case statement, causing 404s on their URLs.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Two bugs in _emit_define_as_def: (1) nested def's _current_cell_vars
was replaced instead of unioned with parent — inner functions lost
access to parent's cell vars (skip_ws/skip_comment used bare pos
instead of _cells['pos']). (2) statement-context set! didn't check
_current_cell_vars, always emitting bare assignment instead of
_cells[...]. (3) nested functions that access parent _cells no longer
shadow it with their own empty _cells = {}.
Fixes UnboundLocalError in bootstrapped parser (sx_parse skip_ws)
that crashed production URL routing.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace hand-written serialize/sx_serialize/sx_parse in Python with
spec-derived versions from parser.sx. Add parser as a Python adapter
alongside html/sx/async — all 48 parser spec tests pass.
Add reactive runtime plan to sx-docs: 7 feature layers (ref, foreign
FFI, state machines, commands with undo/redo, render loops, keyed
lists, client-first app shell) — zero new platform primitives.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
run_type_tests.py, run_signal_tests.py, run_continuation_tests.py all
needed the same sx_ref.eval_expr/trampoline override to tree-walk that
was applied to the CEK test runners. Without this, transpiled HO forms
(ho_map, etc.) re-entered CEK mid-evaluation causing "Unknown frame
type: map" errors. All 186 tests now pass across 5 suites.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
test-cek-reactive.sx: 9 tests across 4 suites — deref pass-through,
signal without reactive-reset, reactive-reset shift with continuation
capture, scope disposal cleanup. run_cek_reactive_tests.py: new runner
loading signals+frames+cek. Both test runners override sx_ref.eval_expr
back to tree-walk so interpreted .sx uses tree-walk internally.
Plan page added to sx-docs.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
frames.sx: ReactiveResetFrame + DerefFrame constructors,
kont-capture-to-reactive-reset, has-reactive-reset-frame?.
cek.sx: deref as CEK special form, step-sf-deref pushes DerefFrame,
reactive-shift-deref captures continuation as signal subscriber,
ReactiveResetFrame in step-continue calls update-fn on re-render.
adapter-dom.sx: cek-reactive-text/cek-reactive-attr using cek-run
with ReactiveResetFrame for implicit DOM bindings.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
SPEC_MODULES + SPEC_MODULE_ORDER for frames/cek in platform_js.py,
PLATFORM_CEK_JS + CEK_FIXUPS_JS constants, auto-inclusion in
run_js_sx.py, 70+ RENAMES in js.sx. Python: CEK always-include in
bootstrap_py.py, eval_expr/trampoline overridden to cek_run in
platform_py.py with _tree_walk_* preserved for test runners.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace _Signal class (Python) and SxSignal constructor (JS) with plain
dicts keyed by "__signal". Nine platform accessor functions become ~20
lines of pure SX in signals.sx. type-of returns "dict" for signals;
signal? is now a structural predicate (dict? + has-key?).
Net: -168 lines platform, +120 lines SX. Zero platform primitives for
reactivity — signals compile to any host via the bootstrappers.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Reactive tracking (deref/computed/effect dep discovery) and island lifecycle
now use the general scoped effects system instead of parallel infrastructure.
Two scope names: "sx-reactive" for tracking context, "sx-island-scope" for
island disposable collection. Eliminates ~98 net lines: _TrackingContext class,
7 tracking context platform functions (Python + JS), *island-scope* global,
and corresponding RENAME_MAP entries. All 20 signal tests pass (17 original +
3 new scope integration tests), plus CEK/continuation/type tests clean.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add `scope` special form to eval.sx: (scope name body...) or
(scope name :value v body...) — general dynamic scope primitive
- `provide` becomes sugar: (provide name value body...) calls scope
- Rename provide-push!/provide-pop! to scope-push!/scope-pop! throughout
all adapters (async, dom, html, sx) and platform implementations
- Update boundary.sx: Tier 5 now "Scoped effects" with scope-push!/
scope-pop! as primary, provide-push!/provide-pop! as aliases
- Add scope form handling to async adapter and aser wire format
- Update sx-browser.js, sx_ref.py (bootstrapped output)
- Add scopes.sx docs page, update provide/spreads/demo docs
- Update nav-data, page-functions, docs page definitions
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Rename to .sx.future — the file uses #z3 reader macros that aren't
implemented yet, causing a ParseError that blocks ALL component loading
and breaks the provide docs page.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Lead with provide/emit! from the first sentence. make-spread/spread?/spread-attrs
are now presented as user-facing API on top of the provide/emit! substrate,
not as independent primitives. Restructured sections, removed redundant
"deeper primitive" content that duplicated the new section I.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- New geography article (provide.sx): four primitives, demos, nested scoping,
adapter comparison, spec explorer links
- Updated spreads article section VI: provide/emit! is now implemented, not planned
- Fixed foundations.sx: ~docs/code-block → ~docs/code (undefined component
was causing the page to silently fail to render)
- Added nav entry and defpage route for provide/emit! article
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
CI was doing git reset --hard on /root/rose-ash (the dev directory),
flipping the checked-out branch and causing empty diffs when merging.
Now builds in /root/rose-ash-ci and uses push event SHAs for diffing.
Also adds --resolve-image always to stack deploys.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Spreads now emit their attrs into the nearest element's provide scope
instead of requiring per-child spread? checks at every intermediate
layer. emit! is tolerant (no-op when no provider), so spreads in
non-element contexts silently vanish.
- adapter-html: element/lake/marsh wrap children in provide, collect
emitted; removed 14 spread filters from fragment, forms, components
- adapter-sx: aser wraps result to catch spread values from fn calls;
aser-call uses provide with attr-parts/child-parts ordering
- adapter-async: same pattern for both render and aser paths
- adapter-dom: added emit! in spread dispatch + provide in element
rendering; kept spread? checks for reactive/island and DOM safety
- platform: emit! returns NIL when no provider instead of erroring
- 3 new aser tests: stored spread, nested element, silent drop
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Spread values from make-spread were crashing the wire format serializer:
- serialize() had no "spread" case, fell through to (str val) producing
Python repr "<shared.sx.ref.sx_ref._Spread...>" which was treated as
an undefined symbol
- aser-call/async-aser-call didn't handle spread children — now merges
spread attrs as keyword args into the parent element
- aser-fragment/async-aser-fragment didn't filter spreads — now filters
them (fragments have no parent element to merge into)
- serialize() now handles spread type: (make-spread {:key "val"})
Added 3 aser-spreads tests. All 562 tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The plans were routed in page-functions.sx (GraphSX URL eval) but
missing from the defpage case in docs.sx (server-side slug route).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add \uXXXX unicode escape support to parser.py and parser.sx spec
- Add char-from-code primitive (Python chr(), JS String.fromCharCode())
- Fix variadic infix operators in both bootstrappers (js.sx, py.sx) —
(+ a b c d) was silently dropping terms, now left-folds correctly
- Rebootstrap sx_ref.py and sx-browser.js with all fixes
- Fix 3 pre-existing map-dict test failures in shared/sx/tests/run.py
- Add live demos alongside examples in spreads essay (side-by-side layout)
- Add scoped-effects plan: algebraic effects as unified foundation for
spread/collect/island/lake/signal/context
- Add foundations plan: CEK machine, the computational floor, three-axis
model (depth/topology/linearity), Curry-Howard correspondence
- Route both plans in page-functions.sx and nav-data.sx
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
SX parser doesn't process \u escapes — they render as literal text.
Use actual UTF-8 characters (→, —, £, ⬡) directly in source.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
(code "...") is an HTML tag — works in render context but not inside
(list ...) which fully evaluates args. Use plain strings in table rows.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Without this, /sx/(geography.(spreads)) 404s because spreads isn't
defined as a page function to return the content component.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Documents the three orthogonal primitives (spread, collect!, reactive-spread),
their operation across server/client/morph boundaries, CSSX as use case,
semantic style variables, and the planned provide/context/emit! unification.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When a spread value (e.g. from ~cssx/tw) appears inside an island with
signal-dependent tokens, reactive-spread tracks deps and updates the
element's class/attrs when signals change. Old classes are surgically
removed, new ones appended, and freshly collected CSS rules are flushed
to the live stylesheet. Multiple reactive spreads on one element are safe.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
adapter-dom.sx: if/when/cond reactive paths now check whether
initial-result is a spread. If so, return it directly — spreads
aren't DOM nodes and can't be appended to fragments. This lets
any spread-returning component (like ~cssx/tw) work inside islands
without the spread being silently dropped.
cssx.sx: revert make-spread workaround — the root cause is now
fixed in the adapter. ~cssx/tw can use a natural top-level if.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Component params are bound from kwargs only in render-dom-component.
Positional args go to children, so (~ cssx/tw "...") binds tokens=nil.
The :tokens keyword is required: (~cssx/tw :tokens "...").
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
layouts.sx: change all (~ cssx/tw :tokens "...") to (~cssx/tw "...")
matching the documented positional calling convention.
Move (~cssx/flush) after children so page content rules are also
collected before the server-side <style data-cssx> flush.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>