fix(templates): move the V1 customer template to Next 16 - #298
Conversation
sharp GHSA-f88m-g3jw-g9cj (HIGH) was unreachable on Next 15: next@15.5.x pins optionalDependencies.sharp to ^0.34.3, so no lockfile edit or `npm update` could take the >=0.35.0 fix, and an `overrides` pin above the range next itself declares would have shipped a stale-override trap the buyer inherits. next@16.3.1 declares sharp ^0.35.3, which resolves it at the source. The same bump also clears the last postcss exposure. The four postcss advisories on this lockfile were only half fixed: the top-level dependency was already 8.5.26, but next 15 carried a NESTED node_modules/next/node_modules/postcss at 8.4.31 that no top-level bump could reach. Next 16 brings it to 8.5.23. `npm audit` on the rendered nextjs/ tree: 0 vulnerabilities, sharp resolved 0.35.3. What Next 16 broke in the template, and the fix: - eslint.config.mjs. eslint-config-next 16 ships native flat configs, so the @eslint/eslintrc FlatCompat shim dies with "TypeError: Converting circular structure to JSON" — the eslintrc validator cannot serialise a flat config's plugin objects. Both entry points are now imported directly and the @eslint/eslintrc devDependency is gone. `npm run lint` exits 0. - tsconfig.json. Next 16 rewrites the customer's committed tsconfig on their first build: jsx must be react-jsx, and include gains .next/dev/types. Both are set up front so the shipped config is already correct and `next build` no longer mutates a file the buyer just checked in (it also kept the pre-build `tsc --noEmit` on a different jsx setting than the build used). Verified NOT broken, since the fleet deferred Next 16 in Qavren-Nexus over next.config.ts failing to load under TS 7: this template is on typescript ^5.8.3, `next build` reports "Running next.config.ts took 36ms", and __dirname in outputFileTracingRoot still resolves. Turbopack is now the default builder and still emits .next/standalone/server.js and .next/static where the Dockerfile copies from. The generation prompt advertised "Next.js 15" to the model in three places (Prompts/V1-generation.md, PromptBuilderService's system line and stack block); those now say 16 so the model is not steered at the previous major. Gates, all real, CI=true so a missing toolchain hard-fails instead of skipping: V1TemplateCompileTests 9 passed / 0 failed (3m02s) Engine suite 403 passed / 1 skipped / 0 failed (4m19s) That covers npm ci + typecheck + next build on the rendered tree, both Dockerfile targets, and the golden recorded LLM response reconstructing with zero orphan files and building both halves. Beyond the gate, the built web image was run: HTTP 200 with the project name in the body, "Next.js 16.3.1 / Ready". Lockfile regenerated the way #294 produced it — npm install straight in the template directory, which accepts the literal {{ProjectNameKebab}} name and keeps the token in both name fields so the rendered lockfile stays valid. Confirmed by `npm ci` succeeding on the harness-rendered tree. No --allow-remote needed; every dependency resolved from the registry. Out of scope, deliberately: the V2 template's package.json (Swiss-Cheese path, not under any compile gate) and the marketing copy in StackAlchemist.Web that still says "Next.js 15" — the same string there also appears in comments about the site's own Next 15 app, so that sweep needs its own pass. Closes #297
Independent review — APPROVEReviewed at No gate was weakened
Gates, re-run here
Security claim — confirmed at the sourcePre-fix, on The nested-postcss point in the description is correct and was the non-obvious part: the top-level After, on a tree rendered through the real
Both negatives reproducedNot just asserted — I put the pre-fix files back into the rendered Next 16 tree and watched them fail. eslint. Restored Verbatim the failure and the stated cause. The dropped
tsconfig. Restored Exactly the two edits this PR pre-applies, and the rewritten file is semantically identical to the shipped one (formatting only). With the PR's version in place, Frontend leg on the rendered tree
Node floor checks out too: Non-blocking follow-upsNothing here blocks the merge; all three are pre-existing or already called out in the description.
Verdict: approve. The security fix is real and verified at the resolved-version level, both breakages were genuinely caused by Next 16 and correctly fixed, no gate was touched, and every number in the description reproduces. |
Description
Moves the V1 customer template's frontend from Next 15 to Next 16.3.1, which is the only
way the sharp advisory in the paid deliverable could be closed.
Fixes #297
Why the bump was the fix and not a lockfile edit
sharpGHSA-f88m-g3jw-g9cj (HIGH) sat at 0.34.5 with the fix at ≥0.35.0.next@15.5.xpins
optionalDependencies.sharp: ^0.34.3, so nothing short of a framework major reachesit —
npm update sharpis a no-op, and anoverridespin above the rangenextitselfdeclares would have shipped the fleet's known stale-override trap inside a repo the buyer
owns outright.
next@16.3.1declaressharp: ^0.35.3, which resolves it at the source.The same bump clears the last postcss exposure, which was only half fixed. Issue #297 read
the top-level
postcss(8.5.26, patched) and called the four advisories resolved, butNext 15 carried a nested
node_modules/next/node_modules/postcssat 8.4.31 that notop-level bump could reach. That is the entry
npm auditwas actually reporting:After, on the harness-rendered
nextjs/tree:What Next 16 broke in the template
eslint.config.mjseslint-config-next16 ships native flat configs, so the@eslint/eslintrcFlatCompatshim the Next 15 config needed now dies withTypeError: Converting circular structure to JSON— the eslintrc validator cannot serialise a flat config's plugin objects. Both entry points are imported directly now and the@eslint/eslintrcdevDependency is dropped.npm run lintexits 0.tsconfig.jsonjsxmust bereact-jsx("mandatory changes were made to your tsconfig.json"), andincludegains.next/dev/types/**/*.ts. Both are now set up front, so the shipped config is already correct andnext buildno longer mutates a file the customer just checked in. It also kept the pre-buildtsc --noEmitrunning on a differentjsxsetting than the build used. Verified: after the change,next buildleavestsconfig.jsonbyte-identical.What was checked and found NOT broken
The fleet deferred Next 16 in
qavren/Qavren-Nexusbecausenext.config.tsfailed to loadunder TS 7 (
Cannot read properties of undefined (reading 'fileExists')). That does notapply here — this template pins
typescript: ^5.8.3, which stays on 5.x whilelatestisnow 7.0.2:
__dirnameinoutputFileTracingRootstill resolves, Turbopack (the default builder in 16)still emits
.next/standalone/server.jsand.next/staticwhere the Dockerfile copiesfrom, and both Dockerfile targets build.
Prompt strings
Prompts/V1-generation.mdandPromptBuilderService(system line + stack block) advertised"Next.js 15" to the model. They now say 16, so the model is not steered at the previous
major while the tree it merges into is on the new one. No test asserted the version string.
Lockfile
Regenerated exactly the way #294 produced it —
npm installstraight in the templatedirectory. npm accepts the literal
{{ProjectNameKebab}}package name and keeps the tokenin both
namefields, so the rendered lockfile stays valid; confirmed bynpm ciexiting 0on the harness-rendered tree. No
--allow-remotewas needed — every dependency resolvedfrom the registry, so the local
allow-remote="none"never fired.Deliberately out of scope
V2-DotNet-NextJs/nextjs/package.jsonstill saysnext: ^15.3.0. That is theSwiss-Cheese path, which is off in prod and under no compile gate. Its own decision.
StackAlchemist.Webstill says "Next.js 15" (pricing, FAQ, about,llms.txt, hero, OG image). The deliverable now ships 16, so that copy is wrong — but thesame string also appears in comments describing the site's own Next 15 app
(
LoginPageClient.tsx,RegisterPageClient.tsx), so a blind sweep would corrupt them.Needs its own pass.
^3.4.1;latestis 4.3.3). Tailwind 4 is an independentmigration with its own PostCSS story and no security driver.
Type of change
Breaking for the deliverable, not for the platform: archives generated from this point
carry a Next 16 frontend. Already-sold archives are unaffected — the buyer owns their copy.
How Has This Been Tested?
Every gate run with
CI=true, so a missing toolchain hard-fails instead of quietly skipping.That covers
npm ci+npm run typecheck+next buildon the rendered tree via the realDotNetBuildStrategy,docker build --target weband--target engineon the same tree,the customer-archive residue/size assertions, and the golden recorded LLM response
(
Fixtures/LlmResponses/v1-invoicehub-golden.txt) reconstructing through the realReconstructionServicewith zero orphan files and building both halves. The single skip isthe pre-existing
CloudflareR2UploadServiceTestscredentialed test.Beyond the gate, the frontend leg on the rendered tree:
npm cinpm run typechecknpm run buildnpm run lintnpm auditdocker build --target webAnd the built image was actually run, since building an image is not the same as it
serving:
Checklist:
15" copy on the marketing site is now stale and is called out above as a follow-up.