Previously defhandler routed to sf-define which tried to evaluate
(&key ...) params as expressions. Now each form has its own spec
with parse-key-params and platform constructors.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
jinja_bridge, helpers, handlers, query_executor now conditionally
import from ref/sx_ref and ref/async_eval_ref when SX_USE_REF=1.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Mirrors bootstrap_js.py pattern — reads the .sx reference spec files
(eval.sx, render.sx, adapter-html.sx) and emits a standalone Python
evaluator module (sx_ref.py) that can be compared against the
hand-written evaluator.py / html.py.
Key transpilation techniques:
- Nested IIFE lambdas for let bindings: (lambda a: body)(val)
- _sx_case helper for case/type dispatch
- Short-circuit and/or via Python ternaries
- Functions with set! emitted as def with _cells dict for mutation
- for-each with inline fn emitted as Python for loops
- Statement-level cond emitted as if/elif/else chains
Passes 27/27 comparison tests against hand-written evaluator.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The sx app is stateless — no database needed. In standalone mode
(SX_STANDALONE=true), the factory now skips register_db() so the app
doesn't crash trying to connect to a non-existent PostgreSQL.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Single Caddy instance handles all domains. sx-web stack joins
externalnet instead of running its own Caddy (port conflict).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- SX_STANDALONE=true env var: no OAuth, no root header, no cross-service
fragments. Same image runs in both rose-ash cooperative and standalone.
- Factory: added no_oauth parameter to create_base_app()
- Standalone layout defcomps skip ~root-header-auto/~root-mobile-auto
- Fixed Dockerfile: was missing sx/sx/ component directory copy
- CI: deploys sx-web swarm stack on main branch when sx changes
- Stack config at ~/sx-web/ (Caddy → sx_docs, Redis)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- New Bootstrappers top-level section with overview index and JS bootstrapper
page that runs bootstrap_js.py and displays both source and generated output
with live script injection (full page load, not SX navigation)
- Essays section: index page with linked cards and summaries, sx-sucks moved
to end of nav, removed "grand tradition" line
- Specs: English prose descriptions alongside all canonical .sx specs, added
Boot/CSSX/Browser spec files to architecture page
- Layout: menu bar nav items wrap instead of overflow, baseline alignment
between label and nav options
- Homepage: added copyright line
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Move rendering logic from Python for-loops building sx_call strings into
SX defcomp components that use map/lambda over data dicts. Python now
serializes display data into plain dicts and passes them via a single
sx_call; the SX layer handles iteration and conditional rendering.
Covers orders (rows, items, calendar, tickets), federation (timeline,
search, actors, profile activities), and blog (cards, pages, filters,
snippets, menu items, tag groups, page search, nav OOB).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Two fixes for sx-browser.js (spec-compiled) vs sx.js (hand-written):
1. CSS meta tag mismatch: initCssTracking read meta[name="sx-css-hash"]
but the page template uses meta[name="sx-css-classes"]. This left
_cssHash empty, causing the server to send ALL CSS as "new" on every
navigation, appending duplicate rules that broke Tailwind responsive
ordering (e.g. menu bar layout).
2. Stale verb info after morph: execute-request used captured verbInfo
from bind time. After morph updated element attributes (e.g. during
OOB nav swap), click handlers still fired with old URLs. Now re-reads
verb info from the element first, matching sx.js behavior.
Also includes: render-expression dispatch in eval.sx, NIL guard for
preload cache in bootstrap_js.py, and helpers.py switched to
sx-browser.js.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Spec file registry (slugs, filenames, titles, descriptions) now lives in
nav-data.sx as SX data definitions. Python helper reduced to pure file I/O
(read-spec-file). Architecture page updated with engine/orchestration split
and dependency graph.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
engine.sx now contains only pure logic: parsing, morph, swap, headers,
retry, target resolution, etc. orchestration.sx contains the browser
wiring: request execution, trigger binding, SSE, boost, post-swap
lifecycle, and init. Dependency is one-way: orchestration → engine.
Bootstrap compiler gains "orchestration" as a separate adapter with
deps on engine+dom. Engine-only builds get morph/swap without the
full browser runtime.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add Architecture intro page explaining the spec's two-layer design
(core language + selectable adapters) with dependency graph
- Split specs into Core (parser, eval, primitives, render) and
Adapters (DOM, HTML, SX wire, SxEngine) overview pages
- Add individual detail pages for all adapter and engine specs
- Update nav with Architecture landing, Core, Adapters, and all
individual spec file links
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Split monolithic render.sx into core (tag registries, shared utils) plus
four adapter .sx files: adapter-html (server HTML strings), adapter-sx
(SX wire format), adapter-dom (browser DOM nodes), and engine (SxEngine
triggers, morphing, swaps). All adapters written in s-expressions with
platform interface declarations for JS bridge functions.
Bootstrap compiler now accepts --adapters flag to emit targeted builds:
-a html → server-only (1108 lines)
-a dom,engine → browser-only (1634 lines)
-a html,sx → server with SX wire (1169 lines)
(default) → all adapters (1800 lines)
Fixes: keyword arg i-counter desync in reduce across all adapters,
render-aware special forms (let/if/when/cond/map) in HTML adapter,
component children double-escaping, ~prefixed macro dispatch.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix highlight() returning SxExpr so syntax-highlighted code renders
as DOM elements instead of leaking SX source text into the page
- Add Specs section that reads and displays canonical SX spec files
from shared/sx/ref/ with syntax highlighting
- Add "The Reflexive Web" essay on SX becoming a complete LISP with
AI as native participant
- Change logo from (<x>) to (<sx>) everywhere
- Unify all backgrounds to bg-stone-100, center code blocks
- Skip component/style cookie cache in dev mode so .sx edits are
visible immediately on refresh without clearing localStorage
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- 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>
Points AI and developers to shared/sx/ref/ as the authoritative
source for SX semantics — eval rules, type system, rendering modes,
component calling convention, and platform interface.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
_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>
bootstrap_js.py reads the reference .sx specification (eval.sx, render.sx)
and transpiles the defined evaluator functions into standalone JavaScript.
The output sx-ref.js is a fully functional SX evaluator bootstrapped from
the s-expression spec, comparable against the hand-written sx.js.
Key features:
- JSEmitter class transpiles SX AST → JS (fn→function, let→IIFE, cond→ternary, etc.)
- Platform interface (types, env ops, primitives) implemented as native JS
- Post-transpilation fixup wraps callLambda to handle both Lambda objects and primitives
- 93/93 tests passing: arithmetic, strings, control flow, closures, HO forms,
components, macros, threading, dict ops, predicates
Fixed during development:
- Bool before int isinstance check (Python bool is subclass of int)
- SX NIL sentinel detection (not Python None)
- Cond style detection (determine Scheme vs Clojure once, not per-pair)
- Predicate null safety (x != null instead of x && to avoid 0-as-falsy in SX)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Dict values (e.g. {:X-CSRFToken csrf}) passed as component kwargs were
not being evaluated through sxEval — symbols stayed unresolved in the DOM.
Also add Cache-Control: no-cache headers for /static/ in dev mode so
browser always fetches fresh JS/CSS without needing hard refresh.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Meta-circular evaluator: the SX language specifying its own semantics.
A thin bootstrap compiler per target (JS, Python, Rust) reads these
.sx files and emits a native evaluator.
Files:
- eval.sx: Core evaluator — type dispatch, special forms, TCO trampoline,
lambda/component/macro invocation, higher-order forms
- primitives.sx: Declarative specification of ~80 built-in pure functions
- render.sx: Three rendering modes (DOM, HTML string, SX wire format)
- parser.sx: Tokenizer, parser, and serializer specification
Platform-specific concerns (DOM ops, async I/O, HTML emission) are
declared as interfaces that each target implements.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
sx-headers attributes now use native SX dict format {:key val} instead of
JSON strings. Eliminates manual JSON string construction in both .sx files
and Python callers.
- sx.js: parse sx-headers/sx-vals as SX dict ({: prefix) with JSON fallback,
add _serializeDict for dict→attribute serialization, fix verbInfo scope in
_doFetch error handler
- html.py: serialize dict attribute values via SX serialize() not str()
- All .sx files: {:X-CSRFToken csrf} replaces (str "{\"X-CSRFToken\": ...}")
- All Python callers: {"X-CSRFToken": csrf} dict replaces f-string JSON
- Blog like: extract ~blog-like-toggle, fix POST returning wrong component,
fix emoji escapes in .sx (parser has no \U support), fix card :hx-headers
keyword mismatch, wrap sx_content in SxExpr for evaluation
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- market/sx/layouts.sx: Update ~market-header-auto macro to build nav
from data fields via ~market-desktop-nav-from-data instead of
expecting pre-built "desktop-nav" SxExpr (removed in Phase 9)
- shared/sx/primitives_io.py: Import _market_header_data instead of
deleted _desktop_category_nav_sx, return individual data fields
- sx/sx/layouts.sx: Fix ~sx-section-layout-oob to use ~oob-header-sx
for inserting sub-row into always-existing container div
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Continues the pattern of eliminating Python sx_call tree-building in favour
of data-driven .sx defcomps. POST/PUT/DELETE routes now pass plain data
(dicts, lists, scalars) and let .sx handle iteration, conditionals, and
layout via map/let/when/if. Single response components wrap OOB swaps.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Convert all 14 events page helpers from returning sx_call() strings
to returning data dicts. Defpage expressions compose SX components
with data bindings using map/fn/if/when.
Complex sub-panels (entry tickets config, buy form, posts panel,
options buttons, entry nav menu) returned as SxExpr from existing
render functions which remain for HTMX handler use.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Convert 5 market page helpers from returning sx_call() strings to
returning data dicts. Defpages now use :data + :content pattern.
Admin panel uses inline map/fn for CRUD item composition.
Removed market-admin-content helper (placeholder inlined in defpage).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Convert all 8 blog page helpers from returning sx_call() strings to
returning data dicts. Defpages now use :data + :content pattern:
helpers load data, SX composes markup. Newsletter options and footer
badges composed inline with map/fn in defpage expressions.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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>
Nav data, section nav, example content, reference table builders, and
all slug dispatch now live in .sx files. Python helpers reduced to
data-only returns (highlight, primitives-data, reference-data,
attr-detail-data). Deleted essays.py and utils.py entirely.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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>
- render_orders_rows: Python loop building row-pairs → ~cart-orders-rows-content
defcomp that maps over order data and handles pagination sentinel
- render_checkout_error_page: conditional order badge composition →
~cart-checkout-error-from-data defcomp
- Remove unused SxExpr import
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Settings form: ~135 lines raw HTML → ~blog-settings-form-content defcomp
- Data introspection: ~110 lines raw HTML → ~blog-data-table-content with
recursive ~blog-data-model-content defcomps, Python extracts ORM data only
- Preview: sx_call composition → ~blog-preview-content defcomp
- Entries browser: ~65 lines raw HTML → ~blog-entries-browser-content +
~blog-calendar-browser-item + ~blog-associated-entries-from-data defcomps
- Editor panels: sx_call composition in both helpers.py and renders.py →
~blog-editor-content and ~blog-edit-content composition defcomps
- renders.py: 178 → 25 lines (87% reduction)
- routes.py _render_associated_entries: data extraction → single sx_call
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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>