feat: the v7-to-v8 migrator and skill use @decocms/tanstack and @decocms/nextjs
Stacked on #699 (P6). P7 of the v8 framework packages: the v7-to-v8 migration script and the deco-v7-to-v8-migration skill use @decocms/tanstack and @decocms/nextjs, so sites stop copying v7's worker, draft, Studio-bridge and image code into their own tree.
After the product owner's review (2026-10-14), this branch follows the stack below it: @decocms/blocks exports no draft names or Studio origins (they are in each binding's root), @decocms/blocks/image is platform-neutral, and VTEX's and Shopify's image URL helpers live in @decocms/apps-vtex/image and @decocms/apps-shopify/image.
@decocms/tanstack/react: Image, Picture, Source
- They take v7's props and render v7's markup, so a call site only changes its import.
- The URLs come from
ImageUrlProvider(anImageUrlthe site builds withcreateImageUrl) instead of v7'sregisterImageCdnDomain/registerImageQualitymodule state. A site puts the provider around its pages withcreateDecoRouter'sWrapoption. - No platform knowledge.
@decocms/tanstackdepends on no@decocms/apps-*package. A site that shows VTEX or Shopify images lists the platform's helper first:
That gives v7's URLs byte for byte, except PO-approved fix 2 (#694): an already-resized Deco CDN URL in content is re-resized from itscreateImageUrl({ providers: [vtexCatalog(), shopifyCdn(), decoCdn({ sources: "all", ...byRelease })] })src.byReleasedepends on the@decocms/blocks7.x release the site ran, because v7'sImagechanged across releases (e.g.stripAssetHosts: "leading"from 7.71.4); the skill has the table and the script reports the line for the site's version. Keep both helpers whatever the platform: v7 resized VTEX and Shopify images on every site. Without a provider, every source goes through the Deco image CDN (DECO_IMAGE_URL, which replaces the earlierV7_IMAGE_URL). - Golden markup:
tests/tanstack-image-markup.golden.test.tsrenders the 160 cases recorded from v7's own components (v7's defaults, and a site with another CDN host and quality) with the helpers supplied as a site would. Zero cases changed. It sits outside the package because the package can't depend onapps-*, not even for tests. With no provider, the non-VTEX/Shopify cases also match. - More golden markup (review pass):
tests/__fixtures__/tanstack-v7-image-markup-nowidth-nuvemshop.jsonl, 90 more cases from v7's own components (7.x latest):Image/Sourcewithout a width (v7'swidth=NaN … NaNwcandidates, which blocks'srcSetnow reproduces, #694) and Nuvemshop product images (v7 from 7.77.1 picks one of the CDN's fixed sizes). No@decocms/apps-*package covers Nuvemshop, so the test holds a store's own provider (the oneframework-code.mdgives) to v7's markup. Zero changed; dropping that provider, or thesrcSetfix, makes it fail. - Differences from v7: no "Missing height" warning on every render, and PO-approved fix 2 (#694, in
decoCdn, not in the component): an already-resized Deco CDN URL that v7 turned intosrc=imageis re-resized from itssrc. The component itself has no image-URL rule of its own.
The migration script
-
Imports (
imports.ts):- It moves the v7 names the bindings keep:
createDecoRouterand the search functions go to/router,NavigationProgress/StableOutletto/react,decoServerFnFetchto/start, and on a TanStack siteImage/Picture/Sourceto/react. DRAFT_PARAM,DRAFT_COOKIE,DRAFT_HEADER,draftResponseHeaders,STUDIO_ORIGINS,STUDIO_FRAME_ANCESTORSandisStudioOriginimported from@decocms/blocksmove to the site's binding root (@decocms/tanstackor@decocms/nextjs). A site with neither binding gets a report line instead.- An import that mixes moved and other names is split.
- Every other binding import is reported name by name with its replacement.
- The image hints (
registerImageCdnDomain,getOptimizedMediaUrl, …) and theImagenote spell out v7's URLs as v8 code:createImageUrlanddecoCdnfrom@decocms/blocks/image,vtexCatalogfrom@decocms/apps-vtex/image,shopifyCdnfrom@decocms/apps-shopify/image, with thedecoCdnoptions of the site's 7.x release (siteBlocksVersion: the installed copy, else thepackage.jsonrange). Rows: before 7.71.0legacyHosts: []withdecoims.comas the default domain; 7.71.0 to 7.71.3legacyHosts: []; 7.71.4legacyHosts: ["decoims.com"]; 7.71.5 on nothing more, plus a Nuvemshop provider from 7.77.1.stripAssetHostsis left atdecoCdn's default,"leading", on every row (see the PO decisions below). Each row was checked against that release'shooks/Image.tsxover every golden source, six widths, three heights, two fits and three CDN configs (Cloudflare,decoims.com+ quality, an Azion host): no changed case. - Left alone as v8: the platform-neutral names of
@decocms/blocks/image(now a list, not*, so a stale name is reported),@decocms/apps-vtex/image,@decocms/apps-shopify/image, the@decocms/tanstackroot, and the@decocms/nextjsroot with its draft names and Studio origins.
- It moves the v7 names the bindings keep:
-
Settings (
siteSettings.ts):<site>.deco.siteand<site>.deco-cx.workers.devare no longer written intoCMS.json, because the bindings allow them from theirsiteoption.- The report says which
siteto pass. DECO_ALLOWED_PREVIEW_HOSTSmaps topreviewHosts.
-
Dependencies note:
@decocms/blocks ^8.3.0plus the matching binding^8.3.0. -
A site's own copy of v7's image helper (f80cf1ab). A site that kept a copy of
hooks/Image.tsxundersrc/(a file that definesgetOptimizedMediaUrl) serves the copy's URLs, not its release's. One storefront's copy was 7.20.7, edited to 7.77.1's CDN domain anddecoims.comrule. The 7.20.7 row would have given itlegacyHosts: []ondecoims.com. The script (scripts/imageCopy.ts) now reads these rules from the copy:- the initial domain and quality;
- asset-host stripping (anywhere or leading);
- the
decoims.comrule (none, Cloudflare-only or always); - the VTEX, Shopify and Nuvemshop rules.
It also reads the site's
registerImageCdnDomain/registerImageQualityliterals through the TypeScript AST, so a comment doesn't count. The copy gets its own report line with thecreateImageUrlcall, and theImagenote uses that line too. A copy whose rules the script can't read is reported for comparison by hand. Fuzz check: the options read from each 7.x release'sImage.tsx(7.2.0, 7.51.0, 7.71.0, 7.71.4, 7.71.5, 7.77.1) and from both storefronts' pre-migration copies, each with four registered domains, give 0 differing URLs (1,944 cases each) besides fix 2's. On those two storefronts' trees, the script prints exactly theimageUrlthey ship. The demo storefront's copy (7.64.3) and its release row agree.
The skill
framework-code.mdcovers the TanStack image setup with the platform helpers. It now says that options come from a site's own copy when it has one, and names fix 2 (an already-resized Deco CDN URL is re-resized from itssrc). For next/image it names fix 1 (via: "thumbor").script-steps.mdandimport-map.mdsay the same.framework-code.mdandnextjs.mdcover the Next.js loader:vtexCatalogWebp()+vtexFileManager({ via })+decoCdn({ quality: "high", fit: "contain", layout: "quality-last" }). The earlier line about file-manager images moving to Thumbor is gone: URLs match the site's current loader.framework-code.mdalso gives the draft names and Studio origins per binding.import-map.md: moved-name rows, the left-alone list, and the image row.- Also updated:
manual-steps.md,script-steps.mdandSKILL.md(10,236 bytes, under the 10 KB budget). - Outside the skill:
packages/tanstack/README.md, CLAUDE.md andknip.json.
PO decisions of 2026-10-15 (merged up from #693–#699, then here)
- Image rule (decision 5).
decoCdnstrips only a leading asset host by default now (#694), so the script writes nostripAssetHostsfor any site. A site whose v7 code stripped the asset hosts anywhere (7.x before 7.71.4, or a copy of v7's image helper that did) moves to"leading"too, as approved, and never getsstripAssetHosts: "anywhere". The report's new "image URLs" line counts and lists the URLs in.deco/blocksandsrc/whose image URL changes: a URL that merely contains an asset URL (https://other.host/https://assets.decocache.com/…).changedByLeadingStripis tested againstdecoCdnitself (both settings, eight sources). On a fresh copy of the demo storefront (7.64.3), the line says no URL changes. - Dev mode (decision 1). The skill no longer tells Next.js sites to pass
dev: isNextDev(...):createCMSreadsNODE_ENV, andisNextDevis gone from@decocms/nextjs(#697). The script never wrote a dev flag.telemetry.md(merge conflict resolved) says to leavedevout. - Schema check in CI (decision 2). New
scripts/ciSchemaCheck.ts, run as part of the scripts step. It addsdeco schema --checkright after the install step of the site's pull-request workflows, or of every workflow when none runs on pull requests, and never to v7'sregen-blocks. It uses the lockfile's runner (bunx,pnpm exec,yarn,npx) and the install's working directory (the site's folder in a monorepo). Comments and layout are kept, a second run changes nothing, and a workflow it can't edit (none, or a multi-line install) gets a report line. On the demo storefront, whose only workflow posts preview links, the script reports that line. - Tests:
ciSchemaCheck.test.ts(5) and 4 newimageCopy.test.tstests. Mutations that widen the install match, drop the working directory, skip the report, misread 7.71.0–7.71.3 or stop comparing against the leading strip each fail them. The skill's scripts pass 90/90. - Gates at aedc30b7:
bun run check,bun run build,skills:checkandskills:readme:checkpass. Plain Node imports all 38 built exports,@decocms/nextjshas noisNextDev, anddeco --helplists--check.bun run test(2 GB): 2,482 passed, 4 failed, 95 skipped. The failures are DS-2 (known; the fix is on its own PR against main) and three timeouts under full-suite load (two inschema.test.ts, plus tr-13). Those three files run alone: 102 passed, 1 failed (DS-2).
Tests
-
steps.test.ts:- The draft names move per binding and are reported without one.
- The image hint names the
apps-*/imagehelpers. - Every move target exports the name.
- The runtime exports of the
@decocms/tanstackroot, the@decocms/nextjsroot,/previewand@decocms/blocks/imageare all listed. @decocms/blocksexports none of the moved names.@decocms/blocks/image's list names no platform.
-
bindings.test.ts: a v7 TanStack Start fixture goes throughmigrate, then the rewritten files type-check against this repo's sources. That includes a prereleaseDRAFT_COOKIE/STUDIO_ORIGINSimport (moved to@decocms/tanstack) and theimageUrl+ImageUrlProvidermodule the report asks for, built on@decocms/apps-vtex/imageand@decocms/apps-shopify/image. Feeding it an old helper signature makes it fail. -
steps.test.tsalso checks the per-release table and how the version is read. -
imageCopy.test.ts(7 tests) uses verbatim excerpts of each release form and of an edited copy. It covers:- telling a copy apart from code that only uses the helper;
- the options for each release form, the registered domain and quality, and an edited copy's own rules;
- an unreadable copy;
- the report line taking priority over the release's.
A mutation that ignores the copy makes it fail.
-
Results (adversarial pass, 2026-10-14, at db44e19b): fix 2 narrowed in #694 (6b7e7001:
decoCdn's default match is back to the pre-fix sources, so a Deco CDN URL that used to pass through, e.g.deco-assets.edgedeco.comundersources: "deco", is unchanged again) and merged up.bun run check,bun run build,skills:checkandskills:readme:checkpass.bun run test: 2,455 passed, 3 failed under full-suite load: DS-2 (known), aschema.test.tstimeout and asyntax.cli.test.tstimeout (saved-01); those three files alone: 89 passed, 1 failed (DS-2). Copy-reading fuzz rerun with a strict fix-2 rule (a difference counts as fix 2 only when v7 wrotesrc=imageorsrc=<cdn>/image): 7 releases'Image.tsx+ 2 storefront copies x 4 registrations, 1,944 cases each, 0 other differences. -
Results (fix 2 + copy reading, 2026-10-14, at f80cf1ab):
bun run check,bun run build,skills:checkandskills:readme:checkpass.bun run test: 2,453 passed, 4 failed under full-suite load: DS-2 (known), twoschema.test.tstimeouts and an HRI-3 timing assertion. Run alone,schema.test.tspasses 58/58 anddelivery.test.tspasses 94/94 (twice);delivery.cli.test.tsfails only DS-2.- The fix-2 golden cases pass: blocks 160 and next/image 60, all marked approved. The TanStack markup goldens (250 cases) have no already-resized CDN source, and all of them still match.
-
Results (review pass, 2026-10-14, at 73c0a225):
bun run check,bun run build,skills:checkandskills:readme:checkpass; plain Node imports all 14 built@decocms/blocksexports and none exports a draft name, a Studio origin or a platform name.bun run test(at 2755ef95): 2,434 passed, 8 failed: DS-2 (known) plus load timeouts in schema, serve, cli (cli-01, cli-02) and migrate. Those five files run alone: 274 passed, 1 failed (DS-2). The skill's scripts pass 74/74 at 73c0a225.
-
Earlier results:
bun run check,bun run build,skills:checkandskills:readme:checkpass.bun run test: 2,433 passed, 7 failed. The failures are DS-2 (known) and six tests that timed out under full-suite load, in check, schema, cli cp-51, HRI-3 and HRI-4. Those five files pass when run alone (338/339, the only failure being DS-2).
For the PO
- The image components are in
@decocms/tanstack, not in blocks, and know no platform.PageBlocksandpageRoutefrom the plan are still not shipped. blocks-clitemplates live only on7.x, so nothing changed for them here.- On Next.js, passing
sitelets<site>.deco.sitepreview, which v7's Next binding didn't allow. The report andnextjs.mdsay so, and say to leavesiteout to keep v7's hosts. - No release or npm action in this PR.
- Decided (PO, 2026-10-14): Nuvemshop's image rule stays in the store's own code. There is no
@decocms/apps-nuvemshop. A Nuvemshop store keeps v7's URLs with a provider in its ownimageUrl.ts; the skill gives that provider, and the golden test holds it to v7. - Approved (PO, 2026-10-14): fixes 1 and 2 from #694. Fix 2 is merged here and documented in the skill. Fix 1 is a site change.
https://claude.ai/code/session_01WNwbSEePYNcY5YCgqZURig
Review round (2026-10-15)
a771f6cf:migrate()itself is now tested to adddeco schema --checkto the site's pull-request CI. Before, onlyaddSchemaCheckwas tested, so dropping its call frommigrate()failed nothing; now it fails the new test.
Studio's "." and outlines in the skill (2026-10-16, 19393a99)
framework-code.mdand the import map: a TanStack page loader returnspathTemplate; a site with its own document rendersDecoPreview(bridge, badge and draft) rather thanStudioBridgealone; a Next.js page rendersStudioPage; page sections keep v7's<section id data-manifest-key>wrapper, which Studio's editor outlines, selects and scrolls to. A Next site whose own section list drops it (v7 Next storefronts did) makes that wrapper a<section>.- The import check accepts
StudioPageandStudioPageInfo; the import test covers them andDecoPreview.
Main refresh (2026-10-17, ced92e19)
- Merged P6
6ce49dcfinto this branch (normal merge, no conflicts), then merged up the stack to M2 #707. - Gates at the top of the stack (M2
5131a3c1):bun run check,bun run build,skills:check,skills:readme:checkpass.bun run test(2 GB memory cap): 122 files pass, 1 skipped; 2,745 tests pass, 95 skipped, 0 fail. DS-2 now passes (fixed on main by #709); no timeouts this run. - CI on this PR's new head: all checks pass.
🤖 Generated with Claude Code