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>
Cover @keyframes (defkeyframes special form + built-in animations),
@container queries, dynamic atom construction (no server round-trip
since client has full dictionary), arbitrary bracket values (w-[347px]),
and inline style fallback for truly unique data-driven values.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Document the design for native s-expression style primitives
(css :flex :gap-4 ...) to replace Tailwind CSS strings with first-class
SX expressions. Covers style dictionary, resolver, delivery/caching
(localStorage like components), server-side session tracking, and
migration tooling.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The service registry uses __getattr__, so .get() is interpreted
as looking up a service named "get". Use attribute access instead.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Move remaining 19 rendering functions from the 2487-line
sx_components.py to their direct callers:
- menu_items/routes.py: menu item form, page search, nav OOB
- post/admin/routes.py: calendar view, associated entries, nav OOB
- sxc/pages/__init__.py: editor panel, post data inspector, preview,
entries browser, settings form, edit page editor
- bp/blog/routes.py: inline new post page composition
Move load_service_components() call from sx_components module-level
to setup_blog_pages() so .sx files still load at startup.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Move _blog_header_sx, _settings_header_sx, _settings_nav_sx, and
_sub_settings_header_sx into the layout module as local helpers.
Eliminates 14 imports from sx_components.py for the layout system.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Home page: inline shared helpers, render_to_sx("blog-home-main")
- Post detail: new ~blog-post-detail-content defcomp with data from service
- Like toggle: call render_to_sx("market-like-toggle-button") directly
- Add post_meta_data() and post_detail_data() to BlogPageService
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
BlogPageService.index_data() assembles all data (cards, filters, actions)
and 7 new .sx defcomps handle rendering: main content, aside, filter,
actions, tag groups filter, authors filter, and sentinel.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The old sx_components.py used os.path.dirname(__file__) to resolve
the app root. When it was deleted, the replacement call in app.py
used the string "cart" which resolves to /app/cart/ (alembic only),
not /app/ where the sx/ directory lives. Use Path(__file__).parent.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Inside the likes container the model is at models.like not
likes.models.like — the container's Python path is /app.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The module-level import of likes.models.like.Like caused ImportError
in non-likes services that register SqlLikesService. Move the import
into a lazy helper called per-method.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The sentinel was written to shared/_reload_sentinel.py but shared/ is
volume-mounted as root:root, so appuser can't create files there.
Move sentinel to /app/_reload_sentinel.py which is owned by appuser
and still under Hypercorn's --reload watch path.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Use dto_to_dict() from shared/contracts/dtos.py for dataclass
serialization instead of raw dataclasses.asdict(). This ensures
datetimes are converted to ISO format strings (not RFC 2822 from
jsonify), matching what dto_from_dict() expects on the receiving end.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The defquery conversion routes inter-service results through
_dto_to_dict which checked __dict__ (absent on slots dataclasses),
producing {"value": obj} instead of proper field dicts. This broke
TicketDTO deserialization in the cart app. Check __dataclass_fields__
first and use dataclasses.asdict() for correct serialization.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The inter-service data layer (fetch_data/call_action) was the least
structured part of the codebase — Python _handlers dicts with ad-hoc
param extraction scattered across 16 route files. This replaces them
with declarative .sx query/action definitions that make the entire
inter-service protocol self-describing and greppable.
Infrastructure:
- defquery/defaction special forms in the sx evaluator
- Query/action registry with load, lookup, and schema introspection
- Query executor using async_eval with I/O primitives
- Blueprint factories (create_data_blueprint/create_action_blueprint)
with sx-first dispatch and Python fallback
- /internal/schema endpoint on every service
- parse-datetime and split-ids primitives for type coercion
Service extractions:
- LikesService (toggle, is_liked, liked_slugs, liked_ids)
- PageConfigService (ensure, get_by_container, get_by_id, get_batch, update)
- RelationsService (wraps module-level functions)
- AccountDataService (user_by_email, newsletters)
- CartItemsService, MarketDataService (raw SQLAlchemy lookups)
50 of 54 handlers converted to sx, 4 Python fallbacks remain
(ghost-sync/push-member, clear-cart-for-order, create-order).
Net: -1,383 lines Python, +251 lines modified.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Convert 6 blog settings pages (settings-home, cache, snippets, menu-items,
tag-groups, tag-group-edit) from Python page helpers to .sx defpages with
(service "blog-page" ...) IO primitives. Create data-driven defcomps that
handle iteration via (map ...) instead of Python loops.
Post-related page helpers (editor, post-admin/data/preview/entries/settings/edit)
remain as Python helpers — they depend on _ensure_post_data and sx_components
rendering functions that need separate conversion.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix undefined symbol 'length' → use 'len' primitive in orders.sx
- Add _RawHTML handling in serialize() — wraps as (raw! "...") for SX wire format
instead of falling through to repr() which produced unparseable symbol names
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Orders defpages now fetch data via (service ...) and generate URLs via
(url-for ...) and (route-prefix) directly in .sx. No Python middleman.
- Add url-for, route-prefix IO primitives to shared/sx/primitives_io.py
- Add generic register()/\_\_getattr\_\_ to ServiceRegistry for dynamic services
- Create OrdersPageService with list_page_data/detail_page_data methods
- Rewrite orders.sx defpages to use IO primitives + defcomp calls
- Remove ~320 lines of Python page helpers from orders/sxc/pages/__init__.py
- Convert :data env merge to use kebab-case keys for SX symbol access
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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>
Fragment responses with text/sx content-type but empty body create
SxExpr(""), which is truthy but fails to parse. Handle this by
returning None from _as_sx for empty SxExpr sources, and treating
empty SxExpr as NIL in _build_component_ast.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The sxc/ directories (defpages, layouts, page helpers) were not
bind-mounted, so dev containers used stale code from the Docker image.
This caused the orders.defpage_order_detail BuildError since the
container had old sxc/pages/__init__.py without the fix.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Move all render functions (orders page/rows/oob, order detail/oob,
checkout error, payments panel), header helpers, and serializers from
cart/sx/sx_components.py into cart/sxc/pages/__init__.py. Update all
route imports from sx.sx_components to sxc.pages. Replace
import sx.sx_components in app.py with load_service_components("cart").
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
defpages mounted via auto_mount_pages() register endpoints without
blueprint prefix. Fix url_for("orders.defpage_*") → url_for("defpage_*").
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Phase 2 (Orders):
- Checkout error/return renders moved directly into route handlers
- Removed orphaned test_sx_helpers.py
Phase 3 (Federation):
- Auth pages use _render_social_auth_page() helper in routes
- Choose-username render inlined into identity routes
- Timeline/search/follow/interaction renders inlined into social routes
using serializers imported from sxc.pages
- Added _social_page() to sxc/pages/__init__.py for shared use
- Home page renders inline in app.py
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Python no longer generates s-expression strings. All SX rendering now
goes through render_to_sx() which builds AST from native Python values
and evaluates via async_eval_to_sx() — no SX string literals in Python.
- Add render_to_sx()/render_to_html() infrastructure in shared/sx/helpers.py
- Add (abort status msg) IO primitive in shared/sx/primitives_io.py
- Convert all 9 services: ~650 sx_call() invocations replaced
- Convert shared helpers (root_header_sx, full_page_sx, etc.) to async
- Fix likes service import bug (likes.models → models)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Re-read verb URL from element attributes at execution time so morphed
nav links navigate to the correct destination
- Reset retry backoff on fresh requests; skip error modal when sx-retry
handles the failure
- Strip attribute selectors in CSS registry so aria-selected:* classes
resolve correctly for on-demand CSS
- Add @css annotations for dynamic aria-selected variant classes
- Add SX docs integration test suite (102 tests)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add dict recursion to _convert_result for service methods returning dict[K, list[DTO]]
- New container-cards.sx: parses post_ids/slugs, calls confirmed-entries-for-posts, emits card-widget markers
- New account-page.sx: dispatches on slug for tickets/bookings panels with status pills and empty states
- Fix blog _parse_card_fragments to handle SxExpr via str() cast
- Remove events Python fragment handlers and simplify app.py to plain auto_mount
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Fragment read API is now fully declarative — every handler is a defhandler
s-expression dispatched through one shared auto_mount_fragment_handlers()
function. Replaces 8 near-identical blueprint files (~35 lines each) with
a single function call per service. Events Python handlers (container-cards,
account-page) extracted to a standalone module.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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>
Documents why sx stays server-driven by default, maps React features
to sx equivalents, and defines targeted escape hatches for the few
interactions that genuinely need client-side state.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>