fix(js): tolerate malformed bootstrap state

Report malformed data-slhx-st with slhx:state-error and continue binding the root with an empty atom store instead of letting state decode failures stop handlers, navigation, or push.

req: state/004

req: state/006

req: runtime/001
This commit is contained in:
slhx agent
2026-06-01 21:35:49 +02:00
parent ad135d81fe
commit c69eb9932c
3 changed files with 23 additions and 2 deletions
+3
View File
@@ -640,6 +640,9 @@ what a valid business email is.
### req: state/004
004 SSR roots may carry a `data-slhx-st` base64url-encoded postcard blob on the `data-slhx-root` element. Runtime decodes it into the client atom store. Atoms computed from server state are immediately available to client-side handlers without a round-trip.
### req: state/006
006 Malformed `data-slhx-st` bootstrap state must not stop the standard runtime from binding roots, handlers, navigation, or push. The runtime reports `slhx:state-error` and continues with an empty atom store for that root.
### req: state/005
005 Not all state is an Atom. Ordinary Rust fields are preferred unless the value must be independently addressed, bootstrapped, synced, or subscribed. Atoms are explicit resources, not the default state container.
+5
View File
@@ -668,8 +668,13 @@
function bootstrapState(root) {
const encoded = root.getAttribute(STATE);
if (!encoded) return;
try {
const store = atomStore(root);
for (const atom of decodeAtomState(encoded)) store.set(String(atom.id), atom.bytes);
} catch (error) {
atomStores.delete(root);
emit(root, "slhx:state-error", String(error));
}
}
function decodeAtomState(encoded) {
+13
View File
@@ -216,6 +216,19 @@ fn runtime_refuses_partial_updates_on_fingerprint_mismatch() {
assert!(source.contains("if (expected && String(batch.fingerprint) !== expected)"));
}
#[test]
fn runtime_malformed_bootstrap_state_reports_and_continues() {
// req: state/004 req: state/006 req: runtime/001
let source = slhx_js::RUNTIME_JS;
assert!(source.contains("function bootstrapState(root)"));
assert!(source.contains("try {\n const store = atomStore(root);"));
assert!(source.contains("atomStores.delete(root)"));
assert!(source.contains("emit(root, \"slhx:state-error\", String(error))"));
assert!(source.contains("bindRoot(root)"));
assert!(source.contains("bindSse(root)"));
}
#[test]
fn runtime_applies_sse_effect_batches_inside_roots() {
// req: push/001 req: push/003 req: push/006 req: runtime/001