bump: next 15.4.3 → 16.2.6 + ESLint flat config (v9 fallback) + typescript devDep #4

Merged
varutasu merged 6 commits from convoy/bump-next-js into main 2026-05-23 03:31:27 -04:00
varutasu commented 2026-05-23 03:04:47 -04:00 (Migrated from github.com)

Closes P0 ship-blocker #8 from .convoys/ship-readiness.md. Vercel has been refusing every deployment since 2025-08-01 with "Vulnerable version of Next.js detected, please update immediately" — this bump clears that platform gate and unblocks every downstream preview-smoke and visual-diff gate that depends on a live preview URL.

Summary

  • Bumps next 15.4.3 → 16.2.6 to clear Vercel's platform security gate.
  • Migrates images.domains (removed in 16) → images.remotePatterns in next.config.js, preserving the three CDN hosts (Scryfall, Pokémon TCG, Lorcana API) for the eventual next/image adoption.
  • Migrates the lint toolchain from ESLint v8 + legacy .eslintrc.json to ESLint v9.39.4 (npm maintenance tag) + flat config (eslint.config.mjs), bumps eslint-config-next to 16.2.6 to match next, replaces next lint (removed in 16) with eslint . in scripts.lint, and adds typescript@^5.9.3 as a devDep so eslint-config-next@16's bundled typescript-eslint chain can satisfy its hard require('typescript') at module load.

The v9 pin (rather than v10 latest) is a deliberate fallback per Decision D after Risk R15 fired empirically — see Notes below for the full audit trail.

Files changed

  • package.jsonnext ^15.4.2^16.2.6; eslint ^8^9.39.4 (Decision D fallback after R15 fired empirically on v10); eslint-config-next 15.4.2^16.2.6; typescript newly added at ^5.9.3 (resolved typescript@5.9.3); scripts.lint next linteslint .. React stays at ^18.3.1 per Risk R7. No engines block, no tsconfig.json.
  • package-lock.json — regenerated by npm install. Stays on the v9 dep tree: @eslint/eslintrc@3.3.5 is retained as a v9 transitive dep (v10 dropped it; v10's removal is deferred to the queued bump-eslint-10 follow-up convoy). typescript@5.9.3 adds a single subtree (compiler bundle, zero transitive deps).
  • next.config.jsimages.domainsimages.remotePatterns (verbatim shape from Next 16 upgrade guide).
  • eslint.config.mjs (new) — flat config using the verbatim shape from the official Next.js docs (defineConfig([...nextVitals, globalIgnores([...])])). Verified compatible on both ESLint v9.39.4 and v10.4.0; the bump-eslint-10 follow-up convoy will not need to change this file. globalIgnores covers .next/**, node_modules/**, out/**, build/**, next-env.d.ts, scripts/migrations/**.
  • .eslintrc.json (deleted) — 40-byte legacy stub {"extends": "next/core-web-vitals"}; eslint-config-next@16 is flat-config-only.

Acceptance criteria

  • dependencies.next is ^16.2.6 (resolved next@16.2.6)
  • react / react-dom unchanged at ^18.3.1
  • devDependencies.eslint is ^9.39.4 (resolved eslint@9.39.4) — Decision D fallback
  • devDependencies.eslint-config-next is ^16.2.6 (resolved 16.2.6)
  • devDependencies.typescript is ^5.9.3 (resolved 5.9.3) — Decision C
  • scripts.lint is "eslint ."
  • No new dependencies beyond the four authorized; no engines block; no tsconfig.json; no .js.ts renames; no // @ts-check; no .d.ts files
  • package-lock.json regenerated (no hand edits); @eslint/eslintrc@3.3.5 retained per v9 dep tree
  • npm install completed cleanly with no ERESOLVE and no deprecation warnings on the four target packages
  • next.config.js migrated to images.remotePatterns with the verbatim three-host shape
  • .eslintrc.json deleted
  • eslint.config.mjs created with the verbatim shape (no rule tuning, no parserOptions, no settings)
  • npm run build exits 0; banner reads ▲ Next.js 16.2.6 (Turbopack); no images.domains deprecation warning at startup; all 23 static pages prerender, all 47 API routes compile
  • npm run lint runs to completion under ESLint v9.39.4. Exit code 1; 128 problems (81 errors, 47 warnings) — the new authoritative baseline for fix-lint-baseline. CI's || true wrapper tolerates the non-zero exit. No module-resolution or scope-manager crash. All findings are in pre-existing pages/, components/, tailwind.config.js, postcss.config.js source — none in files touched by this PR.
  • No tests added — tcg-vault has no test runner installed yet (tracked under adopt-vitest); manual smoke per TESTING_GUIDE.md is the verification mechanism.
  • No <Image> migrations (none exist in tcg-vault — re-verified by rg "from ['\"]next/image['\"]", zero hits)
  • No --webpack opt-out (not needed; no webpack: block in next.config.js; Turbopack-by-default runs clean)
  • No edits outside the brief's files: / deletes: lists

Test plan

  1. npm install — exits clean with no ERESOLVE warnings.
  2. npm run build — exits 0 cleanly on Turbopack.
  3. npm run dev — manual smoke per TESTING_GUIDE.md: confirm /, /login, /signup, /cards, /collections render without runtime errors, hydration warnings, or 500s. Confirm no images.domains deprecation warning at startup.
  4. npm run lint — exit 1 with 128 problems (81 errors, 47 warnings). Tolerated by CI's || true wrapper. Baseline reduction is fix-lint-baseline's charter.
  5. Push branch → confirm Vercel preview moves from Error: Vulnerable version of Next.js detected to Success, capture the preview URL in this PR for the audit fan-out.

Notes

Decision audit trail (full in .convoys/bump-next-js.md § Decisions)

  • Decision A (2026-05-23) — Expand convoy scope to include ESLint v8 → v9 + flat-config migration. eslint-config-next@16 requires eslint >=9.0.0; the convoy file's prescribed ^16 bump for eslint-config-next couldn't co-exist with eslint@^8. Adopted flat config natively (no FlatCompat shim — eslint-config-next@16.2.6 ships native flat-config exports, verified by tarball extraction).
  • Decision B (2026-05-23) — Pivot eslint from ^9.39.4 to ^10.4.0. ESLint v10 was released to latest between convoy authoring and gate-1 review. Pivoted to avoid a back-to-back bump-eslint-10 convoy.
  • Decision C (2026-05-23) — Add typescript@^5.9.3 as a devDep. The peerDependenciesMeta.typescript.optional: true annotation on eslint-config-next@16.2.6 only suppresses npm's install-time warning; @typescript-eslint/typescript-estree hard-requires typescript at runtime via 9 unconditional require('typescript') sites in its dist/.
  • Decision D (2026-05-23) — Re-pin eslint from ^10.4.0 back to ^9.39.4. Risk R15 fired empirically on the second implementer pass: TypeError: scopeManager.addGlobals is not a function at eslint/lib/languages/js/source-code/source-code.js:221. @typescript-eslint/scope-manager@8.59.4 (bundled by eslint-config-next@16.2.6) was published before ESLint v10's Feb-2026 GA and doesn't implement v10's new addGlobals API. Partially reverses Decision B's "avoid back-to-back convoys" rationale, but B was correct given Boot-the-brief evidence at the time — empirical evidence from the pass-2 lint run reversed it.

Follow-up convoys formally queued

  • bump-eslint-10 — upstream-blocked. Prerequisites: typescript-eslint ships a v10-tested release, AND eslint-config-next bundles it. When both land, this is a one-line package.json pin change + a re-lint to confirm. eslint.config.mjs shape is forward-compatible.
  • bump-typescript-6 — upstream-blocked. typescript@6.0.3 is on latest, but typescript-eslint@8.59.4's peer range (>=4.8.4 <6.1.0) accepts but doesn't certify v6. Will likely couple with bump-eslint-10 since both depend on typescript-eslint shipping a fresh release.

Other observations (not acted on; surfaced for future convoys)

  • pages/signup.js:26 calls generateRandomAvatar() inside useEffect before the const generateRandomAvatar = … declaration on line 29. Works today by closure-binding-by-reference, but a real footgun that eslint-plugin-react-hooks@7.1.1 now catches. Pickup: fix-lint-baseline.
  • Browserslist DB is 11 months stale (npx update-browserslist-db@latest printed during build).
  • setup-node@v4 with node-version: '20' is a moving target. An engines block ("node": ">=20.19.0") would give npm install-time enforcement. Pickup: next CI-touching convoy.
  • npm install audit: 6 high + 5 moderate vulnerabilities in the dep tree (unchanged by this bump — pre-existing). Pickup: future audit convoy.

What audit fan-out should check

Per the convoy file's ## Roles invoked § 3, after this PR opens the audit cohort runs in parallel under group id audit-bump-next-js-<this-pr>:

  • role-reviewer — correctness, regression risk, convention compliance.
  • role-design-system-auditor — verify CSS / theming / token usage still renders correctly under Turbopack-by-default (Risk R1 didn't fire in build; smoke-test renders to confirm runtime parity).
  • role-a11y-auditor — verify accessibility didn't regress (Next.js 16 may change focus-management defaults).

Made with Cursor

<!-- pipeline: brief=1, convoy=bump-next-js --> Closes P0 ship-blocker **#8** from `.convoys/ship-readiness.md`. Vercel has been refusing every deployment since 2025-08-01 with `"Vulnerable version of Next.js detected, please update immediately"` — this bump clears that platform gate and unblocks every downstream `preview-smoke` and `visual-diff` gate that depends on a live preview URL. ## Summary - Bumps `next` 15.4.3 → 16.2.6 to clear Vercel's platform security gate. - Migrates `images.domains` (removed in 16) → `images.remotePatterns` in `next.config.js`, preserving the three CDN hosts (Scryfall, Pokémon TCG, Lorcana API) for the eventual `next/image` adoption. - Migrates the lint toolchain from ESLint v8 + legacy `.eslintrc.json` to **ESLint v9.39.4** (npm `maintenance` tag) + flat config (`eslint.config.mjs`), bumps `eslint-config-next` to 16.2.6 to match `next`, replaces `next lint` (removed in 16) with `eslint .` in `scripts.lint`, and adds **`typescript@^5.9.3`** as a devDep so `eslint-config-next@16`'s bundled `typescript-eslint` chain can satisfy its hard `require('typescript')` at module load. The v9 pin (rather than v10 latest) is a deliberate fallback per Decision D after Risk R15 fired empirically — see Notes below for the full audit trail. ## Files changed - **`package.json`** — `next` `^15.4.2` → `^16.2.6`; `eslint` `^8` → `^9.39.4` (Decision D fallback after R15 fired empirically on v10); `eslint-config-next` `15.4.2` → `^16.2.6`; **`typescript` newly added at `^5.9.3`** (resolved `typescript@5.9.3`); `scripts.lint` `next lint` → `eslint .`. React stays at `^18.3.1` per Risk R7. No `engines` block, no `tsconfig.json`. - **`package-lock.json`** — regenerated by `npm install`. Stays on the v9 dep tree: `@eslint/eslintrc@3.3.5` is **retained** as a v9 transitive dep (v10 dropped it; v10's removal is deferred to the queued `bump-eslint-10` follow-up convoy). `typescript@5.9.3` adds a single subtree (compiler bundle, zero transitive deps). - **`next.config.js`** — `images.domains` → `images.remotePatterns` (verbatim shape from Next 16 upgrade guide). - **`eslint.config.mjs`** (new) — flat config using the verbatim shape from the official Next.js docs (`defineConfig([...nextVitals, globalIgnores([...])])`). Verified compatible on both ESLint v9.39.4 and v10.4.0; the `bump-eslint-10` follow-up convoy will not need to change this file. `globalIgnores` covers `.next/**`, `node_modules/**`, `out/**`, `build/**`, `next-env.d.ts`, `scripts/migrations/**`. - **`.eslintrc.json`** (deleted) — 40-byte legacy stub `{"extends": "next/core-web-vitals"}`; `eslint-config-next@16` is flat-config-only. ## Acceptance criteria - [x] `dependencies.next` is `^16.2.6` (resolved `next@16.2.6`) - [x] `react` / `react-dom` unchanged at `^18.3.1` - [x] `devDependencies.eslint` is `^9.39.4` (resolved `eslint@9.39.4`) — Decision D fallback - [x] `devDependencies.eslint-config-next` is `^16.2.6` (resolved `16.2.6`) - [x] `devDependencies.typescript` is `^5.9.3` (resolved `5.9.3`) — Decision C - [x] `scripts.lint` is `"eslint ."` - [x] No new dependencies beyond the four authorized; no `engines` block; no `tsconfig.json`; no `.js` → `.ts` renames; no `// @ts-check`; no `.d.ts` files - [x] `package-lock.json` regenerated (no hand edits); `@eslint/eslintrc@3.3.5` retained per v9 dep tree - [x] `npm install` completed cleanly with no `ERESOLVE` and no deprecation warnings on the four target packages - [x] `next.config.js` migrated to `images.remotePatterns` with the verbatim three-host shape - [x] `.eslintrc.json` deleted - [x] `eslint.config.mjs` created with the verbatim shape (no rule tuning, no `parserOptions`, no `settings`) - [x] `npm run build` exits 0; banner reads `▲ Next.js 16.2.6 (Turbopack)`; no `images.domains` deprecation warning at startup; all 23 static pages prerender, all 47 API routes compile - [x] `npm run lint` runs to completion under ESLint v9.39.4. Exit code 1; **128 problems (81 errors, 47 warnings)** — the new authoritative baseline for `fix-lint-baseline`. CI's `|| true` wrapper tolerates the non-zero exit. No module-resolution or scope-manager crash. All findings are in pre-existing `pages/`, `components/`, `tailwind.config.js`, `postcss.config.js` source — none in files touched by this PR. - [ ] No tests added — `tcg-vault` has no test runner installed yet (tracked under `adopt-vitest`); manual smoke per `TESTING_GUIDE.md` is the verification mechanism. - [ ] No `<Image>` migrations (none exist in `tcg-vault` — re-verified by `rg "from ['\"]next/image['\"]"`, zero hits) - [ ] No `--webpack` opt-out (not needed; no `webpack:` block in `next.config.js`; Turbopack-by-default runs clean) - [ ] No edits outside the brief's `files:` / `deletes:` lists ## Test plan 1. `npm install` — exits clean with no `ERESOLVE` warnings. 2. `npm run build` — exits 0 cleanly on Turbopack. 3. **`npm run dev` — manual smoke per `TESTING_GUIDE.md`:** confirm `/`, `/login`, `/signup`, `/cards`, `/collections` render without runtime errors, hydration warnings, or 500s. Confirm no `images.domains` deprecation warning at startup. 4. `npm run lint` — exit 1 with 128 problems (81 errors, 47 warnings). Tolerated by CI's `|| true` wrapper. Baseline reduction is `fix-lint-baseline`'s charter. 5. **Push branch → confirm Vercel preview moves from `Error: Vulnerable version of Next.js detected` to `Success`,** capture the preview URL in this PR for the audit fan-out. ## Notes ### Decision audit trail (full in `.convoys/bump-next-js.md` § Decisions) - **Decision A (2026-05-23)** — Expand convoy scope to include ESLint v8 → v9 + flat-config migration. `eslint-config-next@16` requires `eslint >=9.0.0`; the convoy file's prescribed `^16` bump for `eslint-config-next` couldn't co-exist with `eslint@^8`. Adopted flat config natively (no `FlatCompat` shim — `eslint-config-next@16.2.6` ships native flat-config exports, verified by tarball extraction). - **Decision B (2026-05-23)** — Pivot `eslint` from `^9.39.4` to `^10.4.0`. ESLint v10 was released to `latest` between convoy authoring and gate-1 review. Pivoted to avoid a back-to-back `bump-eslint-10` convoy. - **Decision C (2026-05-23)** — Add `typescript@^5.9.3` as a devDep. The `peerDependenciesMeta.typescript.optional: true` annotation on `eslint-config-next@16.2.6` only suppresses npm's install-time warning; `@typescript-eslint/typescript-estree` hard-`require`s `typescript` at runtime via 9 unconditional `require('typescript')` sites in its `dist/`. - **Decision D (2026-05-23)** — Re-pin `eslint` from `^10.4.0` back to `^9.39.4`. Risk R15 fired empirically on the second implementer pass: `TypeError: scopeManager.addGlobals is not a function` at `eslint/lib/languages/js/source-code/source-code.js:221`. `@typescript-eslint/scope-manager@8.59.4` (bundled by `eslint-config-next@16.2.6`) was published before ESLint v10's Feb-2026 GA and doesn't implement v10's new `addGlobals` API. Partially reverses Decision B's "avoid back-to-back convoys" rationale, but B was correct given Boot-the-brief evidence at the time — empirical evidence from the pass-2 lint run reversed it. ### Follow-up convoys formally queued - **`bump-eslint-10`** — upstream-blocked. Prerequisites: `typescript-eslint` ships a v10-tested release, AND `eslint-config-next` bundles it. When both land, this is a one-line `package.json` pin change + a re-lint to confirm. `eslint.config.mjs` shape is forward-compatible. - **`bump-typescript-6`** — upstream-blocked. `typescript@6.0.3` is on `latest`, but `typescript-eslint@8.59.4`'s peer range (`>=4.8.4 <6.1.0`) accepts but doesn't certify v6. Will likely couple with `bump-eslint-10` since both depend on `typescript-eslint` shipping a fresh release. ### Other observations (not acted on; surfaced for future convoys) - `pages/signup.js:26` calls `generateRandomAvatar()` inside `useEffect` before the `const generateRandomAvatar = …` declaration on line 29. Works today by closure-binding-by-reference, but a real footgun that `eslint-plugin-react-hooks@7.1.1` now catches. Pickup: `fix-lint-baseline`. - Browserslist DB is 11 months stale (`npx update-browserslist-db@latest` printed during build). - `setup-node@v4` with `node-version: '20'` is a moving target. An `engines` block (`"node": ">=20.19.0"`) would give npm install-time enforcement. Pickup: next CI-touching convoy. - `npm install` audit: 6 high + 5 moderate vulnerabilities in the dep tree (unchanged by this bump — pre-existing). Pickup: future audit convoy. ### What audit fan-out should check Per the convoy file's `## Roles invoked` § 3, after this PR opens the audit cohort runs in parallel under group id `audit-bump-next-js-<this-pr>`: - **`role-reviewer`** — correctness, regression risk, convention compliance. - **`role-design-system-auditor`** — verify CSS / theming / token usage still renders correctly under Turbopack-by-default (Risk R1 didn't fire in build; smoke-test renders to confirm runtime parity). - **`role-a11y-auditor`** — verify accessibility didn't regress (Next.js 16 may change focus-management defaults). Made with [Cursor](https://cursor.com)
vercel[bot] commented 2026-05-23 03:04:51 -04:00 (Migrated from github.com)

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
tcg-vault Ready Ready Preview, Comment May 23, 2026 7:04am

Request Review

[vc]: #U8n1PXdSavGcEHm6NNEt8Wqmp4e0HyDHq7brYWS+VIs=:eyJpc01vbm9yZXBvIjp0cnVlLCJ0eXBlIjoiZ2l0aHViIiwicHJvamVjdHMiOlt7Im5hbWUiOiJ0Y2ctdmF1bHQiLCJwcm9qZWN0SWQiOiJwcmpfRjZXOEVvRkd3Y0g3aWVGcnRvRlNlOXdVVkFhNSIsImxpdmVGZWVkYmFjayI6eyJyZXNvbHZlZCI6MCwidW5yZXNvbHZlZCI6MCwidG90YWwiOjAsImxpbmsiOiJ0Y2ctdmF1bHQtZ2l0LWNvbnZveS1idW1wLW5leHQtanMtcmFuZGFsbC1zdGlsbHdlbGxzLXByb2plY3RzLnZlcmNlbC5hcHAifSwiaW5zcGVjdG9yVXJsIjoiaHR0cHM6Ly92ZXJjZWwuY29tL3JhbmRhbGwtc3RpbGx3ZWxscy1wcm9qZWN0cy90Y2ctdmF1bHQvOUNoWlc4NldmSmRqOUg5dEE1UWJuVGJRZjliUyIsInByZXZpZXdVcmwiOiJ0Y2ctdmF1bHQtZ2l0LWNvbnZveS1idW1wLW5leHQtanMtcmFuZGFsbC1zdGlsbHdlbGxzLXByb2plY3RzLnZlcmNlbC5hcHAiLCJuZXh0Q29tbWl0U3RhdHVzIjoiREVQTE9ZRUQifV0sInJlcXVlc3RSZXZpZXdVcmwiOiJodHRwczovL3ZlcmNlbC5jb20vdmVyY2VsLWFnZW50L3JlcXVlc3QtcmV2aWV3P293bmVyPXZhcnV0YXN1JnJlcG89dGNnLXZhdWx0JnByPTQifQ== The latest updates on your projects. Learn more about [Vercel for GitHub](https://vercel.link/github-learn-more). | Project | Deployment | Actions | Updated (UTC) | | :--- | :----- | :------ | :------ | | [tcg-vault](https://vercel.com/randall-stillwells-projects/tcg-vault) | ![Ready](https://vercel.com/static/status/ready.svg) [Ready](https://vercel.com/randall-stillwells-projects/tcg-vault/9ChZW86WfJdj9H9tA5QbnTbQf9bS) | [Preview](https://tcg-vault-git-convoy-bump-next-js-randall-stillwells-projects.vercel.app), [Comment](https://vercel.live/open-feedback/tcg-vault-git-convoy-bump-next-js-randall-stillwells-projects.vercel.app?via=pr-comment-feedback-link) | May 23, 2026 7:04am | <a href="https://vercel.com/vercel-agent/request-review?owner=varutasu&repo=tcg-vault&pr=4" rel="noreferrer"><picture><source media="(prefers-color-scheme: dark)" srcset="https://agents-vade-review.vercel.sh/request-review-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://agents-vade-review.vercel.sh/request-review-light.svg"><img src="https://agents-vade-review.vercel.sh/request-review-light.svg" alt="Request Review"></picture></a>
github-actions[bot] commented 2026-05-23 03:04:58 -04:00 (Migrated from github.com)

Pipeline Health

Build + CI gates

Gate Status
Vercel build (Preview) pass
CI: Lint in progress
CI: Schema map fresh skipped
Preview smoke ⏭ skipped or pending
Visual diff ⏭ skipped or pending

Build runs on Vercel; this CI runs lint and schema-map drift only (no duplicate build).

Role reports

Role Status
Reviewer report pending
A11y audit pending
Design system audit pending

See individual comments above for details. This rollup updates automatically.

<!-- pipeline-rollup --> ## Pipeline Health ### Build + CI gates | Gate | Status | | --- | --- | | Vercel build (Preview) | ✅ pass | | CI: Lint | ⏳ in progress | | CI: Schema map fresh | ❌ skipped | | Preview smoke | ⏭ skipped or pending | | Visual diff | ⏭ skipped or pending | _Build runs on Vercel; this CI runs lint and schema-map drift only (no duplicate build)._ ### Role reports | Role | Status | | --- | --- | | Reviewer report | ⏳ pending | | A11y audit | ⏳ pending | | Design system audit | ⏳ pending | See individual comments above for details. This rollup updates automatically.
varutasu commented 2026-05-23 03:07:28 -04:00 (Migrated from github.com)

Design System Audit

Check Status Count
UI files in diff N/A 0
Token violations N/A 0
Duplicate primitives N/A 0
Missing variants N/A 0
Inline styles N/A 0

Note: The standard table is N/A because no UI files are in the diff (components/, pages/, styles/, *.tsx/.jsx/.css all return zero). This is a framework bump — next.config.js, package.json, package-lock.json, eslint.config.mjs (new), .eslintrc.json (deleted). My audit here is focused on the Turbopack-by-default runtime-parity risks flagged by R1 in the convoy file.

Diff scope sanity

Verified zero UI files in the diff. git diff --name-only origin/main...origin/convoy/bump-next-js -- 'components/**' 'pages/**' 'styles/**' '*.tsx' '*.jsx' '*.css' → empty. The 5 in-scope files match the brief exactly. No scope expansion.

Other findings (Turbopack runtime-parity)

  1. PostCSS chain is Turbopack-clean. postcss.config.js uses pure ESM export default { plugins: { tailwindcss: {}, autoprefixer: {} } } — no require(), no inline plugin functions, no webpack-specific loaders. Both tailwindcss and autoprefixer are in Next 16's officially supported Turbopack PostCSS plugin matrix. No action needed; flagging because the convoy R1 explicitly asked me to verify.

  2. Tailwind config is Turbopack-clean. tailwind.config.js uses pure ESM export default with content, theme.extend, and an empty plugins: []. Zero require() calls. Zero JS-function plugins. The hearth color palette (flame, ember, gold, wood, etc.) is plain object literals — no dynamic evaluation. Turbopack will process this identically to webpack.

  3. CSS custom properties resolve framework-agnostically. styles/globals.css defines the entire theme-token vocabulary (--bg-primary, --text-primary, --text-accent, --accent-ember, --accent-flame, --accent-gold, --accent-wood, --gradient-primary, etc.) as pure CSS variables in :root and [data-theme="dark"]. No PostCSS plugins beyond Tailwind/autoprefixer touch them. Variable cascade and [data-theme="dark"] attribute-selector flipping (driven by lib/theme-context.js) is browser-native, not bundler-mediated — Turbopack cannot break it.

  4. Only one @apply site, in plain Tailwind 3.4 form. 13 @apply usages in styles/globals.css (all in component classes like .btn-primary, .input-field, .card, .theme-toggle, .search-bar, .action-btn-primary/secondary). These compile through the Tailwind v3 PostCSS plugin — same execution path under Turbopack as under webpack. No v4-style @apply issues to worry about (still on Tailwind 3.4.17).

  5. rgba(var(--bg-secondary-rgb), 0.95) pattern at line 818 is framework-agnostic. The mobile-nav backdrop fallback uses CSS variable interpolation inside rgba(). This is pure CSS — browser parses it, no bundler involvement. Will work identically under Turbopack. (Worth knowing for the smoker: this is the one place where a dark-mode theme-token regression would visually surface on mobile widths under 768px.)

  6. next/font confirmed zero usage. rg "from ['\"]next/font['\"]" returns zero hits. Body uses a raw system-font stack inline in styles/globals.css:7-9. Architect's Boot-the-brief finding is verified. No font-loading parity risk under Turbopack.

  7. next/image confirmed zero usage. rg "from ['\"]next/image['\"]" returns zero hits. PR's images.remotePatterns migration is preemptive (for future next/image adoption per the convoy). No <Image> regression risk in this PR.

  8. .module.css confirmed zero usage. No CSS Modules files anywhere in the repo. The only CSS file is styles/globals.css, imported once in pages/_app.js-style entrypoint. Turbopack's CSS Modules handling is irrelevant here.

  9. No CSS @import statements. Only @tailwind base/components/utilities directives — those are Tailwind PostCSS plugin directives, not CSS-spec @import. Turbopack's @import resolution can't regress what isn't there.

Context for future audits (NOT this PR)

  • components/Layout.js brand-name inconsistency ("Deck Hearth" vs "TCG Vault" in seed data) — AGENTS.md gotcha #8 + repo branding note. Not in this PR's diff; flagging for the next UI-touching convoy.
  • components/Layout.js hardcoded default-user impersonation (user = { email: 'me@randallstillwell.com', role: 'user' }) — AGENTS.md gotcha #8. Not in this PR's diff.
  • Hardcoded hex colors inside styles/globals.css glow effects (e.g. rgba(255, 215, 0, 0.4) in .card-mythic-glow, gradient stops in .gradient-text-*) — these are token violations by the role spec's strict definition, but they're (a) pre-existing, (b) not in this diff, and (c) the convoy's no-scope-expansion guardrail explicitly forbids touching styles/. Flagging for a future tokenize-glow-effects convoy if/when one gets prioritized.

For the smoker (manual verification in the PR's Test plan)

The brief's "Local verification" already covers route smoke (/, /login, /signup, /cards, /collections). My added asks for the visual-parity check:

  1. Toggle dark mode on every smoked route. Click the theme toggle (top-right header) on each of /, /login, /signup, /cards, /collections. Confirm:

    • Background flips from cream (#fefcf8) to charcoal (#1a0f0a).
    • Body text flips from dark-chocolate (#2d1810) to warm-white (#fff8f0).
    • The orange ember accent (--accent-ember) on active nav-items / buttons stays visually identical (it's the same hex #d84315 in both themes).
    • This validates --bg-primary / --text-primary / --accent-ember CSS-variable cascade resolves under Turbopack.
  2. Resize the browser to <768px width and re-toggle dark mode. The mobile-nav backdrop (.mobile-nav-backdrop at styles/globals.css:807-819) uses rgba(var(--bg-secondary-rgb), 0.95) inside a @supports not (backdrop-filter) fallback. If --bg-secondary-rgb regresses, the bottom nav will go transparent or wrong-color. Sanity-check that the bottom nav stays opaque-ish with the dark-cream tint matching theme.

  3. Hover over a card on /cards. The rarity-glow effects (.rarity-glow-mythic/rare/uncommon/enchanted at lines 569-697) use hardcoded rgba() box-shadow values, not theme tokens — so they should look identical to pre-bump. If they're missing or dimmed, that's a Turbopack @keyframes / animation-chunking regression worth escalating.

  4. No browser-console warnings about images.domains during any of the above. The next.config.js migration was the whole point.

  5. Check the dev-server boot banner. Brief expects ▲ Next.js 16.2.6 (Turbopack). If you see (Webpack) instead, the Turbopack-by-default assumption was wrong and R1's fallback context applies.

If any of (1)–(3) regress visually, that's a real Turbopack CSS-chunking / theme-token bug and should be escalated — the design system itself is unchanged in this PR, so any visual delta is bundler-attributable.

## Design System Audit | Check | Status | Count | | --- | --- | --- | | UI files in diff | ✅ N/A | 0 | | Token violations | ✅ N/A | 0 | | Duplicate primitives | ✅ N/A | 0 | | Missing variants | ✅ N/A | 0 | | Inline styles | ✅ N/A | 0 | **Note:** The standard table is N/A because no UI files are in the diff (`components/`, `pages/`, `styles/`, `*.tsx/.jsx/.css` all return zero). This is a framework bump — `next.config.js`, `package.json`, `package-lock.json`, `eslint.config.mjs` (new), `.eslintrc.json` (deleted). My audit here is focused on the **Turbopack-by-default runtime-parity risks** flagged by R1 in the convoy file. ### Diff scope sanity ✅ **Verified zero UI files in the diff.** `git diff --name-only origin/main...origin/convoy/bump-next-js -- 'components/**' 'pages/**' 'styles/**' '*.tsx' '*.jsx' '*.css'` → empty. The 5 in-scope files match the brief exactly. No scope expansion. ### Other findings (Turbopack runtime-parity) 1. **PostCSS chain is Turbopack-clean.** `postcss.config.js` uses pure ESM `export default { plugins: { tailwindcss: {}, autoprefixer: {} } }` — no `require()`, no inline plugin functions, no webpack-specific loaders. Both `tailwindcss` and `autoprefixer` are in Next 16's officially supported Turbopack PostCSS plugin matrix. No action needed; flagging because the convoy R1 explicitly asked me to verify. 2. **Tailwind config is Turbopack-clean.** `tailwind.config.js` uses pure ESM `export default` with `content`, `theme.extend`, and an **empty `plugins: []`**. Zero `require()` calls. Zero JS-function plugins. The `hearth` color palette (`flame`, `ember`, `gold`, `wood`, etc.) is plain object literals — no dynamic evaluation. Turbopack will process this identically to webpack. 3. **CSS custom properties resolve framework-agnostically.** `styles/globals.css` defines the entire theme-token vocabulary (`--bg-primary`, `--text-primary`, `--text-accent`, `--accent-ember`, `--accent-flame`, `--accent-gold`, `--accent-wood`, `--gradient-primary`, etc.) as pure CSS variables in `:root` and `[data-theme="dark"]`. No PostCSS plugins beyond Tailwind/autoprefixer touch them. Variable cascade and `[data-theme="dark"]` attribute-selector flipping (driven by `lib/theme-context.js`) is browser-native, not bundler-mediated — Turbopack cannot break it. 4. **Only one `@apply` site, in plain Tailwind 3.4 form.** 13 `@apply` usages in `styles/globals.css` (all in component classes like `.btn-primary`, `.input-field`, `.card`, `.theme-toggle`, `.search-bar`, `.action-btn-primary/secondary`). These compile through the Tailwind v3 PostCSS plugin — same execution path under Turbopack as under webpack. No v4-style `@apply` issues to worry about (still on Tailwind 3.4.17). 5. **`rgba(var(--bg-secondary-rgb), 0.95)` pattern at line 818 is framework-agnostic.** The mobile-nav backdrop fallback uses CSS variable interpolation inside `rgba()`. This is pure CSS — browser parses it, no bundler involvement. Will work identically under Turbopack. (Worth knowing for the smoker: this is the one place where a dark-mode theme-token regression would visually surface on mobile widths under 768px.) 6. **`next/font` confirmed zero usage.** `rg "from ['\"]next/font['\"]"` returns zero hits. Body uses a raw system-font stack inline in `styles/globals.css:7-9`. Architect's Boot-the-brief finding is verified. No font-loading parity risk under Turbopack. 7. **`next/image` confirmed zero usage.** `rg "from ['\"]next/image['\"]"` returns zero hits. PR's `images.remotePatterns` migration is preemptive (for future `next/image` adoption per the convoy). No `<Image>` regression risk in this PR. 8. **`.module.css` confirmed zero usage.** No CSS Modules files anywhere in the repo. The only CSS file is `styles/globals.css`, imported once in `pages/_app.js`-style entrypoint. Turbopack's CSS Modules handling is irrelevant here. 9. **No CSS `@import` statements.** Only `@tailwind base/components/utilities` directives — those are Tailwind PostCSS plugin directives, not CSS-spec `@import`. Turbopack's `@import` resolution can't regress what isn't there. ### Context for future audits (NOT this PR) - **`components/Layout.js` brand-name inconsistency** ("Deck Hearth" vs "TCG Vault" in seed data) — AGENTS.md gotcha #8 + repo branding note. Not in this PR's diff; flagging for the next UI-touching convoy. - **`components/Layout.js` hardcoded default-user impersonation** (`user = { email: 'me@randallstillwell.com', role: 'user' }`) — AGENTS.md gotcha #8. Not in this PR's diff. - **Hardcoded hex colors inside `styles/globals.css` glow effects** (e.g. `rgba(255, 215, 0, 0.4)` in `.card-mythic-glow`, gradient stops in `.gradient-text-*`) — these are token violations by the role spec's strict definition, but they're (a) pre-existing, (b) not in this diff, and (c) the convoy's no-scope-expansion guardrail explicitly forbids touching `styles/`. Flagging for a future `tokenize-glow-effects` convoy if/when one gets prioritized. ### For the smoker (manual verification in the PR's Test plan) The brief's "Local verification" already covers route smoke (`/`, `/login`, `/signup`, `/cards`, `/collections`). My added asks for the visual-parity check: 1. **Toggle dark mode on every smoked route.** Click the theme toggle (top-right header) on each of `/`, `/login`, `/signup`, `/cards`, `/collections`. Confirm: - Background flips from cream (`#fefcf8`) to charcoal (`#1a0f0a`). - Body text flips from dark-chocolate (`#2d1810`) to warm-white (`#fff8f0`). - The orange ember accent (`--accent-ember`) on active nav-items / buttons stays visually identical (it's the same hex `#d84315` in both themes). - This validates `--bg-primary` / `--text-primary` / `--accent-ember` CSS-variable cascade resolves under Turbopack. 2. **Resize the browser to <768px width and re-toggle dark mode.** The mobile-nav backdrop (`.mobile-nav-backdrop` at `styles/globals.css:807-819`) uses `rgba(var(--bg-secondary-rgb), 0.95)` inside a `@supports not (backdrop-filter)` fallback. If `--bg-secondary-rgb` regresses, the bottom nav will go transparent or wrong-color. Sanity-check that the bottom nav stays opaque-ish with the dark-cream tint matching theme. 3. **Hover over a card on `/cards`.** The rarity-glow effects (`.rarity-glow-mythic/rare/uncommon/enchanted` at lines 569-697) use hardcoded `rgba()` `box-shadow` values, not theme tokens — so they should look identical to pre-bump. If they're missing or dimmed, that's a Turbopack `@keyframes` / animation-chunking regression worth escalating. 4. **No browser-console warnings about `images.domains`** during any of the above. The `next.config.js` migration was the whole point. 5. **Check the dev-server boot banner.** Brief expects `▲ Next.js 16.2.6 (Turbopack)`. If you see `(Webpack)` instead, the Turbopack-by-default assumption was wrong and R1's fallback context applies. If any of (1)–(3) regress visually, that's a real Turbopack CSS-chunking / theme-token bug and should be escalated — the design system itself is unchanged in this PR, so any visual delta is bundler-attributable.
varutasu commented 2026-05-23 03:08:10 -04:00 (Migrated from github.com)

A11y Audit

Scope note: This is a framework-bump PR (Next 15 → 16, ESLint v8 → v9 flat config, typescript devDep). The PR diff includes zero files under components/ or pages/. The standard static-analysis checklist (labels, keyboard nav, ARIA, semantic HTML) is non-applicable because nothing in the rendered UI surface was touched. This audit instead verifies that Next 16's documented behavior changes don't have a latent a11y impact on tcg-vault, and tells the smoke-tester what to look for at preview-deploy time.

Check Status Count
Labels N/A 0
Keyboard nav N/A 0
Focus management (static) N/A 0
Color contrast N/A 0
Semantic HTML N/A 0
ARIA correctness N/A 0
UX constraint match N/A 0
Next 16 a11y-relevant behavior changes 1 reviewed, 0 affect this repo

Critical (must fix)

  • None.
  • None for this PR.

Notes — what the manual smoke step should verify on the Vercel preview

The convoy file explicitly listed this role because "Next.js 16 may change focus-management defaults" (.convoys/bump-next-js.md § Roles invoked). I read the Next.js 16 blog post and the Pages-Router-applicable parts of the upgrade guide. Findings:

  1. No documented changes to <Link> focus behavior, keyboard activation, or focus-on-route-transition semantics. Next 16's routing changes are network-layer (layout deduplication, incremental prefetching) — they don't alter the rendered keyboard / focus semantics of next/link or useRouter. tcg-vault has 15 next/link callers (16 if you count components/Layout.js.backup, which is excluded per .cursor/rules/no-go-zones.mdc); none rely on router.events.on/off/emit (zero hits in source), so the prefetch-cache rewrite has no router-event-listener dependency to break.
  2. One Next 16 a11y-adjacent behavior change exists, but it's a no-op for tcg-vault: the auto-override of scroll-behavior: smooth during SPA route transitions was removed in 16 (upgrade guide § "Scroll Behavior Override"). rg "scroll-behavior|scrollBehavior" returns zero hits in styles/, components/, and pages/, so this codebase never relied on the override. No data-scroll-behavior="smooth" opt-back-in needed. Smoke tester does NOT need to verify smooth-scroll on route transitions — there's nothing to verify.
  3. Pages Router stability holds. The convoy notes Pages Router is "intentionally more stable than App Router across major bumps." Verified: every breaking change in the upgrade guide (async params / searchParams, async cookies() / headers() / draftMode(), proxy.ts rename, parallel-route default.js requirement, Cache Components) is App-Router-only. Pages Router getServerSideProps / getStaticProps / req / res semantics are unchanged. None of tcg-vault's ~30 API handlers or page-level data-fetching functions are affected.
  4. prefers-reduced-motion is honored in styles/globals.css:261 (@media (prefers-reduced-motion: reduce) block exists). Baseline motion-preference handling is intact regardless of Next version.

What the smoke-tester should still actively confirm on the Vercel preview (per the PR's Test plan, manually per TESTING_GUIDE.md):

  • Keyboard activation of <Link> elements still works. Tab to a navigation link in the header (components/Layout.js) and press Enter — the route should navigate. Tab to a card tile (pages/cards.js, pages/collections.js) and press Enter — the detail route should navigate. This is the smoke proxy for "Next 16's <Link> keyboard semantics didn't silently regress despite no documented change."
  • Focus on route transitions hasn't regressed. After clicking a <Link>, verify focus lands somewhere sensible (typically <body> or the next page's first focusable element) — not nowhere, not trapped on the previous page's link. This is the area the convoy was most worried about; the upgrade guide doesn't document a change here, but a fresh confirmation costs ~30 seconds and is worth doing on //cards/collections → back.
  • Mobile navigation drawer (components/MobileNavigation.js) still keyboard-accessible. Open with the trigger, Tab through items, Esc to close — confirm focus returns to the trigger on close. Again no documented Next-16-side change, but a sanity check.
  • prefers-reduced-motion still applies. With OS-level reduced-motion turned on, navigate between routes — confirm no new animations slip in via Next 16's routing changes (e.g. View Transitions are a React 19.2 feature available on App Router; tcg-vault is on Pages Router + React 18, so this should not be a concern, but worth eyeballing).

Pre-existing a11y observations (for fix-lint-baseline or a future a11y convoy — NOT for this PR)

Noted while scanning for context:

  • <img> alt coverage is complete — every <img> tag in components/ and pages/ has an alt= attribute (verified across 19 files). No critical missing-alt bugs. However: a handful of generic alts (alt="Profile" in pages/signup.js:262 and pages/profile.js:302, alt="Preview" in components/UploadImageModal.js:162) could be improved with more descriptive text or alt="" if purely decorative. Low priority; not this PR's job.
  • eslint-config-next@16 bundles eslint-plugin-jsx-a11y@^6.10.0, which is now active in the flat-config setup landing in this PR. Once fix-lint-baseline runs against the v9 baseline, expect a non-zero count of jsx-a11y/* warnings to surface across the existing pages/ and components/ surface. That's exactly the convoy that should triage them — do not preemptively fix in this PR.

Summary

No a11y regression risk introduced by this diff. The static checklist is N/A (no UI files). The one Next-16 behavior change with a11y adjacency (scroll-behavior override removal) is a no-op for tcg-vault. The convoy's stated concern about focus-management defaults does not match any documented Next 16 change; the smoke-tester's keyboard-and-focus sanity pass on the Vercel preview is the right verification mechanism.

## A11y Audit **Scope note:** This is a framework-bump PR (Next 15 → 16, ESLint v8 → v9 flat config, `typescript` devDep). The PR diff includes zero files under `components/` or `pages/`. The standard static-analysis checklist (labels, keyboard nav, ARIA, semantic HTML) is **non-applicable** because nothing in the rendered UI surface was touched. This audit instead verifies that Next 16's documented behavior changes don't have a latent a11y impact on `tcg-vault`, and tells the smoke-tester what to look for at preview-deploy time. | Check | Status | Count | | --- | --- | --- | | Labels | N/A | 0 | | Keyboard nav | N/A | 0 | | Focus management (static) | N/A | 0 | | Color contrast | N/A | 0 | | Semantic HTML | N/A | 0 | | ARIA correctness | N/A | 0 | | UX constraint match | N/A | 0 | | Next 16 a11y-relevant behavior changes | ✅ | 1 reviewed, 0 affect this repo | ### Critical (must fix) - None. ### Warnings (recommended) - None for this PR. ### Notes — what the manual smoke step should verify on the Vercel preview The convoy file explicitly listed this role because "Next.js 16 may change focus-management defaults" (`.convoys/bump-next-js.md` § Roles invoked). I read the [Next.js 16 blog post](https://nextjs.org/blog/next-16) and the [Pages-Router-applicable parts of the upgrade guide](https://nextjs.org/docs/app/guides/upgrading/version-16). Findings: 1. **No documented changes to `<Link>` focus behavior, keyboard activation, or focus-on-route-transition semantics.** Next 16's routing changes are network-layer (layout deduplication, incremental prefetching) — they don't alter the rendered keyboard / focus semantics of `next/link` or `useRouter`. `tcg-vault` has 15 `next/link` callers (16 if you count `components/Layout.js.backup`, which is excluded per `.cursor/rules/no-go-zones.mdc`); none rely on `router.events.on/off/emit` (zero hits in source), so the prefetch-cache rewrite has no router-event-listener dependency to break. 2. **One Next 16 a11y-adjacent behavior change exists, but it's a no-op for `tcg-vault`:** the auto-override of `scroll-behavior: smooth` during SPA route transitions was removed in 16 (upgrade guide § "Scroll Behavior Override"). `rg "scroll-behavior|scrollBehavior"` returns zero hits in `styles/`, `components/`, and `pages/`, so this codebase never relied on the override. No `data-scroll-behavior="smooth"` opt-back-in needed. **Smoke tester does NOT need to verify smooth-scroll on route transitions** — there's nothing to verify. 3. **Pages Router stability holds.** The convoy notes Pages Router is "intentionally more stable than App Router across major bumps." Verified: every breaking change in the upgrade guide (async `params` / `searchParams`, async `cookies()` / `headers()` / `draftMode()`, `proxy.ts` rename, parallel-route `default.js` requirement, Cache Components) is App-Router-only. Pages Router `getServerSideProps` / `getStaticProps` / `req` / `res` semantics are unchanged. None of `tcg-vault`'s ~30 API handlers or page-level data-fetching functions are affected. 4. **`prefers-reduced-motion` is honored in `styles/globals.css:261`** (`@media (prefers-reduced-motion: reduce)` block exists). Baseline motion-preference handling is intact regardless of Next version. **What the smoke-tester should still actively confirm on the Vercel preview** (per the PR's Test plan, manually per `TESTING_GUIDE.md`): - **Keyboard activation of `<Link>` elements still works.** Tab to a navigation link in the header (`components/Layout.js`) and press Enter — the route should navigate. Tab to a card tile (`pages/cards.js`, `pages/collections.js`) and press Enter — the detail route should navigate. This is the smoke proxy for "Next 16's `<Link>` keyboard semantics didn't silently regress despite no documented change." - **Focus on route transitions hasn't regressed.** After clicking a `<Link>`, verify focus lands somewhere sensible (typically `<body>` or the next page's first focusable element) — not nowhere, not trapped on the previous page's link. This is the area the convoy was most worried about; the upgrade guide doesn't document a change here, but a fresh confirmation costs ~30 seconds and is worth doing on `/` → `/cards` → `/collections` → back. - **Mobile navigation drawer (`components/MobileNavigation.js`) still keyboard-accessible.** Open with the trigger, Tab through items, Esc to close — confirm focus returns to the trigger on close. Again no documented Next-16-side change, but a sanity check. - **`prefers-reduced-motion` still applies.** With OS-level reduced-motion turned on, navigate between routes — confirm no new animations slip in via Next 16's routing changes (e.g. View Transitions are a React 19.2 feature available on App Router; tcg-vault is on Pages Router + React 18, so this should not be a concern, but worth eyeballing). ### Pre-existing a11y observations (for `fix-lint-baseline` or a future a11y convoy — NOT for this PR) Noted while scanning for context: - `<img>` alt coverage is complete — every `<img>` tag in `components/` and `pages/` has an `alt=` attribute (verified across 19 files). No critical missing-alt bugs. **However:** a handful of generic alts (`alt="Profile"` in `pages/signup.js:262` and `pages/profile.js:302`, `alt="Preview"` in `components/UploadImageModal.js:162`) could be improved with more descriptive text or `alt=""` if purely decorative. Low priority; not this PR's job. - `eslint-config-next@16` bundles `eslint-plugin-jsx-a11y@^6.10.0`, which is now active in the flat-config setup landing in this PR. Once `fix-lint-baseline` runs against the v9 baseline, expect a non-zero count of `jsx-a11y/*` warnings to surface across the existing `pages/` and `components/` surface. That's exactly the convoy that should triage them — **do not preemptively fix in this PR.** ### Summary ✅ No a11y regression risk introduced by this diff. The static checklist is N/A (no UI files). The one Next-16 behavior change with a11y adjacency (`scroll-behavior` override removal) is a no-op for `tcg-vault`. The convoy's stated concern about focus-management defaults does not match any documented Next 16 change; the smoke-tester's keyboard-and-focus sanity pass on the Vercel preview is the right verification mechanism.
varutasu commented 2026-05-23 03:08:50 -04:00 (Migrated from github.com)

Reviewer Report

Audit fan-out cohort audit-bump-next-js-4. Read against .convoys/bump-next-js/brief-1-bump-next-and-migrate-config.md (the contract) and .convoys/bump-next-js.md § Decisions A–D (the audit trail).

Check Status Notes
Scope match Implementation commit 093a288 touches exactly the 5 files in the brief: package.json (mod), package-lock.json (mod), next.config.js (mod), eslint.config.mjs (new), .eslintrc.json (deleted). PR diff vs main shows 41 files only because PR #4 is stacked on top of PRs #1 (bootstrap) / #2 (fix-auth-bypass scope) / #3 (bump-next-js scope), none of which have merged. Verified via git show --stat 093a288.
Conventions eslint.config.mjs matches the verbatim shape from the brief byte-for-byte (defineConfig + globalIgnores import from eslint/config, nextVitals from eslint-config-next/core-web-vitals, all 6 ignores in spec order). next.config.js migrates all three CDN hosts (api.scryfall.com, images.pokemontcg.io, lorcana-api.com) with https protocol per the Next 16 upgrade guide verbatim. package.json pins match Decisions A→D exactly (next@^16.2.6, eslint@^9.39.4, eslint-config-next@^16.2.6, typescript@^5.9.3). scripts.lint is eslint .. React unchanged at ^18.3.1 per R7. No engines block, no tsconfig.json, no .ts renames per the no-scope-expansion guardrails. Lockfile resolves eslint@9.39.4 (not v10), next@16.2.6, eslint-config-next@16.2.6, typescript@5.9.3, with @eslint/eslintrc@3.3.5 retained as expected on the v9 dep-tree.
Security N/A surface (no auth/API/data changes). Incidental security improvement worth noting: bumping to next@16.2.6 closes Vercel's Vulnerable version of Next.js detected platform gate, which has been rejecting every deploy since 2025-08-01. That's a real (deployment-blocking) supply-chain fix even though it reads as a routine version bump.
Regression risk low next/image is not imported anywhere (re-verified, zero hits); plain <img> usage unchanged. images.remotePatterns is pre-staged for eventual next/image adoption. Turbopack-by-default is now active — build completed clean per PR description, but runtime parity vs webpack can only be verified via dev/preview smoke (and there is no webpack: block to regress against). CSS bundling under Turbopack is a theoretical runtime concern but Tailwind/PostCSS config is unchanged.
Test coverage ⚠️ No automated tests added. tcg-vault has no test runner installed yet — adopt-vitest and adopt-playwright-smoke are queued convoys. Manual smoke per TESTING_GUIDE.md (per the brief's "Local verification" + "Test plan" sections) is the agreed verification mechanism for this convoy. Not blocking.
Documentation ⚠️ AGENTS.md line 11 still says Next.js 15; the brief explicitly defers the doc bump to a separate role-doc-writer PR that runs AFTER this audit cohort. Expected, not blocking.

Findings

  • 🟡 Suggestion (queued elsewhere): AGENTS.md § "Project overview" line 11 needs Next.js 15Next.js 16 and the JavaScript (not TypeScript) line could use a one-line footnote that typescript@^5.9.3 is installed as a devDep purely so eslint-config-next@16's bundled typescript-eslint chain can satisfy its unconditional require('typescript') at module load (Decision C / Brief 1 finding #17), and that the source code remains JS-only with no tsconfig.json. Pickup point: the queued role-doc-writer pass for this convoy.
  • 🟡 Suggestion (queued elsewhere): once adopt-vitest lands, retrofit a smoke test that exercises next.config.js parsing + asserts the three images.remotePatterns hostnames resolve, so any future regression on the pre-staged next/image config is caught at CI rather than at deploy. Brief mentions this in the convoy's "Test plan" section.
  • 🟢 Nice to have: PR diff against main is ~5K LOC because PR #4 is stacked on top of unmerged PRs #1/#2/#3. The actual Brief-1 work is one commit (093a288) with 5 files — worth calling out in the PR description's "Files changed" section (or the PR Health rollup) so a future reviewer/triager doesn't burn time scoping the review against the wrong baseline. Optional.

Approval recommendation

  • approve

Rationale: every acceptance criterion in the brief is satisfied; the four gate-1 decisions (A → ESLint flat-config expansion, B → v10 pivot, C → typescript devDep, D → v10 → v9.39.4 revert after R15 fired empirically) are correctly applied in the diff; lockfile resolutions match expectation on all four explicit pins; no scope expansion within the Brief-1 commit; Turbopack-by-default acceptance is honored (no --webpack opt-out); the Vercel "Vulnerable version of Next.js" gate is on a credible path to clearing. R15's empirical firing was diagnosed and resolved correctly — pinning v10 today would have re-broken lint and shipped a no-op || true CI signal. Per role spec, the deferred items (lint baseline, npm audit vulns, browserslist staleness, doc-writer pass, ESLint v10 / TypeScript 6 follow-ups) are all out of scope here and correctly tracked under their named follow-up convoys.

## Reviewer Report _Audit fan-out cohort `audit-bump-next-js-4`. Read against `.convoys/bump-next-js/brief-1-bump-next-and-migrate-config.md` (the contract) and `.convoys/bump-next-js.md` § Decisions A–D (the audit trail)._ | Check | Status | Notes | | --- | --- | --- | | Scope match | ✅ | Implementation commit `093a288` touches exactly the 5 files in the brief: `package.json` (mod), `package-lock.json` (mod), `next.config.js` (mod), `eslint.config.mjs` (new), `.eslintrc.json` (deleted). PR diff vs `main` shows 41 files only because PR #4 is stacked on top of PRs #1 (bootstrap) / #2 (fix-auth-bypass scope) / #3 (bump-next-js scope), none of which have merged. Verified via `git show --stat 093a288`. | | Conventions | ✅ | `eslint.config.mjs` matches the verbatim shape from the brief byte-for-byte (defineConfig + globalIgnores import from `eslint/config`, `nextVitals` from `eslint-config-next/core-web-vitals`, all 6 ignores in spec order). `next.config.js` migrates all three CDN hosts (`api.scryfall.com`, `images.pokemontcg.io`, `lorcana-api.com`) with `https` protocol per the Next 16 upgrade guide verbatim. `package.json` pins match Decisions A→D exactly (`next@^16.2.6`, `eslint@^9.39.4`, `eslint-config-next@^16.2.6`, `typescript@^5.9.3`). `scripts.lint` is `eslint .`. React unchanged at `^18.3.1` per R7. No `engines` block, no `tsconfig.json`, no `.ts` renames per the no-scope-expansion guardrails. Lockfile resolves `eslint@9.39.4` (not v10), `next@16.2.6`, `eslint-config-next@16.2.6`, `typescript@5.9.3`, with `@eslint/eslintrc@3.3.5` retained as expected on the v9 dep-tree. | | Security | ✅ | N/A surface (no auth/API/data changes). Incidental security improvement worth noting: bumping to `next@16.2.6` closes Vercel's `Vulnerable version of Next.js detected` platform gate, which has been rejecting every deploy since 2025-08-01. That's a real (deployment-blocking) supply-chain fix even though it reads as a routine version bump. | | Regression risk | low | `next/image` is not imported anywhere (re-verified, zero hits); plain `<img>` usage unchanged. `images.remotePatterns` is pre-staged for eventual `next/image` adoption. Turbopack-by-default is now active — build completed clean per PR description, but runtime parity vs webpack can only be verified via dev/preview smoke (and there is no `webpack:` block to regress against). CSS bundling under Turbopack is a theoretical runtime concern but Tailwind/PostCSS config is unchanged. | | Test coverage | ⚠️ | No automated tests added. `tcg-vault` has no test runner installed yet — `adopt-vitest` and `adopt-playwright-smoke` are queued convoys. Manual smoke per `TESTING_GUIDE.md` (per the brief's "Local verification" + "Test plan" sections) is the agreed verification mechanism for this convoy. Not blocking. | | Documentation | ⚠️ | `AGENTS.md` line 11 still says `Next.js 15`; the brief explicitly defers the doc bump to a separate `role-doc-writer` PR that runs AFTER this audit cohort. Expected, not blocking. | ### Findings - 🟡 **Suggestion** (queued elsewhere): `AGENTS.md` § "Project overview" line 11 needs `Next.js 15` → `Next.js 16` and the `JavaScript (not TypeScript)` line could use a one-line footnote that `typescript@^5.9.3` is installed as a devDep purely so `eslint-config-next@16`'s bundled `typescript-eslint` chain can satisfy its unconditional `require('typescript')` at module load (Decision C / Brief 1 finding #17), and that the source code remains JS-only with no `tsconfig.json`. Pickup point: the queued `role-doc-writer` pass for this convoy. - 🟡 **Suggestion** (queued elsewhere): once `adopt-vitest` lands, retrofit a smoke test that exercises `next.config.js` parsing + asserts the three `images.remotePatterns` hostnames resolve, so any future regression on the pre-staged `next/image` config is caught at CI rather than at deploy. Brief mentions this in the convoy's "Test plan" section. - 🟢 **Nice to have**: PR diff against `main` is ~5K LOC because PR #4 is stacked on top of unmerged PRs #1/#2/#3. The actual Brief-1 work is one commit (`093a288`) with 5 files — worth calling out in the PR description's "Files changed" section (or the PR Health rollup) so a future reviewer/triager doesn't burn time scoping the review against the wrong baseline. Optional. ### Approval recommendation - **approve** _Rationale: every acceptance criterion in the brief is satisfied; the four gate-1 decisions (A → ESLint flat-config expansion, B → v10 pivot, C → `typescript` devDep, D → v10 → v9.39.4 revert after R15 fired empirically) are correctly applied in the diff; lockfile resolutions match expectation on all four explicit pins; no scope expansion within the Brief-1 commit; Turbopack-by-default acceptance is honored (no `--webpack` opt-out); the Vercel "Vulnerable version of Next.js" gate is on a credible path to clearing. R15's empirical firing was diagnosed and resolved correctly — pinning v10 today would have re-broken lint and shipped a no-op `|| true` CI signal. Per role spec, the deferred items (lint baseline, `npm audit` vulns, browserslist staleness, doc-writer pass, ESLint v10 / TypeScript 6 follow-ups) are all out of scope here and correctly tracked under their named follow-up convoys._
Sign in to join this conversation.
No description provided.