Revert instance soft nav cache to fix navigation flicker

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-06-23 23:59:27 +08:00
parent 016c93faf2
commit e3559531d9
6 changed files with 41 additions and 340 deletions
+1 -1
View File
@@ -3,7 +3,7 @@
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover"> <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<script src="/static/instance_theme.js?v=12"></script> <script src="/static/instance_theme.js?v=13"></script>
<link rel="stylesheet" href="/static/instance_theme_early.css?v=4"> <link rel="stylesheet" href="/static/instance_theme_early.css?v=4">
<link rel="stylesheet" href="/static/account_risk_badge.css?v=3"> <link rel="stylesheet" href="/static/account_risk_badge.css?v=3">
<script src="/static/account_risk_badge.js?v=3"></script> <script src="/static/account_risk_badge.js?v=3"></script>
+1 -1
View File
@@ -3,7 +3,7 @@
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover"> <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<script src="/static/instance_theme.js?v=12"></script> <script src="/static/instance_theme.js?v=13"></script>
<link rel="stylesheet" href="/static/instance_theme_early.css?v=4"> <link rel="stylesheet" href="/static/instance_theme_early.css?v=4">
<link rel="stylesheet" href="/static/account_risk_badge.css?v=3"> <link rel="stylesheet" href="/static/account_risk_badge.css?v=3">
<script src="/static/account_risk_badge.js?v=3"></script> <script src="/static/account_risk_badge.js?v=3"></script>
+1 -1
View File
@@ -3,7 +3,7 @@
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover"> <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<script src="/static/instance_theme.js?v=12"></script> <script src="/static/instance_theme.js?v=13"></script>
<link rel="stylesheet" href="/static/instance_theme_early.css?v=4"> <link rel="stylesheet" href="/static/instance_theme_early.css?v=4">
<link rel="stylesheet" href="/static/account_risk_badge.css?v=3"> <link rel="stylesheet" href="/static/account_risk_badge.css?v=3">
<script src="/static/account_risk_badge.js?v=3"></script> <script src="/static/account_risk_badge.js?v=3"></script>
+1 -1
View File
@@ -3,7 +3,7 @@
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover"> <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<script src="/static/instance_theme.js?v=12"></script> <script src="/static/instance_theme.js?v=13"></script>
<link rel="stylesheet" href="/static/instance_theme_early.css?v=4"> <link rel="stylesheet" href="/static/instance_theme_early.css?v=4">
<link rel="stylesheet" href="/static/account_risk_badge.css?v=3"> <link rel="stylesheet" href="/static/account_risk_badge.css?v=3">
<script src="/static/account_risk_badge.js?v=3"></script> <script src="/static/account_risk_badge.js?v=3"></script>
+37 -329
View File
@@ -281,7 +281,6 @@
function apply() { function apply() {
if (!mq.matches) return; if (!mq.matches) return;
if (consumeSoftNavPending()) return;
document.querySelectorAll(".top-nav").forEach(scrollActiveTab); document.querySelectorAll(".top-nav").forEach(scrollActiveTab);
} }
@@ -330,331 +329,40 @@
} }
} }
function remapInlineStylesInHtml(html) { /** 仅中控 iframe 内:fetch + document.write 换页,避免 iframe 整页卸载白屏。单独打开实例仍走浏览器正常跳转。 */
if (!html) return html; function initHubEmbedInFrameNav() {
return html.replace(/\sstyle=(["'])([\s\S]*?)\1/gi, (full, q, style) => { if (!isHubLinked()) return;
return ` style=${q}${remapInlineStyle(style, "light")}${q}`;
});
}
function prepareSoftNavHtml(html, theme) {
const t = normalize(theme || get());
const bg = META[t];
let out = html || "";
if (t === "light") {
out = remapInlineStylesInHtml(out);
}
const guard = `<style id="inst-nav-guard">html,body{background:${bg}!important;color-scheme:${t};}#inst-nav-overlay{position:fixed;inset:0;z-index:2147483647;background:${bg};pointer-events:none;opacity:1;}</style>`;
if (out.includes("</head>")) {
out = out.replace("</head>", `${guard}</head>`);
} else {
out = guard + out;
}
out = out.replace(/<html([^>]*)>/i, (m, attrs) => {
if (/data-theme=/i.test(attrs)) {
return m.replace(/data-theme="[^"]*"/i, `data-theme="${t}"`);
}
return `<html${attrs} data-theme="${t}">`;
});
const overlay = `<div id="inst-nav-overlay" aria-hidden="true" style="position:fixed;inset:0;z-index:2147483647;background:${bg};pointer-events:none;opacity:1"></div>`;
if (/<body[^>]*>/i.test(out)) {
out = out.replace(/<body([^>]*)>/i, `<body$1>${overlay}`);
}
return out;
}
function ensureNavOverlay(theme) {
const t = normalize(theme || get());
const bg = META[t];
let el = document.getElementById("inst-nav-overlay");
if (!el) {
el = document.createElement("div");
el.id = "inst-nav-overlay";
el.setAttribute("aria-hidden", "true");
(document.body || document.documentElement).appendChild(el);
}
el.style.cssText = `position:fixed;inset:0;z-index:2147483647;background:${bg};pointer-events:none;opacity:1;`;
return el;
}
function dismissNavOverlay() {
const el = document.getElementById("inst-nav-overlay");
if (!el) return;
requestAnimationFrame(() => {
requestAnimationFrame(() => {
el.style.transition = "opacity 90ms ease";
el.style.opacity = "0";
window.setTimeout(() => {
try {
el.remove();
} catch (_) {}
}, 120);
});
});
}
function markSoftNavPending() {
try {
sessionStorage.setItem("inst-soft-nav", "1");
} catch (_) {}
}
function consumeSoftNavPending() {
try {
if (sessionStorage.getItem("inst-soft-nav") === "1") {
sessionStorage.removeItem("inst-soft-nav");
return true;
}
} catch (_) {}
return false;
}
const PAGE_CACHE_PREFIX = "inst-pc:v1:";
const PAGE_CACHE_DAYS_KEY = "inst-page-cache-days";
const PAGE_CACHE_INDEX_KEY = "inst-page-cache-index";
const DEFAULT_PAGE_CACHE_DAYS = 7;
const MAX_PAGE_CACHE_ENTRIES = 16;
const CACHE_REVALIDATE_KEY = "inst-cache-revalidate";
function getPageCacheMaxAgeMs() {
try {
const days = parseInt(localStorage.getItem(PAGE_CACHE_DAYS_KEY) || "", 10);
if (Number.isFinite(days) && days > 0 && days <= 30) {
return days * 86400000;
}
} catch (_) {}
return DEFAULT_PAGE_CACHE_DAYS * 86400000;
}
function setPageCacheDays(days) {
const d = parseInt(days, 10);
if (!Number.isFinite(d) || d <= 0 || d > 30) return DEFAULT_PAGE_CACHE_DAYS;
try {
localStorage.setItem(PAGE_CACHE_DAYS_KEY, String(d));
} catch (_) {}
return d;
}
function pageCacheStorageKey(cacheKey) {
return PAGE_CACHE_PREFIX + cacheKey;
}
function pageCacheKey(href) {
try {
const u = new URL(href, location.href);
return `${u.pathname}${u.search}|t=${get()}`;
} catch (_) {
return `${href}|t=${get()}`;
}
}
function readPageCacheIndex() {
try {
const raw = localStorage.getItem(PAGE_CACHE_INDEX_KEY);
const data = raw ? JSON.parse(raw) : null;
return Array.isArray(data) ? data : [];
} catch (_) {
return [];
}
}
function writePageCacheIndex(rows) {
try {
localStorage.setItem(PAGE_CACHE_INDEX_KEY, JSON.stringify(rows.slice(0, MAX_PAGE_CACHE_ENTRIES)));
} catch (_) {}
}
function prunePageCache(aggressive) {
const maxAge = getPageCacheMaxAgeMs();
const now = Date.now();
let index = readPageCacheIndex().filter((row) => {
if (!row || !row.key || !row.savedAt) return false;
if (now - row.savedAt > maxAge) {
try {
localStorage.removeItem(pageCacheStorageKey(row.key));
} catch (_) {}
return false;
}
return true;
});
index.sort((a, b) => (b.savedAt || 0) - (a.savedAt || 0));
if (aggressive || index.length > MAX_PAGE_CACHE_ENTRIES) {
index.slice(MAX_PAGE_CACHE_ENTRIES).forEach((row) => {
try {
localStorage.removeItem(pageCacheStorageKey(row.key));
} catch (_) {}
});
index = index.slice(0, MAX_PAGE_CACHE_ENTRIES);
}
writePageCacheIndex(index);
}
function readPageCache(cacheKey) {
prunePageCache(false);
try {
const raw = localStorage.getItem(pageCacheStorageKey(cacheKey));
if (!raw) return null;
const entry = JSON.parse(raw);
if (!entry || typeof entry.html !== "string" || !entry.savedAt) return null;
if (Date.now() - entry.savedAt > getPageCacheMaxAgeMs()) {
localStorage.removeItem(pageCacheStorageKey(cacheKey));
return null;
}
return entry;
} catch (_) {
return null;
}
}
function savePageCache(cacheKey, html) {
if (!html) return;
const savedAt = Date.now();
const payload = JSON.stringify({ html, savedAt, key: cacheKey });
try {
localStorage.setItem(pageCacheStorageKey(cacheKey), payload);
} catch (_) {
prunePageCache(true);
try {
localStorage.setItem(pageCacheStorageKey(cacheKey), payload);
} catch (e2) {
return;
}
}
let index = readPageCacheIndex().filter((row) => row && row.key !== cacheKey);
index.unshift({ key: cacheKey, savedAt });
writePageCacheIndex(index);
}
function clearPageCache() {
readPageCacheIndex().forEach((row) => {
if (!row || !row.key) return;
try {
localStorage.removeItem(pageCacheStorageKey(row.key));
} catch (_) {}
});
try {
localStorage.removeItem(PAGE_CACHE_INDEX_KEY);
} catch (_) {}
}
function markCacheRevalidatePending() {
try {
sessionStorage.setItem(CACHE_REVALIDATE_KEY, "1");
} catch (_) {}
}
function consumeCacheRevalidatePending() {
try {
if (sessionStorage.getItem(CACHE_REVALIDATE_KEY) === "1") {
sessionStorage.removeItem(CACHE_REVALIDATE_KEY);
return true;
}
} catch (_) {}
return false;
}
function refreshInstanceLiveData() {
const fns = [
"refreshAccountSnapshot",
"refreshPriceSnapshotConditional",
"refreshPriceSnapshot",
"refreshOrderDefaults",
"loadJournals",
"loadReviews",
];
fns.forEach((name) => {
const fn = global[name];
if (typeof fn === "function") {
try {
fn();
} catch (_) {}
}
});
try {
document.dispatchEvent(new CustomEvent("instance-page-revalidate"));
} catch (_) {}
}
function scheduleLiveDataRefresh() {
window.setTimeout(refreshInstanceLiveData, 0);
window.setTimeout(refreshInstanceLiveData, 350);
}
function updateSoftNavHistory(href, opts) {
let path = href;
try {
const u = new URL(href, location.href);
path = u.pathname + u.search + u.hash;
} catch (_) {}
if (opts && opts.replace) history.replaceState(null, "", path);
else history.pushState(null, "", path);
}
function paintSoftNavDocument(html, href, opts) {
updateSoftNavHistory(href, opts);
document.open();
document.write(html);
document.close();
}
function seedCurrentPageCache() {
if (global.__instPageCacheSeeded) return;
global.__instPageCacheSeeded = true;
const href = location.pathname + location.search;
const cacheKey = pageCacheKey(href);
if (readPageCache(cacheKey)) return;
window.setTimeout(() => {
fetch(href, { credentials: "same-origin", cache: "no-store" })
.then((r) => (r.ok ? r.text() : null))
.then((html) => {
if (html) savePageCache(cacheKey, html);
})
.catch(() => {});
}, 1500);
}
function initInstanceTopNavSoft() {
prunePageCache(false);
let navToken = 0; let navToken = 0;
function isSoftNavLink(a) { function isSoftNavLink(a) {
if (!a || !a.getAttribute) return false; if (!a || !a.getAttribute) return false;
if (a.hasAttribute("download") || a.target === "_blank") return false;
return !!a.closest(".top-nav, .strategy-subnav"); return !!a.closest(".top-nav, .strategy-subnav");
} }
async function navigateInFrame(href, opts) { async function navigateInFrame(href, opts) {
const token = ++navToken; const token = ++navToken;
const themeNow = get();
const cacheKey = pageCacheKey(href);
ensureNavOverlay(themeNow);
markSoftNavPending();
const cached = readPageCache(cacheKey);
let paintedFromCache = false;
if (cached && cached.html) {
markCacheRevalidatePending();
paintSoftNavDocument(prepareSoftNavHtml(cached.html, themeNow), href, opts);
paintedFromCache = true;
}
try { try {
const r = await fetch(href, { credentials: "same-origin", cache: "no-store" }); const r = await fetch(href, { credentials: "same-origin" });
if (token !== navToken) return; if (token !== navToken) return;
if (!r.ok) { if (!r.ok) {
if (!paintedFromCache) location.href = href; location.href = href;
return; return;
} }
const rawHtml = await r.text(); const html = await r.text();
savePageCache(cacheKey, rawHtml);
if (token !== navToken) return; if (token !== navToken) return;
if (!paintedFromCache) { let path = href;
paintSoftNavDocument(prepareSoftNavHtml(rawHtml, themeNow), href, opts); try {
} else { const u = new URL(href, location.href);
scheduleLiveDataRefresh(); path = u.pathname + u.search + u.hash;
} } catch (_) {}
if (opts && opts.replace) history.replaceState(null, "", path);
else history.pushState(null, "", path);
document.open();
document.write(html);
document.close();
} catch (_) { } catch (_) {
if (!paintedFromCache && token === navToken) location.href = href; if (token === navToken) location.href = href;
} }
} }
@@ -686,18 +394,36 @@
}); });
} }
function purgeLegacySoftNavCache() {
try {
for (let i = localStorage.length - 1; i >= 0; i -= 1) {
const key = localStorage.key(i);
if (!key) continue;
if (
key.startsWith("inst-pc:") ||
key === "inst-page-cache-index" ||
key === "inst-page-cache-days"
) {
localStorage.removeItem(key);
}
}
sessionStorage.removeItem("inst-soft-nav");
sessionStorage.removeItem("inst-cache-revalidate");
} catch (_) {}
}
function boot() { function boot() {
purgeLegacySoftNavCache();
if (isHubLinked()) { if (isHubLinked()) {
apply(get(), { skipStore: true }); apply(get(), { skipStore: true });
window.addEventListener("message", (ev) => initFromHubMessage(ev.data)); window.addEventListener("message", (ev) => initFromHubMessage(ev.data));
initHubEmbedInFrameNav();
try { try {
window.parent.postMessage({ type: "instance-theme-ready" }, "*"); window.parent.postMessage({ type: "instance-theme-ready" }, "*");
} catch (_) {} } catch (_) {}
} else { } else {
apply(getStandalone()); apply(getStandalone());
} }
initInstanceTopNavSoft();
seedCurrentPageCache();
function observeDynamicLists() { function observeDynamicLists() {
["journal-list", "review-list"].forEach((id) => { ["journal-list", "review-list"].forEach((id) => {
@@ -721,20 +447,6 @@
syncInlineStyles(get()); syncInlineStyles(get());
patchHubNavLinks(get()); patchHubNavLinks(get());
observeDynamicLists(); observeDynamicLists();
if (consumeCacheRevalidatePending()) {
scheduleLiveDataRefresh();
}
if (document.getElementById("inst-nav-overlay")) {
dismissNavOverlay();
window.setTimeout(() => {
const el = document.getElementById("inst-nav-overlay");
if (el) {
try {
el.remove();
} catch (_) {}
}
}, 800);
}
}; };
if (document.readyState === "loading") { if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", onReady); document.addEventListener("DOMContentLoaded", onReady);
@@ -765,9 +477,5 @@
mergeHubQueryIntoHref, mergeHubQueryIntoHref,
syncReviewEditButtons, syncReviewEditButtons,
initReviewEditModeSync, initReviewEditModeSync,
setPageCacheDays,
clearPageCache,
getPageCacheMaxAgeMs,
refreshInstanceLiveData,
}; };
})(typeof window !== "undefined" ? window : globalThis); })(typeof window !== "undefined" ? window : globalThis);
-7
View File
@@ -41,10 +41,3 @@ html[data-theme="light"] .stat-item {
background: #fff !important; background: #fff !important;
border-color: #b8c8d8 !important; border-color: #b8c8d8 !important;
} }
#inst-nav-overlay {
position: fixed;
inset: 0;
z-index: 2147483647;
pointer-events: none;
}