feat(compat-v7): @decocms/compat-v7 with v7's types, device detection, scripts and clx

TL
5h ago
+1385-17

Summary

The product owner approved (2026-10-14) a new package, @decocms/compat-v7, as the one home for v7's pure helpers on v8, so migrated sites stop keeping their own copies. This PR adds the package and its framework-neutral helpers. Matchers, the section-loader runner, commerce types and the /tanstack and /nextjs subpaths come in the PRs stacked on this one.

No v7 runtime: no ctx, invoke, request scope or cachedLoader. There are no environment reads, and nothing new goes into @decocms/blocks.

Import pathWhat
@decocms/compat-v7everything below
/types (+ /types/widgets)SectionProps (the loader's awaited return, per the PO), Resolved, LoadingFallbackProps, Flag, v7's widget aliases. Types only, with no AppContext/FnContext
/devicedetectDevice, isMobileUA, Device, v7's MOBILE_RE/TABLET_RE
/scriptuseScript, useScriptAsDataURI, inlineScript: v7's bytes, without v7's NODE_ENV warning
/clxclx (named and default)

Pinned to v7

v7's own files are kept verbatim in src/__fixtures__/v7/ (7.x 9131e9d9, @decocms/blocks 7.79.6), with their sha256 pinned by fixtures.test.ts. Every helper is compared against them:

  • device: same regex sources and flags; 25 real user agents; 50,000 seeded generated user agents, all three outcomes hit
  • clx: edge cases (including v7's untrimmed edge space); 20,000 seeded argument lists
  • script: 10 hand-written function shapes × 4 argument lists (comments, // inside strings, regexes, template literals, generators, classes); 5,000 seeded generated functions with JSON arguments, run through the LRU's evictions; useScriptAsDataURI and inlineScript
  • types: expectTypeOf equality with v7's SectionProps for Promise-returning loaders and the default, Resolved, LoadingFallbackProps, Flag and all 9 widget aliases
  • mutations: adding .trim() to clx, an extra UA token, or ", " as the args separator each fail the suites

Differences from v7 (all PO-approved or forced by the rules)

  • SectionProps of a synchronous loader: you get the value. v7 gave the loader type itself. PO: "SectionProps = Awaited".
  • useScript warning: it no longer prints the dev warning, because packages read no env.
  • deco schema: v8's generator reads the widget aliases by name from any import (tests/compat-v7.schema.test.ts, for root, /types and /types/widgets). The fields come out as v8 already makes them: Color gets format color (v7 wrote color-input, which the site editor renders the same way), and Secret becomes a secret block (v7 used a password string).
  • Not ported: useSection/usePartialSection (Fresh HTMX stubs) and AppContext/FnContext/App/Section (v7's ctx and app runtime).
  • scriptAsDataURI: v7 has no such name, so it ships as v7's useScriptAsDataURI. The Fresh-era scriptAsDataURI (unminified, ", " separator) was a different function.

Release and guardrails

  • Lockstep release: compat-v7 joins .releaserc.json's allowlist after nextjs, the root build order and the release tests (exports shape, tarball contents, peer @decocms/blocks ^8.3.0). CI's "Plain Node imports every built export" loop picks it up (6 exports).
  • New npm name: @decocms/compat-v7 404s on npm, and 7.x publishes only packages/*/ there, which has no compat-v7. Publishing uses Trusted Publishing (OIDC), so the name needs the same bootstrap the other new names got (a manual first publish, then this workflow set as its trusted publisher) before the first release.
  • tests/compat-v7.guardrail.test.ts: the neutral subpaths (and every file they reach) use no fetch, React, "use client" or env reads, and import nothing at runtime but their own modules and @decocms/blocks. They bundle for browser and workerd with no node:/React, and export none of v7's runtime names. @decocms/blocks is the one required peer, and a binding may only be an optional peer. /nextjs never imports tanstack, and /tanstack never imports nextjs. No other package depends on or imports compat-v7.
  • guides.cli.test.ts in-11: amended for the one compat package.
  • CLAUDE.md: "No compat layers" now names this PO-approved exception (pure helpers only, no v7 runtime). Sites, blocks, the bindings and the clients still may not re-create v7 APIs. Also updated: the architecture tree, the exports table, the release allowlist (eleven packages) and the dependency direction. README.md and the release workflow comment are updated too.

Follow-ups (not here)

  • The migration skill's import map still says "copy into the site" for these helpers. The codemod PRs in this stack will point imports at compat-v7.
  • The docs site's /next pages need a matching PR.

Test plan

  • bun run check (typecheck, biome, knip)
  • bun run build
  • Plain-Node import of every built export, as in CI: 11 packages, compat-v7 6/6, deco --help
  • bun run test under a 2 GB cap: 2494 passed / 1 failed / 95 skipped. The failure is DS-2, the known one: it fails the same way when delivery.cli.test.ts runs alone (10/11), and it reads protocol/storage/fs/git.ts, which this PR doesn't touch.
  • compat-v7: 14 tests, guardrail 17, schema 3
  • skills:check + skills:readme:check

Main refresh (2026-10-17, 183bc4b7)

  • Merged P7 ced92e19 into 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:check pass. 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

https://claude.ai/code/session_01WNwbSEePYNcY5YCgqZURig


Summary by cubic

Adds @decocms/compat-v7, the single home for v7's pure helpers on v8: section types and widget aliases (/types), device detection (/device), inline scripts (/script), and clx (/clx), so migrated sites stop keeping their own copies. Every helper is pinned byte-for-byte against v7's own code, kept verbatim in fixtures with pinned hashes and exercised by seeded fuzz runs.

Behavior differences from v7

  • SectionProps of a synchronous loader now yields the loader's return value; v7 gave the loader type itself.
  • useScript no longer logs the development warning, because packages read no environment.
  • deco schema renders Color as format color and Secret as a secret block, which the site editor renders the same way v7's color-input and password field did.
  • Not ported: useSection/usePartialSection (Fresh HTMX stubs) and AppContext/FnContext (v7's ctx runtime).

Release and guardrails

  • The package joins the lockstep release; as a new npm name it needs one manual first publish to set up Trusted Publishing.
  • Guardrail tests hold it pure: no React, network, env reads, or v7 runtime exports, and no other package depends on it.
  • The migration skill's import map still says "copy into the site"; the codemod PRs in this stack will point imports at this package.