Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,15 +46,15 @@
"@tailwindcss/vite": "^4.1.0",
"@types/react": "^19.2.0",
"@types/react-dom": "^19.2.0",
"@vitejs/plugin-react": "^5.0.0",
"@vitejs/plugin-react": "^5.2.0",
"@vitest/browser": "^4.1.11",
"@vitest/browser-playwright": "^4.1.11",
"@vitest/coverage-istanbul": "^4.1.11",
"playwright": "^1.61.0",
"storybook": "^10.5.5",
"tailwindcss": "^4.1.0",
"typescript": "^6.0.3",
"vite": "^7.0.0",
"vite": "^8.2.2",
"vitest": "^4.1.11",
"vitest-browser-react": "^1.0.0"
}
Expand Down
14 changes: 3 additions & 11 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

132 changes: 132 additions & 0 deletions docs/migrations/2026-08-31-vite.md
Original file line number Diff line number Diff line change
Expand Up @@ -268,3 +268,135 @@ Not listed among BC-A1's call sites. `@vitest/browser@4` dropped its `./provider
- `.gitignore`: Vitest 4 writes failure screenshots to a new `**/.vitest-attachments/` directory (alongside the already-ignored `**/__screenshots__/`). Ignored, so test runs don't leave untracked binaries.
- BC-A2's 36-file `@vitest/browser/context` → `vitest/browser` sweep was included. It required an `eslint --fix` pass afterwards: the new specifier sorts differently under `simple-import-sort`, tripping the lint gate in all 36 files.
- BC-A3 and BC-A4 were confirmed correct — no `invocationCallOrder` or automock fixes were needed.

---

## POST-IMPLEMENTATION CORRECTIONS — PR B, as actually landed (2026-08-31)

Landing PR B surfaced five more errors in the plan body, one of them a material understatement of the
migration's own scope. **Read this alongside the PR A corrections above.**

### CORRECTION 6 (understated scope): Vite 8 is a FOUR-engine swap, not just the dep optimizer

BC-B1 describes Vite 8 as replacing "esbuild with Rolldown for dependency pre-bundling/scanning" and
calls the `optimizeDeps` scanner "THE highest-risk item in this whole migration". That is one of four
engine swaps, and not the one that touches the shipped artifact. Vite 8 actually replaces:

| Concern | Vite 7 | Vite 8 |
|---|---|---|
| Production bundler | **Rollup** | **Rolldown** |
| JS transform | esbuild | Oxc |
| JS minifier | esbuild | Oxc Minifier |
| CSS minifier | esbuild | **Lightning CSS** |
| Dep pre-bundling | esbuild | Rolldown *(the only one PR A already exercised)* |

Confirmed by the installed package's own dependency set: `vite@7.3.6` depends on `esbuild` + `rollup`;
`vite@8.2.2` depends on `rolldown` + `lightningcss`, and demotes `esbuild` to an **optional peer**.

Renamed/removed config surfaces that come with it: `build.rollupOptions` → `build.rolldownOptions`,
`worker.rollupOptions` → `worker.rolldownOptions`, the `esbuild:` config option → `oxc:` (with
`esbuild.jsx: 'automatic'` becoming `oxc.jsx: { runtime: 'automatic' }`), removal of the `manualChunks`
object form, removal of `build.rollupOptions.watch.chokidar`, and dropped `system`/`amd` output formats.

**Why PR B still needed zero config edits:** this repo configures *none* of them. Grepped the whole tree —
no `rollupOptions`, no `manualChunks`, no `esbuild:`/`esbuildOptions`, no `worker:`, no `import.meta.hot`,
no lib/formats build. The correct justification for "no config-shape edits required" is *"the repo uses
none of the renamed options"*, **not** BC-B1's *"the API is unchanged"* — the API did change, this repo
just never touched the parts that moved.

### CORRECTION 7: PR A did NOT pre-pay the Rolldown risk for the build path

CORRECTION 1 concludes "the Rolldown risk is already paid and measured". That is true **only for the
dep-optimizer in the test path**. PR A left `node_modules/vite` at 7.3.6, so the production build kept
running Rollup + esbuild-minify + esbuild-CSS-minify the entire time. Rollup→Rolldown bundling, Oxc
minification and Lightning CSS all land for the first time **here, in PR B**. The app build and dev
server were genuinely the new risk surface; the test runner was not.

### CORRECTION 8: `@vitejs/plugin-react` was ALREADY on 5.2.0 before this PR

BC-B3 presents `5.0.0 → 5.2.0` as a version move. The manifest range was `^5.0.0`, which had already
semver-resolved to `5.2.0` on the parent branch (verified on disk pre-change). The `package.json` edit is
a **floor tightening only** — it documents the vite-8-capable minimum and changes nothing about what is
installed. The lockfile shows no resolved-version change for this package.

### CORRECTION 9 (the plan predicted the opposite): vite 8 DE-DUPLICATES the tree

CORRECTION 1 documents the three-copy split PR A created (root 7.3.6 + `vitest/vite` 8.2.2 +
`astro/vite` 8.2.2). Bumping the app to 8.2.2 collapses all three into the single hoisted root copy.
The lockfile delta for this entire PR is **four entries removed, zero added**:

```
- "astro/vite" - "astro/vite/fsevents"
- "vitest/vite" - "vitest/vite/fsevents"
```

There is now exactly **one** `vite` in the tree. Installed package count drops accordingly (884 with a
clean `--frozen-lockfile` install; all 11 `@nightcore/*` workspace links present, hoisted linker intact,
`e2e:ring3 --prove` green). So PR B is net-simplifying, not additive — and the "vitest carries its own
vite 8 either way" caveat in CORRECTION 1 stops being true the moment PR B lands.

### CORRECTION 10: PR A's `esbuildOptions` deprecation warning is RESOLVED by PR B

The warning CORRECTION 1 quotes (`vite:react-babel` setting the deprecated `optimizeDeps.esbuildOptions`)
came from plugin-react targeting a vite-7 API while vitest ran vite 8. With the app on vite 8 too,
plugin-react 5.2.0 emits the `rolldownOptions` form instead. **Zero occurrences** across both cold-cache
`test:web` runs and the production build. PR B cleans up a warning PR A introduced.

### CORRECTION 11: rollback

CORRECTION 1 already voided the plan's original rollback claim. Completing it: reverting PR B does not
reach a clean state either — it restores the **three-copy** vite tree (root 7.3.6 + two nested 8.2.2)
and re-introduces the `esbuildOptions` deprecation warning. Reverting PR A and PR B together is the only
route back to a single-vite-7 tree.

### Measured results (PR B)

**Storybook × vite 8 — admitted, not a blocker.** Every vite-peer consumer in the tree accepts `^8.0.0`:
`@storybook/react-vite@10.5.10` and `@storybook/builder-vite@10.5.10` both declare
`^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0`; also `@tailwindcss/vite@4.3.3` (`^5.2.0 || ^6 || ^7 || ^8`),
`@vitejs/plugin-react@5.2.0`, `@joshwooding/vite-plugin-react-docgen-typescript@0.7.0`, `@vitest/mocker`,
`vitefu`, and `vitest@4.1.11`. `astro@7.2.9` requires `^8.0.13` — satisfied by the now-shared 8.2.2.

**`optimizeDeps` guards needed NO adjustment.** All three locations
(`vite.config.ts` has none; `vitest.config.ts` both projects; `.storybook/main.ts`) were left byte-identical.
The include lists tuned against esbuild's scanner remain sufficient under Rolldown's — now confirmed for
the build path as well as the test path.

**F3 cold-cache trap did not reproduce.** `bun run test:web` ×2, each preceded by
`rm -rf apps/web/node_modules/.vite`: 517 files / 2953 tests passed both times, **zero** occurrences of
`optimized dependencies changed`. `test:stories` (207 files / 918 tests) likewise clean.

**Production build is green and materially faster.** `vite build` 3.81s → **715ms** (~5.3×).

| | vite 7.3.6 | vite 8.2.2 | Δ |
|---|---|---|---|
| dist files | 43 | 53 | +10 (finer code-splitting) |
| dist total | 3,163,690 B | 3,187,272 B | **+0.75%** |
| main JS chunk | 751.94 kB (gzip 229.63) | 691.43 kB (gzip 205.81) | **−60.5 kB / −23.8 kB gzip** |
| main CSS | 109,089 B | 115,465 B | +5.8% (Lightning CSS syntax lowering, documented) |
| CSS files / SVG / html | 2 / 1 / 1 | 2 / 1 / 1 | unchanged |

Output integrity verified, not assumed: all 21 assets referenced by `index.html` plus **301** inter-chunk
relative references resolve on disk; **zero** absolute `/assets` paths, so `base: './'` still satisfies
Tauri's custom-protocol requirement; `@layer`, `prefers-reduced-motion` and the `--nc-*` custom properties
all survive Lightning CSS minification. Chunk filenames became semantic (`terminal-*`, `settings-*`,
`council-*`, `prreview-*`) where Rollup emitted repeated `index-*`, and Vite 8 additionally emits
`<link rel="modulepreload">` hints Vite 7 did not.

**Two new advisory warnings** appear from Rolldown, both pre-existing code conditions rather than
regressions — `[INEFFECTIVE_DYNAMIC_IMPORT]` for `@tauri-apps/plugin-dialog` and `src/components/board/index.ts`,
each dynamically imported somewhere but also statically imported elsewhere, so the dynamic import cannot
split them into their own chunk. Harmless today; a cheap code-splitting win if anyone wants it. The
chunk-size advisory also now points at `build.rolldownOptions.output.codeSplitting` instead of
`manualChunks`.

**CJS interop (the "Consistent CommonJS Interop" break) is a non-issue here.** Only two default-import
sites exist in `apps/web/src`: `dompurify` and `shiki/**/*.mjs`. `dompurify` ships a `module`/`exports`
ESM build so Vite resolves ESM and the CJS rule never applies; the shiki imports are literal `.mjs`.
No default import of `react`/`react-dom` exists anywhere (only `import type`).

**Dev-server path smoke-tested non-interactively.** `vite --port 5173 --strictPort` (the exact
`beforeDevCommand` Tauri's `devUrl` polls) reports `VITE v8.2.2 ready in 163 ms`, serves `/` at 200 with
plugin-react's Babel Fast-Refresh preamble injected, transforms `/src/main.tsx` at 200, and serves
`/@vite/client` at 200. **Still owed: a GUI `bun run desktop` run** to confirm the Tauri WebView window
itself renders — that needs an interactive session and was not attempted.
Loading