3 Commits

Author SHA1 Message Date
48d493e9cc Fix init.sx: move out of component directory to avoid server-side eval
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 5m26s
init-client.sx contains browser-only code (dom-listen, collect! cssx).
It was in sx/sx/ which load_sx_dir scans and evaluates server-side,
causing "Undefined symbol: dom-listen". Move to sx/init-client.sx
which is outside the component load path.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-14 12:07:02 +00:00
7556cc303d Add CEK/frames specs and spec explorer to Language nav
- Add frames.sx and cek.sx to the reactive spec registry with prose
- Add CEK Frames and CEK Machine under Specs → Reactive in nav
- Add Spec Explorer link under Language section

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-14 11:35:56 +00:00
919998be1c Move SX app CSS and init behavior from Python to init.sx
Styles (indicator, jiggle animation) and nav aria-selected behavior
were inline Python strings in sx/app.py. Now they live in sx/sx/init.sx
as proper SX source — styles via collect! "cssx", nav via dom-listen.

The shell's inline_css is empty; CSSX handles style injection on boot.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-14 11:11:49 +00:00
3 changed files with 42 additions and 22 deletions

View File

@@ -69,30 +69,17 @@ def create_app() -> "Quart":
# Minimal shell — no Prism, no SweetAlert, no body.js
# sx docs uses custom highlight.py, not Prism; body.js is for legacy apps
# Load init SX from file — styles + nav behavior, no inline Python strings
import os as _os
_init_path = _os.path.join(_os.path.dirname(__file__), "sxc", "init-client.sx.txt")
with open(_init_path) as _f:
_init_sx = _f.read()
app.config["SX_SHELL"] = {
"head_scripts": [], # no CDN scripts
"body_scripts": [], # no body.js
"inline_head_js": "", # no pre-boot JS (hover-capable, close-details unused)
"inline_css": (
".sx-indicator{display:none}"
".sx-request .sx-indicator{display:inline-flex}"
"@keyframes sxJiggle{0%,100%{transform:translateX(0)}"
"25%{transform:translateX(-.5px)}75%{transform:translateX(.5px)}}"
"a.sx-request{animation:sxJiggle .3s ease-in-out infinite}"
),
# Nav link aria-selected update on client-side routing — pure SX
"init_sx": (
'(dom-listen (dom-body) "sx:clientRoute"'
' (fn (e)'
' (let ((p (get (event-detail e) "pathname")))'
' (when p'
' (for-each'
' (fn (a) (dom-set-attr a "aria-selected" "false"))'
' (dom-query-all "nav a[aria-selected]"))'
' (for-each'
' (fn (a) (dom-set-attr a "aria-selected" "true"))'
' (dom-query-all (str "nav a[href=\\"" p "\\"]")))))))'
),
"inline_css": "", # styles injected via CSSX from init.sx
"init_sx": _init_sx,
}
app.url_map.strict_slashes = False

View File

@@ -133,7 +133,9 @@
{:label "Boot" :href "/sx/(language.(spec.boot))"}
{:label "Router" :href "/sx/(language.(spec.router))"})}
{:label "Reactive" :href "/sx/(language.(spec.reactive))" :children (list
{:label "Signals" :href "/sx/(language.(spec.signals))"})}
{:label "Signals" :href "/sx/(language.(spec.signals))"}
{:label "CEK Frames" :href "/sx/(language.(spec.frames))"}
{:label "CEK Machine" :href "/sx/(language.(spec.cek))"})}
{:label "Host Interface" :href "/sx/(language.(spec.host))" :children (list
{:label "Boundary" :href "/sx/(language.(spec.boundary))"}
{:label "Forms" :href "/sx/(language.(spec.forms))"}
@@ -315,7 +317,13 @@
(define reactive-spec-items (list
(dict :slug "signals" :filename "signals.sx" :title "Signals"
:desc "Fine-grained reactive primitives — signal, computed, effect, batch."
:prose "The signals module defines a fine-grained reactive system for client-side islands. Signals are containers for values that notify subscribers on change. Computed signals derive values lazily from other signals. Effects run side-effects when their dependencies change, with automatic cleanup. Batch coalesces multiple signal writes into a single notification pass. Island scope management ensures all signals, computeds, and effects are cleaned up when an island is removed from the DOM. The spec defines the reactive graph topology and update algorithm — each platform implements the actual signal/tracking types natively.")))
:prose "The signals module defines a fine-grained reactive system for client-side islands. Signals are containers for values that notify subscribers on change. Computed signals derive values lazily from other signals. Effects run side-effects when their dependencies change, with automatic cleanup. Batch coalesces multiple signal writes into a single notification pass. Island scope management ensures all signals, computeds, and effects are cleaned up when an island is removed from the DOM. The spec defines the reactive graph topology and update algorithm — each platform implements the actual signal/tracking types natively.")
(dict :slug "frames" :filename "frames.sx" :title "CEK Frames"
:desc "Continuation frame types for the explicit CEK machine."
:prose "Frames define what to do next when a sub-evaluation completes. Each frame type is a dict with a type key and frame-specific data. IfFrame, WhenFrame, BeginFrame handle control flow. LetFrame, DefineFrame, SetFrame handle bindings. ArgFrame tracks function call argument evaluation. MapFrame, FilterFrame, ReduceFrame, ForEachFrame drive higher-order forms element by element through the CEK machine. ReactiveResetFrame and DerefFrame enable deref-as-shift — the core reactive mechanism where deref inside a reactive boundary captures the continuation as a signal subscriber.")
(dict :slug "cek" :filename "cek.sx" :title "CEK Machine"
:desc "Explicit CEK machine evaluator — step function, run loop, reactive shift."
:prose "The CEK machine makes evaluation explicit. Every step is a pure function from state to state: Control (expression), Environment (bindings), Kontinuation (stack of frames). step-eval dispatches on expression type — literals pass through, symbols are looked up, lists dispatch on head (special forms, higher-order forms, macros, function calls). step-continue dispatches on the top frame type. Higher-order forms (map, filter, reduce, for-each, some, every?) step element by element, so deref-as-shift works inside callbacks. cek-call replaces invoke as the universal function dispatch — lambdas go through cek-run, native callables through apply.")))
(define host-spec-items (list
(dict :slug "boundary" :filename "boundary.sx" :title "Boundary"
@@ -400,6 +408,7 @@
:children (list
{:label "Docs" :href "/sx/(language.(doc))" :children docs-nav-items}
{:label "Specs" :href "/sx/(language.(spec))" :children specs-nav-items}
{:label "Spec Explorer" :href "/sx/(language.(spec.(explore.evaluator)))"}
{:label "Bootstrappers" :href "/sx/(language.(bootstrapper))" :children bootstrappers-nav-items}
{:label "Testing" :href "/sx/(language.(test))" :children testing-nav-items})}
{:label "Applications" :href "/sx/(applications)"

24
sx/sxc/init-client.sx.txt Normal file
View File

@@ -0,0 +1,24 @@
;; ---------------------------------------------------------------------------
;; SX app boot — styles and behaviors injected on page load
;;
;; Replaces inline_css and init_sx from Python app config.
;; Called as a data-init script on every page.
;; ---------------------------------------------------------------------------
;; Framework styles — request indicators + link jiggle
(collect! "cssx" ".sx-indicator{display:none}")
(collect! "cssx" ".sx-request .sx-indicator{display:inline-flex}")
(collect! "cssx" "@keyframes sxJiggle{0%,100%{transform:translateX(0)}25%{transform:translateX(-.5px)}75%{transform:translateX(.5px)}}")
(collect! "cssx" "a.sx-request{animation:sxJiggle .3s ease-in-out infinite}")
;; Nav link aria-selected update on client-side routing
(dom-listen (dom-body) "sx:clientRoute"
(fn (e)
(let ((p (get (event-detail e) "pathname")))
(when p
(for-each
(fn (a) (dom-set-attr a "aria-selected" "false"))
(dom-query-all "nav a[aria-selected]"))
(for-each
(fn (a) (dom-set-attr a "aria-selected" "true"))
(dom-query-all (str "nav a[href=\"" p "\"]")))))))