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>
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>
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>
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>
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>
Spread values returned by components like ~cssx/tw are not DOM nodes
and cannot be passed to appendChild. Filter them in fragment, let,
begin/do, component children, and data list rendering paths — matching
the HTML adapter's existing spread filtering.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Four new primitives for scoped downward value passing and upward
accumulation through the render tree. Specced in .sx, bootstrapped
to Python and JS across all adapters (eval, html, sx, dom, async).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
New SX primitives for child-to-parent communication in the render tree:
- spread type: make-spread, spread?, spread-attrs — child injects attrs
onto parent element (class joins with space, style with semicolon)
- collect!/collected/clear-collected! — render-time accumulation with
dedup into named buckets
~cssx/tw is now a proper defcomp returning a spread value instead of a
macro wrapping children. ~cssx/flush reads collected "cssx" rules and
emits a single <style data-cssx> tag.
All four render adapters (html, async, dom, aser) handle spread values.
Both bootstraps (Python + JS) regenerated. Also fixes length→len in
cssx.sx (length was never a registered primitive).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Extend the type annotation system from defcomp-only to fn/lambda params:
- Infrastructure: sf-lambda, py/js-collect-params-loop, and bootstrap_py.py
now recognize (name :as type) in param lists, extracting just the name
- bootstrap_py.py: add _extract_param_name() helper, fix _emit_for_each_stmt
- 521 type annotations across 22 .sx spec files (eval, types, adapters,
transpilers, engine, orchestration, deps, signals, router, prove, etc.)
- Zero behavioral change: annotations are metadata for static analysis only
- All bootstrappers (Python, JS, G1) pass, 81/81 spec tests pass
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Browser always evaluates in render context — _renderMode must be true
when DOM adapter is loaded, and render-to-dom must call set-render-active!.
Fixes 'Undefined symbol: <>' error in browser.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Rename /reactive-islands/ → /reactive/, /reference/ → /hypermedia/reference/,
/examples/ → /hypermedia/examples/ across all .sx and .py files
- Add 404 error page (not-found.sx) working on both server refresh and
client-side SX navigation via orchestration.sx error response handling
- Add trailing slash redirect (GET only, excludes /api/, /static/, /internal/)
- Remove blue sky-500 header bar from SX docs layout (conditional on header-rows)
- Fix 405 on API endpoints from trailing slash redirect hitting POST/PUT/DELETE
- Fix client-side 404: orchestration.sx now swaps error response content
instead of silently dropping it
- Add new plan files and home page component
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Client-rendered islands were re-hydrated by boot.sx because
renderDomIsland didn't mark them as processed. Hydration read
empty data-sx-state, overwriting kwargs (e.g. path) with NIL.
Fix: mark-processed! in adapter-dom.sx so boot skips them.
New plan: marshes — where reactivity and hypermedia interpenetrate.
Three patterns: server writes to signals, reactive marsh zones with
transforms, and signal-bound hypermedia interpretation.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Lake tag (lake :id "name" children...) creates server-morphable slots
within islands. During morph, the engine enters hydrated islands and
updates data-sx-lake elements by ID while preserving surrounding
reactive DOM (signals, effects, event listeners).
Specced in .sx, bootstrapped to JS and Python:
- adapter-dom.sx: render-dom-lake, reactive-attr marks data-sx-reactive-attrs
- adapter-html.sx: render-html-lake SSR output
- adapter-sx.sx: lake serialized in wire format
- engine.sx: morph-island-children (lake-by-ID matching),
sync-attrs skips reactive attributes
- ~sx-header uses lakes for logo and copyright
- Hegelian essay updated with lake code example
Also includes: lambda nil-padding for missing args, page env ordering fix
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Islands survive hypermedia swaps: morph-node skips hydrated
data-sx-island elements when the same island exists in new content.
dispose-islands-in skips hydrated islands to prevent premature cleanup.
- @client directive: .sx files marked ;; @client send define forms to browser
- CSSX client-side: cssxgroup renamed (no hyphen) to avoid isRenderExpr
matching it as a custom element — was producing [object HTMLElement]
- Island wrappers: div→span to avoid block-in-inline HTML parse breakage
- ~sx-header is now a defisland with inline reactive colour cycling
- bootstrap_js.py defaults output to shared/static/scripts/sx-browser.js
- Deleted stale sx-ref.js (sx-browser.js is the canonical browser build)
- Hegelian Synthesis essay: dialectic of hypertext and reactivity
- component-source helper handles Island types for docs pretty-printing
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Lambda multi-body fix: sf-lambda used (nth args 1), dropping all but the first
body expression. Fixed to collect all body expressions and wrap in (begin ...).
This was foundational — every multi-expression lambda in every island silently
dropped expressions after the first.
Reactive islands: fix dom-parent marker timing (first effect run before marker
is in DOM), fix :key eager evaluation, fix error boundary scope isolation,
fix resource/suspense reactive cond tracking, fix inc not available as JS var.
New essay: "React is Hypermedia" — argues that reactive islands are hypermedia
controls whose behavior is specified in SX, not a departure from hypermedia.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- suspense render-dom form: shows fallback while resource loads, swaps
to body content when resource signal resolves
- resource async signal: wraps promise into signal with loading/data/error
dict, auto-transitions on resolve/reject via promise-then
- transition: defers signal writes to requestIdleCallback, sets pending
signal for UI feedback during expensive operations
- Added schedule-idle, promise-then platform functions
- All Phase 2 features now marked Done in status tables
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- :class-map dict toggles classes reactively via classList.add/remove
- :style-map dict sets inline styles reactively via el.style[prop]
- ref/ref-get/ref-set! mutable boxes (non-reactive, like useRef)
- :ref attribute sets ref.current to DOM element after rendering
- portal render-dom form renders children into remote target element
- Portal content auto-removed on island disposal via register-in-scope
- Added #portal-root div to page shell template
- Added stop-propagation and dom-focus platform functions
- Demo islands for all three features on the demo page
- Updated status tables: all P0/P1 features marked Done
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Items with :key attributes are matched by key across renders — existing
DOM nodes are reused, stale nodes removed, new nodes inserted in order.
Falls back to clear-and-rerender without keys.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Effect and computed auto-register disposers with island scope via
register-in-scope; dispose-islands-in called before every swap point
(orchestration.sx) to clean up intervals/subscriptions on navigation.
- Map + deref inside islands auto-upgrades to reactive-list for signal-
bound list rendering. Demo island with add/remove items.
- New :bind attribute for two-way signal-input binding (text, checkbox,
radio, textarea, select). bind-input in adapter-dom.sx handles both
signal→element (effect) and element→signal (event listener).
- Phase 2 plan page at /reactive-islands/phase2 covering input binding,
keyed reconciliation, reactive class/style, refs, portals, error
boundaries, suspense, and transitions.
- Updated status tables in overview and plan pages.
- Fixed stopwatch reset (fn body needs do wrapper for multiple exprs).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Three bugs prevented islands from working during SX wire navigation:
1. components_for_request() only bundled Component and Macro defs, not
Island defs — client never received defisland definitions during
navigation (components_for_page for initial HTML shell was correct).
2. hydrate-island used morph-children which can't transfer addEventListener
event handlers from freshly rendered DOM to existing nodes. Changed to
clear+append so reactive DOM with live signal subscriptions is inserted
directly.
3. asyncRenderToDom (client-side async page eval) checked _component but
not _island on ~-prefixed names — islands fell through to generic eval
which failed. Now delegates to renderDomIsland.
4. setInterval_/setTimeout_ passed SX Lambda objects directly to native
timers. JS coerced them to "[object Object]" and tried to eval as code,
causing "missing ] after element list". Added _wrapSxFn to convert SX
lambdas to JS functions before passing to timers.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Rebuild sx-browser.js with signals spec module (was missing entirely)
- Register signal functions (signal, deref, effect, computed, etc.) as
PRIMITIVES so runtime-evaluated SX code in island bodies can call them
- Add reactive deref detection in adapter-dom.sx: (deref sig) in island
scope creates reactive-text node instead of static text
- Add Island SSR support in html.py (_render_island with data-sx-island)
- Add Island bundling in jinja_bridge.py (defisland defs sent to client)
- Update deps.py to track Island dependencies alongside Component
- Add defisland to _ASER_FORMS in async_eval.py
- Add clear-interval platform primitive (was missing)
- Create four live demo islands: counter, temperature, imperative, stopwatch
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- adapter-dom.sx: detect :on-click/:on-submit etc. in render-dom-element
— if attr starts with "on-" and value is callable, wire via dom-listen
- orchestration.sx: add process-emit-elements for data-sx-emit attrs
— auto-dispatch custom events on click with optional JSON detail
- bootstrap_js.py: add processEmitElements RENAME
- Regenerate sx-ref.js with all changes
- Update reactive-islands status table
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
New spec file signals.sx defines the signal runtime: signal, computed,
effect, deref, reset!, swap!, batch, dispose, and island scope tracking.
eval.sx: defisland special form + island? type predicate in eval-call.
boundary.sx: signal primitive declarations (Tier 3).
render.sx: defisland in definition-form?.
adapter-dom.sx: render-dom-island with reactive context, reactive-text,
reactive-attr, reactive-fragment, reactive-list helpers.
adapter-html.sx: render-html-island for SSR with data-sx-island/state.
adapter-sx.sx: island? handling in wire format serialization.
special-forms.sx: defisland declaration with docs and example.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The entire parallel CSS system (StyleValue type, style dictionary,
keyword atom resolver, content-addressed class generation, runtime
CSS injection, localStorage caching) was built but never adopted —
the codebase already uses :class strings with defcomp components
for all styling. Remove ~3,000 lines of unused infrastructure.
Deleted:
- cssx.sx spec module (317 lines)
- style_dict.py (782 lines) and style_resolver.py (254 lines)
- StyleValue type, defkeyframes special form, build-keyframes platform fn
- Style dict JSON delivery (<script type="text/sx-styles">), cookies, localStorage
- css/merge-styles primitives, inject-style-value, fnv1a-hash platform interface
Simplified:
- defstyle now binds any value (string, function) — no StyleValue type needed
- render-attrs no longer special-cases :style StyleValue → class conversion
- Boot sequence skips style dict init step
Preserved:
- tw.css parsing + CSS class delivery (SX-Css headers, <style id="sx-css">)
- All component infrastructure (defcomp, caching, bundling, deps)
- defstyle as a binding form for reusable class strings
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
render-dom-unknown-component now calls (error ...) instead of
creating a styled div. This lets tryEvalContent catch the error
and fall back to server fetch, instead of rendering "Unknown
component: ~name" into the page.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Split monolithic render.sx into core (tag registries, shared utils) plus
four adapter .sx files: adapter-html (server HTML strings), adapter-sx
(SX wire format), adapter-dom (browser DOM nodes), and engine (SxEngine
triggers, morphing, swaps). All adapters written in s-expressions with
platform interface declarations for JS bridge functions.
Bootstrap compiler now accepts --adapters flag to emit targeted builds:
-a html → server-only (1108 lines)
-a dom,engine → browser-only (1634 lines)
-a html,sx → server with SX wire (1169 lines)
(default) → all adapters (1800 lines)
Fixes: keyword arg i-counter desync in reduce across all adapters,
render-aware special forms (let/if/when/cond/map) in HTML adapter,
component children double-escaping, ~prefixed macro dispatch.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>