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

@@ -126,22 +126,22 @@ def _register_blog_helpers() -> None:
# --- Editor helpers ---
async def _h_editor_content(**kw):
def _h_editor_content(**kw):
from .renders import render_editor_panel
return await render_editor_panel()
return render_editor_panel()
async def _h_editor_page_content(**kw):
def _h_editor_page_content(**kw):
from .renders import render_editor_panel
return await render_editor_panel(is_page=True)
return render_editor_panel(is_page=True)
# --- Post admin helpers ---
async def _h_post_admin_content(slug=None, **kw):
await _ensure_post_data(slug)
from shared.sx.helpers import render_to_sx
return await render_to_sx("blog-admin-placeholder")
from shared.sx.helpers import sx_call
return sx_call("blog-admin-placeholder")
async def _h_post_data_content(slug=None, **kw):
@@ -264,32 +264,32 @@ async def _h_post_preview_content(slug=None, **kw):
await _ensure_post_data(slug)
from quart import g
from shared.services.registry import services
from shared.sx.helpers import render_to_sx
from shared.sx.helpers import sx_call
from shared.sx.parser import SxExpr
preview = await services.blog_page.preview_data(g.s)
sections: list[str] = []
if preview.get("sx_pretty"):
sections.append(await render_to_sx("blog-preview-section",
sections.append(sx_call("blog-preview-section",
title="S-Expression Source", content=SxExpr(preview["sx_pretty"])))
if preview.get("json_pretty"):
sections.append(await render_to_sx("blog-preview-section",
sections.append(sx_call("blog-preview-section",
title="Lexical JSON", content=SxExpr(preview["json_pretty"])))
if preview.get("sx_rendered"):
rendered_sx = await render_to_sx("blog-preview-rendered", html=preview["sx_rendered"])
sections.append(await render_to_sx("blog-preview-section",
rendered_sx = sx_call("blog-preview-rendered", html=preview["sx_rendered"])
sections.append(sx_call("blog-preview-section",
title="SX Rendered", content=SxExpr(rendered_sx)))
if preview.get("lex_rendered"):
rendered_sx = await render_to_sx("blog-preview-rendered", html=preview["lex_rendered"])
sections.append(await render_to_sx("blog-preview-section",
rendered_sx = sx_call("blog-preview-rendered", html=preview["lex_rendered"])
sections.append(sx_call("blog-preview-section",
title="Lexical Rendered", content=SxExpr(rendered_sx)))
if not sections:
return await render_to_sx("blog-preview-empty")
return sx_call("blog-preview-empty")
inner = " ".join(sections)
return await render_to_sx("blog-preview-panel", sections=SxExpr(f"(<> {inner})"))
return sx_call("blog-preview-panel", sections=SxExpr(f"(<> {inner})"))
async def _h_post_entries_content(slug=None, **kw):
@@ -315,7 +315,7 @@ async def _h_post_entries_content(slug=None, **kw):
await g.s.refresh(calendar, ["entries", "post"])
# Associated entries list
assoc_html = await _render_associated_entries(all_calendars, associated_entry_ids, post_slug)
assoc_html = _render_associated_entries(all_calendars, associated_entry_ids, post_slug)
# Calendar browser
cal_items: list[str] = []
@@ -505,7 +505,7 @@ async def _h_post_edit_content(slug=None, **kw):
from sqlalchemy.orm import selectinload
from shared.infrastructure.data_client import fetch_data
from shared.browser.app.csrf import generate_csrf_token
from shared.sx.helpers import render_to_sx
from shared.sx.helpers import sx_call
from shared.sx.parser import SxExpr, serialize as sx_serialize
from bp.post.admin.routes import _post_to_edit_dict
@@ -584,9 +584,9 @@ async def _h_post_edit_content(slug=None, **kw):
parts: list[str] = []
if save_error:
parts.append(await render_to_sx("blog-editor-error", error=save_error))
parts.append(sx_call("blog-editor-error", error=save_error))
parts.append(await render_to_sx("blog-editor-edit-form",
parts.append(sx_call("blog-editor-edit-form",
csrf=csrf,
updated_at=str(updated_at),
title_val=title_val,
@@ -603,9 +603,9 @@ async def _h_post_edit_content(slug=None, **kw):
footer_extra=footer_extra_sx,
))
parts.append(await render_to_sx("blog-editor-publish-js", already_emailed=already_emailed))
parts.append(await render_to_sx("blog-editor-styles", css_href=editor_css))
parts.append(await render_to_sx("sx-editor-styles"))
parts.append(sx_call("blog-editor-publish-js", already_emailed=already_emailed))
parts.append(sx_call("blog-editor-styles", css_href=editor_css))
parts.append(sx_call("sx-editor-styles"))
init_js = (
'(function() {'
@@ -705,10 +705,10 @@ async def _h_post_edit_content(slug=None, **kw):
' }, 50); }'
'})();'
)
parts.append(await render_to_sx("blog-editor-scripts",
parts.append(sx_call("blog-editor-scripts",
js_src=editor_js,
sx_editor_js_src=sx_editor_js,
init_js=init_js))
return await render_to_sx("blog-editor-panel",
return sx_call("blog-editor-panel",
parts=SxExpr("(<> " + " ".join(parts) + ")"))

View File

@@ -5,19 +5,19 @@ from typing import Any
# ---------------------------------------------------------------------------
# Header helpers (moved from sx_components — thin render_to_sx wrappers)
# Header helpers (moved from sx_components — thin sx_call wrappers)
# ---------------------------------------------------------------------------
async def _settings_header_sx(ctx: dict, *, oob: bool = False) -> str:
from shared.sx.helpers import render_to_sx
def _settings_header_sx(ctx: dict, *, oob: bool = False) -> str:
from shared.sx.helpers import sx_call
from shared.sx.parser import SxExpr
from quart import url_for as qurl
settings_href = qurl("settings.defpage_settings_home")
label_sx = await render_to_sx("blog-admin-label")
nav_sx = await _settings_nav_sx(ctx)
label_sx = sx_call("blog-admin-label")
nav_sx = _settings_nav_sx(ctx)
return await render_to_sx("menu-row-sx",
return sx_call("menu-row-sx",
id="root-settings-row", level=1,
link_href=settings_href,
link_label_content=SxExpr(label_sx),
@@ -25,20 +25,20 @@ async def _settings_header_sx(ctx: dict, *, oob: bool = False) -> str:
child_id="root-settings-header-child", oob=oob)
async def _settings_nav_sx(ctx: dict) -> str:
from shared.sx.helpers import render_to_sx
return await render_to_sx("blog-settings-nav")
def _settings_nav_sx(ctx: dict) -> str:
from shared.sx.helpers import sx_call
return sx_call("blog-settings-nav")
async def _sub_settings_header_sx(row_id: str, child_id: str, href: str,
icon: str, label: str, ctx: dict,
*, oob: bool = False, nav_sx: str = "") -> str:
from shared.sx.helpers import render_to_sx
def _sub_settings_header_sx(row_id: str, child_id: str, href: str,
icon: str, label: str, ctx: dict,
*, oob: bool = False, nav_sx: str = "") -> str:
from shared.sx.helpers import sx_call
from shared.sx.parser import SxExpr
label_sx = await render_to_sx("blog-sub-settings-label",
label_sx = sx_call("blog-sub-settings-label",
icon=f"fa fa-{icon}", label=label)
return await render_to_sx("menu-row-sx",
return sx_call("menu-row-sx",
id=row_id, level=2,
link_href=href,
link_label_content=SxExpr(label_sx),
@@ -82,19 +82,19 @@ async def _settings_full(ctx: dict, **kw: Any) -> str:
from shared.sx.helpers import render_to_sx_with_env
from shared.sx.parser import SxExpr
return await render_to_sx_with_env("settings-layout-full", {},
settings_header=SxExpr(await _settings_header_sx(ctx)))
settings_header=SxExpr(_settings_header_sx(ctx)))
async def _settings_oob(ctx: dict, **kw: Any) -> str:
from shared.sx.helpers import render_to_sx_with_env, oob_header_sx
from shared.sx.parser import SxExpr
rows = await render_to_sx_with_env("settings-layout-full", {},
settings_header=SxExpr(await _settings_header_sx(ctx)))
settings_header=SxExpr(_settings_header_sx(ctx)))
return await oob_header_sx("root-header-child", "root-settings-header-child", rows)
async def _settings_mobile(ctx: dict, **kw: Any) -> str:
return await _settings_nav_sx(ctx)
def _settings_mobile(ctx: dict, **kw: Any) -> str:
return _settings_nav_sx(ctx)
# --- Sub-settings helpers ---
@@ -105,21 +105,21 @@ async def _sub_settings_full(ctx: dict, row_id: str, child_id: str,
from shared.sx.parser import SxExpr
from quart import url_for as qurl
return await render_to_sx_with_env("sub-settings-layout-full", {},
settings_header=SxExpr(await _settings_header_sx(ctx)),
sub_header=SxExpr(await _sub_settings_header_sx(
settings_header=SxExpr(_settings_header_sx(ctx)),
sub_header=SxExpr(_sub_settings_header_sx(
row_id, child_id, qurl(endpoint), icon, label, ctx)))
async def _sub_settings_oob(ctx: dict, row_id: str, child_id: str,
endpoint: str, icon: str, label: str) -> str:
from shared.sx.helpers import oob_header_sx, render_to_sx
from shared.sx.helpers import oob_header_sx, sx_call
from shared.sx.parser import SxExpr
from quart import url_for as qurl
settings_hdr_oob = await _settings_header_sx(ctx, oob=True)
sub_hdr = await _sub_settings_header_sx(
settings_hdr_oob = _settings_header_sx(ctx, oob=True)
sub_hdr = _sub_settings_header_sx(
row_id, child_id, qurl(endpoint), icon, label, ctx)
sub_oob = await oob_header_sx("root-settings-header-child", child_id, sub_hdr)
return await render_to_sx("sub-settings-layout-oob",
return sx_call("sub-settings-layout-oob",
settings_header_oob=SxExpr(settings_hdr_oob),
sub_header_oob=SxExpr(sub_oob))
@@ -180,8 +180,8 @@ async def _tag_group_edit_full(ctx: dict, **kw: Any) -> str:
from shared.sx.parser import SxExpr
g_id = (request.view_args or {}).get("id")
return await render_to_sx_with_env("sub-settings-layout-full", {},
settings_header=SxExpr(await _settings_header_sx(ctx)),
sub_header=SxExpr(await _sub_settings_header_sx(
settings_header=SxExpr(_settings_header_sx(ctx)),
sub_header=SxExpr(_sub_settings_header_sx(
"tag-groups-row", "tag-groups-header-child",
qurl("defpage_tag_group_edit", id=g_id),
"tags", "Tag Groups", ctx)))
@@ -189,15 +189,15 @@ async def _tag_group_edit_full(ctx: dict, **kw: Any) -> str:
async def _tag_group_edit_oob(ctx: dict, **kw: Any) -> str:
from quart import request, url_for as qurl
from shared.sx.helpers import oob_header_sx, render_to_sx
from shared.sx.helpers import oob_header_sx, sx_call
from shared.sx.parser import SxExpr
g_id = (request.view_args or {}).get("id")
settings_hdr_oob = await _settings_header_sx(ctx, oob=True)
sub_hdr = await _sub_settings_header_sx(
settings_hdr_oob = _settings_header_sx(ctx, oob=True)
sub_hdr = _sub_settings_header_sx(
"tag-groups-row", "tag-groups-header-child",
qurl("defpage_tag_group_edit", id=g_id),
"tags", "Tag Groups", ctx)
sub_oob = await oob_header_sx("root-settings-header-child", "tag-groups-header-child", sub_hdr)
return await render_to_sx("sub-settings-layout-oob",
return sx_call("sub-settings-layout-oob",
settings_header_oob=SxExpr(settings_hdr_oob),
sub_header_oob=SxExpr(sub_oob))

View File

@@ -2,12 +2,12 @@
from __future__ import annotations
async def render_editor_panel(save_error: str | None = None, is_page: bool = False) -> str:
def render_editor_panel(save_error: str | None = None, is_page: bool = False) -> str:
"""Build the WYSIWYG editor panel HTML for new post/page creation."""
import os
from quart import url_for as qurl, current_app
from shared.browser.app.csrf import generate_csrf_token
from shared.sx.helpers import render_to_sx
from shared.sx.helpers import sx_call
csrf = generate_csrf_token()
asset_url_fn = current_app.jinja_env.globals.get("asset_url", lambda p: "")
@@ -28,15 +28,15 @@ async def render_editor_panel(save_error: str | None = None, is_page: bool = Fal
parts: list[str] = []
if save_error:
parts.append(await render_to_sx("blog-editor-error", error=str(save_error)))
parts.append(sx_call("blog-editor-error", error=str(save_error)))
parts.append(await render_to_sx("blog-editor-form",
parts.append(sx_call("blog-editor-form",
csrf=csrf, title_placeholder=title_placeholder,
create_label=create_label,
))
parts.append(await render_to_sx("blog-editor-styles", css_href=editor_css))
parts.append(await render_to_sx("sx-editor-styles"))
parts.append(sx_call("blog-editor-styles", css_href=editor_css))
parts.append(sx_call("sx-editor-styles"))
init_js = (
"console.log('[EDITOR-DEBUG] init script running');\n"
@@ -167,11 +167,11 @@ async def render_editor_panel(save_error: str | None = None, is_page: bool = Fal
" }\n"
"})();\n"
)
parts.append(await render_to_sx("blog-editor-scripts",
parts.append(sx_call("blog-editor-scripts",
js_src=editor_js,
sx_editor_js_src=sx_editor_js,
init_js=init_js))
from shared.sx.parser import SxExpr
return await render_to_sx("blog-editor-panel",
return sx_call("blog-editor-panel",
parts=SxExpr("(<> " + " ".join(parts) + ")")) if parts else ""