Phase 6 — deftype: named type aliases, unions, records, and parameterized
types. Type definitions stored as plain dicts in *type-registry*. Includes
resolve-type for named type resolution, substitute-type-vars for
parameterized instantiation, subtype-resolved? for structural record
subtyping, and infer-type extension for record field type inference via get.
Phase 7 — defeffect: static effect annotations. Effects stored in
*effect-registry* and *effect-annotations*. Supports :effects keyword on
defcomp and define. Gradual: unannotated = all effects, empty list = pure.
check-body-walk validates effect containment at call sites.
Standard types defined: (maybe a), type-def, diagnostic, prim-param-sig.
Standard effects declared: io, mutation, render.
84/84 type system tests pass. Both Python and JS bootstrappers succeed.
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>
Take HEAD's updated typed-sx content (deftype, effect system details)
with main's /etc/plans/ path prefix. Take main's newer sx-browser.js
timestamp.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
New hierarchy: Geography (Reactive Islands, Hypermedia Lakes, Marshes,
Isomorphism), Language (Docs, Specs, Bootstrappers, Testing),
Applications (CSSX, Protocols), Etc (Essays, Philosophy, Plans).
All routes updated to match: /reactive/* → /geography/reactive/*,
/docs/* → /language/docs/*, /essays/* → /etc/essays/*, etc.
Updates nav-data.sx, all defpage routes, API endpoints, internal links
across 43 files. Enhanced find-nav-match for nested group resolution.
Also includes: page-helpers-demo sf-total fix (reduce instead of set!),
rebootstrapped sx-browser.js and sx_ref.py, defensive slice/rest guards.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
parse-comp-params now recognizes (name :as type) — a 3-element list
with :as keyword separator. Type annotations are stored on the
Component via component-param-types and used by types.sx for call-site
checking. Unannotated params default to any. 428/428 tests pass (50
types tests including 6 annotation tests).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The cond special form misclassified Clojure-style as scheme-style when
the first test was a 2-element list like (nil? x) — treating it as a
scheme clause ((test body)) instead of a function call. Define
cond-scheme? using every? to check ALL clauses, fix eval.sx sf-cond and
render.sx eval-cond, rewrite engine.sx parse-time/filter-params as
nested if to avoid the ambiguity, add regression tests across eval/
render/aser specs. 378/378 tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Two bugs fixed:
1. process-bindings used merge(env) which returns {} for Env objects
(Env is not a dict subclass). Changed to env-extend in render.sx
and adapter-async.sx. This caused "Undefined symbol: theme" etc.
2. async-aser-eval-call passed evaled-args list to async-invoke(&rest),
double-wrapping it. Changed to inline apply + coroutine check.
Also: bootstrap define-async into sx_ref.py (Phase 6), replace ~1000 LOC
hand-written async_eval_ref.py with 24-line thin re-export shim.
Test runner now uses Env (not flat dict) for render envs to catch scope bugs.
8 new regression tests (4 scope chain, 2 native callable arity, 2 render).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The sync aser-call in adapter-sx.sx didn't flatten list results from
map/filter in positional children — serialize(list) wrapped in parens
creating ((div ...) ...) which re-parses as an invalid call. Rewrote
aser-call from reduce to for-each (bootstrapper can't nest for-each
inside reduce lambdas) and added list flattening in both aser-call
and aser-fragment.
Also adds test-aser.sx (41 tests), render-sx platform function,
expanded test-render.sx (+7 map/filter children tests), and specs
async-eval-slot-inner in adapter-async.sx.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add page-helpers-demo page with defisland ~demo-client-runner (pure SX,
zero JS files) showing spec functions running on both server and client
- Fix _aser_component children serialization: flatten list results from map
instead of serialize(list) which wraps in parens creating ((div ...) ...)
that re-parses as invalid function call. Fixed in adapter-async.sx spec
and async_eval_ref.py
- Switch _eval_slot to use async_eval_ref.py when SX_USE_REF=1 (was
hardcoded to async_eval.py)
- Add Island type support to async_eval_ref.py: import, SSR rendering,
aser dispatch, thread-first, defisland in _ASER_FORMS
- Add server affinity check: components with :affinity :server expand
even when _expand_components is False
- Add diagnostic _aser_stack context to EvalError messages
- New spec files: adapter-async.sx, page-helpers.sx, platform_js.py
- Bootstrappers: page-helpers module support, performance.now() timing
- 0-arity lambda event handler fix in adapter-dom.sx
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
EvalError moved to types.py. All 27 files updated to import eval_expr,
trampoline, call_lambda, etc. directly from shared.sx.ref.sx_ref instead
of through the evaluator.py indirection layer. 320/320 spec tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- evaluator.py: replace 1200 lines of hand-written eval with thin shim
that re-exports from bootstrapped sx_ref.py
- bootstrap_py.py: emit all fn-bodied defines as `def` (not `lambda`),
flatten tail-position if/cond/case/when to if/elif with returns,
fix &rest handling in _emit_define_as_def
- platform_py.py: EvalError imports from evaluator.py so catches work
- __init__.py: remove SX_USE_REF conditional, always use bootstrapped
- tests/run.py: reset render_active after render tests for isolation
- Removes setrecursionlimit(5000) hack — no longer needed with flat code
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix qq-expand in eval.sx: use concat+list instead of append to prevent
nested lists from being flattened during quasiquote expansion
- Update append primitive to match spec ("if x is list, concatenate")
- Rebuild sx_ref.py with quasiquote fix
- Make relations.py self-contained: parse defrelation AST directly
without depending on the evaluator (25/25 tests pass)
- Replace hand-written JSEmitter with js.sx self-hosting bootstrapper
- Guard server-only tests in test-eval.sx with runtime check
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
evaluator.py _sf_lambda used only expr[2] (first body expression) instead
of collecting all body expressions and wrapping in (begin ...) when multiple.
This caused multi-body lambdas to silently discard all but the first expression.
Rebuilt sx_ref.py with --spec-modules deps,router,engine,signals so the
router functions are available from the bootstrapped code. The test runner
already had _load_router_from_bootstrap() but it was falling back to the
hand-written evaluator (which has set! scoping issues) because the router
functions weren't in sx_ref.py. Now 134/134 eval+router tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
eval-list only dispatches to the render adapter when render-active? is true.
render-to-html and aser set render-active! on entry. Pure evaluate() calls
no longer stringify component results through the render adapter.
Fixes component children parity: (defcomp ~wrap (&key &rest children) children)
now returns [1,2,3] in eval mode, renders to "123" only in render mode.
Parity: 112/116 pass (remaining 4 are hand-written evaluator.py bugs).
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>
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>
- Configurable page shell (~sx-page-shell kwargs + SX_SHELL app config)
so each app controls its own assets — sx docs loads only sx-browser.js
- SX-evaluated sx-on:* handlers (eval-expr instead of new Function)
with DOM primitives registered in PRIMITIVES table
- data-init boot mode for pure SX initialization scripts
- Jiggle animation on links while fetching
- Nav: 3-column grid for centered alignment, is-leaf sizing,
fix map-indexed param order (index, item), guard mod-by-zero
- Async route eval failure now falls back to server fetch
instead of silently rendering nothing
- Remove duplicate h1 title from ~doc-page
- Re-bootstrap sx-ref.js + sx-browser.js
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>
- signals.sx: fix has? → has-key?, add def-store/use-store/clear-stores
(L3 named stores), emit-event/on-event/bridge-event (event bridge)
- boot.sx: add sx-hydrate-islands, hydrate-island, dispose-island
for client-side island hydration from SSR output
- bootstrap_js.py: add RENAMES, platform fns (domListen, eventDetail,
domGetData, jsonParse), public API exports for all new functions
- bootstrap_py.py: add RENAMES, server-side no-op stubs for DOM events
- Regenerate sx-ref.js (with boot adapter) and sx_ref.py
- Update reactive-islands status: hydration, stores, bridge all spec'd
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Both bootstrappers now handle the full signal runtime:
- &rest lambda params → JS arguments.slice / Python *args
- Signal/Island/TrackingContext platform functions in both hosts
- RENAMES for all signal, island, tracking, and reactive DOM identifiers
- signals auto-included with DOM adapter (JS) and HTML adapter (Python)
- Signal API exports on Sx object (signal, deref, reset, swap, computed, effect, batch)
- New DOM primitives: createComment, domRemove, domChildNodes, domRemoveChildrenAfter, domSetData
- jsonSerialize/isEmptyDict for island state serialization
- Demo HTML page exercising all signal primitives
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add page-render-plan to deps.sx: given page source + env + IO names,
computes a dict mapping each needed component to "server" or "client",
with server/client lists and IO dep collection. 5 new spec tests.
Integration:
- PageDef.render_plan field caches the plan at registration
- compute_page_render_plans() called from auto_mount_pages()
- Client page registry includes :render-plan per page
- Affinity demo page shows per-page render plans
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>
Add :affinity :client/:server/:auto annotations to defcomp, with
render-target function combining affinity + IO analysis. Includes
spec (eval.sx, deps.sx), tests, Python evaluator, and demo page.
Fix critical bug: Python SX parser _ESCAPE_MAP was missing \r and \0,
causing bootstrapped JS parser to treat 'r' as whitespace — breaking
all client-side SX parsing. Also add \0 to JS string emitter and
fix serializer round-tripping for \r and \0.
Reserved word escaping: bootstrappers now auto-append _ to identifiers
colliding with JS/Python reserved words (e.g. default → default_,
final → final_), so the spec never needs to avoid host language keywords.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The make-raw-html wrapper in eval-list was host-specific: it fixed
server-side HTML escaping but broke the client DOM adapter (render-expr
returns DOM nodes, not strings). The raw-html wrapping belongs in the
host (async_eval_ref.py line 94-101), not the spec.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The spec's eval-list calls render-expr for HTML tags/components in eval
position but returned a plain string. When that string was later passed
through _arender (e.g. as a component keyword arg), it got HTML-escaped.
Fix in eval.sx: wrap render-expr result in make-raw-html so the value
carries the raw-html type through any evaluator boundary. Also add
is_render_expr check in async_eval_ref.py as belt-and-suspenders for
the same issue in the async wrapper.
This fixes the streaming demo where suspense placeholder divs were
displayed as escaped text instead of real DOM elements.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
eval-cond and process-bindings were hand-written platform JS in
bootstrap_js.py rather than specced in .sx files. This violated the
SX host architecture principle. Now specced in render.sx as shared
render adapter helpers, bootstrapped to both JS and Python.
eval-cond handles both scheme-style ((test body) ...) and clojure-style
(test body test body ...) cond clauses. Returns unevaluated body
expression for the adapter to render in its own mode.
process-bindings evaluates let-binding pairs and returns extended env.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Extend the spec with IO scanning functions (scan-io-refs, transitive-io-refs,
compute-all-io-refs, component-pure?) that detect IO primitive references in
component ASTs. Components are classified as pure (no IO deps, safe for client
rendering) or IO-dependent (must expand server-side).
The partial evaluator (_aser) now uses per-component IO metadata instead of
the global _expand_components toggle: IO-dependent components expand server-
side, pure components serialize for client. Layout slot context still expands
all components for backwards compat.
Spec: 5 new functions + 2 platform interface additions in deps.sx
Host: io_refs field + is_pure property on Component, compute_all_io_refs()
Bootstrap: both sx_ref.py and sx-ref.js updated with IO functions
Bundle analyzer: shows pure/IO classification per page
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
deps.sx is now a spec module that both bootstrap_py.py and bootstrap_js.py
can include via --spec-modules deps. Platform functions (component-deps,
component-set-deps!, component-css-classes, env-components, regex-find-all,
scan-css-classes) implemented natively in both Python and JS.
- Fix deps.sx: env-get-or → env-get, extract nested define to top-level
- bootstrap_py.py: SPEC_MODULES, PLATFORM_DEPS_PY, mangle entries, CLI arg
- bootstrap_js.py: SPEC_MODULES, PLATFORM_DEPS_JS, mangle entries, CLI arg
- Regenerate sx_ref.py and sx-ref.js with deps module
- deps.py: thin dispatcher (SX_USE_REF=1 → bootstrapped, else fallback)
- scan_components_from_sx now returns ~prefixed names (consistent with spec)
Verified: 541 Python tests pass, JS deps tested with Node.js, both code
paths (fallback + bootstrapped) produce identical results.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
(index-of s needle from?) returns first index of needle in s, or -1.
Optional start offset. Specced in primitives.sx, implemented in both
hand-written primitives.py and bootstrapper templates, rebootstrapped.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Both bootstrappers (JS + Python) now gate shift/reset behind --extensions
continuations flag. Without it, using reset/shift errors at runtime.
- JS bootstrapper: extracted Continuation/ShiftSignal types, sfReset/sfShift,
continuation? primitive, and typeOf handling into CONTINUATIONS_JS constant.
Extension wraps evalList, aserSpecial, and typeOf post-transpilation.
- Python bootstrapper: added special-forms.sx validation cross-check against
eval.sx dispatch, warns on mismatches.
- Added shared/sx/ref/special-forms.sx: 36 declarative form specs with syntax,
docs, tail-position, and examples. Used by bootstrappers for validation.
- Added ellipsis (...) support to both parser.py and parser.sx spec.
- Updated continuations essay to reflect optional extension architecture.
- Updated specs page and nav with special-forms.sx entry.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Bootstrap shift/reset to both Python and JS targets. The implementation
uses exception-based capture with re-evaluation: reset wraps in try/catch
for ShiftSignal, shift raises to the nearest reset, and continuation
invocation pushes a resume value and re-evaluates the body.
- Add Continuation type and _ShiftSignal to shared/sx/types.py
- Add sf_reset/sf_shift to hand-written evaluator.py
- Add async versions to async_eval.py
- Add shift/reset dispatch to eval.sx spec
- Bootstrap to Python: FIXUPS_PY with sf_reset/sf_shift, regenerate sx_ref.py
- Bootstrap to JS: Continuation/ShiftSignal types, sfReset/sfShift in fixups
- Add continuation? primitive to both bootstrappers and primitives.sx
- Allow callables (including Continuation) in hand-written HO map
- 44 unit tests (22 per evaluator) covering: passthrough, abort, invoke,
double invoke, predicate, stored continuation, nested reset, practical patterns
- Update continuations essay to reflect implemented status with examples
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
is_primitive/get_primitive now check the shared registry
(shared.sx.primitives) when a name isn't in the transpiled PRIMITIVES
dict. Fixes Undefined symbol errors for register_primitive'd functions
like relations-from.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
HO forms (map, filter, reduce, etc.) now use call-fn which dispatches
Lambda → call-lambda, native callable → apply, else → clear EvalError.
Previously call-lambda crashed with AttributeError on native functions.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Previously defhandler routed to sf-define which tried to evaluate
(&key ...) params as expressions. Now each form has its own spec
with parse-key-params and platform constructors.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Mirrors bootstrap_js.py pattern — reads the .sx reference spec files
(eval.sx, render.sx, adapter-html.sx) and emits a standalone Python
evaluator module (sx_ref.py) that can be compared against the
hand-written evaluator.py / html.py.
Key transpilation techniques:
- Nested IIFE lambdas for let bindings: (lambda a: body)(val)
- _sx_case helper for case/type dispatch
- Short-circuit and/or via Python ternaries
- Functions with set! emitted as def with _cells dict for mutation
- for-each with inline fn emitted as Python for loops
- Statement-level cond emitted as if/elif/else chains
Passes 27/27 comparison tests against hand-written evaluator.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>