Skip to content

build(deps): React 19, Vite 8, Vitest 4 and the GitHub Actions majors - #22

Merged
piotrlaczkowski merged 2 commits into
mainfrom
chore/dependency-sweep
Aug 27, 2026
Merged

build(deps): React 19, Vite 8, Vitest 4 and the GitHub Actions majors#22
piotrlaczkowski merged 2 commits into
mainfrom
chore/dependency-sweep

Conversation

@piotrlaczkowski

Copy link
Copy Markdown
Contributor

Closes the dependabot backlog in one sweep. Merging those 17 PRs individually would mean 17 sequential rebase+CI cycles — every one touches package.json/package-lock.json, so each merge conflicts the next.

Supersedes: #1 #2 #3 #6 #7 #9 #10 (Actions) and #12 #13 #14 #15 #16 #18 #20 (web).

GitHub Actions (7)

checkout 4→7, setup-go 5→7, setup-node 4→7, setup-python 5→7, deploy-pages 4→5, upload-pages-artifact 3→5, action-gh-release 2→3. The old majors were also emitting Node 20 deprecation warnings on every run.

Web (7 of 10)

React 19.2.8, @dnd-kit/sortable 10, Vite 8, @vitejs/plugin-react 6, Vitest 4, globals 17, eslint-plugin-react-hooks 7, typescript-eslint 8.68.0.

Three things had to change:

  • React 19 typesuseRef<T>(null) now yields RefObject<T | null>. Fixed at the three declaration sites rather than cast away at the ~10 use sites.
  • Vite 8 → rolldown — accepts manualChunks only as a callback; the object form fails the build outright. Same three vendor chunks, matched on module id.
  • __dirname in the vite/vitest configs → import.meta.dirname.

⚠️ The new React Compiler lint rules are off, deliberately

eslint-plugin-react-hooks@7 enables React-Compiler-derived rules that flag 42 existing patterns — not regressions, just constructs the compiler can't prove safe to memoize (mostly setState-in-effect).

Off rather than warnings: a warning nobody can action 42 times trains people to ignore the whole report. Adopting them is a real refactor of state flow and deserves its own review, not a silent rewrite of every effect riding along in a version bump.

rules-of-hooks and exhaustive-deps — the two that caught real shipped bugs — stay errors. eslint still reports 0 errors and the same 50 pre-existing warnings.

❌ Three PRs are not adoptable — the blocker is upstream

PR Blocker
#17 TypeScript 5.9→7.0 typescript-eslint caps TS at <6.1.0, including its 8.68.1 alphas. Taking TS 7 means deleting TypeScript linting from the project.
#19 ESLint 9→10, #11 @eslint/js 10 eslint-plugin-jsx-a11y latest (6.10.2) peers eslint ^3..^9. No ESLint 10 support has shipped.

Forcing either with --legacy-peer-deps yields a tree whose linting is silently broken — worse than staying on the current major. These stay open until upstream catches up.

Verification

tsc clean · eslint 0 errors · 79/79 vitest · vite build green · Go build + lint.sh at the zero baseline + full suite clean.

Studio also exercised in a browser under React 19: SSE connected, every chunk and API call 200, composition preview and phase rail rendering correctly.

Closes the dependabot backlog in one sweep rather than 17 sequential
rebase-and-merge cycles: every one of those PRs touches package.json or
package-lock.json, so each merge conflicts the next and the queue can only
drain one CI round at a time.

── GitHub Actions (7 PRs) ────────────────────────────────────────────────────

checkout 4→7, setup-go 5→7, setup-node 4→7, setup-python 5→7, deploy-pages 4→5,
upload-pages-artifact 3→5, action-gh-release 2→3. Mechanical; the v4/v5 majors
were also emitting Node 20 deprecation warnings on every run.

── Web (7 of 10 PRs) ─────────────────────────────────────────────────────────

react + react-dom 18.3.1→19.2.8, @types/react 19, @dnd-kit/sortable 8→10
(with core →6.3.0 for its peer), vite 5→8, @vitejs/plugin-react 4→6,
vitest + @vitest/coverage-v8 2→4, globals 15→17, eslint-plugin-react-hooks 5→7,
typescript-eslint →8.68.0.

Three things had to change for these to work:

  • React 19 types. `useRef<T>(null)` now yields `RefObject<T | null>` rather
    than `RefObject<T>` — the honest type, since a ref genuinely is null until
    its element mounts. Fixed at the three declaration sites (useStickToBottom's
    return, Modal's initialFocusRef, HITLPopup's firstActionRef) rather than
    cast away at the ~10 use sites.

  • Vite 8 builds on rolldown, which accepts `manualChunks` only as a callback
    and fails the build outright on the object form. Same three vendor chunks
    (react / dnd / icons), matched on module id.

  • `__dirname` in the vite and vitest configs, which Vite 8's native config
    loader warns about. Now `import.meta.dirname`.

── The new React Compiler lint rules are OFF, deliberately ──────────────────

eslint-plugin-react-hooks v7 enables a family of rules derived from the React
Compiler. They flag 42 EXISTING patterns across Studio — not regressions, just
constructs the compiler cannot prove safe to memoize, setState-inside-an-effect
being most of them.

They are off rather than warnings because a warning nobody can act on 42 times
is noise that trains people to ignore the whole report. Adopting them is a real
refactor of state flow across the app and deserves its own review, not a
silent rewrite of every effect riding along in a version bump.

rules-of-hooks and exhaustive-deps — the two that caught real shipped bugs —
stay errors. The gate is not weakened by one rule: eslint still reports
0 errors and the same 50 pre-existing warnings it did before.

── Three PRs are NOT adoptable, and the blocker is upstream ─────────────────

  typescript 5.9.3 → 7.0.2 (#17)
    typescript-eslint caps TypeScript at <6.1.0 — including its own 8.68.1
    alphas. Taking TS 7 means deleting TypeScript linting from the project.

  eslint 9.39.5 → 10.8.1 (#19) and @eslint/js → 10.0.1 (#11)
    eslint-plugin-jsx-a11y's newest release (6.10.2) peers eslint ^3..^9 only.
    No ESLint 10 support has shipped.

Forcing either with --legacy-peer-deps produces a tree whose linting is
silently broken, which is worse than staying on the current major. They stay
open until the ecosystem catches up.

Verified: tsc clean, eslint 0 errors, 79/79 vitest, vite build green, Go build
+ lint at the zero baseline + full suite clean, and Studio exercised in a
browser under React 19 — SSE connected, all chunks and API calls 200, the
composition preview and phase rail rendering correctly.
Vitest 4 pulls a jsdom whose undici calls `webidl.util.markAsUncloneable`,
added in Node 22.10. On CI's Node 20 that is a TypeError thrown while merely
IMPORTING jsdom, so every test file failed to start — 9 unhandled errors and
'no tests' rather than a test failure.

Two jobs disagreed about this and the disagreement hid it: Web Frontend Check
only runs tsc and the build, so it passed; Lint & Test and Pre-commit run
`make check`, which runs vitest, and both failed. A green check on the job
named after the frontend was reporting on half of it.

`engines: node >=22.10` in web/package.json makes the floor explicit, so a
mismatch fails at install time with a readable message instead of at test time
with a TypeError from inside a transitive dependency. Docs updated from the
stale 'Node 18+' to match.
@piotrlaczkowski
piotrlaczkowski merged commit ff347b2 into main Aug 27, 2026
7 checks passed
@piotrlaczkowski
piotrlaczkowski deleted the chore/dependency-sweep branch August 27, 2026 17:26
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