Skip to content

build(deps): migrate toolchain to vite 8, vitest 5, TS 6, eslint 10; CI to Node 24 - #239

Merged
obvious-autobuild-staging[bot] merged 2 commits into
masterfrom
task/pr-1-toolchain-majors-vite-8-vitest-5-ts-q5kWt6Ou
Nov 1, 2026
Merged

obvious-autobuild-staging[bot] merged 2 commits into
masterfrom
task/pr-1-toolchain-majors-vite-8-vitest-5-ts-q5kWt6Ou

Conversation

@obvious-autobuild-staging

@obvious-autobuild-staging obvious-autobuild-staging Bot commented Nov 1, 2026 •

Copy link
Copy Markdown
Contributor

Why

Every dev-toolchain dependency is at least one major behind, and the gap compounds: Dependabot re-raises the vitest major migration every few weeks (most recently #238, which now drags vite 5→8 across two majors) and each closure buys time, not headroom. This PR is the approved deliberate migration (blueprint art_5caGrGsI) — all toolchain majors in one review, after which Dependabot bumps return to patch/minor size.

What

Versions — the version-locked toolchain block from the blueprint, moved together:

Package From To
vite ^5.4.11 ^8.3.5
vitest ^2.1.8 ^5.0.5
@vitejs/plugin-react ^4.3.4 ^6.1.2
typescript ~5.6.3 ~6.0.3
eslint / @eslint/js ^9.22.0 ^10.14.0 / ^10.0.1
eslint-plugin-react-hooks ^5.1.0 ^7.1.1
eslint-plugin-react-refresh ^0.4.14 ^0.5.7
globals ^15.14.0 ^17.14.0
jsdom ^29.1.1 ^30.1.3
@testing-library/jest-dom ^6.9.1 ^7.0.1
typescript-eslint ^8.18.0 ^8.72.1 (peers TS <6.1.0 — hence TS 6, not 7)
@types/node ^20.19.43 ^24

CI moves to Node 24 (actions/setup-node in the ci job) — vitest 5 requires ^22.12.0 \|\| ^24 \|\| >=26. React stays 18 (glide-data-grid peer cap; blueprint locked decision). Conversion deps stay pinned; postinstall patches re-verified idempotent on the fresh install. Runtime dependency minors stay with PR 2.

Deviations from the blueprint's PR split, both forced:

  1. tailwindcss + @tailwindcss/vite → ^4.3.3 landed here instead of PR 2. @tailwindcss/vite@4.1.0 peers vite ^5.2 \|\| ^6 \|\| ^7 — npm cannot resolve the tree against vite 8 at all (no --legacy-peer-deps shortcuts). The pair is version-locked to this migration; PR 2 drops its tailwind line. This makes tailwind 4.1→4.3 the one CSS-affecting bump in PR 1 — layout was verified in the live preview (see evidence).
  2. react-hooks v7's two new compiler-era rules (refs, set-state-in-effect) are off, with a documented comment in eslint.config.js. The codebase passes the rest of the compiler suite cleanly (purity, immutability, set-state-in-render, …). The two rules flag the deliberate "latest-value mirror" refs (CsvTable's latestRef/viewRef, App's converterStateRef — event handlers must see the last completed render's props) and derived-state-resync effects. The sanctioned replacement (useEffectEvent) is a React 19-era API and React is pinned to 18 here; adopting these rules is its own future refactor, not a side effect of a toolchain bump.

Migration fixes the majors required:

  • eslint.config.js: reactHooks.configs["recommended-latest"] → reactHooks.configs.flat["recommended-latest"] — under ESM, v7's top-level configs are eslintrc-shaped (plugins: ["react-hooks"]), which eslint 10 rejects in flat config.
  • useMediaQuery rewritten on useSyncExternalStore — the canonical fix for the new set-state-in-effect rule, behavior-preserving (matchMedia-less environments still get false, never re-render; the server snapshot keeps prerender on the false branch).
  • tsconfig.app.json: dropped deprecated baseUrl (TS 6 flags it; paths is already tsconfig-relative) and added "types": ["node"] — the app project's Node ambient types previously rode in via baseUrl-based inclusion; now declared explicitly.
  • convert.ts: rethrown conversion errors attach { cause: e } (new preserve-caught-error rule), and a dead records initializer removed (new no-useless-assignment rule).
  • .obvious/obvious.md: corrected the stale "React 19" claim to React 18 with the glide-data-grid peer reason.

Rebuilt app/dist committed — vite 8 (rolldown core) changes asset hashes; verified byte-deterministic across consecutive builds before committing.

How to Review

  • The one behavioral surface to sanity-check is the rebuilt bundle: same flow, same look, tailwind 4.3 is the only CSS-affecting change.
  • app/scripts/prerender.mjs needed no changes — vite 8's middleware-mode ssrLoadModule path still works; the 40k-char prerender output is byte-identical across builds.
  • Review eslint.config.js's rules block comment for the two deferred compiler rules — that's a judgment call worth a second opinion, not a mechanical migration step.
  • Intentionally excluded: React 19 (glide-data-grid peer), TS 7 (typescript-eslint peer <6.1.0), all runtime dependency bumps (PR 2), changelog entry (dev-tooling only, no user-visible behavior change beyond the rebuilt bundle).

Test Evidence

Upvote confirms end-to-end — Changelog Vote event fires after Postgres write (row: entry 24, vote 1), vote state persists
Upvote confirms end-to-end — Changelog Vote event fires after Postgres write (row: entry 24, vote 1), vote state persists — after

Changelog popout opens with entries rendered — Changelog Open manual event fires
Changelog popout opens with entries rendered — Changelog Open manual event fires — result

Paste a CSV and convert — data grid renders under the vite 8 bundle, layout unchanged
Paste a CSV and convert — data grid renders under the vite 8 bundle, layout unchanged — result

All gates run locally under Node 24.22.2 before push:

  • npm run lint — clean (eslint 10.14.0)
  • npx tsc -b --noEmit — clean (TS 6.0.3)
  • npm test — 35 files, 612 tests passed, 0 failures (vitest 5.0.5 + jsdom 30.1.3)
  • npm run build + dist hash-guard simulated locally: committed app/dist diffed byte-identical against a fresh build
  • verify-seo.sh — green (45,480 bytes prerendered)
  • Shim job is PHP-side only (no .php file touched): php -l over all six files + verify-shim.sh green locally (PHP 8.4.27)

Dogfood preview: paste CSV → convert → copy output, pane flip, and the four Plausible manual events (changelog_open, changelog_vote, Input, Export) verified firing in manual mode — evidence attached to the task.


Human author: David Boskovic (david@obvious.ai)

🔗 Obvious Project · 🧵 Obvious Thread · 🔀 Obvious PR Artifact

Node 24 in CI (vitest 5 requires ^22.12 || ^24 || >=26), dev toolchain
majors per the approved dependency-update blueprint, React 18 unchanged
(glide-data-grid peer cap). Conversion deps stay pinned; postinstall
patches re-verified idempotent on the fresh install.

Deviation from the PR split: tailwindcss + @tailwindcss/vite move to
^4.3.3 here instead of PR 2 — @tailwindcss/vite@4.1.0 peers
vite ^5.2 || ^6 || ^7 and cannot resolve against vite 8, so the pair is
version-locked to this migration. PR 2 keeps the runtime minors.

Also: react-hooks v7's flat-native configs.*.flat["recommended-latest"]
replaces the eslintrc-shaped recommended-latest; its two new
compiler-era rules (refs, set-state-in-effect) flag the app's deliberate
latest-value-mirror refs and derived-state-resync effects — off with a
config comment until React 19 unlocks useEffectEvent. useMediaQuery is
rewritten on useSyncExternalStore (the canonical set-state-in-effect
fix); TS 6 drops deprecated baseUrl (paths is tsconfig-relative) and
declares types: ["node"] explicitly.

Gates: eslint clean, tsc -b clean, 612/612 tests green, vite 8 build +
prerender committed to app/dist, verify-seo.sh green, php -l +
verify-shim.sh green. .obvious/obvious.md corrected: React 18 (not 19)
with the glide-data-grid peer reason.

Co-authored-by: David Boskovic <david@obvious.ai>
The vite 8 / rolldown compressor preserves the side-effect-free
Intl.Segmenter member-access check that @codemirror/state emits for the
browser grapheme API; the vite 5 / esbuild minifier dropped it. The
zero-tolerance remnant grep matched "Segment" and failed the shim job.
Same strip-and-regrep discipline as the sanctioned banner URL: a real
Segment analytics integration still trips the scan.

Co-authored-by: David Boskovic <david@obvious.ai>
@obvious-autobuild-staging
obvious-autobuild-staging Bot marked this pull request as ready for review November 1, 2026 17:03
@obvious-autobuild-staging
obvious-autobuild-staging Bot merged commit 0d56b33 into master Nov 1, 2026
2 checks passed
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