Gate server-side component expansion with contextvar, fix nth arg order, add GEB essay and manifesto links
- Add _expand_components contextvar so _aser only expands components during page slot evaluation (fixes highlight on examples, avoids breaking fragment responses) - Fix nth arg order (nth coll n) in docs.sx, examples.sx (delete-row, edit-row, bulk-update) - Add "Godel, Escher, Bach and SX" essay with Wikipedia links - Update SX Manifesto: new authors, Wikipedia links throughout, remove Marx/Engels link Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -41,10 +41,18 @@ Usage::
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import contextvars
|
||||
import inspect
|
||||
from typing import Any
|
||||
|
||||
from .types import Component, Keyword, Lambda, Macro, NIL, StyleValue, Symbol
|
||||
|
||||
# When True, _aser expands known components server-side instead of serializing
|
||||
# them for client rendering. Set during page slot evaluation so Python-only
|
||||
# helpers (e.g. highlight) in component bodies execute on the server.
|
||||
_expand_components: contextvars.ContextVar[bool] = contextvars.ContextVar(
|
||||
"_expand_components", default=False
|
||||
)
|
||||
from .evaluator import _expand_macro, EvalError
|
||||
from .primitives import _PRIMITIVES
|
||||
from .primitives_io import IO_PRIMITIVES, RequestContext, execute_io
|
||||
@@ -1058,6 +1066,24 @@ async def async_eval_slot_to_sx(
|
||||
"""
|
||||
if ctx is None:
|
||||
ctx = RequestContext()
|
||||
|
||||
# Enable server-side component expansion for this slot evaluation.
|
||||
# This lets _aser expand known components (so Python-only helpers
|
||||
# like highlight execute server-side) instead of serializing them
|
||||
# for client rendering.
|
||||
token = _expand_components.set(True)
|
||||
try:
|
||||
return await _eval_slot_inner(expr, env, ctx)
|
||||
finally:
|
||||
_expand_components.reset(token)
|
||||
|
||||
|
||||
async def _eval_slot_inner(
|
||||
expr: Any,
|
||||
env: dict[str, Any],
|
||||
ctx: RequestContext,
|
||||
) -> str:
|
||||
"""Inner implementation — runs with _expand_components=True."""
|
||||
# If expr is a component call, expand it through _aser
|
||||
if isinstance(expr, list) and expr:
|
||||
head = expr[0]
|
||||
@@ -1159,13 +1185,14 @@ async def _aser(expr: Any, env: dict[str, Any], ctx: RequestContext) -> Any:
|
||||
if name.startswith("html:"):
|
||||
return await _aser_call(name[5:], expr[1:], env, ctx)
|
||||
|
||||
# Component call — expand macros, expand known components, serialize unknown
|
||||
# Component call — expand macros, expand known components (in slot
|
||||
# eval context only), serialize unknown
|
||||
if name.startswith("~"):
|
||||
val = env.get(name)
|
||||
if isinstance(val, Macro):
|
||||
expanded = _expand_macro(val, expr[1:], env)
|
||||
return await _aser(expanded, env, ctx)
|
||||
if isinstance(val, Component):
|
||||
if isinstance(val, Component) and _expand_components.get():
|
||||
return await _aser_component(val, expr[1:], env, ctx)
|
||||
return await _aser_call(name, expr[1:], env, ctx)
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -63,7 +63,8 @@
|
||||
(dict :label "SX Native" :href "/essays/sx-native")
|
||||
(dict :label "The SX Manifesto" :href "/essays/sx-manifesto")
|
||||
(dict :label "Tail-Call Optimization" :href "/essays/tail-call-optimization")
|
||||
(dict :label "Continuations" :href "/essays/continuations")))
|
||||
(dict :label "Continuations" :href "/essays/continuations")
|
||||
(dict :label "Godel, Escher, Bach" :href "/essays/godel-escher-bach")))
|
||||
|
||||
;; Find the current nav label for a slug by matching href suffix.
|
||||
;; Returns the label string or nil if no match.
|
||||
|
||||
@@ -61,15 +61,15 @@
|
||||
(defcomp ~doc-nav (&key items current)
|
||||
(nav :class "flex flex-wrap gap-2 mb-8"
|
||||
(map (fn (item)
|
||||
(a :href (nth 1 item)
|
||||
:sx-get (nth 1 item)
|
||||
(a :href (nth item 1)
|
||||
:sx-get (nth item 1)
|
||||
:sx-target "#main-panel"
|
||||
:sx-select "#main-panel"
|
||||
:sx-swap "outerHTML"
|
||||
:sx-push-url "true"
|
||||
:class (str "px-3 py-1.5 rounded text-sm font-medium no-underline "
|
||||
(if (= (nth 0 item) current)
|
||||
(if (= (nth item 0) current)
|
||||
"bg-violet-100 text-violet-800"
|
||||
"bg-stone-100 text-stone-600 hover:bg-stone-200"))
|
||||
(nth 0 item)))
|
||||
(nth item 0)))
|
||||
items)))
|
||||
|
||||
@@ -91,7 +91,7 @@
|
||||
(th :class "px-3 py-2 font-medium text-stone-600 w-20" "")))
|
||||
(tbody :id "delete-rows"
|
||||
(map (fn (item)
|
||||
(~delete-row :id (nth 0 item) :name (nth 1 item)))
|
||||
(~delete-row :id (nth item 0) :name (nth item 1)))
|
||||
items)))))
|
||||
|
||||
(defcomp ~delete-row (&key id name)
|
||||
@@ -344,7 +344,7 @@
|
||||
(th :class "px-3 py-2 font-medium text-stone-600 w-24" "")))
|
||||
(tbody :id "edit-rows"
|
||||
(map (fn (row)
|
||||
(~edit-row-view :id (nth 0 row) :name (nth 1 row) :price (nth 2 row) :stock (nth 3 row)))
|
||||
(~edit-row-view :id (nth row 0) :name (nth row 1) :price (nth row 2) :stock (nth row 3)))
|
||||
rows)))))
|
||||
|
||||
(defcomp ~edit-row-view (&key id name price stock)
|
||||
@@ -415,7 +415,7 @@
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Status")))
|
||||
(tbody :id "bulk-table"
|
||||
(map (fn (u)
|
||||
(~bulk-row :id (nth 0 u) :name (nth 1 u) :email (nth 2 u) :status (nth 3 u)))
|
||||
(~bulk-row :id (nth u 0) :name (nth u 1) :email (nth u 2) :status (nth u 3)))
|
||||
users))))))
|
||||
|
||||
(defcomp ~bulk-row (&key id name email status)
|
||||
|
||||
@@ -239,4 +239,5 @@
|
||||
"sx-manifesto" (~essay-sx-manifesto)
|
||||
"tail-call-optimization" (~essay-tail-call-optimization)
|
||||
"continuations" (~essay-continuations)
|
||||
"godel-escher-bach" (~essay-godel-escher-bach)
|
||||
:else (~essay-sx-sucks)))
|
||||
|
||||
Reference in New Issue
Block a user