Skip to content

Split @decocms/start into a bun workspace monorepo + ship @decocms/next#315

Open
tlgimenes wants to merge 76 commits into
mainfrom
v7
Open

Split @decocms/start into a bun workspace monorepo + ship @decocms/next#315
tlgimenes wants to merge 76 commits into
mainfrom
v7

Conversation

@tlgimenes

@tlgimenes tlgimenes commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Summary

Long-term tracking PR for the v7 line — not intended to merge immediately. main keeps shipping the legacy single-package @decocms/start (currently v6.x); v7 is where the split package set (@decocms/blocks, @decocms/blocks-admin, @decocms/blocks-cli, @decocms/tanstack, @decocms/nextjs) develops and releases independently until it's ready to become the primary line.

Successor to #307 (merged to a branch originally named 7.x, since renamed to v7 — see below for why).

  • Splits @decocms/start into a 5-package Bun workspace with a one-way dependency graph. Root-caused the module-state-duplication bug that sank an earlier attempt at this (shipped as v5.2.2, reverted) — this split uses plain .ts source exports, no dist/tsup bundling.
  • Ships @decocms/nextjs, a new Next.js App Router binding mirroring @decocms/tanstack's capabilities (deferred/streamed sections via RSC Suspense, DecoPageRenderer, createDecoPage, DecoRootLayout, admin protocol Route Handlers).
  • Package naming went through two rounds: @decocms/runtime@decocms/live (collided with a real, unrelated, actively-published npm package) → @decocms/blocks (per a later naming decision). @decocms/admin@decocms/blocks-admin and @decocms/cli@decocms/blocks-cli to disambiguate them as part of the blocks family (@decocms/tanstack/@decocms/nextjs are already self-explanatory as framework bindings, left unchanged). @decocms/next@decocms/nextjs for explicitness.
  • Independent release automation (.releaserc.v7.json + .github/workflows/release.yml): separate tag namespace (blocks-v*.*.*) from main's v*.*.*, workflow_dispatch dry-run escape hatch verified against real CI.
  • All 5 packages published for the first time (7.0.0) via a manual local publish once a properly-configured npm granular access token (2FA bypass + org read/write) was provisioned. Push-triggered releases on v7 are now live — the first real CI release (7.0.1, picking up the renames/fixes since the 7.0.0 seed) is in flight as of this writing.

Test plan

  • All 5 packages typecheck clean (bun run typecheck)
  • All 5 packages' full test suites pass via vitest — 908 tests total (blocks 455, blocks-admin 15, nextjs 10, blocks-cli 369, tanstack 59)
  • Release workflow verified via workflow_dispatch dry-run against real CI
  • Real first publish of all 5 packages to npm (7.0.0), confirmed live on npmjs.com
  • First CI-driven release (7.0.1) — in flight, retrying after npm registry replication lag on the first attempt

🤖 Generated with Claude Code


Summary by cubic

Split the legacy @decocms/start into a Bun workspace monorepo, shipped @decocms/nextjs, and merged the former @decocms/apps into @decocms/apps-*. Recent updates add a Next.js glue tier (withDeco, createNextSetup, createDecoRouteHandlers), non‑Vite section registry generation, codegen/CJS compatibility fixes that stabilize schema IDs and improve admin routing, and now default all codegen outputs into .deco/ with a legacy-path guard; the release pipeline uses npm Trusted Publishing and pins npm to 11.x to avoid npm 12 provenance failures. Docs now record why generate-invoke must stay at src/server/invoke.gen.ts.

  • New Features

    • Next.js glue: withDeco (Studio rewrites + transpilePackages), createNextSetup (one‑call site bootstrap), and createDecoRouteHandlers (single App Router dispatcher). examples/nextjs-smoke updated to use them.
    • Non‑Vite bundlers: @decocms/blocks-cli generate-sections --registry emits a lazy sectionImports map for Next.js.
    • Codegen defaults: @decocms/blocks-cli now writes generated artifacts to .deco/ by default — .deco/blocks.gen.ts, .deco/loaders.gen.ts, .deco/sections.gen.ts, .deco/meta.gen.json — with a one‑line warning when an old default file still exists; generate-invoke stays at src/server/invoke.gen.ts. Dev watcher (Vite plugin), templates, and @decocms/nextjs docs updated to match (use a deco/* alias).
  • Bug Fixes

    • Codegen: exclude test/spec/story/gen files from scans; use repo‑relative schema definition IDs so /live/_meta ETags are stable across machines.
    • Next.js admin routes: accept pre‑rewrite paths (/.decofile, /live/_meta, /live/previews/*), and enforce POST‑only on invoke/*; createNextSetup now clears a rejected bootstrap to avoid poisoning warm instances.
    • CJS/Next compatibility: replaced import.meta.env reads with process.env.NODE_ENV in @decocms/blocks-admin and @decocms/blocks; added "use client" to Picture to prevent react‑server import crashes.
    • CI releases: switched to npm Trusted Publishing (OIDC) and pin npm to 11.x to work around npm 12 sigstore/provenance crashes during Trusted Publishing.
    • Tests: increased generator test timeouts to 30s to eliminate flakiness in subprocess‑driven fixtures.
    • Dev bootstrap: floor tsx to ^4.22.5 in @decocms/blocks-cli to fix Vite tsImport returning an empty namespace (4.22.0–4.22.4); improved the @decocms/tanstack Vite plugin’s error message for this case.
    • Docs: clarified the empirical reason invoke.gen.ts must remain under src/ (server function resolution), no runtime change.

Written for commit 336b91e. Summary will update on new commits.

Review in cubic

…o + ship @decocms/next

Squashed history of PR #307 (originally merged to a branch named 7.x,
then migrated to v7 — see below). Three phases plus release
automation:

Phase 1 — Monorepo split
=========================
Splits the single `@decocms/start` package into a 5-package Bun
workspace with a one-way dependency graph:
  packages/live     → @decocms/live     (CMS core: cms/, matchers/,
                       sdk/, hooks/, types/, admin schema composition —
                       named "live" not "runtime": @decocms/runtime
                       already exists on npm as a real, unrelated,
                       actively-published package at v2.3.1)
  packages/admin    → @decocms/admin    (admin protocol handlers,
                       depends on live)
  packages/cli      → @decocms/cli      (codegen scripts + Fresh→
                       TanStack migration tooling, depends on live)
  packages/tanstack → @decocms/tanstack (TanStack Start + Cloudflare
                       Workers binding: workerEntry, cmsRoute, vite
                       plugin, daemon; depends on live + admin)
  packages/next     → @decocms/next     (new)

Root-caused and fixed the real bug motivating the split: dist/tsup
bundling caused module-state duplication across package boundaries,
which is why an earlier attempt at this shipped as v5.2.2 and had to
be reverted. This split ships plain .ts source exports with no
bundling step.

Phase 2 — @decocms/next (Next.js App Router binding)
======================================================
New package mirroring @decocms/tanstack's capabilities for Next.js:
SectionRenderer, DeferredSectionBoundary (RSC Suspense streaming),
client-only-forced section rendering, DecoPageRenderer,
createDecoPage, DecoRootLayout, and Route Handler re-exports of the
admin protocol. Validated against tanstack-smoke and next-smoke
fixtures.

Follow-up cleanup
==================
Refreshed README.md, CLAUDE.md, and deep-dive docs for the new
architecture. Replaced GAP_ANALYSIS.md + GAP_ANALYSIS_V2.md with
docs/known-gaps.md. Audited all ~30 .cursor/skills/ directories:
deleted 8 stale ones, merged 2 near-duplicate pairs, path-fixed the
rest. Forward-ported 3 fixes that landed on main while this branch
was in flight (x-deco-matchers-override, @title/@ignore JSDoc on app
loaders, a vite dev-watcher delta-apply fix for a real production
incident).

Release automation (v7 branch, ci/release.yml + .releaserc.v7.json)
======================================================================
main keeps releasing the legacy @decocms/start (v*.*.* tags,
unchanged config). v7 releases the 5 new @decocms/* packages
independently, under a separate tag namespace (live-v*.*.*) via a
dedicated semantic-release config — deliberately NOT sharing main's
`branches` array, since main's @decocms/start and the 5 new packages
are unrelated npm packages with no real version relationship.

Two hard-won, non-obvious fixes baked into this config, both costly
to rediscover:

1. The branch cannot be named like a semver range ("7.x"). Semantic-
   release auto-classifies any `N.x`-shaped branch NAME as a
   "maintenance" branch — regardless of how it's written in `branches`
   config, since classification runs on the resolved branch name, not
   the config-side glob. Maintenance branches are structurally
   required to have a LOWER version than a companion "release"-type
   branch, which is backwards from what we want (v7 should be newer
   than legacy main). No in-branches-array trick escapes this
   (confirmed empirically against real semantic-release source:
   lib/branches/index.js's per-type branchesValidator). Renamed the
   branch 7.x -> v7 to sidestep it entirely — "v7" doesn't match the
   range-shape pattern, so it's just a normal branch with a seeded
   starting tag (live-v7.0.0, on a commit unique to this branch's
   history, NOT shared with main — sharing it caused semantic-release
   to compute an impossible `>=7.0.0 <7.0.0`-shaped range once, since
   both branches would "see" the same tag as their own last release).
   v7 still needs a companion branch in `branches` (semantic-release
   requires ≥1 non-maintenance-shaped branch even for a single-branch
   config) — used a genuine orphan placeholder branch
   (decocms-live-legacy-anchor, pinned at a fixed v0.0.1 seed tag)
   rather than main, since main's own fluctuating computed version
   was ALSO capping v7's valid range in confusing ways.

2. semantic-release's `--dry-run` only skips its OWN native git-tag
   creation step. It calls `plugins.prepare()` and `plugins.publish()`
   UNCONDITIONALLY either way, and @semantic-release/exec's
   prepare/publish hooks don't check dry-run themselves. A naive
   exec-based publishCmd would attempt a REAL `npm publish` during a
   "dry run". .releaserc.v7.json's publishCmd checks a `$DRY_RUN` env
   var (set by the workflow from the workflow_dispatch dry_run input)
   before calling npm — verified this is a real, not theoretical, risk:
   an early version of this config without the guard reached a real
   `npm publish --access public` for @decocms/admin during testing
   (caught immediately; failed on 404 only because publish permissions
   aren't provisioned yet, not because of the dry-run guard — that
   guard didn't exist at the time).

Added a workflow_dispatch trigger with a dry_run input (default true)
specifically so the version computation and publish plan can be
inspected in real CI logs before trusting an automatic push-triggered
release — this is genuinely a one-way door (5 packages that have never
been published before) and deserves that scrutiny. First verified
computation: 7.0.1.

Known follow-up, not yet resolved: whatever npm credentials are
currently configured for this repo's Actions (NODE_AUTH_TOKEN via
actions/setup-node) return 404 (not authorized to CREATE new packages)
for @decocms/admin and @decocms/cli — confirmed via the real publish
attempt above. The actual first release will need proper npm token
provisioning with create-package rights on the @decocms org before it
can succeed.

Verified: all 5 packages typecheck clean and pass their full test
suites via vitest (live 455, admin 15, next 10, cli 369, tanstack 59
— 908 tests total).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@tlgimenes tlgimenes requested a review from a team July 7, 2026 18:42
@tlgimenes tlgimenes requested a review from vibe-dex as a code owner July 7, 2026 18:42
tlgimenes and others added 8 commits July 7, 2026 15:52
…decision)

Same rationale/mechanics as the earlier @decocms/runtime -> @decocms/live
rename: renamed packages/live/ -> packages/blocks/ (git mv, preserving
file history) and every @decocms/live import + bare packages/live path
reference (102 files) to @decocms/blocks.

Also renamed the release-line naming to match: tagFormat
live-v${version} -> blocks-v${version} in .releaserc.v7.json, and
the placeholder companion branch decocms-live-legacy-anchor ->
decocms-blocks-legacy-anchor (new orphan branch + blocks-v0.0.1 seed
tag pushed; old decocms-live-legacy-anchor branch and live-v* tags
left in place for now, not yet deleted).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…decocms/cli -> @decocms/blocks-cli

Disambiguates both packages as part of the @decocms/blocks family —
'admin' and 'cli' alone are generic enough to be confusing on their
own, unlike @decocms/tanstack and @decocms/next which are already
self-explanatory as framework bindings (left unchanged).

Renamed packages/admin/ -> packages/blocks-admin/ and packages/cli/ ->
packages/blocks-cli/ (git mv, preserving file history), plus every
@decocms/admin and @decocms/cli import + bare packages/admin and
packages/cli path reference (65 files total) to the new names.
Cross-package workspace dependencies (packages/next and
packages/tanstack both depend on @decocms/admin;
packages/tanstack also depends on @decocms/cli) updated in the same
pass. bin script entries in blocks-cli/package.json use relative
paths, unaffected by the rename.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…in/blocks-cli siblings

vitest run --root ../.. packages/blocks was matching as a path PREFIX,
not an exact directory — since packages/blocks-admin and
packages/blocks-cli both start with the substring 'packages/blocks',
running @decocms/blocks's own test script incorrectly pulled in both
siblings' test files too (35 -> 58 files, 455 -> 839 tests). A
trailing slash forces an exact directory boundary.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…e provisioned

3+ push-triggered runs during development created real git tags
before failing on npm 404 (CI's NODE_AUTH_TOKEN lacks create-package
rights on @decocms org), each requiring manual tag cleanup. Not
removing the capability, just the automatic push trigger — re-add
'v7' to the push branches list once a proper npm Automation token is
configured, then use workflow_dispatch for the actual first release
rather than relying on the next push to catch it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…citness

Renamed packages/next/ -> packages/nextjs/ and examples/next-smoke/ ->
examples/nextjs-smoke/ (git mv, preserving file history), plus every
@decocms/next import + bare packages/next and examples/next-smoke path
reference to the new names.

Also proactively added trailing slashes to every package's
'vitest run --root ../.. packages/X' test script (not just the ones
with a live collision) — packages/blocks's own test script silently
picked up packages/blocks-admin and packages/blocks-cli's tests too in
the previous rename (path-prefix match, not exact directory), and nextjs'
name is now a substring-prefix risk for any future packages/nextjs-*
addition. Trailing slash forces an exact directory boundary in all cases.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
NODE_AUTH_TOKEN is now a granular access token with 2FA bypass and
read/write access to the decocms org + all packages. All 5 v7
packages were bootstrapped to 7.0.0 via a manual local publish
(confirmed live on npm) using the same token; future commits release
through this trigger normally.

Also added .npmrc to .gitignore (holds npm auth tokens during local
publish sessions) and fixed a stale examples/next-smoke comment
missed in the nextjs rename.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…red token

Removes the NODE_AUTH_TOKEN dependency entirely — each of the 5 v7
packages now has this repo's release.yml configured as its trusted
publisher on npmjs.com. Adds an explicit npm upgrade step since
Trusted Publishing needs npm >=11.5.1 and Node 22's bundled npm is
older. Also deleted the now-unused NODE_AUTH_TOKEN repo secret.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ication

npm Trusted Publishing (OIDC) auto-generates sigstore provenance
attestations and cross-checks package.json's repository.url against
the actual GitHub repo the OIDC token came from — publish fails with
E422 if repository is missing. Adds repository.url +
directory to all 5 v7 packages, pointing at
https://github.com/decocms/blocks (this repo's current name).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown

🎉 This PR is included in version 7.0.1 🎉

The release is available on:

Your semantic-release bot 📦🚀

The declared ^1.28.0 floor is too loose: otel.ts uses
ATTR_DEPLOYMENT_ENVIRONMENT_NAME, which doesn't exist until 1.41.1
(verified directly against package tarballs — absent in 1.40.0's
stable_attributes.js, present in 1.41.1's). deco-start's own tree
happens to resolve a high-enough version so this never surfaced
locally, but a consumer (faststore-fila) with a different dependency
tree resolved 1.40.0 and hit a hard TS2724 type error on this package's
own otel code.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown

🎉 This PR is included in version 7.0.2 🎉

The release is available on:

Your semantic-release bot 📦🚀

…mports

The full @decocms/blocks/cms barrel (index.ts) re-exports loader.ts and
resolve.ts, which transitively import node:async_hooks and
node:fs/promises. Bundling ANY export from that barrel for a browser
target — even one with zero Node dependencies itself, like
getResolvedComponent — drags the whole module graph in, since ES
imports are evaluated per-file, not per-export. Turbopack rejects this
outright in production builds; webpack has historically let it
through uncaught. Found via faststore-fila's migration to this
package: a Client Component importing only getResolvedComponent
failed Turbopack's production build.

New @decocms/blocks/cms/client entry point exports just the
verified-safe subset: registry.ts (component lookups — imports only
React types), sectionMixins.ts (goes through useDevice.ts ->
requestContext.ts, whose only node:async_hooks dependency is already
swapped for a browser stub via this package's existing "browser"
export condition), and schema.ts (zero imports). Deliberately excludes
loader.ts, resolve.ts, sectionLoaders.ts, loadDecofileDirectory.ts,
blockSource.ts, and applySectionConventions.ts — resolver/storage
concerns that only make sense server-side.

Verified with a real esbuild browser-target bundle, not just tsc:
client.ts bundles clean with zero node: references; index.ts genuinely
fails the same bundle with real node:async_hooks resolution errors,
proving both that the original bug is real and that the split fixes
it. Kept as an automated regression test
(client.browserBundle.test.ts) — shells out to the esbuild CLI binary
rather than using its JS API, since the JS API's internal
TextEncoder/Uint8Array invariant check fails across Vitest's
module-isolation VM boundary (confirmed the invariant holds under
plain `bun -e`; it's a realm mismatch, not a real environment problem).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown

🎉 This PR is included in version 7.1.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

…arrel split

DecoPageRenderer.tsx (packages/tanstack/src/hooks/) is client-bundled
(useState/useEffect/Suspense/lazy) and only used runtime imports that
are all in the new cms/client subset (getResolvedComponent,
getSectionOptions, getSectionRegistry, getSyncComponent,
preloadSectionModule, setResolvedComponent, SectionOptions) — a real
consumer of the split published as @decocms/blocks@7.1.0. Its
DeferredSection/ResolvedSection type-only imports stay on the full
@decocms/blocks/cms barrel, since type imports are erased at compile
time regardless of source path, and neither type is in cms/client
anyway.

Swept the docs/skills that describe this import path for staleness:
- CLAUDE.md: added a cms/client row to the package-exports table with
  guidance on when to use it vs the full barrel; also fixed the
  Package column still saying "runtime"/"next" from before the
  @decocms/runtime->live->blocks and @decocms/next->nextjs renames.
- import-mapping.md (deco-next-package-migration skill): getResolvedComponent's
  row still described the dev-diagnostic page's view component as "a
  client component" — no longer true, it was converted to a Server
  Component in faststore-fila specifically because of this bug. Added
  the concrete fix-by-case breakdown and pointed future readers at
  cms/client for the common case.
- docs/hydration-and-ssr-migration.md: the DecoPageRenderer
  syncThenable fix sketch didn't show an import statement; added a
  note that getResolvedComponent there needs cms/client, not the full
  barrel, since it's the exact component this session found and fixed.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown

🎉 This PR is included in version 7.1.1 🎉

The release is available on:

Your semantic-release bot 📦🚀

…ability.ts

sdk/observability.ts re-exports instrumentWorker from ./otel, but
otel.ts imported configureMeter/configureTracer/getActiveSpan back
from ./observability — a real cycle (observability.ts -> otel.ts ->
observability.ts). Confirmed via a real Rollup warning surfaced while
building casaevideo-tanstack: "will likely lead to broken execution
order" when the two modules land in different chunks.

Fix: otel.ts now imports those three functions directly from
../middleware/observability (where they're actually defined —
sdk/observability.ts just re-exports them), the same module it
already imported METRIC_METADATA from. Breaks the cycle without
changing any public API.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown

🎉 This PR is included in version 7.1.2 🎉

The release is available on:

Your semantic-release bot 📦🚀

tlgimenes and others added 9 commits July 7, 2026 18:38
Captures the 2026-07-07 design session: split-by-platform package
taxonomy (blocks-* for framework, apps-* for integrations,
nextjs/tanstack for framework bindings), the matcher/flags/UI-component
duplication found and how to resolve it, lockstep release integration,
and what happens to the old apps-start repo. Design only — no
implementation yet.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Confirmed with the user: blocks/hooks (existing subpath, already houses
similar UI primitives like RenderSection/LazySection) over a new
blocks/commerce subpath, to minimize new export-surface additions.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…onorepo

15 tasks: shared migration tooling, apps-commerce (foundational), UI
component relocation to blocks/hooks, matcher/flags cleanup in blocks,
apps-website (reduced scope), then apps-vtex/shopify/magento/algolia/
salesforce/resend/blog, end-to-end verification against faststore-fila,
and a deprecation notice on the old apps-start repo.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…admin/apps/autoconfig mapping

Verified in Task 1 Step 2 by comparing apps-start's registry.ts doc comment
(references autoconfigApps()) against packages/blocks-admin/src/apps/autoconfig.ts,
which exports an equivalent autoconfigApps(blocks, registry) and documents the
old call site directly. Matches the mapping already in this repo's CLAUDE.md.
Moves apps-start's commerce/ (types, app-types, resolve, manifest-utils,
utils/*, sdk/*) and root registry.ts into a new @decocms/apps-commerce
package, excluding commerce/components/ (Task 3 moves those UI components
elsewhere). Rewrites @decocms/start/* imports via
scripts/migrate-apps-import.mjs; the registry.ts doc comment's remaining
@decocms/start references are updated by hand to the confirmed
@decocms/blocks-admin/apps/autoconfig mapping.

Test files that lived in __tests__/ subdirs in apps-start are flattened to
sibling *.test.ts files to match this repo's co-located-test convention;
their relative imports were fixed accordingly (../foo -> ./foo).

Known gap, not resolved here: registry.ts's APP_REGISTRY entries dynamically
import platform mod modules (./shopify/mod, ./vtex/mod, ./resend/mod,
./blog/mod) that lived alongside commerce/ in apps-start's single package.
Now that each platform is a separate @decocms/apps-<platform> package
depending one-way on @decocms/apps-commerce, resolving those imports would
require the reverse edge (apps-commerce -> apps-shopify -> apps-commerce),
violating the monorepo's one-way dependency rule. Left as @ts-expect-error
stopgaps with an explanatory comment pending a plan-level decision (e.g. a
site-level registry aggregator, or non-statically-typed string entries)
rather than guessing a fix.

Verified: bun install clean; packages/apps-commerce typecheck + test clean
(5 files, 50 tests); whole-workspace typecheck and test clean across all 6
packages (blocks, apps-commerce, blocks-admin, blocks-cli, nextjs, tanstack).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…m entries

Task 2's implementation surfaced a real conflict: apps-start's registry.ts
was a single static array whose entries relatively-imported every
platform's mod.ts, only possible because everything lived in one package.
Split by platform, no apps-* package can hold that array without
depending on every other apps-* package, violating the one-way rule.

User decision: apps-commerce/registry keeps only the shared types; each
platform package with a registrable app (vtex, shopify, resend, blog)
exports its own single-entry registry from its own package, and sites
compose their own array explicitly. Updates Tasks 7/8/12/13's scope
accordingly (documented here; each task's own section gets the concrete
addition when reached).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…stry types

Resolves the circular-dependency gap flagged in the previous commit: registry.ts
no longer holds a static APP_REGISTRY array whose entries dynamically imported
sibling platform mods (./shopify/mod, ./vtex/mod, ./resend/mod, ./blog/mod) that
can't resolve from this package without creating a reverse apps-commerce ->
apps-<platform> -> apps-commerce dependency edge.

Per decision: each platform package with a registrable app now exports its own
single-entry registry from its own ./registry subpath (Tasks 7, 8, 12, 13 — not
this task). apps-commerce/registry.ts keeps only the shared AppRegistryEntry /
AppRegistry types those per-platform registries will import. The
@decocms/apps-commerce package.json's "./registry" export subpath is unchanged.

Verified: packages/apps-commerce typecheck + test clean (5 files, 50 tests);
whole-workspace typecheck clean across all 6 packages.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…t commerce/components)

Moves Image, Picture, and the JsonLd SEO components from apps-start's
commerce/components/ into @decocms/blocks/hooks (existing subpath, per
the design's resolved decision to reuse it rather than add
blocks/commerce). Image and Picture were already self-contained (no
commerce-type imports) and moved unchanged; ported Image's Vitest
suite alongside it.

JsonLd.tsx imported Product/ProductListingPage/BreadcrumbList/etc. from
apps-start's commerce types module (now @decocms/apps-commerce/types),
which @decocms/blocks cannot depend on (one-way rule: apps-* depends on
blocks, never the reverse). Each JsonLd function only reads a small,
flat subset of those schema.org types (name/url/sku/image[].url/
offers.price/aggregateRating.ratingValue, etc.) — the original code
already didn't fully trust the nominal Product.offers type either (it
cast to `Offer[] | AggregateOffer`). Replaced the import with local,
minimal structural types (JsonLdProduct, JsonLdProductListingPage,
JsonLdBreadcrumbList) that describe just what's read; any real
apps-commerce Product/ProductListingPage/BreadcrumbList value is a
structural superset and can be passed in without a cast. No `JsonLd`
symbol exists in the source file (only ProductJsonLd, PLPJsonLd,
BreadcrumbJsonLd, seoMetaTags), and Picture.tsx has no default export
(named Picture/Source) — the barrel exports the components' actual
names rather than the brief's placeholder `default as X` shape.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
tlgimenes and others added 3 commits July 8, 2026 15:55
When a widget alias like Color is imported from a module ts-morph can't
resolve (remote/CDN import), the type comes through as any and
typeToJsonSchema returns {}. applyWidgetFormat matched the alias name
correctly but every application branch required a pre-existing type, so
the format was dropped silently -- the field emitted with neither type
nor format, and the admin fell back to a plain text input instead of
the color picker.

Add a final branch to applyWidgetFormat: when the alias matches a known
widget but the schema has no type/$ref (unresolved any), recover it as
{ type: "string", format } -- every widget alias is string-based.

Also makes isMainModule robust to symlinked entrypoints (compares
realpathSync of both sides instead of raw file:// URL strings -- tsx
symlink traversal was silently skipping generation and writing no
meta.gen.json).

Ported from main (#322, commit 73d9345).

Original-Author: guitavano <tavano62@gmail.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Align telemetry with the deco-cx/deco framework so both land on the
same series/labels in ClickHouse (no collisions):

- Durations normalized to SECONDS at the source (was ms). http.server /
  http.client / loader durations all divide by 1000 at record time.
- HTTP server attributes -> OTel semconv: method -> http.request.method,
  route_pattern -> http.route, status -> http.response.status_code.
  Proprietary extras moved under deco.* (deco.http.status_class /
  .outcome / .region, deco.cache.decision / .layer).
- Cache: the two hits/misses counters collapse into a single
  deco.cache.requests counter dimensioned by deco.cache.status (follows
  the OTel semconv pattern, e.g. nfs.server.repcache.requests + .status).
- Loader labels namespaced under deco.* (deco.loader.name,
  deco.cache.result).
- deco.cms.resolve.duration drops its path label entirely: the raw
  request path was unbounded cardinality (one histogram series per
  product/search/facet URL) and was the single biggest series in
  ClickHouse (~56% of otel_metrics_histogram). Per-route detail still
  lives on the resolve span's sampled deco.route attribute -- the
  correct home for high-cardinality per-request dimensions.
- normalizePath exported so every route-labeled metric shares the exact
  same cardinality-bounding normalization.

Caller API (RequestMetricLabels field names) is unchanged -- only
emitted keys change.

Ported from main (decocms/blocks, commits 6655cf3 + be66958 + 0f6ce27 +
11c23df, squashed here since all four are sequential fixes to the same
few functions in this one file -- splitting them into 4 separate
commits here would be artificial). resolve.ts's corresponding one-line
change (drop the RESOLVE_DURATION metric's path label, keep the span
attribute) is bundled into the sticky-flags commit that follows, since
that commit touches the same function for an unrelated reason.

Original-Author: Nicacio Oliveira <nicacio@deco.cx>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Two independent upstream fixes combined here because both touch
sdk/workerEntry.ts's request/response pipeline:

1. Outbound User-Agent + x-powered-by parity (#319):
   Cloudflare Workers fetch sends no User-Agent at all, unlike the old
   Deno runtime where every outbound request implicitly carried
   Deno/x.y.z. UA-less requests trip Cloudflare managed WAF rules / Bot
   Fight Mode on partner origins, blocking loader calls. New
   sdk/outboundHeaders.ts: installDefaultUserAgent() patches
   globalThis.fetch once (idempotent) to set Deco/<version>
   (+https://deco.cx) when the caller didn't set a UA; app-specific UAs
   always win. createDecoWorkerEntry installs it by default; new
   outboundUserAgent option overrides the value or disables with false.
   Responses now carry x-powered-by: deco@<version> (set-if-absent).

2. Sticky, cache-correct A/B multivariate flags (#323):
   website/matchers/random.ts was a stateless Math.random() < traffic,
   so a visitor's variant re-rolled every request and -- because the
   HTML is edge-cached and the cache key never carried the variant --
   the first roll got frozen and served to everyone in the device/geo
   bucket. Analytics never saw the active flag either. New
   packages/blocks/src/sdk/flags.ts (deco_segment cookie codec, shared
   by both packages/blocks and packages/tanstack -- resolve.ts records
   sticky decisions, cmsRoute.ts persists them to the cookie,
   workerEntry.ts folds the cohort into the cache key so each variant
   keeps its own cached HTML). resolve.ts's RESOLVE_DURATION metric also
   drops its path label here (bundled from the observability commit
   just before this one, since this is the same function).

Ported from main (commits 1f6da52 + 109913e). flags.ts lives in
packages/blocks (not packages/tanstack) because resolve.ts, which
records the sticky decisions, is a packages/blocks file and needs a
same-package import; workerEntry.ts and cmsRoute.ts (packages/tanstack)
consume it via the new @decocms/blocks/sdk/flags export instead of the
relative "./flags" import main used before the monorepo split.
tanstack/tsconfig.json's "include" gained "package.json" (matching
packages/blocks's own tsconfig) since outboundHeaders.ts reads its own
package version from it -- without this tsc can't resolve the JSON
import even with resolveJsonModule enabled.

Original-Authors: hugo-ccabral <hugo@deco.cx>, guitavano <tavano62@gmail.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jul 8, 2026

Copy link
Copy Markdown

🎉 This PR is included in version 7.3.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

…function'

Next.js App Router route handlers evaluate their module graph against
React's react-server build (Next's vendored rsc/react.js) and IGNORE
"use client" directives — there is no client graph to move a module
into. That build exports forwardRef/useMemo/createElement but NOT
createContext, so any module-scope createContext call reachable from a
route.ts crashes the whole route at import time:

  TypeError: {imported module .../vendored/rsc/react.js}.createContext
  is not a function

All six admin handlers (metaGET, decofileGET/POST, invokePOST,
renderGET/POST) crashed this way, each bisected individually against a
real Next 16.2.6 + Turbopack consumer (faststore-fila's /.decofile).
Two independent import paths reached a module-scope createContext:

1. blocks-admin -> @decocms/blocks/cms barrel -> sectionMixins.ts
   (detectDevice) and sdk/requestContext.ts (isMobileUA) -> useDevice.ts,
   whose back-compat re-export drags in useDeviceContext.tsx ("use
   client" + top-level createContext). Fix: split the pure UA parsing
   (Device, MOBILE_RE, TABLET_RE, isMobileUA, detectDevice) into a new
   react-free leaf sdk/detectDevice.ts; sectionMixins and requestContext
   import the leaf; useDevice.ts re-exports it so its public API is
   unchanged. New ./sdk/detectDevice subpath export.

2. @decocms/nextjs root barrel also exports the render components,
   whose graph reaches @decocms/blocks/hooks -> Picture.tsx — a
   top-level createContext with no "use client" at all (a latent bug for
   ANY react-server graph, Server Component pages included; it only
   survived because every current consumer happens to import the hooks
   barrel behind an existing client boundary). Fix: create the preload
   context lazily on first render — renders only ever happen under a
   full React build.

Even with both fixed, mixing render components and route handlers in
one barrel leaves a landmine (e.g. SectionErrorFallback's "use client"
+ class extends Component still crashes route graphs, since directives
are ignored there and react-server has no Component). So route files
now get a dedicated component-free subpath:

  export { decofileGET as GET } from "@decocms/nextjs/routeHandlers"

Verified against faststore-fila under real npm-resolved node_modules
(files copied over the installed 7.3.0 packages, no symlinks):
/.decofile 200 with the full 2.5MB decofile JSON, storefront routes
unaffected, zero createContext errors. Not a 7.3.0 regression — the
crash reproduces identically on 7.2.3; this is latent since the
package split.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jul 8, 2026

Copy link
Copy Markdown

🎉 This PR is included in version 7.3.1 🎉

The release is available on:

Your semantic-release bot 📦🚀

tlgimenes and others added 15 commits July 8, 2026 17:47
generate-schema.ts's findTsxFiles and generate-sections.ts's walkDir both
scanned every .ts/.tsx under the sections dir, so a co-located test file
(e.g. sections.test.ts) could be emitted as a bogus section block — this
happened for real in fila's meta.gen.json. Both generators now route their
directory walks through a shared isExcludedCodegenFile predicate.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…le:// paths

meta.gen.json definition keys were derived from ts-morph's absolute
file:/// paths, so the same repo produced different schema IDs (and
/live/_meta ETags) on every machine. Relativize path-derived IDs to
the site root via definitionIdForPath(); CMS-key-derived IDs were
already stable and are untouched.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… can compile

`import.meta.env?.DEV` in decofile.ts's dev-bypass check is a syntax error
when a CJS consumer (ts-jest, as in fila's Next.js jest suite) compiles this
raw-TS package — `import.meta` has no CJS representation at all, so the file
fails to parse before any test even runs. Replace it with a NODE_ENV check
(valid in both ESM and CJS) and rename isViteDev -> isDevRuntime since it's
no longer Vite-specific.

This intentionally widens the dev-bypass to `next dev` (previously always
false there, since Next never defines import.meta.env) and narrows it
slightly under vitest (NODE_ENV=test, not "development"), so
decofile.test.ts now forces NODE_ENV=development in beforeAll/afterAll to
preserve its original no-auth-bypass coverage.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
env.ts was the one signal left after 97695d5 removed import.meta from
decofile.ts. It's importable via the @decocms/blocks/sdk barrel, so any
CJS consumer (ts-jest) compiling this raw-TS package would hit an
ESM-only syntax error. Replace the import.meta.env.DEV read with a
try/catch'd process.env.NODE_ENV read (readNodeEnv()) that bundlers can
still statically define at build time, preserving the NODE_ENV and
DECO_PREVIEW fallback signals unchanged. Adds env.test.ts covering
isDevMode() across production/development/DECO_PREVIEW.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…mport map for non-Vite bundlers

createSiteSetup's `sections` option is normally satisfied by Vite's
import.meta.glob, which Next has no equivalent of. Behind an opt-in
--registry flag, generate-sections now walks the same section files it
already scans and emits `sectionImports`, a glob-style-keyed
Record<string, () => Promise<any>> that drops straight into
SiteSetupOptions.sections (and createNextSetup's sectionGlob later).
Built from every scanned file, not just ones carrying convention
exports, so files like a bare Promo.tsx are still registered. Output
without the flag is untouched, keeping Vite sites' regenerated
sections.gen.ts byte-stable in CI.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Composes createSiteSetup, applySectionConventions, loadBlocks (all
route-handler-safe) with loadDecofileDirectory for filesystem decofile
loading, and lazily imports @decocms/blocks-admin for meta/renderShell/
previewWrapper so the eager import graph never touches module-scope
client-React. Returns a memoized ensureSetup() for Task 6's dispatcher
and fila's Task 10 to call before every admin/route-handler request.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
setupPromise ??= (...) permanently cached a rejected first attempt,
so one transient failure (fs blip during loadDecofileDirectory, flaky
meta() fetch) would poison the warm serverless instance forever. Now
the memo is cleared on rejection so the next ensureSetup() call
retries, while the triggering call still rejects with the original
error.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Adds createDecoRouteHandlers() to routeHandlers.ts: a single GET/POST
dispatcher for app/deco/[[...deco]]/route.ts that routes /deco/decofile,
/deco/meta, /deco/render, /deco/invoke/*, and /deco/previews/* (rebuilt to
the /live/previews/* prefix handleRender parses) to the existing
blocks-admin handlers, running an optional setup() before each request and
404-ing unknown paths.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…oke branch

The catch-all dispatcher wired the same handler to GET and POST with no
method check on invoke/*. handleInvoke has no auth and falls back to a
?props=<json> query string for GET, so an unauthenticated <img> tag on a
third-party page could trigger mutating VTEX actions cross-site (GET is a
CORS simple request, no preflight). The per-URL invokePOST export this
dispatcher replaces was POST-only for exactly this reason — restore it,
and 405 non-GET on meta (read-only schema endpoint) for symmetry.

Also pins the previews-rebuild's `new Request(rebuilt, request)` body-
forwarding semantics with a test, and documents why a plain-object init
would throw "duplex option is required" on a future refactor. That test
needs Node's native Request (jsdom's fetch polyfill drops the body on
Request-as-init), so routeHandlers.test.ts now runs under
@vitest-environment node, matching this package's setup.test.ts precedent.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…lePackages)

Adds packages/nextjs/src/config.cjs (CJS, requireable from a CJS
next.config.js despite the package being "type": "module") exporting
withDeco() and DECO_REWRITES, plus a src/config.d.cts declaration and
a "./config" exports map entry. withDeco() prepends the three Studio
rewrites (/.decofile, /live/_meta, /live/previews/:path*) that Task 6's
dispatcher expects, ahead of any user rewrites in array or object form,
and dedupes transpilePackages with the @decocms packages.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…s/createNextSetup + README recipe

Migrates examples/nextjs-smoke off its ad-hoc rewrites/route files onto the
three new @decocms/nextjs surfaces built in Tasks 5-7 (withDeco,
createDecoRouteHandlers, createNextSetup), and adds packages/nextjs/README.md
as the copy-paste recipe a new Next.js site follows.

While validating end-to-end against a real `next build`/`next dev`, found and
fixed two real bugs surfaced by the migration (not just fixture wiring):

- routeHandlers.ts's dispatch derived the requested action solely from a
  `/deco/`-prefixed pathname, on the assumption that a next.config rewrite
  hands route handlers the destination path. Verified empirically (dev and
  `next start`) that Next.js instead hands `request.url`/`nextUrl.pathname`
  as the ORIGINAL, pre-rewrite path — so every rewritten protocol URL
  (`/.decofile`, `/live/_meta`, `/live/previews/*`) 404'd. `resolveAction`
  now accepts both forms; added regression tests hitting the rewrite-source
  paths directly (the prior tests only ever constructed already-/deco/*
  Requests, so they'd have kept passing against the broken dispatcher).
- Picture.tsx (reachable from DecoRootLayout/SectionRenderer via the
  hooks/index.ts barrel) imports createContext/useContext without
  "use client", which crashes Next's react-server module-graph check for any
  page that imports the root barrel. Added "use client", same precedent as
  SectionErrorFallback.tsx's existing fix for the same class of issue.

Also dropped a stray console.log left in blocks-admin's handleRender.

Verification: `examples/nextjs-smoke` builds clean; dev server curls of
/.decofile, /live/_meta, and / (CMS-resolved Hero content) all 200; repo-wide
`bun run typecheck && bun run test` green across all packages.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
…he block comment

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…enance path

npm 12.0.0 (published 2026-07-08T21:06Z, ~1h before our release run)
fails every 'npm publish' with "Cannot find module 'sigstore'" from
libnpmpublish's provenance.js. Two release runs died on it; the
morning's 7.3.1 release on the same runner image succeeded because
@latest still resolved to 11.18.0 then. Pin the major; revisit after
npm 12 stabilizes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jul 8, 2026

Copy link
Copy Markdown

🎉 This PR is included in version 7.4.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

tlgimenes and others added 3 commits July 8, 2026 21:01
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…tifacts live in the framework's folder)

Flips the default output path of four blocks-cli generators from
src/server/{cms,admin}/ to .deco/, so generated framework artifacts
live alongside .deco/blocks instead of scattered under app source:

  - generate-blocks   -> .deco/blocks.gen.ts (+ sibling .deco/blocks.gen.json,
                          derived from --out-file by extension swap)
  - generate-loaders  -> .deco/loaders.gen.ts
  - generate-sections -> .deco/sections.gen.ts
  - generate-schema   -> .deco/meta.gen.json

generate-invoke is the one exception and keeps its default at
src/server/invoke.gen.ts: unlike the other four (inert data/metadata
snapshots the framework loads at runtime), it emits actual app
server-function code that TanStack Start's build-time compiler
transforms (createServerFn().handler() -> RPC stubs), so it belongs
in src/ with the rest of the compiled app code. Documented inline.

Legacy guard: when no explicit --out/--out-file flag is passed AND
the OLD default file still exists on disk, each generator prints a
one-line stderr warning (lib/legacyArtifact.ts) naming both paths and
telling the caller to move the file and update its importers, then
writes to the NEW default anyway. An explicit flag skips the guard
entirely (deliberate choice, no warning). Every flipped generator
mkdir -p's the new output directory before writing.

Updated in the same commit (ships in the same lockstep release):
  - packages/tanstack/src/vite/plugin.js dev-mode watcher, which called
    generateBlocks()/generate-schema.ts relying on the old defaults
  - packages/blocks-cli/scripts/migrate.ts's scaffolding templates
    (setup.ts, commerce-loaders.ts, knip-config.ts) that imported from
    or referenced the old default paths
  - packages/nextjs README + setup.ts JSDoc: scripts no longer need
    --out/--out-file, and the recipe now imports generated artifacts
    through a `deco/*` tsconfig path alias -> .deco/* instead of a
    relative import, since src/deco/setup.ts isn't adjacent to the
    site-root .deco/ directory
  - packages/blocks-admin's "unknown handler" error message and
    packages/blocks/src/setup.ts JSDoc, which pointed at the old path

examples/nextjs-smoke and examples/tanstack-smoke construct their
setup inline and don't invoke any of these generators, so they needed
no changes.

Tests: extended generate-sections.test.ts and generate-schema.test.ts
with subprocess fixtures covering the new default path, the legacy
warning (both paths + guidance text on stderr, new default still
written), and the explicit-flag no-warning case.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
generate-blocks/loaders/sections/schema now default their output to
.deco/ instead of src/server/{cms,admin}/ (commit 36ebf2a). Update the
two migration-skill docs that hardcoded the old paths in troubleshooting
commands and the setup.ts template's generated-artifact imports.
generate-invoke's src/server/invoke.gen.ts default is unchanged and was
left as-is everywhere it's referenced.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jul 9, 2026

Copy link
Copy Markdown

🎉 This PR is included in version 7.5.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

tlgimenes and others added 2 commits July 8, 2026 21:24
The spawnSync-tsx fixture tests take ~6s under full-suite parallel load,
tripping vitest's 5s default — 2 flaky failures on whole-monorepo runs
that never reproduced in isolated package runs. test: type on purpose —
no release.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… inside Vite

Root cause of the long-standing non-fatal "[deco] blocks bootstrap
failed: TypeError: generateBlocks is not a function" cold-start warning
on lebiscuit-tanstack: tsx 4.22.0-4.22.4 has a loader-hook state bug
(fixed upstream in 4.22.5, "isolate hook state per async
module.register() registration"). Inside a Vite dev-server process,
tsImport(@decocms/blocks-cli/generate-blocks) resolves the module
correctly but returns an EMPTY namespace — no rejection, which is why
every out-of-band reproduction (plain node, same parent URL, same tsx
copy) passed while the in-process call failed. Sites whose lockfiles
froze 4.21.0 (baggagio, casaevideo, fila) never saw it; lebiscuit's
lockfile froze 4.22.0.

Verified in both directions on lebiscuit: tsx 4.21.0 -> bootstrap
succeeds; 4.22.0 -> empty namespace; 4.23.0 -> succeeds.

blocks-cli's tsx range was ^4.19.0, so any fresh install could land in
the broken window — floor it at ^4.22.5. The tanstack vite plugin's
loadGenModule also now fails with an actionable message (naming the tsx
window and the upgrade command) when it sees an empty namespace,
instead of the bare "generateBlocks is not a function" that a site's
own hoisted-broken-tsx lockfile pin would still surface.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jul 9, 2026

Copy link
Copy Markdown

🎉 This PR is included in version 7.5.1 🎉

The release is available on:

Your semantic-release bot 📦🚀

…src/

Smoke-tested moving it to .deco/ on a real site: TanStack Start's client
stub transform handles the new path fine, but the server half can't
resolve the split module back to an executable handler — every
/_serverFn call 500s (unhandled, no stack) while the storefront and
typecheck stay green. Reverted; same probe back under src/ returns 200
with a real VTEX orderForm. docs-only commit, no release.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant