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>
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>
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>
sx-browser.js evaluates test.sx directly in the browser — click
"Run 81 tests" to see SX test itself. Uses the same Sx global that
rendered the page.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Client caches IO results by (name + args) in memory. In-flight
promises are cached too (dedup concurrent calls for same args).
Server adds Cache-Control: public, max-age=300 for HTTP caching.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The Lambda constructor stores properties without underscore prefix,
but asyncRenderMap/asyncRenderMapIndexed accessed them with underscores.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Switch to POST with JSON body when query string exceeds 1500 chars
(highlight calls with large component sources hit URL length limits)
- Include CSRF token header on POST requests
- Add .catch() on fetch to gracefully handle network errors (return NIL)
- Upgrade async eval miss logs from logInfo to logWarn for visibility
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace hardcoded IO primitive lists on both client and server with
data-driven registration. Page registry entries carry :io-deps (list
of IO primitive names) instead of :has-io boolean. Client registers
proxied IO on demand per page via registerIoDeps(). Server builds
allowlist from component analysis.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Wire async rendering into client-side routing: pages whose component
trees reference IO primitives (highlight, current-user, etc.) now
render client-side via Promise-aware asyncRenderToDom. IO calls proxy
through /sx/io/<name> endpoint, which falls back to page helpers.
- Add has-io flag to page registry entries (helpers.py)
- Remove IO purity filter — include IO-dependent components in bundles
- Extend try-client-route with 4 paths: pure, data, IO, data+IO
- Convert tryAsyncEvalContent to callback style, add platform mapping
- IO proxy falls back to page helpers (highlight works via proxy)
- Demo page: /isomorphism/async-io with inline highlight calls
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
bindBoostForm re-reads method/action at submit time.
bind-preload-for re-reads verb-info and headers at preload time.
No closed-over stale values anywhere in the event binding system.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
All click handlers (bind-event, bindBoostLink, bindClientRouteClick)
now re-read href/verb-info from the DOM element when the click fires,
instead of using values captured at bind time. This ensures correct
behavior when DOM is replaced or attributes are morphed after binding.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Three key optimizations to the JS evaluator platform layer:
1. envMerge uses Object.create() instead of copying all keys — O(own) vs O(all)
2. renderDomComponent/renderDomElement override: imperative kwarg/attr
parsing replaces reduce+assoc pattern (no per-arg dict allocation)
3. callComponent/parseKeywordArgs override: same imperative pattern
for the eval path (not just DOM rendering)
Wire format and spec semantics unchanged — these are host-level
performance overrides in the platform JS.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Pages whose components aren't loaded client-side now fall through
to server fetch instead of silently failing in the async callback.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
handle-popstate falls back to #main-panel when no [sx-boost] element
is found, fixing back button for apps using explicit sx-target attrs.
bindClientRouteClick also checks sx-target on the link itself.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
bindClientRouteClick was calling tryClientRoute(pathname) without the
target-sel argument. This caused resolve-route-target to return nil,
so client routing ALWAYS fell back to server fetch on link clicks.
Now finds the sx-boost ancestor and passes its target selector.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The registrations were in the platform eval block which emits before
var PRIMITIVES = {}. Moved to core.list and core.dict primitive sections.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Previously these mutating operations were internal helpers in the JS
bootstrapper but not declared in primitives.sx or registered in the
Python evaluator. Now properly specced and available in both hosts.
Removes mock injections from cache tests — they use real primitives.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
boot.sx uses parse-route-pattern from router.sx, but router was only
included as an opt-in spec module. Now auto-included when boot is in
the adapter set.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Spec layer (orchestration.sx):
- try-client-route now handles :data pages instead of falling back to server
- New abstract primitive resolve-page-data(name, params, callback) — platform
decides transport (HTTP, IPC, cache, etc)
- Extracted swap-rendered-content and resolve-route-target helpers
Platform layer (bootstrap_js.py):
- resolvePageData() browser implementation: fetches /sx/data/<name>, parses
SX response, calls callback. Other hosts provide their own transport.
Server layer (pages.py):
- evaluate_page_data() evaluates :data expr, serializes result as SX
- auto_mount_page_data() mounts /sx/data/ endpoint with per-page auth
- _build_pages_sx now computes component deps for all pages (not just pure)
Test page at /isomorphism/data-test exercises the full pipeline.
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>
Remove strict deps check — for case expressions like essay pages,
deps includes ALL branches but only one is taken. Instead, just
try to eval the content. If a component is missing, tryEvalContent
catches the error and we transparently fall back to server fetch.
deps field remains in registry for future prefetching use.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Each page entry now includes a deps list of component names needed.
Client checks all deps are loaded before attempting eval — if any
are missing, falls through to server fetch with a clear log message.
No bundle bloat: server sends components for the current page only.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
NIL is a frozen sentinel object ({_nil:true}) which is truthy in JS.
(not expr) compiled to !expr, so (not nil) returned false instead of
true. Fixed to compile as !isSxTruthy(expr) which correctly handles
NIL. This was preventing client-side routing from activating.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
SES lockdown may suppress console.error. Use logInfo for error
reporting since we know it works ([sx-ref] prefix visible).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Log "sx:route server fetch <url>" when falling back to network
- Use console.error for eval errors (not console.warn)
- Restructure bind-event to separate client route check from &&-chain
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
parseRoutePattern was undefined because the router module
wasn't included in the build. Now passing --spec-modules router.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The data-mount="body" script replaces the entire body content,
destroying the <script type="text/sx-pages"> tag. Moving
processPageScripts before processSxScripts ensures the page
registry is read before the body is replaced.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Server-side: log page count, output size, and first 200 chars in _build_pages_sx.
Client-side: log script tag count, text length, parsed entry count in processPageScripts.
Helps diagnose why pages: 0 routes loaded.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>