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>
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>
- 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>
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>
deps.sx: scan island bodies for component deps (was only scanning
"component" and "macro", missing "island" type). This ensures
~cssx/tw and its dependencies are sent to the client for islands.
cssx.sx: move if inside make-spread arg so it's evaluated by
eval-expr (no reactive wrapping) instead of render-to-dom which
applies reactive-if inside island scope, converting the spread
into a fragment and losing the class attrs.
Added island dep tests at 3 levels: test-deps.sx (spec),
test_deps.py (Python), test_parity.py (ref vs fallback).
sx-browser.js: temporary debug logging at spread detection points.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Islands render independently on the client, so ~cssx/tw calls
collect!("cssx", rule) but no ~cssx/flush runs. Add flush-cssx-to-dom
in boot.sx that injects collected rules into a persistent <style>
element in <head>.
Called at all lifecycle points: boot-init, sx-mount, resolve-suspense,
post-swap (navigation morph), and swap-rendered-content (client routes).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The previous fix only guarded domAppend/domInsertAfter, but many
platform JS functions (asyncRenderChildren, asyncRenderElement,
asyncRenderMap, render, sxRenderWithEnv) call appendChild directly.
Add _spread guards to all direct appendChild sites. For async element
rendering, merge spread attrs onto parent (class/style join, others
overwrite) matching the sync adapter behavior.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Spread values (from ~cssx/tw etc.) are attribute dicts, not DOM nodes.
When they appear in non-element contexts (fragments, islands, lakes,
reactive branches), they must not be passed to appendChild/insertBefore.
Add _spread guard to platform domAppend and domInsertAfter — fixes
TypeError: Node.appendChild: Argument 1 does not implement interface Node.
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>
Replace the three-layer cssx system (macro + value functions + class
components) with a single token resolver. Tokens like "bg-yellow-199",
"hover:bg-rose-500", "md:text-xl" are parsed into CSS declarations.
Two delivery mechanisms, same token format:
- tw() function: returns inline style string for :style
- ~cssx/tw macro: injects JIT class + <style> onto first child element
The resolver handles: colours (21 names, any shade 0-950), spacing,
typography, display, max-width, rounded, opacity, w/h, gap, text
decoration, cursor, overflow, transitions. States (hover/focus/active)
and responsive breakpoints (sm/md/lg/xl/2xl) for class-based usage.
Next step: replace macro/function approach with spec-level primitives
(defcontext/provide/context + spread) so ~cssx/tw becomes a proper
component returning spread values, with rules collected via context.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix O(n²) postprocessing: compute_all_deps/io_refs/hash were called
per-file (92x for sx app). Now deferred to single finalize_components()
call after all files load.
- Add pickle cache in shared/sx/.cache/ keyed by file mtimes+sizes.
Cache stores fully-processed Component/Island/Macro objects with deps,
io_refs, and css_classes pre-computed. Closures stripped before pickle,
rebuilt from global env after restore.
- Smart finalization: cached loads skip deps/io_refs recomputation
(already in pickle), only recompute component hash.
- Fix ~sx-header → ~layouts/header ref in docs-content.sx
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add url-to-expr, auto-quote-unknowns, prepare-url-expr to router.sx —
the canonical URL-to-expression pipeline. Dots→spaces, parse, then
auto-quote unknown symbols as strings (slugs). The same spec serves
both server (Python) and client (JS) route handling.
- router.sx: three new pure functions for URL evaluation
- bootstrap_py.py: auto-include router module with html adapter
- platform_js.py: export urlToExpr/autoQuoteUnknowns/prepareUrlExpr
- sx_router.py: replace hand-written auto_quote_slugs with bootstrapped
prepare_url_expr — delete ~50 lines of hardcoded function name sets
- Rebootstrap sx_ref.py (4331 lines) and sx-browser.js
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The dep scanner regex only matched [a-zA-Z0-9_-] in component names,
missing the new path separators (/) and namespace delimiters (:).
Fixed in deps.sx spec + rebootstrapped sx_ref.py and sx-browser.js.
Also fixed the Python fallback in deps.py.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The rename script only matched ~prefixed names in .sx files.
Python render calls use bare strings like render_to_html("name")
which also need updating: 37 replacements across 8 files.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Component names now reflect filesystem location using / as path separator
and : as namespace separator for shared components:
~sx-header → ~layouts/header
~layout-app-body → ~shared:layout/app-body
~blog-admin-dashboard → ~admin/dashboard
209 files, 4,941 replacements across all services.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Extends router.sx with the full SX URL algebra — structural navigation
(.slug, .., ...), keyword set/delta (.:page.4, .:page.+1), bare-dot
shorthand, and ! special form parsing (!source, !inspect, !diff, !search,
!raw, !json). All pure SX spec, bootstrapped to both Python and JS.
Fixes: index-of -1/nil portability (_index-of-safe wrapper), variadic
(+ a b c) transpilation bug (use nested binary +). Includes 115 passing
tests covering all operations. Also: "The" strapline and essay title.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add sx-url-to-path to router.sx that converts SX expression URLs to
old-style slash paths for route matching. find-matching-route now
transparently handles both formats — the browser URL stays as the SX
expression while matching uses the equivalent old-style path.
/(language.(doc.introduction)) → /language/docs/introduction for matching
but pushState keeps the SX URL in the browser bar.
- router.sx: add _fn-to-segment (doc→docs, etc.), sx-url-to-path
- router.sx: modify find-matching-route to convert SX URLs before matching
- Rebootstrap sx-browser.js and sx_ref.py
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Every URL at sx-web.org now uses bracketed SX expressions — pages AND
API endpoints. defhandler :path values, sx-get/sx-post/sx-delete attrs,
code examples, and Python route decorators all converted.
- Add SxAtomConverter to handlers.py for parameter matching inside
expression URLs (e.g. /(api.(item.<sx:item_id>)))
- Convert ~50 defhandler :path values in ref-api.sx and examples.sx
- Convert ~90 sx-get/sx-post/sx-delete URLs in reference.sx, examples.sx
- Convert ~30 code example URLs in examples-content.sx
- Convert ~30 API URLs in pages.py (Python string code examples)
- Convert ~70 page navigation URLs in pages.py
- Convert 7 Python route decorators in routes.py
- Convert ~10 reactive API URLs in marshes.sx
- Add API redirect patterns to sx_router.py (301 for old paths)
- Remove /api/ skip in app.py redirects (old API paths now redirect)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
component-source and handler-source are page helpers, not IO primitives.
They need to be in the handler evaluation env just like defpage evaluation.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1. Extra closing paren in ex-tabs handler
2. tab-content dict values contained (div ...) HTML tags which crash
during register_components since HTML primitives aren't in env
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add 14 new IO primitives to boundary.sx: web interop (request-form,
request-json, request-header, request-content-type, request-args-all,
request-form-all, request-headers-all, request-file-name), response
manipulation (set-response-header, set-response-status), ephemeral
state (state-get, state-set!), and timing (now, sleep).
All 19 reference handlers now have :returns type annotations using
types.sx vocabulary. Response meta (headers/status) flows through
context vars, applied by register_route_handlers after execution.
Only SSE endpoint remains in Python (async generator paradigm).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
All 11 define-io-primitive entries now declare :effects [io].
Signal primitives annotated: signal/deref/computed = [] (pure),
reset!/swap!/effect/batch = [mutation].
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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>