bump: next 15.4.3 → 16.2.6 + ESLint flat config (v9 fallback) + typescript devDep #4
No reviewers
Labels
No labels
agent-context-drift
bug
documentation
duplicate
enhancement
good first issue
help wanted
invalid
question
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: rstillwell/deckhearth#4
Loading…
Reference in a new issue
No description provided.
Delete branch "convoy/bump-next-js"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
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 downstreampreview-smokeandvisual-diffgate that depends on a live preview URL.Summary
next15.4.3 → 16.2.6 to clear Vercel's platform security gate.images.domains(removed in 16) →images.remotePatternsinnext.config.js, preserving the three CDN hosts (Scryfall, Pokémon TCG, Lorcana API) for the eventualnext/imageadoption..eslintrc.jsonto ESLint v9.39.4 (npmmaintenancetag) + flat config (eslint.config.mjs), bumpseslint-config-nextto 16.2.6 to matchnext, replacesnext lint(removed in 16) witheslint .inscripts.lint, and addstypescript@^5.9.3as a devDep soeslint-config-next@16's bundledtypescript-eslintchain can satisfy its hardrequire('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-next15.4.2→^16.2.6;typescriptnewly added at^5.9.3(resolvedtypescript@5.9.3);scripts.lintnext lint→eslint .. React stays at^18.3.1per Risk R7. Noenginesblock, notsconfig.json.package-lock.json— regenerated bynpm install. Stays on the v9 dep tree:@eslint/eslintrc@3.3.5is retained as a v9 transitive dep (v10 dropped it; v10's removal is deferred to the queuedbump-eslint-10follow-up convoy).typescript@5.9.3adds 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; thebump-eslint-10follow-up convoy will not need to change this file.globalIgnorescovers.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@16is flat-config-only.Acceptance criteria
dependencies.nextis^16.2.6(resolvednext@16.2.6)react/react-domunchanged at^18.3.1devDependencies.eslintis^9.39.4(resolvedeslint@9.39.4) — Decision D fallbackdevDependencies.eslint-config-nextis^16.2.6(resolved16.2.6)devDependencies.typescriptis^5.9.3(resolved5.9.3) — Decision Cscripts.lintis"eslint ."enginesblock; notsconfig.json; no.js→.tsrenames; no// @ts-check; no.d.tsfilespackage-lock.jsonregenerated (no hand edits);@eslint/eslintrc@3.3.5retained per v9 dep treenpm installcompleted cleanly with noERESOLVEand no deprecation warnings on the four target packagesnext.config.jsmigrated toimages.remotePatternswith the verbatim three-host shape.eslintrc.jsondeletedeslint.config.mjscreated with the verbatim shape (no rule tuning, noparserOptions, nosettings)npm run buildexits 0; banner reads▲ Next.js 16.2.6 (Turbopack); noimages.domainsdeprecation warning at startup; all 23 static pages prerender, all 47 API routes compilenpm run lintruns to completion under ESLint v9.39.4. Exit code 1; 128 problems (81 errors, 47 warnings) — the new authoritative baseline forfix-lint-baseline. CI's|| truewrapper tolerates the non-zero exit. No module-resolution or scope-manager crash. All findings are in pre-existingpages/,components/,tailwind.config.js,postcss.config.jssource — none in files touched by this PR.tcg-vaulthas no test runner installed yet (tracked underadopt-vitest); manual smoke perTESTING_GUIDE.mdis the verification mechanism.<Image>migrations (none exist intcg-vault— re-verified byrg "from ['\"]next/image['\"]", zero hits)--webpackopt-out (not needed; nowebpack:block innext.config.js; Turbopack-by-default runs clean)files:/deletes:listsTest plan
npm install— exits clean with noERESOLVEwarnings.npm run build— exits 0 cleanly on Turbopack.npm run dev— manual smoke perTESTING_GUIDE.md: confirm/,/login,/signup,/cards,/collectionsrender without runtime errors, hydration warnings, or 500s. Confirm noimages.domainsdeprecation warning at startup.npm run lint— exit 1 with 128 problems (81 errors, 47 warnings). Tolerated by CI's|| truewrapper. Baseline reduction isfix-lint-baseline's charter.Error: Vulnerable version of Next.js detectedtoSuccess, capture the preview URL in this PR for the audit fan-out.Notes
Decision audit trail (full in
.convoys/bump-next-js.md§ Decisions)eslint-config-next@16requireseslint >=9.0.0; the convoy file's prescribed^16bump foreslint-config-nextcouldn't co-exist witheslint@^8. Adopted flat config natively (noFlatCompatshim —eslint-config-next@16.2.6ships native flat-config exports, verified by tarball extraction).eslintfrom^9.39.4to^10.4.0. ESLint v10 was released tolatestbetween convoy authoring and gate-1 review. Pivoted to avoid a back-to-backbump-eslint-10convoy.typescript@^5.9.3as a devDep. ThepeerDependenciesMeta.typescript.optional: trueannotation oneslint-config-next@16.2.6only suppresses npm's install-time warning;@typescript-eslint/typescript-estreehard-requirestypescriptat runtime via 9 unconditionalrequire('typescript')sites in itsdist/.eslintfrom^10.4.0back to^9.39.4. Risk R15 fired empirically on the second implementer pass:TypeError: scopeManager.addGlobals is not a functionateslint/lib/languages/js/source-code/source-code.js:221.@typescript-eslint/scope-manager@8.59.4(bundled byeslint-config-next@16.2.6) was published before ESLint v10's Feb-2026 GA and doesn't implement v10's newaddGlobalsAPI. 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-eslintships a v10-tested release, ANDeslint-config-nextbundles it. When both land, this is a one-linepackage.jsonpin change + a re-lint to confirm.eslint.config.mjsshape is forward-compatible.bump-typescript-6— upstream-blocked.typescript@6.0.3is onlatest, buttypescript-eslint@8.59.4's peer range (>=4.8.4 <6.1.0) accepts but doesn't certify v6. Will likely couple withbump-eslint-10since both depend ontypescript-eslintshipping a fresh release.Other observations (not acted on; surfaced for future convoys)
pages/signup.js:26callsgenerateRandomAvatar()insideuseEffectbefore theconst generateRandomAvatar = …declaration on line 29. Works today by closure-binding-by-reference, but a real footgun thateslint-plugin-react-hooks@7.1.1now catches. Pickup:fix-lint-baseline.npx update-browserslist-db@latestprinted during build).setup-node@v4withnode-version: '20'is a moving target. Anenginesblock ("node": ">=20.19.0") would give npm install-time enforcement. Pickup: next CI-touching convoy.npm installaudit: 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 idaudit-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
The latest updates on your projects. Learn more about Vercel for GitHub.
Pipeline Health
Build + CI gates
Build runs on Vercel; this CI runs lint and schema-map drift only (no duplicate build).
Role reports
See individual comments above for details. This rollup updates automatically.
Design System Audit
Note: The standard table is N/A because no UI files are in the diff (
components/,pages/,styles/,*.tsx/.jsx/.cssall 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)
PostCSS chain is Turbopack-clean.
postcss.config.jsuses pure ESMexport default { plugins: { tailwindcss: {}, autoprefixer: {} } }— norequire(), no inline plugin functions, no webpack-specific loaders. Bothtailwindcssandautoprefixerare in Next 16's officially supported Turbopack PostCSS plugin matrix. No action needed; flagging because the convoy R1 explicitly asked me to verify.Tailwind config is Turbopack-clean.
tailwind.config.jsuses pure ESMexport defaultwithcontent,theme.extend, and an emptyplugins: []. Zerorequire()calls. Zero JS-function plugins. Thehearthcolor palette (flame,ember,gold,wood, etc.) is plain object literals — no dynamic evaluation. Turbopack will process this identically to webpack.CSS custom properties resolve framework-agnostically.
styles/globals.cssdefines 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:rootand[data-theme="dark"]. No PostCSS plugins beyond Tailwind/autoprefixer touch them. Variable cascade and[data-theme="dark"]attribute-selector flipping (driven bylib/theme-context.js) is browser-native, not bundler-mediated — Turbopack cannot break it.Only one
@applysite, in plain Tailwind 3.4 form. 13@applyusages instyles/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@applyissues to worry about (still on Tailwind 3.4.17).rgba(var(--bg-secondary-rgb), 0.95)pattern at line 818 is framework-agnostic. The mobile-nav backdrop fallback uses CSS variable interpolation insidergba(). 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.)next/fontconfirmed zero usage.rg "from ['\"]next/font['\"]"returns zero hits. Body uses a raw system-font stack inline instyles/globals.css:7-9. Architect's Boot-the-brief finding is verified. No font-loading parity risk under Turbopack.next/imageconfirmed zero usage.rg "from ['\"]next/image['\"]"returns zero hits. PR'simages.remotePatternsmigration is preemptive (for futurenext/imageadoption per the convoy). No<Image>regression risk in this PR..module.cssconfirmed zero usage. No CSS Modules files anywhere in the repo. The only CSS file isstyles/globals.css, imported once inpages/_app.js-style entrypoint. Turbopack's CSS Modules handling is irrelevant here.No CSS
@importstatements. Only@tailwind base/components/utilitiesdirectives — those are Tailwind PostCSS plugin directives, not CSS-spec@import. Turbopack's@importresolution can't regress what isn't there.Context for future audits (NOT this PR)
components/Layout.jsbrand-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.jshardcoded default-user impersonation (user = { email: 'me@randallstillwell.com', role: 'user' }) — AGENTS.md gotcha #8. Not in this PR's diff.styles/globals.cssglow 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 touchingstyles/. Flagging for a futuretokenize-glow-effectsconvoy 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:Toggle dark mode on every smoked route. Click the theme toggle (top-right header) on each of
/,/login,/signup,/cards,/collections. Confirm:#fefcf8) to charcoal (#1a0f0a).#2d1810) to warm-white (#fff8f0).--accent-ember) on active nav-items / buttons stays visually identical (it's the same hex#d84315in both themes).--bg-primary/--text-primary/--accent-emberCSS-variable cascade resolves under Turbopack.Resize the browser to <768px width and re-toggle dark mode. The mobile-nav backdrop (
.mobile-nav-backdropatstyles/globals.css:807-819) usesrgba(var(--bg-secondary-rgb), 0.95)inside a@supports not (backdrop-filter)fallback. If--bg-secondary-rgbregresses, 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.Hover over a card on
/cards. The rarity-glow effects (.rarity-glow-mythic/rare/uncommon/enchantedat lines 569-697) use hardcodedrgba()box-shadowvalues, 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.No browser-console warnings about
images.domainsduring any of the above. Thenext.config.jsmigration was the whole point.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.
A11y Audit
Scope note: This is a framework-bump PR (Next 15 → 16, ESLint v8 → v9 flat config,
typescriptdevDep). The PR diff includes zero files undercomponents/orpages/. 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 ontcg-vault, and tells the smoke-tester what to look for at preview-deploy time.Critical (must fix)
Warnings (recommended)
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:<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 ofnext/linkoruseRouter.tcg-vaulthas 15next/linkcallers (16 if you countcomponents/Layout.js.backup, which is excluded per.cursor/rules/no-go-zones.mdc); none rely onrouter.events.on/off/emit(zero hits in source), so the prefetch-cache rewrite has no router-event-listener dependency to break.tcg-vault: the auto-override ofscroll-behavior: smoothduring SPA route transitions was removed in 16 (upgrade guide § "Scroll Behavior Override").rg "scroll-behavior|scrollBehavior"returns zero hits instyles/,components/, andpages/, so this codebase never relied on the override. Nodata-scroll-behavior="smooth"opt-back-in needed. Smoke tester does NOT need to verify smooth-scroll on route transitions — there's nothing to verify.params/searchParams, asynccookies()/headers()/draftMode(),proxy.tsrename, parallel-routedefault.jsrequirement, Cache Components) is App-Router-only. Pages RoutergetServerSideProps/getStaticProps/req/ressemantics are unchanged. None oftcg-vault's ~30 API handlers or page-level data-fetching functions are affected.prefers-reduced-motionis honored instyles/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):<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."<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.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-motionstill 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-baselineor a future a11y convoy — NOT for this PR)Noted while scanning for context:
<img>alt coverage is complete — every<img>tag incomponents/andpages/has analt=attribute (verified across 19 files). No critical missing-alt bugs. However: a handful of generic alts (alt="Profile"inpages/signup.js:262andpages/profile.js:302,alt="Preview"incomponents/UploadImageModal.js:162) could be improved with more descriptive text oralt=""if purely decorative. Low priority; not this PR's job.eslint-config-next@16bundleseslint-plugin-jsx-a11y@^6.10.0, which is now active in the flat-config setup landing in this PR. Oncefix-lint-baselineruns against the v9 baseline, expect a non-zero count ofjsx-a11y/*warnings to surface across the existingpages/andcomponents/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-behavioroverride removal) is a no-op fortcg-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.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).093a288touches 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 vsmainshows 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 viagit show --stat 093a288.eslint.config.mjsmatches the verbatim shape from the brief byte-for-byte (defineConfig + globalIgnores import fromeslint/config,nextVitalsfromeslint-config-next/core-web-vitals, all 6 ignores in spec order).next.config.jsmigrates all three CDN hosts (api.scryfall.com,images.pokemontcg.io,lorcana-api.com) withhttpsprotocol per the Next 16 upgrade guide verbatim.package.jsonpins match Decisions A→D exactly (next@^16.2.6,eslint@^9.39.4,eslint-config-next@^16.2.6,typescript@^5.9.3).scripts.lintiseslint .. React unchanged at^18.3.1per R7. Noenginesblock, notsconfig.json, no.tsrenames per the no-scope-expansion guardrails. Lockfile resolveseslint@9.39.4(not v10),next@16.2.6,eslint-config-next@16.2.6,typescript@5.9.3, with@eslint/eslintrc@3.3.5retained as expected on the v9 dep-tree.next@16.2.6closes Vercel'sVulnerable version of Next.js detectedplatform 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.next/imageis not imported anywhere (re-verified, zero hits); plain<img>usage unchanged.images.remotePatternsis pre-staged for eventualnext/imageadoption. 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 nowebpack:block to regress against). CSS bundling under Turbopack is a theoretical runtime concern but Tailwind/PostCSS config is unchanged.tcg-vaulthas no test runner installed yet —adopt-vitestandadopt-playwright-smokeare queued convoys. Manual smoke perTESTING_GUIDE.md(per the brief's "Local verification" + "Test plan" sections) is the agreed verification mechanism for this convoy. Not blocking.AGENTS.mdline 11 still saysNext.js 15; the brief explicitly defers the doc bump to a separaterole-doc-writerPR that runs AFTER this audit cohort. Expected, not blocking.Findings
AGENTS.md§ "Project overview" line 11 needsNext.js 15→Next.js 16and theJavaScript (not TypeScript)line could use a one-line footnote thattypescript@^5.9.3is installed as a devDep purely soeslint-config-next@16's bundledtypescript-eslintchain can satisfy its unconditionalrequire('typescript')at module load (Decision C / Brief 1 finding #17), and that the source code remains JS-only with notsconfig.json. Pickup point: the queuedrole-doc-writerpass for this convoy.adopt-vitestlands, retrofit a smoke test that exercisesnext.config.jsparsing + asserts the threeimages.remotePatternshostnames resolve, so any future regression on the pre-stagednext/imageconfig is caught at CI rather than at deploy. Brief mentions this in the convoy's "Test plan" section.mainis ~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
Rationale: every acceptance criterion in the brief is satisfied; the four gate-1 decisions (A → ESLint flat-config expansion, B → v10 pivot, C →
typescriptdevDep, 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--webpackopt-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|| trueCI signal. Per role spec, the deferred items (lint baseline,npm auditvulns, 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.