Skip to content

routine 2026-10-09: the folded README in both engines — Back, reload and heading links that land, Firefox checks, sized README images, no dead buttons without script, vue-router 5.4 - #20

Merged
physics515 merged 17 commits into
mainfrom
routine/site-2026-10-09
Oct 10, 2026
Merged

physics515 merged 17 commits into
mainfrom
routine/site-2026-10-09

Conversation

@physics515

@physics515 physics515 commented Oct 10, 2026 •

Copy link
Copy Markdown
Contributor

Nightly run, 2026-10-09 21:10 → 2026-10-10 00:10 CDT. Seventeen commits. Theme: the about page's folded README, in both engines — tonight's vue-router check turned up real bugs in how a visitor's own navigation treats it, and Firefox (Tor Browser's engine) turned up more. Every browser check here was Chromium-only until tonight.

Unless noted, each increment was verified on its own build with: nuxt typecheck clean, Vitest, npm run build, the real server on a spare port, and all 42 routes the verify script hits (/healthz, /, /projects, every /projects/<slug> 301 and its /about and /blog 200, deliberate 404s, admin 404s) identical to main's status codes. The server ran with the fixture posts (CONTENT_DIR=test/fixtures/content) for check, a11y and a11y:browser, and headless-Chromium screenshots of home, Skidbladnir and Nisaba were taken at 1280 and 390 and looked at.

1. vue-router 5.4.0 — chore(deps) (8f271bd)

Roadmap: the vue-router 5.4 item. Files: package.json, package-lock.json, ROADMAP.md.

It had been out two days with no issue filed against it. 5.4 changes client navigation defaults: it restores hashes and the top of the page, and invalidates obsolete scroll positions. I wrote a CDP probe that drives README anchors, back/forward between anchors, about → blog → back → forward, a cross-project hop, and a cold fragment load. It ran side by side on 5.3.1's build and 5.4's, on onyums and Skidbladnir.

Result:

  • Back/forward and cold loads landed identically on both builds.
  • README anchor clicks differed by up to ~100 px; I put that down to smooth-scroll timing in the probe and didn't confirm it.
  • No console output.

Nuxt's own scrollBehavior governs all of these navigations. Checks: typecheck clean, 246 tests, build, check, a11y, a11y:browser and audit:runtime clean.

2. Back returns a reader to the README they were reading — fix(about) (a843361)

Files:

  • app/composables/useReadmeFold.ts (new)
  • shared/markdown/anchor.ts (new)
  • test/anchor.test.ts (new)
  • app/pages/projects/[slug]/about.vue
  • README.md, ROADMAP.md

Found by increment 1's probe, and present on main too. A page reached by Back is rendered afresh, folded, so the saved scroll position no longer exists. A reader 6,000 px down onyums' README came back at 2,700, the bottom of the folded page.

The fold state now lives in history.state beside vue-router's own keys:

  • Back and Forward reopen it before Nuxt scrolls.
  • A visit by link starts folded.
  • A fold the visitor closed stays closed.
  • A client navigation to a README #heading opens it (hasAnchor).
  • A reload reopens it at the reader's place. That position goes to sessionStorage on pagehide, because Chromium drops a replaceState made while the page unloads.

Checks (onyums and Nanna, main build vs this one):

  • Back: 2,700 → 6,000.
  • Reload: 2,700 → 6,000.
  • The fold markup the server sends (<details>) is identical.

3. A link to a README heading lands on it in Firefox — fix(about) (800df87)

Files: app/composables/useReadmeFold.ts, ROADMAP.md.

Chromium opens a closed <details> for a fragment by itself; Firefox 155 here did not. A cold link to /projects/onyums/about#how-onyums-compares left Firefox at y 2,701 with the heading hidden.

Once mounted, the composable now looks the fragment up in the document, opens the fold and scrolls the heading in. Checked with a WebDriver BiDi probe in headless Firefox 155: the heading lands 80 px from the top (its scroll margin), and Back and reload behave as in Chromium. Chromium is unchanged, including a percent-encoded emoji slug.

4. The browser accessibility pass runs in Firefox too — feat(checks) (4033b44)

Files:

  • scripts/lib/bidi.mjs (new): headless Firefox over BiDi, using Node's own WebSocket.
  • scripts/lib/browser.mjs (new): one page interface over CDP and BiDi.
  • scripts/check-a11y-browser.mjs: rewritten against it.
  • package.json: npm run a11y:firefox.
  • .github/workflows/ci.yml, README.md, ROADMAP.md.

First run: no violations and no console errors on 23 pages at both widths in Firefox. The refactored Chromium pass is also clean.

Proved it bites, on a stub server in both engines:

  • A console error, an uncaught exception, a CSP-refused inline script and two 8 px buttons each fail it.
  • A clean page passes.

The CI runner's Firefox is a deb from the Mozilla PPA, not a snap (I read the runner image script), so a profile under /tmp works. This CI step runs for the first time on this PR.

5. Stale reading position on a fresh load — fix(about) (b81df31)

Files: app/composables/useReadmeFold.ts.

Found by writing increment 6. vue-router numbers entries from the history length when a document loads, so a fresh load could reuse an older entry's number. My sessionStorage key then sent a cold about#heading link to an old reading position, on Nisaba and Nanna, in both engines. The saved position is now read only when the navigation timing says reload or back/forward.

6. npm run check:navigation — feat(checks) (dc48fd3, 93330fe, 0196dcd)

Files: scripts/check-navigation.mjs (new), scripts/lib/browser.mjs, package.json, .github/workflows/ci.yml, README.md, ROADMAP.md.

What a visitor's own navigation does to the fold runs only in the browser, so nothing else could see it break. For every about page whose README has a heading, at 1280 and 390 px, in Chromium or Firefox (--firefox), it checks:

  • Back, Forward-then-Back, and reload return to the reader's place (±4 px).
  • A visit by link starts folded, at the top.
  • A cold load of #heading lands on it.
  • A client navigation to #heading lands on it.
  • A visitor-closed fold stays closed on Back.
  • A fold opened before hydration stays open (added in 08d8391).
  • No console errors.

It waits for scrolling to stop rather than a fixed time, because the site scrolls smoothly.

Results:

  • 144 checks per engine, green in both.
  • Against main's build (desktop only): 32 failures in Chromium, 41 in Firefox.

Firefox logs every cookie it refuses to a third-party response at error level; a README image from github.com sets three. The driver ignores those as the browser's notice, not a page fault.

7. No dead copy buttons without script — fix(a11y) (44eea71)

Files: app/components/CodeLine.vue, app/components/CodeBlock.vue, app/assets/css/main.css, scripts/check-site.mjs, ROADMAP.md.

Tor Browser's "Safest" level turns script off. Every page renders fine that way (checked in Firefox with javascript.enabled off), except that [copy] was shown and did nothing. It now carries needs-script, and @media (scripting: none) hides it: the browser's own answer, so nothing arrives late for visitors with script.

Checks:

  • Firefox: gone with script off, present with it on.
  • Chromium over CDP with script disabled: display: none; with script, flex.
  • npm run check now fails any public <button> without the class. It failed 12 on main's build and passes this one.

8. My own regression, caught and fixed: a README opened before hydration snapped shut — fix(about) (08d8391)

Files: app/composables/useReadmeFold.ts, scripts/check-navigation.mjs, scripts/lib/browser.mjs, ROADMAP.md.

Increment 2's :open binding made hydration write false over a fold the reader had already opened. On a slow line (Tor) there is time to do that. Open at load, closed 4 s later, on every about page.

  • Not on main; introduced by a843361 in this branch.
  • Found while screenshotting READMEs. I traced it with a MutationObserver plus a throwaway debug build: hydration removes the attribute between setup and mount.
  • Fix: while hydrating, open starts from the document as it stands, and an already-open fold is recorded once mounted.
  • Check: check:navigation now opens the fold at DOMContentLoaded via a preload script. The build with the bug fails exactly those 16 cases (8 pages × 2 widths) and nothing else; this build passes.

9. README images reserve their space — feat(readme) (12bb426, then 0196dcd, 776937d)

Files:

  • shared/markdown/image-size.ts (new)
  • shared/markdown/image-probe.ts (new)
  • server/utils/readmeImages.ts (new)
  • test/image-size.test.ts (new, 17 cases)
  • server/utils/github.ts, scripts/fetch-projects.mjs
  • README.md, ROADMAP.md

Markdown can't state image sizes, so every README <img> was zero-height until it loaded. With Skidbladnir's README open, its last heading moved 691 px as the screenshots landed (splimes 194, WeftDB 22).

How it works:

  • Sizes are read from each file's first 64 KB (PNG, GIF, JPEG, WebP, SVG root) and written in. An author's own width is kept, and its shape is preserved.
  • Only https URLs on the hosts GitHub README images come from are asked, with Range and a hard read cap. None of them is GitHub's API, so the REST quota is untouched.
  • The server caches each URL's size for a day with defineCachedFunction; a failure is not stored. npm run sync does the same, so the snapshot matches.

Results:

  • All 26 live images measured in 0.47 s.
  • Shift: 691 → 0 px in Chromium; Firefox the same within 2 px.
  • This also fixed a client navigation to a README #heading stopping short (Skidbladnir #editions at 771 px). check:navigation now holds it to the cold-load band, and both engines pass.

Cost: a cold start's first render went from 0.88–1.04 s on main to 1.02–1.47 s (three runs each); warm renders are unchanged at ~9 ms. The server's probe now gives up after 2 s (776937d).

10. Headless browsers no longer leave profiles in /tmp — fix(checks) (314c7b3)

Files: scripts/lib/cdp.mjs, scripts/lib/bidi.mjs.

72 Chromium profiles were left in /tmp today. Chromium's network service kept writing after the kill, so the removal lost the race. stop() now asks Chromium to close over CDP, waits for the exit, then removes the profile. A stop takes ~60 ms, and none are left after a full a11y:browser run. I removed the 121 stale ones.

11. /status endpoint links: big enough to tap in Firefox — fix(a11y) (616a9ac)

Files: app/pages/status.vue, ROADMAP.md.

The first CI run of a11y:firefox (Firefox 157 on the runner) failed, on something my local Firefox 155 and Chromium had both passed. /status at 390 px has six endpoint links, each 15 px tall and stacked 8 px apart: axe target-size, serious.

Each link is now its own 24 px row (inline-block py-1, as the footer's), at the same pitch. Locally check, a11y, a11y:browser and a11y:firefox are clean, and I took and looked at a 390 px screenshot of /status.

Roadmap-only commit

eeddc84 (research, below):

  • Nuxt 4.6.1 to soak.
  • security.txt, waiting on a contact.
  • The devtools and esbuild advisories that moved this week.
  • Two questions only a real browser can settle.

Snapshot

npm run sync at 05:05 UTC: all 8 projects, and all 28 README images in the snapshot are sized. With the snapshot forced by a bad token, /healthz reports snapshot/degraded, Skidbladnir's screenshots render at 1280×960 and 1280×2562, and npm run check is clean (19 pages, 74 URLs).

Final tree

  • Typecheck clean, 269 tests.
  • font:check, og:check, sizes:check and contrast pass; build green.
  • All 42 routes identical to main's.
  • check (22 pages, 79 URLs), a11y (23 pages), a11y:browser and a11y:firefox (23 pages × 2 widths) and audit:runtime are clean.
  • check:navigation: 144/144 in each engine.

Research

Done vs open

  • Landed: 11 increments in 17 commits, 3 checks (a11y:firefox, check:navigation, the needs-script assertion), 1 roadmap research commit, the snapshot.
  • Found and fixed in-run: 3 bugs (one pre-existing on main in two forms, one I introduced in this branch).
  • New [ ]:
    • Nuxt 4.6.1 (soak).
    • security.txt (owner: contact).
    • No-script #heading links in Firefox (no CSS can open a <details>).
    • Look at the masthead blur in a real Firefox or Tor Browser.
  • On that masthead item: headless Firefox drew no backdrop-filter blur at all, not even the plain fallback, so its screenshots show text crisp behind the nav. Most likely the headless compositor; I did not change the CSS.
  • Still owner-gated: site token, distroless and healthcheck, Onion-Location, HSTS, basic-automation.github.io, contrast, onion outage cause.
  • Upstream: x265 link, onyums anchor, WeftDB links, Arti 0.47 (needs an onyums release), devtools/simple-git, TypeScript 7.1.

STOP REASON: no workable next item, at 00:10 CDT, 3 h 00 min after the 21:10 start, short of the 4 h bar. Everything left is owner-gated, upstream in repos this routine doesn't push to, blocked, or deliberately soaking: Nuxt 4.6.1 was 70 minutes old.

Recommended next step

  1. Watch this PR's CI. a11y:firefox and check:navigation run on a GitHub runner for the first time (~8 min more CI).
  2. Owner:
    • A contact for security.txt.
    • A look at the masthead and a README #heading link in a real Tor Browser.
  3. Next run: Nuxt 4.6.1 once it has soaked, with check:navigation in both engines as part of the gate.

🤖 Generated with Claude Code

physics515 and others added 17 commits October 9, 2026 21:21
package.json, package-lock.json: vue-router ^5.3.1 -> ^5.4.0, after two
days with no issue filed against it. The lockfile also gains the router's
own build-time parser deps (ast-walker-scope's babel 8 and ast-kit).

5.4 restores hashes and the top of the page by default and invalidates
obsolete scroll positions. Checked in headless Chromium against 5.3.1's
build side by side (README anchors, back/forward, tab and cross-project
navigation, a cold fragment load): the same positions on both, no console
output. Nuxt's own scrollBehavior governs these navigations.

ROADMAP.md: ticked.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The about page folds its README, and a page reached by Back is rendered
afresh, folded, so the scroll position vue-router saved no longer existed:
a visitor 6,000 px down onyums' README landed at 2,700, the bottom of the
folded page.

app/composables/useReadmeFold.ts keeps the fold in history.state beside
vue-router's own keys, so Back and Forward reopen it before Nuxt's
scrollBehavior runs. A visit by link starts folded; one folded again stays
folded, even with a fragment in the URL. A client navigation to a fragment
inside the README opens it (shared/markdown/anchor.ts, test/anchor.test.ts).
Never while hydrating, so the server's markup is unchanged; on a reload
onMounted reopens it and scrolls back to where the visitor was, which is
kept in sessionStorage on pagehide because Chromium drops a replaceState
made while the page unloads.

app/pages/projects/[slug]/about.vue: binds it. README.md, ROADMAP.md: say so.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Chromium opens a closed <details> for a fragment by itself; Firefox does
not, and Tor Browser is Firefox. A link to
/projects/onyums/about#how-onyums-compares left a Firefox visitor at the
bottom of the folded page with the heading hidden inside it.

app/composables/useReadmeFold.ts: once mounted, look the fragment up in the
document (the README is markup, not a string, while hydrating), open the
fold and scroll the heading in, unless the visitor already chose a fold
state for this history entry. Checked in headless Firefox 155 over
WebDriver BiDi and in Chromium.

ROADMAP.md: recorded.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Every browser check here was Chromium, and the onion service is reached in
Tor Browser, which is Firefox. The README-heading bug fixed in the previous
commit was visible only there.

scripts/lib/bidi.mjs: headless Firefox over WebDriver BiDi with Node's own
WebSocket, on a port Firefox picks and records in the profile.
scripts/lib/browser.mjs: one page interface (open, goto, evaluate, run,
takeErrors, close) over cdp.mjs and bidi.mjs.
scripts/check-a11y-browser.mjs: written against it; --firefox selects the
engine. package.json: npm run a11y:firefox. ci.yml: runs it after
a11y:browser.

Clean on 23 pages at both widths in both engines. Proved on a stub server
that a console error, an uncaught exception, a CSP refusal and undersized
buttons each fail it in both.

README.md, ROADMAP.md: documented.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…position

useReadmeFold keeps a reader's position in session storage for a reload,
keyed by vue-router's entry number. That number restarts from the length
of the history whenever a document loads, so a fresh load could reuse an
older entry's number: on Nisaba and Nanna a cold load of about#<heading>
landed at the position an earlier reload had saved, not at the heading,
in both Chromium and Firefox. Found by the navigation check in the next
commit.

app/composables/useReadmeFold.ts: the saved position is read only when the
navigation timing says the document is a reload or Back/Forward to it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
What a visitor's own navigation does to the folded README runs only in the
browser, so neither check nor axe could see it break. On every about page
whose README has a heading, scripts/check-navigation.mjs asserts that Back,
Forward and a reload return the reader to where they were reading, that a
visit by link starts folded, that a cold load and a client navigation to a
README heading land on it, and that a fold the visitor closed stays closed
on Back; any console error fails it too. It waits for scrolling to stop,
because the site scrolls smoothly.

64 checks per engine, green on this build; against main's build it fails
32 in Chromium and 41 in Firefox.

scripts/lib/browser.mjs: reload() for both drivers. package.json: the
script. ci.yml: runs it in Chromium and Firefox. README.md, ROADMAP.md:
documented, plus a new item for the client hash scroll that stops short.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Every page is rendered on the server and reads fine with script off, which
is how Tor Browser's "Safest" level serves the onion service. The [copy]
buttons were the exception: shown, and inert when pressed.

app/components/CodeLine.vue, CodeBlock.vue: the buttons carry needs-script.
app/assets/css/main.css: @media (scripting: none) hides .needs-script, by
the browser's own answer rather than by script, so nothing arrives late for
a visitor with script. Checked in Firefox with javascript.enabled off and
on, and in Chromium with script execution disabled over CDP.

scripts/check-site.mjs: a <button> on a public page without the class is a
failure; main's build fails it on 12.

ROADMAP.md: recorded.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
scripts/check-navigation.mjs: every about page at 1280 and 390 px, the two
widths check-a11y-browser.mjs uses; 128 checks per engine, green in both.

scripts/lib/browser.mjs: Firefox logs, at error level, every cookie it
refuses to a third-party response; README images served by github.com set
three, which failed the phone pass in Firefox. That is the browser
declining someone else's cookie, not the page failing, and is ignored.

ROADMAP.md, README.md: updated.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Nuxt 4.6.1 to soak (its first issue predates it); security.txt, waiting on
a contact; the devtools/esbuild advisories that moved this week; the
scripting media feature's support; the Firefox runner being a PPA deb; and
two things only a real browser can settle: whether Firefox opens a <details>
for a fragment (Mozilla says since 139, headless 155 did not here) and what
the masthead's blur looks like in Firefox. The client hash item now says no
site link makes that navigation.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Binding the fold to `open` made hydration write the bound false over the
element, so a reader who opened the README before the app hydrated (a slow
line, Tor say) had it shut under them: open at load, closed 4 s later.
Introduced by a843361 in this branch; main had no binding.

app/composables/useReadmeFold.ts: while hydrating, start from the document
as it stands; once mounted, record an already-open fold in the history
entry, since no toggle reached onToggle.

scripts/lib/browser.mjs: preload() for both drivers (CDP's
addScriptToEvaluateOnNewDocument, BiDi's addPreloadScript).
scripts/check-navigation.mjs: opens the fold at DOMContentLoaded and asserts
it is still open after hydration. The build with the bug fails exactly those
16 cases; this one passes 144 per engine.

ROADMAP.md: recorded.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
scripts/lib/cdp.mjs: stop() killed Chromium and removed the profile at once;
the network service kept writing to it after the browser had gone, so the
removal lost the race (72 profiles left in /tmp in one day). It now asks the
browser to close over CDP, which takes its children with it, waits for the
exit (killing after 5 s), then removes the profile. A stop takes ~60 ms.
scripts/lib/bidi.mjs: the same wait also counts an exit by signal.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Markdown cannot state an image's size, so every README <img> was a
zero-height box until its file arrived and everything below it moved when
it did: with Skidbladnir's README open its last heading moved 691 px as the
screenshots landed (splimes 194, WeftDB 22). Now 0 in Chromium, within 2 px
in Firefox.

shared/markdown/image-size.ts: the size from a file's first 64 KB (PNG,
GIF, JPEG, WebP, an SVG root), and width/height written into the markup,
keeping an author's own width and its shape. Linear on unclosed tags.
shared/markdown/image-probe.ts: https only, only the hosts GitHub README
images come from, Range plus a hard 64 KB read limit, never throws.
server/utils/readmeImages.ts: once per URL per day via
defineCachedFunction; a failure is not stored. None of these hosts is
GitHub's API, so the REST quota is untouched.
server/utils/github.ts, scripts/fetch-projects.mjs: both renderers size
images, so the snapshot matches the live path.
test/image-size.test.ts: 17 cases.

ROADMAP.md: recorded.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
With README images sized (12bb426), nothing grows while a smooth hash
scroll passes it, so the navigation check holds a client navigation to the
same band as a cold load: the heading within 200 px of the top. 144 checks
per engine, both green; before the sizes this assertion failed in Chromium
(Skidbladnir's #editions at 771 px).

ROADMAP.md: the item ticked. README.md: the about page's README images say
so.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A cold start renders a README with its image probes in front of the
visitor; measured +0.1–0.4 s on Skidbladnir's first render, warm renders
unchanged. A hanging image host would have cost the probe's default 5 s,
so the server's cached probe now stops at 2 s and the image renders
unsized until the next refresh.

server/utils/readmeImages.ts, ROADMAP.md.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
npm run sync on 2026-10-10 05:05 UTC: all 8 projects, and every README
image in it (28) now carries its width and height, as the live path
renders them. Checked with the snapshot forced by a bad token: Skidbladnir's
screenshots render sized, npm run check clean.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The verify script's output has a header line.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The first CI run of a11y:firefox (Firefox 157) failed /status at 390 px:
its six endpoint links were 15 px tall and stacked 8 px apart, a
target-size failure Chromium's layout had passed by a hair. Each is now its
own 24 px row, as in the footer, at the same pitch.

app/pages/status.vue, ROADMAP.md.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@physics515
physics515 merged commit 4bb4e13 into main Oct 10, 2026
3 checks passed
physics515 added a commit that referenced this pull request Oct 11, 2026
…y pass measures a page at rest, the visitor Cloudflare saw in the log, Nuxt 4.6.1 (#21)

* feat(log): name the visitor Cloudflare saw, beside Caddy's peer

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>

* fix(checks): the browser accessibility pass measures a page at rest

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>

* chore(deps): Nuxt 4.6.1, vue-tsc 3.3.13, @types/node 26.6.5

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>

* fix(checks): check:navigation allows half a line on a restored position

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>

* docs(roadmap): the cfIp log field, and a verified client address at the 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>

* chore(data): refresh the fallback snapshot

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>

* fix(scroll): a restored position is a jump, not an animation

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>

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
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.

1 participant