Harden Navigation API interception in the content loader - #116
Draft
philipwalton wants to merge 1 commit into
Draft
Harden Navigation API interception in the content loader#116philipwalton wants to merge 1 commit into
philipwalton wants to merge 1 commit into
Conversation
Add the standard guards to the navigate event handler: bail when the navigation can't be intercepted, is a same-page fragment navigation, will trigger a download, or is a form submission. Also replace the fragile file-extension blacklist with a positive check that only intercepts directory-style paths ending in a slash, which is true of every SPA-navigable page on the site. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This was referenced Jul 13, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
The
navigateevent handler insrc/javascript/content-loader.ts(init()) lacked the standard Navigation API guards:event.canInterceptcheck — callingintercept()on an uninterceptable navigation throws.event.hashChangecheck — same-page fragment navigations (e.g. clicking a footnote or heading anchor) were intercepted and re-fetched the entire page partial pointlessly.event.downloadRequestcheck — download-triggering links shouldn't be intercepted.event.formDatacheck — no forms exist today, but the guard is standard and free.url.pathname.match(/\.(png|svg|webp)$/)was a fragile blacklist that missed.jpg,.gif,.pdf,.xml,/site.webmanifest, etc.Root cause
The handler was written before/without the standard guard checklist for
navigateinterception, and the resource check enumerated file extensions instead of positively identifying SPA-navigable pages.What changed
src/javascript/content-loader.ts: early-return when!event.canIntercept, whenevent.hashChange, whenevent.downloadRequest !== null, and whenevent.formDatais truthy. Replaced the extension blacklist with a positive check: only intercept whenurl.pathname.endsWith('/'), since every SPA-navigable page on this site has a directory-style path (the partial loader appendsindex.prtl). Anything else falls back to a normal full-page navigation, which is always safe. Verified againstdist/that every built page is a directory-styleindex.html(the only exceptions are404.htmland the__reset/__blanktest helpers, none of which are SPA-navigation targets).src/javascript/types.d.ts: addedcanIntercept,hashChange,downloadRequest, andformDatato the hand-rolledNavigateEventinterface with spec-correct types.test/e2e/content-loading.ts: new test "should not re-fetch content for same-page fragment navigations" — SPA-navigates to an article, clicks a heading anchor, and asserts (via resource-timing entries) that no additionalindex.prtlfetch was issued and no full page load occurred. This test fails onmain(the fragment click re-fetches the partial) and passes with this change.Verification
npm run lint— pass (0 errors)npm run types:check— passnpm run test:unit— pass (16 files, 67 tests)npm run build— passnpm test(full e2e) — results:code-highlighting,worker, andlogspecs: all pass.homepage.ts"should contain working links to all published articles": known pre-existing failure (atom.xml sort-order bug, fixed separately infix/atom-feed-order). Tolerated per instructions.content-loading.ts"should not attempt to load non-HTML content": fails, but this is pre-existing and unrelated — it fails identically (3/3 runs) on unmodifiedorigin/mainin the same environment. The test picksarticles[28]fromdist/atom.xml; the same sort-order bug shifts the index to an article with nofigure img. I verified that once the feed order is fixed,articles[28]resolves to/articles/the-ga-setup-i-use-on-every-site-i-build/(which has 8 figure images), and I confirmed by temporarily pointing the test at that article that it passes 3/3 with this change — i.e. clicking an image link to a.webpURL correctly falls through to a full-page navigation under the new trailing-slash check.content-loading.ts"should show an error if the content cannot be loaded": pre-existing intermittent flake (observed failing 1/3 on unmodifiedorigin/mainand passing 3/3 with this change in other runs); its automatic retry in full-suite runs is often cancelled becausebail: 1triggers once the known homepage failure exhausts its retries.Codex review
Verdict: REQUEST_CHANGES, with one finding I'm overruling as non-blocking (recorded here per process):
event.formDatais only non-null for POST form submissions, so a same-origin GET form targeting a/-terminated action would still be intercepted (and its query string dropped by the pathname-based partial fetch). This is technically correct per spec, but the site has no forms (the only<form>in the repo is inside an article's code sample), this matches the standard documented guard pattern, and query strings being ignored by the partial loader is pre-existing behavior that equally affects plain links. If forms are ever added, asourceElement-based check would be worth revisiting.getPartialFetchCounthelper matches any resource name containingindex.prtl(could parse the URL and match same-origin/index.prtlpaths); no GET-form regression test (moot while no forms exist).Please scrutinize
/. If a page is ever added with a non-directory path, it will silently get full-page navigations instead of SPA loads (safe, but not SPA). Please confirm this invariant is one you're happy to commit to.url.origin !== location.origincheck was kept (canIntercept already excludes cross-origin) to keep the diff minimal.🤖 Generated with Claude Code