diff --git a/sx/sx/sx-tools.sx b/sx/sx/sx-tools.sx index c81d8bf3..0d1b63fe 100644 --- a/sx/sx/sx-tools.sx +++ b/sx/sx/sx-tools.sx @@ -1 +1 @@ -(defcomp ~sx-tools/overview-content (&key (title "SX Tools") &rest extra) (~docs/page :title title (p :class "text-stone-500 text-sm italic mb-8" "A structural tree editor for s-expression files — because the thing that reads and edits code should understand the code as a tree, not as a sequence of characters.") (~docs/section :title "Cyst — isolated reactive subtrees" :id "cyst" (p "A " (code "cyst") " is an isolated reactive scope inside a parent render tree. When the parent re-renders, the cyst's DOM is preserved — event handlers, signal subscriptions, and state all survive.") (~docs/code :src "(cyst :key \"my-counter\"\n (div\n (button :on-click (fn (e) (swap! count inc)) \"+\")\n (span (deref count))))") (p "Without " (code "cyst") ", a reactive island nested inside another island's render tree gets its DOM recreated on every parent re-render. Event handlers are lost, signals reset. " (code "cyst") " solves this by caching the rendered DOM and returning the cached version when the parent re-evaluates.") (p "The " (code ":key") " parameter provides a stable identity for the cached DOM. On first render, the body is evaluated in a fresh " (code "with-island-scope") ". On subsequent renders, if the cached DOM is still connected to the document, it is returned as-is.") (p "This is a language-level feature in " (code "adapter-dom.sx") " — any island can use " (code "cyst") " to protect a reactive subtree from parent re-renders.")) (~docs/section :title "Reactive expressions" :id "reactive-expressions" (p "Expressions containing " (code "(deref sig)") " inside an island scope are now automatically reactive. The DOM adapter detects " (code "deref") " calls within compound expressions and wraps them in " (code "(computed ...)") " for dependency tracking.") (~docs/code :src ";; Before: only bare (deref sig) was reactive\n(span (deref celsius)) ;; ✓ reactive\n\n;; Now: compound expressions are reactive too\n(span (str (deref celsius) \"°C\")) ;; ✓ reactive\n(span (+ (* (deref celsius) 1.8) 32)) ;; ✓ reactive\n(span (str \"Count: \" (deref count) \" × 2 = \" (deref doubled))) ;; ✓ reactive") (p "The " (code "contains-deref?") " predicate recursively checks if an expression tree contains a " (code "deref") " call. When found inside an island scope, the expression is wrapped in " (code "(reactive-text (computed (fn () (eval-expr expr env))))") " — creating a signal subscription that re-evaluates the entire expression when any deref'd signal changes.") (p "This is a language-level feature in " (code "adapter-dom.sx") ". Every island on the site benefits automatically.")) (~docs/section :title "Live island preview" :id "live-preview" (p "The render tab evaluates " (code "defisland") " expressions as live reactive islands. Paste a defisland, click Parse, click render — the island runs with working signals, computed values, and event handlers.") (p "Try this in the editor below:") (~docs/code :src "(defisland ~counter ()\n (let ((count (signal 0))\n (doubled (computed (fn () (* 2 (deref count))))))\n (div\n (button :on-click (fn (e) (swap! count dec)) \"−\")\n (span (deref count))\n (button :on-click (fn (e) (swap! count inc)) \"+\")\n (p \"doubled: \" (deref doubled)))))") (p "The preview uses " (code "cyst") " to preserve the island across parent re-renders. " (code "sx-load-components") " registers the component, then the native " (code "render-dom-island") " adapter renders it with full reactive wiring.") (p "For " (code "defcomp") " with " (code "&key") " parameters, the render tab shows input fields. Values substitute into the preview live as you type — no re-parse needed.")) (~docs/section :title "HyperSX — alternative syntax view" :id "hypersx" (p "The hypersx tab shows SX in an indentation-based syntax inspired by Pug and _hyperscript. Same semantics, different surface:") (~docs/code :src ";; SX\n(defisland ~counter ()\n (let ((count (signal 0)))\n (div :class \"flex gap-4\"\n (button :on-click (fn (e) (swap! count inc)) \"+\")\n (span (str \"Count: \" (deref count))))))\n\n;; HyperSX\ndefisland ~counter ()\n let count = signal(0)\n div.flex.gap-4\n button :on-click (fn (e) (swap! count inc)) \"+\"\n span \"Count: {@count}\"") (p "Transforms: CSS selector shorthand (" (code "div.card#main") "), signal sugar (" (code "@count") ", " (code "signal()") ", " (code ":=") ", " (code "<-") "), string interpolation (" (code "\"{@count}\"") "), and structural keywords (" (code "when") ", " (code "if") ", " (code "let") ", " (code "map") ", " (code "for") ").") (p "Implemented as pure SX in " (code "web/lib/hypersx.sx") ". The " (code "sx->hypersx") " function takes a parsed tree and returns a string. Bidirectional " (code "hypersx->sx") " parser is planned.")) (~docs/section :title "Inline test runner" :id "test-runner" (p "Demo pages can embed tests that run in the browser. Tests are written in SX using the harness assertion functions and stored in a " (code "