Merge branch 'ui-shell': nav state, ears, feeds table, font flash, faster navigation

# Conflicts:
#	src/web/static/app.css
This commit is contained in:
2026-09-04 17:08:10 +00:00
11 changed files with 442 additions and 30 deletions
File diff suppressed because one or more lines are too long
+7 -1
View File
@@ -109,7 +109,13 @@ document.addEventListener("click", (event) => {
/* step 6: reload a job page every N seconds while its job is requested/running */
document.querySelectorAll("[data-refresh]").forEach((element) => {
const seconds = Number(element.dataset.refresh);
if (seconds > 0) setTimeout(() => window.location.reload(), seconds * 1000);
if (seconds <= 0) return;
const schedule = () => setTimeout(() => window.location.reload(), seconds * 1000);
if (document.prerendering) {
document.addEventListener("prerenderingchange", schedule, { once: true });
} else {
schedule();
}
});
/* step 4: table-of-contents panel, current chapter, and reading progress */
const tocPanel = document.querySelector("[data-toc-panel]");
+26
View File
@@ -0,0 +1,26 @@
{
"prerender": [
{
"source": "document",
"where": {
"and": [
{ "href_matches": ["/", "/issues/*"] },
{ "not": { "href_matches": ["/logout", "/rate", "/static/*", "/*\\?*"] } }
]
},
"eagerness": "moderate"
}
],
"prefetch": [
{
"source": "document",
"where": {
"and": [
{ "href_matches": ["/dashboard", "/dashboard/*"] },
{ "not": { "href_matches": ["/logout", "/rate", "/static/*", "/*\\?*"] } }
]
},
"eagerness": "moderate"
}
]
}
+19
View File
@@ -9,4 +9,23 @@
} catch (_) {
document.documentElement.removeAttribute("data-theme");
}
// Restore persisted disclosures as the parser creates them. This head script
// runs before paint, so a cold app.js request cannot expose the server-default
// open state and then shift the dashboard when the script finally arrives.
const restoreDetails = (root) => {
const details = [];
if (root.nodeType === Node.ELEMENT_NODE && root.matches("details[id]")) details.push(root);
root.querySelectorAll?.("details[id]").forEach((element) => details.push(element));
details.forEach((element) => {
try {
element.open = localStorage.getItem("details:" + element.id) === "open";
} catch (_) {}
});
};
const detailsObserver = new MutationObserver((records) => {
records.forEach((record) => record.addedNodes.forEach(restoreDetails));
});
detailsObserver.observe(document.documentElement, { childList: true, subtree: true });
document.addEventListener("DOMContentLoaded", () => detailsObserver.disconnect(), { once: true });
})();