Enforce SX boundary contract via boundary.sx spec + runtime validation
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 2m33s
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 2m33s
Add boundary.sx declaring all 34 I/O primitives, 32 page helpers, and 9 allowed boundary types. Runtime validation in boundary.py checks every registration against the spec — undeclared primitives/helpers crash at startup with SX_BOUNDARY_STRICT=1 (now set in both dev and prod). Key changes: - Move 5 I/O-in-disguise primitives (app-url, asset-url, config, jinja-global, relations-from) from primitives.py to primitives_io.py - Remove duplicate url-for/route-prefix from primitives.py (already in IO) - Fix parse-datetime to return ISO string instead of raw datetime - Add datetime→isoformat conversion in _convert_result at the edge - Wrap page helper return values with boundary type validation - Replace all SxExpr(f"...") patterns with sx_call() or _sx_fragment() - Add assert declaration to primitives.sx Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -33,6 +33,7 @@ STYLE_ATOMS: dict[str, str] = {
|
||||
"flex": "display:flex",
|
||||
"inline-flex": "display:inline-flex",
|
||||
"table": "display:table",
|
||||
"table-row": "display:table-row",
|
||||
"grid": "display:grid",
|
||||
"contents": "display:contents",
|
||||
"hidden": "display:none",
|
||||
@@ -84,6 +85,7 @@ STYLE_ATOMS: dict[str, str] = {
|
||||
"flex-row": "flex-direction:row",
|
||||
"flex-col": "flex-direction:column",
|
||||
"flex-wrap": "flex-wrap:wrap",
|
||||
"flex-0": "flex:0",
|
||||
"flex-1": "flex:1 1 0%",
|
||||
"flex-shrink-0": "flex-shrink:0",
|
||||
"shrink-0": "flex-shrink:0",
|
||||
@@ -149,6 +151,7 @@ STYLE_ATOMS: dict[str, str] = {
|
||||
"mt-2": "margin-top:.5rem",
|
||||
"mt-3": "margin-top:.75rem",
|
||||
"mt-4": "margin-top:1rem",
|
||||
"mt-5": "margin-top:1.25rem",
|
||||
"mt-6": "margin-top:1.5rem",
|
||||
"mt-8": "margin-top:2rem",
|
||||
"mt-[8px]": "margin-top:8px",
|
||||
@@ -196,6 +199,7 @@ STYLE_ATOMS: dict[str, str] = {
|
||||
"pb-8": "padding-bottom:2rem",
|
||||
"pb-[48px]": "padding-bottom:48px",
|
||||
"pl-2": "padding-left:.5rem",
|
||||
"pl-3": "padding-left:.75rem",
|
||||
"pl-5": "padding-left:1.25rem",
|
||||
"pl-6": "padding-left:1.5rem",
|
||||
"pr-1": "padding-right:.25rem",
|
||||
@@ -216,11 +220,15 @@ STYLE_ATOMS: dict[str, str] = {
|
||||
"w-10": "width:2.5rem",
|
||||
"w-11": "width:2.75rem",
|
||||
"w-12": "width:3rem",
|
||||
"w-14": "width:3.5rem",
|
||||
"w-16": "width:4rem",
|
||||
"w-20": "width:5rem",
|
||||
"w-24": "width:6rem",
|
||||
"w-28": "width:7rem",
|
||||
"w-32": "width:8rem",
|
||||
"w-40": "width:10rem",
|
||||
"w-48": "width:12rem",
|
||||
"w-56": "width:14rem",
|
||||
"w-1/2": "width:50%",
|
||||
"w-1/3": "width:33.333333%",
|
||||
"w-1/4": "width:25%",
|
||||
@@ -241,6 +249,7 @@ STYLE_ATOMS: dict[str, str] = {
|
||||
"h-10": "height:2.5rem",
|
||||
"h-12": "height:3rem",
|
||||
"h-14": "height:3.5rem",
|
||||
"h-14": "height:3.5rem",
|
||||
"h-16": "height:4rem",
|
||||
"h-24": "height:6rem",
|
||||
"h-28": "height:7rem",
|
||||
@@ -268,11 +277,15 @@ STYLE_ATOMS: dict[str, str] = {
|
||||
"max-w-3xl": "max-width:48rem",
|
||||
"max-w-4xl": "max-width:56rem",
|
||||
"max-w-full": "max-width:100%",
|
||||
"max-w-0": "max-width:0",
|
||||
"max-w-none": "max-width:none",
|
||||
"max-w-screen-2xl": "max-width:1536px",
|
||||
"max-w-[360px]": "max-width:360px",
|
||||
"max-w-[768px]": "max-width:768px",
|
||||
"max-w-[640px]": "max-width:640px",
|
||||
"max-h-32": "max-height:8rem",
|
||||
"max-h-64": "max-height:16rem",
|
||||
"max-h-72": "max-height:18rem",
|
||||
"max-h-96": "max-height:24rem",
|
||||
"max-h-none": "max-height:none",
|
||||
"max-h-[448px]": "max-height:448px",
|
||||
@@ -282,6 +295,7 @@ STYLE_ATOMS: dict[str, str] = {
|
||||
"text-xs": "font-size:.75rem;line-height:1rem",
|
||||
"text-sm": "font-size:.875rem;line-height:1.25rem",
|
||||
"text-base": "font-size:1rem;line-height:1.5rem",
|
||||
"text-md": "font-size:1rem;line-height:1.5rem", # alias for text-base
|
||||
"text-lg": "font-size:1.125rem;line-height:1.75rem",
|
||||
"text-xl": "font-size:1.25rem;line-height:1.75rem",
|
||||
"text-2xl": "font-size:1.5rem;line-height:2rem",
|
||||
@@ -345,6 +359,7 @@ STYLE_ATOMS: dict[str, str] = {
|
||||
"text-rose-500": "color:rgb(244 63 94)",
|
||||
"text-rose-600": "color:rgb(225 29 72)",
|
||||
"text-rose-700": "color:rgb(190 18 60)",
|
||||
"text-rose-800": "color:rgb(159 18 57)",
|
||||
"text-rose-800/80": "color:rgba(159,18,57,.8)",
|
||||
"text-rose-900": "color:rgb(136 19 55)",
|
||||
"text-orange-600": "color:rgb(234 88 12)",
|
||||
@@ -355,6 +370,10 @@ STYLE_ATOMS: dict[str, str] = {
|
||||
"text-yellow-700": "color:rgb(161 98 7)",
|
||||
"text-green-600": "color:rgb(22 163 74)",
|
||||
"text-green-800": "color:rgb(22 101 52)",
|
||||
"text-green-900": "color:rgb(20 83 45)",
|
||||
"text-neutral-400": "color:rgb(163 163 163)",
|
||||
"text-neutral-500": "color:rgb(115 115 115)",
|
||||
"text-neutral-600": "color:rgb(82 82 82)",
|
||||
"text-emerald-500": "color:rgb(16 185 129)",
|
||||
"text-emerald-600": "color:rgb(5 150 105)",
|
||||
"text-emerald-700": "color:rgb(4 120 87)",
|
||||
@@ -371,6 +390,7 @@ STYLE_ATOMS: dict[str, str] = {
|
||||
"text-violet-600": "color:rgb(124 58 237)",
|
||||
"text-violet-700": "color:rgb(109 40 217)",
|
||||
"text-violet-800": "color:rgb(91 33 182)",
|
||||
"text-violet-900": "color:rgb(76 29 149)",
|
||||
|
||||
# ── Background Colors ────────────────────────────────────────────────
|
||||
"bg-transparent": "background-color:transparent",
|
||||
@@ -413,6 +433,9 @@ STYLE_ATOMS: dict[str, str] = {
|
||||
"bg-yellow-300": "background-color:rgb(253 224 71)",
|
||||
"bg-green-50": "background-color:rgb(240 253 244)",
|
||||
"bg-green-100": "background-color:rgb(220 252 231)",
|
||||
"bg-green-200": "background-color:rgb(187 247 208)",
|
||||
"bg-neutral-50/70": "background-color:rgba(250,250,250,.7)",
|
||||
"bg-black/70": "background-color:rgba(0,0,0,.7)",
|
||||
"bg-emerald-50": "background-color:rgb(236 253 245)",
|
||||
"bg-emerald-50/80": "background-color:rgba(236,253,245,.8)",
|
||||
"bg-emerald-100": "background-color:rgb(209 250 229)",
|
||||
@@ -435,6 +458,12 @@ STYLE_ATOMS: dict[str, str] = {
|
||||
"bg-violet-400": "background-color:rgb(167 139 250)",
|
||||
"bg-violet-500": "background-color:rgb(139 92 246)",
|
||||
"bg-violet-600": "background-color:rgb(124 58 237)",
|
||||
"bg-violet-700": "background-color:rgb(109 40 217)",
|
||||
"bg-amber-200": "background-color:rgb(253 230 138)",
|
||||
"bg-blue-700": "background-color:rgb(29 78 216)",
|
||||
"bg-emerald-700": "background-color:rgb(4 120 87)",
|
||||
"bg-purple-700": "background-color:rgb(126 34 206)",
|
||||
"bg-stone-50/60": "background-color:rgba(250,250,249,.6)",
|
||||
|
||||
# ── Border ───────────────────────────────────────────────────────────
|
||||
"border": "border-width:1px",
|
||||
@@ -445,6 +474,7 @@ STYLE_ATOMS: dict[str, str] = {
|
||||
"border-b": "border-bottom-width:1px",
|
||||
"border-b-2": "border-bottom-width:2px",
|
||||
"border-r": "border-right-width:1px",
|
||||
"border-l": "border-left-width:1px",
|
||||
"border-l-4": "border-left-width:4px",
|
||||
"border-dashed": "border-style:dashed",
|
||||
"border-none": "border-style:none",
|
||||
@@ -472,6 +502,9 @@ STYLE_ATOMS: dict[str, str] = {
|
||||
"border-violet-200": "border-color:rgb(221 214 254)",
|
||||
"border-violet-300": "border-color:rgb(196 181 253)",
|
||||
"border-violet-400": "border-color:rgb(167 139 250)",
|
||||
"border-neutral-200": "border-color:rgb(229 229 229)",
|
||||
"border-red-400": "border-color:rgb(248 113 113)",
|
||||
"border-stone-400": "border-color:rgb(168 162 158)",
|
||||
"border-t-white": "border-top-color:rgb(255 255 255)",
|
||||
"border-t-stone-600": "border-top-color:rgb(87 83 78)",
|
||||
"border-l-stone-400": "border-left-color:rgb(168 162 158)",
|
||||
@@ -499,17 +532,26 @@ STYLE_ATOMS: dict[str, str] = {
|
||||
"opacity-0": "opacity:0",
|
||||
"opacity-40": "opacity:.4",
|
||||
"opacity-50": "opacity:.5",
|
||||
"opacity-90": "opacity:.9",
|
||||
"opacity-100": "opacity:1",
|
||||
|
||||
# ── Ring / Outline ───────────────────────────────────────────────────
|
||||
"outline-none": "outline:2px solid transparent;outline-offset:2px",
|
||||
"ring-2": "box-shadow:0 0 0 2px var(--tw-ring-color,rgb(59 130 246))",
|
||||
"ring-offset-2": "box-shadow:0 0 0 2px rgb(255 255 255),0 0 0 4px var(--tw-ring-color,rgb(59 130 246))",
|
||||
"ring-stone-300": "--tw-ring-color:rgb(214 211 209)",
|
||||
"ring-stone-500": "--tw-ring-color:rgb(120 113 108)",
|
||||
"ring-violet-500": "--tw-ring-color:rgb(139 92 246)",
|
||||
"ring-blue-500": "--tw-ring-color:rgb(59 130 246)",
|
||||
"ring-green-500": "--tw-ring-color:rgb(22 163 74)",
|
||||
"ring-purple-500": "--tw-ring-color:rgb(147 51 234)",
|
||||
|
||||
# ── Overflow ─────────────────────────────────────────────────────────
|
||||
"overflow-hidden": "overflow:hidden",
|
||||
"overflow-x-auto": "overflow-x:auto",
|
||||
"overflow-y-auto": "overflow-y:auto",
|
||||
"overflow-visible": "overflow:visible",
|
||||
"overflow-y-visible": "overflow-y:visible",
|
||||
"overscroll-contain": "overscroll-behavior:contain",
|
||||
|
||||
# ── Text Decoration ──────────────────────────────────────────────────
|
||||
@@ -655,8 +697,13 @@ PSEUDO_VARIANTS: dict[str, str] = {
|
||||
"placeholder": "::placeholder",
|
||||
"file": "::file-selector-button",
|
||||
"aria-selected": "[aria-selected=true]",
|
||||
"invalid": ":invalid",
|
||||
"placeholder-shown": ":placeholder-shown",
|
||||
"group-hover": ":is(.group:hover) &",
|
||||
"group-open": ":is(.group[open]) &",
|
||||
"group-open/cat": ":is(.group\\/cat[open]) &",
|
||||
"group-open/filter": ":is(.group\\/filter[open]) &",
|
||||
"group-open/root": ":is(.group\\/root[open]) &",
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user