chore(repo): save local work

This commit is contained in:
slhx agent
2026-07-27 13:23:56 +02:00
parent 276f40ee59
commit 2dc7703936
5 changed files with 61 additions and 8 deletions
+47 -2
View File
@@ -343,6 +343,7 @@
await navigateUrl(pageRequestUrl(form, source), rootOf(form) || rootOf(el), mode);
return;
}
savePage(roots()[0]);
const response = await fetch(requestUrl(form, data, method), {
method,
body,
@@ -382,7 +383,32 @@
}
}
function snapshotPage(root) {
return root ? {
hemx: true,
pageHtml: root.innerHTML,
pageTitle: document.title,
scrollX: window.scrollX,
scrollY: window.scrollY,
} : null;
}
function savePage(root) {
const snapshot = snapshotPage(root);
if (snapshot) history.replaceState(snapshot, "", location.href);
}
function restorePage(state, root) {
if (!root || !state || typeof state.pageHtml !== "string") return false;
root.innerHTML = state.pageHtml;
if (state.pageTitle) document.title = state.pageTitle;
bindRoot(root);
requestAnimationFrame(() => scrollTo(state.scrollX, state.scrollY));
return true;
}
async function navigateUrl(href, root, mode = "replace") {
if (mode === "push") savePage(root);
const response = await fetch(href, {
headers: { "X-HEMX-Partial": "1", "Accept": "text/html" },
credentials: "same-origin",
@@ -443,6 +469,8 @@
return;
}
for (const op of batch.ops) applyOp(scope, op);
bindPolling(scope);
bindRevealed(scope);
}
function canApplyOp(scope, op) {
@@ -1101,6 +1129,19 @@
stopDescendantPolling(root);
}
function restoreRevealed(event) {
if (!event.persisted) return;
roots().forEach((root) => {
const observer = revealObservers.get(root);
if (observer) observer.disconnect();
revealObservers.delete(root);
forEachElement(root, (el) => {
if (attrEquals(el, "data-hemx-on", "revealed") || el.hasAttribute("data-hemx-revealed")) revealed.delete(el);
});
bindRevealed(root);
});
}
function start() {
roots().forEach((root) => {
root.setAttribute("data-hemx-request-timeout-ms", String(REQUEST_TIMEOUT_MS));
@@ -1147,9 +1188,13 @@
}
}
addEventListener("popstate", () => {
window.addEventListener("pageshow", restoreRevealed);
addEventListener("popstate", (event) => {
const root = roots()[0];
if (root) navigateUrl(location.href, root, "none").catch((error) => emit(root, "hemx:error", String(error)));
if (root && !restorePage(event.state, root)) {
if (root) navigateUrl(location.href, root, "none").catch((error) => emit(root, "hemx:error", String(error)));
}
});
window.hemx = Object.freeze({
+9 -1
View File
@@ -148,11 +148,16 @@ fn runtime_supports_tiny_delay_and_revealed_scheduling() {
assert!(source.contains("data-hemx-revealed"));
assert!(source.contains("typeof IntersectionObserver === \"undefined\""));
assert!(source.contains("schedule(entry.target, \"revealed\")"));
assert!(source.contains("window.addEventListener(\"pageshow\", restoreRevealed)"));
assert!(source.contains("revealed.delete(el)")); // req: convention/014 test
assert!(source.contains("record.addedNodes.forEach((node) =>"));
assert!(source.contains("descendantRoots(node).forEach(bindRoot)"));
assert!(source.contains("const owner = rootOf(node.parentElement)"));
assert!(source.contains("bindPolling(owner)"));
assert!(source.contains("bindRevealed(owner)")); // req: runtime/007 test
assert!(source.contains("bindRevealed(owner)"));
assert!(source.contains(
"for (const op of batch.ops) applyOp(scope, op);\n bindPolling(scope);\n bindRevealed(scope);"
)); // req: runtime/007 test
}
#[test]
@@ -312,6 +317,9 @@ fn runtime_exposes_page_swap_hooks() {
assert!(source.contains("data-hemx-nav"));
assert!(source.contains("data-hemx-boost"));
assert!(source.contains("history.pushState"));
assert!(source.contains("snapshotPage(root)"));
assert!(source.contains("restorePage(event.state, root)"));
assert!(source.contains("scrollTo(state.scrollX, state.scrollY)")); // req: page_swap/005 test
assert!(source.contains("popstate"));
assert!(source.contains("x-hemx-title"));
assert!(source.contains("x-hemx-fingerprint"));