From feecbb66badd406162c2f1305796fb40235fbe7f Mon Sep 17 00:00:00 2001 From: giles Date: Thu, 12 Mar 2026 10:02:26 +0000 Subject: [PATCH] Convert all API endpoint URLs to SX expression format MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every URL at sx-web.org now uses bracketed SX expressions — pages AND API endpoints. defhandler :path values, sx-get/sx-post/sx-delete attrs, code examples, and Python route decorators all converted. - Add SxAtomConverter to handlers.py for parameter matching inside expression URLs (e.g. /(api.(item.))) - Convert ~50 defhandler :path values in ref-api.sx and examples.sx - Convert ~90 sx-get/sx-post/sx-delete URLs in reference.sx, examples.sx - Convert ~30 code example URLs in examples-content.sx - Convert ~30 API URLs in pages.py (Python string code examples) - Convert ~70 page navigation URLs in pages.py - Convert 7 Python route decorators in routes.py - Convert ~10 reactive API URLs in marshes.sx - Add API redirect patterns to sx_router.py (301 for old paths) - Remove /api/ skip in app.py redirects (old API paths now redirect) Co-Authored-By: Claude Opus 4.6 --- shared/sx/handlers.py | 18 ++++ sx/app.py | 4 - sx/bp/pages/routes.py | 14 +-- sx/content/pages.py | 174 +++++++++++++++--------------- sx/sx/examples-content.sx | 52 ++++----- sx/sx/handlers/examples.sx | 76 ++++++------- sx/sx/handlers/ref-api.sx | 38 +++---- sx/sx/reactive-islands/marshes.sx | 22 ++-- sx/sxc/examples.sx | 94 ++++++++-------- sx/sxc/pages/sx_router.py | 45 ++++++++ sx/sxc/reference.sx | 120 ++++++++++----------- 11 files changed, 358 insertions(+), 299 deletions(-) diff --git a/shared/sx/handlers.py b/shared/sx/handlers.py index b3b7444..7950af9 100644 --- a/shared/sx/handlers.py +++ b/shared/sx/handlers.py @@ -24,11 +24,25 @@ import logging import os from typing import Any, Callable, Awaitable +from werkzeug.routing import BaseConverter + from .types import HandlerDef logger = logging.getLogger("sx.handlers") +class SxAtomConverter(BaseConverter): + """URL converter for SX atoms inside expression URLs. + + Matches a single atom — stops at dots, parens, slashes, and query chars. + Use as ```` in route patterns like:: + + /(geography.(hypermedia.(reference.(api.(item.))))) + """ + + regex = r"[^./)(?&= ]+" + + # --------------------------------------------------------------------------- # Registry — service → handler-name → HandlerDef # --------------------------------------------------------------------------- @@ -229,6 +243,10 @@ def register_route_handlers(app_or_bp: Any, service_name: str) -> int: from quart import Response, request from shared.browser.app.csrf import csrf_exempt + # Register SX atom converter for expression URL parameters + if hasattr(app_or_bp, 'url_map'): + app_or_bp.url_map.converters.setdefault('sx', SxAtomConverter) + handlers = get_all_handlers(service_name) count = 0 diff --git a/sx/app.py b/sx/app.py index 797a9ce..d53e82b 100644 --- a/sx/app.py +++ b/sx/app.py @@ -128,9 +128,6 @@ def create_app() -> "Quart": # Skip SX expression URLs (already in new format) if path.startswith("/(") or path.startswith("/~"): return None - # Skip API/handler paths - if "/api/" in path: - return None new_url = redirect_old_url(path) if new_url: qs = request.query_string.decode() @@ -149,7 +146,6 @@ def create_app() -> "Quart": and not path.endswith("/") and request.method == "GET" and not path.startswith(("/static/", "/internal/", "/auth/")) - and "/api/" not in path and "." not in path.rsplit("/", 1)[-1]): qs = request.query_string.decode() target = path + "/" + ("?" + qs if qs else "") diff --git a/sx/bp/pages/routes.py b/sx/bp/pages/routes.py index 44881f2..62775e7 100644 --- a/sx/bp/pages/routes.py +++ b/sx/bp/pages/routes.py @@ -23,7 +23,7 @@ def register(url_prefix: str = "/") -> Blueprint: # SSE stays in Python — fundamentally different paradigm (async generator). # ------------------------------------------------------------------ - @bp.get("/geography/hypermedia/reference/api/sse-time") + @bp.get("/(geography.(hypermedia.(reference.(api.sse-time))))") async def ref_sse_time(): async def generate(): for _ in range(30): # stream for 60 seconds max @@ -38,7 +38,7 @@ def register(url_prefix: str = "/") -> Blueprint: _marsh_sale_idx = {"n": 0} - @bp.get("/geography/reactive/api/flash-sale") + @bp.get("/(geography.(reactive.(api.flash-sale)))") async def api_marsh_flash_sale(): from shared.sx.helpers import sx_response prices = [14.99, 9.99, 24.99, 12.49, 7.99, 29.99, 4.99, 16.50] @@ -60,7 +60,7 @@ def register(url_prefix: str = "/") -> Blueprint: _settle_counter = {"n": 0} - @bp.get("/geography/reactive/api/settle-data") + @bp.get("/(geography.(reactive.(api.settle-data)))") async def api_settle_data(): from shared.sx.helpers import sx_response _settle_counter["n"] += 1 @@ -76,7 +76,7 @@ def register(url_prefix: str = "/") -> Blueprint: # --- Demo 4: signal-bound URL endpoints --- - @bp.get("/geography/reactive/api/search/products") + @bp.get("/(geography.(reactive.(api.search-products)))") async def api_search_products(): from shared.sx.helpers import sx_response q = request.args.get("q", "") @@ -95,7 +95,7 @@ def register(url_prefix: str = "/") -> Blueprint: ) return sx_response(sx_src) - @bp.get("/geography/reactive/api/search/events") + @bp.get("/(geography.(reactive.(api.search-events)))") async def api_search_events(): from shared.sx.helpers import sx_response q = request.args.get("q", "") @@ -114,7 +114,7 @@ def register(url_prefix: str = "/") -> Blueprint: ) return sx_response(sx_src) - @bp.get("/geography/reactive/api/search/posts") + @bp.get("/(geography.(reactive.(api.search-posts)))") async def api_search_posts(): from shared.sx.helpers import sx_response q = request.args.get("q", "") @@ -135,7 +135,7 @@ def register(url_prefix: str = "/") -> Blueprint: # --- Demo 5: marsh transform endpoint --- - @bp.get("/geography/reactive/api/catalog") + @bp.get("/(geography.(reactive.(api.catalog)))") async def api_catalog(): from shared.sx.helpers import sx_response items = [ diff --git a/sx/content/pages.py b/sx/content/pages.py index ac86218..6508ef2 100644 --- a/sx/content/pages.py +++ b/sx/content/pages.py @@ -10,79 +10,79 @@ from __future__ import annotations # --------------------------------------------------------------------------- DOCS_NAV = [ - ("Introduction", "/language/docs/introduction"), - ("Getting Started", "/language/docs/getting-started"), - ("Components", "/language/docs/components"), - ("Evaluator", "/language/docs/evaluator"), - ("Primitives", "/language/docs/primitives"), - ("CSS", "/language/docs/css"), - ("Server Rendering", "/language/docs/server-rendering"), + ("Introduction", "/(language.(doc.introduction))"), + ("Getting Started", "/(language.(doc.getting-started))"), + ("Components", "/(language.(doc.components))"), + ("Evaluator", "/(language.(doc.evaluator))"), + ("Primitives", "/(language.(doc.primitives))"), + ("CSS", "/(language.(doc.css))"), + ("Server Rendering", "/(language.(doc.server-rendering))"), ] REFERENCE_NAV = [ - ("Attributes", "/geography/hypermedia/reference/attributes"), - ("Headers", "/geography/hypermedia/reference/headers"), - ("Events", "/geography/hypermedia/reference/events"), - ("JS API", "/geography/hypermedia/reference/js-api"), + ("Attributes", "/(geography.(hypermedia.(reference.attributes)))"), + ("Headers", "/(geography.(hypermedia.(reference.headers)))"), + ("Events", "/(geography.(hypermedia.(reference.events)))"), + ("JS API", "/(geography.(hypermedia.(reference.js-api)))"), ] PROTOCOLS_NAV = [ - ("Wire Format", "/applications/protocols/wire-format"), - ("Fragments", "/applications/protocols/fragments"), - ("Resolver I/O", "/applications/protocols/resolver-io"), - ("Internal Services", "/applications/protocols/internal-services"), - ("ActivityPub", "/applications/protocols/activitypub"), - ("Future", "/applications/protocols/future"), + ("Wire Format", "/(applications.(protocol.wire-format))"), + ("Fragments", "/(applications.(protocol.fragments))"), + ("Resolver I/O", "/(applications.(protocol.resolver-io))"), + ("Internal Services", "/(applications.(protocol.internal-services))"), + ("ActivityPub", "/(applications.(protocol.activitypub))"), + ("Future", "/(applications.(protocol.future))"), ] EXAMPLES_NAV = [ - ("Click to Load", "/geography/hypermedia/examples/click-to-load"), - ("Form Submission", "/geography/hypermedia/examples/form-submission"), - ("Polling", "/geography/hypermedia/examples/polling"), - ("Delete Row", "/geography/hypermedia/examples/delete-row"), - ("Inline Edit", "/geography/hypermedia/examples/inline-edit"), - ("OOB Swaps", "/geography/hypermedia/examples/oob-swaps"), - ("Lazy Loading", "/geography/hypermedia/examples/lazy-loading"), - ("Infinite Scroll", "/geography/hypermedia/examples/infinite-scroll"), - ("Progress Bar", "/geography/hypermedia/examples/progress-bar"), - ("Active Search", "/geography/hypermedia/examples/active-search"), - ("Inline Validation", "/geography/hypermedia/examples/inline-validation"), - ("Value Select", "/geography/hypermedia/examples/value-select"), - ("Reset on Submit", "/geography/hypermedia/examples/reset-on-submit"), - ("Edit Row", "/geography/hypermedia/examples/edit-row"), - ("Bulk Update", "/geography/hypermedia/examples/bulk-update"), - ("Swap Positions", "/geography/hypermedia/examples/swap-positions"), - ("Select Filter", "/geography/hypermedia/examples/select-filter"), - ("Tabs", "/geography/hypermedia/examples/tabs"), - ("Animations", "/geography/hypermedia/examples/animations"), - ("Dialogs", "/geography/hypermedia/examples/dialogs"), - ("Keyboard Shortcuts", "/geography/hypermedia/examples/keyboard-shortcuts"), - ("PUT / PATCH", "/geography/hypermedia/examples/put-patch"), - ("JSON Encoding", "/geography/hypermedia/examples/json-encoding"), - ("Vals & Headers", "/geography/hypermedia/examples/vals-and-headers"), - ("Loading States", "/geography/hypermedia/examples/loading-states"), - ("Request Abort", "/geography/hypermedia/examples/sync-replace"), - ("Retry", "/geography/hypermedia/examples/retry"), + ("Click to Load", "/(geography.(hypermedia.(example.click-to-load)))"), + ("Form Submission", "/(geography.(hypermedia.(example.form-submission)))"), + ("Polling", "/(geography.(hypermedia.(example.polling)))"), + ("Delete Row", "/(geography.(hypermedia.(example.delete-row)))"), + ("Inline Edit", "/(geography.(hypermedia.(example.inline-edit)))"), + ("OOB Swaps", "/(geography.(hypermedia.(example.oob-swaps)))"), + ("Lazy Loading", "/(geography.(hypermedia.(example.lazy-loading)))"), + ("Infinite Scroll", "/(geography.(hypermedia.(example.infinite-scroll)))"), + ("Progress Bar", "/(geography.(hypermedia.(example.progress-bar)))"), + ("Active Search", "/(geography.(hypermedia.(example.active-search)))"), + ("Inline Validation", "/(geography.(hypermedia.(example.inline-validation)))"), + ("Value Select", "/(geography.(hypermedia.(example.value-select)))"), + ("Reset on Submit", "/(geography.(hypermedia.(example.reset-on-submit)))"), + ("Edit Row", "/(geography.(hypermedia.(example.edit-row)))"), + ("Bulk Update", "/(geography.(hypermedia.(example.bulk-update)))"), + ("Swap Positions", "/(geography.(hypermedia.(example.swap-positions)))"), + ("Select Filter", "/(geography.(hypermedia.(example.select-filter)))"), + ("Tabs", "/(geography.(hypermedia.(example.tabs)))"), + ("Animations", "/(geography.(hypermedia.(example.animations)))"), + ("Dialogs", "/(geography.(hypermedia.(example.dialogs)))"), + ("Keyboard Shortcuts", "/(geography.(hypermedia.(example.keyboard-shortcuts)))"), + ("PUT / PATCH", "/(geography.(hypermedia.(example.put-patch)))"), + ("JSON Encoding", "/(geography.(hypermedia.(example.json-encoding)))"), + ("Vals & Headers", "/(geography.(hypermedia.(example.vals-and-headers)))"), + ("Loading States", "/(geography.(hypermedia.(example.loading-states)))"), + ("Request Abort", "/(geography.(hypermedia.(example.sync-replace)))"), + ("Retry", "/(geography.(hypermedia.(example.retry)))"), ] ESSAYS_NAV = [ - ("sx sucks", "/etc/essays/sx-sucks"), - ("Why S-Expressions", "/etc/essays/why-sexps"), - ("The htmx/React Hybrid", "/etc/essays/htmx-react-hybrid"), - ("On-Demand CSS", "/etc/essays/on-demand-css"), - ("Client Reactivity", "/etc/essays/client-reactivity"), - ("SX Native", "/etc/essays/sx-native"), - ("The SX Manifesto", "/etc/philosophy/sx-manifesto"), - ("Tail-Call Optimization", "/etc/essays/tail-call-optimization"), - ("Continuations", "/etc/essays/continuations"), + ("sx sucks", "/(etc.(essay.sx-sucks))"), + ("Why S-Expressions", "/(etc.(essay.why-sexps))"), + ("The htmx/React Hybrid", "/(etc.(essay.htmx-react-hybrid))"), + ("On-Demand CSS", "/(etc.(essay.on-demand-css))"), + ("Client Reactivity", "/(etc.(essay.client-reactivity))"), + ("SX Native", "/(etc.(essay.sx-native))"), + ("The SX Manifesto", "/(etc.(philosophy.sx-manifesto))"), + ("Tail-Call Optimization", "/(etc.(essay.tail-call-optimization))"), + ("Continuations", "/(etc.(essay.continuations))"), ] MAIN_NAV = [ - ("Docs", "/language/docs/introduction"), - ("Reference", "/geography/hypermedia/reference/"), - ("Protocols", "/applications/protocols/wire-format"), - ("Examples", "/geography/hypermedia/examples/click-to-load"), - ("Essays", "/etc/essays/sx-sucks"), + ("Docs", "/(language.(doc.introduction))"), + ("Reference", "/(geography.(hypermedia.(reference)))"), + ("Protocols", "/(applications.(protocol.wire-format))"), + ("Examples", "/(geography.(hypermedia.(example.click-to-load)))"), + ("Essays", "/(etc.(essay.sx-sucks))"), ] # --------------------------------------------------------------------------- @@ -744,7 +744,7 @@ ATTR_DETAILS: dict[str, dict] = { ), "demo": "ref-get-demo", "example": ( - '(button :sx-get "/geography/hypermedia/reference/api/time"\n' + '(button :sx-get "/(geography.(hypermedia.(reference.(api.time))))"\n' ' :sx-target "#ref-get-result"\n' ' :sx-swap "innerHTML"\n' ' "Load server time")' @@ -764,7 +764,7 @@ ATTR_DETAILS: dict[str, dict] = { ), "demo": "ref-post-demo", "example": ( - '(form :sx-post "/geography/hypermedia/reference/api/greet"\n' + '(form :sx-post "/(geography.(hypermedia.(reference.(api.greet))))"\n' ' :sx-target "#ref-post-result"\n' ' :sx-swap "innerHTML"\n' ' (input :type "text" :name "name"\n' @@ -786,7 +786,7 @@ ATTR_DETAILS: dict[str, dict] = { ), "demo": "ref-put-demo", "example": ( - '(button :sx-put "/geography/hypermedia/reference/api/status"\n' + '(button :sx-put "/(geography.(hypermedia.(reference.(api.status))))"\n' ' :sx-target "#ref-put-view"\n' ' :sx-swap "innerHTML"\n' ' :sx-vals "{\\"status\\": \\"published\\"}"\n' @@ -807,7 +807,7 @@ ATTR_DETAILS: dict[str, dict] = { ), "demo": "ref-delete-demo", "example": ( - '(button :sx-delete "/geography/hypermedia/reference/api/item/1"\n' + '(button :sx-delete "/(geography.(hypermedia.(reference.(api.(item.1)))))"\n' ' :sx-target "#ref-del-1"\n' ' :sx-swap "delete"\n' ' "Remove")' @@ -826,7 +826,7 @@ ATTR_DETAILS: dict[str, dict] = { ), "demo": "ref-patch-demo", "example": ( - '(button :sx-patch "/geography/hypermedia/reference/api/theme"\n' + '(button :sx-patch "/(geography.(hypermedia.(reference.(api.theme))))"\n' ' :sx-vals "{\\"theme\\": \\"dark\\"}"\n' ' :sx-target "#ref-patch-val"\n' ' :sx-swap "innerHTML"\n' @@ -852,7 +852,7 @@ ATTR_DETAILS: dict[str, dict] = { "example": ( '(input :type "text" :name "q"\n' ' :placeholder "Type to search..."\n' - ' :sx-get "/geography/hypermedia/reference/api/trigger-search"\n' + ' :sx-get "/(geography.(hypermedia.(reference.(api.trigger-search))))"\n' ' :sx-trigger "input changed delay:300ms"\n' ' :sx-target "#ref-trigger-result"\n' ' :sx-swap "innerHTML")' @@ -874,12 +874,12 @@ ATTR_DETAILS: dict[str, dict] = { "demo": "ref-target-demo", "example": ( ';; Two buttons targeting different elements\n' - '(button :sx-get "/geography/hypermedia/reference/api/time"\n' + '(button :sx-get "/(geography.(hypermedia.(reference.(api.time))))"\n' ' :sx-target "#ref-target-a"\n' ' :sx-swap "innerHTML"\n' ' "Update Box A")\n' '\n' - '(button :sx-get "/geography/hypermedia/reference/api/time"\n' + '(button :sx-get "/(geography.(hypermedia.(reference.(api.time))))"\n' ' :sx-target "#ref-target-b"\n' ' :sx-swap "innerHTML"\n' ' "Update Box B")' @@ -894,13 +894,13 @@ ATTR_DETAILS: dict[str, dict] = { "demo": "ref-swap-demo", "example": ( ';; Append to the end of a list\n' - '(button :sx-get "/geography/hypermedia/reference/api/swap-item"\n' + '(button :sx-get "/(geography.(hypermedia.(reference.(api.swap-item))))"\n' ' :sx-target "#ref-swap-list"\n' ' :sx-swap "beforeend"\n' ' "beforeend")\n' '\n' ';; Prepend to the start\n' - '(button :sx-get "/geography/hypermedia/reference/api/swap-item"\n' + '(button :sx-get "/(geography.(hypermedia.(reference.(api.swap-item))))"\n' ' :sx-target "#ref-swap-list"\n' ' :sx-swap "afterbegin"\n' ' "afterbegin")' @@ -921,7 +921,7 @@ ATTR_DETAILS: dict[str, dict] = { ), "demo": "ref-oob-demo", "example": ( - '(button :sx-get "/geography/hypermedia/reference/api/oob"\n' + '(button :sx-get "/(geography.(hypermedia.(reference.(api.oob))))"\n' ' :sx-target "#ref-oob-main"\n' ' :sx-swap "innerHTML"\n' ' "Update both boxes")' @@ -944,7 +944,7 @@ ATTR_DETAILS: dict[str, dict] = { ), "demo": "ref-select-demo", "example": ( - '(button :sx-get "/geography/hypermedia/reference/api/select-page"\n' + '(button :sx-get "/(geography.(hypermedia.(reference.(api.select-page))))"\n' ' :sx-target "#ref-select-result"\n' ' :sx-select "#the-content"\n' ' :sx-swap "innerHTML"\n' @@ -968,7 +968,7 @@ ATTR_DETAILS: dict[str, dict] = { ), "demo": "ref-confirm-demo", "example": ( - '(button :sx-delete "/geography/hypermedia/reference/api/item/confirm"\n' + '(button :sx-delete "/(geography.(hypermedia.(reference.(api.(item.confirm)))))"\n' ' :sx-target "#ref-confirm-item"\n' ' :sx-swap "delete"\n' ' :sx-confirm "Are you sure you want to delete this file?"\n' @@ -1003,7 +1003,7 @@ ATTR_DETAILS: dict[str, dict] = { "example": ( '(input :type "text" :name "q"\n' ' :placeholder "Type quickly..."\n' - ' :sx-get "/geography/hypermedia/reference/api/slow-echo"\n' + ' :sx-get "/(geography.(hypermedia.(reference.(api.slow-echo))))"\n' ' :sx-trigger "input changed delay:100ms"\n' ' :sx-sync "replace"\n' ' :sx-target "#ref-sync-result"\n' @@ -1024,7 +1024,7 @@ ATTR_DETAILS: dict[str, dict] = { ), "demo": "ref-encoding-demo", "example": ( - '(form :sx-post "/geography/hypermedia/reference/api/upload-name"\n' + '(form :sx-post "/(geography.(hypermedia.(reference.(api.upload-name))))"\n' ' :sx-encoding "multipart/form-data"\n' ' :sx-target "#ref-encoding-result"\n' ' :sx-swap "innerHTML"\n' @@ -1044,7 +1044,7 @@ ATTR_DETAILS: dict[str, dict] = { ), "demo": "ref-headers-demo", "example": ( - '(button :sx-get "/geography/hypermedia/reference/api/echo-headers"\n' + '(button :sx-get "/(geography.(hypermedia.(reference.(api.echo-headers))))"\n' ' :sx-headers \'{"X-Custom-Token": "abc123", "X-Request-Source": "demo"}\'\n' ' :sx-target "#ref-headers-result"\n' ' :sx-swap "innerHTML"\n' @@ -1073,7 +1073,7 @@ ATTR_DETAILS: dict[str, dict] = { ' (option :value "books" "Books")\n' ' (option :value "tools" "Tools"))\n' '\n' - '(button :sx-get "/geography/hypermedia/reference/api/echo-vals"\n' + '(button :sx-get "/(geography.(hypermedia.(reference.(api.echo-vals))))"\n' ' :sx-include "#ref-inc-cat"\n' ' :sx-target "#ref-include-result"\n' ' :sx-swap "innerHTML"\n' @@ -1097,7 +1097,7 @@ ATTR_DETAILS: dict[str, dict] = { ), "demo": "ref-vals-demo", "example": ( - '(button :sx-post "/geography/hypermedia/reference/api/echo-vals"\n' + '(button :sx-post "/(geography.(hypermedia.(reference.(api.echo-vals))))"\n' ' :sx-vals \'{"source": "demo", "page": "3"}\'\n' ' :sx-target "#ref-vals-result"\n' ' :sx-swap "innerHTML"\n' @@ -1133,7 +1133,7 @@ ATTR_DETAILS: dict[str, dict] = { ';; Left box: sx works normally\n' ';; Right box: sx-disable prevents any sx behavior\n' '(div :sx-disable "true"\n' - ' (button :sx-get "/geography/hypermedia/reference/api/time"\n' + ' (button :sx-get "/(geography.(hypermedia.(reference.(api.time))))"\n' ' :sx-target "#ref-dis-b"\n' ' :sx-swap "innerHTML"\n' ' "Load")\n' @@ -1166,7 +1166,7 @@ ATTR_DETAILS: dict[str, dict] = { ), "demo": "ref-retry-demo", "example": ( - '(button :sx-get "/geography/hypermedia/reference/api/flaky"\n' + '(button :sx-get "/(geography.(hypermedia.(reference.(api.flaky))))"\n' ' :sx-target "#ref-retry-result"\n' ' :sx-swap "innerHTML"\n' ' :sx-retry "true"\n' @@ -1239,7 +1239,7 @@ ATTR_DETAILS: dict[str, dict] = { ), "demo": "ref-preload-demo", "example": ( - '(button :sx-get "/geography/hypermedia/reference/api/time"\n' + '(button :sx-get "/(geography.(hypermedia.(reference.(api.time))))"\n' ' :sx-target "#ref-preload-result"\n' ' :sx-swap "innerHTML"\n' ' :sx-preload "mouseover"\n' @@ -1275,7 +1275,7 @@ ATTR_DETAILS: dict[str, dict] = { ), "demo": "ref-indicator-demo", "example": ( - '(button :sx-get "/geography/hypermedia/reference/api/slow-echo"\n' + '(button :sx-get "/(geography.(hypermedia.(reference.(api.slow-echo))))"\n' ' :sx-target "#ref-indicator-result"\n' ' :sx-swap "innerHTML"\n' ' :sx-indicator "#ref-spinner"\n' @@ -1302,7 +1302,7 @@ ATTR_DETAILS: dict[str, dict] = { ), "demo": "ref-validate-demo", "example": ( - '(form :sx-post "/geography/hypermedia/reference/api/greet"\n' + '(form :sx-post "/(geography.(hypermedia.(reference.(api.greet))))"\n' ' :sx-target "#ref-validate-result"\n' ' :sx-swap "innerHTML"\n' ' :sx-validate "true"\n' @@ -1340,7 +1340,7 @@ ATTR_DETAILS: dict[str, dict] = { ), "demo": "ref-optimistic-demo", "example": ( - '(button :sx-delete "/geography/hypermedia/reference/api/item/opt1"\n' + '(button :sx-delete "/(geography.(hypermedia.(reference.(api.(item.opt1)))))"\n' ' :sx-target "#ref-opt-item"\n' ' :sx-swap "delete"\n' ' :sx-optimistic "remove"\n' @@ -1362,7 +1362,7 @@ ATTR_DETAILS: dict[str, dict] = { ), "demo": "ref-replace-url-demo", "example": ( - '(button :sx-get "/geography/hypermedia/reference/api/time"\n' + '(button :sx-get "/(geography.(hypermedia.(reference.(api.time))))"\n' ' :sx-target "#ref-replurl-result"\n' ' :sx-swap "innerHTML"\n' ' :sx-replace-url "true"\n' @@ -1378,7 +1378,7 @@ ATTR_DETAILS: dict[str, dict] = { ), "demo": "ref-disabled-elt-demo", "example": ( - '(button :sx-get "/geography/hypermedia/reference/api/slow-echo"\n' + '(button :sx-get "/(geography.(hypermedia.(reference.(api.slow-echo))))"\n' ' :sx-target "#ref-diselt-result"\n' ' :sx-swap "innerHTML"\n' ' :sx-disabled-elt "this"\n' @@ -1394,7 +1394,7 @@ ATTR_DETAILS: dict[str, dict] = { ), "demo": "ref-prompt-demo", "example": ( - '(button :sx-get "/geography/hypermedia/reference/api/prompt-echo"\n' + '(button :sx-get "/(geography.(hypermedia.(reference.(api.prompt-echo))))"\n' ' :sx-target "#ref-prompt-result"\n' ' :sx-swap "innerHTML"\n' ' :sx-prompt "Enter your name:"\n' @@ -1414,7 +1414,7 @@ ATTR_DETAILS: dict[str, dict] = { ), "demo": "ref-params-demo", "example": ( - '(form :sx-post "/geography/hypermedia/reference/api/echo-vals"\n' + '(form :sx-post "/(geography.(hypermedia.(reference.(api.echo-vals))))"\n' ' :sx-target "#ref-params-result"\n' ' :sx-swap "innerHTML"\n' ' :sx-params "name"\n' @@ -1433,7 +1433,7 @@ ATTR_DETAILS: dict[str, dict] = { ), "demo": "ref-sse-demo", "example": ( - '(div :sx-sse "/geography/hypermedia/reference/api/sse-time"\n' + '(div :sx-sse "/(geography.(hypermedia.(reference.(api.sse-time))))"\n' ' :sx-sse-swap "time"\n' ' :sx-target "#ref-sse-result"\n' ' :sx-swap "innerHTML"\n' diff --git a/sx/sx/examples-content.sx b/sx/sx/examples-content.sx index 07f097d..f9a9247 100644 --- a/sx/sx/examples-content.sx +++ b/sx/sx/examples-content.sx @@ -8,7 +8,7 @@ :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 \"/geography/hypermedia/examples/api/click\"\n :sx-target \"#click-result\"\n :sx-swap \"innerHTML\"\n \"Load content\")" + :sx-code "(button\n :sx-get \"/(geography.(hypermedia.(example.(api.click))))\"\n :sx-target \"#click-result\"\n :sx-swap \"innerHTML\"\n \"Load content\")" :handler-code (handler-source "ex-click") :comp-placeholder-id "click-comp" :wire-placeholder-id "click-wire" @@ -20,7 +20,7 @@ :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 \"/geography/hypermedia/examples/api/form\"\n :sx-target \"#form-result\"\n :sx-swap \"innerHTML\"\n (input :type \"text\" :name \"name\")\n (button :type \"submit\" \"Submit\"))" + :sx-code "(form\n :sx-post \"/(geography.(hypermedia.(example.(api.form))))\"\n :sx-target \"#form-result\"\n :sx-swap \"innerHTML\"\n (input :type \"text\" :name \"name\")\n (button :type \"submit\" \"Submit\"))" :handler-code (handler-source "ex-form") :comp-placeholder-id "form-comp" :wire-placeholder-id "form-wire")) @@ -31,7 +31,7 @@ :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 \"/geography/hypermedia/examples/api/poll\"\n :sx-trigger \"load, every 2s\"\n :sx-swap \"innerHTML\"\n \"Loading...\")" + :sx-code "(div\n :sx-get \"/(geography.(hypermedia.(example.(api.poll))))\"\n :sx-trigger \"load, every 2s\"\n :sx-swap \"innerHTML\"\n \"Loading...\")" :handler-code (handler-source "ex-poll") :comp-placeholder-id "poll-comp" :wire-placeholder-id "poll-wire" @@ -48,7 +48,7 @@ (list "3" "Write documentation") (list "4" "Deploy to production") (list "5" "Add unit tests"))) - :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\")" + :sx-code "(button\n :sx-delete \"/(geography.(hypermedia.(example.(api.(delete.1)))))\"\n :sx-target \"#row-1\"\n :sx-swap \"outerHTML\"\n :sx-confirm \"Delete this item?\"\n \"delete\")" :handler-code (handler-source "ex-delete") :comp-placeholder-id "delete-comp" :wire-placeholder-id "delete-wire" @@ -73,7 +73,7 @@ :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 \"/geography/hypermedia/examples/api/oob\"\n :sx-target \"#oob-box-a\"\n :sx-swap \"innerHTML\"\n \"Update both boxes\")" + :sx-code ";; Button targets Box A\n(button\n :sx-get \"/(geography.(hypermedia.(example.(api.oob))))\"\n :sx-target \"#oob-box-a\"\n :sx-swap \"innerHTML\"\n \"Update both boxes\")" :handler-code (handler-source "ex-oob") :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.")) @@ -84,7 +84,7 @@ :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 \"/geography/hypermedia/examples/api/lazy\"\n :sx-trigger \"load\"\n :sx-swap \"innerHTML\"\n (div :class \"animate-pulse\" \"Loading...\"))" + :sx-code "(div\n :sx-get \"/(geography.(hypermedia.(example.(api.lazy))))\"\n :sx-trigger \"load\"\n :sx-swap \"innerHTML\"\n (div :class \"animate-pulse\" \"Loading...\"))" :handler-code (handler-source "ex-lazy") :comp-placeholder-id "lazy-comp" :wire-placeholder-id "lazy-wire")) @@ -95,7 +95,7 @@ :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 \"/geography/hypermedia/examples/api/scroll?page=2\"\n :sx-trigger \"intersect once\"\n :sx-target \"#scroll-items\"\n :sx-swap \"beforeend\"\n \"Loading more...\")" + :sx-code "(div :id \"scroll-sentinel\"\n :sx-get \"/(geography.(hypermedia.(example.(api.scroll))))?page=2\"\n :sx-trigger \"intersect once\"\n :sx-target \"#scroll-items\"\n :sx-swap \"beforeend\"\n \"Loading more...\")" :handler-code (handler-source "ex-scroll") :comp-placeholder-id "scroll-comp" :wire-placeholder-id "scroll-wire")) @@ -106,7 +106,7 @@ :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 \"/geography/hypermedia/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\")" + :sx-code ";; Start the job\n(button\n :sx-post \"/(geography.(hypermedia.(example.(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 \"/(geography.(hypermedia.(example.(api.progress-status))))?job=ID\"\n :sx-trigger \"load delay:500ms\"\n :sx-target \"#progress-target\"\n :sx-swap \"innerHTML\")" :handler-code (str (handler-source "ex-progress-start") "\n\n" (handler-source "ex-progress-status")) :comp-placeholder-id "progress-comp" :wire-placeholder-id "progress-wire")) @@ -117,7 +117,7 @@ :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 \"/geography/hypermedia/examples/api/search\"\n :sx-trigger \"keyup delay:300ms changed\"\n :sx-target \"#search-results\"\n :sx-swap \"innerHTML\"\n :placeholder \"Search...\")" + :sx-code "(input :type \"text\" :name \"q\"\n :sx-get \"/(geography.(hypermedia.(example.(api.search))))\"\n :sx-trigger \"keyup delay:300ms changed\"\n :sx-target \"#search-results\"\n :sx-swap \"innerHTML\"\n :placeholder \"Search...\")" :handler-code (handler-source "ex-search") :comp-placeholder-id "search-comp" :wire-placeholder-id "search-wire")) @@ -128,7 +128,7 @@ :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 \"/geography/hypermedia/examples/api/validate\"\n :sx-trigger \"blur\"\n :sx-target \"#email-feedback\"\n :sx-swap \"innerHTML\"\n :placeholder \"user@example.com\")" + :sx-code "(input :type \"text\" :name \"email\"\n :sx-get \"/(geography.(hypermedia.(example.(api.validate))))\"\n :sx-trigger \"blur\"\n :sx-target \"#email-feedback\"\n :sx-swap \"innerHTML\"\n :placeholder \"user@example.com\")" :handler-code (handler-source "ex-validate") :comp-placeholder-id "validate-comp" :wire-placeholder-id "validate-wire")) @@ -139,7 +139,7 @@ :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 \"/geography/hypermedia/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\"))" + :sx-code "(select :name \"category\"\n :sx-get \"/(geography.(hypermedia.(example.(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 (handler-source "ex-values") :comp-placeholder-id "values-comp" :wire-placeholder-id "values-wire")) @@ -150,7 +150,7 @@ :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 \"/geography/hypermedia/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\"))" + :sx-code "(form :id \"reset-form\"\n :sx-post \"/(geography.(hypermedia.(example.(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 (handler-source "ex-reset-submit") :comp-placeholder-id "reset-comp" :wire-placeholder-id "reset-wire")) @@ -165,7 +165,7 @@ (list "2" "Widget B" "24.50" "89") (list "3" "Widget C" "12.00" "305") (list "4" "Widget D" "45.00" "67"))) - :sx-code "(button\n :sx-get \"/geography/hypermedia/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 \"/geography/hypermedia/examples/api/editrow/1\"\n :sx-target \"#erow-1\"\n :sx-swap \"outerHTML\"\n :sx-include \"#erow-1\"\n \"save\")" + :sx-code "(button\n :sx-get \"/(geography.(hypermedia.(example.(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 \"/(geography.(hypermedia.(example.(api.(editrow.1)))))\"\n :sx-target \"#erow-1\"\n :sx-swap \"outerHTML\"\n :sx-include \"#erow-1\"\n \"save\")" :handler-code (str (handler-source "ex-editrow-form") "\n\n" (handler-source "ex-editrow-save")) :comp-placeholder-id "editrow-comp" :wire-placeholder-id "editrow-wire")) @@ -181,7 +181,7 @@ (list "3" "Carol Zhang" "carol@example.com" "active") (list "4" "Dan Okafor" "dan@example.com" "inactive") (list "5" "Eve Larsson" "eve@example.com" "active"))) - :sx-code "(button\n :sx-post \"/geography/hypermedia/examples/api/bulk?action=activate\"\n :sx-target \"#bulk-table\"\n :sx-swap \"innerHTML\"\n :sx-include \"#bulk-form\"\n \"Activate\")" + :sx-code "(button\n :sx-post \"/(geography.(hypermedia.(example.(api.bulk))))?action=activate\"\n :sx-target \"#bulk-table\"\n :sx-swap \"innerHTML\"\n :sx-include \"#bulk-form\"\n \"Activate\")" :handler-code (handler-source "ex-bulk") :comp-placeholder-id "bulk-comp" :wire-placeholder-id "bulk-wire")) @@ -192,7 +192,7 @@ :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\")" + :sx-code ";; Append to end\n(button :sx-post \"/(geography.(hypermedia.(example.(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 \"/(geography.(hypermedia.(example.(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 \"/(geography.(hypermedia.(example.(api.swap-log))))?mode=none\"\n :sx-target \"#swap-log\" :sx-swap \"none\"\n \"Silent Ping\")" :handler-code (handler-source "ex-swap-log") :wire-placeholder-id "swap-wire")) @@ -202,7 +202,7 @@ :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 \"/geography/hypermedia/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 \"/geography/hypermedia/examples/api/dashboard\"\n :sx-target \"#filter-target\"\n :sx-swap \"innerHTML\"\n \"Full Dashboard\")" + :sx-code ";; Pick just the stats section from the response\n(button\n :sx-get \"/(geography.(hypermedia.(example.(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 \"/(geography.(hypermedia.(example.(api.dashboard))))\"\n :sx-target \"#filter-target\"\n :sx-swap \"innerHTML\"\n \"Full Dashboard\")" :handler-code (handler-source "ex-dashboard") :wire-placeholder-id "filter-wire")) @@ -212,7 +212,7 @@ :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 \"/geography/hypermedia/examples/api/tabs/tab1\"\n :sx-target \"#tab-content\"\n :sx-swap \"innerHTML\"\n :sx-push-url \"/geography/hypermedia/examples/tabs?tab=tab1\"\n \"Overview\")" + :sx-code "(button\n :sx-get \"/(geography.(hypermedia.(example.(api.(tabs.tab1)))))\"\n :sx-target \"#tab-content\"\n :sx-swap \"innerHTML\"\n :sx-push-url \"/(geography.(hypermedia.(example.tabs)))?tab=tab1\"\n \"Overview\")" :handler-code (handler-source "ex-tabs") :wire-placeholder-id "tabs-wire")) @@ -222,7 +222,7 @@ :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 \"/geography/hypermedia/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!\")))" + :sx-code "(button\n :sx-get \"/(geography.(hypermedia.(example.(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 (handler-source "ex-animate") :comp-placeholder-id "anim-comp" :wire-placeholder-id "anim-wire")) @@ -233,7 +233,7 @@ :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 \"/geography/hypermedia/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 \"/geography/hypermedia/examples/api/dialog/close\"\n :sx-target \"#dialog-container\"\n :sx-swap \"innerHTML\"\n \"Close\")" + :sx-code "(button\n :sx-get \"/(geography.(hypermedia.(example.(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 \"/(geography.(hypermedia.(example.(api.dialog-close))))\"\n :sx-target \"#dialog-container\"\n :sx-swap \"innerHTML\"\n \"Close\")" :handler-code (str (handler-source "ex-dialog") "\n\n" (handler-source "ex-dialog-close")) :comp-placeholder-id "dialog-comp" :wire-placeholder-id "dialog-wire")) @@ -244,7 +244,7 @@ :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 \"/geography/hypermedia/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...\")" + :sx-code "(div :id \"kbd-target\"\n :sx-get \"/(geography.(hypermedia.(example.(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 (handler-source "ex-keyboard") :comp-placeholder-id "kbd-comp" :wire-placeholder-id "kbd-wire")) @@ -255,7 +255,7 @@ :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 (~put-patch-demo :name "Ada Lovelace" :email "ada@example.com" :role "Engineer") - :sx-code ";; Replace entire resource\n(form :sx-put \"/geography/hypermedia/examples/api/putpatch\"\n :sx-target \"#pp-target\" :sx-swap \"innerHTML\"\n (input :name \"name\") (input :name \"email\")\n (button \"Save All (PUT)\"))" + :sx-code ";; Replace entire resource\n(form :sx-put \"/(geography.(hypermedia.(example.(api.putpatch))))\"\n :sx-target \"#pp-target\" :sx-swap \"innerHTML\"\n (input :name \"name\") (input :name \"email\")\n (button \"Save All (PUT)\"))" :handler-code (str (handler-source "ex-pp-edit-all") "\n\n" (handler-source "ex-pp-put")) :comp-placeholder-id "pp-comp" :wire-placeholder-id "pp-wire")) @@ -266,7 +266,7 @@ :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 \"/geography/hypermedia/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\"))" + :sx-code "(form\n :sx-post \"/(geography.(hypermedia.(example.(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 (handler-source "ex-json-echo") :comp-placeholder-id "json-comp" :wire-placeholder-id "json-wire")) @@ -277,7 +277,7 @@ :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 \"/geography/hypermedia/examples/api/echo-vals\"\n :sx-vals \"{\\\"source\\\": \\\"button\\\"}\"\n \"Send with vals\")\n\n;; Send custom headers\n(button\n :sx-get \"/geography/hypermedia/examples/api/echo-headers\"\n :sx-headers {:X-Custom-Token \"abc123\"}\n \"Send with headers\")" + :sx-code ";; Send extra values with the request\n(button\n :sx-get \"/(geography.(hypermedia.(example.(api.echo-vals))))\"\n :sx-vals \"{\\\"source\\\": \\\"button\\\"}\"\n \"Send with vals\")\n\n;; Send custom headers\n(button\n :sx-get \"/(geography.(hypermedia.(example.(api.echo-headers))))\"\n :sx-headers {:X-Custom-Token \"abc123\"}\n \"Send with headers\")" :handler-code (str (handler-source "ex-echo-vals") "\n\n" (handler-source "ex-echo-headers")) :comp-placeholder-id "vals-comp" :wire-placeholder-id "vals-wire")) @@ -288,7 +288,7 @@ :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 \"/geography/hypermedia/examples/api/slow\"\n :sx-target \"#loading-result\"\n (span :class \"sx-spinner animate-spin\" \"...\")\n \"Load slow endpoint\")" + :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 \"/(geography.(hypermedia.(example.(api.slow))))\"\n :sx-target \"#loading-result\"\n (span :class \"sx-spinner animate-spin\" \"...\")\n \"Load slow endpoint\")" :handler-code (handler-source "ex-slow") :comp-placeholder-id "loading-comp" :wire-placeholder-id "loading-wire")) @@ -299,7 +299,7 @@ :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 \"/geography/hypermedia/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...\")" + :sx-code "(input :type \"text\" :name \"q\"\n :sx-get \"/(geography.(hypermedia.(example.(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 (handler-source "ex-slow-search") :comp-placeholder-id "sync-comp" :wire-placeholder-id "sync-wire")) @@ -310,7 +310,7 @@ :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 \"/geography/hypermedia/examples/api/flaky\"\n :sx-target \"#retry-result\"\n :sx-swap \"innerHTML\"\n :sx-retry \"exponential:1000:8000\"\n \"Call flaky endpoint\")" + :sx-code "(button\n :sx-get \"/(geography.(hypermedia.(example.(api.flaky))))\"\n :sx-target \"#retry-result\"\n :sx-swap \"innerHTML\"\n :sx-retry \"exponential:1000:8000\"\n \"Call flaky endpoint\")" :handler-code (handler-source "ex-flaky") :comp-placeholder-id "retry-comp" :wire-placeholder-id "retry-wire")) diff --git a/sx/sx/handlers/examples.sx b/sx/sx/handlers/examples.sx index 68ac88d..99a2593 100644 --- a/sx/sx/handlers/examples.sx +++ b/sx/sx/handlers/examples.sx @@ -55,7 +55,7 @@ ;; -------------------------------------------------------------------------- (defhandler ex-click - :path "/geography/hypermedia/examples/api/click" + :path "/(geography.(hypermedia.(example.(api.click))))" :method :get :returns "element" (&key) @@ -73,7 +73,7 @@ ;; -------------------------------------------------------------------------- (defhandler ex-form - :path "/geography/hypermedia/examples/api/form" + :path "/(geography.(hypermedia.(example.(api.form))))" :method :post :csrf false :returns "element" @@ -92,7 +92,7 @@ ;; -------------------------------------------------------------------------- (defhandler ex-poll - :path "/geography/hypermedia/examples/api/poll" + :path "/(geography.(hypermedia.(example.(api.poll))))" :method :get :returns "element" (&key) @@ -113,7 +113,7 @@ ;; -------------------------------------------------------------------------- (defhandler ex-delete - :path "/geography/hypermedia/examples/api/delete/" + :path "/(geography.(hypermedia.(example.(api.(delete.)))))" :method :delete :csrf false :returns "element" @@ -130,7 +130,7 @@ ;; -------------------------------------------------------------------------- (defhandler ex-edit-form - :path "/geography/hypermedia/examples/api/edit" + :path "/(geography.(hypermedia.(example.(api.edit))))" :method :get :returns "element" (&key) @@ -143,7 +143,7 @@ :text (str "(~inline-edit-form :value \"" value "\")"))))) (defhandler ex-edit-save - :path "/geography/hypermedia/examples/api/edit" + :path "/(geography.(hypermedia.(example.(api.edit))))" :method :post :csrf false :returns "element" @@ -157,7 +157,7 @@ :text (str "(~inline-view :value \"" value "\")"))))) (defhandler ex-edit-cancel - :path "/geography/hypermedia/examples/api/edit/cancel" + :path "/(geography.(hypermedia.(example.(api.edit-cancel))))" :method :get :returns "element" (&key) @@ -175,7 +175,7 @@ ;; -------------------------------------------------------------------------- (defhandler ex-oob - :path "/geography/hypermedia/examples/api/oob" + :path "/(geography.(hypermedia.(example.(api.oob))))" :method :get :returns "element" (&key) @@ -195,7 +195,7 @@ ;; -------------------------------------------------------------------------- (defhandler ex-lazy - :path "/geography/hypermedia/examples/api/lazy" + :path "/(geography.(hypermedia.(example.(api.lazy))))" :method :get :returns "element" (&key) @@ -213,7 +213,7 @@ ;; -------------------------------------------------------------------------- (defhandler ex-scroll - :path "/geography/hypermedia/examples/api/scroll" + :path "/(geography.(hypermedia.(example.(api.scroll))))" :method :get :returns "element" (&key) @@ -227,7 +227,7 @@ (range start (+ start 5))) (if (<= (+ pg 1) 6) (div :id "scroll-sentinel" - :sx-get (str "/geography/hypermedia/examples/api/scroll?page=" (+ pg 1)) + :sx-get (str "/(geography.(hypermedia.(example.(api.scroll))))?page=" (+ pg 1)) :sx-trigger "intersect once" :sx-target "#scroll-items" :sx-swap "beforeend" @@ -244,7 +244,7 @@ ;; -------------------------------------------------------------------------- (defhandler ex-progress-start - :path "/geography/hypermedia/examples/api/progress/start" + :path "/(geography.(hypermedia.(example.(api.progress-start))))" :method :post :csrf false :returns "element" @@ -261,7 +261,7 @@ :text (str "(~progress-status :percent 0 :job-id \"" job-id "\")")))))) (defhandler ex-progress-status - :path "/geography/hypermedia/examples/api/progress/status" + :path "/(geography.(hypermedia.(example.(api.progress-status))))" :method :get :returns "element" (&key) @@ -282,7 +282,7 @@ ;; -------------------------------------------------------------------------- (defhandler ex-search - :path "/geography/hypermedia/examples/api/search" + :path "/(geography.(hypermedia.(example.(api.search))))" :method :get :returns "element" (&key) @@ -304,7 +304,7 @@ ;; -------------------------------------------------------------------------- (defhandler ex-validate - :path "/geography/hypermedia/examples/api/validate" + :path "/(geography.(hypermedia.(example.(api.validate))))" :method :get :returns "element" (&key) @@ -331,7 +331,7 @@ :text (nth result 1)))))) (defhandler ex-validate-submit - :path "/geography/hypermedia/examples/api/validate/submit" + :path "/(geography.(hypermedia.(example.(api.validate-submit))))" :method :post :csrf false :returns "element" @@ -347,7 +347,7 @@ ;; -------------------------------------------------------------------------- (defhandler ex-values - :path "/geography/hypermedia/examples/api/values" + :path "/(geography.(hypermedia.(example.(api.values))))" :method :get :returns "element" (&key) @@ -367,7 +367,7 @@ ;; -------------------------------------------------------------------------- (defhandler ex-reset-submit - :path "/geography/hypermedia/examples/api/reset-submit" + :path "/(geography.(hypermedia.(example.(api.reset-submit))))" :method :post :csrf false :returns "element" @@ -387,7 +387,7 @@ ;; -------------------------------------------------------------------------- (defhandler ex-editrow-form - :path "/geography/hypermedia/examples/api/editrow/" + :path "/(geography.(hypermedia.(example.(api.(editrow.)))))" :method :get :returns "element" (&key row-id) @@ -402,7 +402,7 @@ :text (str "(~edit-row-form :id \"" (get row "id") "\" ...)")))))) (defhandler ex-editrow-save - :path "/geography/hypermedia/examples/api/editrow/" + :path "/(geography.(hypermedia.(example.(api.(editrow.)))))" :method :post :csrf false :returns "element" @@ -420,7 +420,7 @@ :text (str "(~edit-row-view :id \"" row-id "\" ...)"))))) (defhandler ex-editrow-cancel - :path "/geography/hypermedia/examples/api/editrow//cancel" + :path "/(geography.(hypermedia.(example.(api.(editrow-cancel.)))))" :method :get :returns "element" (&key row-id) @@ -439,7 +439,7 @@ ;; -------------------------------------------------------------------------- (defhandler ex-bulk - :path "/geography/hypermedia/examples/api/bulk" + :path "/(geography.(hypermedia.(example.(api.bulk))))" :method :post :csrf false :returns "element" @@ -476,7 +476,7 @@ ;; -------------------------------------------------------------------------- (defhandler ex-swap-log - :path "/geography/hypermedia/examples/api/swap-log" + :path "/(geography.(hypermedia.(example.(api.swap-log))))" :method :post :csrf false :returns "element" @@ -500,7 +500,7 @@ ;; -------------------------------------------------------------------------- (defhandler ex-dashboard - :path "/geography/hypermedia/examples/api/dashboard" + :path "/(geography.(hypermedia.(example.(api.dashboard))))" :method :get :returns "element" (&key) @@ -530,7 +530,7 @@ ;; -------------------------------------------------------------------------- (defhandler ex-tabs - :path "/geography/hypermedia/examples/api/tabs/" + :path "/(geography.(hypermedia.(example.(api.(tabs.)))))" :method :get :returns "element" (&key tab) @@ -551,7 +551,7 @@ ;; -------------------------------------------------------------------------- (defhandler ex-animate - :path "/geography/hypermedia/examples/api/animate" + :path "/(geography.(hypermedia.(example.(api.animate))))" :method :get :returns "element" (&key) @@ -571,7 +571,7 @@ ;; -------------------------------------------------------------------------- (defhandler ex-dialog - :path "/geography/hypermedia/examples/api/dialog" + :path "/(geography.(hypermedia.(example.(api.dialog))))" :method :get :returns "element" (&key) @@ -584,7 +584,7 @@ :text "(~dialog-modal :title \"Confirm Action\" :message \"...\")"))) (defhandler ex-dialog-close - :path "/geography/hypermedia/examples/api/dialog/close" + :path "/(geography.(hypermedia.(example.(api.dialog-close))))" :method :get :returns "element" (&key) @@ -598,7 +598,7 @@ ;; -------------------------------------------------------------------------- (defhandler ex-keyboard - :path "/geography/hypermedia/examples/api/keyboard" + :path "/(geography.(hypermedia.(example.(api.keyboard))))" :method :get :returns "element" (&key) @@ -617,7 +617,7 @@ ;; -------------------------------------------------------------------------- (defhandler ex-pp-edit-all - :path "/geography/hypermedia/examples/api/putpatch/edit-all" + :path "/(geography.(hypermedia.(example.(api.putpatch-edit-all))))" :method :get :returns "element" (&key) @@ -631,7 +631,7 @@ :text (str "(~pp-form-full :name \"" (get p "name") "\" ...)"))))) (defhandler ex-pp-put - :path "/geography/hypermedia/examples/api/putpatch" + :path "/(geography.(hypermedia.(example.(api.putpatch))))" :method :put :csrf false :returns "element" @@ -648,7 +648,7 @@ :text (str "(~pp-view :name \"" name "\" ...)"))))) (defhandler ex-pp-cancel - :path "/geography/hypermedia/examples/api/putpatch/cancel" + :path "/(geography.(hypermedia.(example.(api.putpatch-cancel))))" :method :get :returns "element" (&key) @@ -667,7 +667,7 @@ ;; -------------------------------------------------------------------------- (defhandler ex-json-echo - :path "/geography/hypermedia/examples/api/json-echo" + :path "/(geography.(hypermedia.(example.(api.json-echo))))" :method :post :csrf false :returns "element" @@ -688,7 +688,7 @@ ;; -------------------------------------------------------------------------- (defhandler ex-echo-vals - :path "/geography/hypermedia/examples/api/echo-vals" + :path "/(geography.(hypermedia.(example.(api.echo-vals))))" :method :get :returns "element" (&key) @@ -705,7 +705,7 @@ :text (str "(~echo-result :label \"values\" :items (list ...))"))))))) (defhandler ex-echo-headers - :path "/geography/hypermedia/examples/api/echo-headers" + :path "/(geography.(hypermedia.(example.(api.echo-headers))))" :method :get :returns "element" (&key) @@ -725,7 +725,7 @@ ;; -------------------------------------------------------------------------- (defhandler ex-slow - :path "/geography/hypermedia/examples/api/slow" + :path "/(geography.(hypermedia.(example.(api.slow))))" :method :get :returns "element" (&key) @@ -744,7 +744,7 @@ ;; -------------------------------------------------------------------------- (defhandler ex-slow-search - :path "/geography/hypermedia/examples/api/slow-search" + :path "/(geography.(hypermedia.(example.(api.slow-search))))" :method :get :returns "element" (&key) @@ -764,7 +764,7 @@ ;; -------------------------------------------------------------------------- (defhandler ex-flaky - :path "/geography/hypermedia/examples/api/flaky" + :path "/(geography.(hypermedia.(example.(api.flaky))))" :method :get :returns "element" (&key) diff --git a/sx/sx/handlers/ref-api.sx b/sx/sx/handlers/ref-api.sx index 19826b2..155325a 100644 --- a/sx/sx/handlers/ref-api.sx +++ b/sx/sx/handlers/ref-api.sx @@ -6,7 +6,7 @@ ;; --- sx-get demo: server time --- (defhandler ref-time - :path "/geography/hypermedia/reference/api/time" + :path "/(geography.(hypermedia.(reference.(api.time))))" :method :get :returns "element" (&key) @@ -19,7 +19,7 @@ ;; --- sx-post demo: greet --- (defhandler ref-greet - :path "/geography/hypermedia/reference/api/greet" + :path "/(geography.(hypermedia.(reference.(api.greet))))" :method :post :csrf false :returns "element" @@ -33,7 +33,7 @@ ;; --- sx-put demo: status update --- (defhandler ref-status - :path "/geography/hypermedia/reference/api/status" + :path "/(geography.(hypermedia.(reference.(api.status))))" :method :put :csrf false :returns "element" @@ -47,7 +47,7 @@ ;; --- sx-patch demo: theme --- (defhandler ref-theme - :path "/geography/hypermedia/reference/api/theme" + :path "/(geography.(hypermedia.(reference.(api.theme))))" :method :patch :csrf false :returns "element" @@ -61,7 +61,7 @@ ;; --- sx-delete demo --- (defhandler ref-delete-item - :path "/geography/hypermedia/reference/api/item/" + :path "/(geography.(hypermedia.(reference.(api.(item.)))))" :method :delete :csrf false :returns "element" @@ -72,7 +72,7 @@ ;; --- sx-trigger demo: search --- (defhandler ref-trigger-search - :path "/geography/hypermedia/reference/api/trigger-search" + :path "/(geography.(hypermedia.(reference.(api.trigger-search))))" :method :get :returns "element" (&key) @@ -89,7 +89,7 @@ ;; --- sx-swap demo --- (defhandler ref-swap-item - :path "/geography/hypermedia/reference/api/swap-item" + :path "/(geography.(hypermedia.(reference.(api.swap-item))))" :method :get :returns "element" (&key) @@ -102,7 +102,7 @@ ;; --- sx-swap-oob demo --- (defhandler ref-oob - :path "/geography/hypermedia/reference/api/oob" + :path "/(geography.(hypermedia.(reference.(api.oob))))" :method :get :returns "element" (&key) @@ -117,7 +117,7 @@ ;; --- sx-select demo --- (defhandler ref-select-page - :path "/geography/hypermedia/reference/api/select-page" + :path "/(geography.(hypermedia.(reference.(api.select-page))))" :method :get :returns "element" (&key) @@ -134,7 +134,7 @@ ;; --- sx-sync demo: slow echo --- (defhandler ref-slow-echo - :path "/geography/hypermedia/reference/api/slow-echo" + :path "/(geography.(hypermedia.(reference.(api.slow-echo))))" :method :get :returns "element" (&key) @@ -148,7 +148,7 @@ ;; --- sx-prompt demo --- (defhandler ref-prompt-echo - :path "/geography/hypermedia/reference/api/prompt-echo" + :path "/(geography.(hypermedia.(reference.(api.prompt-echo))))" :method :get :returns "element" (&key) @@ -161,7 +161,7 @@ ;; --- Error demo --- (defhandler ref-error-500 - :path "/geography/hypermedia/reference/api/error-500" + :path "/(geography.(hypermedia.(reference.(api.error-500))))" :method :get :returns "nil" (&key) @@ -175,7 +175,7 @@ ;; --- sx-encoding demo: file upload name --- (defhandler ref-upload-name - :path "/geography/hypermedia/reference/api/upload-name" + :path "/(geography.(hypermedia.(reference.(api.upload-name))))" :method :post :csrf false :returns "element" @@ -190,7 +190,7 @@ ;; --- sx-headers demo: echo custom headers --- (defhandler ref-echo-headers - :path "/geography/hypermedia/reference/api/echo-headers" + :path "/(geography.(hypermedia.(reference.(api.echo-headers))))" :method :get :returns "element" (&key) @@ -214,7 +214,7 @@ ;; --- sx-include demo: echo GET query params --- (defhandler ref-echo-vals-get - :path "/geography/hypermedia/reference/api/echo-vals" + :path "/(geography.(hypermedia.(reference.(api.echo-vals))))" :method :get :returns "element" (&key) @@ -235,7 +235,7 @@ ;; --- sx-vals demo: echo POST form values --- (defhandler ref-echo-vals-post - :path "/geography/hypermedia/reference/api/echo-vals" + :path "/(geography.(hypermedia.(reference.(api.echo-vals))))" :method :post :csrf false :returns "element" @@ -257,7 +257,7 @@ ;; --- sx-retry demo: flaky endpoint (fails 2/3 times) --- (defhandler ref-flaky - :path "/geography/hypermedia/reference/api/flaky" + :path "/(geography.(hypermedia.(reference.(api.flaky))))" :method :get :returns "element" (&key) @@ -275,7 +275,7 @@ ;; --- sx-trigger-event demo: response header triggers --- (defhandler ref-trigger-event - :path "/geography/hypermedia/reference/api/trigger-event" + :path "/(geography.(hypermedia.(reference.(api.trigger-event))))" :method :get :returns "element" (&key) @@ -287,7 +287,7 @@ ;; --- sx-retarget demo: response header retargets --- (defhandler ref-retarget - :path "/geography/hypermedia/reference/api/retarget" + :path "/(geography.(hypermedia.(reference.(api.retarget))))" :method :get :returns "element" (&key) diff --git a/sx/sx/reactive-islands/marshes.sx b/sx/sx/reactive-islands/marshes.sx index 4100461..243ff9f 100644 --- a/sx/sx/reactive-islands/marshes.sx +++ b/sx/sx/reactive-islands/marshes.sx @@ -291,7 +291,7 @@ (~demo-marsh-product) - (~doc-code :code (highlight ";; Island with a store-backed price signal\n(defisland ~demo-marsh-product ()\n (let ((price (def-store \"demo-price\" (fn () (signal 19.99))))\n (qty (signal 1))\n (total (computed (fn () (* (deref price) (deref qty))))))\n (div\n ;; Reactive price display — updates when store changes\n (span \"$\" (deref price))\n (span \"Qty:\") (button \"-\") (span (deref qty)) (button \"+\")\n (span \"Total: $\" (deref total))\n\n ;; Fetch from server — response arrives as hypermedia\n (button :sx-get \"/geography/reactive/api/flash-sale\"\n :sx-target \"#marsh-server-msg\"\n :sx-swap \"innerHTML\"\n \"Fetch Price\")\n ;; Server response lands here:\n (div :id \"marsh-server-msg\"))))" "lisp")) + (~doc-code :code (highlight ";; Island with a store-backed price signal\n(defisland ~demo-marsh-product ()\n (let ((price (def-store \"demo-price\" (fn () (signal 19.99))))\n (qty (signal 1))\n (total (computed (fn () (* (deref price) (deref qty))))))\n (div\n ;; Reactive price display — updates when store changes\n (span \"$\" (deref price))\n (span \"Qty:\") (button \"-\") (span (deref qty)) (button \"+\")\n (span \"Total: $\" (deref total))\n\n ;; Fetch from server — response arrives as hypermedia\n (button :sx-get \"/(geography.(reactive.(api.flash-sale)))\"\n :sx-target \"#marsh-server-msg\"\n :sx-swap \"innerHTML\"\n \"Fetch Price\")\n ;; Server response lands here:\n (div :id \"marsh-server-msg\"))))" "lisp")) (~doc-code :code (highlight ";; Server returns SX content + a data-init script:\n;;\n;; (<>\n;; (p \"Flash sale! Price: $14.99\")\n;; (script :type \"text/sx\" :data-init\n;; \"(reset! (use-store \\\"demo-price\\\") 14.99)\"))\n;;\n;; The

is swapped in as normal hypermedia content.\n;; The script writes to the store signal.\n;; The island's (deref price), total, and savings\n;; all update reactively — no re-render, no diffing." "lisp")) @@ -315,7 +315,7 @@ (div :id "marsh-flash-target" :class "min-h-[2rem]") (button :class "mt-2 px-3 py-1.5 rounded bg-violet-600 text-white text-sm font-medium hover:bg-violet-700" - :sx-get "/geography/reactive/api/flash-sale" + :sx-get "/(geography.(reactive.(api.flash-sale)))" :sx-target "#marsh-flash-target" :sx-swap "innerHTML" "Fetch from server")) @@ -334,7 +334,7 @@ (~demo-marsh-settle) - (~doc-code :code (highlight ";; sx-on-settle runs SX after the swap settles\n(defisland ~demo-marsh-settle ()\n (let ((count (def-store \"settle-count\" (fn () (signal 0)))))\n (div\n ;; Reactive counter — updates from sx-on-settle\n (span \"Fetched: \" (deref count) \" times\")\n\n ;; Button with sx-on-settle hook\n (button :sx-get \"/geography/reactive/api/settle-data\"\n :sx-target \"#settle-result\"\n :sx-swap \"innerHTML\"\n :sx-on-settle \"(swap! (use-store \\\"settle-count\\\") inc)\"\n \"Fetch Item\")\n\n ;; Server content lands here (pure hypermedia)\n (div :id \"settle-result\"))))" "lisp")) + (~doc-code :code (highlight ";; sx-on-settle runs SX after the swap settles\n(defisland ~demo-marsh-settle ()\n (let ((count (def-store \"settle-count\" (fn () (signal 0)))))\n (div\n ;; Reactive counter — updates from sx-on-settle\n (span \"Fetched: \" (deref count) \" times\")\n\n ;; Button with sx-on-settle hook\n (button :sx-get \"/(geography.(reactive.(api.settle-data)))\"\n :sx-target \"#settle-result\"\n :sx-swap \"innerHTML\"\n :sx-on-settle \"(swap! (use-store \\\"settle-count\\\") inc)\"\n \"Fetch Item\")\n\n ;; Server content lands here (pure hypermedia)\n (div :id \"settle-result\"))))" "lisp")) (p "The server knows nothing about signals or counters. It returns plain content. The " (code "sx-on-settle") " hook is a client-side concern — it runs in the global SX environment with access to all primitives.")) @@ -348,7 +348,7 @@ (~demo-marsh-signal-url) - (~doc-code :code (highlight ";; sx-get URL computed from a signal\n(defisland ~demo-marsh-signal-url ()\n (let ((mode (signal \"products\"))\n (query (signal \"\")))\n (div\n ;; Mode selector — changes what we're searching\n (div :class \"flex gap-2\"\n (button :on-click (fn (e) (reset! mode \"products\"))\n :class (computed (fn () ...active-class...))\n \"Products\")\n (button :on-click (fn (e) (reset! mode \"events\")) \"Events\")\n (button :on-click (fn (e) (reset! mode \"posts\")) \"Posts\"))\n\n ;; Search button — URL is a computed expression\n (button :sx-get (computed (fn ()\n (str \"/geography/reactive/api/search/\"\n (deref mode) \"?q=\" (deref query))))\n :sx-target \"#signal-results\"\n :sx-swap \"innerHTML\"\n \"Search\")\n\n (div :id \"signal-results\"))))" "lisp")) + (~doc-code :code (highlight ";; sx-get URL computed from a signal\n(defisland ~demo-marsh-signal-url ()\n (let ((mode (signal \"products\"))\n (query (signal \"\")))\n (div\n ;; Mode selector — changes what we're searching\n (div :class \"flex gap-2\"\n (button :on-click (fn (e) (reset! mode \"products\"))\n :class (computed (fn () ...active-class...))\n \"Products\")\n (button :on-click (fn (e) (reset! mode \"events\")) \"Events\")\n (button :on-click (fn (e) (reset! mode \"posts\")) \"Posts\"))\n\n ;; Search button — URL is a computed expression\n (button :sx-get (computed (fn ()\n (str \"/(geography.(reactive.(api.search-\"\n (deref mode) \")))\" \"?q=\" (deref query))))\n :sx-target \"#signal-results\"\n :sx-swap \"innerHTML\"\n \"Search\")\n\n (div :id \"signal-results\"))))" "lisp")) (p "No custom plumbing. The same " (code "reactive-attr") " mechanism that makes " (code ":class") " reactive also makes " (code ":sx-get") " reactive. " (code "get-verb-info") " reads " (code "dom-get-attr") " at trigger time — it sees the current URL because the effect already updated the DOM attribute.")) @@ -361,7 +361,7 @@ (~demo-marsh-view-transform) - (~doc-code :code (highlight ";; View mode transforms display without refetch\n(defisland ~demo-marsh-view-transform ()\n (let ((view (signal \"list\"))\n (items (signal nil)))\n (div\n ;; View toggle\n (div :class \"flex gap-2\"\n (button :on-click (fn (e) (reset! view \"list\")) \"List\")\n (button :on-click (fn (e) (reset! view \"grid\")) \"Grid\")\n (button :on-click (fn (e) (reset! view \"compact\")) \"Compact\"))\n\n ;; Fetch from server — stores raw data in signal\n (button :sx-get \"/geography/reactive/api/catalog\"\n :sx-target \"#catalog-raw\"\n :sx-swap \"innerHTML\"\n \"Fetch Catalog\")\n\n ;; Raw server content (hidden, used as data source)\n (div :id \"catalog-raw\" :class \"hidden\")\n\n ;; Reactive display — re-renders when view changes\n (div (computed (fn () (render-view (deref view) (deref items))))))))" "lisp")) + (~doc-code :code (highlight ";; View mode transforms display without refetch\n(defisland ~demo-marsh-view-transform ()\n (let ((view (signal \"list\"))\n (items (signal nil)))\n (div\n ;; View toggle\n (div :class \"flex gap-2\"\n (button :on-click (fn (e) (reset! view \"list\")) \"List\")\n (button :on-click (fn (e) (reset! view \"grid\")) \"Grid\")\n (button :on-click (fn (e) (reset! view \"compact\")) \"Compact\"))\n\n ;; Fetch from server — stores raw data in signal\n (button :sx-get \"/(geography.(reactive.(api.catalog)))\"\n :sx-target \"#catalog-raw\"\n :sx-swap \"innerHTML\"\n \"Fetch Catalog\")\n\n ;; Raw server content (hidden, used as data source)\n (div :id \"catalog-raw\" :class \"hidden\")\n\n ;; Reactive display — re-renders when view changes\n (div (computed (fn () (render-view (deref view) (deref items))))))))" "lisp")) (p "The view signal doesn't just toggle CSS classes — it fundamentally reshapes the DOM. List view shows description. Grid view arranges in columns. Compact view shows names only. All from the same server data, transformed by client state.")) @@ -408,7 +408,7 @@ (div :class "border-t border-stone-200 pt-3" (div :class "flex items-center gap-3" (button :class "px-4 py-2 rounded bg-violet-600 text-white text-sm font-medium hover:bg-violet-700" - :sx-get "/geography/reactive/api/flash-sale" + :sx-get "/(geography.(reactive.(api.flash-sale)))" :sx-target "#marsh-server-msg" :sx-swap "innerHTML" "Fetch Price from Server") @@ -471,7 +471,7 @@ (span :class "text-sm text-stone-600" " times"))) (div :class "flex items-center gap-3" (button :class "px-4 py-2 rounded bg-violet-600 text-white text-sm font-medium hover:bg-violet-700" - :sx-get "/geography/reactive/api/settle-data" + :sx-get "/(geography.(reactive.(api.settle-data)))" :sx-target "#settle-result" :sx-swap "innerHTML" :sx-on-settle "(swap! (use-store \"settle-count\") inc)" @@ -516,15 +516,15 @@ :class "flex-1 px-3 py-1.5 rounded border border-stone-300 text-sm focus:outline-none focus:border-violet-400") (button :class "px-4 py-1.5 rounded bg-violet-600 text-white text-sm font-medium hover:bg-violet-700" :sx-get (computed (fn () - (str "/geography/reactive/api/search/" (deref mode) - "?q=" (deref query)))) + (str "/(geography.(reactive.(api.search-" (deref mode) + ")))""?q=" (deref query)))) :sx-target "#signal-results" :sx-swap "innerHTML" "Search")) ;; Current URL display (p :class "text-xs text-stone-400 font-mono" "URL: " (computed (fn () - (str "/geography/reactive/api/search/" (deref mode) "?q=" (deref query))))) + (str "/(geography.(reactive.(api.search-" (deref mode) ")))" "?q=" (deref query))))) ;; Results (div :id "signal-results" :class "min-h-[3rem] rounded bg-stone-50 p-2" (p :class "text-sm text-stone-400 italic" "Select a category and search."))))) @@ -561,7 +561,7 @@ ;; Fetch button — response writes structured data to store via data-init (div :class "flex items-center gap-3" (button :class "px-4 py-2 rounded bg-emerald-600 text-white text-sm font-medium hover:bg-emerald-700" - :sx-get "/geography/reactive/api/catalog" + :sx-get "/(geography.(reactive.(api.catalog)))" :sx-target "#catalog-msg" :sx-swap "innerHTML" "Fetch Catalog") diff --git a/sx/sxc/examples.sx b/sx/sxc/examples.sx index 22a7dc8..5682afd 100644 --- a/sx/sxc/examples.sx +++ b/sx/sxc/examples.sx @@ -22,7 +22,7 @@ (div :id "click-result" :class "p-4 rounded bg-stone-100 text-stone-500 text-center" "Click the button to load content.") (button - :sx-get "/geography/hypermedia/examples/api/click" + :sx-get "/(geography.(hypermedia.(example.(api.click))))" :sx-target "#click-result" :sx-swap "innerHTML" :class "px-4 py-2 bg-violet-600 text-white rounded hover:bg-violet-700 transition-colors" @@ -39,7 +39,7 @@ (defcomp ~form-demo () (div :class "space-y-4" (form - :sx-post "/geography/hypermedia/examples/api/form" + :sx-post "/(geography.(hypermedia.(example.(api.form))))" :sx-target "#form-result" :sx-swap "innerHTML" :class "space-y-3" @@ -63,7 +63,7 @@ (defcomp ~polling-demo () (div :class "space-y-4" (div :id "poll-target" - :sx-get "/geography/hypermedia/examples/api/poll" + :sx-get "/(geography.(hypermedia.(example.(api.poll))))" :sx-trigger "load, every 2s" :sx-swap "innerHTML" :class "p-4 rounded border border-stone-200 bg-stone-100 text-center font-mono" @@ -99,7 +99,7 @@ (td :class "px-3 py-2 text-stone-700" name) (td :class "px-3 py-2" (button - :sx-delete (str "/geography/hypermedia/examples/api/delete/" id) + :sx-delete (str "/(geography.(hypermedia.(example.(api.(delete." id ")))))") :sx-target (str "#row-" id) :sx-swap "outerHTML" :sx-confirm "Delete this item?" @@ -116,7 +116,7 @@ (div :class "flex items-center justify-between p-3 rounded border border-stone-200" (span :class "text-stone-800" value) (button - :sx-get (str "/geography/hypermedia/examples/api/edit?value=" value) + :sx-get (str "/(geography.(hypermedia.(example.(api.edit))))?value=" value) :sx-target "#edit-target" :sx-swap "innerHTML" :class "text-sm text-violet-600 hover:text-violet-800" @@ -124,7 +124,7 @@ (defcomp ~inline-edit-form (&key value) (form - :sx-post "/geography/hypermedia/examples/api/edit" + :sx-post "/(geography.(hypermedia.(example.(api.edit))))" :sx-target "#edit-target" :sx-swap "innerHTML" :class "flex items-center gap-2 p-3 rounded border border-violet-300 bg-violet-50" @@ -134,7 +134,7 @@ :class "px-3 py-1.5 bg-violet-600 text-white rounded text-sm hover:bg-violet-700" "save") (button :type "button" - :sx-get (str "/geography/hypermedia/examples/api/edit/cancel?value=" value) + :sx-get (str "/(geography.(hypermedia.(example.(api.edit-cancel))))?value=" value) :sx-target "#edit-target" :sx-swap "innerHTML" :class "px-3 py-1.5 bg-stone-200 text-stone-700 rounded text-sm hover:bg-stone-300" @@ -152,7 +152,7 @@ (p :class "text-stone-500" "Box B") (p :class "text-sm text-stone-400" "Waiting..."))) (button - :sx-get "/geography/hypermedia/examples/api/oob" + :sx-get "/(geography.(hypermedia.(example.(api.oob))))" :sx-target "#oob-box-a" :sx-swap "innerHTML" :class "px-4 py-2 bg-violet-600 text-white rounded hover:bg-violet-700 transition-colors text-sm" @@ -164,7 +164,7 @@ (div :class "space-y-4" (p :class "text-sm text-stone-500" "The content below loads automatically when the page renders.") (div :id "lazy-target" - :sx-get "/geography/hypermedia/examples/api/lazy" + :sx-get "/(geography.(hypermedia.(example.(api.lazy))))" :sx-trigger "load" :sx-swap "innerHTML" :class "p-6 rounded border border-stone-200 bg-stone-100 text-center" @@ -187,7 +187,7 @@ (str "Item " (+ i 1) " — loaded with the page"))) (list 1 2 3 4 5)) (div :id "scroll-sentinel" - :sx-get "/geography/hypermedia/examples/api/scroll?page=2" + :sx-get "/(geography.(hypermedia.(example.(api.scroll))))?page=2" :sx-trigger "intersect once" :sx-target "#scroll-items" :sx-swap "beforeend" @@ -201,7 +201,7 @@ items) (when (<= page 5) (div :id "scroll-sentinel" - :sx-get (str "/geography/hypermedia/examples/api/scroll?page=" page) + :sx-get (str "/(geography.(hypermedia.(example.(api.scroll))))?page=" page) :sx-trigger "intersect once" :sx-target "#scroll-items" :sx-swap "beforeend" @@ -217,7 +217,7 @@ (div :class "bg-violet-600 h-4 rounded-full transition-all" :style "width: 0%")) (p :class "text-sm text-stone-500 text-center" "Click start to begin.")) (button - :sx-post "/geography/hypermedia/examples/api/progress/start" + :sx-post "/(geography.(hypermedia.(example.(api.progress-start))))" :sx-target "#progress-target" :sx-swap "innerHTML" :class "px-4 py-2 bg-violet-600 text-white rounded hover:bg-violet-700 transition-colors text-sm" @@ -230,7 +230,7 @@ :style (str "width: " percent "%"))) (p :class "text-sm text-stone-500 text-center" (str percent "% complete")) (when (< percent 100) - (div :sx-get (str "/geography/hypermedia/examples/api/progress/status?job=" job-id) + (div :sx-get (str "/(geography.(hypermedia.(example.(api.progress-status))))?job=" job-id) :sx-trigger "load delay:500ms" :sx-target "#progress-target" :sx-swap "innerHTML")) @@ -242,7 +242,7 @@ (defcomp ~active-search-demo () (div :class "space-y-3" (input :type "text" :name "q" - :sx-get "/geography/hypermedia/examples/api/search" + :sx-get "/(geography.(hypermedia.(example.(api.search))))" :sx-trigger "keyup delay:300ms changed" :sx-target "#search-results" :sx-swap "innerHTML" @@ -262,11 +262,11 @@ ;; --- Inline validation demo --- (defcomp ~inline-validation-demo () - (form :class "space-y-4" :sx-post "/geography/hypermedia/examples/api/validate/submit" :sx-target "#validation-result" :sx-swap "innerHTML" + (form :class "space-y-4" :sx-post "/(geography.(hypermedia.(example.(api.validate-submit))))" :sx-target "#validation-result" :sx-swap "innerHTML" (div (label :class "block text-sm font-medium text-stone-700 mb-1" "Email") (input :type "text" :name "email" :placeholder "user@example.com" - :sx-get "/geography/hypermedia/examples/api/validate" + :sx-get "/(geography.(hypermedia.(example.(api.validate))))" :sx-trigger "blur" :sx-target "#email-feedback" :sx-swap "innerHTML" @@ -290,7 +290,7 @@ (div (label :class "block text-sm font-medium text-stone-700 mb-1" "Category") (select :name "category" - :sx-get "/geography/hypermedia/examples/api/values" + :sx-get "/(geography.(hypermedia.(example.(api.values))))" :sx-trigger "change" :sx-target "#value-items" :sx-swap "innerHTML" @@ -314,7 +314,7 @@ (defcomp ~reset-on-submit-demo () (div :class "space-y-3" (form :id "reset-form" - :sx-post "/geography/hypermedia/examples/api/reset-submit" + :sx-post "/(geography.(hypermedia.(example.(api.reset-submit))))" :sx-target "#reset-result" :sx-swap "innerHTML" :sx-on:afterSwap "this.reset()" @@ -354,7 +354,7 @@ (td :class "px-3 py-2 text-stone-700" stock) (td :class "px-3 py-2" (button - :sx-get (str "/geography/hypermedia/examples/api/editrow/" id) + :sx-get (str "/(geography.(hypermedia.(example.(api.(editrow." id ")))))") :sx-target (str "#erow-" id) :sx-swap "outerHTML" :class "text-sm text-violet-600 hover:text-violet-800" @@ -373,14 +373,14 @@ :class "w-20 px-2 py-1 border border-stone-300 rounded text-sm")) (td :class "px-3 py-2 space-x-1" (button - :sx-post (str "/geography/hypermedia/examples/api/editrow/" id) + :sx-post (str "/(geography.(hypermedia.(example.(api.(editrow." id ")))))") :sx-target (str "#erow-" id) :sx-swap "outerHTML" :sx-include (str "#erow-" id) :class "text-sm text-emerald-600 hover:text-emerald-800" "save") (button - :sx-get (str "/geography/hypermedia/examples/api/editrow/" id "/cancel") + :sx-get (str "/(geography.(hypermedia.(example.(api.(editrow-cancel." id ")))))") :sx-target (str "#erow-" id) :sx-swap "outerHTML" :class "text-sm text-stone-500 hover:text-stone-700" @@ -393,14 +393,14 @@ (form :id "bulk-form" (div :class "flex gap-2 mb-3" (button :type "button" - :sx-post "/geography/hypermedia/examples/api/bulk?action=activate" + :sx-post "/(geography.(hypermedia.(example.(api.bulk))))?action=activate" :sx-target "#bulk-table" :sx-swap "innerHTML" :sx-include "#bulk-form" :class "px-3 py-1.5 bg-emerald-600 text-white rounded text-sm hover:bg-emerald-700" "Activate") (button :type "button" - :sx-post "/geography/hypermedia/examples/api/bulk?action=deactivate" + :sx-post "/(geography.(hypermedia.(example.(api.bulk))))?action=deactivate" :sx-target "#bulk-table" :sx-swap "innerHTML" :sx-include "#bulk-form" @@ -437,19 +437,19 @@ (div :class "space-y-3" (div :class "flex gap-2" (button - :sx-post "/geography/hypermedia/examples/api/swap-log?mode=beforeend" + :sx-post "/(geography.(hypermedia.(example.(api.swap-log))))?mode=beforeend" :sx-target "#swap-log" :sx-swap "beforeend" :class "px-3 py-1.5 bg-violet-600 text-white rounded text-sm hover:bg-violet-700" "Add to End") (button - :sx-post "/geography/hypermedia/examples/api/swap-log?mode=afterbegin" + :sx-post "/(geography.(hypermedia.(example.(api.swap-log))))?mode=afterbegin" :sx-target "#swap-log" :sx-swap "afterbegin" :class "px-3 py-1.5 bg-violet-600 text-white rounded text-sm hover:bg-violet-700" "Add to Start") (button - :sx-post "/geography/hypermedia/examples/api/swap-log?mode=none" + :sx-post "/(geography.(hypermedia.(example.(api.swap-log))))?mode=none" :sx-target "#swap-log" :sx-swap "none" :class "px-3 py-1.5 bg-stone-600 text-white rounded text-sm hover:bg-stone-700" @@ -469,21 +469,21 @@ (div :class "space-y-3" (div :class "flex gap-2" (button - :sx-get "/geography/hypermedia/examples/api/dashboard" + :sx-get "/(geography.(hypermedia.(example.(api.dashboard))))" :sx-target "#filter-target" :sx-swap "innerHTML" :sx-select "#dash-stats" :class "px-3 py-1.5 bg-violet-600 text-white rounded text-sm hover:bg-violet-700" "Stats Only") (button - :sx-get "/geography/hypermedia/examples/api/dashboard" + :sx-get "/(geography.(hypermedia.(example.(api.dashboard))))" :sx-target "#filter-target" :sx-swap "innerHTML" :sx-select "#dash-header" :class "px-3 py-1.5 bg-violet-600 text-white rounded text-sm hover:bg-violet-700" "Header Only") (button - :sx-get "/geography/hypermedia/examples/api/dashboard" + :sx-get "/(geography.(hypermedia.(example.(api.dashboard))))" :sx-target "#filter-target" :sx-swap "innerHTML" :class "px-3 py-1.5 bg-stone-600 text-white rounded text-sm hover:bg-stone-700" @@ -505,7 +505,7 @@ (defcomp ~tab-btn (&key tab label active) (button - :sx-get (str "/geography/hypermedia/examples/api/tabs/" tab) + :sx-get (str "/(geography.(hypermedia.(example.(api.(tabs." tab ")))))") :sx-target "#tab-content" :sx-swap "innerHTML" :sx-push-url (str "/geography/hypermedia/examples/tabs?tab=" tab) @@ -520,7 +520,7 @@ (defcomp ~animations-demo () (div :class "space-y-4" (button - :sx-get "/geography/hypermedia/examples/api/animate" + :sx-get "/(geography.(hypermedia.(example.(api.animate))))" :sx-target "#anim-target" :sx-swap "innerHTML" :class "px-4 py-2 bg-violet-600 text-white rounded hover:bg-violet-700 transition-colors text-sm" @@ -539,7 +539,7 @@ (defcomp ~dialogs-demo () (div (button - :sx-get "/geography/hypermedia/examples/api/dialog" + :sx-get "/(geography.(hypermedia.(example.(api.dialog))))" :sx-target "#dialog-container" :sx-swap "innerHTML" :class "px-4 py-2 bg-violet-600 text-white rounded hover:bg-violet-700 transition-colors text-sm" @@ -549,7 +549,7 @@ (defcomp ~dialog-modal (&key title message) (div :class "fixed inset-0 z-50 flex items-center justify-center" (div :class "absolute inset-0 bg-black/50" - :sx-get "/geography/hypermedia/examples/api/dialog/close" + :sx-get "/(geography.(hypermedia.(example.(api.dialog-close))))" :sx-target "#dialog-container" :sx-swap "innerHTML") (div :class "relative bg-stone-100 rounded-lg shadow-xl p-6 max-w-md w-full mx-4 space-y-4" @@ -557,13 +557,13 @@ (p :class "text-stone-600" message) (div :class "flex justify-end gap-2" (button - :sx-get "/geography/hypermedia/examples/api/dialog/close" + :sx-get "/(geography.(hypermedia.(example.(api.dialog-close))))" :sx-target "#dialog-container" :sx-swap "innerHTML" :class "px-4 py-2 bg-stone-200 text-stone-700 rounded text-sm hover:bg-stone-300" "Cancel") (button - :sx-get "/geography/hypermedia/examples/api/dialog/close" + :sx-get "/(geography.(hypermedia.(example.(api.dialog-close))))" :sx-target "#dialog-container" :sx-swap "innerHTML" :class "px-4 py-2 bg-violet-600 text-white rounded text-sm hover:bg-violet-700" @@ -586,16 +586,16 @@ (kbd :class "px-2 py-0.5 bg-stone-100 border border-stone-300 rounded text-xs font-mono" "h") (span :class "text-sm text-stone-500" "Help")))) (div :id "kbd-target" - :sx-get "/geography/hypermedia/examples/api/keyboard?key=s" + :sx-get "/(geography.(hypermedia.(example.(api.keyboard))))?key=s" :sx-trigger "keyup[key=='s'&&!event.target.matches('input,textarea')] from:body" :sx-swap "innerHTML" :class "p-4 rounded border border-stone-200 bg-stone-100 text-center" (p :class "text-stone-400 text-sm" "Press a shortcut key...")) - (div :sx-get "/geography/hypermedia/examples/api/keyboard?key=n" + (div :sx-get "/(geography.(hypermedia.(example.(api.keyboard))))?key=n" :sx-trigger "keyup[key=='n'&&!event.target.matches('input,textarea')] from:body" :sx-target "#kbd-target" :sx-swap "innerHTML") - (div :sx-get "/geography/hypermedia/examples/api/keyboard?key=h" + (div :sx-get "/(geography.(hypermedia.(example.(api.keyboard))))?key=h" :sx-trigger "keyup[key=='h'&&!event.target.matches('input,textarea')] from:body" :sx-target "#kbd-target" :sx-swap "innerHTML"))) @@ -619,7 +619,7 @@ (p :class "text-sm text-stone-500" email) (p :class "text-sm text-stone-500" role)) (button - :sx-get "/geography/hypermedia/examples/api/putpatch/edit-all" + :sx-get "/(geography.(hypermedia.(example.(api.putpatch-edit-all))))" :sx-target "#pp-target" :sx-swap "innerHTML" :class "text-sm text-violet-600 hover:text-violet-800" @@ -627,7 +627,7 @@ (defcomp ~pp-form-full (&key name email role) (form - :sx-put "/geography/hypermedia/examples/api/putpatch" + :sx-put "/(geography.(hypermedia.(example.(api.putpatch))))" :sx-target "#pp-target" :sx-swap "innerHTML" :class "space-y-3" @@ -648,7 +648,7 @@ :class "px-4 py-2 bg-violet-600 text-white rounded text-sm hover:bg-violet-700" "Save All (PUT)") (button :type "button" - :sx-get "/geography/hypermedia/examples/api/putpatch/cancel" + :sx-get "/(geography.(hypermedia.(example.(api.putpatch-cancel))))" :sx-target "#pp-target" :sx-swap "innerHTML" :class "px-4 py-2 bg-stone-200 text-stone-700 rounded text-sm hover:bg-stone-300" @@ -659,7 +659,7 @@ (defcomp ~json-encoding-demo () (div :class "space-y-4" (form - :sx-post "/geography/hypermedia/examples/api/json-echo" + :sx-post "/(geography.(hypermedia.(example.(api.json-echo))))" :sx-target "#json-result" :sx-swap "innerHTML" :sx-encoding "json" @@ -691,7 +691,7 @@ (div :class "space-y-2" (h4 :class "text-sm font-semibold text-stone-700" "sx-vals — send extra values") (button - :sx-get "/geography/hypermedia/examples/api/echo-vals" + :sx-get "/(geography.(hypermedia.(example.(api.echo-vals))))" :sx-target "#vals-result" :sx-swap "innerHTML" :sx-vals "{\"source\": \"button\", \"version\": \"2.0\"}" @@ -702,7 +702,7 @@ (div :class "space-y-2" (h4 :class "text-sm font-semibold text-stone-700" "sx-headers — send custom headers") (button - :sx-get "/geography/hypermedia/examples/api/echo-headers" + :sx-get "/(geography.(hypermedia.(example.(api.echo-headers))))" :sx-target "#headers-result" :sx-swap "innerHTML" :sx-headers {:X-Custom-Token "abc123" :X-Request-Source "demo"} @@ -723,7 +723,7 @@ (defcomp ~loading-states-demo () (div :class "space-y-4" (button - :sx-get "/geography/hypermedia/examples/api/slow" + :sx-get "/(geography.(hypermedia.(example.(api.slow))))" :sx-target "#loading-result" :sx-swap "innerHTML" :class "sx-loading-btn px-4 py-2 bg-violet-600 text-white rounded hover:bg-violet-700 transition-colors text-sm flex items-center gap-2" @@ -742,7 +742,7 @@ (defcomp ~sync-replace-demo () (div :class "space-y-3" (input :type "text" :name "q" - :sx-get "/geography/hypermedia/examples/api/slow-search" + :sx-get "/(geography.(hypermedia.(example.(api.slow-search))))" :sx-trigger "keyup delay:200ms changed" :sx-target "#sync-result" :sx-swap "innerHTML" @@ -762,7 +762,7 @@ (defcomp ~retry-demo () (div :class "space-y-4" (button - :sx-get "/geography/hypermedia/examples/api/flaky" + :sx-get "/(geography.(hypermedia.(example.(api.flaky))))" :sx-target "#retry-result" :sx-swap "innerHTML" :sx-retry "exponential:1000:8000" diff --git a/sx/sxc/pages/sx_router.py b/sx/sxc/pages/sx_router.py index 2ad3850..503b380 100644 --- a/sx/sxc/pages/sx_router.py +++ b/sx/sxc/pages/sx_router.py @@ -280,6 +280,51 @@ async def eval_sx_url(raw_path: str) -> Any: # --------------------------------------------------------------------------- _REDIRECT_PATTERNS = [ + # --- API endpoint redirects (most specific first) --- + + # Reference API: /geography/hypermedia/reference/api/item/ + (re.compile(r"^/geography/hypermedia/reference/api/item/(.+?)/?$"), + lambda m: f"/(geography.(hypermedia.(reference.(api.(item.{m.group(1)})))))"), + # Reference API: /geography/hypermedia/reference/api/ + (re.compile(r"^/geography/hypermedia/reference/api/(.+?)/?$"), + lambda m: f"/(geography.(hypermedia.(reference.(api.{m.group(1)}))))"), + + # Example API: sub-paths with params + (re.compile(r"^/geography/hypermedia/examples/api/editrow/(.+?)/cancel/?$"), + lambda m: f"/(geography.(hypermedia.(example.(api.(editrow-cancel.{m.group(1)})))))"), + (re.compile(r"^/geography/hypermedia/examples/api/editrow/(.+?)/?$"), + lambda m: f"/(geography.(hypermedia.(example.(api.(editrow.{m.group(1)})))))"), + (re.compile(r"^/geography/hypermedia/examples/api/delete/(.+?)/?$"), + lambda m: f"/(geography.(hypermedia.(example.(api.(delete.{m.group(1)})))))"), + (re.compile(r"^/geography/hypermedia/examples/api/tabs/(.+?)/?$"), + lambda m: f"/(geography.(hypermedia.(example.(api.(tabs.{m.group(1)})))))"), + # Example API: sub-paths collapsed to hyphens + (re.compile(r"^/geography/hypermedia/examples/api/edit/cancel/?$"), + "/(geography.(hypermedia.(example.(api.edit-cancel))))"), + (re.compile(r"^/geography/hypermedia/examples/api/progress/start/?$"), + "/(geography.(hypermedia.(example.(api.progress-start))))"), + (re.compile(r"^/geography/hypermedia/examples/api/progress/status/?$"), + "/(geography.(hypermedia.(example.(api.progress-status))))"), + (re.compile(r"^/geography/hypermedia/examples/api/validate/submit/?$"), + "/(geography.(hypermedia.(example.(api.validate-submit))))"), + (re.compile(r"^/geography/hypermedia/examples/api/dialog/close/?$"), + "/(geography.(hypermedia.(example.(api.dialog-close))))"), + (re.compile(r"^/geography/hypermedia/examples/api/putpatch/edit-all/?$"), + "/(geography.(hypermedia.(example.(api.putpatch-edit-all))))"), + (re.compile(r"^/geography/hypermedia/examples/api/putpatch/cancel/?$"), + "/(geography.(hypermedia.(example.(api.putpatch-cancel))))"), + # Example API: simple names + (re.compile(r"^/geography/hypermedia/examples/api/(.+?)/?$"), + lambda m: f"/(geography.(hypermedia.(example.(api.{m.group(1)}))))"), + + # Reactive API: sub-paths collapsed + (re.compile(r"^/geography/reactive/api/search/(.+?)/?$"), + lambda m: f"/(geography.(reactive.(api.search-{m.group(1)})))"), + (re.compile(r"^/geography/reactive/api/(.+?)/?$"), + lambda m: f"/(geography.(reactive.(api.{m.group(1)})))"), + + # --- Page redirects --- + # More specific first (re.compile(r"^/language/specs/explore/(.+?)/?$"), lambda m: f"/(language.(spec.(explore.{m.group(1)})))"), diff --git a/sx/sxc/reference.sx b/sx/sxc/reference.sx index 8d80753..66e1425 100644 --- a/sx/sxc/reference.sx +++ b/sx/sxc/reference.sx @@ -10,7 +10,7 @@ (defcomp ~ref-get-demo () (div :class "space-y-3" (button - :sx-get "/geography/hypermedia/reference/api/time" + :sx-get "/(geography.(hypermedia.(reference.(api.time))))" :sx-target "#ref-get-result" :sx-swap "innerHTML" :class "px-4 py-2 bg-violet-600 text-white rounded hover:bg-violet-700 transition-colors text-sm" @@ -26,7 +26,7 @@ (defcomp ~ref-post-demo () (div :class "space-y-3" (form - :sx-post "/geography/hypermedia/reference/api/greet" + :sx-post "/(geography.(hypermedia.(reference.(api.greet))))" :sx-target "#ref-post-result" :sx-swap "innerHTML" :class "flex gap-2" @@ -48,7 +48,7 @@ (div :class "flex items-center justify-between p-3 bg-stone-100 rounded" (span :class "text-stone-700 text-sm" "Status: " (strong "draft")) (button - :sx-put "/geography/hypermedia/reference/api/status" + :sx-put "/(geography.(hypermedia.(reference.(api.status))))" :sx-target "#ref-put-view" :sx-swap "innerHTML" :sx-vals "{\"status\": \"published\"}" @@ -63,17 +63,17 @@ (div :class "space-y-2" (div :id "ref-del-1" :class "flex items-center justify-between p-2 border border-stone-200 rounded" (span :class "text-sm text-stone-700" "Item A") - (button :sx-delete "/geography/hypermedia/reference/api/item/1" + (button :sx-delete "/(geography.(hypermedia.(reference.(api.(item.1)))))" :sx-target "#ref-del-1" :sx-swap "delete" :class "text-red-500 text-sm hover:text-red-700" "Remove")) (div :id "ref-del-2" :class "flex items-center justify-between p-2 border border-stone-200 rounded" (span :class "text-sm text-stone-700" "Item B") - (button :sx-delete "/geography/hypermedia/reference/api/item/2" + (button :sx-delete "/(geography.(hypermedia.(reference.(api.(item.2)))))" :sx-target "#ref-del-2" :sx-swap "delete" :class "text-red-500 text-sm hover:text-red-700" "Remove")) (div :id "ref-del-3" :class "flex items-center justify-between p-2 border border-stone-200 rounded" (span :class "text-sm text-stone-700" "Item C") - (button :sx-delete "/geography/hypermedia/reference/api/item/3" + (button :sx-delete "/(geography.(hypermedia.(reference.(api.(item.3)))))" :sx-target "#ref-del-3" :sx-swap "delete" :class "text-red-500 text-sm hover:text-red-700" "Remove")))) @@ -86,11 +86,11 @@ (div :class "p-3 bg-stone-100 rounded" (span :class "text-stone-700 text-sm" "Theme: " (strong :id "ref-patch-val" "light"))) (div :class "flex gap-2" - (button :sx-patch "/geography/hypermedia/reference/api/theme" + (button :sx-patch "/(geography.(hypermedia.(reference.(api.theme))))" :sx-vals "{\"theme\": \"dark\"}" :sx-target "#ref-patch-val" :sx-swap "innerHTML" :class "px-3 py-1 bg-stone-800 text-white rounded text-sm" "Dark") - (button :sx-patch "/geography/hypermedia/reference/api/theme" + (button :sx-patch "/(geography.(hypermedia.(reference.(api.theme))))" :sx-vals "{\"theme\": \"light\"}" :sx-target "#ref-patch-val" :sx-swap "innerHTML" :class "px-3 py-1 bg-stone-100 border border-stone-300 text-stone-700 rounded text-sm" "Light")))) @@ -102,7 +102,7 @@ (defcomp ~ref-trigger-demo () (div :class "space-y-3" (input :type "text" :name "q" :placeholder "Type to search..." - :sx-get "/geography/hypermedia/reference/api/trigger-search" + :sx-get "/(geography.(hypermedia.(reference.(api.trigger-search))))" :sx-trigger "input changed delay:300ms" :sx-target "#ref-trigger-result" :sx-swap "innerHTML" @@ -118,12 +118,12 @@ (defcomp ~ref-target-demo () (div :class "space-y-3" (div :class "flex gap-2" - (button :sx-get "/geography/hypermedia/reference/api/time" + (button :sx-get "/(geography.(hypermedia.(reference.(api.time))))" :sx-target "#ref-target-a" :sx-swap "innerHTML" :class "px-3 py-1 bg-violet-600 text-white rounded text-sm hover:bg-violet-700" "Update Box A") - (button :sx-get "/geography/hypermedia/reference/api/time" + (button :sx-get "/(geography.(hypermedia.(reference.(api.time))))" :sx-target "#ref-target-b" :sx-swap "innerHTML" :class "px-3 py-1 bg-emerald-600 text-white rounded text-sm hover:bg-emerald-700" @@ -141,13 +141,13 @@ (defcomp ~ref-swap-demo () (div :class "space-y-3" (div :class "flex gap-2 flex-wrap" - (button :sx-get "/geography/hypermedia/reference/api/swap-item" + (button :sx-get "/(geography.(hypermedia.(reference.(api.swap-item))))" :sx-target "#ref-swap-list" :sx-swap "beforeend" :class "px-3 py-1 bg-violet-600 text-white rounded text-sm" "beforeend") - (button :sx-get "/geography/hypermedia/reference/api/swap-item" + (button :sx-get "/(geography.(hypermedia.(reference.(api.swap-item))))" :sx-target "#ref-swap-list" :sx-swap "afterbegin" :class "px-3 py-1 bg-emerald-600 text-white rounded text-sm" "afterbegin") - (button :sx-get "/geography/hypermedia/reference/api/swap-item" + (button :sx-get "/(geography.(hypermedia.(reference.(api.swap-item))))" :sx-target "#ref-swap-list" :sx-swap "innerHTML" :class "px-3 py-1 bg-blue-600 text-white rounded text-sm" "innerHTML")) (div :id "ref-swap-list" @@ -160,7 +160,7 @@ (defcomp ~ref-oob-demo () (div :class "space-y-3" - (button :sx-get "/geography/hypermedia/reference/api/oob" + (button :sx-get "/(geography.(hypermedia.(reference.(api.oob))))" :sx-target "#ref-oob-main" :sx-swap "innerHTML" :class "px-4 py-2 bg-violet-600 text-white rounded text-sm hover:bg-violet-700" @@ -179,7 +179,7 @@ (defcomp ~ref-select-demo () (div :class "space-y-3" - (button :sx-get "/geography/hypermedia/reference/api/select-page" + (button :sx-get "/(geography.(hypermedia.(reference.(api.select-page))))" :sx-target "#ref-select-result" :sx-select "#the-content" :sx-swap "innerHTML" @@ -198,7 +198,7 @@ (div :id "ref-confirm-item" :class "flex items-center justify-between p-3 border border-stone-200 rounded" (span :class "text-sm text-stone-700" "Important file.txt") - (button :sx-delete "/geography/hypermedia/reference/api/item/confirm" + (button :sx-delete "/(geography.(hypermedia.(reference.(api.(item.confirm)))))" :sx-target "#ref-confirm-item" :sx-swap "delete" :sx-confirm "Are you sure you want to delete this file?" :class "px-3 py-1 text-red-500 text-sm border border-red-200 rounded hover:bg-red-50" @@ -211,14 +211,14 @@ (defcomp ~ref-pushurl-demo () (div :class "space-y-3" (div :class "flex gap-2" - (a :href "/geography/hypermedia/reference/attributes/sx-get" - :sx-get "/geography/hypermedia/reference/attributes/sx-get" + (a :href "/(geography.(hypermedia.(reference-detail.attributes.sx-get)))" + :sx-get "/(geography.(hypermedia.(reference-detail.attributes.sx-get)))" :sx-target "#main-panel" :sx-select "#main-panel" :sx-swap "outerHTML" :sx-push-url "true" :class "px-3 py-1 bg-violet-100 text-violet-700 rounded text-sm no-underline hover:bg-violet-200" "sx-get page") - (a :href "/geography/hypermedia/reference/attributes/sx-post" - :sx-get "/geography/hypermedia/reference/attributes/sx-post" + (a :href "/(geography.(hypermedia.(reference-detail.attributes.sx-post)))" + :sx-get "/(geography.(hypermedia.(reference-detail.attributes.sx-post)))" :sx-target "#main-panel" :sx-select "#main-panel" :sx-swap "outerHTML" :sx-push-url "true" :class "px-3 py-1 bg-violet-100 text-violet-700 rounded text-sm no-underline hover:bg-violet-200" @@ -233,7 +233,7 @@ (defcomp ~ref-sync-demo () (div :class "space-y-3" (input :type "text" :name "q" :placeholder "Type quickly..." - :sx-get "/geography/hypermedia/reference/api/slow-echo" + :sx-get "/(geography.(hypermedia.(reference.(api.slow-echo))))" :sx-trigger "input changed delay:100ms" :sx-sync "replace" :sx-target "#ref-sync-result" @@ -251,7 +251,7 @@ (defcomp ~ref-encoding-demo () (div :class "space-y-3" - (form :sx-post "/geography/hypermedia/reference/api/upload-name" + (form :sx-post "/(geography.(hypermedia.(reference.(api.upload-name))))" :sx-encoding "multipart/form-data" :sx-target "#ref-encoding-result" :sx-swap "innerHTML" @@ -271,7 +271,7 @@ (defcomp ~ref-headers-demo () (div :class "space-y-3" - (button :sx-get "/geography/hypermedia/reference/api/echo-headers" + (button :sx-get "/(geography.(hypermedia.(reference.(api.echo-headers))))" :sx-headers {:X-Custom-Token "abc123" :X-Request-Source "demo"} :sx-target "#ref-headers-result" :sx-swap "innerHTML" @@ -295,7 +295,7 @@ (option :value "all" "All") (option :value "books" "Books") (option :value "tools" "Tools"))) - (button :sx-get "/geography/hypermedia/reference/api/echo-vals" + (button :sx-get "/(geography.(hypermedia.(reference.(api.echo-vals))))" :sx-include "#ref-inc-cat" :sx-target "#ref-include-result" :sx-swap "innerHTML" @@ -311,7 +311,7 @@ (defcomp ~ref-vals-demo () (div :class "space-y-3" - (button :sx-post "/geography/hypermedia/reference/api/echo-vals" + (button :sx-post "/(geography.(hypermedia.(reference.(api.echo-vals))))" :sx-vals "{\"source\": \"demo\", \"page\": \"3\"}" :sx-target "#ref-vals-result" :sx-swap "innerHTML" @@ -327,8 +327,8 @@ (defcomp ~ref-media-demo () (div :class "space-y-3" - (a :href "/geography/hypermedia/reference/attributes/sx-get" - :sx-get "/geography/hypermedia/reference/attributes/sx-get" + (a :href "/(geography.(hypermedia.(reference-detail.attributes.sx-get)))" + :sx-get "/(geography.(hypermedia.(reference-detail.attributes.sx-get)))" :sx-target "#main-panel" :sx-select "#main-panel" :sx-swap "outerHTML" :sx-push-url "true" :sx-media "(min-width: 768px)" @@ -346,13 +346,13 @@ (div :class "grid grid-cols-2 gap-3" (div :class "p-3 border border-stone-200 rounded" (p :class "text-xs text-stone-400 mb-2" "sx enabled") - (button :sx-get "/geography/hypermedia/reference/api/time" + (button :sx-get "/(geography.(hypermedia.(reference.(api.time))))" :sx-target "#ref-dis-a" :sx-swap "innerHTML" :class "px-3 py-1 bg-violet-600 text-white rounded text-sm" "Load") (div :id "ref-dis-a" :class "mt-2 text-sm text-stone-500" "—")) (div :sx-disable "true" :class "p-3 border border-stone-200 rounded" (p :class "text-xs text-stone-400 mb-2" "sx disabled") - (button :sx-get "/geography/hypermedia/reference/api/time" + (button :sx-get "/(geography.(hypermedia.(reference.(api.time))))" :sx-target "#ref-dis-b" :sx-swap "innerHTML" :class "px-3 py-1 bg-stone-400 text-white rounded text-sm" "Load") (div :id "ref-dis-b" :class "mt-2 text-sm text-stone-500" @@ -378,7 +378,7 @@ (defcomp ~ref-retry-demo () (div :class "space-y-3" - (button :sx-get "/geography/hypermedia/reference/api/flaky" + (button :sx-get "/(geography.(hypermedia.(reference.(api.flaky))))" :sx-target "#ref-retry-result" :sx-swap "innerHTML" :sx-retry "true" @@ -414,13 +414,13 @@ (defcomp ~ref-boost-demo () (div :class "space-y-3" (nav :sx-boost "true" :class "flex gap-3" - (a :href "/geography/hypermedia/reference/attributes/sx-get" + (a :href "/(geography.(hypermedia.(reference-detail.attributes.sx-get)))" :class "text-violet-600 hover:text-violet-800 underline text-sm" "sx-get") - (a :href "/geography/hypermedia/reference/attributes/sx-post" + (a :href "/(geography.(hypermedia.(reference-detail.attributes.sx-post)))" :class "text-violet-600 hover:text-violet-800 underline text-sm" "sx-post") - (a :href "/geography/hypermedia/reference/attributes/sx-target" + (a :href "/(geography.(hypermedia.(reference-detail.attributes.sx-target)))" :class "text-violet-600 hover:text-violet-800 underline text-sm" "sx-target")) (p :class "text-xs text-stone-400" @@ -434,7 +434,7 @@ (defcomp ~ref-preload-demo () (div :class "space-y-3" (button - :sx-get "/geography/hypermedia/reference/api/time" + :sx-get "/(geography.(hypermedia.(reference.(api.time))))" :sx-target "#ref-preload-result" :sx-swap "innerHTML" :sx-preload "mouseover" @@ -452,7 +452,7 @@ (div :class "space-y-3" (div :class "flex gap-2 items-center" (button - :sx-get "/geography/hypermedia/reference/api/time" + :sx-get "/(geography.(hypermedia.(reference.(api.time))))" :sx-target "#ref-preserve-container" :sx-swap "innerHTML" :class "px-4 py-2 bg-violet-600 text-white rounded hover:bg-violet-700 transition-colors text-sm" @@ -473,7 +473,7 @@ (div :class "space-y-3" (div :class "flex gap-3 items-center" (button - :sx-get "/geography/hypermedia/reference/api/slow-echo" + :sx-get "/(geography.(hypermedia.(reference.(api.slow-echo))))" :sx-target "#ref-indicator-result" :sx-swap "innerHTML" :sx-indicator "#ref-spinner" @@ -495,7 +495,7 @@ (defcomp ~ref-validate-demo () (div :class "space-y-3" (form - :sx-post "/geography/hypermedia/reference/api/greet" + :sx-post "/(geography.(hypermedia.(reference.(api.greet))))" :sx-target "#ref-validate-result" :sx-swap "innerHTML" :sx-validate "true" @@ -517,7 +517,7 @@ (defcomp ~ref-ignore-demo () (div :class "space-y-3" (button - :sx-get "/geography/hypermedia/reference/api/time" + :sx-get "/(geography.(hypermedia.(reference.(api.time))))" :sx-target "#ref-ignore-container" :sx-swap "innerHTML" :class "px-4 py-2 bg-violet-600 text-white rounded hover:bg-violet-700 transition-colors text-sm" @@ -539,14 +539,14 @@ (div :id "ref-opt-item-1" :class "flex items-center justify-between p-2 border border-stone-200 rounded" (span :class "text-sm text-stone-700" "Optimistic item A") - (button :sx-delete "/geography/hypermedia/reference/api/item/opt1" + (button :sx-delete "/(geography.(hypermedia.(reference.(api.(item.opt1)))))" :sx-target "#ref-opt-item-1" :sx-swap "delete" :sx-optimistic "remove" :class "text-red-500 text-sm hover:text-red-700" "Remove")) (div :id "ref-opt-item-2" :class "flex items-center justify-between p-2 border border-stone-200 rounded" (span :class "text-sm text-stone-700" "Optimistic item B") - (button :sx-delete "/geography/hypermedia/reference/api/item/opt2" + (button :sx-delete "/(geography.(hypermedia.(reference.(api.(item.opt2)))))" :sx-target "#ref-opt-item-2" :sx-swap "delete" :sx-optimistic "remove" :class "text-red-500 text-sm hover:text-red-700" "Remove")) @@ -560,7 +560,7 @@ (defcomp ~ref-replace-url-demo () (div :class "space-y-3" (button - :sx-get "/geography/hypermedia/reference/api/time" + :sx-get "/(geography.(hypermedia.(reference.(api.time))))" :sx-target "#ref-replurl-result" :sx-swap "innerHTML" :sx-replace-url "true" @@ -578,7 +578,7 @@ (div :class "space-y-3" (div :class "flex gap-3 items-center" (button :id "ref-diselt-btn" - :sx-get "/geography/hypermedia/reference/api/slow-echo" + :sx-get "/(geography.(hypermedia.(reference.(api.slow-echo))))" :sx-target "#ref-diselt-result" :sx-swap "innerHTML" :sx-disabled-elt "#ref-diselt-btn" @@ -597,7 +597,7 @@ (defcomp ~ref-prompt-demo () (div :class "space-y-3" (button - :sx-get "/geography/hypermedia/reference/api/prompt-echo" + :sx-get "/(geography.(hypermedia.(reference.(api.prompt-echo))))" :sx-target "#ref-prompt-result" :sx-swap "innerHTML" :sx-prompt "Enter your name:" @@ -614,7 +614,7 @@ (defcomp ~ref-params-demo () (div :class "space-y-3" (form - :sx-post "/geography/hypermedia/reference/api/echo-vals" + :sx-post "/(geography.(hypermedia.(reference.(api.echo-vals))))" :sx-target "#ref-params-result" :sx-swap "innerHTML" :sx-params "name" @@ -636,7 +636,7 @@ (defcomp ~ref-sse-demo () (div :class "space-y-3" - (div :sx-sse "/geography/hypermedia/reference/api/sse-time" + (div :sx-sse "/(geography.(hypermedia.(reference.(api.sse-time))))" :sx-sse-swap "time" :sx-swap "innerHTML" (div :id "ref-sse-result" @@ -656,7 +656,7 @@ (defcomp ~ref-header-prompt-demo () (div :class "space-y-3" (button - :sx-get "/geography/hypermedia/reference/api/prompt-echo" + :sx-get "/(geography.(hypermedia.(reference.(api.prompt-echo))))" :sx-target "#ref-hdr-prompt-result" :sx-swap "innerHTML" :sx-prompt "Enter your name:" @@ -673,7 +673,7 @@ (defcomp ~ref-header-trigger-demo () (div :class "space-y-3" (button - :sx-get "/geography/hypermedia/reference/api/trigger-event" + :sx-get "/(geography.(hypermedia.(reference.(api.trigger-event))))" :sx-target "#ref-hdr-trigger-result" :sx-swap "innerHTML" :class "px-4 py-2 bg-violet-600 text-white rounded hover:bg-violet-700 transition-colors text-sm" @@ -690,7 +690,7 @@ (defcomp ~ref-header-retarget-demo () (div :class "space-y-3" (button - :sx-get "/geography/hypermedia/reference/api/retarget" + :sx-get "/(geography.(hypermedia.(reference.(api.retarget))))" :sx-target "#ref-hdr-retarget-main" :sx-swap "innerHTML" :class "px-4 py-2 bg-violet-600 text-white rounded hover:bg-violet-700 transition-colors text-sm" @@ -717,7 +717,7 @@ (input :id "ref-evt-br-input" :type "text" :placeholder "Type something first..." :class "flex-1 px-3 py-2 border border-stone-300 rounded text-sm focus:outline-none focus:ring-2 focus:ring-violet-500") (button - :sx-get "/geography/hypermedia/reference/api/time" + :sx-get "/(geography.(hypermedia.(reference.(api.time))))" :sx-target "#ref-evt-br-result" :sx-swap "innerHTML" :sx-on:sx:beforeRequest "if (!document.getElementById('ref-evt-br-input').value) { event.preventDefault(); document.getElementById('ref-evt-br-result').textContent = 'Cancelled — input is empty!'; }" @@ -734,7 +734,7 @@ (defcomp ~ref-event-after-request-demo () (div :class "space-y-3" (button - :sx-get "/geography/hypermedia/reference/api/time" + :sx-get "/(geography.(hypermedia.(reference.(api.time))))" :sx-target "#ref-evt-ar-result" :sx-swap "innerHTML" :sx-on:sx:afterRequest "document.getElementById('ref-evt-ar-log').textContent = 'Response status: ' + (event.detail ? event.detail.status : '?')" @@ -754,7 +754,7 @@ (defcomp ~ref-event-after-swap-demo () (div :class "space-y-3" (button - :sx-get "/geography/hypermedia/reference/api/swap-item" + :sx-get "/(geography.(hypermedia.(reference.(api.swap-item))))" :sx-target "#ref-evt-as-list" :sx-swap "beforeend" :sx-on:sx:afterSwap "var items = document.querySelectorAll('#ref-evt-as-list > div'); if (items.length) items[items.length-1].scrollIntoView({behavior:'smooth'}); document.getElementById('ref-evt-as-count').textContent = items.length + ' items'" @@ -774,7 +774,7 @@ (defcomp ~ref-event-response-error-demo () (div :class "space-y-3" (button - :sx-get "/geography/hypermedia/reference/api/error-500" + :sx-get "/(geography.(hypermedia.(reference.(api.error-500))))" :sx-target "#ref-evt-err-result" :sx-swap "innerHTML" :sx-on:sx:responseError "var s=document.getElementById('ref-evt-err-status'); s.style.display='block'; s.textContent='Error ' + (event.detail ? event.detail.status || '?' : '?') + ' received'" @@ -796,7 +796,7 @@ (defcomp ~ref-event-validation-failed-demo () (div :class "space-y-3" (form - :sx-post "/geography/hypermedia/reference/api/greet" + :sx-post "/(geography.(hypermedia.(reference.(api.greet))))" :sx-target "#ref-evt-vf-result" :sx-swap "innerHTML" :sx-validate "true" @@ -847,13 +847,13 @@ "Open DevTools console, then navigate to a pure page (no :data expression). " "You'll see \"sx:route client /path\" in the console — no network request is made.") (div :class "flex gap-2 flex-wrap" - (a :href "/etc/essays/" + (a :href "/(etc.(essay))" :class "px-3 py-1 bg-violet-100 text-violet-700 rounded text-sm no-underline hover:bg-violet-200" "Essays") - (a :href "/etc/plans/" + (a :href "/(etc.(plan))" :class "px-3 py-1 bg-violet-100 text-violet-700 rounded text-sm no-underline hover:bg-violet-200" "Plans") - (a :href "/applications/protocols/" + (a :href "/(applications.(protocol))" :class "px-3 py-1 bg-violet-100 text-violet-700 rounded text-sm no-underline hover:bg-violet-200" "Protocols")) (p :class "text-xs text-stone-400" @@ -866,7 +866,7 @@ (defcomp ~ref-event-sse-open-demo () (div :class "space-y-3" - (div :sx-sse "/geography/hypermedia/reference/api/sse-time" + (div :sx-sse "/(geography.(hypermedia.(reference.(api.sse-time))))" :sx-sse-swap "time" :sx-swap "innerHTML" :sx-on:sx:sseOpen "document.getElementById('ref-evt-sseopen-status').textContent = 'Connected'; document.getElementById('ref-evt-sseopen-status').className = 'inline-block px-2 py-0.5 rounded text-xs bg-emerald-100 text-emerald-700'" @@ -884,7 +884,7 @@ (defcomp ~ref-event-sse-message-demo () (div :class "space-y-3" - (div :sx-sse "/geography/hypermedia/reference/api/sse-time" + (div :sx-sse "/(geography.(hypermedia.(reference.(api.sse-time))))" :sx-sse-swap "time" :sx-swap "innerHTML" :sx-on:sx:sseMessage "var c = parseInt(document.getElementById('ref-evt-ssemsg-count').dataset.count || '0') + 1; document.getElementById('ref-evt-ssemsg-count').dataset.count = c; document.getElementById('ref-evt-ssemsg-count').textContent = c + ' messages received'" @@ -902,7 +902,7 @@ (defcomp ~ref-event-sse-error-demo () (div :class "space-y-3" - (div :sx-sse "/geography/hypermedia/reference/api/sse-time" + (div :sx-sse "/(geography.(hypermedia.(reference.(api.sse-time))))" :sx-sse-swap "time" :sx-swap "innerHTML" :sx-on:sx:sseError "document.getElementById('ref-evt-sseerr-status').textContent = 'Disconnected'; document.getElementById('ref-evt-sseerr-status').className = 'inline-block px-2 py-0.5 rounded text-xs bg-red-100 text-red-700'"