Files
rose-ash/shared/sx/ref/BOUNDARY.md
giles a8bfff9e0b Remove CSSX style dictionary infrastructure — styling is just components
The entire parallel CSS system (StyleValue type, style dictionary,
keyword atom resolver, content-addressed class generation, runtime
CSS injection, localStorage caching) was built but never adopted —
the codebase already uses :class strings with defcomp components
for all styling. Remove ~3,000 lines of unused infrastructure.

Deleted:
- cssx.sx spec module (317 lines)
- style_dict.py (782 lines) and style_resolver.py (254 lines)
- StyleValue type, defkeyframes special form, build-keyframes platform fn
- Style dict JSON delivery (<script type="text/sx-styles">), cookies, localStorage
- css/merge-styles primitives, inject-style-value, fnv1a-hash platform interface

Simplified:
- defstyle now binds any value (string, function) — no StyleValue type needed
- render-attrs no longer special-cases :style StyleValue → class conversion
- Boot sequence skips style dict init step

Preserved:
- tw.css parsing + CSS class delivery (SX-Css headers, <style id="sx-css">)
- All component infrastructure (defcomp, caching, bundling, deps)
- defstyle as a binding form for reusable class strings

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 00:00:23 +00:00

4.0 KiB

SX Boundary Enforcement

Principle

SX is an uninterrupted island of pure evaluation. Host code (Python, JavaScript, Rust, etc.) interacts with it only through declared entry points. The specification enforces this — violations are errors, not style suggestions.

The Three Tiers

Tier 1: Pure Primitives

Declared in primitives.sx. Stateless, synchronous, no side effects. Available in every SX environment on every target.

Examples: +, str, map, get, concat, merge

Tier 2: I/O Primitives

Declared in boundary.sx. Async, side-effectful, require host context (request, config, services). Server-side only.

Examples: frag, query, current-user, csrf-token, request-arg

Tier 3: Page Helpers

Declared in boundary.sx with a :service scope. Registered per-app, return data that .sx components render. Server-side only.

Examples: highlight (sx), editor-data (blog), all-markets-data (market)

Boundary Types

Only these types may cross the host-SX boundary:

Type Python JavaScript Rust (future)
number int, float number f64
string str string String
boolean bool boolean bool
nil NIL sentinel NIL sentinel SxValue::Nil
keyword str (colon-prefixed) string String
list list Array Vec<SxValue>
dict dict Object / Map HashMap<String, SxValue>
sx-source SxExpr wrapper string String

NOT allowed: ORM models, datetime objects, request objects, raw callables, framework types. Convert at the edge before crossing.

Enforcement Mechanism

The bootstrappers (bootstrap_js.py, bootstrap_py.py, future bootstrap_rs.py, etc.) read boundary.sx and emit target-native validation:

  • Typed targets (Rust, Haskell, TypeScript): Boundary types become an enum/ADT/discriminated union. Registration functions have type signatures that reject non-SX values at compile time. You literally cannot register a primitive that returns a datetime — it won't typecheck.

  • Python + mypy: Boundary types become a Protocol/Union type. validate_boundary_value() checks at runtime; mypy catches most violations statically.

  • JavaScript: Runtime validation only. registerPrimitive() checks the name against the declared set. Boundary type checking is runtime.

The Contract

  1. Spec-first. Every primitive, I/O function, and page helper must be declared in primitives.sx or boundary.sx before it can be registered. Undeclared registration = error.

  2. SX types only. Values crossing the boundary must be SX-typed. Host-native types (datetime, ORM models, request objects) must be converted to dicts/strings at the edge.

  3. Data in, markup out. Python returns data (dicts, lists, strings). .sx files compose markup. No SX source construction in Python — no f-strings, no string concatenation, no SxExpr(f"...").

  4. Closed island. SX code can only call symbols in its env + declared primitives. There is no FFI, no eval-python, no escape hatch from inside SX.

  5. Fail fast. Violations are runtime errors (startup crash), not warnings. For typed targets, they're compile errors.

Adding a New Primitive

  1. Add declaration to primitives.sx (pure) or boundary.sx (I/O / page helper)
  2. Implement in the target language's primitive file
  3. The bootstrapper-emitted validator will accept it on next rebuild/restart
  4. If you skip step 1, the app crashes on startup telling you exactly what's missing

File Map

shared/sx/ref/
  primitives.sx    — Pure primitive declarations
  boundary.sx      — I/O primitive + page helper + boundary type declarations
  bootstrap_js.py  — JS bootstrapper (reads both, emits validation)
  bootstrap_py.py  — Python bootstrapper (reads both, emits validation)
  eval.sx          — Evaluator spec (symbol resolution, env model)
  parser.sx        — Parser spec
  render.sx        — Renderer spec (shared registries)