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:
2026-03-05 11:03:50 +00:00
parent 4a515f1a0d
commit 6fa843016b
6 changed files with 43 additions and 11 deletions

View File

@@ -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)