Fix signal-add-sub! losing subscribers after remove, fix build pipeline
signal-add-sub! used (append! subscribers f) which returns a new list for immutable List but discards the result — after signal-remove-sub! replaces the subscribers list via dict-set!, re-adding subscribers silently fails. Counter island only worked once (0→1 then stuck). Fix: use (dict-set! s "subscribers" (append ...)) to explicitly update the dict field, matching signal-remove-sub!'s pattern. Build pipeline fixes: - sx-build-all.sh now bundles spec→dist and recompiles .sxbc bytecode - compile-modules.js syncs .sx source files alongside .sxbc to wasm/sx/ - Per-file cache busting: wasm, platform JS, and sxbc each get own hash - bundle.sh adds cssx.sx to dist Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1597,6 +1597,8 @@ let http_render_page env path headers =
|
||||
Keyword "sx-css-classes"; get_shell "sx-css-classes";
|
||||
Keyword "asset-url"; get_shell "asset-url";
|
||||
Keyword "wasm-hash"; get_shell "wasm-hash";
|
||||
Keyword "platform-hash"; get_shell "platform-hash";
|
||||
Keyword "sxbc-hash"; get_shell "sxbc-hash";
|
||||
Keyword "inline-css"; get_shell "inline-css";
|
||||
Keyword "inline-head-js"; get_shell "inline-head-js";
|
||||
Keyword "init-sx"; get_shell "init-sx";
|
||||
@@ -1678,6 +1680,20 @@ let file_hash path =
|
||||
String.sub (Digest.string (In_channel.with_open_bin path In_channel.input_all) |> Digest.to_hex) 0 12
|
||||
else ""
|
||||
|
||||
let sxbc_combined_hash dir =
|
||||
let sxbc_dir = dir ^ "/sx" in
|
||||
if Sys.file_exists sxbc_dir && Sys.is_directory sxbc_dir then begin
|
||||
let files = Array.to_list (Sys.readdir sxbc_dir) in
|
||||
let sxbc_files = List.filter (fun f -> Filename.check_suffix f ".sxbc") files in
|
||||
let sorted = List.sort String.compare sxbc_files in
|
||||
let buf = Buffer.create 65536 in
|
||||
List.iter (fun f ->
|
||||
let path = sxbc_dir ^ "/" ^ f in
|
||||
Buffer.add_string buf (In_channel.with_open_bin path In_channel.input_all)
|
||||
) sorted;
|
||||
String.sub (Digest.string (Buffer.contents buf) |> Digest.to_hex) 0 12
|
||||
end else ""
|
||||
|
||||
let read_css_file path =
|
||||
if Sys.file_exists path then
|
||||
In_channel.with_open_text path In_channel.input_all
|
||||
@@ -1726,8 +1742,10 @@ let http_inject_shell_statics env static_dir sx_sxc =
|
||||
literals, preventing the HTML parser from matching </script>. *)
|
||||
let component_defs = raw_defs in
|
||||
let component_hash = Digest.string component_defs |> Digest.to_hex in
|
||||
(* Compute file hashes for cache busting *)
|
||||
(* Compute per-file hashes for cache busting *)
|
||||
let wasm_hash = file_hash (static_dir ^ "/wasm/sx_browser.bc.wasm.js") in
|
||||
let platform_hash = file_hash (static_dir ^ "/wasm/sx-platform-2.js") in
|
||||
let sxbc_hash = sxbc_combined_hash (static_dir ^ "/wasm") in
|
||||
(* Read CSS for inline injection *)
|
||||
let tw_css = read_css_file (static_dir ^ "/styles/tw.css") in
|
||||
let basics_css = read_css_file (static_dir ^ "/styles/basics.css") in
|
||||
@@ -1781,6 +1799,8 @@ let http_inject_shell_statics env static_dir sx_sxc =
|
||||
ignore (env_bind env "__shell-sx-css-classes" (String ""));
|
||||
ignore (env_bind env "__shell-asset-url" (String "/static"));
|
||||
ignore (env_bind env "__shell-wasm-hash" (String wasm_hash));
|
||||
ignore (env_bind env "__shell-platform-hash" (String platform_hash));
|
||||
ignore (env_bind env "__shell-sxbc-hash" (String sxbc_hash));
|
||||
ignore (env_bind env "__shell-inline-css" Nil);
|
||||
ignore (env_bind env "__shell-inline-head-js" Nil);
|
||||
(* init-sx: trigger client-side render when sx-root is empty (SSR failed).
|
||||
@@ -1793,8 +1813,8 @@ let http_inject_shell_statics env static_dir sx_sxc =
|
||||
SX.renderPage(); \
|
||||
} \
|
||||
});"));
|
||||
Printf.eprintf "[sx-http] Shell statics: defs=%d hash=%s css=%d wasm=%s\n%!"
|
||||
(String.length component_defs) component_hash (String.length sx_css) wasm_hash
|
||||
Printf.eprintf "[sx-http] Shell statics: defs=%d hash=%s css=%d wasm=%s platform=%s sxbc=%s\n%!"
|
||||
(String.length component_defs) component_hash (String.length sx_css) wasm_hash platform_hash sxbc_hash
|
||||
|
||||
let http_setup_declarative_stubs env =
|
||||
(* Stub declarative forms that are metadata-only — no-ops at render time. *)
|
||||
|
||||
@@ -65,6 +65,9 @@ cp "$ROOT/web/engine.sx" "$DIST/sx/"
|
||||
cp "$ROOT/web/orchestration.sx" "$DIST/sx/"
|
||||
cp "$ROOT/web/boot.sx" "$DIST/sx/"
|
||||
|
||||
# 9. CSSX (stylesheet language)
|
||||
cp "$ROOT/sx/sx/cssx.sx" "$DIST/sx/"
|
||||
|
||||
# Summary
|
||||
WASM_SIZE=$(du -sh "$DIST/sx_browser.bc.wasm.assets" | cut -f1)
|
||||
JS_SIZE=$(du -sh "$DIST/sx_browser.bc.js" | cut -f1)
|
||||
|
||||
@@ -185,6 +185,7 @@ const staticSxDir = path.resolve(__dirname, '..', '..', '..', 'shared', 'static'
|
||||
if (fs.existsSync(staticSxDir)) {
|
||||
let copied = 0;
|
||||
for (const file of FILES) {
|
||||
// Copy bytecode
|
||||
for (const ext of ['.sxbc', '.sxbc.json']) {
|
||||
const src = path.join(sxDir, file.replace(/\.sx$/, ext));
|
||||
const dst = path.join(staticSxDir, file.replace(/\.sx$/, ext));
|
||||
@@ -193,6 +194,13 @@ if (fs.existsSync(staticSxDir)) {
|
||||
copied++;
|
||||
}
|
||||
}
|
||||
// Also sync .sx source files (fallback when .sxbc missing)
|
||||
const sxSrc = path.join(sxDir, file);
|
||||
const sxDst = path.join(staticSxDir, file);
|
||||
if (fs.existsSync(sxSrc) && !fs.lstatSync(sxSrc).isSymbolicLink()) {
|
||||
fs.copyFileSync(sxSrc, sxDst);
|
||||
copied++;
|
||||
}
|
||||
}
|
||||
console.log('Copied', copied, 'files to', staticSxDir);
|
||||
}
|
||||
|
||||
@@ -180,8 +180,12 @@
|
||||
|
||||
var _baseUrl = "";
|
||||
|
||||
// Detect base URL from current script
|
||||
// Detect base URL and cache-bust params from current script tag.
|
||||
// _cacheBust comes from the script's own ?v= query string (used for .sx source fallback).
|
||||
// _sxbcCacheBust comes from data-sxbc-hash attribute — a separate content hash
|
||||
// covering all .sxbc files so each file gets its own correct cache buster.
|
||||
var _cacheBust = "";
|
||||
var _sxbcCacheBust = "";
|
||||
(function() {
|
||||
if (typeof document !== "undefined") {
|
||||
var scripts = document.getElementsByTagName("script");
|
||||
@@ -191,6 +195,8 @@
|
||||
_baseUrl = src.substring(0, src.lastIndexOf("/") + 1);
|
||||
var qi = src.indexOf("?");
|
||||
if (qi !== -1) _cacheBust = src.substring(qi);
|
||||
var sxbcHash = scripts[i].getAttribute("data-sxbc-hash");
|
||||
if (sxbcHash) _sxbcCacheBust = "?v=" + sxbcHash;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -233,7 +239,7 @@
|
||||
*/
|
||||
function loadBytecodeFile(path) {
|
||||
var bcPath = path.replace(/\.sx$/, '.sxbc.json');
|
||||
var url = _baseUrl + bcPath + _cacheBust;
|
||||
var url = _baseUrl + bcPath + _sxbcCacheBust;
|
||||
try {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open("GET", url, false);
|
||||
|
||||
Reference in New Issue
Block a user