Commit Graph

58 Commits

Author SHA1 Message Date
2c97542ee8 Fix island dep scanning + spread-through-reactive-if debug
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>
2026-03-13 04:37:45 +00:00
ea2b71cfa3 Add provide/context/emit!/emitted — render-time dynamic scope
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>
2026-03-13 02:58:21 +00:00
41097eeef9 Add spread + collect primitives, rewrite ~cssx/tw as defcomp
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>
2026-03-13 02:38:31 +00:00
5f06e2e2cc Spec URL evaluation in router.sx, bootstrap to Python/JS
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>
2026-03-12 23:05:01 +00:00
355f57a60b Fix component name regex to support : and / in paths
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>
2026-03-12 22:27:52 +00:00
b23e81730c SX URL algebra: relative resolution, keyword ops, ! special forms
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>
2026-03-12 18:31:21 +00:00
2076e1805f Phase 4: Client-side routing for SX expression URLs
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>
2026-03-12 10:27:28 +00:00
c2d9a3d2b1 Merge worktree-endpoints: migrate all ref endpoints to SX with typed handlers 2026-03-12 00:15:22 +00:00
575d100f67 Migrate remaining 7 ref endpoints to SX, add :returns type annotations
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>
2026-03-12 00:14:40 +00:00
89e8645d8f Merge branch 'worktree-endpoints' into macros 2026-03-11 23:48:30 +00:00
fba84540e2 Extend defhandler with :path/:method/:csrf, migrate 12 ref endpoints to SX
defhandler now supports keyword options for public route registration:
  (defhandler name :path "/..." :method :post :csrf false (&key) body)

Infrastructure: forms.sx parses options, HandlerDef stores path/method/csrf,
register_route_handlers() mounts path-based handlers as app routes.

New IO primitives (boundary.sx "Web interop" section): now, sleep,
request-form, request-json, request-header, request-content-type.

First migration: 12 reference API endpoints from Python f-string SX
to declarative .sx handlers in sx/sx/handlers/ref-api.sx.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 23:48:05 +00:00
2f42e8826c Add :effects annotations to all spec files and update bootstrappers
Bootstrappers (bootstrap_py.py, js.sx) now skip :effects keyword in
define forms, enabling effect annotations throughout the spec without
changing generated output.

Annotated 180+ functions across 14 spec files:
- signals.sx: signal/deref [] pure, reset!/swap!/effect/batch [mutation]
- engine.sx: parse-* [] pure, morph-*/swap-* [mutation io]
- orchestration.sx: all [mutation io] (browser event binding)
- adapter-html.sx: render-* [render]
- adapter-dom.sx: render-* [render], reactive-* [render mutation]
- adapter-sx.sx: aser-* [render]
- adapter-async.sx: async-render-*/async-aser-* [render io]
- parser.sx: all [] pure
- render.sx: predicates [] pure, process-bindings [mutation]
- boot.sx: all [mutation io] (browser init)
- deps.sx: scan-*/transitive-* [] pure, compute-all-* [mutation]
- router.sx: all [] pure (URL matching)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 23:22:34 +00:00
ce7ad125b6 Add deftype and defeffect to SX type system (Phases 6-7)
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>
2026-03-11 22:51:19 +00:00
b99e69d1bb Add (param :as type) annotations to all fn/lambda params across SX spec
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>
2026-03-11 20:27:36 +00:00
c82941d93c Merge main into macros: resolve nav restructure conflicts
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>
2026-03-11 19:26:56 +00:00
4d54be6b6b Restructure SX docs nav into 4 top-level sections with nested routing
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>
2026-03-11 18:50:31 +00:00
8a530569a2 Add (name :as type) annotation syntax for defcomp params
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>
2026-03-11 17:12:54 +00:00
cd7653d8c3 Fix cond ambiguity: check ALL clauses with cond-scheme?, not just first
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>
2026-03-11 16:51:41 +00:00
ff6c1fab71 Fix process-bindings scope loss and async-invoke arity, bootstrap async adapter
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>
2026-03-11 16:38:47 +00:00
e843602ac9 Fix aser list flattening bug, add wire format test suite (41 tests)
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>
2026-03-11 14:59:31 +00:00
c95e19dcf2 Page helpers demo: defisland, map-in-children fix, _eval_slot ref evaluator
- 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>
2026-03-11 14:30:12 +00:00
29c90a625b Delete evaluator.py shim: all imports go directly to bootstrapped sx_ref.py
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>
2026-03-11 11:15:48 +00:00
4c4806c8dd Fix all 9 spec test failures: Env scope chain, IO detection, offline mutation
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 4m11s
- env.py: Add MergedEnv with dual-parent lookup (primary for set!,
  secondary for reads), add dict-compat methods to Env
- platform_py.py: make_lambda stores env reference (no copy), env_merge
  uses MergedEnv for proper set! propagation, ancestor detection prevents
  unbounded chains in TCO recursion, sf_set_bang walks scope chain
- types.py: Component/Island io_refs defaults to None (not computed)
  instead of empty set, so component-pure? falls through to scan
- run.py: Test env uses Env class, mock execute-action calls SX lambdas
  via _call_sx instead of direct Python call

Spec tests: 320/320 (was 311/320)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 09:42:04 +00:00
d8cddbd971 Replace hand-written evaluator with bootstrapped spec, emit flat Python
- 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>
2026-03-11 09:18:17 +00:00
3906ab3558 Fix quasiquote flattening bug, decouple relations from evaluator
- 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>
2026-03-11 04:53:34 +00:00
46cd179703 Fix multi-body lambda in evaluator, rebuild sx_ref.py with router module
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>
2026-03-11 04:34:17 +00:00
f54ebf26f8 Separate eval from render: render-active? gate in eval-list
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>
2026-03-11 03:42:04 +00:00
1341c144da URL restructure, 404 page, trailing slash normalization, layout fixes
- 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>
2026-03-10 21:30:18 +00:00
9b9fc6b6a5 Level 2-3: lake morphing — server content flows through reactive islands
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>
2026-03-10 14:29:54 +00:00
8a5c115557 SX docs: configurable shell, SX-native event handlers, nav fixes
- 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>
2026-03-10 11:00:59 +00:00
03ba8e58e5 Implement reader macros (#;, #|...|, #', #name) and #z3 demo
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 11m13s
Reader macros in parser.sx spec, Python parser.py, and hand-written sx.js:
- #; datum comment: read and discard next expression
- #|...|  raw string: no escape processing
- #' quote shorthand: (quote expr)
- #name extensible dispatch: registered handler transforms next expression

#z3 reader macro demo (reader_z3.py): translates define-primitive
declarations from primitives.sx into SMT-LIB verification conditions.
Same source, two interpretations — bootstrappers compile to executable
code, #z3 extracts proof obligations.

48 parser tests (SX spec + Python), all passing. Rebootstrapped JS+Python.
Demo page at /plans/reader-macro-demo with side-by-side examples.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 20:21:40 +00:00
8683cf24c3 Island disposal, reactive lists, input binding, and Phase 2 plan
- 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>
2026-03-08 16:10:52 +00:00
189a0258d9 Fix reactive islands client-side navigation and hydration
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>
2026-03-08 15:18:45 +00:00
c55f0956bc Bootstrap stores + event bridge, add island hydration to boot.sx
- 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>
2026-03-08 11:13:18 +00:00
c344b0d7b0 Regenerate sx_ref.py with deps + signals spec modules
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 10:22:04 +00:00
fe289287ec Transpile signals.sx to JS and Python via bootstrappers
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>
2026-03-08 10:17:16 +00:00
9a707dbe56 Merge branch 'worktree-cssx-components' into macros
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 14m0s
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 00:25:22 +00:00
2da80c69ed Phase 7b: page render plans — per-page boundary optimizer
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>
2026-03-08 00:02:53 +00:00
a8bfff9e0b Remove CSSX style dictionary infrastructure — styling is just components
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>
2026-03-08 00:00:23 +00:00
a70ff2b153 Phase 7a: affinity annotations + fix parser escape sequences
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>
2026-03-07 23:53:33 +00:00
5a68046bd8 Restore stashed WIP: live streaming plan, forms, CI pipeline, streaming demo
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 22:07:59 +00:00
5cca22ae6d Revert make-raw-html wrapping from eval.sx spec
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>
2026-03-07 19:23:27 +00:00
668a46bec0 Fix render-expr in eval position: wrap result in raw-html
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>
2026-03-07 18:58:42 +00:00
917a487195 Add deps and engine test specs, bootstrap engine to Python
New test specs (test-deps.sx: 33 tests, test-engine.sx: 37 tests) covering
component dependency analysis and engine pure functions. All 6 spec modules
now have formal SX tests: eval (81), parser (39), router (18), render (23),
deps (33), engine (37) = 231 total.

- Add engine as spec module in bootstrap_py.py (alongside deps)
- Add primitive aliases (trim, replace, parse_int, upper) for engine functions
- Fix parse-int to match JS parseInt semantics (strip trailing non-digits)
- Regenerate sx_ref.py with --spec-modules deps,engine
- Update all three test runners (run.js, run.py, sx-test-runner.js)
- Add Dependencies and Engine nav items and testing page entries
- Wire deps-source/engine-source through testing overview UI

Node.js: 231/231 pass. Python: 226/231 (5 pre-existing parser/router gaps).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 18:01:33 +00:00
20ac0fe948 Spec eval-cond and process-bindings in render.sx (remove platform implementations)
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>
2026-03-06 16:58:53 +00:00
cf5e767510 Phase 3: Client-side routing with SX page registry + routing analyzer demo
Add client-side route matching so pure pages (no IO deps) can render
instantly without a server roundtrip. Page metadata serialized as SX
dict literals (not JSON) in <script type="text/sx-pages"> blocks.

- New router.sx spec: route pattern parsing and matching (6 pure functions)
- boot.sx: process page registry using SX parser at startup
- orchestration.sx: intercept boost links for client routing with
  try-first/fallback — client attempts local eval, falls back to server
- helpers.py: _build_pages_sx() serializes defpage metadata as SX
- Routing analyzer demo page showing per-page client/server classification
- 32 tests for Phase 2 IO detection (scan_io_refs, transitive_io_refs,
  compute_all_io_refs, component_pure?) + fallback/ref parity
- 37 tests for Phase 3 router functions + page registry serialization
- Fix bootstrap_py.py _emit_let cell variable initialization bug
- Fix missing primitive aliases (split, length, merge) in bootstrap_py.py

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 15:47:56 +00:00
0ba7ebe349 Phase 2: IO detection & selective expansion in deps.sx
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>
2026-03-06 13:19:17 +00:00
4c97b03dda Wire deps.sx into both bootstrappers, rebootstrap Python + JS
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>
2026-03-06 11:55:32 +00:00
e112bffe5c Add index-of string primitive: spec, Python, JS, rebootstrap
(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>
2026-03-06 09:48:44 +00:00
6c27ebd3b4 Merge branch 'worktree-refactor-primitives' into macros
# Conflicts:
#	shared/sx/ref/bootstrap_js.py
#	shared/sx/ref/bootstrap_py.py
2026-03-06 01:50:29 +00:00