Skip to content

feat(checks): browser:check reads every page the way a visitor's browser does - #1650

Merged
oddharsh merged 2 commits into
mainfrom
claude/kind-keller-ps8ge9
Nov 10, 2026
Merged

oddharsh merged 2 commits into
mainfrom
claude/kind-keller-ps8ge9

Conversation

@oddharsh

@oddharsh oddharsh commented Nov 10, 2026 •

Copy link
Copy Markdown
Owner

#1635 and #1647 fixed what a hand crawl found, a page at a time. This keeps the crawl and fixes everything it flags today.

What changed

bun run browser:check (tools/check-browser.ts) opens all 76 manifest pages in Chrome and reads four things on each:

reading what fails it
scroll a scroller wider than its box at 320, 360 or 390px (gotcha 97)
shift cumulative layout shift over 0.01 at 1280 or 390px, under 4x CPU throttle (gotcha 98, new)
intl any Intl call before the reader acts (gotcha 92)
error an uncaught exception during load

It replaces phone:check, which was the first of the four, and takes over its non-blocking CI job (now browser check). config/browser-budget.json holds four exceptions, each with its reason. An entry a page no longer needs is itself a failure, so the file can only shrink. --base <url> reads a deployment in place of the local build.

What --base https://aadhar.sh found that a local run can't. /around and /ledger scroll sideways in production at every phone width (103px and 3px out at 390px). Both passed locally, where the harness's empty KV leaves their tables empty. Each table scrolls in its own box now.

Everything else it flagged, fixed here:

pages fix
scroll at 320px, 3 to 37px /restore, /lens, /garage/blueprint, compression, dyno, masonry, octane min-width: 0 on a range input, tighter padding or wrapping under 340px
shift 0.086 (0.192 at 390px in production) /garage/round-trip the frame holds the window's height while the Markdown loads; the demo's output box holds its final height
shift 0.021, in 4 of 9 loads /access the empty header strip above the graph reserves its aspect ratio
Intl at load /restore names months and weekdays itself

Why

Each defect class was invisible on a fast machine with a wide window, and each came back to "nobody opened the page the way a visitor does". The two production-only findings are the sharper version: a check with the wrong data passes.

Understanding check

  • Intended model: a visitor-facing defect that a browser can measure fails the PR that introduces it. A reading is trusted only if its planted defect was caught in the same run.
  • Prediction: revert any one of this PR's page fixes and browser:check names that page and reading. Add a budget entry for a clean page and it reports the entry as stale.
  • Likely misconception or failure mode:
    • A green local run means production is clean. It doesn't for data-backed pages, which is why --base exists and why three budget entries say "local fallback only".
    • Layout shift is deterministic. With three pages loading at once a paint can land early: /garage/pretext read 0.056 in one sweep and 0.005 in six loads alone. A reading over the limit is taken a second time and judged on the lower one; a script that moves content after paint does it on every load, so it still fails.
    • A machine-dependent shift can be budgeted. It can't. I gave /access a 0.03 budget because it read 0.021 here; the first CI run read 0.000 there and called the budget stale. The page is fixed instead (second commit), and a budget now counts as stale only under half the limit, so a page on the line can't flap.
  • Evidence:
    • Controls, every run: a 600px block (scroll, at each width), a 300px block pushed in above the content (shift), a formatted number and a built formatter (intl), a thrown error (error). A miss exits 2.
    • With the page fixes stashed and a stale budget planted: 10 failures and 2 stale entries. With them: 0 and 0.
    • On the GitHub runner (first CI run): every control caught, 0 failures across 76 pages, sweep in 2m25s. Its one complaint was the stale /access budget above.
    • The table wrappers, applied to the live /around and /ledger DOM at 360px: main overflow 133px to 0 and 33px to 0, with the box scrolling.
    • /restore's hand-built dates match toLocaleDateString("en-US") for all 131 restore points. /access reads 0.003 at most in 12 loads.
  • Remaining uncertainty:

Validation

  • bun run browser:check: 76 pages, 0 failures, 0 stale budgets, every control caught.
  • bun run lint, format:check, typecheck, derive:check, pages:check, check-wrangler: pass.
  • bun run routes:check: 380 routes, 0 hard failures.
  • bun run test: 1777 pass, 0 fail (Node 26). cal tests pass.
  • Touches ci.yml: the phone-layout job becomes browser-check, still non-required with continue-on-error. Three contract tests that name the job and the node script follow.
  • No /a/ URL moves.

🤖 Generated with Claude Code

https://claude.ai/code/session_01UnguFuTGB1AyfrTeixtbCT

…ser does

Sideways scroll, layout shift and early Intl were each found by hand on
2026-11-10, a page at a time. browser:check is that crawl, kept: it opens
all 76 manifest pages in Chrome and reads four things on each.

  scroll  no scroller wider than its box at 320, 360 and 390px
  shift   cumulative layout shift at 1280 and 390px, under 4x CPU throttle
  intl    no Intl call before the reader acts
  error   no uncaught exception during load

It replaces phone:check, which was the first of the four, and takes its
CI job. Before each sweep it plants a 600px block, a 300px block pushed
in above the content, a formatted number, a built formatter and a thrown
error, and exits 2 if any goes unseen. config/browser-budget.json holds
five exceptions with their reasons, and an entry a page no longer needs
is a failure, so the file can only shrink. --base reads a deployment.

That last flag found what a local run cannot. With real rows /around and
/ledger scrolled sideways in production at every phone width, 103px and
3px out at 390px, while both passed locally on empty tables. Each table
scrolls in its own box now; wrapped that way on the live pages, both read
0 at 360px.

The rest of what it flagged is fixed here too:
- 320px: /restore, /lens and five garage pages overflowed by 3 to 37px.
- /garage/round-trip shifted 0.086 (0.192 at 390px in production). Its
  frame holds the window's height while the Markdown loads and the demo's
  output box holds its final height.
- /restore names months and weekdays itself; the text matches en-US for
  all 131 restore points.

With these fixes stashed and a stale budget planted, the check reports 10
failures and 2 stale entries. With them, 0 and 0. Gotcha 98 records the
shift trap; 92 and 97 name the new check.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UnguFuTGB1AyfrTeixtbCT
@github-actions

github-actions Bot commented Nov 10, 2026 •

Copy link
Copy Markdown
Contributor

Wire-size diff vs merge base

7fdfd6cfb38ab63b3f82ab9a30c0c4a516b58b34 → 9ca5306685ca89ae71d0cb31ff0c35df6ce52802

Brotli q11, the quality the edge serves. Every number here is deterministic:
an unchanged file produces no row. Advisory — this check fails on nothing.

Client assets

No change. 40 files, 245.02 KiB Brotli total.

Pages

Total Brotli: 718.80 KiB → 718.95 KiB (+0.15 KiB, +0.0%) across 77 files, 11 changed.

No single file moved by 0.13 KiB or more.

Plus 11 files under the 0.13 KiB floor, net +0.15 KiB — usually content-hash churn in /a/ references, counted in the total above.

Worker bundle

299.34 KiB → 299.50 KiB gzip (+0.16 KiB, +0.1%)

Largest module deltas (raw bytes in bundle):

file base head Δ
.build/src/worker/around.ts 15.04 KiB 15.10 KiB +0.06 KiB (+0.4%)
.build/src/worker/ledger.ts 13.43 KiB 13.49 KiB +0.05 KiB (+0.4%)
.build/src/worker/lens.ts 134.08 KiB 134.13 KiB +0.05 KiB (+0.0%)
.build/src/worker/dyno.ts 8.84 KiB 8.89 KiB +0.05 KiB (+0.5%)
.build/src/worker/updates.ts 12.64 KiB 12.68 KiB +0.04 KiB (+0.3%)

Dictionary transport

Family dictionary acquisition: no change, 14.50 KiB q11.

pd/: 446 → 457 deltas, 525.78 KiB → 529.44 KiB (+3.66 KiB).

Head tags vs merge base

No change. 77 documents, every title, description, canonical, robots, og:*, twitter:*, alternate, JSON-LD type and oracle status identical.

…opping 21px

browser:check's first CI run read /access 0.000 and called its 0.03
shift budget stale, where 4 of 9 loads in the sandbox read 0.021. The
shift depended on whether paint beat the script at the end of the page:
that script fills an empty header strip above the graph with an SVG
1002 by 26, scaled to the pane, and the graph below dropped by its
height. The strip reserves that aspect ratio while empty now, and 12
loads read 0.003 at most. The budget entry goes.

The stale rule fires under half the limit, so a page sitting on the
line (/lens/census, 0.011 against 0.01) cannot flap between failing and
stale.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UnguFuTGB1AyfrTeixtbCT
@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
aadhar-sh 1ae47ff Commit Preview URL

Branch Preview URL
Nov 10 2026, 08:02 PM

@oddharsh
oddharsh merged commit ec3ac49 into main Nov 10, 2026
10 checks passed
@oddharsh
oddharsh deleted the claude/kind-keller-ps8ge9 branch November 10, 2026 21:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants