Repository navigation
routine 2026-10-10: main green again (restored scroll is a jump), a11y pass measures a page at rest, the visitor Cloudflare saw in the log, Nuxt 4.6.1 - #21
Merged
Conversation
Every clearnet request line in 21 hours of live log carried `"ip": "172.18.0.1"` (5,548 of 5,555): Caddy trusts no proxy, so the x-forwarded-for it sets is its own peer, and behind Docker's port proxy that is the bridge gateway. Only LAN visits, which skip Cloudflare, ever showed a real address. The request log now also writes `cfIp`, from `cf-connecting-ip`, which Caddy passes through. It is logged under its own name because nothing verifies the request really came through Cloudflare; nothing in the site decides anything from it. A request over the onion gateway logs neither address. - shared/log/client.ts: clientAddress() - server/plugins/request-log.ts: uses it on both the request and error line - test/client-address.test.ts: 4 cases - README.md: the log fields Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The site scrolls smoothly (`scroll-behavior: smooth`), so the reading pass's bare `scrollTo` calls only started animations. Measured at 390 px: the pass stalled a screen or so down (Chromium, onyums: 1,014 of 3,894 px by its last step), and the jump back to the top was still ~1,700 px out when axe ran. axe then judged a page in motion, with links passing under the fixed masthead: `target-size` failed /status's /sitemap.xml link as "partially obscured" on one Firefox run of four tonight, and passed the other three. Every jump is now `behavior: 'instant'`, and the pass throws if the page is not at the top when axe starts. Probed in both engines: the pass now reaches the bottom (3,894 / 3,968 px) and is at 0 when axe runs. a11y:browser and a11y:firefox clean, twice each. - scripts/check-a11y-browser.mjs Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Nuxt 4.6.1 after 27 hours with two issues filed against it, neither touching this site (#36529 reproduces on 3.x; #36530 is the cloudflare_module preset). Mostly dev-server and typing fixes plus a lazy portable-event view in Nitro. The lockfile also moved cssnano 9.3 -> 9.6; the built CSS is byte-identical (same hash). Checks on the upgraded tree: typecheck clean, 273 tests, build green, all 42 verify routes identical to 4.6.0's with every page body the same size, check (22 pages, 79 URLs), a11y (23 pages), a11y:browser, a11y:firefox, check:navigation 144/144 in Chromium and in Firefox, audit:runtime, font:check, og:check, sizes:check, contrast, bases. - package.json, package-lock.json - ROADMAP.md: the 4.6.1 item Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
main's CI went red after #20 merged on one check:navigation case: a reload of onyums' README at 390 px landed at 5,425 against the 5,418 the reader left, outside the 4 px slack. Reproduced locally at +2 px on about one reload in three. The site's own scrollTo lands exactly; the extra pixels arrive in the next frame with no script scrolling (every scrolling API hooked), page height and README offset unchanged, and with scrollRestoration manual or overflow-anchor: none alike, so it is the browser's own. The slack is now 12 px, half a 23 px line; the bug this check guards against was 3,300 px. check:navigation 144/144 in Chromium and in Firefox on this build. - scripts/check-navigation.mjs - ROADMAP.md: this and the a11y scroll fix Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…he edge (owner) Research from tonight: Cloudflare's header reference, Docker's port-publishing notes on the userland proxy, Caddy's trusted_proxies and client_ip_headers. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
npm run sync, 2026-10-11 03:3x UTC: all 8 projects; Nanna's v0.3.42-beta.51, WeftDB's README and pushedAt, counts. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The real cause of main's red check:navigation, which 9cba524 only widened the slack around: the PR's own CI then landed a reload of Skidbladnir's README 21 px past the reader's place. vue-router restores a saved position (Back, Forward, and a reload from history.state.scroll) with a bare scrollTo({ left, top }), so the site's `scroll-behavior: smooth` made each an animation from the top, racing the README fold's own instant restore. Fast machines won the race; slow ones did not. Reproduced with the CPU slowed 6x: all 16 reload cases failed, 7-34 px past. app/router.options.ts keeps Nuxt's scrollBehavior (reached by path, it is not exported, so a move breaks the build loudly) and marks a saved position `instant`, as a browser's own restoration is. Hash links still scroll smoothly. A client plugin could not do this: Nuxt's router plugin resets router.options.scrollBehavior after the first navigation. - app/router.options.ts (new) - scripts/check-navigation.mjs: slack back to 4 px; a failed reload check prints a frame-by-frame trace of the page's first seconds (scroll calls, position, height, README offset, font state, shifts), which is how this was found - app/composables/useReadmeFold.ts: comment, vue-router does restore on reload - README.md, ROADMAP.md Checks: typecheck, 273 tests, build; 42 routes identical; check, a11y, a11y:browser, a11y:firefox clean; check:navigation 144/144 in Chromium and Firefox at 4 px, and 144/144 in Chromium with the CPU slowed 6x at a 1 px slack (16 failures before this change). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Contributor
Author
|
Shipped. Squash-merged as 25a8a15; CI green. Release: Deploy: the old image was tagged Post-deploy gate on the live site:
The onion gateway reported No rollback was needed. 🤖 Generated with Claude Code |
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.
Nightly run, 2026-10-10 21:10 → 2026-10-11 00:05 CDT. Seven commits. The night's theme is trustworthy checks and logs.
mainhas been red since #20 merged:check:navigationfailed by 3 px on the push run. Tonight's own gate run also turned up a second flaky check, and the live log turned out to be naming Docker's gateway instead of the visitor.Unless noted, each increment was verified on its own build:
nuxt typecheckclean, Vitest,npm run build.verifyhits returned identical status codes before and after (/healthz200,/200,/projects200, every/projects/<slug>301 with/aboutand/blog200, deliberate 404s 404, admin 404s,/onion-frame503 with no gateway).1. The log names the visitor Cloudflare saw —
feat(log)(a26d715)Roadmap: found in the live log (new
[x], plus an owner[ ]).Files:
shared/log/client.ts(new)server/plugins/request-log.tstest/client-address.test.ts(new, 4 cases)README.md,ROADMAP.mdThe problem: 5,548 of 5,555 request lines in the container's 21 hours of log carry
"ip": "172.18.0.1". Caddy trusts no proxy, so thex-forwarded-forit sets is its own peer, and that peer is Docker's bridge gateway. The site is proxied through Cloudflare (server: cloudflare,cf-rayon the public address).The change: every line now also carries
cfIp, fromcf-connecting-ip, which Caddy passes through. It is logged under its own name because nothing checks that a request came through Cloudflare, and nothing in the site acts on it. Over Tor, neither address is logged.Checked on the built server:
x-forwarded-for: 172.18.0.1+cf-connecting-ip: 203.0.113.7→"ip":"172.18.0.1","cfIp":"203.0.113.7", on both the 200 line and the 404 line.x-via-onion+ a forgedcf-connecting-ip→"ip":null,"cfIp":null,"via":"onion".2. The browser a11y pass measures a page at rest —
fix(checks)(fa732a7)Files:
scripts/check-a11y-browser.mjs,ROADMAP.md.Found when tonight's first
a11y:firefoxrun failedtarget-sizeon/status's/sitemap.xmllink ("partially obscured", 88.6×21 px). The next three runs passed.Cause: the site has
scroll-behavior: smooth, so the pass's barescrollTocalls only start animations. Probed at 390 px:scrollTo(0, 0)was still 1,696 px down right before axe ran.So axe was measuring links while they slid under the fixed masthead.
Fix: every jump is
behavior: 'instant', and the pass throws if the page isn't at the top when axe starts.After: the pass reaches the bottom (3,894 / 3,968 px) and is at 0 when axe runs.
a11y:browseranda11y:firefoxare clean, twice each.3. Nuxt 4.6.1, vue-tsc 3.3.13, @types/node 26.6.5 —
chore(deps)(a7d2d88)Roadmap: the Nuxt 4.6.1 item, ticked. Files:
package.json,package-lock.json,ROADMAP.md.Soak: 27 h. Only two issues were filed against 4.6.1, both checked through the issues API (search was lagging):
cloudflare_modulepreset, now closed.A 4.6.2 release PR is open with nitro/vite fixes that don't touch this site.
Lockfile: cssnano also moved 9.3 → 9.6. The built CSS is byte-identical (
entry.DpTKoio1.css, same hash both builds).Checks:
check,a11y,a11y:browser,a11y:firefox(after increment 2),audit:runtime,font:check,og:check,sizes:check,contrast,basesall clean.check:navigation: 144/144 in each engine.4.
maingreen again: a restored scroll position is a jump, not an animation —fix(checks)(9cba524), thenfix(scroll)(5e60bd1)Files:
app/router.options.ts(new)scripts/check-navigation.mjsapp/composables/useReadmeFold.ts(comment only)README.md,ROADMAP.mdWhat failed: main's push run after #20 failed one
check:navigationcase: a reload of onyums' README at 390 px landed at 5,425 against the 5,418 left, outside the ±4 px slack.First attempt, wrong: I couldn't find the cause locally (+2 px at most). 9cba524 widened the slack to 12 px. This PR's first CI run then failed by 21 px on Skidbladnir's desktop reload. The slack was hiding a real bug, not noise.
Root cause. I added a frame-by-frame trace to the reload check and slowed the CPU 6× to match the runner. The trace showed what happens:
history.state.scroll) with a barescrollTo({ left: 0, top: 6537 }).scroll-behavior: smooth, that becomes an animation from the top.At 6× slowdown, all 16 reload cases failed, 7–34 px past.
Fix:
app/router.options.tskeeps Nuxt's ownscrollBehaviorand marks a saved positioninstant, the way a browser restores a page. Hash links still scroll smoothly.router.options.scrollBehaviorafter the first navigation. I tried it first; the trace showed the wrapper gone.The slack is back to 4 px. A failed reload check now prints the trace.
Result:
Roadmap/README-only commits
83dc303:
cfIpitem.[ ]: a verified client address at the edge (details under Research).2e3cd52, snapshot:
npm run synccovered all 8 projects, with Nanna v0.3.42-beta.51 and WeftDB's README. All 28 README images in it are sized.Final tree (5e60bd1)
check(22 pages, 79 URLs),a11y(23),a11y:browseranda11y:firefox(23 × 2 widths) clean.check:navigation144/144 in Chromium and in Firefox.audit:runtime,font:check,og:check,sizes:check,contrast,basespass.Live, before this deploy:
npm run vitals -- https://basicautomation.io(phone): every page measured was "good", CLS 0.000, LCP 1,068–2,496 ms.Research
CF-Connecting-IPcarries the client's address to the origin, and Cloudflare recommends it overX-Forwarded-For. It can be spoofed by anyone who reaches the origin directly. https://developers.cloudflare.com/fundamentals/reference/http-headers/caddy-shared-networkis IPv4-only, which would explain172.18.0.1. Unconfirmed: whether Cloudflare reaches the origin over IPv6 is in the Cloudflare dashboard. https://docs.docker.com/engine/network/port-publishing/trusted_proxies(none trusted by default) andclient_ip_headersare how the real visitor would reach{client_ip}once the real peer is visible. https://caddyserver.com/docs/caddyfile/optionsnpm audit: the same dev-time advisories as last night, none shipped (audit:runtimeclean).Done vs open
main's red CI: root cause found and fixed (increment 4), CI green on this PR.[ ]: a verified client address at the edge (owner: Caddy/Docker/Cloudflare).security.txtcontact, distroless/healthcheck, Onion-Location, HSTS,basic-automation.github.io, contrast, and the cause of the onion outage.reachable.node .output/server/index.mjson port 3124 from another session's.worktrees/site-mainworktree was left alone.STOP REASON: no workable next item, at 00:05 CDT, 2 h 55 min after the 21:10 start, short of the 4 h bar. Every remaining roadmap item is owner-gated, upstream in repos this routine doesn't push to, or blocked. I looked for more: the live log, CI history, a vitals sweep of the live site,
npm audit, TODOs, and a screenshot tour of seven more pages. That search found increments 1, 2 and 4, and increment 4 took a second round when CI disproved my first fix. Nothing more turned up.Recommended next step
trusted_proxies.security.txtcontact.🤖 Generated with Claude Code