Replace env free-variable threading with IO-primitive auto-fetch macros
Layout components now self-resolve context (cart-mini, auth-menu, nav-tree, rights, URLs) via new IO primitives (root-header-ctx, select-colours, account-nav-ctx, app-rights) and defmacro wrappers (~root-header-auto, ~auth-header-row-auto, ~root-mobile-auto). This eliminates _ctx_to_env(), HELPER_CSS_CLASSES, and verbose :key threading across all 10 services. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
65
sx/sx/docs.sx
Normal file
65
sx/sx/docs.sx
Normal file
@@ -0,0 +1,65 @@
|
||||
;; SX docs utility components
|
||||
|
||||
(defcomp ~doc-placeholder (&key id)
|
||||
(div :id id
|
||||
(div :class "bg-stone-50 border border-stone-200 rounded p-4 mt-3"
|
||||
(p :class "text-stone-400 italic text-sm"
|
||||
"Trigger the demo to see the actual content."))))
|
||||
|
||||
(defcomp ~doc-oob-code (&key target-id text)
|
||||
(div :id target-id :sx-swap-oob "innerHTML"
|
||||
(div :class "bg-stone-50 border border-stone-200 rounded p-4 mt-3 overflow-x-auto"
|
||||
(pre :class "text-sm whitespace-pre-wrap"
|
||||
(code text)))))
|
||||
|
||||
(defcomp ~doc-attr-table (&key title rows)
|
||||
(div :class "space-y-3"
|
||||
(h3 :class "text-xl font-semibold text-stone-700" title)
|
||||
(div :class "overflow-x-auto rounded border border-stone-200"
|
||||
(table :class "w-full text-left text-sm"
|
||||
(thead (tr :class "border-b border-stone-200 bg-stone-50"
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Attribute")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Description")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600 text-center w-20" "In sx?")))
|
||||
(tbody rows)))))
|
||||
|
||||
(defcomp ~doc-headers-table (&key title rows)
|
||||
(div :class "space-y-3"
|
||||
(h3 :class "text-xl font-semibold text-stone-700" title)
|
||||
(div :class "overflow-x-auto rounded border border-stone-200"
|
||||
(table :class "w-full text-left text-sm"
|
||||
(thead (tr :class "border-b border-stone-200 bg-stone-50"
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Header")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Value")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Description")))
|
||||
(tbody rows)))))
|
||||
|
||||
(defcomp ~doc-headers-row (&key name value description)
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700 whitespace-nowrap" name)
|
||||
(td :class "px-3 py-2 font-mono text-sm text-stone-500" value)
|
||||
(td :class "px-3 py-2 text-stone-700 text-sm" description)))
|
||||
|
||||
(defcomp ~doc-two-col-row (&key name description)
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700 whitespace-nowrap" name)
|
||||
(td :class "px-3 py-2 text-stone-700 text-sm" description)))
|
||||
|
||||
(defcomp ~doc-two-col-table (&key title intro col1 col2 rows)
|
||||
(div :class "space-y-3"
|
||||
(when title (h3 :class "text-xl font-semibold text-stone-700" title))
|
||||
(when intro (p :class "text-stone-600 mb-6" intro))
|
||||
(div :class "overflow-x-auto rounded border border-stone-200"
|
||||
(table :class "w-full text-left text-sm"
|
||||
(thead (tr :class "border-b border-stone-200 bg-stone-50"
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" (or col1 "Name"))
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" (or col2 "Description"))))
|
||||
(tbody rows)))))
|
||||
|
||||
(defcomp ~sx-docs-label ()
|
||||
(span :class "font-mono" "(<x>)"))
|
||||
|
||||
(defcomp ~doc-clear-cache-btn ()
|
||||
(button :onclick "localStorage.removeItem('sx-components-hash');localStorage.removeItem('sx-components-src');var e=Sx.getEnv();Object.keys(e).forEach(function(k){if(k.charAt(0)==='~')delete e[k]});var b=this;b.textContent='Cleared!';setTimeout(function(){b.textContent='Clear component cache'},2000)"
|
||||
:class "text-xs text-stone-400 hover:text-stone-600 border border-stone-200 rounded px-2 py-1 transition-colors"
|
||||
"Clear component cache"))
|
||||
@@ -1,12 +1,10 @@
|
||||
;; SX docs layout defcomps — root header from env free variables,
|
||||
;; SX docs layout defcomps — root header via ~root-header-auto,
|
||||
;; sx-specific headers passed as &key params.
|
||||
|
||||
;; --- SX home layout: root + sx menu row ---
|
||||
|
||||
(defcomp ~sx-layout-full (&key sx-row)
|
||||
(<> (~root-header :cart-mini cart-mini :blog-url blog-url :site-title site-title
|
||||
:app-label app-label :nav-tree nav-tree :auth-menu auth-menu
|
||||
:nav-panel nav-panel :settings-url settings-url :is-admin is-admin)
|
||||
(<> (~root-header-auto)
|
||||
sx-row))
|
||||
|
||||
(defcomp ~sx-layout-oob (&key root-header sx-row)
|
||||
@@ -15,7 +13,5 @@
|
||||
;; --- SX section layout: root + sx row (with child sub-row) ---
|
||||
|
||||
(defcomp ~sx-section-layout-full (&key sx-row)
|
||||
(<> (~root-header :cart-mini cart-mini :blog-url blog-url :site-title site-title
|
||||
:app-label app-label :nav-tree nav-tree :auth-menu auth-menu
|
||||
:nav-panel nav-panel :settings-url settings-url :is-admin is-admin)
|
||||
(<> (~root-header-auto)
|
||||
sx-row))
|
||||
|
||||
@@ -419,60 +419,46 @@ async def _reference_attrs_sx() -> str:
|
||||
)
|
||||
|
||||
|
||||
def _reference_headers_sx() -> str:
|
||||
async def _reference_headers_sx() -> str:
|
||||
from content.pages import REQUEST_HEADERS, RESPONSE_HEADERS
|
||||
req_table = await _headers_table_sx("Request Headers", REQUEST_HEADERS)
|
||||
resp_table = await _headers_table_sx("Response Headers", RESPONSE_HEADERS)
|
||||
return (
|
||||
f'(~doc-page :title "Headers"'
|
||||
f' (p :class "text-stone-600 mb-6"'
|
||||
f' "sx uses custom HTTP headers to coordinate between client and server.")'
|
||||
f' (div :class "space-y-8"'
|
||||
f' {_headers_table_sx("Request Headers", REQUEST_HEADERS)}'
|
||||
f' {_headers_table_sx("Response Headers", RESPONSE_HEADERS)}))'
|
||||
f' {req_table}'
|
||||
f' {resp_table}))'
|
||||
)
|
||||
|
||||
|
||||
def _reference_events_sx() -> str:
|
||||
async def _reference_events_sx() -> str:
|
||||
from shared.sx.helpers import render_to_sx
|
||||
from shared.sx.parser import SxExpr
|
||||
from content.pages import EVENTS
|
||||
rows = []
|
||||
for name, desc in EVENTS:
|
||||
rows.append(
|
||||
f'(tr :class "border-b border-stone-100"'
|
||||
f' (td :class "px-3 py-2 font-mono text-sm text-violet-700 whitespace-nowrap" "{name}")'
|
||||
f' (td :class "px-3 py-2 text-stone-700 text-sm" "{desc}"))'
|
||||
)
|
||||
return (
|
||||
f'(~doc-page :title "Events"'
|
||||
f' (p :class "text-stone-600 mb-6"'
|
||||
f' "sx fires custom DOM events at various points in the request lifecycle.")'
|
||||
f' (div :class "overflow-x-auto rounded border border-stone-200"'
|
||||
f' (table :class "w-full text-left text-sm"'
|
||||
f' (thead (tr :class "border-b border-stone-200 bg-stone-50"'
|
||||
f' (th :class "px-3 py-2 font-medium text-stone-600" "Event")'
|
||||
f' (th :class "px-3 py-2 font-medium text-stone-600" "Description")))'
|
||||
f' (tbody {" ".join(rows)}))))'
|
||||
)
|
||||
rows.append(await render_to_sx("doc-two-col-row", name=name, description=desc))
|
||||
rows_sx = "(<> " + " ".join(rows) + ")"
|
||||
table = await render_to_sx("doc-two-col-table",
|
||||
intro="sx fires custom DOM events at various points in the request lifecycle.",
|
||||
col1="Event", col2="Description", rows=SxExpr(rows_sx))
|
||||
return f'(~doc-page :title "Events" {table})'
|
||||
|
||||
|
||||
def _reference_js_api_sx() -> str:
|
||||
async def _reference_js_api_sx() -> str:
|
||||
from shared.sx.helpers import render_to_sx
|
||||
from shared.sx.parser import SxExpr
|
||||
from content.pages import JS_API
|
||||
rows = []
|
||||
for name, desc in JS_API:
|
||||
rows.append(
|
||||
f'(tr :class "border-b border-stone-100"'
|
||||
f' (td :class "px-3 py-2 font-mono text-sm text-violet-700 whitespace-nowrap" "{name}")'
|
||||
f' (td :class "px-3 py-2 text-stone-700 text-sm" "{desc}"))'
|
||||
)
|
||||
return (
|
||||
f'(~doc-page :title "JavaScript API"'
|
||||
f' (p :class "text-stone-600 mb-6"'
|
||||
f' "The client-side sx.js library exposes a public API for programmatic use.")'
|
||||
f' (div :class "overflow-x-auto rounded border border-stone-200"'
|
||||
f' (table :class "w-full text-left text-sm"'
|
||||
f' (thead (tr :class "border-b border-stone-200 bg-stone-50"'
|
||||
f' (th :class "px-3 py-2 font-medium text-stone-600" "Method")'
|
||||
f' (th :class "px-3 py-2 font-medium text-stone-600" "Description")))'
|
||||
f' (tbody {" ".join(rows)}))))'
|
||||
)
|
||||
rows.append(await render_to_sx("doc-two-col-row", name=name, description=desc))
|
||||
rows_sx = "(<> " + " ".join(rows) + ")"
|
||||
table = await render_to_sx("doc-two-col-table",
|
||||
intro="The client-side sx.js library exposes a public API for programmatic use.",
|
||||
col1="Method", col2="Description", rows=SxExpr(rows_sx))
|
||||
return f'(~doc-page :title "JavaScript API" {table})'
|
||||
|
||||
|
||||
def _protocol_wire_format_sx() -> str:
|
||||
|
||||
@@ -15,30 +15,30 @@ def _register_sx_layouts() -> None:
|
||||
|
||||
async def _sx_full_headers(ctx: dict, **kw: Any) -> str:
|
||||
"""Full headers for sx home page: root + sx menu row."""
|
||||
from shared.sx.helpers import render_to_sx_with_env, _ctx_to_env
|
||||
from shared.sx.helpers import render_to_sx_with_env
|
||||
from shared.sx.parser import SxExpr
|
||||
|
||||
main_nav = await _main_nav_sx(kw.get("section"))
|
||||
sx_row = await _sx_header_sx(main_nav)
|
||||
return await render_to_sx_with_env("sx-layout-full", _ctx_to_env(ctx),
|
||||
return await render_to_sx_with_env("sx-layout-full", {},
|
||||
sx_row=SxExpr(sx_row))
|
||||
|
||||
|
||||
async def _sx_oob_headers(ctx: dict, **kw: Any) -> str:
|
||||
"""OOB headers for sx home page."""
|
||||
from shared.sx.helpers import render_to_sx_with_env, _ctx_to_env, oob_header_sx
|
||||
from shared.sx.helpers import render_to_sx_with_env, oob_header_sx
|
||||
from shared.sx.parser import SxExpr
|
||||
|
||||
main_nav = await _main_nav_sx(kw.get("section"))
|
||||
sx_row = await _sx_header_sx(main_nav)
|
||||
rows = await render_to_sx_with_env("sx-layout-full", _ctx_to_env(ctx),
|
||||
rows = await render_to_sx_with_env("sx-layout-full", {},
|
||||
sx_row=SxExpr(sx_row))
|
||||
return await oob_header_sx("root-header-child", "sx-header-child", rows)
|
||||
|
||||
|
||||
async def _sx_section_full_headers(ctx: dict, **kw: Any) -> str:
|
||||
"""Full headers for sx section pages: root + sx row + sub row."""
|
||||
from shared.sx.helpers import render_to_sx_with_env, _ctx_to_env
|
||||
from shared.sx.helpers import render_to_sx_with_env
|
||||
from shared.sx.parser import SxExpr
|
||||
|
||||
section = kw.get("section", "")
|
||||
@@ -50,13 +50,13 @@ async def _sx_section_full_headers(ctx: dict, **kw: Any) -> str:
|
||||
main_nav = await _main_nav_sx(section)
|
||||
sub_row = await _sub_row_sx(sub_label, sub_href, sub_nav, selected)
|
||||
sx_row = await _sx_header_sx(main_nav, child=sub_row)
|
||||
return await render_to_sx_with_env("sx-section-layout-full", _ctx_to_env(ctx),
|
||||
return await render_to_sx_with_env("sx-section-layout-full", {},
|
||||
sx_row=SxExpr(sx_row))
|
||||
|
||||
|
||||
async def _sx_section_oob_headers(ctx: dict, **kw: Any) -> str:
|
||||
"""OOB headers for sx section pages."""
|
||||
from shared.sx.helpers import render_to_sx_with_env, _ctx_to_env, oob_header_sx
|
||||
from shared.sx.helpers import render_to_sx_with_env, oob_header_sx
|
||||
from shared.sx.parser import SxExpr
|
||||
|
||||
section = kw.get("section", "")
|
||||
@@ -68,7 +68,7 @@ async def _sx_section_oob_headers(ctx: dict, **kw: Any) -> str:
|
||||
main_nav = await _main_nav_sx(section)
|
||||
sub_row = await _sub_row_sx(sub_label, sub_href, sub_nav, selected)
|
||||
sx_row = await _sx_header_sx(main_nav, child=sub_row)
|
||||
rows = await render_to_sx_with_env("sx-section-layout-full", _ctx_to_env(ctx),
|
||||
rows = await render_to_sx_with_env("sx-section-layout-full", {},
|
||||
sx_row=SxExpr(sx_row))
|
||||
return await oob_header_sx("root-header-child", "sx-header-child", rows)
|
||||
|
||||
|
||||
@@ -18,10 +18,8 @@ def _example_code(code: str, language: str = "lisp") -> str:
|
||||
|
||||
def _placeholder(div_id: str) -> str:
|
||||
"""Empty placeholder that will be filled by OOB swap on interaction."""
|
||||
return (f'(div :id "{div_id}"'
|
||||
f' (div :class "bg-stone-50 border border-stone-200 rounded p-4 mt-3"'
|
||||
f' (p :class "text-stone-400 italic text-sm"'
|
||||
f' "Trigger the demo to see the actual content.")))')
|
||||
from shared.sx.helpers import sx_call
|
||||
return sx_call("doc-placeholder", id=div_id)
|
||||
|
||||
|
||||
def _component_source_text(*names: str) -> str:
|
||||
@@ -45,23 +43,14 @@ def _component_source_text(*names: str) -> str:
|
||||
|
||||
def _oob_code(target_id: str, text: str) -> str:
|
||||
"""OOB swap that displays plain code in a styled block."""
|
||||
escaped = text.replace('\\', '\\\\').replace('"', '\\"')
|
||||
return (f'(div :id "{target_id}" :sx-swap-oob "innerHTML"'
|
||||
f' (div :class "bg-stone-50 border border-stone-200 rounded p-4 mt-3 overflow-x-auto"'
|
||||
f' (pre :class "text-sm whitespace-pre-wrap"'
|
||||
f' (code "{escaped}"))))')
|
||||
from shared.sx.helpers import sx_call
|
||||
return sx_call("doc-oob-code", target_id=target_id, text=text)
|
||||
|
||||
|
||||
def _clear_components_btn() -> str:
|
||||
"""Button that clears the client-side component cache (localStorage + in-memory)."""
|
||||
js = ("localStorage.removeItem('sx-components-hash');"
|
||||
"localStorage.removeItem('sx-components-src');"
|
||||
"var e=Sx.getEnv();Object.keys(e).forEach(function(k){if(k.charAt(0)==='~')delete e[k]});"
|
||||
"var b=this;b.textContent='Cleared!';setTimeout(function(){b.textContent='Clear component cache'},2000)")
|
||||
return (f'(button :onclick "{js}"'
|
||||
f' :class "text-xs text-stone-400 hover:text-stone-600 border border-stone-200'
|
||||
f' rounded px-2 py-1 transition-colors"'
|
||||
f' "Clear component cache")')
|
||||
from shared.sx.helpers import sx_call
|
||||
return sx_call("doc-clear-cache-btn")
|
||||
|
||||
|
||||
def _full_wire_text(sx_src: str, *comp_names: str) -> str:
|
||||
|
||||
@@ -36,40 +36,18 @@ async def _attr_table_sx(title: str, attrs: list[tuple[str, str, bool]]) -> str:
|
||||
rows.append(await render_to_sx("doc-attr-row", attr=attr, description=desc,
|
||||
exists="true" if exists else None,
|
||||
href=href))
|
||||
return (
|
||||
f'(div :class "space-y-3"'
|
||||
f' (h3 :class "text-xl font-semibold text-stone-700" "{title}")'
|
||||
f' (div :class "overflow-x-auto rounded border border-stone-200"'
|
||||
f' (table :class "w-full text-left text-sm"'
|
||||
f' (thead (tr :class "border-b border-stone-200 bg-stone-50"'
|
||||
f' (th :class "px-3 py-2 font-medium text-stone-600" "Attribute")'
|
||||
f' (th :class "px-3 py-2 font-medium text-stone-600" "Description")'
|
||||
f' (th :class "px-3 py-2 font-medium text-stone-600 text-center w-20" "In sx?")))'
|
||||
f' (tbody {" ".join(rows)}))))'
|
||||
)
|
||||
rows_sx = "(<> " + " ".join(rows) + ")"
|
||||
return await render_to_sx("doc-attr-table", title=title, rows=SxExpr(rows_sx))
|
||||
|
||||
|
||||
def _headers_table_sx(title: str, headers: list[tuple[str, str, str]]) -> str:
|
||||
async def _headers_table_sx(title: str, headers: list[tuple[str, str, str]]) -> str:
|
||||
"""Build a headers reference table."""
|
||||
rows = []
|
||||
for name, value, desc in headers:
|
||||
rows.append(
|
||||
f'(tr :class "border-b border-stone-100"'
|
||||
f' (td :class "px-3 py-2 font-mono text-sm text-violet-700 whitespace-nowrap" "{name}")'
|
||||
f' (td :class "px-3 py-2 font-mono text-sm text-stone-500" "{value}")'
|
||||
f' (td :class "px-3 py-2 text-stone-700 text-sm" "{desc}"))'
|
||||
)
|
||||
return (
|
||||
f'(div :class "space-y-3"'
|
||||
f' (h3 :class "text-xl font-semibold text-stone-700" "{title}")'
|
||||
f' (div :class "overflow-x-auto rounded border border-stone-200"'
|
||||
f' (table :class "w-full text-left text-sm"'
|
||||
f' (thead (tr :class "border-b border-stone-200 bg-stone-50"'
|
||||
f' (th :class "px-3 py-2 font-medium text-stone-600" "Header")'
|
||||
f' (th :class "px-3 py-2 font-medium text-stone-600" "Value")'
|
||||
f' (th :class "px-3 py-2 font-medium text-stone-600" "Description")))'
|
||||
f' (tbody {" ".join(rows)}))))'
|
||||
)
|
||||
rows.append(await render_to_sx("doc-headers-row",
|
||||
name=name, value=value, description=desc))
|
||||
rows_sx = "(<> " + " ".join(rows) + ")"
|
||||
return await render_to_sx("doc-headers-table", title=title, rows=SxExpr(rows_sx))
|
||||
|
||||
|
||||
async def _primitives_section_sx() -> str:
|
||||
@@ -86,10 +64,11 @@ async def _primitives_section_sx() -> str:
|
||||
|
||||
async def _sx_header_sx(nav: str | None = None, *, child: str | None = None) -> str:
|
||||
"""Build the sx docs menu-row."""
|
||||
label_sx = await render_to_sx("sx-docs-label")
|
||||
return await render_to_sx("menu-row-sx",
|
||||
id="sx-row", level=1, colour="violet",
|
||||
link_href="/", link_label="sx",
|
||||
link_label_content=SxExpr('(span :class "font-mono" "(<x>)")'),
|
||||
link_label_content=SxExpr(label_sx),
|
||||
nav=SxExpr(nav) if nav else None,
|
||||
child_id="sx-header-child",
|
||||
child=SxExpr(child) if child else None,
|
||||
|
||||
Reference in New Issue
Block a user