Commit Graph

146 Commits

Author SHA1 Message Date
015469e401 Fix Undefined symbol: div — delegate HTML tags to renderDOM in sxEval
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>
2026-03-04 16:04:29 +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
4939884f25 Add debug logging for Undefined symbol errors in sx.js
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>
2026-03-04 15:45:32 +00:00
715df11f82 Phase 8-9: Convert events + sx layouts, add missing JS primitives
Events (Phase 8):
- Create events/sx/layouts.sx with 18 defcomps for all 9 layout pairs
- Convert all layout functions to render_to_sx_with_env + _ctx_to_env
- Convert 5 render functions to eliminate root_header_sx calls
- Zero root_header_sx references remain in events

SX Docs (Phase 9):
- Create sx/sx/layouts.sx with layout defcomps
- Convert 4 layout functions to render_to_sx_with_env + _ctx_to_env

JS primitives:
- Add slice, replace, upper, lower, trim, escape, strip-tags, split,
  join, pluralize, clamp, parse-int, format-decimal, format-date,
  parse-datetime, split-ids, starts-with?, ends-with?, dissoc, into
- Fix contains? for strings (indexOf instead of in operator)
- Prevents "Undefined symbol" errors when .sx expressions using
  server-side primitives are evaluated client-side

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 15:27:41 +00:00
121aa30f32 Fix contains? primitive to handle strings in both JS and Python
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>
2026-03-04 15:15:50 +00:00
a30e7228d8 Pass all values as &key args to ~root-header/~root-mobile
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>
2026-03-04 14:50:14 +00:00
e4bfd46c48 Fix register_sx_layout: use async_eval_slot_to_sx to expand component bodies
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>
2026-03-04 14:48:43 +00:00
45c5e4a0db Add register_sx_layout infrastructure, convert account/federation/orders
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>
2026-03-04 14:39:53 +00:00
213421516e Add SSE, response headers, view transitions, and 5 new sx attributes
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>
2026-03-04 11:55:21 +00:00
b51b050dda Delete last sx_components.py files: relations + test (phase 9)
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>
2026-03-04 11:26:58 +00:00
31a8b755d9 Implement 7 missing sx attributes: boost, preload, preserve, indicator, validate, ignore, optimistic
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>
2026-03-04 11:18:31 +00:00
e72f7485f4 Add TCO trampolining to async evaluator and sx.js client
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
5069072715 Add TCO to evaluator, update SX docs messaging
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>
2026-03-04 10:31:31 +00:00
8e16cc459a Fix Like model import path in SqlLikesService
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>
2026-03-04 08:52:51 +00:00
336a4ad9a1 Lazy-import Like model in SqlLikesService
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>
2026-03-04 08:51:11 +00:00
d6f3250a77 Fix dev_watcher sentinel path for container permissions
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>
2026-03-04 08:37:29 +00:00
486ab834de Fix datetime serialization in _dto_to_dict
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>
2026-03-04 08:28:47 +00:00
41e803335a Fix _dto_to_dict for slots=True dataclasses
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>
2026-03-04 08:24:40 +00:00
1f36987f77 Replace inter-service _handlers dicts with declarative sx defquery/defaction
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>
2026-03-04 08:13:50 +00:00
fb8f115acb Fix orders defpage: length→len primitive, handle _RawHTML in serialize()
- 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>
2026-03-04 01:55:32 +00:00
63b895afd8 Eliminate Python page helpers from orders — pure .sx defpages with IO primitives
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>
2026-03-04 01:50:15 +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
bd314a0be7 Guard against empty SxExpr in _as_sx and _build_component_ast
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>
2026-03-04 01:37:27 +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
e085fe43b4 Replace sx_call() with render_to_sx() across all services
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>
2026-03-04 00:08:33 +00:00
0554f8a113 Refactor sx.js: extract string renderer, deduplicate helpers, remove dead code
Extract Node-only string renderer (renderToString, renderStr, etc.) to
sx-test.js. Add shared helpers (_processOOBSwaps, _postSwap, _processBindings,
_evalCond, _logParseError) replacing duplicated logic. Remove dead isTruthy
and _sxCssKnown class-list fallback. Compress section banners. sx.js goes
from 2652 to 2279 lines (-14%) with zero browser-side behavior change.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 23:00:58 +00:00
193578ef88 Move SX construction from Python to .sx defcomps (phases 0-4)
Eliminate Python s-expression string building across account, orders,
federation, and cart services. Visual rendering logic now lives entirely
in .sx defcomp components; Python files contain only data serialization,
header/layout wiring, and thin wrappers that call defcomps.

Phase 0: Shared DRY extraction — auth/orders header defcomps, format-decimal/
pluralize/escape/route-prefix primitives.
Phase 1: Account — dashboard, newsletters, login/device/check-email content.
Phase 2: Orders — order list, detail, filter, checkout return assembled defcomps.
Phase 3: Federation — social nav, post cards, timeline, search, actors,
notifications, compose, profile assembled defcomps.
Phase 4: Cart — overview, page cart items/calendar/tickets/summary, admin,
payments assembled defcomps; orders rendering reuses Phase 2 shared defcomps.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 22:36:34 +00:00
03f0929fdf Fix SX nav morphing, retry error modal, and aria-selected CSS extraction
- 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>
2026-03-03 20:37:17 +00:00
f551fc7453 Convert last Python fragment handlers to SX defhandlers: 100% declarative fragment API
- 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>
2026-03-03 19:42:19 +00:00
e30cb0a992 Auto-mount fragment handlers: eliminate fragment blueprint boilerplate across all 8 services
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>
2026-03-03 19:13:15 +00:00
293f7713d6 Auto-mount defpages: eliminate Python route stubs across all 9 services
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
0a81a2af01 Convert social and federation profile from Jinja to SX rendering
Add primitives (replace, strip-tags, slice, csrf-token), convert all
social blueprint routes and federation profile to SX content builders,
delete 12 unused Jinja templates and social_lite layout.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 17:43:47 +00:00
a98354c0f0 Fix duplicate headers on HTMX nav, editor content loading, and double mount
- Nest admin header inside post-header-child (layouts.py/helpers.py) so
  full-page DOM matches OOB swap structure, eliminating duplicate headers
- Clear post-header-child on post layout OOB to remove stale admin rows
- Read SX initial content from #sx-content-input instead of
  window.__SX_INITIAL__ to avoid escaping issues through SX pipeline
- Fix client-side SX parser RE_STRING to handle escaped newlines
- Clear root element in SxEditor.mount() to prevent double content on
  HTMX re-mount
- Remove unused ~blog-editor-sx-initial component

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 16:27:47 +00:00
df8b19ccb8 Convert post edit form from raw HTML to SX expressions
Replace _post_edit_content_sx raw HTML builder with sx_call() pattern
matching render_editor_panel. Add ~blog-editor-edit-form,
~blog-editor-publish-js, ~blog-editor-sx-initial components to
editor.sx. Fixes (~sx-editor-styles) rendering as literal text on
the edit page.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 15:53:50 +00:00
544892edd9 Delete 391 dead Jinja templates replaced by sx_components/defpage
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>
2026-03-03 15:10:56 +00:00
c243d17eeb Migrate all apps to defpage declarative page routes
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>
2026-03-03 14:52:34 +00:00
5b4cacaf19 Fix NIL leaking into Python service calls, add mobile navigation menu
Strip NIL values at I/O primitive boundaries (frag, query, action, service)
to prevent _Nil objects from reaching Python code that expects None. Add
mobile_nav_sx() helper that auto-populates the hamburger menu from nav_tree
and auth_menu context fragments when no menu slot is provided.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 10:45:52 +00:00
a8c0741f54 Add SX editor to post edit page, prevent sx_content clearing on save
- Add sx_content to _post_to_edit_dict so edit page receives existing content
- Add SX/Koenig editor tabs, sx-editor mount point, and SxEditor.mount init
- Only pass sx_content to writer_update when form field is present (prevents
  accidental clearing when editing via Koenig-only path)
- Add csrf_exempt to example API POST/DELETE/PUT demo endpoints
- Add defpage infrastructure (pages.py, layouts.py) and sx docs page definitions
- Add defhandler definitions for example API handlers (examples.sx)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 10:23:33 +00:00
222738546a Fix value-select: include SELECT element value in GET requests
sx.js only appended INPUT values to GET request URLs. SELECT and
TEXTAREA elements with a name attribute were silently ignored,
so the category parameter was never sent.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 08:56:48 +00:00
5dd1161816 Move example CSS to basics.css, pretty-print wire response, update sx logo
- Move .sx-fade-in and .sx-loading-btn CSS from inline (style) tags to
  basics.css so they go through the on-demand CSS registry
- Pretty-print sx source in wire response display (not all on one line)
- Change sx logo from </> icon to (</>) text

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 08:45:54 +00:00
8024fa5b13 Live wire response + component display with OOB swaps on all examples
- All 6 examples show Component and Wire response as placeholders that
  fill with actual content when the demo is triggered (via OOB swaps)
- Wire response shows full wire content including component definitions
  (when not cached) and CSS style block
- Component display only includes defs the client doesn't already have,
  matching real sx_response() behaviour
- Add "Clear component cache" button to reset localStorage + in-memory
  component env so next interaction shows component download
- Rebuild tw.css with Tailwind v3.4.19 including sx content paths
- Optimize sx_response() CSS scanning to only scan sent comp_defs

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 01:54:45 +00:00
e4e43177a8 Fix code blocks + add violet bg classes to tw.css
- Pass :code keyword to ~doc-code and ~example-source components
  (highlighted content was positional but components use &key code)
- Rebuild tw.css (v3.4.19) with sx/sxc and sx/content in content paths
  so highlight.py classes (text-violet-600, text-rose-600, etc.) are included
- Add bg-violet-{100-500} classes for the sx app's violet menu bar
- Add highlight.py custom syntax highlighter (sx, python, bash)

IMPORTANT: tw.css must contain bg-violet-{100-500} rules for the sx
app's menu bar. Do not rebuild tw.css without ensuring violet classes
are included (via safelist or content paths).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 01:13:01 +00:00
5578923242 Fix defhandler to produce sx wire format instead of HTML
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
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
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
13bcf755f6 Add OOB header swaps for sx docs navigation + enable OAuth + fragments
- OOB nav updates: AJAX navigation now swaps both menu bar levels
  (main nav highlighting + sub-nav with current page) using the same
  oob_header_sx/oob_page_sx pattern as blog/market/events
- Enable OAuth for sx and test apps (removed from _NO_OAUTH, added sx
  to ALLOWED_CLIENTS, added app_urls for sx/test/orders)
- Fetch real cross-service fragments (cart-mini, auth-menu, nav-tree)
  instead of hardcoding empty values
- Add :selected param to ~menu-row-sx for white text current-page label
- Fix duplicate element IDs: use menu-row-sx child_id/child mechanism
  instead of manual header_child_sx wrappers
- Fix home page copy: "Server-rendered DOM over the wire (no HTML)"

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 23:22:01 +00:00
8b2785ccb0 Skip OAuth registration for sx docs app
sx is a public documentation site like test — no auth needed.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 21:31:32 +00:00
815c5285d5 Add Prism.js syntax highlighting CSS for code blocks
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 21:00:58 +00:00
ed30f88f05 Fix missing SxExpr wraps in events + pretty-print sx in dev mode + multi-expr render
- Wrap 15 call sites in events/sx_components.py where sx-generating
  functions were passed as plain strings to sx_call(), causing raw
  s-expression source to leak into the rendered page.

- Add dev-mode pretty-printing (RELOAD=true) for sx responses and
  full page sx source — indented output in Network tab and View Source.

- Fix Sx.render to handle multiple top-level expressions by falling
  back to parseAll and returning a DocumentFragment.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 20:29:22 +00:00
8aedbc9e62 Add version logging, Markdown card menu item, and oembed card types
- sx-editor prints version on init: [sx-editor] v2026-03-02b-exorcism
- Add Markdown to card insert menu with /markdown and /md slash commands
- Add YouTube, X/Twitter, Vimeo, Spotify, CodePen as dedicated embed
  menu items with brand icons (all create ~kg-embed cards)

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