Repository navigation
fix(nav): survive a partial Navigation API, and score agent readiness against a real agent browser - #353
Conversation
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
aadhar-sh | 951303d | Aug 12 2026, 10:54 PM |
Wire-size diff vs merge base
Brotli q11, the quality the edge serves. Every number here is deterministic: Client assetsTotal Brotli: 68.07 KiB → 68.08 KiB (+0.01 KiB, +0.0%) across 19 files, 1 changed.
PagesTotal Brotli: 494.19 KiB → 494.22 KiB (+0.04 KiB, +0.0%) across 48 files, 45 changed. No single file moved by 0.13 KiB or more. Plus 45 files under the 0.13 KiB floor, net +0.04 KiB — usually content-hash churn in Worker bundle276.59 KiB → 278.56 KiB gzip (+1.97 KiB, +0.7%) Largest module deltas (raw bytes in bundle):
Dictionary deltas (
|
Folded into
|
… tag CodeQL, on #353: js/bad-tag-filter against httpWords(). An end tag may carry attributes and may hold whitespace, so `</script >` and `</script bar>` close a script element as surely as `</script>` does. A stripper spelled the short way hands the whole body through. Same class and same fix as #347, which closed this across the tree. The closers are `<\/tag\b[^>]*>` now, matching what lens.js already uses. Not cosmetic. The control, on `<p>keep</p><script >var leak=1;</script >tail`: OLD: ["keep","var","leak=1;","tail"] NEW: ["keep","tail"] Those three leaked words land in the HTTP side of the legible-without- JavaScript comparison, so a page carrying an inline script closed with a space reads as MORE legible than it is. A measurement tool that quietly inflates the number it exists to report is worse than one that fails. httpWords is exported and main() now runs behind `import.meta.main`, so the assertion is behavioural instead of a regex-shape match on the source. That matters here specifically: the short spelling LOOKS right, and a source-shape test would have passed on the broken version.
… tag CodeQL, on #353: js/bad-tag-filter against httpWords(). An end tag may carry attributes and may hold whitespace, so `</script >` and `</script bar>` close a script element as surely as `</script>` does. A stripper spelled the short way hands the whole body through. Same class and same fix as #347, which closed this across the tree. The closers are `<\/tag\b[^>]*>` now, matching what lens.js already uses. Not cosmetic. The control, on `<p>keep</p><script >var leak=1;</script >tail`: OLD: ["keep","var","leak=1;","tail"] NEW: ["keep","tail"] Those three leaked words land in the HTTP side of the legible-without- JavaScript comparison, so a page carrying an inline script closed with a space reads as MORE legible than it is. A measurement tool that quietly inflates the number it exists to report is worse than one that fails. httpWords is exported and main() now runs behind `import.meta.main`, so the assertion is behavioural instead of a regex-shape match on the source. That matters here specifically: the short spelling LOOKS right, and a source-shape test would have passed on the broken version.
7e023e5 to
a485f97
Compare
Kitesurf, Cloudflare's agent browser, ships a `navigation` stub carrying
entries() and currentEntry and nothing else. No canGoBack, no
addEventListener. Every `if (window.navigation)` guard in the history-nav
block therefore passed, and the listener line threw:
TypeError: navigation.addEventListener is not a function
It throws inside a requestAnimationFrame pass, so it takes out the rest of
that function, which is where the maximize/restore control gets wired.
Measured 2026-08-12 against the public Kitesurf CDP: the control was dead on
all six pages tested (/, /writing, /terminal, /lens, /coffee, /garage) and a
Chrome control wired it on all six. Verified the fix by reproducing the stub
in Chrome and A/B-ing the two files against the same page: production nav.js
gives maximize=DEAD plus the TypeError, this one gives maximize=WIRED and a
clean console.
Asking for the member each branch actually uses means a partial
implementation degrades to the history fallback rather than crashing past it.
Every agent check here reads a declaration. The route oracle sweeps routes, gen-mcp-cards diffs a card against tools/list, build.mjs counts the Markdown twins. All of them were green on 2026-08-12 while an agent browser was getting a homepage with 12 blank squares and a script that threw on every page, because the defect is in what an ENGINE does with bytes that are themselves correct. `pnpm run agent:check` drives Cloudflare's Kitesurf over the routes and scores what comes back. It is free: the public playground at kitesurf.cloudflare.app takes no account and no token, so it spends none of the 10 browser-minutes a day that /lens/shot and /lens/browser share. The same sweep through the account binding would black out the browser lenses on its first run. lib/agent-rubric.js is the scoring, kept pure for the reason lib/tui.js is pure: /lens will score arbitrary URLs out of the evidence its machine tabs already collect, and one definition has to answer a Node script, an HTTP response and `node --test` without caring which is asking. Two rules make the number defensible, both pinned in contract tests. A dimension nobody measured is unmeasured rather than zero, so max shrinks instead of the score becoming an accusation the scored site cannot answer. And the browser dimension carries the heaviest weight at 25, because the declared-surfaces layer everyone builds first scored full marks on the same run that turned up both real defects. --control is the other half. gotcha 15 and gotcha 33 are both the same lesson: an instrument reporting a browser feature as missing is usually broken itself, so a Kitesurf-only defect means nothing until the identical script passes in Chrome. Without the flag the report says the control did not run rather than implying one did. Not a CI gate. It reaches a third-party playground on every run, so as a required check it would redden PRs on somebody else's outage.
…ription The rubric's own first bug, and it defamed the site it was written to check. It counted images with non-empty alt against ALL images, so /garage scored 1/10 for describability when 14 of its 16 images are taskbar sprites carrying a deliberate alt="", which is the right markup for a decorative icon. The homepage scored 5/10 on the same mistake. Three states, not two. A non-empty alt describes, an alt="" is a decision that the image owes no description, and a missing alt attribute is the only omission. Decorative images leave both sides of the ratio, which is the whole point of writing one. A page whose images are all decorative is now unmeasured on this dimension rather than failed. After the repair, measured against production: / scores 12/12 content images described with 14 decorative, /garage 2/2 with 14 decorative. Both 10/10. Also probes the Markdown twin per route, since it is the one discovery surface that is not site-wide. That immediately found four of the eight sampled routes serving no twin: /writing, /lens, /coffee and /around.
…eclares /lens has scored agent readiness since it was built, across twenty checks in five categories. Every one of them is a declaration audit: does robots.txt exist, does the sitemap parse, does the MCP server card have the right shape. That layer was perfect on aadhar.sh on 2026-08-12, on a day an agent browser was getting a homepage with twelve blank squares and a script that threw on every page. A declaration cannot catch either defect, because both live in what an ENGINE does with bytes that are themselves correct. So this adds a sixth category, Execution, with two checks a declaration structurally cannot answer: agentScripts did the page's own JavaScript survive the engine? agentMedia could the engine decode the images the page served? Both ride the CDP session /lens/wire already opens, so they cost no extra browser instance and no extra minute of the 10-a-day account ceiling. Runtime and Log are enabled before the navigation, because an exception thrown during load is the interesting case, and the DOM census runs after the settle window because an image that has not finished loading yet reports naturalWidth 0 and is not broken. NEUTRAL UNTIL MEASURED is the load-bearing rule, and it is deliberately the opposite of how `unknown` is treated everywhere else in this rubric. An unknown declared check is the SITE's fact: we asked and it did not answer. An unmeasured execution check is OURS, and docking a stranger's score because our render budget was spent would make the number dishonest. Verified through the real scorer: an unrendered scan leaves `counted` at 15 and renders the category as skipped in the existing grid, a render takes it to 17. lib/agent-execution.js holds the checks and the in-page probe so the rubric and `pnpm run agent:check` cannot drift into two definitions of one claim. The CLI now publishes no overall score of its own for the same reason. Two things deliberately NOT built, both because the surface already exists. No standalone rubric module: /lens owns the score. No header band above the panes: verdictStrip() already renders a score that opens Agent-ready?, and a third copy of a number on one screen is worse than none. The honesty the band was for lands anyway, since the client already draws a zero-total category as skipped and both checks state why they are neutral.
… tag CodeQL, on #353: js/bad-tag-filter against httpWords(). An end tag may carry attributes and may hold whitespace, so `</script >` and `</script bar>` close a script element as surely as `</script>` does. A stripper spelled the short way hands the whole body through. Same class and same fix as #347, which closed this across the tree. The closers are `<\/tag\b[^>]*>` now, matching what lens.js already uses. Not cosmetic. The control, on `<p>keep</p><script >var leak=1;</script >tail`: OLD: ["keep","var","leak=1;","tail"] NEW: ["keep","tail"] Those three leaked words land in the HTTP side of the legible-without- JavaScript comparison, so a page carrying an inline script closed with a space reads as MORE legible than it is. A measurement tool that quietly inflates the number it exists to report is worse than one that fails. httpWords is exported and main() now runs behind `import.meta.main`, so the assertion is behavioural instead of a regex-shape match on the source. That matters here specifically: the short spelling LOOKS right, and a source-shape test would have passed on the broken version.
a485f97 to
951303d
Compare
Cloudflare's Kitesurf is the browser an agent actually runs when it drives Cloudflare's stack. Pointing it at aadhar.sh found two defects that every existing check was structurally unable to see, because both live in what an ENGINE does with bytes that are themselves correct.
The public playground at
kitesurf.cloudflare.apptakes no account and no token, so all of this costs zero of the 10 browser-minutes a day that/lens/shotand/lens/browsershare.The two defects
nav.jsthrew on every page. Kitesurf ships anavigationstub carryingentries()andcurrentEntryand nothing else: nocanGoBack, noaddEventListener. Everyif (window.navigation)guard passed and the listener line threw, inside a rAF, taking out the rest of that function. The maximize control was dead on all six pages tested and wired on all six in a Chrome control.Verified by reproducing the stub in Chrome and A/B-ing the two files against the same page:
12 of 12 photo tiles fail to decode.
/photos/grid.htmlserves a bare<img src="/i/<stem>-400.<hash>.avif">with no fallback and Kitesurf cannot decode AVIF. The requests return 200, so this is a decode gap. Not fixed here, deliberately:lib/photo-grid.jsdropped<picture>on 2026-08-11 on a measured premise ("every browser this site targets decodes AVIF") and every tile carries real alt text, so a text-reading agent loses nothing while a screenshotting one loses the grid. That tradeoff is the owner's call, not this PR's.pnpm run agent:checkDrives Kitesurf over the routes and scores what comes back.
--allfor every manifest page,--controlto run the identical script in Chrome,--jsonfor machine output.--controlis not decoration. Gotcha 15 and gotcha 33 are the same lesson: an instrument reporting a browser feature as missing is usually broken itself. A Kitesurf-only defect means nothing until Chrome passes, and without the flag the report says the control did not run rather than implying one did.Not a CI gate. It reaches a third-party playground on every run, so as a required check it would redden PRs on somebody else's outage. Workstation control, same idiom as
kitesurf:checkandbun:check.The rubric
lib/agent-rubric.jsis pure for the reasonlib/tui.jsis pure:/lenswill score arbitrary URLs out of the evidence its machine tabs already collect, and one definition has to answer a Node script, an HTTP response andnode --testwithout caring which is asking.Two rules keep the number defensible, both pinned in contract tests:
maxshrinks instead of a missing probe becoming an accusation the scored site cannot answer.The rubric's own first bug is worth reading in the diff: it counted non-empty alt against ALL images, scoring
/garage1/10 when 14 of its 16 images are taskbar sprites carrying a deliberatealt="". Three states now, and an all-decorative page is unmeasured rather than failed.Measured against production
//garage/writing/lens/coffee/aroundEvery declared surface answered 200 throughout: llms.txt, agent card, both MCP cards, sitemap,
tools/list. That layer was already perfect and was not the problem.247/247 contract tests, typecheck clean.