Repository navigation
chore(deps): upgrade the React apps to the latest stable versions (part of #1453) - #1461
Merged
Merged
Conversation
Both clients move to Vite 8 (Rolldown) with @vitejs/plugin-react 6,
TypeScript 6.0, ESLint 10, eslint-plugin-react-hooks 7, lucide-react 1,
globals 17 and @types/node 26, plus every minor and patch bump. Admin
moves to zod 4 and @hookform/resolvers 7.
- tsconfig: drop the deprecated baseUrl (paths were already relative)
and the now-empty DOM.Iterable lib.
- vite.config: import.meta.dirname instead of __dirname; the admin dev
proxy forwards WebSocket upgrades for the SignalR hub.
- eslint: defineConfig from eslint/config; eslint-plugin-jsx-a11y has no
ESLint 10 release, so use the eslint-plugin-jsx-a11y-x fork under the
original plugin name (same recommended rules).
- Fix every react-hooks 7 error instead of disabling the rules: state
resets move out of effects into handlers or render-time adjustment,
refs are no longer touched during render, and icon components chosen
at render time become module-level components.
- zod 4: z.email()/z.url() via pipe, { error } instead of { message }.
- Drop the esbuild override; Vite 8 no longer depends on esbuild.
- Node 26 LTS in the Dockerfiles and Frontend CI (setup-node v7), nginx
pinned to 1.31.8-alpine, engines.node raised to Vite 8 / ESLint 10's
floor.
Part of #1453
…d-stack # Conflicts: # clients/admin/src/pages/login.tsx # clients/dashboard/src/pages/login.tsx
This was referenced Nov 3, 2026
Merged
This was referenced Nov 3, 2026
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.
This is the frontend half of #1453. It moves both React apps (
clients/admin,clients/dashboard) to the latest stable versions, moves their build to Node 26 LTS, and fixes what the new majors flagged. The backend half landed in #1460.Version bumps
alpineresolves to today)engines.nodeis now^20.19.0 || >=22.13.0, which is what Vite 8 and ESLint 10 need. Both lockfiles were regenerated with a plainnpm install, with no--legacy-peer-depsand no overrides. The oldesbuildoverride is gone because Vite 8 no longer depends on esbuild, and nothing else pulls it in. (#1460 had already movedsetup-nodeto v7 in this workflow, so the only workflow change here is the Node version.)What changed in our code, per major
Vite 8 and plugin-react 6. Neither config used
manualChunks,rollupOptions,esbuildoptions or the plugin'sbabeloption, so nothing needed porting to Rolldown.__dirnamebecameimport.meta.dirname, which clears the warning about the upcoming native config loader. The admin dev proxy now forwards WebSocket upgrades on/api(ws: true), as the dashboard's already did. Without it, the admin's SignalR hub negotiated over HTTP but its upgrade landed on Vite's own server. The runtime/config.jsonloading is unchanged, and so is the dashboard's dev middleware that serves it. Production builds take about 0.5 s instead of 5 s.TypeScript 6.0.
baseUrlis deprecated, so I removed it. Thepathsentries were already./src/*, so imports resolve the same way.DOM.Iterableis folded intoDOMnow, so it's gone fromlib. Every config already settypes,strict,moduleandtargetexplicitly, so the new defaults change nothing.ESLint 10. The configs now use
defineConfigfromeslint/configinstead of the deprecatedtseslint.config(). The one blocker was eslint-plugin-jsx-a11y: its latest release, 6.10.2, accepts ESLint up to 9 and has had no release since 2024 (jsx-eslint/eslint-plugin-jsx-a11y#1075 has been open since February). I swapped it for eslint-plugin-jsx-a11y-x, the es-tooling fork, which supports ESLint 9 and 10. I compared the two: the fork's recommended rule set is identical, and the only rules it drops are three that upstream had already deprecated (accessible-emoji,label-has-for,no-onchange), none of which we enabled. It is registered under the originaljsx-a11yplugin name, so rule ids and existing disable comments don't change, and going back to upstream later is a one-line import change. A comment in both configs explains this.eslint-plugin-react-hooks 7. The new React Compiler rules reported 79 errors: 66
set-state-in-effect, 6refs, 5static-componentsand 2use-memo. I fixed all of them. No rule is disabled and noeslint-disablecomments were added; three oldexhaustive-depsdisables went away along the way. The fixes follow react.dev's "You Might Not Need an Effect":openor target value in state), or in the open/close handler.A few edge cases now behave slightly differently, all on purpose:
The remaining warnings are the existing
react-refresh/only-export-componentsones, plus one newincompatible-librarynotice on react-hook-form'swatch()in the create-tenant dialog. That one is informational: it says the compiler would skip memoizing that component.lucide-react 1.x. We used none of the removed brand icons, so every import still resolves (
tscchecks this). Icons now getaria-hidden="true"by default. Our icon-only controls already have anaria-label, and no spec relied on icons being exposed.zod 4 and @hookform/resolvers 7 (admin).
.email()and.url()becamez.string().trim().pipe(z.email(...))and.pipe(z.url(...)), so the value is still trimmed before it is validated.{ message }became{ error }. I checked the schemas against the cases the forms rely on: an empty or invalid URL still reports the URL message first, a non-http scheme reports the scheme message, and a password mismatch still lands onconfirmPassword. The messages still come fromt(), so the pt-BR messages still show. While there,confirmPasswordnow uses the existingcreate.validation.min8message; it used to fall back to zod's built-in English text.Exceptions
typescript-eslint8.72.1 requirestypescript <6.1.0, so we stay on 6.0.3, as Upgrade the whole stack to the latest stable versions (.NET 10.0.14, Aspire 13.6, Vite 8, ESLint 10, zod 4, Node 26) #1453 decided. TypeScript is the only packagenpm outdatedlists in either app.npm auditwith dev dependencies shows 12 moderate findings in each app. They all trace back to one advisory,minimatch <11.0.1(GHSA-g3hv-75wg-m249), which comes in through ESLint 10 itself, typescript-eslint and the a11y plugin. No fixed release exists yet, it only affects lint tooling, andnpm audit --omit=devis clean.template-smoke.ymlstill builds the scaffolded apps on Node 22. That still meetsengines, and that workflow is outside this PR.Verification
"Before" is origin/main. "After" is this branch merged with current main, which includes #1456 and #1459. Local Node is 24.13.
npm cinpm outdatednpm audit --omit=devnpm run linttsc -b --noEmitnpm run buildThe suites have more tests now because main gained specs through the merge. In one full admin run,
billing/invoice-detail.spec.ts:76timed out under load. Run on its own it passed 21/21 (7 tests × 3 repeats), and the next full run was 183/183.Docker: both images build on
node:26-alpineandnginx:1.31.8-alpine./config.jsonis rendered from the environment, including the dashboard'sFSH_DEFAULT_LANGUAGE./returns 200, and afterdocker restartthe container reportstrue 0(running, no restarts). The Vite 8 dev servers start with no backend and load the login page (Playwright starts them for every run).Bundle size (gzip)
The JS is about 8 to 11% larger. Most of the difference comes from how Rolldown splits chunks. Rollup put everything the entry needed into one file. Rolldown splits it into many shared chunks, and each one is gzipped separately. In admin, zod 4 also adds about 26 kB, but only in the lazy-loaded form chunks. The minifier isn't the cause: building with esbuild instead of Oxc made the bundle bigger (510.8 KiB). Putting all lucide icons into one chunk brought the dashboard down to 89 files and 481 KiB in total, but initial load didn't change, so I didn't add that config. Chunk tuning is a reasonable follow-up, but it doesn't need to block this upgrade.
Part of #1453