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>
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>
All app-level templates have been replaced by native sx component builders
and defpage declarative routes. Removes ~15,200 lines of dead HTML.
Kept: shared/browser templates (errors, ap_social, macros, root layout),
account + federation _email/magic_link, federation profile.html chain.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace Python GET page handlers with declarative defpage definitions in .sx
files across all 8 apps (sx docs, orders, account, market, cart, federation,
events, blog). Each app now has sxc/pages/ with setup functions, layout
registrations, page helpers, and .sx defpage declarations.
Core infrastructure: add g I/O primitive, PageDef support for auth/layout/
data/content/filter/aside/menu slots, post_author auth level, and custom
layout registration. Remove ~1400 lines of render_*_page/render_*_oob
boilerplate. Update all endpoint references in routes, sx_components, and
templates to defpage_* naming.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Phase 1: Wire 16 events routes to existing sx render functions
- slot, slots, ticket_types, ticket_type, calendar_entries,
calendar_entry, calendar_entry/admin
Phase 2: Orders checkout return (2 calls)
- New orders/sx/checkout.sx with return page components
- New render_checkout_return_page() in orders/sx/sx_components.py
Phase 3: Blog menu items (3 calls)
- New blog/sx/menu_items.sx with search result components
- New render_menu_item_form() and render_page_search_results()
in blog/sx/sx_components.py
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Rename all sexp directories, files, identifiers, and references to sx.
artdag/ excluded (separate media processing DSL).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Remove old raw! layout components (~app-head, ~app-layout, ~oob-response,
~header-row, ~menu-row, ~oob-header, ~header-child) from layout.sexp
- Convert nav-tree fragment from Jinja HTML to sexp source, fixing the
"Unexpected character: ." parse error caused by HTML leaking into sexp
- Add _as_sexp() helper to safely coerce HTML fragments to ~rich-text
- Fix federation/sexp/search.sexpr extra closing paren
- Remove dead _html() wrappers from blog and account sexp_components
- Remove stale render import from cart sexp_components
- Add dev_watcher.py to auto-reload on .sexp/.sexpr/.js/.css changes
- Add test_parse_all.py to parse-check all 59 sexpr/sexp files
- Fix test assertions for sx- attribute prefix (was hx-)
- Add sexp.js version logging for cache debugging
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Server sends sexp source text, client (sexp.js) renders everything
- SexpExpr marker class for nested sexp composition in serialize()
- sexp_page() HTML shell with data-mount="body" for full page loads
- sexp_response() returns text/sexp for OOB/partial responses
- ~app-body layout component replaces ~app-layout (no raw!)
- ~rich-text is the only component using raw! (for CMS HTML content)
- Fragment endpoints return text/sexp, auto-wrapped in SexpExpr
- All _*_html() helpers converted to _*_sexp() returning sexp source
- Head auto-hoist: sexp.js moves meta/title/link/script[ld+json]
from rendered body to document.head automatically
- Unknown components render warning box instead of crashing page
- Component kwargs preserve AST for lazy rendering (fixes <> in kwargs)
- Fix unterminated paren in events/sexp/tickets.sexpr
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Run tests for all 10 services via per-service pytest subprocesses
- Group results by service with section headers
- Clickable summary cards filter by outcome (passed/failed/errors/skipped)
- Service filter nav using ~nav-link buttons in menu bar
- Full menu integration: ~header-row + ~header-child + ~menu-row
- Show logo image via cart-mini rendering
- Mount full service directories in docker-compose for test access
- Add 24 unit test files across 9 services
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace all 676 inline sexp() string calls across 7 services with
render(component_name, **kwargs) calls backed by 46 external .sexpr
component definition files (587 defcomps total).
- Add render() function to shared/sexp/jinja_bridge.py
- Add load_service_components() helper and update load_sexp_dir() for *.sexpr
- Update parser keyword regex to support HTMX hx-on::event syntax
- Convert remaining inline HTML in route files to render() calls
- Add shared/sexp/templates/misc.sexp for cross-service utility components
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Eliminates all f-string HTML from the remaining three services,
completing the migration of all sexp_components.py files to the
s-expression rendering system.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Disable htmx selfRequestsOnly, add CORS headers for *.rose-ash.com
- Remove same-origin guards from ~menu-row and ~nav-link htmx attrs
- Convert ~app-layout from string-concatenated HTML to pure sexp tree
- Extract ~app-head component, replace ~app-shell with inline structure
- Convert hamburger SVG from Python HTML constant to ~hamburger sexp component
- Fix cross-domain fragment URLs (events_url, market_url)
- Fix starts-with? primitive to handle nil values
- Fix duplicate admin menu rows on OOB swaps
- Add calendar admin nav links (slots, description)
- Convert slots page from Jinja to sexp rendering
- Disable page caching in development mode
- Backfill migration to clean orphaned container_relations
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Migrate ~52 GET route handlers across all 7 services from Jinja
render_template() to s-expression component rendering. Each service
gets a sexp_components.py with page/oob/cards render functions.
- Add per-service sexp_components.py (account, blog, cart, events,
federation, market, orders) with full page, OOB, and pagination
card rendering
- Add shared/sexp/helpers.py with call_url, root_header_html,
full_page, oob_page utilities
- Update all GET routes to use get_template_context() + render fns
- Fix get_template_context() to inject Jinja globals (URL helpers)
- Add qs_filter to base_context for sexp filter URL building
- Mount sexp_components.py in docker-compose.dev.yml for all services
- Import sexp_components in app.py for Hypercorn --reload watching
- Fix route_prefix import (shared.utils not shared.infrastructure.urls)
- Fix federation choose-username missing actor in context
- Fix market page_markets missing post in context
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add 9 new shared s-expression components (cart-mini, auth-menu,
account-nav-item, calendar-entry-nav, calendar-link-nav, market-link-nav,
post-card, base-shell, error-page) and wire them into all fragment route
handlers. 404/403 error pages now render entirely via s-expressions as a
full-page proof-of-concept, with Jinja fallback on failure.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Phase 1 - Relations service (internal): owns ContainerRelation, exposes
get-children data + attach/detach-child actions. Retargeted events, blog,
market callers from cart to relations.
Phase 2 - Likes service (internal): unified Like model replaces ProductLike
and PostLike with generic target_type/target_slug/target_id. Exposes
is-liked, liked-slugs, liked-ids data + toggle action.
Phase 3 - PageConfig → blog: moved ownership to blog with direct DB queries,
removed proxy endpoints from cart.
Phase 4 - Orders service (public): owns Order/OrderItem + SumUp checkout
flow. Cart checkout now delegates to orders via create-order action.
Webhook/return routes and reconciliation moved to orders.
Phase 5 - Infrastructure: docker-compose, deploy.sh, Dockerfiles updated
for all 3 new services. Added orders_url helper and factory model imports.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>