"""All content generator functions and dispatchers for sx docs pages. Content markup lives in .sx files (sx/sx/*.sx). Python functions here are thin dispatchers that either return a component name or pass data values via sx_call. """ from __future__ import annotations from shared.sx.helpers import sx_call, SxExpr from .utils import _attr_table_sx, _primitives_section_sx, _headers_table_sx # --------------------------------------------------------------------------- # Dispatcher functions — route slugs to content builders # --------------------------------------------------------------------------- async def _docs_content_sx(slug: str) -> str: """Route to the right docs content builder.""" import inspect builders = { "introduction": _docs_introduction_sx, "getting-started": _docs_getting_started_sx, "components": _docs_components_sx, "evaluator": _docs_evaluator_sx, "primitives": _docs_primitives_sx, "css": _docs_css_sx, "server-rendering": _docs_server_rendering_sx, } builder = builders.get(slug, _docs_introduction_sx) result = builder() return await result if inspect.isawaitable(result) else result async def _reference_content_sx(slug: str) -> str: import inspect builders = { "attributes": _reference_attrs_sx, "headers": _reference_headers_sx, "events": _reference_events_sx, "js-api": _reference_js_api_sx, } result = builders.get(slug or "", _reference_attrs_sx)() return await result if inspect.isawaitable(result) else result def _protocol_content_sx(slug: str) -> str: builders = { "wire-format": _protocol_wire_format_sx, "fragments": _protocol_fragments_sx, "resolver-io": _protocol_resolver_io_sx, "internal-services": _protocol_internal_services_sx, "activitypub": _protocol_activitypub_sx, "future": _protocol_future_sx, } return builders.get(slug, _protocol_wire_format_sx)() def _examples_content_sx(slug: str) -> str: builders = { "click-to-load": _example_click_to_load_sx, "form-submission": _example_form_submission_sx, "polling": _example_polling_sx, "delete-row": _example_delete_row_sx, "inline-edit": _example_inline_edit_sx, "oob-swaps": _example_oob_swaps_sx, "lazy-loading": _example_lazy_loading_sx, "infinite-scroll": _example_infinite_scroll_sx, "progress-bar": _example_progress_bar_sx, "active-search": _example_active_search_sx, "inline-validation": _example_inline_validation_sx, "value-select": _example_value_select_sx, "reset-on-submit": _example_reset_on_submit_sx, "edit-row": _example_edit_row_sx, "bulk-update": _example_bulk_update_sx, "swap-positions": _example_swap_positions_sx, "select-filter": _example_select_filter_sx, "tabs": _example_tabs_sx, "animations": _example_animations_sx, "dialogs": _example_dialogs_sx, "keyboard-shortcuts": _example_keyboard_shortcuts_sx, "put-patch": _example_put_patch_sx, "json-encoding": _example_json_encoding_sx, "vals-and-headers": _example_vals_and_headers_sx, "loading-states": _example_loading_states_sx, "sync-replace": _example_sync_replace_sx, "retry": _example_retry_sx, } return builders.get(slug, _example_click_to_load_sx)() def _essay_content_sx(slug: str) -> str: builders = { "sx-sucks": _essay_sx_sucks, "why-sexps": _essay_why_sexps, "htmx-react-hybrid": _essay_htmx_react_hybrid, "on-demand-css": _essay_on_demand_css, "client-reactivity": _essay_client_reactivity, "sx-native": _essay_sx_native, "sx-manifesto": _essay_sx_manifesto, "tail-call-optimization": _essay_tail_call_optimization, "continuations": _essay_continuations, } return builders.get(slug, _essay_sx_sucks)() # --------------------------------------------------------------------------- # Docs content — self-contained .sx components (sx/sx/docs-content.sx) # --------------------------------------------------------------------------- def _docs_introduction_sx() -> str: return "(~docs-introduction-content)" def _docs_getting_started_sx() -> str: return "(~docs-getting-started-content)" def _docs_components_sx() -> str: return "(~docs-components-content)" def _docs_evaluator_sx() -> str: return "(~docs-evaluator-content)" def _docs_primitives_sx() -> str: prims = _primitives_section_sx() return sx_call("docs-primitives-content", prims=SxExpr(prims)) def _docs_css_sx() -> str: return "(~docs-css-content)" def _docs_server_rendering_sx() -> str: return "(~docs-server-rendering-content)" # --------------------------------------------------------------------------- # Protocol content — self-contained .sx components (sx/sx/protocols.sx) # --------------------------------------------------------------------------- def _protocol_wire_format_sx() -> str: return "(~protocol-wire-format-content)" def _protocol_fragments_sx() -> str: return "(~protocol-fragments-content)" def _protocol_resolver_io_sx() -> str: return "(~protocol-resolver-io-content)" def _protocol_internal_services_sx() -> str: return "(~protocol-internal-services-content)" def _protocol_activitypub_sx() -> str: return "(~protocol-activitypub-content)" def _protocol_future_sx() -> str: return "(~protocol-future-content)" # --------------------------------------------------------------------------- # Essay content — self-contained .sx components (sx/sx/essays.sx) # --------------------------------------------------------------------------- def _essay_sx_sucks() -> str: return "(~essay-sx-sucks)" def _essay_why_sexps() -> str: return "(~essay-why-sexps)" def _essay_htmx_react_hybrid() -> str: return "(~essay-htmx-react-hybrid)" def _essay_on_demand_css() -> str: return "(~essay-on-demand-css)" def _essay_client_reactivity() -> str: return "(~essay-client-reactivity)" def _essay_sx_native() -> str: return "(~essay-sx-native)" def _essay_sx_manifesto() -> str: return "(~essay-sx-manifesto)" def _essay_tail_call_optimization() -> str: return "(~essay-tail-call-optimization)" def _essay_continuations() -> str: return "(~essay-continuations)" # --------------------------------------------------------------------------- # Reference pages — data-driven, page layouts in .sx (sx/sx/reference.sx) # --------------------------------------------------------------------------- def _reference_index_sx() -> str: return "(~reference-index-content)" def _reference_attr_detail_sx(slug: str) -> str: from content.pages import ATTR_DETAILS detail = ATTR_DETAILS.get(slug) if not detail: return sx_call("reference-attr-not-found", slug=slug) demo_name = detail.get("demo") demo = SxExpr(f"(~{demo_name})") if demo_name else None wire_placeholder_id = None if "handler" in detail: wire_id = slug.replace(":", "-").replace("*", "star") wire_placeholder_id = f"ref-wire-{wire_id}" return sx_call("reference-attr-detail-content", title=slug, description=detail["description"], demo=demo, example_code=detail["example"], handler_code=detail.get("handler"), wire_placeholder_id=wire_placeholder_id) def _reference_attrs_sx() -> str: from content.pages import REQUEST_ATTRS, BEHAVIOR_ATTRS, SX_UNIQUE_ATTRS return sx_call("reference-attrs-content", req_table=SxExpr(_attr_table_sx("Request Attributes", REQUEST_ATTRS)), beh_table=SxExpr(_attr_table_sx("Behavior Attributes", BEHAVIOR_ATTRS)), uniq_table=SxExpr(_attr_table_sx("Unique to sx", SX_UNIQUE_ATTRS))) def _reference_headers_sx() -> str: from content.pages import REQUEST_HEADERS, RESPONSE_HEADERS return sx_call("reference-headers-content", req_table=SxExpr(_headers_table_sx("Request Headers", REQUEST_HEADERS)), resp_table=SxExpr(_headers_table_sx("Response Headers", RESPONSE_HEADERS))) def _reference_events_sx() -> str: from content.pages import EVENTS rows = [] for name, desc in EVENTS: rows.append(sx_call("doc-two-col-row", name=name, description=desc)) rows_sx = "(<> " + " ".join(rows) + ")" table = sx_call("doc-two-col-table", intro="sx fires custom DOM events at various points in the request lifecycle.", col1="Event", col2="Description", rows=SxExpr(rows_sx)) return sx_call("reference-events-content", table=SxExpr(table)) def _reference_js_api_sx() -> str: from content.pages import JS_API rows = [] for name, desc in JS_API: rows.append(sx_call("doc-two-col-row", name=name, description=desc)) rows_sx = "(<> " + " ".join(rows) + ")" table = sx_call("doc-two-col-table", intro="The client-side sx.js library exposes a public API for programmatic use.", col1="Method", col2="Description", rows=SxExpr(rows_sx)) return sx_call("reference-js-api-content", table=SxExpr(table)) # --------------------------------------------------------------------------- # Example pages — template in .sx (sx/sx/examples.sx), data values from Python # --------------------------------------------------------------------------- def _build_example(title, description, demo_description, demo, sx_code, handler_code, *, comp_placeholder_id=None, wire_placeholder_id=None, wire_note=None, comp_heading=None, handler_heading=None) -> str: """Build an example page by passing data values to the .sx template.""" kw = dict( title=title, description=description, demo_description=demo_description, demo=SxExpr(demo), sx_code=sx_code, handler_code=handler_code, ) if comp_placeholder_id: kw["comp_placeholder_id"] = comp_placeholder_id if wire_placeholder_id: kw["wire_placeholder_id"] = wire_placeholder_id if wire_note: kw["wire_note"] = wire_note if comp_heading: kw["comp_heading"] = comp_heading if handler_heading: kw["handler_heading"] = handler_heading return sx_call("example-page-content", **kw) def _example_click_to_load_sx() -> str: return _build_example( title="Click to Load", description="The simplest sx interaction: click a button, fetch content from the server, swap it in.", demo_description="Click the button to load server-rendered content.", demo="(~click-to-load-demo)", sx_code='(button\n :sx-get "/examples/api/click"\n :sx-target "#click-result"\n :sx-swap "innerHTML"\n "Load content")', handler_code='@bp.get("/examples/api/click")\nasync def api_click():\n now = datetime.now().strftime(...)\n return sx_response(\n f\'(~click-result :time "{now}")\')', comp_placeholder_id="click-comp", wire_placeholder_id="click-wire", wire_note="The server responds with content-type text/sx. New CSS rules are prepended as a style tag. Clear the component cache to see component definitions included in the wire response.") def _example_form_submission_sx() -> str: return _build_example( title="Form Submission", description="Forms with sx-post submit via AJAX and swap the response into a target.", demo_description="Enter a name and submit.", demo="(~form-demo)", sx_code='(form\n :sx-post "/examples/api/form"\n :sx-target "#form-result"\n :sx-swap "innerHTML"\n (input :type "text" :name "name")\n (button :type "submit" "Submit"))', handler_code='@bp.post("/examples/api/form")\nasync def api_form():\n form = await request.form\n name = form.get("name", "")\n return sx_response(\n f\'(~form-result :name "{name}")\')', comp_placeholder_id="form-comp", wire_placeholder_id="form-wire") def _example_polling_sx() -> str: return _build_example( title="Polling", description='Use sx-trigger with "every" to poll the server at regular intervals.', demo_description="This div polls the server every 2 seconds.", demo="(~polling-demo)", sx_code='(div\n :sx-get "/examples/api/poll"\n :sx-trigger "load, every 2s"\n :sx-swap "innerHTML"\n "Loading...")', handler_code='@bp.get("/examples/api/poll")\nasync def api_poll():\n poll_count["n"] += 1\n now = datetime.now().strftime("%H:%M:%S")\n count = min(poll_count["n"], 10)\n return sx_response(\n f\'(~poll-result :time "{now}" :count {count})\')', comp_placeholder_id="poll-comp", wire_placeholder_id="poll-wire", wire_note="Updates every 2 seconds — watch the time and count change.") def _example_delete_row_sx() -> str: from content.pages import DELETE_DEMO_ITEMS items_sx = " ".join(f'(list "{id}" "{name}")' for id, name in DELETE_DEMO_ITEMS) return _build_example( title="Delete Row", description='sx-delete with sx-swap "outerHTML" and an empty response removes the row from the DOM.', demo_description="Click delete to remove a row. Uses sx-confirm for confirmation.", demo=f"(~delete-demo :items (list {items_sx}))", sx_code='(button\n :sx-delete "/api/delete/1"\n :sx-target "#row-1"\n :sx-swap "outerHTML"\n :sx-confirm "Delete this item?"\n "delete")', handler_code='@bp.delete("/examples/api/delete/")\nasync def api_delete(item_id: str):\n # Empty response — outerHTML swap removes the row\n return Response("", status=200,\n content_type="text/sx")', comp_placeholder_id="delete-comp", wire_placeholder_id="delete-wire", wire_note="Empty body — outerHTML swap replaces the target element with nothing.") def _example_inline_edit_sx() -> str: return _build_example( title="Inline Edit", description="Click edit to swap a display view for an edit form. Save swaps back.", demo_description="Click edit, modify the text, save or cancel.", demo="(~inline-edit-demo)", sx_code=';; View mode — shows text + edit button\n(~inline-view :value "some text")\n\n;; Edit mode — returned by server on click\n(~inline-edit-form :value "some text")', handler_code='@bp.get("/examples/api/edit")\nasync def api_edit_form():\n value = request.args.get("value", "")\n return sx_response(\n f\'(~inline-edit-form :value "{value}")\')\n\n@bp.post("/examples/api/edit")\nasync def api_edit_save():\n form = await request.form\n value = form.get("value", "")\n return sx_response(\n f\'(~inline-view :value "{value}")\')', comp_placeholder_id="edit-comp", comp_heading="Components", handler_heading="Server handlers", wire_placeholder_id="edit-wire") def _example_oob_swaps_sx() -> str: return _build_example( title="Out-of-Band Swaps", description="sx-swap-oob lets a single response update multiple elements anywhere in the DOM.", demo_description="One request updates both Box A (via sx-target) and Box B (via sx-swap-oob).", demo="(~oob-demo)", sx_code=';; Button targets Box A\n(button\n :sx-get "/examples/api/oob"\n :sx-target "#oob-box-a"\n :sx-swap "innerHTML"\n "Update both boxes")', handler_code='@bp.get("/examples/api/oob")\nasync def api_oob():\n now = datetime.now().strftime("%H:%M:%S")\n return sx_response(\n f\'(<>\'\n f\' (p "Box A updated at {now}")\'\n f\' (div :id "oob-box-b"\'\n f\' :sx-swap-oob "innerHTML"\'\n f\' (p "Box B updated at {now}")))\')', wire_placeholder_id="oob-wire", wire_note="The fragment contains both the main content and an OOB element. sx.js splits them: main content goes to sx-target, OOB elements find their targets by ID.") def _example_lazy_loading_sx() -> str: return _build_example( title="Lazy Loading", description='Use sx-trigger="load" to fetch content as soon as the element enters the DOM. Great for deferring expensive content below the fold.', demo_description="Content loads automatically when the page renders.", demo="(~lazy-loading-demo)", sx_code='(div\n :sx-get "/examples/api/lazy"\n :sx-trigger "load"\n :sx-swap "innerHTML"\n (div :class "animate-pulse" "Loading..."))', handler_code='@bp.get("/examples/api/lazy")\nasync def api_lazy():\n now = datetime.now().strftime(...)\n return sx_response(\n f\'(~lazy-result :time "{now}")\')', comp_placeholder_id="lazy-comp", wire_placeholder_id="lazy-wire") def _example_infinite_scroll_sx() -> str: return _build_example( title="Infinite Scroll", description='A sentinel element at the bottom uses sx-trigger="intersect once" to load the next page when scrolled into view. Each response appends items and a new sentinel.', demo_description="Scroll down in the container to load more items (5 pages total).", demo="(~infinite-scroll-demo)", sx_code='(div :id "scroll-sentinel"\n :sx-get "/examples/api/scroll?page=2"\n :sx-trigger "intersect once"\n :sx-target "#scroll-items"\n :sx-swap "beforeend"\n "Loading more...")', handler_code='@bp.get("/examples/api/scroll")\nasync def api_scroll():\n page = int(request.args.get("page", 2))\n items = [f"Item {i}" for i in range(...)]\n # Include next sentinel if more pages\n return sx_response(items_sx + sentinel_sx)', comp_placeholder_id="scroll-comp", wire_placeholder_id="scroll-wire") def _example_progress_bar_sx() -> str: return _build_example( title="Progress Bar", description='Start a server-side job, then poll for progress using sx-trigger="load delay:500ms" on each response. The bar fills up and stops when complete.', demo_description="Click start to begin a simulated job.", demo="(~progress-bar-demo)", sx_code=';; Start the job\n(button\n :sx-post "/examples/api/progress/start"\n :sx-target "#progress-target"\n :sx-swap "innerHTML")\n\n;; Each response re-polls via sx-trigger="load"\n(div :sx-get "/api/progress/status?job=ID"\n :sx-trigger "load delay:500ms"\n :sx-target "#progress-target"\n :sx-swap "innerHTML")', handler_code='@bp.post("/examples/api/progress/start")\nasync def api_progress_start():\n job_id = str(uuid4())[:8]\n _jobs[job_id] = 0\n return sx_response(\n f\'(~progress-status :percent 0 :job-id "{job_id}")\')', comp_placeholder_id="progress-comp", wire_placeholder_id="progress-wire") def _example_active_search_sx() -> str: return _build_example( title="Active Search", description='An input with sx-trigger="keyup delay:300ms changed" debounces keystrokes and only fires when the value changes. The server filters a list of programming languages.', demo_description="Type to search through 20 programming languages.", demo="(~active-search-demo)", sx_code='(input :type "text" :name "q"\n :sx-get "/examples/api/search"\n :sx-trigger "keyup delay:300ms changed"\n :sx-target "#search-results"\n :sx-swap "innerHTML"\n :placeholder "Search...")', handler_code='@bp.get("/examples/api/search")\nasync def api_search():\n q = request.args.get("q", "").lower()\n results = [l for l in LANGUAGES if q in l.lower()]\n return sx_response(\n f\'(~search-results :items (...) :query "{q}")\')', comp_placeholder_id="search-comp", wire_placeholder_id="search-wire") def _example_inline_validation_sx() -> str: return _build_example( title="Inline Validation", description="Validate an email field on blur. The server checks format and whether it is taken, returning green or red feedback inline.", demo_description="Enter an email and click away (blur) to validate.", demo="(~inline-validation-demo)", sx_code='(input :type "text" :name "email"\n :sx-get "/examples/api/validate"\n :sx-trigger "blur"\n :sx-target "#email-feedback"\n :sx-swap "innerHTML"\n :placeholder "user@example.com")', handler_code='@bp.get("/examples/api/validate")\nasync def api_validate():\n email = request.args.get("email", "")\n if "@" not in email:\n return sx_response(\'(~validation-error ...)\')\n return sx_response(\'(~validation-ok ...)\')', comp_placeholder_id="validate-comp", wire_placeholder_id="validate-wire") def _example_value_select_sx() -> str: return _build_example( title="Value Select", description="Two linked selects: pick a category and the second select updates with matching items via sx-get.", demo_description="Select a category to populate the item dropdown.", demo="(~value-select-demo)", sx_code='(select :name "category"\n :sx-get "/examples/api/values"\n :sx-trigger "change"\n :sx-target "#value-items"\n :sx-swap "innerHTML"\n (option "Languages")\n (option "Frameworks")\n (option "Databases"))', handler_code='@bp.get("/examples/api/values")\nasync def api_values():\n cat = request.args.get("category", "")\n items = VALUE_SELECT_DATA.get(cat, [])\n return sx_response(\n f\'(~value-options :items (list ...))\')', comp_placeholder_id="values-comp", wire_placeholder_id="values-wire") def _example_reset_on_submit_sx() -> str: return _build_example( title="Reset on Submit", description='Use sx-on:afterSwap="this.reset()" to clear form inputs after a successful submission.', demo_description="Submit a message — the input resets after each send.", demo="(~reset-on-submit-demo)", sx_code='(form :id "reset-form"\n :sx-post "/examples/api/reset-submit"\n :sx-target "#reset-result"\n :sx-swap "innerHTML"\n :sx-on:afterSwap "this.reset()"\n (input :type "text" :name "message")\n (button :type "submit" "Send"))', handler_code='@bp.post("/examples/api/reset-submit")\nasync def api_reset_submit():\n form = await request.form\n msg = form.get("message", "")\n return sx_response(\n f\'(~reset-message :message "{msg}" :time "...")\')', comp_placeholder_id="reset-comp", wire_placeholder_id="reset-wire") def _example_edit_row_sx() -> str: from content.pages import EDIT_ROW_DATA rows_sx = " ".join( f'(list "{r["id"]}" "{r["name"]}" "{r["price"]}" "{r["stock"]}")' for r in EDIT_ROW_DATA ) return _build_example( title="Edit Row", description="Click edit to replace a table row with input fields. Save or cancel swaps back the display row. Uses sx-include to gather form values from the row.", demo_description="Click edit on any row to modify it inline.", demo=f"(~edit-row-demo :rows (list {rows_sx}))", sx_code='(button\n :sx-get "/examples/api/editrow/1"\n :sx-target "#erow-1"\n :sx-swap "outerHTML"\n "edit")\n\n;; Save sends form data via POST\n(button\n :sx-post "/examples/api/editrow/1"\n :sx-target "#erow-1"\n :sx-swap "outerHTML"\n :sx-include "#erow-1"\n "save")', handler_code='@bp.get("/examples/api/editrow/")\nasync def api_editrow_form(id):\n row = EDIT_ROW_DATA[id]\n return sx_response(\n f\'(~edit-row-form :id ... :name ...)\')\n\n@bp.post("/examples/api/editrow/")\nasync def api_editrow_save(id):\n form = await request.form\n return sx_response(\n f\'(~edit-row-view :id ... :name ...)\')', comp_placeholder_id="editrow-comp", wire_placeholder_id="editrow-wire") def _example_bulk_update_sx() -> str: from content.pages import BULK_USERS users_sx = " ".join( f'(list "{u["id"]}" "{u["name"]}" "{u["email"]}" "{u["status"]}")' for u in BULK_USERS ) return _build_example( title="Bulk Update", description="Select rows with checkboxes and use Activate/Deactivate buttons. sx-include gathers checkbox values from the form.", demo_description="Check some rows, then click Activate or Deactivate.", demo=f"(~bulk-update-demo :users (list {users_sx}))", sx_code='(button\n :sx-post "/examples/api/bulk?action=activate"\n :sx-target "#bulk-table"\n :sx-swap "innerHTML"\n :sx-include "#bulk-form"\n "Activate")', handler_code='@bp.post("/examples/api/bulk")\nasync def api_bulk():\n action = request.args.get("action")\n form = await request.form\n ids = form.getlist("ids")\n # Update matching users\n return sx_response(updated_rows)', comp_placeholder_id="bulk-comp", wire_placeholder_id="bulk-wire") def _example_swap_positions_sx() -> str: return _build_example( title="Swap Positions", description="Demonstrates different swap modes: beforeend appends, afterbegin prepends, and none skips the main swap while still processing OOB updates.", demo_description="Try each button to see different swap behaviours.", demo="(~swap-positions-demo)", sx_code=';; Append to end\n(button :sx-post "/api/swap-log?mode=beforeend"\n :sx-target "#swap-log" :sx-swap "beforeend"\n "Add to End")\n\n;; Prepend to start\n(button :sx-post "/api/swap-log?mode=afterbegin"\n :sx-target "#swap-log" :sx-swap "afterbegin"\n "Add to Start")\n\n;; No swap — OOB counter update only\n(button :sx-post "/api/swap-log?mode=none"\n :sx-target "#swap-log" :sx-swap "none"\n "Silent Ping")', handler_code='@bp.post("/examples/api/swap-log")\nasync def api_swap_log():\n mode = request.args.get("mode")\n # OOB counter updates on every request\n oob = f\'(span :id "swap-counter" :sx-swap-oob "innerHTML" "Count: {n}")\'\n return sx_response(entry + oob)', wire_placeholder_id="swap-wire") def _example_select_filter_sx() -> str: return _build_example( title="Select Filter", description="sx-select lets the client pick a specific section from the server response by CSS selector. The server always returns the full dashboard — the client filters.", demo_description="Different buttons select different parts of the same server response.", demo="(~select-filter-demo)", sx_code=';; Pick just the stats section from the response\n(button\n :sx-get "/examples/api/dashboard"\n :sx-target "#filter-target"\n :sx-swap "innerHTML"\n :sx-select "#dash-stats"\n "Stats Only")\n\n;; No sx-select — get the full response\n(button\n :sx-get "/examples/api/dashboard"\n :sx-target "#filter-target"\n :sx-swap "innerHTML"\n "Full Dashboard")', handler_code='@bp.get("/examples/api/dashboard")\nasync def api_dashboard():\n # Returns header + stats + footer\n # Client uses sx-select to pick sections\n return sx_response(\n \'(<> (div :id "dash-header" ...) \'\n \' (div :id "dash-stats" ...) \'\n \' (div :id "dash-footer" ...))\')', wire_placeholder_id="filter-wire") def _example_tabs_sx() -> str: return _build_example( title="Tabs", description="Tab navigation using sx-push-url to update the browser URL. Back/forward buttons navigate between previously visited tabs.", demo_description="Click tabs to switch content. Watch the browser URL change.", demo="(~tabs-demo)", sx_code='(button\n :sx-get "/examples/api/tabs/tab1"\n :sx-target "#tab-content"\n :sx-swap "innerHTML"\n :sx-push-url "/examples/tabs?tab=tab1"\n "Overview")', handler_code='@bp.get("/examples/api/tabs/")\nasync def api_tabs(tab: str):\n content = TAB_CONTENT[tab]\n return sx_response(content)', wire_placeholder_id="tabs-wire") def _example_animations_sx() -> str: return _build_example( title="Animations", description="CSS animations play on swap. The component injects a style tag with a keyframe animation and applies the class. Each click picks a random background colour.", demo_description="Click to swap in content with a fade-in animation.", demo="(~animations-demo)", sx_code='(button\n :sx-get "/examples/api/animate"\n :sx-target "#anim-target"\n :sx-swap "innerHTML"\n "Load with animation")\n\n;; Component uses CSS animation class\n(defcomp ~anim-result (&key color time)\n (div :class "sx-fade-in ..."\n (style ".sx-fade-in { animation: sxFadeIn 0.5s }")\n (p "Faded in!")))', handler_code='@bp.get("/examples/api/animate")\nasync def api_animate():\n colors = ["bg-violet-100", "bg-emerald-100", ...]\n color = random.choice(colors)\n return sx_response(\n f\'(~anim-result :color "{color}" :time "{now}")\')', comp_placeholder_id="anim-comp", wire_placeholder_id="anim-wire") def _example_dialogs_sx() -> str: return _build_example( title="Dialogs", description="Open a modal dialog by swapping in the dialog component. Close by swapping in empty content. Pure sx — no JavaScript library needed.", demo_description="Click to open a modal dialog.", demo="(~dialogs-demo)", sx_code='(button\n :sx-get "/examples/api/dialog"\n :sx-target "#dialog-container"\n :sx-swap "innerHTML"\n "Open Dialog")\n\n;; Dialog closes by swapping empty content\n(button\n :sx-get "/examples/api/dialog/close"\n :sx-target "#dialog-container"\n :sx-swap "innerHTML"\n "Close")', handler_code='@bp.get("/examples/api/dialog")\nasync def api_dialog():\n return sx_response(\n \'(~dialog-modal :title "Confirm"\'\n \' :message "Are you sure?")\')\n\n@bp.get("/examples/api/dialog/close")\nasync def api_dialog_close():\n return sx_response("")', comp_placeholder_id="dialog-comp", wire_placeholder_id="dialog-wire") def _example_keyboard_shortcuts_sx() -> str: return _build_example( title="Keyboard Shortcuts", description="Use sx-trigger with keyup event filters and from:body to listen for global keyboard shortcuts. The filter prevents firing when typing in inputs.", demo_description="Press s, n, or h on your keyboard.", demo="(~keyboard-shortcuts-demo)", sx_code='(div :id "kbd-target"\n :sx-get "/examples/api/keyboard?key=s"\n :sx-trigger "keyup[key==\'s\'&&!event.target.matches(\'input,textarea\')] from:body"\n :sx-swap "innerHTML"\n "Press a shortcut key...")', handler_code='@bp.get("/examples/api/keyboard")\nasync def api_keyboard():\n key = request.args.get("key", "")\n actions = {"s": "Search", "n": "New item", "h": "Help"}\n return sx_response(\n f\'(~kbd-result :key "{key}" :action "{actions[key]}")\')', comp_placeholder_id="kbd-comp", wire_placeholder_id="kbd-wire") def _example_put_patch_sx() -> str: from content.pages import PROFILE_DEFAULT n, e, r = PROFILE_DEFAULT["name"], PROFILE_DEFAULT["email"], PROFILE_DEFAULT["role"] return _build_example( title="PUT / PATCH", description="sx-put replaces the entire resource. This example shows a profile card with an Edit All button that sends a PUT with all fields.", demo_description="Click Edit All to replace the full profile via PUT.", demo=f'(~put-patch-demo :name "{n}" :email "{e}" :role "{r}")', sx_code=';; Replace entire resource\n(form :sx-put "/examples/api/putpatch"\n :sx-target "#pp-target" :sx-swap "innerHTML"\n (input :name "name") (input :name "email")\n (button "Save All (PUT)"))', handler_code='@bp.put("/examples/api/putpatch")\nasync def api_put():\n form = await request.form\n # Full replacement\n return sx_response(\'(~pp-view ...)\')', comp_placeholder_id="pp-comp", wire_placeholder_id="pp-wire") def _example_json_encoding_sx() -> str: return _build_example( title="JSON Encoding", description='Use sx-encoding="json" to send form data as a JSON body instead of URL-encoded form data. The server echoes back what it received.', demo_description="Submit the form and see the JSON body the server received.", demo="(~json-encoding-demo)", sx_code='(form\n :sx-post "/examples/api/json-echo"\n :sx-target "#json-result"\n :sx-swap "innerHTML"\n :sx-encoding "json"\n (input :name "name" :value "Ada")\n (input :type "number" :name "age" :value "36")\n (button "Submit as JSON"))', handler_code='@bp.post("/examples/api/json-echo")\nasync def api_json_echo():\n data = await request.get_json()\n body = json.dumps(data, indent=2)\n ct = request.content_type\n return sx_response(\n f\'(~json-result :body "{body}" :content-type "{ct}")\')', comp_placeholder_id="json-comp", wire_placeholder_id="json-wire") def _example_vals_and_headers_sx() -> str: return _build_example( title="Vals & Headers", description="sx-vals adds extra key/value pairs to the request parameters. sx-headers adds custom HTTP headers. The server echoes back what it received.", demo_description="Click each button to see what the server receives.", demo="(~vals-headers-demo)", sx_code=';; Send extra values with the request\n(button\n :sx-get "/examples/api/echo-vals"\n :sx-vals "{\\"source\\": \\"button\\"}"\n "Send with vals")\n\n;; Send custom headers\n(button\n :sx-get "/examples/api/echo-headers"\n :sx-headers "{\\"X-Custom-Token\\": \\"abc123\\"}"\n "Send with headers")', handler_code='@bp.get("/examples/api/echo-vals")\nasync def api_echo_vals():\n vals = dict(request.args)\n return sx_response(\n f\'(~echo-result :label "values" :items (...))\')\n\n@bp.get("/examples/api/echo-headers")\nasync def api_echo_headers():\n custom = {k: v for k, v in request.headers\n if k.startswith("X-")}\n return sx_response(\n f\'(~echo-result :label "headers" :items (...))\')', comp_placeholder_id="vals-comp", wire_placeholder_id="vals-wire") def _example_loading_states_sx() -> str: return _build_example( title="Loading States", description="sx.js adds the .sx-request CSS class to any element that has an active request. Use pure CSS to show spinners, disable buttons, or change opacity during loading.", demo_description="Click the button — it shows a spinner during the 2-second request.", demo="(~loading-states-demo)", sx_code=';; .sx-request class added during request\n(style ".sx-loading-btn.sx-request {\n opacity: 0.7; pointer-events: none; }\n.sx-loading-btn.sx-request .sx-spinner {\n display: inline-block; }\n.sx-loading-btn .sx-spinner {\n display: none; }")\n\n(button :class "sx-loading-btn"\n :sx-get "/examples/api/slow"\n :sx-target "#loading-result"\n (span :class "sx-spinner animate-spin" "...")\n "Load slow endpoint")', handler_code='@bp.get("/examples/api/slow")\nasync def api_slow():\n await asyncio.sleep(2)\n return sx_response(\n f\'(~loading-result :time "{now}")\')', comp_placeholder_id="loading-comp", wire_placeholder_id="loading-wire") def _example_sync_replace_sx() -> str: return _build_example( title="Request Abort", description='sx-sync="replace" aborts any in-flight request before sending a new one. This prevents stale responses from overwriting newer ones, even with random server delays.', demo_description="Type quickly — only the latest result appears despite random 0.5-2s server delays.", demo="(~sync-replace-demo)", sx_code='(input :type "text" :name "q"\n :sx-get "/examples/api/slow-search"\n :sx-trigger "keyup delay:200ms changed"\n :sx-target "#sync-result"\n :sx-swap "innerHTML"\n :sx-sync "replace"\n "Type to search...")', handler_code='@bp.get("/examples/api/slow-search")\nasync def api_slow_search():\n delay = random.uniform(0.5, 2.0)\n await asyncio.sleep(delay)\n q = request.args.get("q", "")\n return sx_response(\n f\'(~sync-result :query "{q}" :delay "{delay_ms}")\')', comp_placeholder_id="sync-comp", wire_placeholder_id="sync-wire") def _example_retry_sx() -> str: return _build_example( title="Retry", description='sx-retry="exponential:1000:8000" retries failed requests with exponential backoff starting at 1s up to 8s. The endpoint fails the first 2 attempts and succeeds on the 3rd.', demo_description="Click the button — watch it retry automatically after failures.", demo="(~retry-demo)", sx_code='(button\n :sx-get "/examples/api/flaky"\n :sx-target "#retry-result"\n :sx-swap "innerHTML"\n :sx-retry "exponential:1000:8000"\n "Call flaky endpoint")', handler_code='@bp.get("/examples/api/flaky")\nasync def api_flaky():\n _flaky["n"] += 1\n if _flaky["n"] % 3 != 0:\n return Response("", status=503)\n return sx_response(\n f\'(~retry-result :attempt {n} ...)\')', comp_placeholder_id="retry-comp", wire_placeholder_id="retry-wire")