URL restructure, 404 page, trailing slash normalization, layout fixes

- Rename /reactive-islands/ → /reactive/, /reference/ → /hypermedia/reference/,
  /examples/ → /hypermedia/examples/ across all .sx and .py files
- Add 404 error page (not-found.sx) working on both server refresh and
  client-side SX navigation via orchestration.sx error response handling
- Add trailing slash redirect (GET only, excludes /api/, /static/, /internal/)
- Remove blue sky-500 header bar from SX docs layout (conditional on header-rows)
- Fix 405 on API endpoints from trailing slash redirect hitting POST/PUT/DELETE
- Fix client-side 404: orchestration.sx now swaps error response content
  instead of silently dropping it
- Add new plan files and home page component

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-10 21:30:18 +00:00
parent e149dfe968
commit 1341c144da
35 changed files with 2305 additions and 438 deletions

View File

@@ -52,8 +52,13 @@
(create-fragment)
(render-dom-list expr env ns))
;; Fallback
:else (create-text-node (str expr)))))
;; Signal → reactive text in island scope, deref outside
:else
(if (signal? expr)
(if *island-scope*
(reactive-text expr)
(create-text-node (str (deref expr))))
(create-text-node (str expr))))))
;; --------------------------------------------------------------------------
@@ -81,6 +86,10 @@
(= name "lake")
(render-dom-lake args env ns)
;; marsh — reactive server-morphable slot within an island
(= name "marsh")
(render-dom-marsh args env ns)
;; html: prefix → force element rendering
(starts-with? name "html:")
(render-dom-element (slice name 5) args env ns)
@@ -490,7 +499,8 @@
(if (and *island-scope*
(= (type-of coll-expr) "list")
(> (len coll-expr) 1)
(= (first coll-expr) "deref"))
(= (type-of (first coll-expr)) "symbol")
(= (symbol-name (first coll-expr)) "deref"))
;; Reactive path: pass signal to reactive-list
(let ((f (trampoline (eval-expr (nth expr 1) env)))
(sig (trampoline (eval-expr (nth coll-expr 1) env))))
@@ -698,6 +708,56 @@
el))))
;; --------------------------------------------------------------------------
;; render-dom-marsh — reactive server-morphable slot within an island
;; --------------------------------------------------------------------------
;;
;; (marsh :id "name" :tag "div" :transform fn children...)
;;
;; Like a lake but reactive: during morph, new content is parsed as SX and
;; re-evaluated in the island's signal scope. The :transform function (if
;; present) reshapes server content before evaluation.
;;
;; Renders as <div data-sx-marsh="name">children</div>.
;; Stores the island env and transform on the element for morph retrieval.
(define render-dom-marsh
(fn (args env ns)
(let ((marsh-id nil)
(marsh-tag "div")
(marsh-transform nil)
(children (list)))
(reduce
(fn (state arg)
(let ((skip (get state "skip")))
(if skip
(assoc state "skip" false "i" (inc (get state "i")))
(if (and (= (type-of arg) "keyword")
(< (inc (get state "i")) (len args)))
(let ((kname (keyword-name arg))
(kval (trampoline (eval-expr (nth args (inc (get state "i"))) env))))
(cond
(= kname "id") (set! marsh-id kval)
(= kname "tag") (set! marsh-tag kval)
(= kname "transform") (set! marsh-transform kval))
(assoc state "skip" true "i" (inc (get state "i"))))
(do
(append! children arg)
(assoc state "i" (inc (get state "i"))))))))
(dict "i" 0 "skip" false)
args)
(let ((el (dom-create-element marsh-tag nil)))
(dom-set-attr el "data-sx-marsh" (or marsh-id ""))
;; Store transform function and island env for morph retrieval
(when marsh-transform
(dom-set-data el "sx-marsh-transform" marsh-transform))
(dom-set-data el "sx-marsh-env" env)
(for-each
(fn (c) (dom-append el (render-to-dom c env ns)))
children)
el))))
;; --------------------------------------------------------------------------
;; Reactive DOM rendering helpers
;; --------------------------------------------------------------------------
@@ -726,14 +786,17 @@
(updated (if (empty? existing) attr-name (str existing "," attr-name))))
(dom-set-attr el "data-sx-reactive-attrs" updated))
(effect (fn ()
(let ((val (compute-fn)))
(cond
(or (nil? val) (= val false))
(dom-remove-attr el attr-name)
(= val true)
(dom-set-attr el attr-name "")
:else
(dom-set-attr el attr-name (str val))))))))
(let ((raw (compute-fn)))
;; If compute-fn returned a signal (e.g. from computed), deref it
;; to get the actual value and track the dependency
(let ((val (if (signal? raw) (deref raw) raw)))
(cond
(or (nil? val) (= val false))
(dom-remove-attr el attr-name)
(= val true)
(dom-set-attr el attr-name "")
:else
(dom-set-attr el attr-name (str val)))))))))
;; reactive-fragment — conditionally render a fragment based on a signal
;; Used for (when (deref sig) ...) or (if (deref sig) ...) inside an island.