Events: route imports now point to specific sub-modules (entries,
tickets, slots) instead of all going through renders.py. Merged
layouts into helpers.py. __init__.py now 20 lines.
SX Docs: moved dispatchers from helpers.py into essays.py, cleaned
up __init__.py to 24 lines.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Plain Python lists (e.g. from map) were serialized as ((item1) (item2))
which re-parses as a function application, causing "Not callable: _RawHTML"
when the head gets fully evaluated. Keyword list values now wrap as
(<> item1 item2) fragments; positional list children are flattened.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Move ~1670 lines to 6 sub-modules: renders.py, layouts.py, helpers.py,
cards.py, filters.py, utils.py. Update all bp route imports.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Move render functions, layouts, helpers, and utils from __init__.py
to sub-modules (renders.py, layouts.py, helpers.py, utils.py).
Update all bp route imports to point at sub-modules directly.
Each __init__.py is now ≤20 lines of setup + registration.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- 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>