The Python bootstrapper now auto-includes deps (component analysis)
and signals (reactive islands) when the HTML adapter is present,
matching production requirements where sx_ref.py must export
compute_all_deps, transitive_deps, page_render_plan, etc.
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>
test-signals.sx: 17 tests covering signal basics (create, deref, reset!,
swap!), computed (derive, update, chain), effects (run, re-run, dispose,
cleanup), batch (deferred deduped notifications), and defisland (create,
call, children).
types.py: Island dataclass mirroring Component but for reactive boundaries.
evaluator.py: sf_defisland special form, Island in call dispatch.
run.py: Signal platform primitives (make-signal, tracking context, etc)
and native effect/computed/batch implementations that bridge Lambda
calls across the Python↔SX boundary.
signals.sx: Updated batch to deduplicate subscribers across signals.
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>
Tests cover page data cache, optimistic cache update/revert/confirm,
offline connectivity tracking, offline queue mutation, and offline-aware
routing. Registered in test runner with mocked platform functions.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
7c: Client data cache management via element attributes
(sx-cache-invalidate) and response headers (SX-Cache-Invalidate,
SX-Cache-Update). Programmatic API: invalidate-page-cache,
invalidate-all-page-cache, update-page-cache.
7d: Service Worker (sx-sw.js) with IndexedDB for offline-capable
data caching. Network-first for /sx/data/ and /sx/io/, stale-while-
revalidate for /static/. Cache invalidation propagates from
in-memory cache to SW via postMessage.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
defpage is already portable: server executes via execute_page(),
client via try-client-route. Add render plan logging to client
routing so console shows boundary decisions on navigation:
"sx:route plan pagename — N server, M client"
Mark Phase 7 (Full Isomorphism) as complete:
- 7a: affinity annotations + render-target
- 7b: page render plans (boundary optimizer)
- 7e: cross-host isomorphic testing (61 tests)
- 7f: universal page descriptor + visibility
7c (optimistic updates) and 7d (offline data) remain as future work.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
61 tests evaluate the same SX expressions on both Python (sx_ref.py)
and JS (sx-browser.js via Node.js), comparing output:
- 37 eval tests (arithmetic, strings, collections, logic, HO forms)
- 24 render tests (elements, attrs, void elements, components, escaping)
All pass — both hosts produce identical results from the same spec.
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>
_script_hash used relative Path("static") which resolved to /app/static/
inside the container, but the file is at /app/shared/static/. Use
Path(__file__).parent.parent to resolve from shared/ correctly.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Parser: chained .replace() calls processed \n before \\, causing \\n
to become a real newline. Replaced with character-by-character
_unescape_string. Fixes 2 parser spec test failures.
Primitives: prim_get only handled dict and list. Objects with .get()
methods (like PageDef) returned None. Added hasattr fallback.
Fixes 9 defpage spec test failures.
All 259 spec tests now pass (was 244/259).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
domCreateElement treated SX NIL (a truthy JS object) as a real namespace,
calling createElementNS("nil", tag) instead of createElement(tag). All
elements created by resolveSuspense ended up in the "nil" XML namespace
where CSS class selectors don't match.
Also fix ~suspense fallback: empty &rest list is truthy in SX, so
fallback content never rendered.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
parse() returns a list of expressions. resolveSuspense was passing the
entire array to renderToDom, which interpreted [(<> ...)] as a call
expression ((<> ...)) — causing "Not callable: {}". Now iterates
each expression individually, matching the public render() API.
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>
Streaming resolve scripts arrive after boot, so any extra component
defs sent as <script type="text/sx"> tags weren't being loaded.
Fix in the spec (boot.sx): call (process-sx-scripts nil) at the
start of resolve-suspense so late-arriving component defs are
available in componentEnv before rendering.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Resolved SX content may reference components not in the initial shell
scan (e.g. ~cart-mini from IO-generated headers). Diff the needed
components against what the shell already sent and prepend any extra
defcomps as a <script type="text/sx"> block before the resolve script.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The streaming page's component scan only covered the suspense
placeholders, missing transitive deps from layout headers (e.g.
~cart-mini, ~auth-menu). Add layout.component_names to Layout class
and include them plus page content_expr in the scan source.
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>
The streaming shell now uses render_to_html so [data-suspense] elements
are real DOM elements immediately when the browser parses the HTML.
Previously the shell used SX wire format in a <script data-mount> tag,
requiring sx-browser.js to boot and render before suspense elements
existed — creating a race condition where resolution scripts fired
before the elements were in the DOM.
Now: server renders HTML with suspense placeholders → browser has real
DOM elements → resolution scripts find and replace them reliably.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Async generator bodies don't execute until __anext__(), by which time
the request context is gone. Restructure execute_page_streaming as a
regular async function that does all context-dependent work (g, request,
current_app access, layout resolution, task creation) while the context
is live, then returns an inner async generator that only yields strings
and awaits pre-created tasks.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
stream_with_context decorates a generator function, not a generator
instance. Wrap execute_page_streaming in a decorated inner function.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Quart's ASGI layer doesn't propagate request/app context into async
generator iterations. Wrap execute_page_streaming with stream_with_context
so current_app and request remain accessible after each yield.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
boundary.sx now contains only generic web-platform I/O primitives that
any SX host would provide (current-user, request-arg, url-for, etc.).
Moved to boundary-app.sx (deployment-specific):
- Inter-service: frag, query, action, service
- Framework: htmx-request?, g, jinja-global
- Domain: nav-tree, get-children, relations-from
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Client-side routing was only swapping #main-panel content without
updating OOB headers (nav rows, sub-rows). Now each page entry in the
registry includes a layout identity (e.g. "sx-section:Testing") and
try-client-route falls through to server when layout changes, so OOB
header updates are applied correctly.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Server streams HTML shell with ~suspense placeholders immediately,
then sends resolution <script> chunks as async IO completes. Browser
renders loading skeletons instantly, replacing them with real content
as data arrives via __sxResolve().
- defpage :stream true opts pages into streaming response
- ~suspense component renders fallback with data-suspense attr
- resolve-suspense in boot.sx (spec) + bootstrapped to sx-browser.js
- __sxPending queue handles resolution before sx-browser.js loads
- execute_page_streaming() async generator with concurrent IO tasks
- Streaming demo page at /isomorphism/streaming with 1.5s simulated delay
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
New /testing/ section with 6 pages: overview (all specs), evaluator,
parser, router, renderer, and runners. Each page runs tests server-side
(Python) and offers a browser "Run tests" button (JS). Modular browser
runner (sxRunModularTests) loads framework + per-spec sources from DOM.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>