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:
2026-03-04 18:20:57 +00:00
parent 8be00df6d9
commit 7fda7a8027
41 changed files with 551 additions and 523 deletions

View File

@@ -3,6 +3,11 @@
(defcomp ~cart-page-label-img (&key src)
(img :src src :class "h-8 w-8 rounded-full object-cover border border-stone-300 flex-shrink-0"))
(defcomp ~cart-page-label (&key feature-image title)
(<> (when feature-image
(~cart-page-label-img :src feature-image))
(span title)))
(defcomp ~cart-all-carts-link (&key href)
(a :href href :class "inline-flex items-center gap-1.5 px-3 py-1.5 text-sm rounded-full border border-stone-300 bg-white hover:bg-stone-50 transition"
(i :class "fa fa-arrow-left text-xs" :aria-hidden "true") "All carts"))

View File

@@ -1,12 +1,9 @@
;; Cart layout defcomps — root header from env free variables,
;; cart-specific headers passed as &key params.
;; Cart layout defcomps — fully self-contained via IO primitives.
;; --- cart-page layout: root + cart row + page-cart row ---
(defcomp ~cart-page-layout-full (&key cart-row page-cart-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)
(~header-child-sx
:inner (<> cart-row
(~header-child-sx :id "cart-header-child" :inner page-cart-row)))))
@@ -19,40 +16,31 @@
;; --- cart-admin layout: root + post header + admin header ---
(defcomp ~cart-admin-layout-full (&key post-header admin-header)
(<> (~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)
post-header admin-header))
;; --- orders-within-cart: root + auth-simple + orders ---
(defcomp ~cart-orders-layout-full (&key list-url)
(<> (~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)
(~header-child-sx
:inner (<> (~auth-header-row-simple :account-url account-url)
:inner (<> (~auth-header-row-simple-auto)
(~header-child-sx :id "auth-header-child"
:inner (~orders-header-row :list-url list-url))))))
(defcomp ~cart-orders-layout-oob (&key list-url)
(<> (~auth-header-row-simple :account-url account-url :oob true)
(<> (~auth-header-row-simple-auto true)
(~oob-header-sx
:parent-id "auth-header-child"
:row (~orders-header-row :list-url list-url))
(~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
:oob true)))
(~root-header-auto true)))
;; --- order-detail-within-cart: root + auth-simple + orders + order ---
(defcomp ~cart-order-detail-layout-full (&key list-url detail-url order-label)
(<> (~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)
(~header-child-sx
:inner (<> (~auth-header-row-simple :account-url account-url)
:inner (<> (~auth-header-row-simple-auto)
(~header-child-sx :id "auth-header-child"
:inner (<> (~orders-header-row :list-url list-url)
(~header-child-sx :id "orders-header-child"
@@ -67,10 +55,7 @@
:row (~menu-row-sx :id "order-row" :level 3 :colour "sky"
:link-href detail-url :link-label order-label
:icon "fa fa-gbp" :oob true))
(~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
:oob true)))
(~root-header-auto true)))
;; --- orders rows wrapper (for infinite scroll) ---

View File

@@ -3,7 +3,6 @@ from __future__ import annotations
from typing import Any
from markupsafe import escape
from shared.sx.parser import SxExpr
@@ -73,11 +72,9 @@ async def _page_cart_header_sx(ctx: dict, page_post: Any, *, oob: bool = False)
from shared.sx.helpers import render_to_sx, call_url
slug = page_post.slug if page_post else ""
title = ((page_post.title if page_post else None) or "")[:160]
label_parts = []
if page_post and page_post.feature_image:
label_parts.append(await render_to_sx("cart-page-label-img", src=page_post.feature_image))
label_parts.append(f'(span "{escape(title)}")')
label_sx = "(<> " + " ".join(label_parts) + ")"
label_sx = await render_to_sx("cart-page-label",
feature_image=page_post.feature_image if page_post else None,
title=title)
nav_sx = await render_to_sx("cart-all-carts-link", href=call_url(ctx, "cart_url", "/"))
return await render_to_sx(
"menu-row-sx",
@@ -101,9 +98,9 @@ async def _cart_page_admin_header_sx(ctx: dict, page_post: Any, *, oob: bool = F
# ---------------------------------------------------------------------------
async def _cart_page_full(ctx: dict, **kw: Any) -> str:
from shared.sx.helpers import render_to_sx_with_env, _ctx_to_env
from shared.sx.helpers import render_to_sx_with_env
page_post = ctx.get("page_post")
env = _ctx_to_env(ctx)
env = {}
return await render_to_sx_with_env("cart-page-layout-full", env,
cart_row=SxExpr(await _cart_header_sx(ctx)),
page_cart_row=SxExpr(await _page_cart_header_sx(ctx, page_post)),
@@ -111,9 +108,9 @@ async def _cart_page_full(ctx: dict, **kw: Any) -> str:
async def _cart_page_oob(ctx: dict, **kw: Any) -> str:
from shared.sx.helpers import render_to_sx_with_env, _ctx_to_env, root_header_sx
from shared.sx.helpers import render_to_sx_with_env, root_header_sx
page_post = ctx.get("page_post")
env = _ctx_to_env(ctx, oob=True)
env = {}
return await render_to_sx_with_env("cart-page-layout-oob", env,
root_header_oob=SxExpr(await root_header_sx(ctx, oob=True)),
cart_row_oob=SxExpr(await _cart_header_sx(ctx, oob=True)),
@@ -122,10 +119,10 @@ async def _cart_page_oob(ctx: dict, **kw: Any) -> str:
async def _cart_admin_full(ctx: dict, **kw: Any) -> str:
from shared.sx.helpers import render_to_sx_with_env, _ctx_to_env
from shared.sx.helpers import render_to_sx_with_env
page_post = ctx.get("page_post")
selected = kw.get("selected", "")
env = _ctx_to_env(ctx)
env = {}
return await render_to_sx_with_env("cart-admin-layout-full", env,
post_header=SxExpr(await _post_header_sx(ctx, page_post)),
admin_header=SxExpr(await _cart_page_admin_header_sx(ctx, page_post, selected=selected)),

View File

@@ -7,7 +7,7 @@ from .utils import _serialize_order, _serialize_calendar_entry
async def render_orders_page(ctx, orders, page, total_pages, search, search_count, url_for_fn, qs_fn):
from shared.sx.helpers import render_to_sx, render_to_sx_with_env, _ctx_to_env, search_desktop_sx, search_mobile_sx, full_page_sx
from shared.sx.helpers import render_to_sx, render_to_sx_with_env, search_desktop_sx, search_mobile_sx, full_page_sx
from shared.utils import route_prefix
ctx["search"] = search
ctx["search_count"] = search_count
@@ -17,7 +17,7 @@ async def render_orders_page(ctx, orders, page, total_pages, search, search_coun
order_dicts = [_serialize_order(o) for o in orders]
content = await render_to_sx("orders-list-content", orders=order_dicts,
page=page, total_pages=total_pages, rows_url=list_url, detail_url_prefix=detail_url_prefix)
header_rows = await render_to_sx_with_env("cart-orders-layout-full", _ctx_to_env(ctx),
header_rows = await render_to_sx_with_env("cart-orders-layout-full", {},
list_url=list_url,
)
filt = await render_to_sx("order-list-header", search_mobile=SxExpr(await search_mobile_sx(ctx)))
@@ -49,7 +49,7 @@ async def render_orders_rows(ctx, orders, page, total_pages, url_for_fn, qs_fn):
async def render_orders_oob(ctx, orders, page, total_pages, search, search_count, url_for_fn, qs_fn):
from shared.sx.helpers import render_to_sx, render_to_sx_with_env, _ctx_to_env, search_desktop_sx, search_mobile_sx, oob_page_sx
from shared.sx.helpers import render_to_sx, render_to_sx_with_env, search_desktop_sx, search_mobile_sx, oob_page_sx
from shared.utils import route_prefix
ctx["search"] = search
ctx["search_count"] = search_count
@@ -59,7 +59,7 @@ async def render_orders_oob(ctx, orders, page, total_pages, search, search_count
order_dicts = [_serialize_order(o) for o in orders]
content = await render_to_sx("orders-list-content", orders=order_dicts,
page=page, total_pages=total_pages, rows_url=list_url, detail_url_prefix=detail_url_prefix)
oobs = await render_to_sx_with_env("cart-orders-layout-oob", _ctx_to_env(ctx, oob=True),
oobs = await render_to_sx_with_env("cart-orders-layout-oob", {},
list_url=list_url,
)
filt = await render_to_sx("order-list-header", search_mobile=SxExpr(await search_mobile_sx(ctx)))
@@ -67,7 +67,7 @@ async def render_orders_oob(ctx, orders, page, total_pages, search, search_count
async def render_order_page(ctx, order, calendar_entries, url_for_fn):
from shared.sx.helpers import render_to_sx, render_to_sx_with_env, _ctx_to_env, full_page_sx
from shared.sx.helpers import render_to_sx, render_to_sx_with_env, full_page_sx
from shared.utils import route_prefix
from shared.browser.app.csrf import generate_csrf_token
pfx = route_prefix()
@@ -80,7 +80,7 @@ async def render_order_page(ctx, order, calendar_entries, url_for_fn):
main = await render_to_sx("order-detail-content", order=order_data, calendar_entries=cal_data)
filt = await render_to_sx("order-detail-filter-content", order=order_data,
list_url=list_url, recheck_url=recheck_url, pay_url=pay_url, csrf=generate_csrf_token())
header_rows = await render_to_sx_with_env("cart-order-detail-layout-full", _ctx_to_env(ctx),
header_rows = await render_to_sx_with_env("cart-order-detail-layout-full", {},
list_url=list_url, detail_url=detail_url,
order_label=f"Order {order.id}",
)
@@ -88,7 +88,7 @@ async def render_order_page(ctx, order, calendar_entries, url_for_fn):
async def render_order_oob(ctx, order, calendar_entries, url_for_fn):
from shared.sx.helpers import render_to_sx, render_to_sx_with_env, _ctx_to_env, oob_page_sx
from shared.sx.helpers import render_to_sx, render_to_sx_with_env, oob_page_sx
from shared.utils import route_prefix
from shared.browser.app.csrf import generate_csrf_token
pfx = route_prefix()
@@ -101,7 +101,7 @@ async def render_order_oob(ctx, order, calendar_entries, url_for_fn):
main = await render_to_sx("order-detail-content", order=order_data, calendar_entries=cal_data)
filt = await render_to_sx("order-detail-filter-content", order=order_data,
list_url=list_url, recheck_url=recheck_url, pay_url=pay_url, csrf=generate_csrf_token())
oobs = await render_to_sx_with_env("cart-order-detail-layout-oob", _ctx_to_env(ctx, oob=True),
oobs = await render_to_sx_with_env("cart-order-detail-layout-oob", {},
detail_url=detail_url,
order_label=f"Order {order.id}",
)
@@ -109,11 +109,11 @@ async def render_order_oob(ctx, order, calendar_entries, url_for_fn):
async def render_checkout_error_page(ctx, error=None, order=None):
from shared.sx.helpers import render_to_sx, render_to_sx_with_env, _ctx_to_env, full_page_sx
from shared.sx.helpers import render_to_sx, render_to_sx_with_env, full_page_sx
from shared.infrastructure.urls import cart_url
err_msg = error or "Unexpected error while creating the hosted checkout session."
order_sx = await render_to_sx("checkout-error-order-id", oid=f"#{order.id}") if order else None
hdr = await render_to_sx_with_env("layout-root-full", _ctx_to_env(ctx))
hdr = await render_to_sx_with_env("layout-root-full", {})
filt = await render_to_sx("checkout-error-header")
content = await render_to_sx("checkout-error-content", msg=err_msg,
order=SxExpr(order_sx) if order_sx else None, back_url=cart_url("/"))