You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
#2073 fixes that instance — Node 26 ships an inert localStorage global that
vitest's happy-dom environment declines to replace, so happy-dom tests read undefined. It does not fix the reason nobody found out from CI.
Nothing pins the local version either: no .nvmrc, no engines in web/package.json. So contributors drift onto whatever Node they have, and
AGENTS.md tells every one of them to trust make ci locally before pushing. A
suite that is red for reasons unrelated to your change is a suite people learn
to ignore — which is the part of #2058 worth more than the four tests.
Why this shape of bug will recur
The failure was not a broken API. Node 26 added globals (Storage, localStorage, sessionStorage), and adding one was enough: vitest's populateGlobal skips any window key the platform already defines unless the
key is on its hardcoded allow-list, and localStorage is not. Every future Node
that lands a web global can shadow happy-dom the same way, silently — and
silently is the operative word: profile.svelte.ts and palette.svelte.ts guard
on typeof localStorage === 'undefined', so the no-storage branch passes on
default data rather than erroring. Only a test asserting storage round-trips
goes red.
What to decide
Two candidate remedies, and they are not exclusive:
Add the current Node to the web job's matrix (node-version: [24, 26]).
Catches this class at the gate. Costs one more web-suite run per PR — the Go,
e2e and vulncheck jobs need not be duplicated, and this repo already cancels
superseded runs (Revert "ci: report e2e on the PRs where it does not run" #963). The open question is whether to gate the merge on the
newer entry or let it be advisory, since a Node-26 regression in a dependency would then block unrelated PRs.
Pin the local Node so the two gates cannot disagree — .nvmrc plus an engines field. Cheap, but advisory: no tooling here reads .nvmrc
automatically, and pinning to 24 tells contributors on 26 that their working
setup is unsupported when, after fix(web): restore happy-dom's localStorage over Node 26's inert global #2073, it is not.
My read is that (1) is the durable one and (2) alone would only have relabelled
this bug rather than surfaced it. Worth a contributor's call on whether the
second matrix entry blocks or informs, which is why this is an issue and not a PR.
Split out of #2058 / #2073 rather than grown into that PR's scope.
The blind spot
Four
webtests were red on a cleanmainfor every contributor on Node 26 andgreen in CI, for a week, because CI pins one Node version:
#2073 fixes that instance — Node 26 ships an inert
localStorageglobal thatvitest's happy-dom environment declines to replace, so happy-dom tests read
undefined. It does not fix the reason nobody found out from CI.Nothing pins the local version either: no
.nvmrc, noenginesinweb/package.json. So contributors drift onto whatever Node they have, andAGENTS.md tells every one of them to trust
make cilocally before pushing. Asuite that is red for reasons unrelated to your change is a suite people learn
to ignore — which is the part of #2058 worth more than the four tests.
Why this shape of bug will recur
The failure was not a broken API. Node 26 added globals (
Storage,localStorage,sessionStorage), and adding one was enough: vitest'spopulateGlobalskips any window key the platform already defines unless thekey is on its hardcoded allow-list, and
localStorageis not. Every future Nodethat lands a web global can shadow happy-dom the same way, silently — and
silently is the operative word:
profile.svelte.tsandpalette.svelte.tsguardon
typeof localStorage === 'undefined', so the no-storage branch passes ondefault data rather than erroring. Only a test asserting storage round-trips
goes red.
What to decide
Two candidate remedies, and they are not exclusive:
webjob's matrix (node-version: [24, 26]).Catches this class at the gate. Costs one more web-suite run per PR — the Go,
e2e and vulncheck jobs need not be duplicated, and this repo already cancels
superseded runs (Revert "ci: report e2e on the PRs where it does not run" #963). The open question is whether to gate the merge on the
newer entry or let it be advisory, since a Node-26 regression in a
dependency would then block unrelated PRs.
.nvmrcplus anenginesfield. Cheap, but advisory: no tooling here reads.nvmrcautomatically, and pinning to 24 tells contributors on 26 that their working
setup is unsupported when, after fix(web): restore happy-dom's localStorage over Node 26's inert global #2073, it is not.
My read is that (1) is the durable one and (2) alone would only have relabelled
this bug rather than surfaced it. Worth a contributor's call on whether the
second matrix entry blocks or informs, which is why this is an issue and not a PR.