Migrate all apps to defpage declarative page routes
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 3m41s

Replace Python GET page handlers with declarative defpage definitions in .sx
files across all 8 apps (sx docs, orders, account, market, cart, federation,
events, blog). Each app now has sxc/pages/ with setup functions, layout
registrations, page helpers, and .sx defpage declarations.

Core infrastructure: add g I/O primitive, PageDef support for auth/layout/
data/content/filter/aside/menu slots, post_author auth level, and custom
layout registration. Remove ~1400 lines of render_*_page/render_*_oob
boilerplate. Update all endpoint references in routes, sx_components, and
templates to defpage_* naming.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-03 14:52:34 +00:00
parent 5b4cacaf19
commit c243d17eeb
108 changed files with 3598 additions and 2851 deletions

View File

@@ -33,7 +33,7 @@ from __future__ import annotations
from typing import Any
from .types import Component, HandlerDef, Keyword, Lambda, Macro, NIL, RelationDef, Symbol
from .types import Component, HandlerDef, Keyword, Lambda, Macro, NIL, PageDef, RelationDef, Symbol
from .primitives import _PRIMITIVES
@@ -635,6 +635,85 @@ def _sf_defrelation(expr: list, env: dict) -> RelationDef:
return defn
def _sf_defpage(expr: list, env: dict) -> PageDef:
"""``(defpage name :path "/..." :auth :public :content expr ...)``
Parses keyword args from the expression. All slot values are stored
as unevaluated AST — they are resolved at request time by execute_page().
"""
if len(expr) < 2:
raise EvalError("defpage requires a name")
name_sym = expr[1]
if not isinstance(name_sym, Symbol):
raise EvalError(f"defpage name must be symbol, got {type(name_sym).__name__}")
# Parse keyword args — values are NOT evaluated (stored as AST)
slots: dict[str, Any] = {}
i = 2
while i < len(expr):
key = expr[i]
if isinstance(key, Keyword) and i + 1 < len(expr):
slots[key.name] = expr[i + 1]
i += 2
else:
i += 1
# Required fields
path = slots.get("path")
if path is None:
raise EvalError(f"defpage {name_sym.name} missing required :path")
if not isinstance(path, str):
raise EvalError(f"defpage {name_sym.name} :path must be a string")
auth_val = slots.get("auth", "public")
if isinstance(auth_val, Keyword):
auth: str | list = auth_val.name
elif isinstance(auth_val, list):
# (:rights "a" "b") → ["rights", "a", "b"]
auth = []
for item in auth_val:
if isinstance(item, Keyword):
auth.append(item.name)
elif isinstance(item, str):
auth.append(item)
else:
auth.append(_eval(item, env))
else:
auth = str(auth_val) if auth_val else "public"
# Layout — keep unevaluated
layout = slots.get("layout")
if isinstance(layout, Keyword):
layout = layout.name
elif isinstance(layout, list):
# Keep as unevaluated list for execute_page to resolve at request time
pass
# Cache — evaluate if present (it's a static config dict)
cache_val = slots.get("cache")
cache = None
if cache_val is not None:
cache_result = _eval(cache_val, env)
if isinstance(cache_result, dict):
cache = cache_result
page = PageDef(
name=name_sym.name,
path=path,
auth=auth,
layout=layout,
cache=cache,
data_expr=slots.get("data"),
content_expr=slots.get("content"),
filter_expr=slots.get("filter"),
aside_expr=slots.get("aside"),
menu_expr=slots.get("menu"),
closure=dict(env),
)
env[f"page:{name_sym.name}"] = page
return page
_SPECIAL_FORMS: dict[str, Any] = {
"if": _sf_if,
"when": _sf_when,
@@ -657,6 +736,7 @@ _SPECIAL_FORMS: dict[str, Any] = {
"defmacro": _sf_defmacro,
"quasiquote": _sf_quasiquote,
"defhandler": _sf_defhandler,
"defpage": _sf_defpage,
}