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

@@ -474,16 +474,24 @@
(define morph-island-children
(fn (old-island new-island)
;; Find all lake slots in both old and new islands
;; Find all lake and marsh slots in both old and new islands
(let ((old-lakes (dom-query-all old-island "[data-sx-lake]"))
(new-lakes (dom-query-all new-island "[data-sx-lake]")))
;; Build ID→element map for new lakes
(let ((new-lake-map (dict)))
(new-lakes (dom-query-all new-island "[data-sx-lake]"))
(old-marshes (dom-query-all old-island "[data-sx-marsh]"))
(new-marshes (dom-query-all new-island "[data-sx-marsh]")))
;; Build ID→element maps for new lakes and marshes
(let ((new-lake-map (dict))
(new-marsh-map (dict)))
(for-each
(fn (lake)
(let ((id (dom-get-attr lake "data-sx-lake")))
(when id (dict-set! new-lake-map id lake))))
new-lakes)
(for-each
(fn (marsh)
(let ((id (dom-get-attr marsh "data-sx-marsh")))
(when id (dict-set! new-marsh-map id marsh))))
new-marshes)
;; Morph each old lake from its new counterpart
(for-each
(fn (old-lake)
@@ -492,7 +500,76 @@
(when new-lake
(sync-attrs old-lake new-lake)
(morph-children old-lake new-lake)))))
old-lakes)))))
old-lakes)
;; Morph each old marsh from its new counterpart
(for-each
(fn (old-marsh)
(let ((id (dom-get-attr old-marsh "data-sx-marsh")))
(let ((new-marsh (dict-get new-marsh-map id)))
(when new-marsh
(morph-marsh old-marsh new-marsh old-island)))))
old-marshes)
;; Process data-sx-signal attributes — server writes to named stores
(process-signal-updates new-island)))))
;; --------------------------------------------------------------------------
;; morph-marsh — re-evaluate server content in island's reactive scope
;; --------------------------------------------------------------------------
;;
;; Marshes are zones inside islands where server content is re-evaluated by
;; the island's reactive evaluator. During morph, the new content is parsed
;; as SX and rendered in the island's signal context. If the marsh has a
;; :transform function, it reshapes the content before evaluation.
(define morph-marsh
(fn (old-marsh new-marsh island-el)
(let ((transform (dom-get-data old-marsh "sx-marsh-transform"))
(env (dom-get-data old-marsh "sx-marsh-env"))
(new-html (dom-inner-html new-marsh)))
(if (and env new-html (not (empty? new-html)))
;; Parse new content as SX and re-evaluate in island scope
(let ((parsed (parse new-html)))
(let ((sx-content (if transform (invoke transform parsed) parsed)))
;; Dispose old reactive bindings in this marsh
(dispose-marsh-scope old-marsh)
;; Evaluate the SX in a new marsh scope — creates new reactive bindings
(with-marsh-scope old-marsh
(fn ()
(let ((new-dom (render-to-dom sx-content env nil)))
;; Replace marsh children
(dom-remove-children-after old-marsh nil)
(dom-append old-marsh new-dom))))))
;; Fallback: morph like a lake
(do
(sync-attrs old-marsh new-marsh)
(morph-children old-marsh new-marsh))))))
;; --------------------------------------------------------------------------
;; process-signal-updates — server responses write to named store signals
;; --------------------------------------------------------------------------
;;
;; Elements with data-sx-signal="name:value" trigger signal writes.
;; After processing, the attribute is removed (consumed).
;;
;; Values are JSON-parsed: "7" → 7, "\"hello\"" → "hello", "true" → true.
(define process-signal-updates
(fn (root)
(let ((signal-els (dom-query-all root "[data-sx-signal]")))
(for-each
(fn (el)
(let ((spec (dom-get-attr el "data-sx-signal")))
(when spec
(let ((colon-idx (index-of spec ":")))
(when (> colon-idx 0)
(let ((store-name (slice spec 0 colon-idx))
(raw-value (slice spec (+ colon-idx 1))))
(let ((parsed (json-parse raw-value)))
(reset! (use-store store-name) parsed))
(dom-remove-attr el "data-sx-signal")))))))
signal-els))))
;; --------------------------------------------------------------------------