Remove render_to_sx from public API: enforce sx_call for all service code
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 1m44s

Replace ~250 render_to_sx calls across all services with sync sx_call,
converting many async functions to sync where no other awaits remained.
Make render_to_sx/render_to_sx_with_env private (_render_to_sx).
Add (post-header-ctx) IO primitive and shared post/post-admin defmacros.
Convert built-in post/post-admin layouts from Python to register_sx_layout
with .sx defcomps. Remove dead post_admin_mobile_nav_sx.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-04 19:30:45 +00:00
parent 57e0d0c341
commit 959e63d440
61 changed files with 1352 additions and 1208 deletions

View File

@@ -2,7 +2,7 @@
from __future__ import annotations
from shared.sx.helpers import render_to_sx
from shared.sx.helpers import sx_call
from shared.sx.parser import SxExpr
@@ -111,7 +111,7 @@ _SLOT_PICKER_JS = """\
# Slot options (shared by entry edit + add forms)
# ---------------------------------------------------------------------------
async def _slot_options_html(day_slots, selected_slot_id=None) -> str:
def _slot_options_html(day_slots, selected_slot_id=None) -> str:
"""Build slot <option> elements."""
parts = []
for slot in day_slots:
@@ -129,7 +129,7 @@ async def _slot_options_html(day_slots, selected_slot_id=None) -> str:
label_parts.append("[flexible]")
label = " ".join(label_parts)
parts.append(await render_to_sx("events-slot-option",
parts.append(sx_call("events-slot-option",
value=str(slot.id),
data_start=start, data_end=end,
data_flexible="1" if flexible else "0",
@@ -143,7 +143,7 @@ async def _slot_options_html(day_slots, selected_slot_id=None) -> str:
# Slot header row
# ---------------------------------------------------------------------------
async def _slot_header_html(ctx: dict, *, oob: bool = False) -> str:
def _slot_header_html(ctx: dict, *, oob: bool = False) -> str:
"""Build the slot detail header row."""
from quart import url_for
@@ -156,10 +156,10 @@ async def _slot_header_html(ctx: dict, *, oob: bool = False) -> str:
return ""
desc = getattr(slot, "description", "") or ""
label_sx = await render_to_sx("events-slot-label",
label_sx = sx_call("events-slot-label",
name=slot.name, description=desc)
return await render_to_sx("menu-row-sx", id="slot-row", level=5,
return sx_call("menu-row-sx", id="slot-row", level=5,
link_label_content=SxExpr(label_sx),
child_id="slot-header-child", oob=oob)
@@ -168,7 +168,7 @@ async def _slot_header_html(ctx: dict, *, oob: bool = False) -> str:
# Slot main panel
# ---------------------------------------------------------------------------
async def render_slot_main_panel(slot, calendar, *, oob: bool = False) -> str:
def render_slot_main_panel(slot, calendar, *, oob: bool = False) -> str:
"""Render slot detail view."""
from quart import url_for, g
@@ -191,15 +191,15 @@ async def render_slot_main_panel(slot, calendar, *, oob: bool = False) -> str:
# Days pills
if days and days[0] != "\u2014":
days_inner = "".join(
await render_to_sx("events-slot-day-pill", day=d) for d in days
sx_call("events-slot-day-pill", day=d) for d in days
)
days_html = await render_to_sx("events-slot-days-pills", days_inner=SxExpr(days_inner))
days_html = sx_call("events-slot-days-pills", days_inner=SxExpr(days_inner))
else:
days_html = await render_to_sx("events-slot-no-days")
days_html = sx_call("events-slot-no-days")
sid = str(slot.id)
result = await render_to_sx("events-slot-panel",
result = sx_call("events-slot-panel",
slot_id=sid, list_container=list_container,
days=SxExpr(days_html),
flexible="yes" if flexible else "no",
@@ -208,7 +208,7 @@ async def render_slot_main_panel(slot, calendar, *, oob: bool = False) -> str:
pre_action=pre_action, edit_url=edit_url)
if oob:
result += await render_to_sx("events-slot-description-oob", description=desc)
result += sx_call("events-slot-description-oob", description=desc)
return result
@@ -217,7 +217,7 @@ async def render_slot_main_panel(slot, calendar, *, oob: bool = False) -> str:
# Slots table
# ---------------------------------------------------------------------------
async def render_slots_table(slots, calendar) -> str:
def render_slots_table(slots, calendar) -> str:
"""Render slots table with rows and add button."""
from quart import url_for, g
from shared.browser.app.csrf import generate_csrf_token
@@ -243,18 +243,18 @@ async def render_slots_table(slots, calendar) -> str:
day_list = days_display.split(", ")
if day_list and day_list[0] != "\u2014":
days_inner = "".join(
await render_to_sx("events-slot-day-pill", day=d) for d in day_list
sx_call("events-slot-day-pill", day=d) for d in day_list
)
days_html = await render_to_sx("events-slot-days-pills", days_inner=SxExpr(days_inner))
days_html = sx_call("events-slot-days-pills", days_inner=SxExpr(days_inner))
else:
days_html = await render_to_sx("events-slot-no-days")
days_html = sx_call("events-slot-no-days")
time_start = s.time_start.strftime("%H:%M") if s.time_start else ""
time_end = s.time_end.strftime("%H:%M") if s.time_end else ""
cost = getattr(s, "cost", None)
cost_str = f"{cost:.2f}" if cost is not None else ""
rows_html += await render_to_sx("events-slots-row",
rows_html += sx_call("events-slots-row",
tr_cls=tr_cls, slot_href=slot_href,
pill_cls=pill_cls, hx_select=hx_select,
slot_name=s.name, description=desc,
@@ -265,11 +265,11 @@ async def render_slots_table(slots, calendar) -> str:
del_url=del_url,
csrf_hdr=f'{{"X-CSRFToken": "{csrf}"}}')
else:
rows_html = await render_to_sx("events-slots-empty-row")
rows_html = sx_call("events-slots-empty-row")
add_url = url_for("calendar.slots.add_form", calendar_slug=cal_slug)
return await render_to_sx("events-slots-table",
return sx_call("events-slots-table",
list_container=list_container, rows=SxExpr(rows_html),
pre_action=pre_action, add_url=add_url)
@@ -278,7 +278,7 @@ async def render_slots_table(slots, calendar) -> str:
# Slot edit form
# ---------------------------------------------------------------------------
async def render_slot_edit_form(slot, calendar) -> str:
def render_slot_edit_form(slot, calendar) -> str:
"""Render slot edit form (replaces _types/slot/_edit.html)."""
from quart import url_for, g
from shared.browser.app.csrf import generate_csrf_token
@@ -305,18 +305,18 @@ async def render_slot_edit_form(slot, calendar) -> str:
("fri", "Fri"), ("sat", "Sat"), ("sun", "Sun")]
all_checked = all(getattr(slot, k, False) for k, _ in day_keys)
days_parts = [await render_to_sx("events-day-all-checkbox",
days_parts = [sx_call("events-day-all-checkbox",
checked="checked" if all_checked else None)]
for key, label in day_keys:
checked = getattr(slot, key, False)
days_parts.append(await render_to_sx("events-day-checkbox",
days_parts.append(sx_call("events-day-checkbox",
name=key, label=label,
checked="checked" if checked else None))
days_html = "".join(days_parts)
flexible = getattr(slot, "flexible", False)
return await render_to_sx("events-slot-edit-form",
return sx_call("events-slot-edit-form",
slot_id=str(sid), list_container=list_container,
put_url=put_url, cancel_url=cancel_url, csrf=csrf,
name_val=slot.name or "", cost_val=cost_val,
@@ -330,7 +330,7 @@ async def render_slot_edit_form(slot, calendar) -> str:
# Slot add form / button
# ---------------------------------------------------------------------------
async def render_slot_add_form(calendar) -> str:
def render_slot_add_form(calendar) -> str:
"""Render slot add form (replaces _types/slots/_add.html)."""
from quart import url_for, g
from shared.browser.app.csrf import generate_csrf_token
@@ -348,19 +348,19 @@ async def render_slot_add_form(calendar) -> str:
# Days checkboxes (all unchecked for add)
day_keys = [("mon", "Mon"), ("tue", "Tue"), ("wed", "Wed"), ("thu", "Thu"),
("fri", "Fri"), ("sat", "Sat"), ("sun", "Sun")]
days_parts = [await render_to_sx("events-day-all-checkbox", checked=None)]
days_parts = [sx_call("events-day-all-checkbox", checked=None)]
for key, label in day_keys:
days_parts.append(await render_to_sx("events-day-checkbox", name=key, label=label, checked=None))
days_parts.append(sx_call("events-day-checkbox", name=key, label=label, checked=None))
days_html = "".join(days_parts)
return await render_to_sx("events-slot-add-form",
return sx_call("events-slot-add-form",
post_url=post_url, csrf=csrf_hdr,
days=SxExpr(days_html),
action_btn=action_btn, cancel_btn=cancel_btn,
cancel_url=cancel_url)
async def render_slot_add_button(calendar) -> str:
def render_slot_add_button(calendar) -> str:
"""Render slot add button (replaces _types/slots/_add_button.html)."""
from quart import url_for, g
@@ -369,4 +369,4 @@ async def render_slot_add_button(calendar) -> str:
cal_slug = getattr(calendar, "slug", "")
add_url = url_for("calendar.slots.add_form", calendar_slug=cal_slug)
return await render_to_sx("events-slot-add-button", pre_action=pre_action, add_url=add_url)
return sx_call("events-slot-add-button", pre_action=pre_action, add_url=add_url)