Commit Graph

27 Commits

Author SHA1 Message Date
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
31ace8768e Merge scheme-forms into macros: named let, letrec, dynamic-wind, eq?/eqv?/equal?
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 01:16:16 +00:00
f34e55aa9b Add Scheme forms: named let, letrec, dynamic-wind, three-tier equality
Spec (eval.sx, primitives.sx):
- Named let: (let loop ((i 0)) body) — self-recursive lambda with TCO
- letrec: mutually recursive local bindings with closure patching
- dynamic-wind: entry/exit guards with wind stack for future continuations
- eq?/eqv?/equal?: identity, atom-value, and deep structural equality

Implementation (evaluator.py, async_eval.py, primitives.py):
- Both sync and async evaluators implement all four forms
- 33 new tests covering all forms including TCO at 10k depth

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 01:11:31 +00:00
102a27e845 Implement delimited continuations (shift/reset) across all evaluators
Bootstrap shift/reset to both Python and JS targets. The implementation
uses exception-based capture with re-evaluation: reset wraps in try/catch
for ShiftSignal, shift raises to the nearest reset, and continuation
invocation pushes a resume value and re-evaluates the body.

- Add Continuation type and _ShiftSignal to shared/sx/types.py
- Add sf_reset/sf_shift to hand-written evaluator.py
- Add async versions to async_eval.py
- Add shift/reset dispatch to eval.sx spec
- Bootstrap to Python: FIXUPS_PY with sf_reset/sf_shift, regenerate sx_ref.py
- Bootstrap to JS: Continuation/ShiftSignal types, sfReset/sfShift in fixups
- Add continuation? primitive to both bootstrappers and primitives.sx
- Allow callables (including Continuation) in hand-written HO map
- 44 unit tests (22 per evaluator) covering: passthrough, abort, invoke,
  double invoke, predicate, stored continuation, nested reset, practical patterns
- Update continuations essay to reflect implemented status with examples

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 00:58:50 +00:00
6fa843016b Gate server-side component expansion with contextvar, fix nth arg order, add GEB essay and manifesto links
- Add _expand_components contextvar so _aser only expands components
  during page slot evaluation (fixes highlight on examples, avoids
  breaking fragment responses)
- Fix nth arg order (nth coll n) in docs.sx, examples.sx (delete-row,
  edit-row, bulk-update)
- Add "Godel, Escher, Bach and SX" essay with Wikipedia links
- Update SX Manifesto: new authors, Wikipedia links throughout,
  remove Marx/Engels link

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 11:03:50 +00:00
dea4f52454 Expand known components server-side in _aser to fix nested highlight calls
_aser previously serialized all ~component calls for client rendering.
Components whose bodies call Python-only functions (e.g. highlight) would
fail on the client with "Undefined symbol". Now _aser expands components
that are defined in the env via _aser_component, producing SX wire format
with tag-level bodies inlined. Unknown components still serialize as-is.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 10:20:24 +00:00
aed4c03537 Fix highlight undefined symbol by expanding component results server-side
When defpage content expressions use case/if branches that resolve to
component calls (e.g. `(case slug "intro" (~docs-intro-content) ...)`),
_aser serializes them for the client. Components containing Python-only
helpers like `highlight` then fail with "Undefined symbol" on the client.

Add _maybe_expand_component_result() which detects when the evaluated
result (SxExpr or string) is a component call starting with "(~" and
re-parses + expands it through async_eval_slot_to_sx server-side.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 01:52:45 +00:00
b15025befd Fix highlight undefined symbol by expanding component strings server-side
Page helpers returning SX component call strings (e.g. "(~docs-intro-content)")
were sent to the client unexpanded. Components containing Python-only helpers
like `highlight` then failed with "Undefined symbol" on the client. Now
async_eval_slot_to_sx re-parses and expands these strings server-side.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 01:45:04 +00:00
4298d5be16 Fix sx docs pages leaking raw s-expressions and missing sub-row
Three issues fixed:

- async_eval_slot_to_sx (and async_eval_to_sx) was calling serialize()
  on plain strings returned by page helpers, quoting them as literals
  instead of treating them as sx source. Added str check to wrap
  directly in SxExpr.

- _render_to_sx_with_env passed layout kwargs only as env free
  variables, but _aser_component defaults all declared params to NIL
  regardless of env. Now builds the AST with extra_env entries as
  keyword args so they bind through normal param mechanism.

- _nav_items_sx returned plain str; changed to SxExpr so nav fragments
  serialize unquoted when passed as layout kwargs.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 23:02:23 +00:00
278ae3e8f6 Make SxExpr a str subclass, sx_call/render functions return SxExpr
SxExpr is now a str subclass so it works everywhere a plain string
does (join, isinstance, f-strings) while serialize() still emits it
unquoted. sx_call() and all internal render functions (_render_to_sx,
async_eval_to_sx, etc.) return SxExpr, eliminating the "forgot to
wrap" bug class that caused the sx_content leak and list serialization
bugs.

- Phase 0: SxExpr(str) with .source property, __add__/__radd__
- Phase 1: sx_call returns SxExpr (drop-in, all 200+ sites unchanged)
- Phase 2: async_eval_to_sx, async_eval_slot_to_sx, _render_to_sx,
  mobile_menu_sx return SxExpr; remove isinstance(str) workaround
- Phase 3: Remove ~150 redundant SxExpr() wrappings across 45 files
- Phase 4: serialize() docstring, handler return docs, ;; returns: sx

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 21:47:00 +00:00
0456b3d25c Fix _aser_call and sx_call list serialization: use (list ...) for data arrays
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 2m5s
Data lists (dicts, strings, numbers) were wrapped in (<> ...) fragments
which the client rendered as empty DocumentFragments instead of iterable
arrays. This broke map/filter over cards, tag_groups, and authors in
blog index and similar components.

- _aser_call: data lists → (list ...), rendered content (SxExpr) → (<> ...)
- sx_call: all list kwargs → (list ...)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 20:11:11 +00:00
57e0d0c341 Fix defmacro expansion in _aser: check for macros before serializing ~components
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 1m39s
The ~component check in _aser immediately serialized all names starting
with ~ as unexpanded component calls. This meant defmacro definitions
like ~root-header-auto were sent to the client unexpanded, causing
"Undefined symbol: root-header-ctx" errors since IO primitives only
exist server-side. Now checks env for Macro instances first.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 18:29:14 +00:00
8772d59d84 Fix _aser_call list serialization causing EvalError on re-parse
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>
2026-03-04 17:12:17 +00:00
76bc293faa Document SX rendering pipeline, add missing sx_docs mount, loud error on missing component
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 3m18s
- 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>
2026-03-04 16:48:01 +00:00
2258a0790b Merge branch 'worktree-macros-essays' into macros 2026-03-04 15:49:02 +00:00
527c4186ee Fix _aser_component: evaluate kwargs with _aser not async_eval
_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>
2026-03-04 15:48:57 +00:00
a84916e82f Fix filter/map tag disambiguation inside SVG context without keyword attrs
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 3m19s
(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>
2026-03-04 14:03:02 +00:00
f5c266e785 Fix custom element check: require keyword arg to disambiguate from variables
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 3m14s
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>
2026-03-04 13:55:36 +00:00
d551806976 Add SVG namespace auto-detection, custom elements, html: prefix, and fix filter/map tag collision
Some checks failed
Build and Deploy / build-and-deploy (push) Has been cancelled
- 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>
2026-03-04 13:53:08 +00:00
19d59f5f4b Implement CSSX Phase 2: native SX style primitives
Some checks failed
Build and Deploy / build-and-deploy (push) Has been cancelled
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>
2026-03-04 12:47:51 +00:00
e72f7485f4 Add TCO trampolining to async evaluator and sx.js client
Some checks failed
Build and Deploy / build-and-deploy (push) Has been cancelled
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>
2026-03-04 10:53:16 +00:00
50b33ab08e Fix page helper results being quoted as string literals in defpage slots
Page helpers return SX source strings from render_to_sx(), but _aser's
serialize() was wrapping them in double quotes. In async_eval_slot_to_sx,
pass string results through directly since they're already SX source.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 01:43:00 +00:00
400667b15a Delete account/sx/sx_components.py — all rendering now in .sx
Phase 1 of zero-Python rendering: account service.

- Auth pages (login, device, check-email) use _render_auth_page() helper
  calling render_to_sx() + full_page_sx() directly in routes
- Newsletter toggle POST renders inline via render_to_sx()
- Newsletter page helper returns data dict; defpage :data slot fetches,
  :content slot renders via ~account-newsletters-content defcomp
- Fragment page uses (frag ...) IO primitive directly in .sx
- Defpage _eval_slot now uses async_eval_slot_to_sx which expands
  component bodies server-side (executing IO) but serializes tags as SX
- Fix pre-existing OOB ParseError: _eval_slot was producing HTML instead
  of s-expressions for component content slots
- Fix market url_for endpoint: defpage_market_home (app-level, not blueprint)
- Fix events calendar nav: wrap multiple SX parts in fragment

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 01:16:01 +00:00
293f7713d6 Auto-mount defpages: eliminate Python route stubs across all 9 services
Some checks failed
Build and Deploy / build-and-deploy (push) Failing after 16s
Defpages are now declared with absolute paths in .sx files and auto-mounted
directly on the Quart app, removing ~850 lines of blueprint mount_pages calls,
before_request hooks, and g.* wrapper boilerplate. A new page = one defpage
declaration, nothing else.

Infrastructure:
- async_eval awaits coroutine results from callable dispatch
- auto_mount_pages() mounts all registered defpages on the app
- g._defpage_ctx pattern passes helper data to layout context

Migrated: sx, account, orders, federation, cart, market, events, blog

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 19:03:15 +00:00
5578923242 Fix defhandler to produce sx wire format instead of HTML
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 3m28s
execute_handler was using async_render() which renders all the way to
HTML. Fragment providers need to return sx source (s-expression strings)
that consuming apps parse and render client-side.

Added async_eval_to_sx() — a new execution mode that evaluates I/O
primitives and control flow but serializes component/tag calls as sx
source instead of rendering them to HTML. This mirrors how the old
Python handlers used sx_call() to build sx strings.

Also fixed: _ASER_FORMS checked after HTML_TAGS, causing "map" (which
is both an HTML tag and an sx special form) to be serialized as a tag
instead of evaluated. Moved _ASER_FORMS check before HTML_TAGS.

Also fixed: empty? primitive now handles non-len()-able types gracefully.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 01:00:00 +00:00
9754b892d6 Fix double-escaping when render forms (<>, HTML tags) appear in eval position
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 4m30s
Return _RawHTML wrapper so pre-rendered HTML in let bindings isn't
escaped when used in render context.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 00:31:31 +00:00
ab75e505a8 Add macros, declarative handlers (defhandler), and convert all fragment routes to sx
Some checks failed
Build and Deploy / build-and-deploy (push) Has been cancelled
Phase 1 — Macros: defmacro + quasiquote syntax (`, ,, ,@) in parser,
evaluator, HTML renderer, and JS mirror. Macro type, expansion, and
round-trip serialization.

Phase 2 — Expanded primitives: app-url, url-for, asset-url, config,
format-date, parse-int (pure); service, request-arg, request-path,
nav-tree, get-children (I/O); jinja-global, relations-from (pure).
Updated _io_service to accept (service "registry-name" "method" :kwargs)
with auto kebab→snake conversion. DTO-to-dict now expands datetime fields
into year/month/day convenience keys. Tuple returns converted to lists.

Phase 3 — Declarative handlers: HandlerDef type, defhandler special form,
handler registry (service → name → HandlerDef), async evaluator+renderer
(async_eval.py) that awaits I/O primitives inline within control flow.
Handler loading from .sx files, execute_handler, blueprint factory.

Phase 4 — Convert all fragment routes: 13 Python fragment handlers across
8 services replaced with declarative .sx handler files. All routes.py
simplified to uniform sx dispatch pattern. Two Jinja HTML handlers
(events/container-cards, events/account-page) kept as Python.

New files: shared/sx/async_eval.py, shared/sx/handlers.py,
shared/sx/tests/test_handlers.py, plus 13 handler .sx files under
{service}/sx/handlers/. MarketService.product_by_slug() added.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 00:22:18 +00:00