Skip to content

feat(migrator): v7 helpers from @decocms/compat-v7; matchers, section loaders, views and image URLs wired - #706

Closed
tlgimenes wants to merge 1 commit into
feat/compat-v7-commerce-bindingsfrom
feat/migrator-compat-imports
Closed

tlgimenes wants to merge 1 commit into
feat/compat-v7-commerce-bindingsfrom
feat/migrator-compat-imports

Conversation

@tlgimenes

@tlgimenes tlgimenes commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

The v7-to-v8 migration script now uses @decocms/compat-v7. Before this change, its report told sites to copy v7's helpers into their own code; now the script rewrites the imports and generates the wiring. It's stacked on #705.

What the script does now

Imports onto @decocms/compat-v7 (scripts/compat.ts, merged into MOVES):

  • Commerce: @decocms/apps-commerce/<subpath> becomes @decocms/compat-v7/commerce/<subpath>: the same files, in any import form, including inside vendored loaders. A stray 6.x @decocms/apps/commerce/<subpath> gets the same rewrite. v7's app runtime (app-types, registry, resolve, manifest-utils) is reported, not moved.
  • Helpers: clx, useScript/useScriptAsDataURI/inlineScript, detectDevice and its siblings, SectionProps/Resolved/LoadingFallbackProps/Flag, the widget types, and the mixins (compose, withDevice, withMobile, withSearchParam, withSectionLoader) each move to their compat subpath. A declaration that mixes moved and unmoved names is split.
  • Device context: useDevice, DeviceProvider and DeviceContext move to /tanstack or /nextjs, by binding. The report says how to seed the provider with requestDevice().
  • Names with no port (AppContext, useSection, registerLayoutSections, …) are reported with what to do instead.

ctx.device: in a v7 loader (props, req, ctx) it becomes detectDevice(req.headers.get("user-agent") ?? ""). That is what v7's buildSectionLoaderContext computed. Any other ctx.* read is reported: it throws in v8, and its section keeps its saved props.

Section loaders (scripts/sectionLoaders.ts):

  • registerSectionLoaders({ … }) becomes export const sectionLoaders, and registerNonCriticalSections([…]) becomes export const nonCriticalSections.
  • .deco/index.ts exports loadSections(sections, request, url?) over runSectionLoaders. It uses nested: "direct" for sites on 7.77.12 or earlier, and the default deep walk otherwise.
  • v7 ran only the loaders a site registered. The block map now also runs every other section's own loader (PO decision 8, 2026-10-15): the registered map wins where it lists a section, loadSections exists even when the site registered nothing, and each loader v7 never ran is reported by name as an approved difference to list in the PR. A loader typed with v7's ctx is wrapped, since the runner calls (props, request).

Matchers (scripts/matchers.ts):

  • On a TanStack Start or Next.js site, each v7 matcher the content uses is registered under its v7 name from @decocms/compat-v7/matchers: device, random, cookie, host, pathname, queryString, location and multi.
  • The request comes from the binding's matcherContext(). On TanStack, the generated matchPage(href) tells the matchers which page is opening during a client-side navigation.
  • environment is reported.
  • Each saved random matcher gets its saved name as cohort (the key v7's sticky random used in deco_segment), only on sites where the matcher is registered, so deco check stays green elsewhere.
  • The report says to call commitSegmentCookie() and pass cohortSegment to the worker on TanStack, or pageUrlRequestHeaders and <SegmentCookie /> on Next.js.

Block names: each block is registered under its v7 names only, with no short name, so the site editor's pickers list each block once (PO decision 9).

Views: a descriptor's component is now the section's v7 type name (site/sections/….tsx), the key v7's loader map uses. src/views.ts maps each name to its module. An existing src/views.ts is kept.

Images: on TanStack, the script writes src/imageUrl.ts with v7's URLs. These come from the release's rules, or from the site's own copy of v7's image helper, plus the domain and quality it registers. The script also puts ImageUrlProvider into createDecoRouter's Wrap. It reports the cases it can't handle: a non-literal registered value, an unreadable copy, a Wrap it can't read, or a .ts router. src/imageUrl.ts never sets stripAssetHosts (decoCdn's "leading" default, PO decision 5); for a site whose v7 Image stripped the asset hosts anywhere, the file's comment names the approved difference and the report's "image URLs" line counts the URLs it changes.

A/B split: a v7 withABTesting import is reported with abTestingRoutes from @decocms/compat-v7/tanstack/worker. It isn't rewritten, because a wrapper becomes a routes handler.

From the lower branches: deco schema --check in the site's CI, and no dev/isNextDev (decisions 1 and 2, from #700).

Docs: SKILL.md, import-map.md, script-steps.md, manual-steps.md, framework-code.md and CLAUDE.md describe the new behaviour. knip.json lets the skill's tests import compat-v7, as it already does for the other packages.

Tests

  • compat.test.ts (new): 21 tests, at least one per codemod.
    • Commerce mapping, with the entry list pinned to compat-v7's exports.
    • Name moves and splits, and the device context by binding.
    • ctx.device: the rewrite, aliasing, what is left alone and what is reported. A check runs the generated expression against detectDevice, as v7 did.
    • The section-loader conversion; an unregistered loader wired, listed, and overridden by the registered map (run end to end); loadSections with no registration.
    • Matcher registration on TanStack, and what a site without a binding gets.
    • cohort, views.ts, and Wrap (inline, added, unreadable, .ts).
  • bindings.test.ts: migrates a v7 TanStack Start fixture and a v7 Next.js one (an A/B test, a device rule, every helper, a registered withSectionLoader). Then it type-checks every src/ file plus .deco/index.ts and src/views.ts against the repo's packages.
  • migrate.test.ts: imports the migrated block map and runs loadSections on the migrated page. The result: v7's compose(withDevice(), withSearchParam()) output, and a ctx.device loader through withSectionLoader.
  • imageCopy.test.ts: for each 7.x Image excerpt, releaseRules(version) gives the same decoCdn options as the excerpt's own code, under five registered domain/quality pairs. src/imageUrl.ts is checked from a copy and from a release.
  • Dry runs: on throwaway copies of three v7 storefronts, with nothing committed. The script ran cleanly on each. On the demo, the generated src/imageUrl.ts equals the hand-migrated site's.

Gates

Gate Result
bun run check pass
bun run build pass
Skill tests 151 pass
bun run test (2 GB cap, at #707's head) 2,723 passed, 2 failed, 95 skipped. DS-2 also fails on main (fix in its own PR); the schema.test.ts timeout passes alone (68/69 with DS-2)
skills:check and skills:readme:check pass
Plain-Node import of every built export 64/64 exports (compat-v7 27/27); deco --help lists --check

Not in this PR

  • A scaffold step (the page loader that calls matchPage, loadSections, commitSegmentCookie): the report says what to call where.
  • An invoke codemod.
  • v7's onBeforeResolveProps section convention.
  • The docs site's /next pages still say to copy these helpers into the site. A docs PR should follow.

Main refresh (2026-10-17, ceba1386)

🤖 Generated with Claude Code

https://claude.ai/code/session_016ZsmzpZ2zahRBoATBb2tBu


Summary by cubic

The v7-to-v8 migration script now rewrites v7 helpers, fixtures, and wiring onto @decocms/compat-v7 instead of telling sites to copy them in.

  • Rewrites commerce and helper imports (clx, useScript, detectDevice, widget types, section-loader mixins) to @decocms/compat-v7; names with no port are reported instead.
  • Rewrites a v7 loader's ctx.device to detectDevice(req.headers.get("user-agent") ?? ""); any other ctx.* read is reported since it throws in v8.
  • Converts registerSectionLoaders and registerNonCriticalSections into exports that .deco/index.ts runs via loadSections, along with every section's own loader (v7 only ran registered ones). The registered map wins where it lists a section; each loader v7 never ran is reported as an approved difference.
  • Registers the v7 matchers the content uses on TanStack and Next.js sites, keeping each saved random matcher's cohort so deco_segment groups carry over.
  • Registers every block under its v7 names only, no short names, so the editor lists each once; each section's descriptor component is its v7 type name and src/views.ts maps them (an existing one is kept).
  • On TanStack, writes src/imageUrl.ts with v7's URLs and puts ImageUrlProvider into createDecoRouter's Wrap; unwritable cases are reported. src/imageUrl.ts never sets stripAssetHosts; sites whose v7 Image stripped asset hosts get a comment naming the difference and a report line counting the URLs changed.
  • Types the page's seo as the block map's PageSeo, v7's shape, so existing SEO blocks register and pass deco check; the SEO hint references that same type.
  • Reports a v7 withABTesting import with abTestingRoutes (@decocms/compat-v7/tanstack/worker) instead of rewriting it, since a wrapper becomes a routes handler.
  • A scaffold step, an invoke codemod, and v7's onBeforeResolveProps convention are still left to the report.

New compat.test.ts covers each codemod, with the entry list pinned to compat-v7's exports. bindings.test.ts migrates and type-checks both a TanStack and a Next.js fixture; migrate.test.ts runs the migrated loadSections end to end. The image release rules for src/imageUrl.ts are checked against each 7.x Image excerpt in imageCopy.test.ts.

Written for commit a99a734. Summary will update on new commits.

View guided diff Turn on auto-fix

… loaders, views and image URLs wired (#706)

Rebased onto main as one commit per PR (PO, 2026-10-18); the previous history is kept on backup/migrator-compat-imports-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