Nav hardening: app-safe deep-link Back + stale-load guard on Today

Per Codex audit follow-ups:
* Track in-app navigation depth (forward goto/link increments, popstate
  unwinds, clamped at 0) and base the in-page Back on it instead of
  history.length. A direct deep link (email/social/article) now sends the
  in-page Back to Highlights rather than out of the site.
* Apply the same stale-load guard to the Today/Highlights path that feed views
  have, and only scroll-to-top when the load is still current — avoids stale
  error/scroll state during quick navigation.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
jay
2026-06-08 09:11:24 -04:00
parent dc245ab6ea
commit eb91a2f856
+12 -3
View File
@@ -236,9 +236,13 @@
if (source) sourceNames = { ...sourceNames, [source.id]: source.name };
navigate(key);
}
// How many forward in-app navigations deep we are from the landing entry, so
// the in-page Back stays INSIDE the app even when someone deep-links in from
// email/social/an article (history.length can't tell us that).
let appNavDepth = 0;
function goBack() {
if (typeof history !== 'undefined' && history.length > 1) history.back();
else goto(urlForView('today'));
if (appNavDepth > 0 && typeof history !== 'undefined') history.back();
else goto(urlForView('today')); // landed here directly → app-safe Highlights
}
// Load the data for a view. Called by afterNavigate (URL-driven: in-app goto,
@@ -251,6 +255,7 @@
try {
if (key === 'today') {
await loadToday(fresh);
if (seq !== loadSeq) return; // a newer navigation superseded this one
} else {
const items = (await getJSON(feedUrl(key, 0))).items;
if (seq !== loadSeq) return;
@@ -264,13 +269,17 @@
} catch (e) {
if (seq === loadSeq) error = 'Something went quiet — could not reach the feed.';
}
if (typeof window !== 'undefined') window.scrollTo({ top: 0, behavior: 'smooth' });
if (seq === loadSeq && typeof window !== 'undefined') window.scrollTo({ top: 0, behavior: 'smooth' });
}
// Every URL change (in-app goto, browser back/forward, link) reloads the view.
// The initial 'enter' is handled by onMount, after its data deps are fetched.
afterNavigate((nav) => {
if (nav.type === 'enter') return;
// Forward in-app nav deepens the stack; back/forward (popstate) unwinds it.
// Clamped at 0, so an out-of-app landing keeps Back app-safe (→ Highlights).
if (nav.type === 'goto' || nav.type === 'link') appNavDepth += 1;
else if (nav.type === 'popstate') appNavDepth = Math.max(0, appNavDepth - 1);
loadView(selected);
});