Convert test/cart/blog/market layouts to use _ctx_to_env + render_to_sx_with_env

Phase 4 (Test): Update ~test-layout-full and ~test-detail-layout-full defcomps
to use ~root-header with env free variables. Switch render functions to
render_to_sx_with_env.

Phase 5 (Cart): Convert cart-page, cart-admin, and order render functions.
Update cart .sx layout defcomps to use ~root-header from free variables.

Phase 6 (Blog): Convert all 7 blog layouts (blog, settings, sub-settings x5).
Remove all root_header_sx calls from blog.

Phase 7 (Market): Convert market and market-admin layouts plus browse/product
render functions. Remove root_header_sx import.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-04 15:02:59 +00:00
parent a30e7228d8
commit 1dbf600af2
8 changed files with 507 additions and 350 deletions

78
cart/sx/layouts.sx Normal file
View File

@@ -0,0 +1,78 @@
;; Cart layout defcomps — root header from env free variables,
;; cart-specific headers passed as &key params.
;; --- 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)
(~header-child-sx
:inner (<> cart-row
(~header-child-sx :id "cart-header-child" :inner page-cart-row)))))
(defcomp ~cart-page-layout-oob (&key root-header-oob cart-row-oob page-cart-row)
(<> (~oob-header-sx :parent-id "cart-header-child" :row page-cart-row)
cart-row-oob
root-header-oob))
;; --- 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)
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)
(~header-child-sx
:inner (<> (~auth-header-row-simple :account-url account-url)
(~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)
(~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)))
;; --- 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)
(~header-child-sx
:inner (<> (~auth-header-row-simple :account-url account-url)
(~header-child-sx :id "auth-header-child"
:inner (<> (~orders-header-row :list-url list-url)
(~header-child-sx :id "orders-header-child"
:inner (~menu-row-sx :id "order-row" :level 3 :colour "sky"
:link-href detail-url
:link-label order-label
:icon "fa fa-gbp"))))))))
(defcomp ~cart-order-detail-layout-oob (&key detail-url order-label)
(<> (~oob-header-sx
:parent-id "orders-header-child"
: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)))
;; --- orders rows wrapper (for infinite scroll) ---
(defcomp ~cart-orders-rows (&key rows next-scroll)
(<> rows next-scroll))

View File

@@ -20,7 +20,7 @@ def _load_cart_page_files() -> None:
# ---------------------------------------------------------------------------
# Header helpers (moved from sx_components.py)
# Header helpers (still needed by layouts and render functions)
# ---------------------------------------------------------------------------
def _ensure_post_ctx(ctx: dict, page_post: Any) -> dict:
@@ -94,30 +94,8 @@ async def _page_cart_header_sx(ctx: dict, page_post: Any, *, oob: bool = False)
)
async def _auth_header_sx(ctx: dict, *, oob: bool = False) -> str:
from shared.sx.helpers import render_to_sx, call_url
return await render_to_sx(
"auth-header-row-simple",
account_url=call_url(ctx, "account_url", ""),
oob=oob,
)
async def _orders_header_sx(ctx: dict, list_url: str) -> str:
from shared.sx.helpers import render_to_sx
return await render_to_sx("orders-header-row", list_url=list_url)
async def _cart_page_admin_header_sx(ctx: dict, page_post: Any, *, oob: bool = False,
selected: str = "") -> str:
from shared.sx.helpers import post_admin_header_sx
slug = page_post.slug if page_post else ""
ctx = _ensure_post_ctx(ctx, page_post)
return await post_admin_header_sx(ctx, slug, oob=oob, selected=selected)
# ---------------------------------------------------------------------------
# Order serialization helpers (used by route render functions below)
# Order serialization helpers
# ---------------------------------------------------------------------------
def _serialize_order(order: Any) -> dict:
@@ -157,11 +135,11 @@ def _serialize_calendar_entry(e: Any) -> dict:
# ---------------------------------------------------------------------------
# Render functions (called by routes)
# Render functions (called by routes) — delegate header composition to .sx
# ---------------------------------------------------------------------------
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, root_header_sx, search_desktop_sx, search_mobile_sx, full_page_sx
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.utils import route_prefix
ctx["search"] = search
ctx["search_count"] = search_count
@@ -171,12 +149,9 @@ 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)
hdr = await root_header_sx(ctx)
auth = await _auth_header_sx(ctx)
orders_hdr = await _orders_header_sx(ctx, list_url)
auth_child_inner = await render_to_sx("header-child-sx", id="auth-header-child", inner=SxExpr(orders_hdr))
auth_child = await render_to_sx("header-child-sx", inner=SxExpr("(<> " + auth + " " + auth_child_inner + ")"))
header_rows = "(<> " + hdr + " " + auth_child + ")"
header_rows = await render_to_sx_with_env("cart-orders-layout-full", _ctx_to_env(ctx),
list_url=list_url,
)
filt = await render_to_sx("order-list-header", search_mobile=SxExpr(await search_mobile_sx(ctx)))
return await full_page_sx(ctx, header_rows=header_rows, filter=filt,
aside=await search_desktop_sx(ctx), content=content)
@@ -192,17 +167,21 @@ async def render_orders_rows(ctx, orders, page, total_pages, url_for_fn, qs_fn):
parts = []
for od in order_dicts:
parts.append(await render_to_sx("order-row-pair", order=od, detail_url_prefix=detail_url_prefix))
next_scroll = ""
if page < total_pages:
next_url = list_url + qs_fn(page=page + 1)
parts.append(await render_to_sx("infinite-scroll", url=next_url, page=page,
total_pages=total_pages, id_prefix="orders", colspan=5))
next_scroll = await render_to_sx("infinite-scroll", url=next_url, page=page,
total_pages=total_pages, id_prefix="orders", colspan=5)
else:
parts.append(await render_to_sx("order-end-row"))
return "(<> " + " ".join(parts) + ")"
next_scroll = await render_to_sx("order-end-row")
return await render_to_sx("cart-orders-rows",
rows=SxExpr("(<> " + " ".join(parts) + ")"),
next_scroll=SxExpr(next_scroll),
)
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, root_header_sx, search_desktop_sx, search_mobile_sx, oob_page_sx
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.utils import route_prefix
ctx["search"] = search
ctx["search_count"] = search_count
@@ -212,17 +191,15 @@ 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)
auth_oob = await _auth_header_sx(ctx, oob=True)
orders_hdr = await _orders_header_sx(ctx, list_url)
auth_child_oob = await render_to_sx("oob-header-sx", parent_id="auth-header-child", row=SxExpr(orders_hdr))
root_oob = await root_header_sx(ctx, oob=True)
oobs = "(<> " + auth_oob + " " + auth_child_oob + " " + root_oob + ")"
oobs = await render_to_sx_with_env("cart-orders-layout-oob", _ctx_to_env(ctx, oob=True),
list_url=list_url,
)
filt = await render_to_sx("order-list-header", search_mobile=SxExpr(await search_mobile_sx(ctx)))
return await oob_page_sx(oobs=oobs, filter=filt, aside=await search_desktop_sx(ctx), content=content)
async def render_order_page(ctx, order, calendar_entries, url_for_fn):
from shared.sx.helpers import render_to_sx, root_header_sx, full_page_sx
from shared.sx.helpers import render_to_sx, render_to_sx_with_env, _ctx_to_env, full_page_sx
from shared.utils import route_prefix
from shared.browser.app.csrf import generate_csrf_token
pfx = route_prefix()
@@ -235,20 +212,15 @@ 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())
hdr = await root_header_sx(ctx)
order_row = await render_to_sx("menu-row-sx", id="order-row", level=3, colour="sky",
link_href=detail_url, link_label=f"Order {order.id}", icon="fa fa-gbp")
auth = await _auth_header_sx(ctx)
orders_hdr = await _orders_header_sx(ctx, list_url)
orders_child = await render_to_sx("header-child-sx", id="orders-header-child", inner=SxExpr(order_row))
auth_inner = "(<> " + orders_hdr + " " + orders_child + ")"
auth_child = await render_to_sx("header-child-sx", id="auth-header-child", inner=SxExpr(auth_inner))
order_child = await render_to_sx("header-child-sx", inner=SxExpr("(<> " + auth + " " + auth_child + ")"))
return await full_page_sx(ctx, header_rows="(<> " + hdr + " " + order_child + ")", filter=filt, content=main)
header_rows = await render_to_sx_with_env("cart-order-detail-layout-full", _ctx_to_env(ctx),
list_url=list_url, detail_url=detail_url,
order_label=f"Order {order.id}",
)
return await full_page_sx(ctx, header_rows=header_rows, filter=filt, content=main)
async def render_order_oob(ctx, order, calendar_entries, url_for_fn):
from shared.sx.helpers import render_to_sx, root_header_sx, oob_page_sx
from shared.sx.helpers import render_to_sx, render_to_sx_with_env, _ctx_to_env, oob_page_sx
from shared.utils import route_prefix
from shared.browser.app.csrf import generate_csrf_token
pfx = route_prefix()
@@ -261,19 +233,19 @@ 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())
order_row_oob = await render_to_sx("menu-row-sx", id="order-row", level=3, colour="sky",
link_href=detail_url, link_label=f"Order {order.id}", icon="fa fa-gbp", oob=True)
orders_child_oob = await render_to_sx("oob-header-sx", parent_id="orders-header-child", row=SxExpr(order_row_oob))
root_oob = await root_header_sx(ctx, oob=True)
return await oob_page_sx(oobs="(<> " + orders_child_oob + " " + root_oob + ")", filter=filt, content=main)
oobs = await render_to_sx_with_env("cart-order-detail-layout-oob", _ctx_to_env(ctx, oob=True),
detail_url=detail_url,
order_label=f"Order {order.id}",
)
return await oob_page_sx(oobs=oobs, filter=filt, content=main)
async def render_checkout_error_page(ctx, error=None, order=None):
from shared.sx.helpers import render_to_sx, root_header_sx, full_page_sx
from shared.sx.helpers import render_to_sx, render_to_sx_with_env, _ctx_to_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 root_header_sx(ctx)
hdr = await render_to_sx_with_env("layout-root-full", _ctx_to_env(ctx))
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("/"))
@@ -294,7 +266,7 @@ async def render_cart_payments_panel(ctx):
# ---------------------------------------------------------------------------
# Layouts
# Layouts — thin wrappers delegating to .sx defcomps in cart/sx/layouts.sx
# ---------------------------------------------------------------------------
def _register_cart_layouts() -> None:
@@ -304,50 +276,46 @@ def _register_cart_layouts() -> None:
async def _cart_page_full(ctx: dict, **kw: Any) -> str:
from shared.sx.helpers import root_header_sx, render_to_sx
from shared.sx.parser import SxExpr
from shared.sx.helpers import render_to_sx_with_env, _ctx_to_env
page_post = ctx.get("page_post")
root_hdr = await root_header_sx(ctx)
child = await _cart_header_sx(ctx)
page_hdr = await _page_cart_header_sx(ctx, page_post)
inner_child = await render_to_sx("header-child-sx", id="cart-header-child", inner=SxExpr(page_hdr))
nested = await render_to_sx(
"header-child-sx",
inner=SxExpr("(<> " + child + " " + inner_child + ")"),
env = _ctx_to_env(ctx)
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)),
)
return "(<> " + root_hdr + " " + nested + ")"
async def _cart_page_oob(ctx: dict, **kw: Any) -> str:
from shared.sx.helpers import root_header_sx, render_to_sx
from shared.sx.parser import SxExpr
from shared.sx.helpers import render_to_sx_with_env, _ctx_to_env, root_header_sx
page_post = ctx.get("page_post")
page_hdr = await _page_cart_header_sx(ctx, page_post)
child_oob = await render_to_sx("oob-header-sx",
parent_id="cart-header-child",
row=SxExpr(page_hdr))
cart_hdr_oob = await _cart_header_sx(ctx, oob=True)
root_hdr_oob = await root_header_sx(ctx, oob=True)
return "(<> " + child_oob + " " + cart_hdr_oob + " " + root_hdr_oob + ")"
env = _ctx_to_env(ctx, oob=True)
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)),
page_cart_row=SxExpr(await _page_cart_header_sx(ctx, page_post)),
)
async def _cart_admin_full(ctx: dict, **kw: Any) -> str:
from shared.sx.helpers import root_header_sx
from shared.sx.helpers import render_to_sx_with_env, _ctx_to_env
page_post = ctx.get("page_post")
selected = kw.get("selected", "")
root_hdr = await root_header_sx(ctx)
post_hdr = await _post_header_sx(ctx, page_post)
admin_hdr = await _cart_page_admin_header_sx(ctx, page_post, selected=selected)
return "(<> " + root_hdr + " " + post_hdr + " " + admin_hdr + ")"
env = _ctx_to_env(ctx)
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)),
)
async def _cart_admin_oob(ctx: dict, **kw: Any) -> str:
page_post = ctx.get("page_post")
selected = kw.get("selected", "")
return await _cart_page_admin_header_sx(ctx, page_post, oob=True, selected=selected)
async def _cart_page_admin_header_sx(ctx: dict, page_post: Any, *, oob: bool = False,
selected: str = "") -> str:
from shared.sx.helpers import post_admin_header_sx
slug = page_post.slug if page_post else ""
ctx = _ensure_post_ctx(ctx, page_post)
return await post_admin_header_sx(ctx, slug, oob=oob, selected=selected)