SX request handler + AJAX nav + shared JIT globals + shell cleanup
- Remove prism.js, sweetalert2, body.js, sx-browser.js from shell — only WASM kernel (sx_browser.bc.wasm.js + sx-platform.js) loads - Restore request-handler.sx integration: SX handles routing + AJAX detection, OCaml does aser → SSR → shell render pipeline - AJAX fragment support: SX-Request header returns content fragment (~14KB) instead of full page (~858KB), cached with "ajax:" prefix - Fix language/applications/etc page functions to return empty fragment instead of nil (was causing 404s) - Shared JIT VM globals: env_bind hook mirrors ALL bindings to a single shared globals table — eliminates stale-snapshot class of JIT bugs - Add native `parse` function for components that need SX parsing - Clean up unused shell params (sx-js-hash, body-js-hash, head-scripts, body-scripts, use-wasm) from shell.sx, helpers.py, and server.ml 14/32 Playwright tests pass (navigation, SSR, isomorphic, geography). Remaining failures are client-side (WASM bytecode 404s block hydration). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -883,16 +883,11 @@ def _get_shell_static() -> dict[str, Any]:
|
||||
pages_sx=pages_sx,
|
||||
sx_css=sx_css,
|
||||
sx_css_classes=sx_css_classes,
|
||||
sx_js_hash=_script_hash("sx-browser.js"),
|
||||
body_js_hash=_script_hash("body.js"),
|
||||
wasm_hash=_wasm_hash("sx_browser.bc.js"),
|
||||
asset_url=_ca.config.get("ASSET_URL", "/static"),
|
||||
head_scripts=_shell_cfg.get("head_scripts"),
|
||||
inline_css=_shell_cfg.get("inline_css"),
|
||||
inline_head_js=_shell_cfg.get("inline_head_js"),
|
||||
init_sx=_shell_cfg.get("init_sx"),
|
||||
body_scripts=_shell_cfg.get("body_scripts"),
|
||||
use_wasm=os.environ.get("SX_USE_WASM") == "1",
|
||||
)
|
||||
|
||||
t1 = time.monotonic()
|
||||
@@ -1037,8 +1032,7 @@ def sx_page_streaming_parts(ctx: dict, page_html: str, *,
|
||||
from quart import current_app
|
||||
pages_sx = _build_pages_sx(current_app.name)
|
||||
|
||||
sx_js_hash = _script_hash("sx-browser.js")
|
||||
body_js_hash = _script_hash("body.js")
|
||||
wasm_hash = _wasm_hash("sx_browser.bc.js")
|
||||
|
||||
# Shell: head + body with server-rendered HTML (not SX mount script)
|
||||
shell = (
|
||||
@@ -1053,11 +1047,6 @@ def sx_page_streaming_parts(ctx: dict, page_html: str, *,
|
||||
f'<meta name="csrf-token" content="{_html_escape(csrf)}">\n'
|
||||
f'<style id="sx-css">{sx_css}</style>\n'
|
||||
f'<meta name="sx-css-classes" content="{sx_css_classes}">\n'
|
||||
'<script src="https://unpkg.com/prismjs/prism.js"></script>\n'
|
||||
'<script src="https://unpkg.com/prismjs/components/prism-javascript.min.js"></script>\n'
|
||||
'<script src="https://unpkg.com/prismjs/components/prism-python.min.js"></script>\n'
|
||||
'<script src="https://unpkg.com/prismjs/components/prism-bash.min.js"></script>\n'
|
||||
'<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>\n'
|
||||
"<script>if(matchMedia('(hover:hover) and (pointer:fine)').matches){document.documentElement.classList.add('hover-capable')}</script>\n"
|
||||
"<script>document.addEventListener('click',function(e){var t=e.target.closest('[data-close-details]');if(!t)return;var d=t.closest('details');if(d)d.removeAttribute('open')})</script>\n"
|
||||
'<style>\n'
|
||||
@@ -1084,11 +1073,8 @@ def sx_page_streaming_parts(ctx: dict, page_html: str, *,
|
||||
# Tail: bootstrap suspense resolver + scripts + close
|
||||
tail = (
|
||||
_SX_STREAMING_BOOTSTRAP + '\n'
|
||||
+ (f'<script src="{asset_url}/wasm/sx_browser.bc.wasm.js"></script>\n'
|
||||
f'<script src="{asset_url}/wasm/sx-platform.js"></script>\n'
|
||||
if os.environ.get("SX_USE_WASM") == "1" else
|
||||
f'<script src="{asset_url}/scripts/sx-browser.js?v={sx_js_hash}"></script>\n')
|
||||
+ f'<script src="{asset_url}/scripts/body.js?v={body_js_hash}"></script>\n'
|
||||
+ f'<script src="{asset_url}/wasm/sx_browser.bc.wasm.js?v={wasm_hash}"></script>\n'
|
||||
f'<script src="{asset_url}/wasm/sx-platform.js?v={wasm_hash}"></script>\n'
|
||||
)
|
||||
|
||||
return shell, tail
|
||||
|
||||
@@ -1,29 +1,25 @@
|
||||
;; ---------------------------------------------------------------------------
|
||||
;; Page shell — the outermost HTML document structure
|
||||
;;
|
||||
;; Replaces the Python HTML string template. All page data is computed in
|
||||
;; Python and passed as keyword arguments. The component just arranges
|
||||
;; the precomputed values into HTML structure.
|
||||
;;
|
||||
;; raw! is used for:
|
||||
;; - <!doctype html> (not an element)
|
||||
;; - Script/style content (must not be HTML-escaped)
|
||||
;; - Pre-rendered meta HTML from callers
|
||||
;; ---------------------------------------------------------------------------
|
||||
|
||||
(defcomp ~shared:shell/sx-page-shell (&key (title :as string) (meta-html :as string?) (csrf :as string)
|
||||
(sx-css :as string?) (sx-css-classes :as string?)
|
||||
(component-hash :as string?) (component-defs :as string?)
|
||||
(pages-sx :as string?) (page-sx :as string?)
|
||||
(body-html :as string?)
|
||||
(asset-url :as string) (sx-js-hash :as string) (body-js-hash :as string?)
|
||||
(wasm-hash :as string?)
|
||||
(head-scripts :as list?) (inline-css :as string?) (inline-head-js :as string?)
|
||||
(init-sx :as string?) (body-scripts :as list?)
|
||||
(use-wasm :as boolean?))
|
||||
(defcomp
|
||||
~shared:shell/sx-page-shell
|
||||
(&key
|
||||
(title :as string)
|
||||
(meta-html :as string?)
|
||||
(csrf :as string)
|
||||
(sx-css :as string?)
|
||||
(sx-css-classes :as string?)
|
||||
(component-hash :as string?)
|
||||
(component-defs :as string?)
|
||||
(pages-sx :as string?)
|
||||
(page-sx :as string?)
|
||||
(body-html :as string?)
|
||||
(asset-url :as string)
|
||||
(wasm-hash :as string?)
|
||||
(inline-css :as string?)
|
||||
(inline-head-js :as string?)
|
||||
(init-sx :as string?))
|
||||
(<>
|
||||
(raw! "<!doctype html>")
|
||||
(html :lang "en"
|
||||
(html
|
||||
:lang "en"
|
||||
(head
|
||||
(meta :charset "utf-8")
|
||||
(meta :name "viewport" :content "width=device-width, initial-scale=1")
|
||||
@@ -33,70 +29,53 @@
|
||||
(when meta-html (raw! meta-html))
|
||||
(meta :name "csrf-token" :content csrf)
|
||||
(style :id "sx-css" (raw! (or sx-css "")))
|
||||
;; CSSX rules from island SSR — must be in <head> so they survive
|
||||
;; #main-panel morphs during SPA navigation.
|
||||
(let ((cssx-rules (collected "cssx")))
|
||||
(let
|
||||
((cssx-rules (collected "cssx")))
|
||||
(clear-collected! "cssx")
|
||||
(when (not (empty? cssx-rules))
|
||||
(when
|
||||
(not (empty? cssx-rules))
|
||||
(style :data-cssx true (raw! (join "" cssx-rules)))))
|
||||
(meta :name "sx-css-classes" :content (or sx-css-classes ""))
|
||||
;; CDN / head scripts — configurable per app
|
||||
;; Pass a list (even empty) to override defaults; nil = use defaults
|
||||
(if (not (nil? head-scripts))
|
||||
(map (fn (src) (script :src src)) head-scripts)
|
||||
;; Default: Prism + SweetAlert (legacy apps)
|
||||
(if
|
||||
(not (nil? inline-head-js))
|
||||
(when
|
||||
(not (empty? inline-head-js))
|
||||
(script (raw! inline-head-js)))
|
||||
(<>
|
||||
(script :src "https://unpkg.com/prismjs/prism.js")
|
||||
(script :src "https://unpkg.com/prismjs/components/prism-javascript.min.js")
|
||||
(script :src "https://unpkg.com/prismjs/components/prism-python.min.js")
|
||||
(script :src "https://unpkg.com/prismjs/components/prism-bash.min.js")
|
||||
(script :src "https://cdn.jsdelivr.net/npm/sweetalert2@11")))
|
||||
;; Inline JS — skipped when app provides its own inline-head-js (even empty)
|
||||
(if (not (nil? inline-head-js))
|
||||
(when (not (empty? inline-head-js)) (script (raw! inline-head-js)))
|
||||
(<>
|
||||
(script (raw! "if(matchMedia('(hover:hover) and (pointer:fine)').matches){document.documentElement.classList.add('hover-capable')}"))
|
||||
(script (raw! "document.addEventListener('click',function(e){var t=e.target.closest('[data-close-details]');if(!t)return;var d=t.closest('details');if(d)d.removeAttribute('open')})"))))
|
||||
;; Inline CSS — configurable per app
|
||||
;; Pass a string (even empty) to override defaults; nil = use defaults
|
||||
(if (not (nil? inline-css))
|
||||
(script
|
||||
(raw!
|
||||
"if(matchMedia('(hover:hover) and (pointer:fine)').matches){document.documentElement.classList.add('hover-capable')}"))
|
||||
(script
|
||||
(raw!
|
||||
"document.addEventListener('click',function(e){var t=e.target.closest('[data-close-details]');if(!t)return;var d=t.closest('details');if(d)d.removeAttribute('open')})"))))
|
||||
(if
|
||||
(not (nil? inline-css))
|
||||
(style (raw! inline-css))
|
||||
;; Default: all shared styles (legacy apps)
|
||||
(style (raw! "details[data-toggle-group=\"mobile-panels\"]>summary{list-style:none}
|
||||
details[data-toggle-group=\"mobile-panels\"]>summary::-webkit-details-marker{display:none}
|
||||
@media(min-width:768px){.nav-group:focus-within .submenu,.nav-group:hover .submenu{display:block}}
|
||||
img{max-width:100%;height:auto}
|
||||
.clamp-2{display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
|
||||
.clamp-3{display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;overflow:hidden}
|
||||
.no-scrollbar::-webkit-scrollbar{display:none}.no-scrollbar{-ms-overflow-style:none;scrollbar-width:none}
|
||||
details.group{overflow:hidden}details.group>summary{list-style:none}details.group>summary::-webkit-details-marker{display:none}
|
||||
.sx-indicator{display:none}.sx-request .sx-indicator{display:inline-flex}
|
||||
.sx-error .sx-indicator{display:none}.sx-loading .sx-indicator{display:inline-flex}
|
||||
.js-wrap.open .js-pop{display:block}.js-wrap.open .js-backdrop{display:block}"))))
|
||||
(body :class "bg-stone-50 text-stone-900"
|
||||
;; Server-rendered HTML — visible immediately before JS loads
|
||||
(style
|
||||
(raw!
|
||||
"details[data-toggle-group=\"mobile-panels\"]>summary{list-style:none}\ndetails[data-toggle-group=\"mobile-panels\"]>summary::-webkit-details-marker{display:none}\n@media(min-width:768px){.nav-group:focus-within .submenu,.nav-group:hover .submenu{display:block}}\nimg{max-width:100%;height:auto}\n.clamp-2{display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}\n.clamp-3{display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;overflow:hidden}\n.no-scrollbar::-webkit-scrollbar{display:none}.no-scrollbar{-ms-overflow-style:none;scrollbar-width:none}\ndetails.group{overflow:hidden}details.group>summary{list-style:none}details.group>summary::-webkit-details-marker{display:none}\n.sx-indicator{display:none}.sx-request .sx-indicator{display:inline-flex}\n.sx-error .sx-indicator{display:none}.sx-loading .sx-indicator{display:inline-flex}\n.js-wrap.open .js-pop{display:block}.js-wrap.open .js-backdrop{display:block}"))))
|
||||
(body
|
||||
:class "bg-stone-50 text-stone-900"
|
||||
(div :id "sx-root" (raw! (or body-html "")))
|
||||
(div :id "portal-root")
|
||||
;; Island interactive elements — cursor pointer from SSR, no JS needed
|
||||
(style (raw! "[data-sx-island] button,[data-sx-island] a,[data-sx-island] [role=button]{cursor:pointer}"))
|
||||
(script :type "text/sx" :data-components true :data-hash component-hash
|
||||
(style
|
||||
(raw!
|
||||
"[data-sx-island] button,[data-sx-island] a,[data-sx-island] [role=button]{cursor:pointer}"))
|
||||
(script
|
||||
:type "text/sx"
|
||||
:data-components true
|
||||
:data-hash component-hash
|
||||
(raw! (or component-defs "")))
|
||||
(when init-sx
|
||||
(script :type "text/sx" :data-init true
|
||||
(raw! init-sx)))
|
||||
(script :type "text/sx-pages"
|
||||
(raw! (or pages-sx "")))
|
||||
(script :type "text/sx" :data-mount "#sx-root"
|
||||
(when
|
||||
init-sx
|
||||
(script :type "text/sx" :data-init true (raw! init-sx)))
|
||||
(script :type "text/sx-pages" (raw! (or pages-sx "")))
|
||||
(script
|
||||
:type "text/sx"
|
||||
:data-mount "#sx-root"
|
||||
(raw! (or page-sx "")))
|
||||
(if use-wasm
|
||||
(let ((wv (or wasm-hash "dev")))
|
||||
(<>
|
||||
(script :src (str asset-url "/wasm/sx_browser.bc.wasm.js?v=" wv))
|
||||
(script :src (str asset-url "/wasm/sx-platform.js?v=" wv))))
|
||||
(script :src (str asset-url "/scripts/sx-browser.js?v=" sx-js-hash)))
|
||||
;; Body scripts — configurable per app
|
||||
;; Pass a list (even empty) to override defaults; nil = use defaults
|
||||
(if (not (nil? body-scripts))
|
||||
(map (fn (src) (script :src src)) body-scripts)
|
||||
;; Default: body.js (legacy apps)
|
||||
(script :src (str asset-url "/scripts/body.js?v=" body-js-hash)))))))
|
||||
(let
|
||||
((wv (or wasm-hash "0")))
|
||||
(<>
|
||||
(script :src (str asset-url "/wasm/sx_browser.bc.wasm.js?v=" wv))
|
||||
(script :src (str asset-url "/wasm/sx-platform.js?v=" wv))))))))
|
||||
|
||||
Reference in New Issue
Block a user