Fix client routing: fall through to server on layout/section change
Client-side routing was only swapping #main-panel content without updating OOB headers (nav rows, sub-rows). Now each page entry in the registry includes a layout identity (e.g. "sx-section:Testing") and try-client-route falls through to server when layout changes, so OOB header updates are applied correctly. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -14,7 +14,7 @@
|
|||||||
// =========================================================================
|
// =========================================================================
|
||||||
|
|
||||||
var NIL = Object.freeze({ _nil: true, toString: function() { return "nil"; } });
|
var NIL = Object.freeze({ _nil: true, toString: function() { return "nil"; } });
|
||||||
var SX_VERSION = "2026-03-07T17:30:45Z";
|
var SX_VERSION = "2026-03-07T17:45:49Z";
|
||||||
|
|
||||||
function isNil(x) { return x === NIL || x === null || x === undefined; }
|
function isNil(x) { return x === NIL || x === null || x === undefined; }
|
||||||
function isSxTruthy(x) { return x !== false && !isNil(x); }
|
function isSxTruthy(x) { return x !== false && !isNil(x); }
|
||||||
@@ -2007,6 +2007,13 @@ return domAppendToHead(link); }, domQueryAll(container, "link[rel=\"stylesheet\"
|
|||||||
// page-data-cache-set
|
// page-data-cache-set
|
||||||
var pageDataCacheSet = function(cacheKey, data) { return dictSet(_pageDataCache, cacheKey, {"data": data, "ts": nowMs()}); };
|
var pageDataCacheSet = function(cacheKey, data) { return dictSet(_pageDataCache, cacheKey, {"data": data, "ts": nowMs()}); };
|
||||||
|
|
||||||
|
// current-page-layout
|
||||||
|
var currentPageLayout = function() { return (function() {
|
||||||
|
var pathname = urlPathname(browserLocationHref());
|
||||||
|
var match = findMatchingRoute(pathname, _pageRoutes);
|
||||||
|
return (isSxTruthy(isNil(match)) ? "" : sxOr(get(match, "layout"), ""));
|
||||||
|
})(); };
|
||||||
|
|
||||||
// swap-rendered-content
|
// swap-rendered-content
|
||||||
var swapRenderedContent = function(target, rendered, pathname) { return (domSetTextContent(target, ""), domAppend(target, rendered), hoistHeadElementsFull(target), processElements(target), sxHydrateElements(target), domDispatch(target, "sx:clientRoute", {["pathname"]: pathname}), logInfo((String("sx:route client ") + String(pathname)))); };
|
var swapRenderedContent = function(target, rendered, pathname) { return (domSetTextContent(target, ""), domAppend(target, rendered), hoistHeadElementsFull(target), processElements(target), sxHydrateElements(target), domDispatch(target, "sx:clientRoute", {["pathname"]: pathname}), logInfo((String("sx:route client ") + String(pathname)))); };
|
||||||
|
|
||||||
@@ -2024,6 +2031,9 @@ return domAppendToHead(link); }, domQueryAll(container, "link[rel=\"stylesheet\"
|
|||||||
var tryClientRoute = function(pathname, targetSel) { return (function() {
|
var tryClientRoute = function(pathname, targetSel) { return (function() {
|
||||||
var match = findMatchingRoute(pathname, _pageRoutes);
|
var match = findMatchingRoute(pathname, _pageRoutes);
|
||||||
return (isSxTruthy(isNil(match)) ? (logInfo((String("sx:route no match (") + String(len(_pageRoutes)) + String(" routes) ") + String(pathname))), false) : (function() {
|
return (isSxTruthy(isNil(match)) ? (logInfo((String("sx:route no match (") + String(len(_pageRoutes)) + String(" routes) ") + String(pathname))), false) : (function() {
|
||||||
|
var targetLayout = sxOr(get(match, "layout"), "");
|
||||||
|
var curLayout = currentPageLayout();
|
||||||
|
return (isSxTruthy(!isSxTruthy((targetLayout == curLayout))) ? (logInfo((String("sx:route server (layout: ") + String(curLayout) + String(" -> ") + String(targetLayout) + String(") ") + String(pathname))), false) : (function() {
|
||||||
var contentSrc = get(match, "content");
|
var contentSrc = get(match, "content");
|
||||||
var closure = sxOr(get(match, "closure"), {});
|
var closure = sxOr(get(match, "closure"), {});
|
||||||
var params = get(match, "params");
|
var params = get(match, "params");
|
||||||
@@ -2061,6 +2071,7 @@ return (function() {
|
|||||||
})()));
|
})()));
|
||||||
})());
|
})());
|
||||||
})());
|
})());
|
||||||
|
})());
|
||||||
})(); };
|
})(); };
|
||||||
|
|
||||||
// bind-client-route-link
|
// bind-client-route-link
|
||||||
|
|||||||
@@ -704,6 +704,31 @@ def _build_pages_sx(service: str) -> str:
|
|||||||
if io_deps else "()"
|
if io_deps else "()"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Extract layout identity for client-side routing.
|
||||||
|
# When layout changes between pages, client routing falls through
|
||||||
|
# to server so OOB header updates are applied.
|
||||||
|
layout_id = ""
|
||||||
|
if isinstance(page_def.layout, str):
|
||||||
|
layout_id = page_def.layout
|
||||||
|
elif isinstance(page_def.layout, list):
|
||||||
|
from .types import Keyword as _Kw, Symbol as _Sym
|
||||||
|
first = page_def.layout[0]
|
||||||
|
if isinstance(first, _Kw):
|
||||||
|
layout_id = first.name
|
||||||
|
elif isinstance(first, _Sym):
|
||||||
|
layout_id = first.name
|
||||||
|
else:
|
||||||
|
layout_id = str(first)
|
||||||
|
# Append section kwarg to distinguish same-layout-type
|
||||||
|
# with different sections (e.g. sx-section+Docs vs sx-section+Testing)
|
||||||
|
raw_layout = page_def.layout
|
||||||
|
for li in range(1, len(raw_layout) - 1):
|
||||||
|
if isinstance(raw_layout[li], _Kw) and raw_layout[li].name == "section":
|
||||||
|
val = raw_layout[li + 1]
|
||||||
|
if val is not None:
|
||||||
|
layout_id = f"{layout_id}:{val}"
|
||||||
|
break
|
||||||
|
|
||||||
# Build closure as SX dict
|
# Build closure as SX dict
|
||||||
closure_parts: list[str] = []
|
closure_parts: list[str] = []
|
||||||
for k, v in page_def.closure.items():
|
for k, v in page_def.closure.items():
|
||||||
@@ -719,6 +744,7 @@ def _build_pages_sx(service: str) -> str:
|
|||||||
+ " :auth " + _sx_literal(auth)
|
+ " :auth " + _sx_literal(auth)
|
||||||
+ " :has-data " + has_data
|
+ " :has-data " + has_data
|
||||||
+ " :stream " + stream
|
+ " :stream " + stream
|
||||||
|
+ " :layout " + _sx_literal(layout_id)
|
||||||
+ " :io-deps " + io_deps_sx
|
+ " :io-deps " + io_deps_sx
|
||||||
+ " :content " + _sx_literal(content_src)
|
+ " :content " + _sx_literal(content_src)
|
||||||
+ " :deps " + deps_sx
|
+ " :deps " + deps_sx
|
||||||
|
|||||||
@@ -593,7 +593,14 @@
|
|||||||
;; Client-side routing
|
;; Client-side routing
|
||||||
;; --------------------------------------------------------------------------
|
;; --------------------------------------------------------------------------
|
||||||
|
|
||||||
;; No app-specific nav update here — apps handle sx:clientRoute event.
|
(define current-page-layout
|
||||||
|
(fn ()
|
||||||
|
;; Find the layout name of the currently displayed page by matching
|
||||||
|
;; the browser URL against the page route table.
|
||||||
|
(let ((pathname (url-pathname (browser-location-href)))
|
||||||
|
(match (find-matching-route pathname _page-routes)))
|
||||||
|
(if (nil? match) ""
|
||||||
|
(or (get match "layout") "")))))
|
||||||
|
|
||||||
|
|
||||||
(define swap-rendered-content
|
(define swap-rendered-content
|
||||||
@@ -634,20 +641,25 @@
|
|||||||
;; Try to render a page client-side. Returns true if successful, false otherwise.
|
;; Try to render a page client-side. Returns true if successful, false otherwise.
|
||||||
;; target-sel is the CSS selector for the swap target (from sx-boost value).
|
;; target-sel is the CSS selector for the swap target (from sx-boost value).
|
||||||
;; For pure pages: renders immediately. For :data pages: fetches data then renders.
|
;; For pure pages: renders immediately. For :data pages: fetches data then renders.
|
||||||
|
;; Falls through to server when layout changes (needs OOB header update).
|
||||||
(let ((match (find-matching-route pathname _page-routes)))
|
(let ((match (find-matching-route pathname _page-routes)))
|
||||||
(if (nil? match)
|
(if (nil? match)
|
||||||
(do (log-info (str "sx:route no match (" (len _page-routes) " routes) " pathname)) false)
|
(do (log-info (str "sx:route no match (" (len _page-routes) " routes) " pathname)) false)
|
||||||
(let ((content-src (get match "content"))
|
(let ((target-layout (or (get match "layout") ""))
|
||||||
(closure (or (get match "closure") {}))
|
(cur-layout (current-page-layout)))
|
||||||
(params (get match "params"))
|
(if (not (= target-layout cur-layout))
|
||||||
(page-name (get match "name")))
|
(do (log-info (str "sx:route server (layout: " cur-layout " -> " target-layout ") " pathname)) false)
|
||||||
(if (or (nil? content-src) (empty? content-src))
|
(let ((content-src (get match "content"))
|
||||||
(do (log-warn (str "sx:route no content for " pathname)) false)
|
(closure (or (get match "closure") {}))
|
||||||
(let ((target (resolve-route-target target-sel)))
|
(params (get match "params"))
|
||||||
(if (nil? target)
|
(page-name (get match "name")))
|
||||||
(do (log-warn (str "sx:route target not found: " target-sel)) false)
|
(if (or (nil? content-src) (empty? content-src))
|
||||||
(if (not (deps-satisfied? match))
|
(do (log-warn (str "sx:route no content for " pathname)) false)
|
||||||
(do (log-info (str "sx:route deps miss for " page-name)) false)
|
(let ((target (resolve-route-target target-sel)))
|
||||||
|
(if (nil? target)
|
||||||
|
(do (log-warn (str "sx:route target not found: " target-sel)) false)
|
||||||
|
(if (not (deps-satisfied? match))
|
||||||
|
(do (log-info (str "sx:route deps miss for " page-name)) false)
|
||||||
(let ((io-deps (get match "io-deps"))
|
(let ((io-deps (get match "io-deps"))
|
||||||
(has-io (and io-deps (not (empty? io-deps)))))
|
(has-io (and io-deps (not (empty? io-deps)))))
|
||||||
;; Ensure IO deps are registered as proxied primitives
|
;; Ensure IO deps are registered as proxied primitives
|
||||||
@@ -715,7 +727,7 @@
|
|||||||
(do (log-info (str "sx:route server (eval failed) " pathname)) false)
|
(do (log-info (str "sx:route server (eval failed) " pathname)) false)
|
||||||
(do
|
(do
|
||||||
(swap-rendered-content target rendered pathname)
|
(swap-rendered-content target rendered pathname)
|
||||||
true)))))))))))))))
|
true)))))))))))))))))
|
||||||
|
|
||||||
|
|
||||||
(define bind-client-route-link
|
(define bind-client-route-link
|
||||||
|
|||||||
Reference in New Issue
Block a user