Skip to content

feat(compat-v7): /commerce, /tanstack and /nextjs - #705

Closed
tlgimenes wants to merge 1 commit into
feat/compat-v7-sectionsfrom
feat/compat-v7-commerce-bindings
Closed

tlgimenes wants to merge 1 commit into
feat/compat-v7-sectionsfrom
feat/compat-v7-commerce-bindings

Conversation

@tlgimenes

@tlgimenes tlgimenes commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

Stacked on #704 (feat/compat-v7-sections). Adds the last pieces of @decocms/compat-v7 that the product owner approved on 2026-10-14: v7's commerce types and helpers, and the framework-bound parts for each binding. Since 2026-10-15 it also has /tanstack/worker, v7's legacy-origin A/B split (PO decision 6).

/commerce/*: v7's @decocms/apps-commerce

These are v7's pure files, kept under v7's own subpath layout: /commerce and /commerce/types, /commerce/types/cart, /commerce/sdk/{analytics,formatPrice,url,useOffer,useVariantPossibilities} and /commerce/utils/{canonical,constants,filters,productToAnalyticsItem,stateByZip}. A site only has to replace @decocms/apps-commerce/ with @decocms/compat-v7/commerce/ in its imports. None of it uses React.

v7's app runtime is not ported: app-types, manifest-utils, registry and resolve.

How it's pinned to v7. v7's files are kept verbatim with pinned hashes (7.x 9131e9d9; they are the same at the 7.x head, d586d6f0).

  • Each of our files prints the same TypeScript AST as v7's (comments and formatting aside), so a changed type or expression fails the test.
  • Each helper runs next to v7's own file over 5,000 generated inputs: prices, currencies and locales, URLs, offers and installments, variants, products and breadcrumbs, ranges and ZIP codes.
  • I made 7 mutations to the code; the tests caught all 7.

/tanstack and /tanstack/server

The site's code for each piece:

// block map
"website/matchers/random.ts": (props) => random(props, matcherContext()),
// page server function
const page = await openPage(data.href); commitSegmentCookie(); return { ...page, device: requestDevice() };
// page component
<DeviceProvider value={page.device}>…
// worker
segment: (request) => ({ ...cohortSegment(request) }),
  • matcherContext(pageUrl?): one flags array per request. On a client-side navigation the request is the server-function call, so pageUrl must be given there; without it, the call throws a message that says what to pass.
  • commitSegmentCookie(): sets deco_segment exactly as v7's persistFlags did: deco_segment=<value>; Max-Age=31536000; Path=/; SameSite=Lax. It sets nothing when the visitor's cookie already says the same. createDecoWorker never stores a response that sets this cookie, as long as the site keeps deco_segment out of safeCookies.
  • cohortSegment(request): v7's __abf split of the edge cache. It uses v7's worker readRequestCookie, ported as is.
  • requestDevice(): v7's detectDevice on the request's user agent.
  • DeviceProvider, useDevice and DeviceContext: v7's context, in a single "use client" module shared with /nextjs.

No change to @decocms/tanstack was needed: TanStack Start's own response headers carry the cookie.

Works across TanStack Start versions. The response is written with getResponseHeaders() and setResponseHeader(name, list), which exist in every TanStack Start the binding supports. getResponse() was removed in 1.169; a consumer install found that and it is fixed here.

/tanstack/worker: v7's A/B split with the legacy origin (PO decision 6, 2026-10-15)

abTestingRoutes({ worker, ...v7's ABTestConfig }) is v7's withABTesting (7.x packages/blocks/src/sdk/abTesting.ts) as a createDecoWorker routes handler:

const abTesting = abTestingRoutes({ worker: () => worker, kvBinding: "SITES_KV", shouldBypassAB });
const worker = createDecoWorker({ /* … */ routes: async (request, env, ctx) => (await abTesting(request, env, ctx)) ?? siteRoutes(request) });
  • It has the same KV entry, buckets, _deco_bucket=<bucket>:<ratio %> cookie, ?_deco_bucket= override, x-deco-bucket header, circuit breaker, shouldBypassAB, preHandler and streamed fallback proxy. v7's getStableBucket, tagBucket, proxyToFallback and createReplaceStream are exported too. It uses Web APIs only.
  • The "worker" bucket. v7 wrapped the whole worker, so it could tag the worker's response and fall back to the legacy origin when the worker threw. A routes handler only answers before the worker serves. So, for that bucket, the handler calls the worker back (worker). A header carrying a random token for each handler marks that inner call, so a client can't forge it, and the inner call skips the handler and goes on to the site's other routes, the edge cache and the page. Its response keeps the worker's preload Link hints, the only ones the page gets: the worker adds none to a routes response (P4 68167723). The token is drawn on the handler's first request, not when it is created: a site builds its routes at module scope, where Workers refuse random values, so drawing it there made the worker fail to start (found on a storefront's preview build, fixed in 0b91f011). No change to @decocms/tanstack was needed: _deco_bucket was already one of the worker's default safeCookies, so cached pages are tagged too.
  • Pinned to v7. v7's file is a hash-pinned fixture (the same at 9131e9d9 and at the 7.x head). Its tests:
    • 3,000 seeded requests through v7's withABTesting(worker) and through the handler inside a worker give the same responses (status, headers, body), the same legacy-origin calls, the same worker calls and the same errors. The cases vary KV configs, ratios, cookies (legacy formats included), overrides, bypass, the pre-handler, POST bodies, chunked multi-byte bodies, redirects, failing origins and the circuit breaker.
    • v7's helpers get the same inputs as ours.
    • Inside the real createDecoWorker, a MISS and a HIT are both tagged, the page renders once, the preloads appear once, other routes still answer, and bypassed or unconfigured requests stay untagged.
    • 12 mutations: the tests catch all 12.
  • A storefront that vendored v7's copy now uses this handler on its TanStack binding branch; that commit is local to the site's worktree.

PO decision 3 against the compat random

@decocms/blocks' multivariate now stops at the first matching rule, as v7 did. A new test resolves 2,000 seeded pages two ways:

  • with the real multivariate and the block map's compat random (saved randoms carry their name as cohort);
  • with v7's resolver loop over evaluateVariantRule, then persistFlags.

Both give the same winner and the same deco_segment Set-Cookie. On more than 300 of those pages, rules after the winner are never drawn. If every rule is evaluated, the test fails. The 5,000-page sticky fuzz still passes.

/nextjs, /nextjs/server and /nextjs/proxy

  • The URL the matchers read. A server component sees the request's headers but not its URL, and a proxy may rewrite the path a page renders under. So the site's proxy forwards the visitor's URL on x-deco-page-url with pageUrlRequestHeaders(), which replaces any copy that came from outside. /nextjs/proxy imports no React and no next.
  • matcherContext() is async: it reads next/headers and keeps the flags per request through React's cache.
  • The cookie. A server component can't set a cookie, and the proxy runs before the page has made its decisions. So SegmentCookie renders a one-line script that writes the same cookie bytes as v7, and, after a client-side navigation (where React runs no script it renders), an effect that writes the same value. The page renders it after its content has resolved. A page that reads the request is dynamic, so Next never shares it between visitors.
  • requestDevice() and the same DeviceProvider.

Boundaries

tests/compat-v7.guardrail.test.ts now checks:

  • /nextjs* never reaches @decocms/tanstack or @tanstack/*, and /tanstack* never reaches @decocms/nextjs or next;
  • the client entries import nothing server-only;
  • the framework subpaths import only declared peers;
  • no other package (blocks, tanstack, nextjs, apps-*) depends on compat-v7 or imports it. The import check now also catches side-effect imports, which it missed before.

I verified each check by planting a violation.

The bindings and their frameworks (@tanstack/react-start, next, react) are optional peers. Nothing new in @decocms/blocks.

Tests

  • Sticky random on each framework: 5,000 seeded pages per framework through matcherContext() and the cookie writer. The decisions, the response's Set-Cookie list (on TanStack) and the browser cookie (on Next) equal what v7's evaluateVariantRule and persistFlags produced. This includes returning visitors, who get no new cookie.
  • Cache split: cohortSegment equals v7's worker token over 20,000 cookie headers.
  • Device: the device context matches v7's useDeviceContext.tsx with a value, in a browser, and on the server.
  • Mutations: I made 6 mutations in the framework code and the tests caught 5. The sixth, dropping .trim() on the cookie value, can't change a result, because atob ignores whitespace.

Gates

At ddd58ce2, after merging the lower branches up through P7 aedc30b7. That merge brings in PO decisions 1–5: NODE_ENV dev mode, deco schema --check, the first-match multivariate, the "." shortcut on by default, and the leading host strip.

Gate Result
bun run check pass (GOMAXPROCS=4)
bun run build pass
Plain-Node import of every built export 64/64 across all packages, compat-v7 27/27; deco --help lists --check
skills:check and skills:readme:check pass
bun run test (2 GB cap) 2,640 passed, 4 failed, 95 skipped. One failure is DS-2, which fails the same way on main since #689 (a fix is open against main). The other three are schema.test.ts timeouts under full-suite load. Run alone, those two files pass 68/69, and the one failure is DS-2.
compat-v7 + guardrail + schema tests 148 at the merge, 155 with this commit

At 0b91f011 (the token fix): bun run check and bun run build pass; plain-Node import 64/64; skills checks pass; bun run test 2,642 passed, 3 failed (DS-2 and two schema.test.ts timeouts; those two files alone 68/69, only DS-2), 95 skipped. The new test (creating the handler draws no random value and does no I/O) fails on the previous code.

Release dry run (throwaway clone)

  • semantic-release --dry-run gives 8.3.0.
  • After the version sync and the build, all 11 packages are at 8.3.0 with no workspace: range, and npm publish --dry-run passes for each. The compat-v7 tarball has 172 files and no tests or fixtures.
  • Consumers: one with blocks, tanstack, compat-v7 and apps-vtex (it resolved @tanstack/react-start 1.169.2) and one with blocks, nextjs, compat-v7, apps-vtex and next 16.4.1. Each has one copy of @decocms/blocks, and plain Node imports each consumer's subpaths. The Next consumer gets no TanStack package.
  • Name: @decocms/compat-v7 returns 404 on npm, and 7.x has no packages/compat-v7, so 7.x never publishes this name. The first publish needs the Trusted Publishing bootstrap (noted in feat(compat-v7): @decocms/compat-v7 with v7's types, device detection, scripts and clx #702).

Differences from v7

  • Next.js sites: v7's Next binding never wrote deco_segment, so random was drawn again on every request. Here random is sticky on Next too, as approved ("latest 7.x sticky for every site"), and the cookie is written by a page script instead of a Set-Cookie header.
  • useDevice() outside a provider on the server returns "desktop". v7 read its request scope; mount DeviceProvider with requestDevice() to get the request's device.
  • The A/B split runs inside the worker (routes), where v7 wrapped it around everything. The worker answers /deco/_liveness, the cache purge and the CMS redirects before the split runs. A legacy-origin response then leaves through the worker's single exit like any routes response, so it gets CDN-Cache-Control: no-store when it sets none and the site's security headers when it sets none. It gets none of the worker's preload Link hints (they name the new site's assets), as in v7 (P4 68167723, compat 7fad06ae). v7 passed the legacy response through untouched. A site's middleware around the worker (for example its VTEX cookie middleware) now also sees legacy responses.

For the step that wires compat-v7 into the migrator and the sites

  • Replace @decocms/apps-commerce/ imports with @decocms/compat-v7/commerce/. The skill's import map still says to copy these files into the site.
  • Multivariate: no longer a difference. Since PO decision 3, @decocms/blocks stops at the first match as v7 did (tested above with the compat random).

Review round (2026-10-15)

  • 7aecbca3: the fuzz against v7's withABTesting also draws ratios that round to 1% and 99% (0.004, 0.006, 0.01, 0.99). An off-by-one at the always-fallback or always-worker edge used to pass; both now fail it.
  • 7fad06ae (after P4 68167723): the legacy page carries no preload hints; the inner call's hints are no longer stripped, so the "worker" bucket keeps them once. New test: the fallback bucket's HTML response has no Link header. Reverting the P4 check fails it and the worker-bucket test (hints twice).

Main refresh (2026-10-17, 18e9a8bb)

🤖 Generated with Claude Code

https://claude.ai/code/session_016ZsmzpZ2zahRBoATBb2tBu

Rebased onto main as one commit per PR (PO, 2026-10-18); the previous history is kept on backup/compat-v7-commerce-bindings-2026-10-18.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016ZsmzpZ2zahRBoATBb2tBu
@tlgimenes

Copy link
Copy Markdown
Contributor Author

Superseded by #722 so the stack lands as one commit and one 8.4.0 release (PO, 2026-10-18).

tlgimenes added a commit that referenced this pull request Oct 18, 2026
…cocms/compat-v7, /preview, lockstep release and the migrator (#722)

This PR replaces the 11 stacked PRs #695 … #707 so the rest of the v8
framework packages land on `main` as **one commit and one release,
8.4.0**, publishing all 11 packages together: `@decocms/blocks`,
`@decocms/tanstack`, `@decocms/nextjs`, `@decocms/compat-v7` and the
seven `@decocms/apps-*`. Its branch is #707's
(`feat/migrator-invoke-scaffold`), already rebased onto `main` with one
commit per former PR and their trees unchanged, plus one commit that
raises every `@decocms/blocks` peer floor to `^8.4.0`.

#693 and #694 are already on `main` and shipped in 8.3.0 (`NODE_ENV` dev
default, `deco schema --check`, first-match multivariate, the
platform-neutral `/image` with `@decocms/apps-vtex/image` and
`@decocms/apps-shopify/image`).

## What lands

### #695 — `@decocms/blocks/preview`: the draft badge and the Studio
bridge
- `DraftBadge({ pointer, exitHref? })`: v7's "Preview mode" pill,
unchanged on screen. Renders nothing without a pointer, on the server
and inside a frame. The inlined Deco mark is now a 36px PNG (~2KB, v7
inlined ~22KB).
- `StudioBridge({ site, trustedOrigins, editShortcut?, nonce? })`: v7's
`LiveControls` as one static inline script, so it runs before hydration
and a Server Component can render it. `trustedOrigins` is required; it
answers only the parent frame from those origins.
- `@decocms/blocks` exports no Studio origins and no draft names: the
bindings own them.
- PO decision 4: the "." / Ctrl+Shift+E edit shortcut is on by default,
as in v7; `editShortcut={false}` turns it off.

### #696 — `@decocms/tanstack` for v8
- For TanStack Start on Cloudflare Workers, depending only on
`@decocms/blocks`. Subpaths: root (draft names, `draftResponseHeaders`,
`STUDIO_ORIGINS`), `/worker` (`createDecoWorker`), `/server`, `/start`,
`/router`, `/react` (`DecoDocument`, `NavigationProgress`,
`StableOutlet`), `/vite` (`decoTanstack`), `/scope`.
- `createDecoWorker`: Studio framing, v7's draft host allowlist (D1),
CMS redirects, the edge cache (build + revision + device + segment key,
SWR/SIE, never storing private, signed-in or degraded responses), the
`__cseg` server-function cache check, Set-Cookie dedup, immutable
assets. `onCache` is the only metrics hook (D3); build hash from CI's
sha (D4).
- Owns the draft names, headers and Studio origins that #693 had put in
blocks (PO review of #693); values unchanged.

### #697 — `@decocms/nextjs` for v8
- Helpers for any Next.js App Router app, no hosting assumptions,
depending only on `@decocms/blocks`: `/proxy` (`decideDraft`,
`draftProxy`, …), `/server` (`bindDraft`, `contentClient`), root
(`DraftPreviewIndicator`, `DraftBadge`, `StudioBridge` with Studio's
origins), `/config` (`withDeco`), `/image-loader`
(`createDecoImageLoader`, platform-neutral).
- PO decision 1: `isNextDev` is gone; `createCMS` reads `NODE_ENV`. PO
decision 4: the edit shortcut is on by default. Its own copy of the
draft names and Studio origins (PO review of #693). Image golden test:
1,212 cases, only the 60 of PO-approved fix 2 (#694) change.

### #699 — lockstep release
- `.releaserc.json` publishes every package in `packages/` at one
version on `latest` from `main`, blocks first, then the bindings.
- Every package that builds on blocks takes it as a caret **peer** (now
`^8.4.0`) with `workspace:*` only in `devDependencies`, so a site
installs one blocks copy.
- `scripts/sync-versions.mjs` writes the version and refuses a plain
in-repo dependency or a peer floor above the release.
`tests/release.test.ts` checks the allowlist, the ranges, every export's
shape and what each tarball ships; CI runs it on the built dist.

### #700 — the migrator and skill use the bindings
- The v7-to-v8 script and the `deco-v7-to-v8-migration` skill move sites
onto `@decocms/tanstack` / `@decocms/nextjs` instead of copying v7's
worker, draft, bridge and image code.
- `@decocms/tanstack/react` gets v7's `Image`, `Picture`, `Source` (same
props and markup, URLs from `ImageUrlProvider`); 160 golden markup
cases, zero changed.
- PO decisions 1, 2 and 5: no dev flag; the script adds `deco schema
--check` to the site's CI; `decoCdn` strips only a leading asset host
and the report lists every URL that changes.

### #702 — `@decocms/compat-v7`: types, device, scripts, clx
- The PO-approved home (2026-10-14) for v7's **pure** helpers on v8, so
migrated sites stop keeping copies. No v7 runtime (`ctx`, invoke,
request scope), no env reads, nothing added to blocks.
- `/types` (`SectionProps` = the loader's awaited return, per the PO),
`/device`, `/script`, `/clx`. v7's own files are hash-pinned fixtures
and every helper is compared against them over seeded inputs.

### #703 — `@decocms/compat-v7/matchers`
- v7's website matchers with v7's results; each takes the request as its
second argument. `random` is latest 7.x's sticky random on the
`deco_segment` cookie (PO decision), keyed by the saved block's name
(`cohort`).

### #704 — `@decocms/compat-v7/sections`
- `runSectionLoaders` / `runSectionLoader`: v7's `(props, req)` section
loaders after a page resolves, with v7's nested walk (deep, or `nested:
"direct"` for 7.77.12 and earlier), `__pageUrl`/`__pagePath`, and the
`degraded` list. v7's mixins. Reading `ctx` throws with what to use
instead.

### #705 — `@decocms/compat-v7` `/commerce`, `/tanstack`, `/nextjs`
- `/commerce/*`: v7's `@decocms/apps-commerce` pure files under the same
layout (AST-equal to v7, 5,000 generated inputs each).
- `/tanstack` and `/nextjs`: `matcherContext`, `commitSegmentCookie`,
`cohortSegment`, `requestDevice`, `DeviceProvider`.
- `/tanstack/worker` (PO decision 6): v7's `withABTesting` legacy-origin
split as a `createDecoWorker` `routes` handler, matched to v7 over 3,000
seeded requests. PO decision 3 checked against the compat random.

### #706 — the migrator imports `@decocms/compat-v7`
- Rewrites v7 helper and `@decocms/apps-commerce` imports onto
compat-v7, rewrites `ctx.device`, turns `registerSectionLoaders` into
`sectionLoaders` and wires `loadSections`, registers the matchers the
content uses and writes the image URLs module.
- PO decision 8: every section's own `loader` runs, and each one v7
never ran is reported. PO decision 9: blocks are registered under their
v7 names only.

### #707 — the migrator writes invoke, app clients, the page setup and
stubs
- Each `/deco/invoke` key becomes one server function (TanStack Start
`GET` loaders marked private and `POST` actions; Next.js `"use
server"`), imported at each call site.
- PO decision 7: VTEX invoke-map keys need an explicit `--vtex-invoke
keep|v8` choice, no default.
- `deco-vtex`/`-shopify`/`-wake`/`-resend` app blocks become
`src/clients/<app>.ts`; `src/migration-stubs.ts` holds deprecated stubs
for v7 runtime with no port; the page setup (`src/cms.ts`, `open-page`,
the catch-all route) is written per binding.

### This PR's own commit — peer floors `^8.4.0`
The bindings and compat-v7 use blocks APIs that 8.3.0 doesn't have
(`@decocms/blocks/preview` and the rest of this stack), so every
`@decocms/blocks` peer (and compat-v7's optional binding peers) is
`^8.4.0`. `tests/release.test.ts` syncs at 8.4.0 and 8.5.1 and refuses
8.3.x; the migrator writes `^8.4.0`; CLAUDE.md, the README and the skill
name 8.4 as the floor.

## Checks

- **Release dry run** in a throwaway clone of a mirror with the tags
(deleted afterwards; nothing pushed to GitHub or published): `main` +
this branch squashed into one commit with this PR's title.
`semantic-release --dry-run`: "Found git tag blocks-v8.3.0 … The next
release version is 8.4.0". The prepare step (`VERSION=8.4.0 node
scripts/sync-versions.mjs && bun run build`): 11 manifests at 8.4.0,
every peer `^8.4.0`, all builds exit 0. `npm publish --dry-run --tag
latest` for all 11: `@decocms/blocks@8.4.0` …
`@decocms/apps-wake@8.4.0`, each "with tag latest". The 11 tarballs
installed together: `npm ls @decocms/blocks` shows one `8.4.0`, every
other entry deduped; no required peer is unmet.
- **Gates at the top:** `bun run check`, `bun run build`,
`skills:check`, `skills:readme:check`, `bun test tests/release.test.ts`
(42 pass), the compat-v7 tests (13 files, 153 pass), and `bun run test`
under a 2 GB cap: 122 files pass, 1 skipped; 2,746 tests pass, 95
skipped, 0 fail.

## Landing

1. Approve, then **squash** with this PR's `feat:` title. The squash
message is the PR body (repo setting), so this body is what
commit-analyzer reads: one `feat:`, a minor release.
2. The release publishes **8.4.0 on `latest`** for all 11 packages.
3. **Before landing**, check that npm Trusted Publishing for
`@decocms/tanstack`, `@decocms/nextjs` and `@decocms/compat-v7` accepts
`main`'s `.github/workflows/release.yml`. The bindings were last
published from the 7.x branch: npm's trusted publisher names the repo
and workflow file, not a branch, so `main` should be accepted, but if
either is tied to a GitHub environment, add it for `main` first.
compat-v7's trust config already exists and its `0.0.0` placeholder is
published.

Once this lands, the 11 stacked PRs can be closed.

## npm commands for the PO after the release (not run)

```bash
# 1. Remove the stale `next` dist-tag (8.1.0) from blocks and every apps-* that has it (all seven do today).
npm dist-tag rm @decocms/blocks next
npm dist-tag rm @decocms/apps-algolia next
npm dist-tag rm @decocms/apps-magento next
npm dist-tag rm @decocms/apps-resend next
npm dist-tag rm @decocms/apps-sfmc-personalization next
npm dist-tag rm @decocms/apps-shopify next
npm dist-tag rm @decocms/apps-vtex next
npm dist-tag rm @decocms/apps-wake next

# 2. Rewrite the 8.0.0 deprecation (it says "use @latest (7.x)", wrong once latest is 8.4.0).
npm deprecate @decocms/tanstack@8.0.0 "Published by mistake. Use ^8.4.0 for v8, or 7.x for v7."
npm deprecate @decocms/nextjs@8.0.0 "Published by mistake. Use ^8.4.0 for v8, or 7.x for v7."

# 3. Deprecate the two prereleases (not deprecated today).
npm deprecate @decocms/tanstack@"8.0.0-next.0 || 8.1.0-next.0" "Prerelease. Use ^8.4.0 for v8, or 7.x for v7."
npm deprecate @decocms/nextjs@"8.0.0-next.0 || 8.1.0-next.0" "Prerelease. Use ^8.4.0 for v8, or 7.x for v7."
```

No `latest-7` command is needed: #698 already moved `latest-7` to 7.80.2
for both bindings. `latest` moves to 8.4.0 by itself when the release
publishes.

Checks afterwards:

```bash
npm view @decocms/blocks dist-tags      # { latest: '8.4.0', 'latest-7': '7.80.2' }, no next
npm view @decocms/tanstack dist-tags    # { latest: '8.4.0', 'latest-7': '7.80.2' }
npm view @decocms/nextjs dist-tags      # { latest: '8.4.0', 'latest-7': '7.80.2' }
npm view @decocms/compat-v7 dist-tags   # { latest: '8.4.0' }
for p in algolia magento resend sfmc-personalization shopify vtex wake; do npm view @decocms/apps-$p dist-tags; done   # latest: '8.4.0', no next
npm view @decocms/tanstack@8.0.0 deprecated
npm view @decocms/nextjs@8.1.0-next.0 deprecated
npm view @decocms/tanstack@8.4.0 peerDependencies   # @decocms/blocks: '^8.4.0'
```

🤖 Generated with [Claude Code](https://claude.com/claude-code)

https://claude.ai/code/session_016ZsmzpZ2zahRBoATBb2tBu


<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Replaces PRs #695–#707 so the v8 framework packages land as one commit
and one release: 8.4.0 publishes all 11 packages together
(`@decocms/blocks`, `@decocms/tanstack`, `@decocms/nextjs`,
`@decocms/compat-v7`, and the seven `@decocms/apps-*`). The v7-to-v8
migrator and its skill now move sites onto these bindings instead of
copying v7's worker, draft, bridge, and image code into the site.

**What ships**
- `@decocms/blocks/preview`: the draft badge and Studio bridge, as one
static inline script that runs before hydration.
- `@decocms/tanstack`: the TanStack Start binding on Cloudflare Workers
— drafts, edge cache, CMS redirects, draft names and Studio origins.
- `@decocms/nextjs`: the Next.js App Router binding — draft proxy,
content client, image loader, Studio bridge, without hosting
assumptions.
- `@decocms/compat-v7`: v7's pure helpers (types, device, scripts, clx,
matchers, section loaders, commerce files) with v7's output, so migrated
sites stop keeping their own copies.
- The migrator rewrites `/deco/invoke` calls into server functions,
writes the page setup per binding, registers v7 matchers and section
loaders, and reports everything left for a person.

**Release and migration**
- All packages release in lockstep at one version on `latest`; every
package that builds on blocks takes it as a `^8.4.0` caret peer, so a
site installs one blocks copy.
- Existing v7 sites stay on `latest-7`; `next` dist-tags and misplaced
`8.0.0`/prerelease deprecations must be cleaned up after release
(commands in the PR body).

<sup>Written for commit 6195fe5.
Summary will update on new commits.</sup>

<a href="https://cubic.dev/pr/decocms/blocks/pull/722?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="View guided diff"
src="https://www.cubic.dev/buttons/review-in-cubic-light.svg"></picture></a>
<a
href="https://www.cubic.dev/action/auto-fix/pr/decocms/blocks/722?returnTo=https%3A%2F%2Fgithub.com%2Fdecocms%2Fblocks%2Fpull%2F722&source=description"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/turn-on-auto-fix-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/turn-on-auto-fix-light.svg"><img
alt="Turn on auto-fix"
src="https://www.cubic.dev/buttons/turn-on-auto-fix-light.svg"></picture></a>

<!-- End of auto-generated description by cubic. -->

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
@tlgimenes

Copy link
Copy Markdown
Contributor Author

Landed in #722 (squashed as one commit and one 8.4.0 release).

@tlgimenes tlgimenes closed this Oct 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant