- CLAUDE.md: add SX rendering pipeline overview, service sx/ vs sxc/
convention, dev container mount convention
- docker-compose.dev.yml: add missing ./sx/sx:/app/sx bind mount for
sx_docs (root cause of "Unknown component: ~sx-layout-full")
- async_eval.py: add evaluation modes table to module docstring; log
error when async_eval_slot_to_sx can't find a component instead of
silently falling through to client-side serialization
- helpers.py: remove debug logging from render_to_sx_with_env
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The load_service_components call used dirname twice from
sxc/pages/__init__.py, yielding {service}/sxc/ instead of
{service}/. This meant {service}/sx/*.sx files (layouts, calendar
components, etc.) were never loaded into the component env.
- sx: ~sx-layout-full not found → Unknown component on client
- events: ~events-calendar-grid not found → Unknown component
- market: also fix url_for endpoint for defpage_market_admin
(mounted on app, not blueprint — no prefix needed)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When an HTML tag like (div) appears as a kwarg value in SX wire format,
callComponent evaluates it with sxEval (data mode) which doesn't handle
HTML tags. Now sxEval delegates to renderDOM for any render expression
(HTML tags, SVG tags, fragments, raw!, components).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
_aser_component expands component bodies in SX wire format mode,
but was evaluating kwarg values with async_eval (HTML mode). This
caused SxExpr kwargs to be fully rendered to HTML strings, which
then broke when serialized back to SX — producing bare symbols
like 'div' that the client couldn't resolve.
Fix: use _aser() for kwarg evaluation to keep values in SX format.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Logs env keys (non-function) when a symbol lookup fails, to help
diagnose which component/context is missing the expected binding.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The JS contains? used `k in c` which throws TypeError on strings.
The Python version silently returned False for strings. Both now
use indexOf/`in` for substring matching on strings.
Fixes: sx.js MOUNT PARSE ERROR on blog index where
(contains? current-local-href "?") was evaluated client-side.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Phase 4 (Test): Update ~test-layout-full and ~test-detail-layout-full defcomps
to use ~root-header with env free variables. Switch render functions to
render_to_sx_with_env.
Phase 5 (Cart): Convert cart-page, cart-admin, and order render functions.
Update cart .sx layout defcomps to use ~root-header from free variables.
Phase 6 (Blog): Convert all 7 blog layouts (blog, settings, sub-settings x5).
Remove all root_header_sx calls from blog.
Phase 7 (Market): Convert market and market-admin layouts plus browse/product
render functions. Remove root_header_sx import.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Nested component calls in _aser are serialized without body expansion,
so free variables inside ~root-header would be sent unresolved to the
client. Fix by making ~root-header/~root-mobile take all values as
&key params, and having parent layout defcomps pass them explicitly.
The parent layout bodies ARE expanded (via async_eval_slot_to_sx),
so their free variables resolve correctly during that expansion.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
async_eval_to_sx serializes component calls without expanding their bodies,
so free variables from _ctx_to_env were passed through as unresolved symbols
to the client. Switch to async_eval_slot_to_sx which expands the top-level
component body server-side, resolving free variables during expansion.
Also inline ~root-header/~root-mobile into layout defcomps rather than using
wrapper defcomps (nested ~component calls in _aser are serialized without
expansion, so wrapper defcomps would still leave free vars unresolved).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Phase 0: Add _ctx_to_env() and render_to_sx_with_env() to shared/sx/helpers.py,
register_sx_layout() to shared/sx/layouts.py, and ~root-header/~root-mobile
wrapper defcomps to layout.sx. Convert built-in "root" layout to .sx.
Phases 1-3: Convert account (65→19 lines), federation (105→97 lines),
and orders (88→21 lines) to use register_sx_layout with .sx defcomps
that read ctx values as free variables from the evaluation environment.
No more Python building SX strings via SxExpr(await root_header_sx(ctx)).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
(filter (feTurbulence ...)) inside (svg ...) has no keyword first arg,
so the keyword-only check dispatched it as a HO function. Now also
check SVG/MathML context (ns in client, _svg_context in server).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Hyphenated names like app-url are variables, not custom elements.
Only treat as custom element when first arg is a Keyword (tag call pattern).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix filter/map dispatching as HO functions when used as SVG/HTML tags
(peek at first arg — Keyword means tag call, not function call)
- Add html: prefix escape hatch to force any name to render as an element
- Support custom elements (hyphenated names) per Web Components spec
- SVG/MathML namespace auto-detection: client threads ns param through
render chain; server uses _svg_context ContextVar so unknown tags
inside (svg ...) or (math ...) render as elements without enumeration
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Soviet constructivist poster with paper texture filters, grid lines,
aged stain spots, and "(<x>)" symbol in red.
Add missing SVG filter primitive tags to both server (html.py) and
client (sx.js): feTurbulence, feColorMatrix, feBlend,
feComponentTransfer, feFuncR/G/B/A, feDisplacementMap, feComposite,
feFlood, feImage, feMorphology, feSpecularLighting, feDiffuseLighting,
fePointLight, feSpotLight, feDistantLight.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Wrap \"this\" in sx string quotes so backslash escapes are inside a string
- Remove stray quote before closing paren on wire protocol li item
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
SX is a paradigm, not a language. Changed 7 instances where "language"
referred to SX itself: "one paradigm since 1958", "the paradigm is the
framework", "not a framework but a paradigm", "paradigms do not have
breaking changes", "the paradigm itself provides", "a paradigm that
does not require a migration guide", "distinct from the paradigm".
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
In the SX Manifesto: "the spectre of s-expressionism", "S-expressionism
abolishes", "S-expressionism needs no ecosystem", "S-expressionism
resolves the CSS question", "The s-expressionist revolution".
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace Tailwind class strings with native SX expressions:
(css :flex :gap-4 :hover:bg-sky-200) instead of :class "flex gap-4 ..."
- Add style_dict.py: 516 atoms, variants, breakpoints, keyframes, patterns
- Add style_resolver.py: memoized resolver with variant splitting
- Add StyleValue type to types.py (frozen dataclass with class_name, declarations, etc.)
- Add css and merge-styles primitives to primitives.py
- Add defstyle and defkeyframes special forms to evaluator.py and async_eval.py
- Integrate StyleValue into html.py and async_eval.py render paths
- Add register_generated_rule() to css_registry.py, fix media query selector
- Add style dict JSON delivery with localStorage caching to helpers.py
- Add client-side css primitive, resolver, and style injection to sx.js
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The (code :class "text-violet-700" ...) was embedded inside a string
child of (p), causing the SX parser to see text-violet-700 as a bare
symbol. Close the text string before the (code) element so it becomes
a proper child of the paragraph.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The em span variable was embedded inside an unclosed sx string,
causing the " before "italic" to close the outer string and
leaving italic as an undefined bare symbol.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Implement missing SxEngine features:
- SSE (sx-sse, sx-sse-swap) with EventSource management and auto-cleanup
- Response headers: SX-Trigger, SX-Retarget, SX-Reswap, SX-Redirect,
SX-Refresh, SX-Location, SX-Replace-Url, SX-Trigger-After-Swap/Settle
- View Transitions API: transition:true swap modifier + global config
- every:<time> trigger for polling (setInterval)
- sx-replace-url (replaceState instead of pushState)
- sx-disabled-elt (disable elements during request)
- sx-prompt (window.prompt, value sent as SX-Prompt header)
- sx-params (filter form parameters: *, none, not x,y, x,y)
Adds docs (ATTR_DETAILS, BEHAVIOR_ATTRS, headers, events), demo
components in reference.sx, API endpoints (prompt-echo, sse-time),
and 27 new unit tests for engine logic.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The .sx component files (home.sx, docs.sx, etc.) live in sxc/, but
the path was pointing to sxc/pages/ after the move from sx_components.py.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Move relations component loading into app.py. Move test rendering
functions to test/sxc/pages/__init__.py, update route imports, and
delete both sx_components.py files. Zero sx_components imports remain.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
These are sx implementations of htmx attributes (boost, preload,
preserve, indicator, validate, ignore, optimistic), not unique to sx.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Consolidate 86 component rendering functions into sxc/pages/__init__.py,
update 37 import sites in routes.py, remove app.py side-effect imports,
and delete sx/sxc/sx_components.py.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Phase 7 of the zero-Python-rendering plan. All 100 rendering functions
move from events/sx/sx_components.py into events/sxc/pages/__init__.py.
Route handlers (15 files) import from sxc.pages instead.
load_service_components call moves into _load_events_page_files.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add sx-preserve/sx-ignore (morph skip), sx-indicator (loading element),
sx-validate (form validation), sx-boost (progressive enhancement),
sx-preload (hover prefetch with 30s cache), and sx-optimistic (instant
UI preview with rollback). Move all from HTMX_MISSING_ATTRS to
SX_UNIQUE_ATTRS with full ATTR_DETAILS docs and reference.sx demos.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Phase 6 of the zero-Python-rendering plan. All 46 rendering functions
move from market/sx/sx_components.py into market/sxc/pages/__init__.py.
Route handlers import from sxc.pages instead. load_service_components
call moves into _load_market_page_files.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Both evaluators now use thunk-based trampolining to eliminate stack
overflow on deep tail recursion (verified at 50K+ depth). Mirrors
the sync evaluator TCO added in 5069072.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Without call/cc you get callback pyramids, state machines, command
pattern undo stacks, Promise chains, and framework-specific hooks —
each a partial ad-hoc reinvention of continuations with its own edge
cases. The complexity doesn't disappear; it moves into user code.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Covers server-side (suspendable rendering, streaming, error boundaries),
client-side (linear async flows, wizard forms, cooperative scheduling,
undo), and implementation path from the existing TCO trampoline. Updates
TCO essay's continuations section to link to the new essay instead of
dismissing the idea. Fixes "What sx is not" to acknowledge macros + TCO.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Evaluator: add _Thunk + _trampoline for tail-call optimization in
lambdas, components, if/when/cond/case/let/begin. All callers in
html.py, resolver.py, handlers.py, pages.py, jinja_bridge.py, and
query_registry.py unwrap thunks at non-tail positions.
SX docs: update tagline to "s-expressions for the web", rewrite intro
to reflect that SX replaces most JavaScript need, fix "What sx is not"
to acknowledge macros and TCO exist.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
A satirical essay in the style of The Communist Manifesto, recasting
the historic struggle between bourgeoisie and proletariat as the war
between HTML, JS, and CSS — with frameworks as petty-bourgeois lackeys
and s-expressions as the revolutionary force that abolishes the
language distinction itself.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>