From 0ef4a93a9249751f7e265e4a3cee50b3bd0fb7b9 Mon Sep 17 00:00:00 2001 From: giles Date: Sun, 1 Mar 2026 15:16:42 +0000 Subject: [PATCH] Wrap raw Jinja HTML in (raw! "...") for sx source embedding Post edit, data, entries, and settings pages pass raw Jinja HTML as content to full_page_sx/oob_page_sx, which wraps it in SxExpr(). This injects unescaped HTML directly into sx source, breaking the parser. Fix by serializing the HTML into a (raw! "...") expression that the sx evaluator renders unescaped. Co-Authored-By: Claude Opus 4.6 --- blog/sx/sx_components.py | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/blog/sx/sx_components.py b/blog/sx/sx_components.py index c6e96a5..160a476 100644 --- a/blog/sx/sx_components.py +++ b/blog/sx/sx_components.py @@ -13,6 +13,7 @@ from typing import Any from markupsafe import escape from shared.sx.jinja_bridge import load_service_components +from shared.sx.parser import serialize as sx_serialize from shared.sx.helpers import ( SxExpr, sx_call, call_url, get_asset_url, @@ -1341,13 +1342,13 @@ async def render_post_data_page(ctx: dict) -> str: post_hdr = _post_header_sx(ctx) admin_hdr = _post_admin_header_sx(ctx, selected="data") header_rows = "(<> " + root_hdr + " " + post_hdr + ")" + admin_hdr - content = ctx.get("data_html", "") + content = _raw_html_sx(ctx.get("data_html", "")) return full_page_sx(ctx, header_rows=header_rows, content=content) async def render_post_data_oob(ctx: dict) -> str: admin_hdr_oob = _post_admin_header_sx(ctx, oob=True, selected="data") - content = ctx.get("data_html", "") + content = _raw_html_sx(ctx.get("data_html", "")) return oob_page_sx(oobs=admin_hdr_oob, content=content) @@ -1358,31 +1359,37 @@ async def render_post_entries_page(ctx: dict) -> str: post_hdr = _post_header_sx(ctx) admin_hdr = _post_admin_header_sx(ctx, selected="entries") header_rows = "(<> " + root_hdr + " " + post_hdr + ")" + admin_hdr - content = ctx.get("entries_html", "") + content = _raw_html_sx(ctx.get("entries_html", "")) return full_page_sx(ctx, header_rows=header_rows, content=content) async def render_post_entries_oob(ctx: dict) -> str: admin_hdr_oob = _post_admin_header_sx(ctx, oob=True, selected="entries") - content = ctx.get("entries_html", "") + content = _raw_html_sx(ctx.get("entries_html", "")) return oob_page_sx(oobs=admin_hdr_oob, content=content) # ---- Post edit ---- +def _raw_html_sx(html: str) -> str: + """Wrap raw HTML in (raw! "...") so it's valid inside sx source.""" + if not html: + return "" + return "(raw! " + sx_serialize(html) + ")" + + async def render_post_edit_page(ctx: dict) -> str: root_hdr = root_header_sx(ctx) post_hdr = _post_header_sx(ctx) admin_hdr = _post_admin_header_sx(ctx, selected="edit") header_rows = "(<> " + root_hdr + " " + post_hdr + ")" + admin_hdr - content = ctx.get("edit_html", "") - body_end = ctx.get("body_end_html", "") + content = _raw_html_sx(ctx.get("edit_html", "")) return full_page_sx(ctx, header_rows=header_rows, content=content) async def render_post_edit_oob(ctx: dict) -> str: admin_hdr_oob = _post_admin_header_sx(ctx, oob=True, selected="edit") - content = ctx.get("edit_html", "") + content = _raw_html_sx(ctx.get("edit_html", "")) return oob_page_sx(oobs=admin_hdr_oob, content=content) @@ -1393,13 +1400,13 @@ async def render_post_settings_page(ctx: dict) -> str: post_hdr = _post_header_sx(ctx) admin_hdr = _post_admin_header_sx(ctx, selected="settings") header_rows = "(<> " + root_hdr + " " + post_hdr + ")" + admin_hdr - content = ctx.get("settings_html", "") + content = _raw_html_sx(ctx.get("settings_html", "")) return full_page_sx(ctx, header_rows=header_rows, content=content) async def render_post_settings_oob(ctx: dict) -> str: admin_hdr_oob = _post_admin_header_sx(ctx, oob=True, selected="settings") - content = ctx.get("settings_html", "") + content = _raw_html_sx(ctx.get("settings_html", "")) return oob_page_sx(oobs=admin_hdr_oob, content=content)