From 8ed8134d660424be53411b2c0f21ed41af217dbb Mon Sep 17 00:00:00 2001 From: giles Date: Sun, 15 Mar 2026 02:09:29 +0000 Subject: [PATCH] Phase 1: Create directory structure for spec/hosts/web/sx Co-Authored-By: Claude Opus 4.6 (1M context) --- .gitignore | 2 + _config/dev-sh-config.yaml | 86 ++++++++ dev-sx.sh | 30 +++ docker-compose.dev-sx.yml | 60 ++++++ sx/sx/plans/isolated-evaluator.sx | 333 ++++++++++++++++++++++++++++++ sx/sx/plans/rust-wasm-host.sx | 263 +++++++++++++++++++++++ 6 files changed, 774 insertions(+) create mode 100644 _config/dev-sh-config.yaml create mode 100755 dev-sx.sh create mode 100644 docker-compose.dev-sx.yml create mode 100644 sx/sx/plans/isolated-evaluator.sx create mode 100644 sx/sx/plans/rust-wasm-host.sx diff --git a/.gitignore b/.gitignore index 73b8d6a..ff099ff 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,5 @@ build/ venv/ _snapshot/ _debug/ +sx-haskell/ +sx-rust/ diff --git a/_config/dev-sh-config.yaml b/_config/dev-sh-config.yaml new file mode 100644 index 0000000..cbec5ef --- /dev/null +++ b/_config/dev-sh-config.yaml @@ -0,0 +1,86 @@ +root: "/rose-ash-wholefood-coop" # no trailing slash needed (we normalize it) +host: "https://rose-ash.com" +base_host: "wholesale.suma.coop" +base_login: https://wholesale.suma.coop/customer/account/login/ +base_url: https://wholesale.suma.coop/ +title: sx-web +market_root: /market +market_title: Market +blog_root: / +blog_title: all the news +cart_root: /cart +app_urls: + blog: "https://blog.rose-ash.com" + market: "https://market.rose-ash.com" + cart: "https://cart.rose-ash.com" + events: "https://events.rose-ash.com" + federation: "https://federation.rose-ash.com" + account: "https://account.rose-ash.com" + sx: "https://sx.rose-ash.com" + test: "https://test.rose-ash.com" + orders: "https://orders.rose-ash.com" +cache: + fs_root: /app/_snapshot # <- absolute path to your snapshot dir +categories: + allow: + Basics: basics + Branded Goods: branded-goods + Chilled: chilled + Frozen: frozen + Non-foods: non-foods + Supplements: supplements + Christmas: christmas +slugs: + skip: + - "" + - customer + - account + - checkout + - wishlist + - sales + - contact + - privacy-policy + - terms-and-conditions + - delivery + - catalogsearch + - quickorder + - apply + - search + - static + - media +section-titles: + - ingredients + - allergy information + - allergens + - nutritional information + - nutrition + - storage + - directions + - preparation + - serving suggestions + - origin + - country of origin + - recycling + - general information + - additional information + - a note about prices + +blacklist: + category: + - branded-goods/alcoholic-drinks + - branded-goods/beers + - branded-goods/ciders + - branded-goods/wines + product: + - list-price-suma-current-suma-price-list-each-bk012-2-html + product-details: + - General Information + - A Note About Prices +sumup: + merchant_code: "ME4J6100" + currency: "GBP" + # Name of the environment variable that holds your SumUp API key + api_key_env: "SUMUP_API_KEY" + webhook_secret: "jfwlekjfwef798ewf769ew8f679ew8f7weflwef" + + diff --git a/dev-sx.sh b/dev-sx.sh new file mode 100755 index 0000000..e9f347a --- /dev/null +++ b/dev-sx.sh @@ -0,0 +1,30 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Dev mode for sx_docs only (standalone, no DB) +# Bind-mounted source + auto-reload on externalnet +# Browse to sx.rose-ash.com +# +# Usage: +# ./dev-sx.sh # Start sx_docs dev +# ./dev-sx.sh down # Stop +# ./dev-sx.sh logs # Tail logs +# ./dev-sx.sh --build # Rebuild image then start + +COMPOSE="docker compose -p sx-dev -f docker-compose.dev-sx.yml" + +case "${1:-up}" in + down) + $COMPOSE down + ;; + logs) + $COMPOSE logs -f sx_docs + ;; + *) + BUILD_FLAG="" + if [[ "${1:-}" == "--build" ]]; then + BUILD_FLAG="--build" + fi + $COMPOSE up $BUILD_FLAG + ;; +esac diff --git a/docker-compose.dev-sx.yml b/docker-compose.dev-sx.yml new file mode 100644 index 0000000..abcc300 --- /dev/null +++ b/docker-compose.dev-sx.yml @@ -0,0 +1,60 @@ +# Standalone dev mode for sx_docs only +# Replaces ~/sx-web production stack with bind-mounted source + auto-reload +# Accessible at sx.rose-ash.com via Caddy on externalnet + +services: + sx_docs: + image: registry.rose-ash.com:5000/sx_docs:latest + environment: + SX_STANDALONE: "true" + SECRET_KEY: "${SECRET_KEY:-sx-dev-secret}" + REDIS_URL: redis://redis:6379/0 + WORKERS: "1" + ENVIRONMENT: development + RELOAD: "true" + SX_USE_REF: "1" + SX_BOUNDARY_STRICT: "1" + SX_DEV: "1" + volumes: + - /root/rose-ash/_config/dev-sh-config.yaml:/app/config/app-config.yaml:ro + - ./shared:/app/shared + - ./sx/app.py:/app/app.py + - ./sx/sxc:/app/sxc + - ./sx/bp:/app/bp + - ./sx/services:/app/services + - ./sx/content:/app/content + - ./sx/sx:/app/sx + - ./sx/path_setup.py:/app/path_setup.py + - ./sx/entrypoint.sh:/usr/local/bin/entrypoint.sh + - ./sx/__init__.py:/app/__init__.py:ro + # sibling models for cross-domain SQLAlchemy imports + - ./blog/__init__.py:/app/blog/__init__.py:ro + - ./blog/models:/app/blog/models:ro + - ./market/__init__.py:/app/market/__init__.py:ro + - ./market/models:/app/market/models:ro + - ./cart/__init__.py:/app/cart/__init__.py:ro + - ./cart/models:/app/cart/models:ro + - ./events/__init__.py:/app/events/__init__.py:ro + - ./events/models:/app/events/models:ro + - ./federation/__init__.py:/app/federation/__init__.py:ro + - ./federation/models:/app/federation/models:ro + - ./account/__init__.py:/app/account/__init__.py:ro + - ./account/models:/app/account/models:ro + - ./relations/__init__.py:/app/relations/__init__.py:ro + - ./relations/models:/app/relations/models:ro + - ./likes/__init__.py:/app/likes/__init__.py:ro + - ./likes/models:/app/likes/models:ro + - ./orders/__init__.py:/app/orders/__init__.py:ro + - ./orders/models:/app/orders/models:ro + networks: + - externalnet + - default + restart: unless-stopped + + redis: + image: redis:7-alpine + restart: unless-stopped + +networks: + externalnet: + external: true diff --git a/sx/sx/plans/isolated-evaluator.sx b/sx/sx/plans/isolated-evaluator.sx new file mode 100644 index 0000000..9809f5a --- /dev/null +++ b/sx/sx/plans/isolated-evaluator.sx @@ -0,0 +1,333 @@ +;; --------------------------------------------------------------------------- +;; Isolated Evaluator — Shared platform layer, isolated JS, Rust WASM +;; --------------------------------------------------------------------------- + +(defcomp ~plans/isolated-evaluator/plan-isolated-evaluator-content () + (~docs/page :title "Isolated Evaluator" + + (~docs/section :title "Context" :id "context" + (p "The SX spec is already split into three layers:") + (ul :class "list-disc list-inside space-y-1 mt-2" + (li (code "spec/") " \u2014 Core language (19 files): evaluator, parser, primitives, CEK, types, continuations. Host-independent.") + (li (code "web/") " \u2014 Web framework (20 files): signals, adapters, engine, orchestration, boot, router, deps. Built on core spec.") + (li (code "sx/") " \u2014 Application (sx-docs website). Built on web framework.")) + (p "Bootstrappers search " (code "spec/ \u2192 web/ \u2192 shared/sx/ref/") " for " (code ".sx") " files. The separation is clean.") + (p "This plan takes the next step: " (strong "isolate the evaluator from the real world") ". The JS evaluator should run in the same sandbox as Rust/WASM \u2014 unable to touch DOM, fetch, timers, or storage directly. Both evaluators call into a shared " (code "sx-platform.js") " for all browser access.") + (p "This also involves sorting out the JavaScript: eliminating hand-coded JS that duplicates specced " (code ".sx") " logic, and moving web framework " (code ".sx") " from compiled-into-evaluator to runtime-evaluated.")) + + (~docs/section :title "Existing Architecture" :id "existing" + (h4 :class "font-semibold mt-4 mb-2" "Three-layer spec split (DONE)") + (div :class "overflow-x-auto rounded border border-stone-200 mb-4" + (table :class "w-full text-left text-sm" + (thead (tr :class "border-b border-stone-200 bg-stone-100" + (th :class "px-3 py-2 font-medium text-stone-600" "Layer") + (th :class "px-3 py-2 font-medium text-stone-600" "Directory") + (th :class "px-3 py-2 font-medium text-stone-600" "Files") + (th :class "px-3 py-2 font-medium text-stone-600" "Content"))) + (tbody + (tr :class "border-b border-stone-100" + (td :class "px-3 py-2 text-stone-700" "Core spec") + (td :class "px-3 py-2 text-stone-700" (code "spec/")) + (td :class "px-3 py-2 text-stone-700" "19") + (td :class "px-3 py-2 text-stone-600" "eval, parser, primitives, render, types, CEK, continuations, boundary-core")) + (tr :class "border-b border-stone-100" + (td :class "px-3 py-2 text-stone-700" "Web framework") + (td :class "px-3 py-2 text-stone-700" (code "web/")) + (td :class "px-3 py-2 text-stone-700" "20") + (td :class "px-3 py-2 text-stone-600" "adapters, signals, engine, orchestration, boot, router, deps, forms, boundary-web")) + (tr + (td :class "px-3 py-2 text-stone-700" "Application") + (td :class "px-3 py-2 text-stone-700" (code "sx/")) + (td :class "px-3 py-2 text-stone-700" "\u2014") + (td :class "px-3 py-2 text-stone-600" "sx-docs website, page components, content"))))) + + (h4 :class "font-semibold mt-4 mb-2" "Rust/WASM evaluator (DONE)") + (p (code "sx-rust/") " has a working parser + evaluator + render-to-html in WASM: 9,823 lines generated Rust, 75 real primitives, 154 stubs, 92 tests passing. Currently pure computation \u2014 no DOM interaction.") + + (h4 :class "font-semibold mt-4 mb-2" "What needs to change") + (p "Currently " (strong "everything") " (core + web framework) gets compiled into one monolithic " (code "sx-browser.js") ". The web framework " (code ".sx") " files (signals, engine, orchestration, boot, etc.) are baked into the evaluator output by the bootstrapper. They should instead be " (strong "evaluated at runtime") " by the core evaluator, like any other " (code ".sx") " code.") + (p "The JavaScript platform primitives (DOM, fetch, timers, storage) are also inlined into the bootstrapped output. They need to be extracted into a standalone " (code "sx-platform.js") " module that both JS and WASM evaluators share.")) + + + ;; ----------------------------------------------------------------------- + ;; What's core vs web (bootstrapped vs runtime) + ;; ----------------------------------------------------------------------- + + (~docs/section :title "Bootstrapped vs Runtime-Evaluated" :id "bootstrap-vs-runtime" + (p "The key question: what MUST be compiled into the evaluator vs what can be loaded as " (code ".sx") " at runtime?") + + (h4 :class "font-semibold mt-4 mb-2" "Must be bootstrapped (core spec)") + (div :class "overflow-x-auto rounded border border-stone-200 mb-4" + (table :class "w-full text-left text-sm" + (thead (tr :class "border-b border-stone-200 bg-stone-100" + (th :class "px-3 py-2 font-medium text-stone-600" "File") + (th :class "px-3 py-2 font-medium text-stone-600" "Dir") + (th :class "px-3 py-2 font-medium text-stone-600" "Why"))) + (tbody + (tr :class "border-b border-stone-100" + (td :class "px-3 py-2 text-stone-700" (code "eval.sx")) + (td :class "px-3 py-2 text-stone-700" "spec/") + (td :class "px-3 py-2 text-stone-600" "IS the language \u2014 can't evaluate without it")) + (tr :class "border-b border-stone-100" + (td :class "px-3 py-2 text-stone-700" (code "parser.sx")) + (td :class "px-3 py-2 text-stone-700" "spec/") + (td :class "px-3 py-2 text-stone-600" "Can't read .sx source without a parser")) + (tr :class "border-b border-stone-100" + (td :class "px-3 py-2 text-stone-700" (code "primitives.sx")) + (td :class "px-3 py-2 text-stone-700" "spec/") + (td :class "px-3 py-2 text-stone-600" "80+ built-in pure functions \u2014 must be native")) + (tr :class "border-b border-stone-100" + (td :class "px-3 py-2 text-stone-700" (code "render.sx")) + (td :class "px-3 py-2 text-stone-700" "spec/") + (td :class "px-3 py-2 text-stone-600" "HTML_TAGS registry, parse-element-args")) + (tr :class "border-b border-stone-100" + (td :class "px-3 py-2 text-stone-700" (code "adapter-html.sx")) + (td :class "px-3 py-2 text-stone-700" "web/") + (td :class "px-3 py-2 text-stone-600" "render-to-html \u2014 co-recursive with eval-expr")) + (tr :class "border-b border-stone-100" + (td :class "px-3 py-2 text-stone-700" (code "adapter-sx.sx")) + (td :class "px-3 py-2 text-stone-700" "web/") + (td :class "px-3 py-2 text-stone-600" "aser (wire format) \u2014 co-recursive with eval-expr")) + (tr + (td :class "px-3 py-2 text-stone-700" (code "adapter-dom.sx")) + (td :class "px-3 py-2 text-stone-700" "web/") + (td :class "px-3 py-2 text-stone-600" "render-to-dom \u2014 co-recursive with eval-expr"))))) + + (h4 :class "font-semibold mt-4 mb-2" "Runtime-evaluated (web framework)") + (div :class "overflow-x-auto rounded border border-stone-200 mb-4" + (table :class "w-full text-left text-sm" + (thead (tr :class "border-b border-stone-200 bg-stone-100" + (th :class "px-3 py-2 font-medium text-stone-600" "File") + (th :class "px-3 py-2 font-medium text-stone-600" "Dir") + (th :class "px-3 py-2 font-medium text-stone-600" "Why it can be runtime"))) + (tbody + (tr :class "border-b border-stone-100" + (td :class "px-3 py-2 text-stone-700" (code "signals.sx")) + (td :class "px-3 py-2 text-stone-700" "web/") + (td :class "px-3 py-2 text-stone-600" "Pure computation \u2014 dicts with markers, no new types")) + (tr :class "border-b border-stone-100" + (td :class "px-3 py-2 text-stone-700" (code "engine.sx")) + (td :class "px-3 py-2 text-stone-700" "web/") + (td :class "px-3 py-2 text-stone-600" "Pure logic \u2014 trigger parsing, swap specs, morph")) + (tr :class "border-b border-stone-100" + (td :class "px-3 py-2 text-stone-700" (code "orchestration.sx")) + (td :class "px-3 py-2 text-stone-700" "web/") + (td :class "px-3 py-2 text-stone-600" "Event binding + fetch \u2014 calls platform primitives")) + (tr :class "border-b border-stone-100" + (td :class "px-3 py-2 text-stone-700" (code "boot.sx")) + (td :class "px-3 py-2 text-stone-700" "web/") + (td :class "px-3 py-2 text-stone-600" "Page lifecycle \u2014 calls platform primitives")) + (tr :class "border-b border-stone-100" + (td :class "px-3 py-2 text-stone-700" (code "router.sx")) + (td :class "px-3 py-2 text-stone-700" "web/") + (td :class "px-3 py-2 text-stone-600" "URL pattern matching \u2014 pure computation")) + (tr :class "border-b border-stone-100" + (td :class "px-3 py-2 text-stone-700" (code "deps.sx")) + (td :class "px-3 py-2 text-stone-700" "web/") + (td :class "px-3 py-2 text-stone-600" "Component dependency analysis \u2014 pure AST walking")) + (tr :class "border-b border-stone-100" + (td :class "px-3 py-2 text-stone-700" (code "page-helpers.sx")) + (td :class "px-3 py-2 text-stone-700" "web/") + (td :class "px-3 py-2 text-stone-600" "Data transformation helpers")) + (tr + (td :class "px-3 py-2 text-stone-700" (code "forms.sx")) + (td :class "px-3 py-2 text-stone-700" "web/") + (td :class "px-3 py-2 text-stone-600" "Server-only definition forms"))))) + + (h4 :class "font-semibold mt-4 mb-2" "adapter-dom.sx reactive split") + (p (code "adapter-dom.sx") " contains reactive-aware code (" (code "reactive-text") ", " (code "reactive-attr") ", " (code "render-dom-island") ", " (code "render-dom-lake") ") interleaved with core DOM rendering. These call " (code "signal?") " and " (code "deref") " from " (code "signals.sx") " via environment lookup \u2014 no compile-time dependency. Option: split reactive DOM functions into " (code "adapter-dom-reactive.sx") " (web/ layer), keeping base " (code "adapter-dom.sx") " purely about elements/text/fragments/components.") + + (h4 :class "font-semibold mt-4 mb-2" "Hand-coded JS to clean up") + (ul :class "list-disc list-inside space-y-2 mt-2" + (li (code "CONTINUATIONS_JS") " in " (code "platform_js.py") " \u2014 hand-coded shift/reset. Should use specced " (code "continuations.sx") " or be eliminated if continuations are application-level.") + (li (code "ASYNC_IO_JS") " in " (code "platform_js.py") " \u2014 hand-coded async rendering dispatch. Already replaced by " (code "adapter-async.sx") " for Python. JS version should also be bootstrapped or eliminated.") + (li "Various wrapper functions in " (code "PLATFORM_BOOT_JS") " that duplicate logic from " (code "boot.sx") "."))) + + + ;; ----------------------------------------------------------------------- + ;; Phase 1: Extract sx-platform.js + ;; ----------------------------------------------------------------------- + + (~docs/section :title "Phase 1: Extract sx-platform.js" :id "phase-1" + (p (strong "Goal:") " All real-world-touching JavaScript lives in one standalone module. The evaluator never directly accesses " (code "document") ", " (code "window") ", " (code "fetch") ", " (code "localStorage") ", " (code "history") ", etc.") + + (h4 :class "font-semibold mt-4 mb-2" "Architecture") + (~docs/code :code (highlight " \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n \u2502 sx-platform.js \u2502 \u2190 DOM, fetch, timers, storage\n \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n \u2502\n \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n \u2502 \u2502\n \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n \u2502 sx-evaluator.js \u2502 \u2502 sx-wasm-shim.js \u2502\n \u2502 (isolated JS) \u2502 \u2502 (WASM instance \u2502\n \u2502 \u2502 \u2502 + handle table) \u2502\n \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518" "text")) + + (h4 :class "font-semibold mt-4 mb-2" "What moves into sx-platform.js") + (p "Extracted from " (code "platform_js.py") " string constants:") + (div :class "overflow-x-auto rounded border border-stone-200 mb-4" + (table :class "w-full text-left text-sm" + (thead (tr :class "border-b border-stone-200 bg-stone-100" + (th :class "px-3 py-2 font-medium text-stone-600" "Category") + (th :class "px-3 py-2 font-medium text-stone-600" "Source") + (th :class "px-3 py-2 font-medium text-stone-600" "~Functions"))) + (tbody + (tr :class "border-b border-stone-100" + (td :class "px-3 py-2 text-stone-700" "DOM primitives") + (td :class "px-3 py-2 text-stone-700" (code "PLATFORM_DOM_JS")) + (td :class "px-3 py-2 text-stone-600" "~50 (createElement, setAttribute, appendChild...)")) + (tr :class "border-b border-stone-100" + (td :class "px-3 py-2 text-stone-700" "Engine platform") + (td :class "px-3 py-2 text-stone-700" (code "PLATFORM_ENGINE_PURE_JS")) + (td :class "px-3 py-2 text-stone-600" "~6 (locationHref, pushState, nowMs...)")) + (tr :class "border-b border-stone-100" + (td :class "px-3 py-2 text-stone-700" "Orchestration platform") + (td :class "px-3 py-2 text-stone-700" (code "PLATFORM_ORCHESTRATION_JS")) + (td :class "px-3 py-2 text-stone-600" "~80 (fetch, abort, timers, SSE, scroll, media...)")) + (tr :class "border-b border-stone-100" + (td :class "px-3 py-2 text-stone-700" "Boot platform") + (td :class "px-3 py-2 text-stone-700" (code "PLATFORM_BOOT_JS")) + (td :class "px-3 py-2 text-stone-600" "~20 (mount target, localStorage, cookies, logging...)")) + (tr + (td :class "px-3 py-2 text-stone-700" "Parser helpers") + (td :class "px-3 py-2 text-stone-700" (code "PLATFORM_PARSER_JS")) + (td :class "px-3 py-2 text-stone-600" "~4 (isIdentStart, parseNumber...)"))))) + + (h4 :class "font-semibold mt-4 mb-2" "Isolation rule") + (p "After extraction, searching " (code "sx-evaluator.js") " for " (code "document") ", " (code "window") ", " (code "fetch") ", " (code "localStorage") ", " (code "history") ", " (code "setTimeout") ", " (code "console") " should find " (strong "zero") " direct references.") + + (h4 :class "font-semibold mt-4 mb-2" "The callSxFunction bridge") + (p "Platform code (event listeners, timers) needs to invoke SX lambdas. The evaluator provides a single " (code "callSxFunction(fn, args) \u2192 result") " bridge to the platform at registration time. This is the ONE evaluator-to-platform callback.") + + (h4 :class "font-semibold mt-4 mb-2" "Implementation") + (ul :class "list-disc list-inside space-y-2 mt-2" + (li "Modify " (code "platform_js.py") " to emit platform functions as a separate output section") + (li "Create " (code "sx-platform.js") " as an IIFE that sets " (code "globalThis.SxPlatform = {...}")) + (li "The evaluator IIFE reads " (code "globalThis.SxPlatform") " at init, registers each function as a PRIMITIVE") + (li "Clean up " (code "CONTINUATIONS_JS") " and " (code "ASYNC_IO_JS") " \u2014 eliminate or bootstrap") + (li "Test that existing pages work identically"))) + + + ;; ----------------------------------------------------------------------- + ;; Phase 2: Isolate the JS Evaluator + ;; ----------------------------------------------------------------------- + + (~docs/section :title "Phase 2: Isolate the JS Evaluator" :id "phase-2" + (p (strong "Goal:") " " (code "sx-evaluator.js") " contains ONLY core spec + render adapters. Web framework " (code ".sx") " is evaluated at runtime.") + + (h4 :class "font-semibold mt-4 mb-2" "Core-only build") + (p "The bootstrapper already supports selecting which modules to compile. A core-only build:") + (~docs/code :code (highlight "# In run_js_sx.py \u2014 core-only build\ncompile_ref_to_js(\n adapters=[\"parser\", \"html\", \"sx\", \"dom\"], # core spec + adapters\n modules=None, # no signals, engine, orchestration, boot\n extensions=None, # no continuations\n spec_modules=None # no deps, router, cek, frames, page-helpers\n)" "python")) + + (h4 :class "font-semibold mt-4 mb-2" "Web framework loading") + (p "Web framework " (code ".sx") " files ship as " (code "\n\n\n" "html")) + + (h4 :class "font-semibold mt-4 mb-2" "Boot chicken-and-egg") + (p (code "boot.sx") " orchestrates the boot sequence but is itself web framework code. Solution: thin native boot shim (~30 lines) in " (code "sx-platform.js") ":") + (~docs/code :code (highlight "SxPlatform.boot = function(evaluator) {\n // 1. Evaluate web framework .sx libraries\n var libs = document.querySelectorAll('script[type=\"text/sx-lib\"]');\n for (var i = 0; i < libs.length; i++) {\n evaluator.evalSource(libs[i].textContent);\n }\n // 2. Call boot-init (defined in boot.sx)\n evaluator.callFunction('boot-init');\n};" "javascript")) + + (h4 :class "font-semibold mt-4 mb-2" "Performance") + (p "Parsing + evaluating ~5,000 lines of web framework " (code ".sx") " at startup takes ~10\u201350ms. After " (code "define") ", functions are Lambda objects dispatched identically to compiled functions. " (strong "Zero ongoing performance difference."))) + + + ;; ----------------------------------------------------------------------- + ;; Phase 3: Wire Up Rust/WASM + ;; ----------------------------------------------------------------------- + + (~docs/section :title "Phase 3: Wire Up Rust/WASM" :id "phase-3" + (p (strong "Goal:") " Rust evaluator calls " (code "sx-platform.js") " via wasm-bindgen imports. Handle table bridges DOM references.") + + (h4 :class "font-semibold mt-4 mb-2" "Handle table (JS-side)") + (~docs/code :code (highlight "// In sx-wasm-shim.js\nconst handles = [null]; // index 0 = null handle\nfunction allocHandle(obj) { handles.push(obj); return handles.length - 1; }\nfunction getHandle(id) { return handles[id]; }\nfunction freeHandle(id) { handles[id] = null; }" "javascript")) + (p "DOM nodes are JS objects. The handle table maps " (code "u32") " IDs to JS objects. Rust stores " (code "Value::Handle(u32)") " and passes the " (code "u32") " to imported JS functions.") + + (h4 :class "font-semibold mt-4 mb-2" "Value::Handle in Rust") + (~docs/code :code (highlight "// In platform.rs\npub enum Value {\n // ... existing variants ...\n Handle(u32), // opaque reference to JS-side object\n}" "rust")) + + (h4 :class "font-semibold mt-4 mb-2" "WASM imports from platform") + (~docs/code :code (highlight "#[wasm_bindgen(module = \"/sx-platform-wasm.js\")]\nextern \"C\" {\n fn platform_create_element(tag: &str) -> u32;\n fn platform_create_text_node(text: &str) -> u32;\n fn platform_set_attr(handle: u32, name: &str, value: &str);\n fn platform_append_child(parent: u32, child: u32);\n fn platform_add_event_listener(handle: u32, event: &str, callback_id: u32);\n // ... ~50 DOM primitives\n}" "rust")) + + (h4 :class "font-semibold mt-4 mb-2" "Callback table for events") + (p "When Rust creates an event handler (a Lambda), it stores it in a callback table and gets a " (code "u32") " ID. JS " (code "addEventListener") " wraps it: when the event fires, JS calls into WASM with the callback ID. Rust looks up the Lambda and evaluates it.") + + (h4 :class "font-semibold mt-4 mb-2" "sx-wasm-shim.js") + (p "Thin glue (~100 lines):") + (ul :class "list-disc list-inside space-y-1 mt-2" + (li "Instantiate WASM module") + (li "Wire handle table") + (li "Delegate all platform calls to " (code "sx-platform.js")) + (li "Provide " (code "invoke_callback") " \u2192 Rust for event dispatch"))) + + + ;; ----------------------------------------------------------------------- + ;; Phase 4: Web Framework Loading + ;; ----------------------------------------------------------------------- + + (~docs/section :title "Phase 4: Web Framework Loading" :id "phase-4" + (p (strong "Goal:") " Both JS and WASM evaluators load the same web framework " (code ".sx") " files at runtime.") + + (h4 :class "font-semibold mt-4 mb-2" "Boot sequence (identical for both evaluators)") + (ol :class "list-decimal list-inside space-y-2 mt-2" + (li "Load " (code "sx-platform.js") " + evaluator (" (code "sx-evaluator.js") " or " (code "sx-wasm-shim.js") ")") + (li "Platform registers primitives with evaluator") + (li "Platform boot shim evaluates " (code "