{
  "statuses": [
    {
      "id": "to-build",
      "label": "To build",
      "word": "to build",
      "word_one": "to build",
      "tile": "Nothing in the proposed API yet",
      "legend": "nothing in the proposed API yet; needs a framework change"
    },
    {
      "id": "to-finish",
      "label": "To finish",
      "word": "to finish",
      "word_one": "to finish",
      "tile": "The core is there; pieces are missing",
      "legend": "the core is there; material pieces are missing or undocumented"
    },
    {
      "id": "site-code",
      "label": "Site code",
      "word": "site code",
      "word_one": "site code",
      "tile": "Left to a few lines of site code",
      "legend": "deliberately left to the site; a few lines of app code"
    },
    {
      "id": "done",
      "label": "Done",
      "word": "done",
      "word_one": "done",
      "tile": "Works as documented",
      "legend": "works as documented"
    },
    {
      "id": "goes-away",
      "label": "Goes away",
      "word": "go away",
      "word_one": "goes away",
      "tile": "Disappears with the migration",
      "legend": "disappears with the migration"
    }
  ],
  "sections": [
    {
      "id": "roadmap",
      "nav": "Overview",
      "eyebrow": "Next-major roadmap",
      "title": "Roadmap to the next major"
    },
    {
      "id": "roadmap-blockers",
      "nav": "The ten blockers",
      "eyebrow": "Release blockers",
      "title": "Ten blockers to clear before release"
    },
    {
      "id": "roadmap-studio-new",
      "nav": "On a next-major site",
      "eyebrow": "Site editor support",
      "title": "Site editor on a next-major site"
    },
    {
      "id": "roadmap-studio-legacy",
      "nav": "With legacy content",
      "eyebrow": "Site editor support",
      "title": "Site editor with legacy content"
    },
    {
      "id": "roadmap-api",
      "nav": "API additions",
      "eyebrow": "Work items",
      "title": "API additions"
    },
    {
      "id": "roadmap-cli",
      "nav": "CLI and manifest",
      "eyebrow": "Work items",
      "title": "CLI and manifest"
    },
    {
      "id": "roadmap-platform",
      "nav": "Site editor and Deco API",
      "eyebrow": "Work items",
      "title": "Site editor and Deco API"
    },
    {
      "id": "roadmap-docs",
      "nav": "Docs fixes",
      "eyebrow": "Work items",
      "title": "Docs fixes and additions"
    },
    {
      "id": "roadmap-later",
      "nav": "Follow-ups",
      "eyebrow": "After the first release",
      "title": "Planned after the first release"
    },
    {
      "id": "roadmap-storefront",
      "nav": "TanStack storefront",
      "eyebrow": "Site migrations",
      "title": "Migrate the TanStack storefront"
    },
    {
      "id": "roadmap-blog",
      "nav": "TanStack blog",
      "eyebrow": "Site migrations",
      "title": "Migrate the TanStack blog"
    },
    {
      "id": "roadmap-faststore",
      "nav": "Next.js storefront",
      "eyebrow": "Site migrations",
      "title": "Migrate the Next.js storefront"
    },
    {
      "id": "roadmap-features",
      "nav": "Every feature",
      "eyebrow": "Feature readiness",
      "title": "Every feature, and where it stands"
    }
  ],
  "overview": {
    "intro": "<p>Deco CMS is the next major version of Deco's framework, which ships today as the <code>@decocms</code> 7.x packages. The API in these docs is proposed and hasn't shipped yet; this page is the to-do list for releasing it (a release of the framework, not to be confused with content releases). To build it, we checked the proposed API feature by feature against three real sites that will move to it: a <a href=\"#roadmap-storefront\">TanStack storefront</a> on the current packages, a <a href=\"#roadmap-blog\">TanStack blog</a> on an older package, and a <a href=\"#roadmap-faststore\">Next.js storefront</a> on another CMS.</p>\n<p>No site can move using only what these docs describe. Each first needs something missing: site editor support, a way for blocks to read the current request, or SDK functions beyond the CMS. The <a href=\"#roadmap-blockers\">ten release blockers</a> gate the release; the status of every feature the three sites use is under <a href=\"#roadmap--release-readiness\">Release readiness</a>.</p>",
    "readiness_lead": "None of the 111 features those sites use is done yet: 11 are still to build, 66 are to finish, 28 are left to site code, and 6 go away with the migration.",
    "fix_docs": "<a class=\"rm-do-t\" href=\"#roadmap-docs\">Fix the docs</a> A few statements in these docs are still loose or wrong. The fixes are the first two docs work items: {{item:roadmap-docs--resolve-contradictions-in-these-docs}} and {{item:roadmap-docs--correct-studio-compatibility}}."
  },
  "blockers": [
    {
      "id": "roadmap-blockers--let-studio-talk-to-a-next-major-site",
      "title": "Let the site editor talk to a next-major site",
      "today": "The site editor reads the schema and content from the running site (<code>/live/_meta</code>, <code>/.decofile</code>) and calls it for previews, <code>/deco/invoke</code> and the secrets encrypt action. A next-major site serves none of these.",
      "plan": "The site editor talks to stored content through the content protocol instead, which needs only the committed schema and <code>.deco/blocks</code>: the site editor's GitHub backend in production, <code>deco serve</code> on a developer's machine. Features that need the site's code degrade in this version (see <a href=\"#site-editor--what-works-without-your-code\">What works without your code</a>).",
      "features": [
        "admin-protocol-endpoints",
        "invoke-and-actions",
        "section-catalog",
        "global-layout-sections",
        "dev-studio-content-loop",
        "site-bootstrap"
      ],
      "delivered_by": [
        "roadmap-api--publish-the-content-protocol-and-its-package",
        "roadmap-platform--build-studio-s-github-content-backend",
        "roadmap-platform--move-studio-s-editor-onto-the-protocol-client",
        "roadmap-cli--ship-deco-serve-a-local-server-for-studio",
        "roadmap-docs--correct-studio-compatibility",
        "roadmap-platform--synchronize-editor-drafts"
      ]
    },
    {
      "id": "roadmap-blockers--make-the-cli-write-a-schema-studio-can-read",
      "title": "Make the CLI write a schema the site editor can read",
      "today": "The CLI writes <code>.deco/schema.gen.json</code> with top-level <code>definitions</code>; the site editor reads <code>.deco/meta.gen.json</code> in LiveMeta shape (btoa keys, <code>schema.root</code>). There are no <code>apps</code> or <code>actions</code> groups, no per-type variant definitions, and the widget vocabulary is undocumented, so 22 image pickers would become text inputs and HTML fields plain strings.",
      "plan": "Emit the site editor's exact format as <code>.deco/schema.gen.json</code>, one multivariate definition per field type a variant can fill, write static option lists into the schema, publish the JSDoc/@format table, and keep widget-alias detection and return-type loader unions.",
      "features": [
        "studio-schema-generation",
        "codegen-pipeline",
        "ci-and-release-workflows",
        "editor-widgets-and-image-fields",
        "rich-text-html-props",
        "app-domain-types",
        "inline-loader-props"
      ],
      "delivered_by": [
        "roadmap-cli--write-a-studio-compatible-schema-file",
        "roadmap-cli--publish-the-tag-and-widget-vocabulary",
        "roadmap-cli--define-the-loader-picker-rule",
        "roadmap-cli--write-static-option-lists-into-the-schema"
      ]
    },
    {
      "id": "roadmap-blockers--give-blocks-the-page-url-and-route-params",
      "title": "Give blocks the page URL and route params",
      "today": "On SPA navigation <code>getRequest()</code> returns the <code>/_serverFn</code> URL, Next's <code>headers()</code> has no URL, app packages' loaders have no standard way to receive the URL or <code>match.params</code>, and apps-vtex's RequestContext is never populated.",
      "plan": "Add one request scope (<code>url</code>, <code>params</code>, <code>request</code>, <code>client</code>, response headers) that the templates' openPage populates and that block functions and site code can read, passing what a client needs as arguments.",
      "features": [
        "section-loaders",
        "plp-filters-sort-pagination",
        "request-context-cookies",
        "commerce-platform-binding",
        "code-composed-pdp"
      ],
      "delivered_by": [
        "roadmap-api--add-a-request-scope"
      ]
    },
    {
      "id": "roadmap-blockers--make-draft-preview-work-inside-studio-s-iframe",
      "title": "Make draft preview work inside the site editor's iframe",
      "today": "The SameSite=Lax cookie is rejected cross-site, the TanStack guide never sets it, and <code>?__draft=off</code> is ignored. There's no no-store rule, and no StudioBridge or <code>data-manifest-key</code>, so click-to-select is lost.",
      "plan": "Give the templates <code>withDeco()</code>/<code>decoProxy()</code> recipes, which set the cookie as <code>Secure; SameSite=None; Partitioned</code>, treat <code>?__draft=off</code> as exit and mark drafts private/no-store. Add <code>&lt;StudioBridge/&gt;</code>, and have the templates wrap each rendered block in the site editor's <code>section[data-manifest-key]</code> marker.",
      "features": [
        "studio-preview",
        "worker-server-entry",
        "root-document-layout",
        "router-and-client-navigation"
      ],
      "delivered_by": [
        "roadmap-api--ship-the-withdeco-and-decoproxy-entry-wrappers",
        "roadmap-api--add-a-studio-bridge"
      ]
    },
    {
      "id": "roadmap-blockers--define-the-non-cms-sdk-surface",
      "title": "Define the non-CMS SDK surface",
      "today": "apps-* and the ClickHouse telemetry depend on <code>sdk/requestContext</code>, <code>instrumentedFetch</code>, <code>fetchCache</code> and <code>cachedLoader</code>, and sites' widgets on <code>sdk/useDevice</code> and the Image and JsonLd hooks. These docs only list 'loaders, client, resolution, router'.",
      "plan": "The framework doesn't fetch data: the apps become thin, instrumented API clients over the framework's instrumented fetch, the core drops <code>/deco/invoke</code> and <code>cachedLoader</code>, and caching moves to template recipes. Ship <code>@decocms/blocks/image</code>.",
      "features": [
        "framework-import-surface",
        "in-isolate-caches",
        "upstream-fetch-instrumentation",
        "image-optimization",
        "json-ld-structured-data",
        "interactive-ui-widgets"
      ],
      "delivered_by": [
        "roadmap-api--turn-the-apps-into-thin-instrumented-clients",
        "roadmap-api--remove-deco-invoke-and-cachedloader",
        "roadmap-api--cache-upstream-requests-in-the-bindings",
        "roadmap-api--add-an-image-module"
      ]
    },
    {
      "id": "roadmap-blockers--add-a-revision-handle-an-edge-cache-kit-and-an-isr-recipe",
      "title": "Add a revision handle, an edge cache recipe and an ISR recipe",
      "today": "Clients expose no revision and there's no <code>forRevision</code>. Degraded pages get cached. The planned check for a new release runs once a minute, at an idle moment (inside <code>ctx.waitUntil</code> on Workers), so propagation depends on the check interval and manifest cache lifetime; a cold isolate serves the content module it was deployed with until its first check.",
      "plan": "Add a revision handle, a way to read and pin the revision a client serves (<code>client.revision()</code>, <code>cms.forRevision()</code>, <code>onUpdate</code>), and <code>markDegraded()</code>. Add an edge cache recipe for Workers templates, <code>withEdgeCache()</code>, for caching rendered pages at the edge, and an ISR (Incremental Static Regeneration: re-rendering cached pages in the background) recipe for Next.js.",
      "features": [
        "edge-html-cache-profiles",
        "fast-deploy-kv",
        "lazy-deferred-sections",
        "otel-observability"
      ],
      "delivered_by": [
        "roadmap-api--add-a-revision-handle",
        "roadmap-api--add-an-edge-cache-kit",
        "roadmap-api--add-remoteloader-options",
        "roadmap-docs--fix-the-next-guide"
      ]
    },
    {
      "id": "roadmap-blockers--stop-resolving-everything-eagerly",
      "title": "Stop resolving everything eagerly",
      "today": "The one registry rule resolves inputs bottom-up. Lazy defers nothing (43 wrappers on the storefront), hidden variants still run their loaders, and <code>Resolved&lt;T&gt;</code> can't be expressed.",
      "plan": "Add the built-in <code>lazy</code> block, so multivariate runs only the chosen variant. The alias bridge unwraps the legacy Lazy/SingleDeferred/Deferred block wrappers: the wrapped block renders normally, with no client-side deferral.",
      "features": [
        "lazy-deferred-sections",
        "matchers-and-variants",
        "site-search-and-autocomplete"
      ],
      "delivered_by": [
        "roadmap-api--add-the-lazy-block",
        "roadmap-cli--widen-the-legacy-alias-bridge"
      ]
    },
    {
      "id": "roadmap-blockers--make-the-next-major-load-and-resolve-legacy-content",
      "title": "Make the next major load and resolve legacy content",
      "today": "Filename decoding is unspecified, and the alias bridge covers only pages, matchers and multivariate, while the site editor writes Lazy, SeoV2, <code>site/apps/site.ts</code>, redirect, secret and the <code>multi</code> matcher. UNKNOWN_BLOCK now fails the parent block.",
      "plan": "Use the protocol's one filename rule (decode exactly once), widen the alias bridge, ship <code>deco content</code> (with <code>deco schema</code> reporting collisions), and have the CLI emit a runtime alias table.",
      "features": [
        "block-type-discriminator",
        "orphan-and-dangling-blocks",
        "content-schema-drift",
        "content-storage-and-delivery"
      ],
      "delivered_by": [
        "roadmap-cli--specify-the-filename-rule-and-ship-a-content-bundle",
        "roadmap-cli--widen-the-legacy-alias-bridge",
        "roadmap-cli--add-validation-commands-to-the-cli",
        "roadmap-cli--emit-a-runtime-alias-table"
      ]
    },
    {
      "id": "roadmap-blockers--make-routing-accept-the-content-sites-already-store",
      "title": "Make routing accept the content sites already store",
      "today": "There's no splat, so <code>/*</code> PLPs never match. <code>matchRoute</code> throws per request on an ambiguity a single site editor commit can introduce. <code>Page.seo</code> is required but content stores <code>null</code>, and with no 404 gate unknown URLs return soft 200s.",
      "plan": "Add a trailing splat and make <code>matchRoute</code> non-throwing, with a deterministic tie-break. A hosted publish isn't validated: the tie-break keeps the site up and <code>deco check</code> catches ambiguous routes in CI. Make seo optional and add a critical-block notFound/redirect sentinel.",
      "features": [
        "cms-page-routing",
        "not-found-and-error-pages",
        "redirects",
        "page-template-targeting"
      ],
      "delivered_by": [
        "roadmap-api--add-a-splat-to-matchroute-and-stop-request-time-throws",
        "roadmap-api--add-a-not-found-and-redirect-gate-before-streaming",
        "roadmap-platform--build-the-deco-api-release-service",
        "roadmap-docs--resolve-contradictions-in-these-docs"
      ]
    },
    {
      "id": "roadmap-blockers--define-a-model-for-apps-config-and-secrets",
      "title": "Define a model for apps, config and secrets",
      "today": "App loaders get no config, there's no <code>apps</code> manifest group, <code>website/loaders/secret.ts</code> can't decrypt, and the site editor encrypts Secret fields through an action the site serves.",
      "plan": "Define the client contract (a factory that takes its config from site code), and vendored loaders keep their old type names as aliases. No apps group in this version. Ship a built-in <code>secret</code> block: content holds only ciphertext, encrypted with a public key committed in <code>.deco/</code> and decrypted on the server, and the site editor gets a write-only <code>Secret</code> field.",
      "features": [
        "app-installation-and-store-config",
        "env-vars-and-secrets",
        "commerce-platform-binding"
      ],
      "delivered_by": [
        "roadmap-api--define-an-app-contract",
        "roadmap-api--ship-decocms-blocks-secrets",
        "roadmap-platform--encrypt-secret-fields-in-the-site-editor",
        "roadmap-cli--write-a-studio-compatible-schema-file"
      ]
    }
  ],
  "studio_new": [
    {
      "id": "roadmap-studio-new--give-studio-a-schema-it-can-read-so-the-editor-loads",
      "title": "Give the site editor a schema it can read, so the editor loads",
      "today": "The site editor reads <code>.deco/meta.gen.json</code> or <code>/live/_meta</code> in LiveMeta shape, so a next-major site shows 'live meta unavailable'. On protocol projects it reads the committed <code>.deco/schema.gen.json</code>, which must be in that shape.",
      "features": [
        "studio-schema-generation",
        "ci-and-release-workflows",
        "codegen-pipeline"
      ],
      "delivered_by": [
        "roadmap-cli--write-a-studio-compatible-schema-file",
        "roadmap-platform--build-studio-s-github-content-backend"
      ]
    },
    {
      "id": "roadmap-studio-new--keep-gallery-theme-and-in-place-previews-working",
      "title": "Replace gallery, theme and in-place previews",
      "today": "The site editor's section thumbnails (its legacy name for block previews), global and theme previews, and Fast Preview in-place render all go through <code>/live/previews</code>, which runs site code. The protocol never does: these become name cards, or open the real page.",
      "features": [
        "section-catalog",
        "theming-and-styling",
        "global-layout-sections"
      ],
      "delivered_by": [
        "roadmap-platform--move-studio-s-editor-onto-the-protocol-client",
        "roadmap-platform--fix-the-add-section-gate",
        "roadmap-platform--align-the-preview-protocol"
      ]
    },
    {
      "id": "roadmap-studio-new--let-editors-save-secret-fields-on-next-major-sites",
      "title": "Let editors set Secret fields on next-major sites",
      "today": "The site editor encrypts Secret fields through an encrypt action the site serves, with a symmetric key (DECO_CRYPTO_KEY). Protocol projects have no such action, so nothing can encrypt a new secret.",
      "features": [
        "env-vars-and-secrets",
        "app-installation-and-store-config",
        "invoke-and-actions"
      ],
      "delivered_by": [
        "roadmap-api--ship-decocms-blocks-secrets",
        "roadmap-platform--encrypt-secret-fields-in-the-site-editor",
        "roadmap-platform--move-studio-s-editor-onto-the-protocol-client"
      ]
    },
    {
      "id": "roadmap-studio-new--keep-options-run-and-pickers-working",
      "title": "Fall back for <code>@options</code>, Run and pickers",
      "today": "These need <code>/deco/invoke</code> and <code>/live/invoke</code>, which the protocol replaces. The blog ProductShelf picker even calls production's <code>/deco/invoke</code> with legacy VTEX keys. Pickers fall back to the schema's options, else free text; Run is hidden.",
      "features": [
        "invoke-and-actions",
        "app-installation-and-store-config",
        "icon-by-name-enums"
      ],
      "delivered_by": [
        "roadmap-cli--write-static-option-lists-into-the-schema",
        "roadmap-platform--move-studio-s-editor-onto-the-protocol-client"
      ]
    },
    {
      "id": "roadmap-studio-new--restore-click-to-select",
      "title": "Restore click-to-select",
      "today": "There's no <code>editor::inject</code> listener, and the guides' renderers emit no site editor <code>section[data-manifest-key]</code> marker.",
      "features": [
        "root-document-layout",
        "interactive-ui-widgets"
      ],
      "delivered_by": [
        "roadmap-api--add-a-studio-bridge"
      ]
    },
    {
      "id": "roadmap-studio-new--keep-the-draft-past-the-first-in-frame-click",
      "title": "Keep the draft past the first in-frame click",
      "today": "The Lax cookie isn't stored cross-site and the TanStack guide never sets it. <code>?__draft=off</code> can leave a stale draft behind.",
      "features": [
        "studio-preview",
        "worker-server-entry"
      ],
      "delivered_by": [
        "roadmap-api--ship-the-withdeco-and-decoproxy-entry-wrappers",
        "roadmap-docs--fix-the-tanstack-guides",
        "roadmap-platform--align-the-preview-protocol"
      ]
    },
    {
      "id": "roadmap-studio-new--give-field-level-variants-a-schema-contract",
      "title": "Give field-level variants a schema contract",
      "today": "The site editor's field variant UI needs a single-option block-ref whose definition has <code>variants</code>, and takes the inner field from <code>variants.items.properties.value</code>, else a plain string (schema-form.tsx:76-87, 254-280). The proposed API's one generic <code>multivariate&lt;T&gt;</code> gives the CLI no concrete T, so it has to take T from the field the variant fills. Legacy schemas had one flag per kind, e.g. <code>website/flags/multivariate/image.ts</code>.",
      "features": [
        "matchers-and-variants",
        "editor-widgets-and-image-fields",
        "studio-schema-generation"
      ],
      "delivered_by": [
        "roadmap-cli--write-a-studio-compatible-schema-file",
        "roadmap-docs--fill-in-the-schema-reference-details"
      ]
    },
    {
      "id": "roadmap-studio-new--keep-variant-tabs-and-the-device-toggle-working",
      "title": "Keep variant tabs and the device toggle working",
      "today": "<code>x-deco-matchers-override</code> and <code>?deviceHint</code> have no reader in the built-in multivariate.",
      "features": [
        "device-detection-and-targeting",
        "matchers-and-variants"
      ],
      "delivered_by": [
        "roadmap-api--add-a-request-scope",
        "roadmap-platform--align-the-preview-protocol"
      ]
    },
    {
      "id": "roadmap-studio-new--stop-studio-reading-short-type-names-as-saved-entries",
      "title": "Stop the site editor reading short type names as saved entries",
      "today": "The site editor classifies keys by file extension, so a site's UI blocks must keep legacy <code>site/sections/X.tsx</code> keys until it does a manifest lookup.",
      "features": [
        "block-type-discriminator",
        "section-registration-conventions"
      ],
      "delivered_by": [
        "roadmap-platform--use-manifest-lookup-instead-of-path-heuristics",
        "roadmap-cli--emit-a-runtime-alias-table"
      ]
    },
    {
      "id": "roadmap-studio-new--prevent-entry-names-from-colliding-with-types",
      "title": "Prevent entry names from colliding with types",
      "today": "The site editor's only write guard rejects keys with a source-file extension (block-key.ts:48-50). An editor can save an entry named <code>hero</code>, <code>seo</code> or <code>page</code>; under the proposed API's <code>{ ...savedEntries, ...blocks }</code> the function wins and the entry is silently ignored. Only <code>deco schema</code> in CI catches it, after the commit is on the production branch.",
      "features": [
        "block-type-discriminator",
        "orphan-and-dangling-blocks"
      ],
      "delivered_by": [
        "roadmap-platform--use-manifest-lookup-instead-of-path-heuristics"
      ]
    },
    {
      "id": "roadmap-studio-new--let-studio-see-and-edit-flat-redirect-entries",
      "title": "Let the site editor see and edit flat redirect entries",
      "today": "The Redirects collection lists only blocks typed exactly <code>website/loaders/redirect.ts</code> and writes <code>{ redirect: { from, to, type, discardQueryParameters } }</code>, with temporary = 307 (redirect-data.ts:14,29-32,77). Flat <code>redirect</code> entries written by developers or agents never appear there and can't be edited.",
      "features": [
        "redirects"
      ],
      "delivered_by": [
        "roadmap-platform--read-manifest-groups-for-pages-redirects-content-and-apps",
        "roadmap-api--add-a-splat-to-matchroute-and-stop-request-time-throws",
        "roadmap-cli--widen-the-legacy-alias-bridge"
      ]
    },
    {
      "id": "roadmap-studio-new--fix-local-and-tunnel-modes-show-local-edits-in-dev",
      "title": "Edit local files from the site editor; show local edits in dev",
      "today": "The dev server serves neither endpoint the site editor's local and tunnel modes use. With a site and token in <code>.dev.vars</code>, <code>remoteLoader</code> swaps in the production release over local edits.",
      "features": [
        "dev-studio-content-loop",
        "bundler-config"
      ],
      "delivered_by": [
        "roadmap-cli--ship-deco-serve-a-local-server-for-studio",
        "roadmap-api--add-remoteloader-options"
      ]
    },
    {
      "id": "roadmap-studio-new--speed-up-publishes-and-make-failures-visible",
      "title": "Speed up publishes and make failures visible",
      "today": "The planned once-a-minute check delays propagation, and nothing tells the editor which release is prepared, promoted or served. A failed <code>forDraft</code> silently shows published content.",
      "features": [
        "fast-deploy-kv",
        "otel-observability"
      ],
      "delivered_by": [
        "roadmap-platform--add-a-content-live-signal",
        "roadmap-api--add-remoteloader-options"
      ]
    },
    {
      "id": "roadmap-studio-new--keep-experiments-working-on-next-major-sites",
      "title": "Keep experiments working on next-major sites",
      "today": "Results are keyed on the random matcher's saved-entry name, which a pure block function never sees.",
      "features": [
        "analytics-trackers"
      ],
      "delivered_by": [
        "roadmap-platform--drop-legacy-names-from-the-blog-tab-and-experiments",
        "roadmap-api--add-an-analytics-bootstrap-and-experiment-support",
        "roadmap-cli--widen-the-legacy-alias-bridge"
      ]
    }
  ],
  "studio_legacy": [
    {
      "id": "roadmap-studio-legacy--handle-the-legacy-types-studio-keeps-writing",
      "title": "Handle the legacy types the site editor keeps writing",
      "today": "<code>website/pages/Page.tsx</code>, SeoV2 on every new page, <code>Rendering/Lazy.tsx</code> (the ⚡ toggle), <code>flags/multivariate/section.ts</code> (hide), nested <code>website/loaders/redirect.ts</code>, <code>website/loaders/secret.ts</code>, <code>site/apps/site.ts</code>, the <code>multi</code> matcher, Theme. The bridge only aliases pages, matchers and multivariate, so these site editor actions produce UNKNOWN_BLOCK unless each site registers them.",
      "features": [
        "lazy-deferred-sections",
        "page-seo-blocks",
        "site-config-block",
        "redirects",
        "theming-and-styling",
        "matchers-and-variants"
      ],
      "delivered_by": [
        "roadmap-cli--widen-the-legacy-alias-bridge"
      ]
    },
    {
      "id": "roadmap-studio-legacy--stop-hidden-sections-from-running",
      "title": "Stop hidden blocks from running",
      "today": "'Every variant resolves': a block hidden with a never-rule still runs its loaders, and its <code>undefined</code> renders 'temporarily unavailable'.",
      "features": [
        "matchers-and-variants"
      ],
      "delivered_by": [
        "roadmap-api--add-the-lazy-block",
        "roadmap-docs--resolve-contradictions-in-these-docs"
      ]
    },
    {
      "id": "roadmap-studio-legacy--drop-hidden-array-items-instead-of-leaving-holes",
      "title": "Drop hidden array items instead of leaving holes",
      "today": "The site editor hides a banner or link by wrapping it in <code>website/flags/multivariate.ts</code> with a <code>never</code> rule (array-item-hidden.ts:35-40). Today's resolver drops <code>undefined</code> array items (resolve.ts:849-860). The proposed API's rule and <code>resolve(page.sections)</code> ('An array of results, one per block in the list') keep them, so components receive <code>undefined</code> entries.",
      "features": [
        "matchers-and-variants"
      ],
      "delivered_by": [
        "roadmap-docs--resolve-contradictions-in-these-docs"
      ]
    },
    {
      "id": "roadmap-studio-legacy--fix-the-default-variant-rule-that-makes-other-variants-unreachable",
      "title": "Flag variants that an <code>always</code> rule makes unreachable",
      "today": "The site editor seeds every new variant with an <code>always</code> rule (section-types.ts:26; a new field variant starts as two) and keeps the first <code>always</code>, else the last variant (section-variants.ts:158-168). These docs say 'put the always() variant first as the default' and 'first match wins', which makes every other variant unreachable; their own HomeHero puts <code>always</code> last.",
      "features": [
        "matchers-and-variants"
      ],
      "delivered_by": [
        "roadmap-cli--add-validation-commands-to-the-cli",
        "roadmap-docs--resolve-contradictions-in-these-docs"
      ]
    },
    {
      "id": "roadmap-studio-legacy--decode-filenames-exactly-once",
      "title": "Decode filenames exactly once",
      "today": "The site editor writes <code>encodeURIComponent(key)</code>. Real stems include <code>pages-Category%2520Page-*</code> and <code>collections%2Fblog%2F*</code>.",
      "features": [
        "block-type-discriminator",
        "content-storage-and-delivery"
      ],
      "delivered_by": [
        "roadmap-cli--specify-the-filename-rule-and-ship-a-content-bundle",
        "roadmap-api--publish-the-content-protocol-and-its-package"
      ]
    },
    {
      "id": "roadmap-studio-legacy--handle-short-form-pages-in-the-page-list-and-seo",
      "title": "Handle short-form pages in the page list and SEO",
      "today": "<code>page</code> entries are missing from the page list and can be mistaken for the site-SEO owner.",
      "features": [
        "cms-page-routing",
        "site-seo-defaults"
      ],
      "delivered_by": []
    },
    {
      "id": "roadmap-studio-legacy--stop-keying-the-blog-tab-on-legacy-names",
      "title": "Stop keying the Blog tab on legacy names",
      "today": "It looks for <code>blog/loaders/*.ts</code> and <code>@decocms/apps-blog</code> ≥ 7.53.0. The TanStack blog reads as unsupported-runtime today.",
      "features": [
        "key-prefix-content-collections",
        "studio-workspace-spaces"
      ],
      "delivered_by": [
        "roadmap-platform--drop-legacy-names-from-the-blog-tab-and-experiments"
      ]
    },
    {
      "id": "roadmap-studio-legacy--upgrade-both-tanstack-sites-so-studio-can-frame-them",
      "title": "Upgrade both TanStack sites so the site editor can frame them",
      "today": "Both TanStack sites send <code>X-Frame-Options: SAMEORIGIN</code> and lack <code>?__draft</code>, so both plans start with an upgrade to the latest 7.x, past #390 (7.20.10) and #442 (7.34.0).",
      "features": [
        "csp-security-headers",
        "worker-server-entry"
      ],
      "delivered_by": [
        "roadmap-storefront--upgrade-7-20-7-to-the-latest-7-x",
        "roadmap-blog--upgrade-6-12-to-the-latest-7-x-first"
      ]
    },
    {
      "id": "roadmap-studio-legacy--make-content-drift-visible-to-editors",
      "title": "Catch content drift in CI",
      "today": "Fields missing from the schema are hidden in the editor and dropped on save, and that stays as it is. Defaults apply only on save. The storefront's Header children are typed <code>string[]</code> today.",
      "features": [
        "content-schema-drift",
        "cms-navigation-menus"
      ],
      "delivered_by": [
        "roadmap-cli--add-validation-commands-to-the-cli"
      ]
    }
  ],
  "work_items": [
    {
      "id": "roadmap-api--add-a-request-scope",
      "group": "api",
      "title": "Add a request scope",
      "plan": "<p>One scope in <code>@decocms/blocks</code>, behind conditional exports: <code>requestScope()</code> returns <code>{ url, params, request, client, device, forcedRules }</code>, plus <code>appendResponseHeader()</code>. The templates' openPage populates it with the real page href (port <code>derivePageUrl</code>), <code>match.params</code> and the per-request client. It replaces <code>sdk/requestContext</code>, the page-scope helpers and <code>currentClient()</code>, so apps import one framework-neutral port and content-reading loaders stay revision-pinned. <code>device</code> honors <code>?deviceHint</code> and <code>forcedRules</code> exposes <code>x-deco-matchers-override</code>, both on drafts only.</p>",
      "features": [
        "section-loaders",
        "inline-loader-props",
        "request-context-cookies",
        "route-params-request-to-param",
        "code-composed-pdp",
        "plp-filters-sort-pagination",
        "home-route-override",
        "commerce-platform-binding",
        "section-registration-conventions",
        "saved-loader-blocks",
        "session-regionalization",
        "cart-and-minicart",
        "shipping-simulation",
        "otel-observability",
        "key-prefix-content-collections",
        "denormalized-collection-references",
        "page-seo-blocks",
        "in-isolate-caches",
        "site-search-and-autocomplete",
        "device-detection-and-targeting",
        "matchers-and-variants",
        "studio-preview"
      ],
      "docs": [],
      "pinned": false
    },
    {
      "id": "roadmap-api--publish-the-content-protocol-and-its-package",
      "group": "api",
      "title": "Publish the content protocol in <code>@decocms/blocks</code>",
      "plan": "<p>Write down the protocol between the site editor and stored content: JSON-RPC 2.0 over one HTTP endpoint (<code>POST /rpc</code>, batches allowed) with four methods, <code>describe</code>, <code>schema.get</code>, <code>blocks.list</code> and <code>blocks.apply</code>, plus its errors, limits and conditional reads for polling. Ship it as a subpath of <code>@decocms/blocks</code> (<code>@decocms/blocks/protocol</code>), not a separate package. It holds the types and method definitions, a client, a server core over a <code>ContentStorage</code> interface, a filesystem storage and a black-box conformance suite any implementation runs over HTTP. Its key module holds the one filename rule (decode exactly once) that the site editor, <code>deco serve</code> and <code>deco content</code> all import. See <a href=\"#studio-compatibility\">Site editor compatibility</a>.</p><p>Add advertised durable idempotency receipts and optional schema preconditions, bound uncompressed reads and batch responses, and recheck guards after synchronization and storage retries. Preserve atomic apply semantics through autosave coalescing. Extend conformance for lost responses, restart recovery, racing writes, oversized payloads and tenant isolation. Hosted draft lifecycle, publishing and rollback stay outside the four-method protocol.</p><p>Use the <a href=\"#studio-implementation\">Studio implementation handoff</a> for module ownership, durable state and APIs, operation sequencing, proposed defaults and acceptance scenarios; completion includes the documented failure and concurrency cases.</p>",
      "features": [
        "admin-protocol-endpoints",
        "dev-studio-content-loop",
        "block-type-discriminator",
        "content-storage-and-delivery",
        "studio-schema-generation",
        "section-catalog",
        "site-bootstrap"
      ],
      "docs": [
        "studio-compatibility",
        "content-protocol",
        "studio-implementation"
      ]
    },
    {
      "id": "roadmap-api--let-studio-follow-the-deployed-schema",
      "group": "api",
      "title": "Read the schema from where the site editor points",
      "plan": "<p>The site editor builds its forms from the schema of the site it targets. Targeting localhost, it reads the schema from <code>deco serve</code>, so new block types show up as soon as <code>deco schema</code> writes them. Targeting a draft on any other server, it reads the committed <code>.deco/schema.gen.json</code>. There's no option to follow the production deployment's schema: <code>deco check</code> in <code>prebuild</code> already stops content that uses undeployed types from shipping. Update <a href=\"#schema\">Schema generation</a> once it ships.</p>",
      "features": [
        "admin-protocol-endpoints",
        "content-schema-drift",
        "studio-schema-generation"
      ],
      "docs": [
        "schema",
        "studio-compatibility"
      ],
      "pinned": false
    },
    {
      "id": "roadmap-api--add-the-lazy-block",
      "group": "api",
      "title": "Add the <code>lazy</code> block and <code>Lazy&lt;T&gt;</code>",
      "plan": "<p>The resolver's one special case: <code>{ \"__resolveType\": \"lazy\", \"value\": … }</code> resolves to <code>() =&gt; Promise&lt;T&gt;</code>, which resolves <code>value</code> on the first call and returns the same result after that. Export <code>type Lazy&lt;T&gt; = () =&gt; Promise&lt;T&gt;</code>. The built-in <code>multivariate</code> takes <code>{ rule: boolean; value: Lazy&lt;T&gt; }[]</code> and calls only the first value whose rule is true. <code>deco schema</code> turns a <code>Lazy&lt;T&gt;</code> field into a lazy block whose value is <code>T</code>; the site editor shows the <code>T</code> form and writes the wrapper; <code>deco check</code> flags a <code>Lazy&lt;T&gt;</code> field without a lazy block and a lazy block in a field that isn't <code>Lazy&lt;T&gt;</code>. The alias bridge wraps the values of legacy <code>website/flags/multivariate.ts</code> content.</p>",
      "features": [
        "matchers-and-variants",
        "lazy-deferred-sections",
        "studio-schema-generation"
      ],
      "docs": [
        "blocks",
        "matchers-and-variants",
        "schema"
      ],
      "pinned": false
    },
    {
      "id": "roadmap-api--ship-the-withdeco-and-decoproxy-entry-wrappers",
      "group": "api",
      "title": "Add <code>withDeco()</code> and <code>decoProxy()</code> entry wrappers to the templates",
      "plan": "<p>Template code, not a package: <code>withDeco(handler, { cms })</code> in the TanStack templates and <code>decoProxy()</code> in the Next templates own the per-request plumbing. They set the draft cookie as <code>Secure; SameSite=None; Partitioned; HttpOnly</code> and treat <code>__draft=off</code> as exit. Drafted responses are private/no-store/noindex, and security headers carry frame-ancestors from an exported <code>STUDIO_FRAME_ANCESTORS</code>, never X-Frame-Options. Site editor routes are optional. The CMS keys draft caches on the API ETag, bounded by an LRU.</p>",
      "features": [
        "worker-server-entry",
        "csp-security-headers",
        "env-vars-and-secrets",
        "hosting-and-deploy-config",
        "fast-deploy-kv",
        "studio-preview",
        "router-and-client-navigation",
        "plp-filters-sort-pagination",
        "edge-html-cache-profiles",
        "in-isolate-caches",
        "bundler-config"
      ],
      "docs": [],
      "pinned": false
    },
    {
      "id": "roadmap-api--add-a-revision-handle",
      "group": "api",
      "title": "Add a revision handle",
      "plan": "<p><code>client.revision()</code>, <code>cms.forRevision(rev)</code> / <code>forRelease({ revision })</code>, <code>CMS.onUpdate(cb)</code> and <code>update({ force })</code>.</p>",
      "features": [
        "edge-html-cache-profiles",
        "in-isolate-caches",
        "lazy-deferred-sections",
        "tabbed-shelf-partial",
        "plp-filters-sort-pagination",
        "section-registration-conventions",
        "otel-observability",
        "app-installation-and-store-config"
      ],
      "docs": [],
      "pinned": false
    },
    {
      "id": "roadmap-api--turn-the-apps-into-thin-instrumented-clients",
      "group": "api",
      "title": "Turn the apps into thin, instrumented clients",
      "plan": "<p>The framework doesn't fetch data. Each companion app (VTEX, Shopify, Wake, Magento, Algolia, Resend, …) becomes a thin API client for its platform: typed request functions over the framework's instrumented fetch, keeping the platform's API and generated types. Latency is measured to the response headers, retries count as one data point with a <code>retries</code> attribute, and error logs are structured and never contain upstream bodies, tokens or cookies. The VTEX client keeps retry and circuit breaking on by default. Converters to commerce types, the cart, user and wishlist hooks, cart, session and auth flows, and website features beyond the built-in matchers and multivariate (SEO helpers, sitemaps, redirect logic, analytics scripts) move to platform templates and site code. <a href=\"#upstream-clients\">Upstream API clients</a> has the recipe for writing a client.</p>",
      "features": [
        "framework-import-surface",
        "upstream-fetch-instrumentation",
        "commerce-platform-binding",
        "cart-customer-server-functions",
        "client-commerce-state-hooks",
        "vtex-io-app-settings",
        "bff-custom-endpoints",
        "web-fonts"
      ],
      "docs": [
        "upstream-clients"
      ]
    },
    {
      "id": "roadmap-api--remove-deco-invoke-and-cachedloader",
      "group": "api",
      "title": "Remove <code>/deco/invoke</code> and <code>cachedLoader</code>",
      "plan": "<p>The core drops the <code>/deco/invoke</code> endpoint and <code>cachedLoader</code>. Per-shopper reads and actions become the framework's own server functions or route handlers, which call the clients directly. A block function may still fetch data when a site wants it to; it calls a client like any other code.</p>",
      "features": [
        "invoke-and-actions",
        "in-isolate-caches",
        "site-local-loaders",
        "saved-loader-blocks"
      ],
      "docs": [
        "upstream-clients",
        "api-reference"
      ]
    },
    {
      "id": "roadmap-api--cache-upstream-requests-in-the-bindings",
      "group": "api",
      "title": "Document upstream caching recipes",
      "plan": "<p>Caching depends on the platform, so it lives in the templates, not in a package or the clients: a short recipe per platform, a fetch over the Cloudflare Cache API on Workers and over Next's data cache on Next.js, passed as a client's <code>fetch</code> option. The instrumented fetch reports whether each response came from the cache (the <code>cached</code> label), and drafts bypass the cache.</p>",
      "features": [
        "in-isolate-caches",
        "upstream-fetch-instrumentation",
        "edge-html-cache-profiles"
      ],
      "docs": [
        "upstream-clients",
        "caching"
      ]
    },
    {
      "id": "roadmap-api--add-remoteloader-options",
      "group": "api",
      "title": "Add <code>remoteLoader</code> options",
      "plan": "<p><code>{ interval, coldStartTimeoutMs, channel, onUpdate, onError }</code>, also accepted by <code>createCMS</code>. <code>interval</code> is in milliseconds, defaults to <code>DECO_CONTENT_INTERVAL</code> or 60 000, and has a 60 000 minimum (lower values are raised to one minute with a warning). Each check runs one interval ± 10 s after the previous one and fetches only the revision hash. The SDK schedules due checks itself at an idle moment: <code>requestIdleCallback</code>, then <code>scheduler.postTask</code>, then <code>setTimeout</code>; an unref'd timer on Node; and after the response inside <code>ctx.waitUntil</code> on Workers. Instances are process-wide singletons keyed by configuration, with a <code>resetForTests()</code> helper. In dev, the local <code>.deco/blocks</code> wins over the published release even with a site token set, while <code>?__draft=</code> links still fetch the draft from the API. A draft that fails to load is the app's call, not the framework's: <code>cms.forDraft(pointer).resolve()</code> returns <code>[null, error]</code> and never falls back to published content silently.</p>",
      "features": [
        "fast-deploy-kv",
        "env-vars-and-secrets",
        "hosting-and-deploy-config",
        "otel-observability",
        "dev-studio-content-loop",
        "bundler-config",
        "edge-html-cache-profiles"
      ],
      "docs": [
        "hosted-publishing"
      ],
      "pinned": false
    },
    {
      "id": "roadmap-api--document-and-support-kv-only-content-for-large-workers-sites",
      "group": "api",
      "title": "Document and support KV-only content for large Workers sites",
      "plan": "<p>With <code>content</code> plus <code>site</code> and <code>token</code>, the bundled content module stays in memory as the fallback next to the live release, against a Worker isolate's 128 MB limit, and it also counts toward the script size limit. Document passing a KV-backed <code>Loader</code> as <code>content</code> for large sites, so no copy is bundled and the fallback is read from KV only when needed. Document a few-line KV loader as template code (no new API: any object with <code>load()</code>), and a deploy step that writes the content to the key before the new Worker takes traffic, since with a missing key every new isolate fails its first reads until its first release check.</p>",
      "features": [
        "fast-deploy-kv",
        "hosting-and-deploy-config",
        "in-isolate-caches",
        "content-storage-and-delivery"
      ],
      "docs": [
        "hosted-publishing"
      ],
      "pinned": false
    },
    {
      "id": "roadmap-api--add-session-region-delivery-promise-and-prices-to-apps-vtex",
      "group": "api",
      "title": "Add session, region, delivery promise and prices to <code>apps-vtex</code>",
      "plan": "<p>Typed requests for delivery promise in Intelligent Search, tax-inclusive prices, full installments with InterestRate, Intelligent Search events, and <code>createVtexIoClient</code> for persisted IO queries. The session module (<code>resolveRegion</code>, <code>setSegment</code>), the session and cart hooks move to the VTEX platform template, which sites copy.</p>",
      "features": [
        "session-regionalization",
        "delivery-promise",
        "product-card",
        "graphql-schema-extensions",
        "cart-and-minicart",
        "vtex-io-app-settings",
        "ecommerce-analytics-events"
      ],
      "docs": [],
      "pinned": false
    },
    {
      "id": "roadmap-api--add-a-studio-bridge",
      "group": "api",
      "title": "Add a site editor bridge",
      "plan": "<p>A client-safe <code>&lt;StudioBridge /&gt;</code> that accepts <code>editor::inject</code> only from an explicit list of site editor origins, configurable for a self-hosted or native site editor. Also <code>resolve(t, { withType: true })</code> to recover the block type.</p>",
      "features": [
        "root-document-layout",
        "studio-preview",
        "interactive-ui-widgets",
        "section-catalog",
        "framework-import-surface",
        "lazy-deferred-sections"
      ],
      "docs": [],
      "pinned": false
    },
    {
      "id": "roadmap-api--ship-an-seo-kit-in-apps-website-and-apps-commerce",
      "group": "api",
      "title": "Ship an SEO kit in the platform templates",
      "plan": "<p>SEO helpers are website features, so they live in the platform templates, as code a site owns: <code>PageSeo</code> and SEO functions pre-aliased to SeoV2/Seo/SeoPDPV2/SeoPLPV2, <code>mergeSiteSeo(siteSeo, pageSeo)</code> as the one site-SEO merge (sharing a test suite with the site editor), <code>seoToNextMetadata</code> / <code>seoToTanStackHead</code>, JSON-LD builders and a <code>&lt;JsonLd&gt;</code> renderer.</p>",
      "features": [
        "page-seo-blocks",
        "site-seo-defaults",
        "commerce-seo-sections-in-sections",
        "json-ld-structured-data",
        "site-config-block",
        "robots-and-static-seo-files"
      ],
      "docs": [],
      "pinned": false
    },
    {
      "id": "roadmap-api--define-an-app-contract",
      "group": "api",
      "title": "Define the client contract",
      "plan": "<p>Each client package exports a factory that takes its configuration as arguments (for example <code>createVtexClient({ account, appKey, appToken })</code>, read from environment variables in site code) and typed request functions. No block map, no framework package dependency, no dynamic <code>module</code> import. Installing an app from the site editor's store is unavailable on next-major sites: a client is added in code.</p>",
      "features": [
        "app-installation-and-store-config",
        "commerce-platform-binding",
        "app-loader-overrides",
        "inline-loader-props",
        "cart-customer-server-functions",
        "shopify-autoconfig-workaround"
      ],
      "docs": [],
      "pinned": false
    },
    {
      "id": "roadmap-api--add-an-edge-cache-kit",
      "group": "api",
      "title": "Add an edge cache recipe",
      "plan": "<p>A Workers template recipe, <code>withEdgeCache(entry, { segment, profiles })</code>, keyed by URL, build, revision and segment. It skips drafts, cold-start fallback and degraded pages. Plus <code>cacheHeaders(profile)</code> and <code>markDegraded()</code>.</p>",
      "features": [
        "edge-html-cache-profiles",
        "saved-loader-blocks",
        "inline-loader-props",
        "device-detection-and-targeting",
        "session-regionalization",
        "cart-customer-server-functions"
      ],
      "docs": [],
      "pinned": false
    },
    {
      "id": "roadmap-api--define-a-per-client-memoization-contract",
      "group": "api",
      "title": "Define a per-client memoization contract",
      "plan": "<p>A saved entry, or an identical inline block, resolves once per client (keyed by type plus canonical inputs), and concurrent calls share in-flight promises.</p>",
      "features": [
        "saved-loader-blocks",
        "inline-loader-props",
        "native-section-overrides",
        "commerce-seo-sections-in-sections",
        "implicit-page-data-context"
      ],
      "docs": [],
      "pinned": false
    },
    {
      "id": "roadmap-api--add-a-splat-to-matchroute-and-stop-request-time-throws",
      "group": "api",
      "title": "Add a splat to <code>matchRoute</code> and stop request-time throws",
      "plan": "<p>A trailing <code>/*</code> that matches one or more segments, at lowest precedence (also for <code>Redirect.from</code>, with substitution that keeps segments percent-encoded), and a deterministic tie-break (the entry earlier in the array wins, so <code>matchRoute</code> never throws). The built-in <code>redirect</code> also takes an optional <code>status</code> (301, 302, 307 or 308) and <code>discardQueryParameters</code>, so legacy redirects keep their exact behaviour.</p>",
      "features": [
        "cms-page-routing",
        "redirects",
        "page-template-targeting",
        "plp-filters-sort-pagination",
        "commerce-platform-binding"
      ],
      "docs": [],
      "pinned": false
    },
    {
      "id": "roadmap-api--ship-decocms-blocks-secrets",
      "group": "api",
      "title": "Ship the built-in <code>secret</code> block",
      "plan": "<p>A built-in <code>secret</code> block, <code>{ \"__resolveType\": \"secret\", \"ciphertext\": \"…\" }</code>, resolves to the decrypted string on the server. Fields typed <code>Secret</code> (a branded string) get a write-only password widget in the schema. Encryption is asymmetric: the public key is committed in <code>.deco/</code> (for example <code>.deco/secrets.pub</code>), so the site editor, <code>deco serve</code> and agents can encrypt, and the private key lives only on the server, passed as <code>createCMS({ secrets: { key: process.env.DECO_SECRETS_KEY } })</code>. Only ciphertext is committed.</p><p>Guardrails: a secret resolves only on the server (it throws in the browser and when passed to a client component), <code>{ run: false }</code> returns the ciphertext, telemetry redacts it, and <code>deco check</code> validates the ciphertext's format only. Open-source users create the key pair by hand with one standard command the docs show; there's no new CLI command. Migrating a v7 site re-encrypts its secrets once: decrypt with DECO_CRYPTO_KEY, encrypt with the new public key.</p>",
      "features": [
        "app-installation-and-store-config",
        "invoke-and-actions",
        "env-vars-and-secrets",
        "commerce-platform-binding"
      ],
      "docs": [
        "built-in-blocks"
      ],
      "pinned": false
    },
    {
      "id": "roadmap-api--add-a-site-wide-layout-helper",
      "group": "api",
      "title": "Add a site-wide layout helper",
      "plan": "<p><code>mergeGlobals(site, page)</code>, with layout blocks resolved on the page's client. Site SEO defaults go through the SEO kit's <code>mergeSiteSeo</code>.</p>",
      "features": [
        "site-config-block",
        "global-layout-sections",
        "theming-and-styling",
        "build-time-site-globals-snapshot"
      ],
      "docs": [],
      "pinned": false
    },
    {
      "id": "roadmap-api--keep-the-tanstack-router-helpers-available",
      "group": "api",
      "title": "Export the TanStack router helpers",
      "plan": "<p>Drop <code>createDecoRouter</code>. Export its pieces instead, <code>decoParseSearch</code>/<code>decoStringifySearch</code>, the CSP-nonce wiring and the scroll and intent defaults, so sites pass them to TanStack's own <code>createRouter</code>.</p>",
      "features": [
        "router-and-client-navigation",
        "home-route-override",
        "plp-filters-sort-pagination",
        "interactive-ui-widgets"
      ],
      "docs": [],
      "pinned": false
    },
    {
      "id": "roadmap-api--add-an-image-module",
      "group": "api",
      "title": "Add an image module",
      "plan": "<p><code>@decocms/blocks/image</code> (optimized URLs, srcset, site editor quality params), a Next <code>loaderFile</code>, and TanStack <code>&lt;Image&gt;</code>/<code>&lt;Picture&gt;</code>. Export the widget aliases.</p>",
      "features": [
        "image-optimization",
        "editor-widgets-and-image-fields",
        "framework-import-surface"
      ],
      "docs": [],
      "pinned": false
    },
    {
      "id": "roadmap-api--add-an-analytics-bootstrap-and-experiment-support",
      "group": "api",
      "title": "Support experiments; move the analytics bootstrap to the templates",
      "plan": "<p>Analytics scripts are a website feature: the DECO.events bootstrap and a typed <code>useSendEvent</code>/dispatch move to the platform templates. Template ecommerce events call <code>track()</code> alongside their GTM push; the core stays out of ecommerce. The framework keeps what experiments need: an <code>assignExperiments()</code> middleware helper and an explicit experiment id prop that keys results, which the site editor fills (the legacy alias bridge copies the old saved entry name into it, so existing results carry over). Variant exposure events for real-user monitoring come after the first release (see {{item:roadmap-later--add-real-user-monitoring}}).</p>",
      "features": [
        "analytics-trackers",
        "ecommerce-analytics-events",
        "root-document-layout"
      ],
      "docs": [],
      "pinned": false
    },
    {
      "id": "roadmap-api--add-a-not-found-and-redirect-gate-before-streaming",
      "group": "api",
      "title": "Add a not-found and redirect gate before streaming",
      "plan": "<p>A critical block (or seo) may return <code>{ notFound }</code> or <code>{ redirect }</code>, which the app maps to <code>notFound()</code>/<code>redirect()</code>. Framework control-flow errors are rethrown.</p>",
      "features": [
        "not-found-and-error-pages",
        "code-composed-pdp",
        "redirects"
      ],
      "docs": [],
      "pinned": false
    },
    {
      "id": "roadmap-api--add-observability-seams",
      "group": "api",
      "title": "Send the SDK's measurements to any collector",
      "plan": "<p>The SDK reports what it sees: resolution, upstream fetches through <code>createInstrumentedFetch</code>, and errors. Inbound requests and page caches come from the framework's own OpenTelemetry setup, not from Deco CMS. Where the SDK's measurements go is the <code>telemetry</code> option of <code>createCMS</code>: <code>{ endpoint, headers? }</code> sends OpenTelemetry over OTLP/HTTP to any collector, <code>{ site, token }</code> sends to the hosted Deco CMS collector, and <code>false</code> turns it off; when the option is omitted, <code>OTEL_EXPORTER_OTLP_ENDPOINT</code> is used if set, otherwise nothing is sent. Sending happens in the background (<code>ctx.waitUntil</code> on Workers), so a slow collector never delays a response.</p>",
      "features": [
        "otel-observability",
        "upstream-fetch-instrumentation"
      ],
      "docs": [
        "telemetry"
      ],
      "pinned": false
    },
    {
      "id": "roadmap-api--route-telemetry-with-several-cms-instances",
      "group": "api",
      "title": "Route telemetry with several CMS instances in one process",
      "plan": "<p>With several CMS instances in one process (several sites in one app, for example), each instrumented-fetch measurement goes to the CMS whose client is handling the current request, so every site reports only its own upstream traffic.</p>",
      "features": [
        "otel-observability",
        "upstream-fetch-instrumentation"
      ],
      "docs": [
        "telemetry-internals",
        "telemetry"
      ],
      "pinned": false
    },
    {
      "id": "roadmap-api--add-the-telemetry-block-sampling-and-aggregation",
      "group": "api",
      "title": "Add the Telemetry block, sampling and aggregation",
      "plan": "<p><code>createCMS({ blocks, content, telemetry })</code>: <code>telemetry</code> is <code>{ endpoint, headers? }</code> for your own OpenTelemetry (OTLP/HTTP) collector, <code>{ site, token }</code> for the hosted Deco CMS collector, or <code>false</code>. The top-level <code>site</code> and <code>token</code> load hosted releases and drafts only; they never turn telemetry on by themselves. Telemetry settings (<code>enabled</code>, <code>metrics</code>, <code>errorSampleRate</code>, <code>traceSampleRate</code>) live in an optional well-known saved block, <code>Telemetry</code>, of the built-in <code>telemetry</code> type; <code>deco content</code> doesn't scaffold it, and without it the defaults apply. Editors change it in the site editor or by hand like any content edit; it travels with every release. Code caps what content can raise with <code>telemetry.limits</code> (<code>errorSampleRate</code>, <code>traceSampleRate</code>), so an editor can't increase what leaves for a third party. Error logs are sampled; metrics are aggregated in memory before they're sent; credentials, cookies and bodies are scrubbed in the SDK.</p>",
      "features": [
        "otel-observability",
        "upstream-fetch-instrumentation",
        "env-vars-and-secrets"
      ],
      "docs": [
        "api-reference",
        "built-in-blocks",
        "telemetry"
      ]
    },
    {
      "id": "roadmap-api--add-open-source-analytics",
      "group": "api",
      "title": "Add open-source analytics compatible with One Dollar Stats",
      "plan": "<p>Ship a built-in <code>analytics</code> block, separate from telemetry (it doesn't read the <code>telemetry</code> option or the <code>Telemetry</code> block). It's a settings function, <code>(props?: Analytics) =&gt; Required&lt;Analytics&gt;</code>: it returns <code>{ enabled: true, collector: &lt;hosted Deco CMS collector&gt;, ...props }</code>, so <code>{ \"__resolveType\": \"analytics\" }</code> with no arguments uses the hosted collector, <code>collector</code> points it at your own endpoint and <code>enabled: false</code> turns it off. A site saves an <code>Analytics</code> block, resolves it in its root layout and renders <code>&lt;AnalyticsScript {...analytics} /&gt;</code> (nothing when <code>enabled</code> is false), the same on every framework, and editors change or switch it off in the site editor. There's no site ID: the collector tells sites apart by the page's hostname, as One Dollar Stats does. The script sends page views (path without query string, referrer, hostname) from the browser with no cookies; <code>track(name, props?)</code> from <code>@decocms/blocks/analytics</code> sends custom events through it. Events use the One Dollar Stats tracker's wire format (base64 JSON in <code>GET ?data=</code> when short, otherwise <code>POST</code>/<code>sendBeacon</code>), tested against a pinned tracker version, so <code>collector</code> can be One Dollar Stats' own collector or any endpoint that accepts that format; by default, the hosted Deco CMS collector receives them.</p>",
      "features": [
        "analytics-trackers",
        "ecommerce-analytics-events"
      ],
      "docs": [
        "analytics",
        "api-reference",
        "built-in-blocks",
        "telemetry-internals"
      ]
    },
    {
      "id": "roadmap-api--rename-apps-salesforce-to-a-marketing-cloud-personalization-client",
      "group": "api",
      "title": "Rename <code>apps-salesforce</code> to <code>apps-sfmc-personalization</code>",
      "plan": "<p>The package is a client for Salesforce Marketing Cloud Personalization (formerly Evergage), so it becomes <code>apps-sfmc-personalization</code>, on the same thin-client model as the other apps.</p>",
      "features": [
        "commerce-platform-binding",
        "upstream-fetch-instrumentation"
      ],
      "docs": [
        "upstream-clients"
      ]
    },
    {
      "id": "roadmap-api--add-predictive-search-and-shipping-rates-to-apps-shopify",
      "group": "api",
      "title": "Add predictive search and shipping rates to <code>apps-shopify</code>",
      "plan": "<p>A predictive-search suggestions loader and a shipping-rates helper.</p>",
      "features": [
        "site-search-and-autocomplete",
        "shipping-simulation"
      ],
      "docs": [],
      "pinned": false
    },
    {
      "id": "roadmap-api--generate-the-sitemap-from-a-client",
      "group": "api",
      "title": "Put sitemap generation in the platform templates",
      "plan": "<p>Sitemaps are a website feature, so the templates ship them as site code: <code>sitemapEntries(client, { types, expand, exclude })</code>, <code>toSitemapXml</code>/<code>toSitemapIndex</code>, plus a Shopify sitemap merge.</p>",
      "features": [
        "sitemap"
      ],
      "docs": [],
      "pinned": false
    },
    {
      "id": "roadmap-api--give-the-traffic-split-a-home-and-bypass-drafts-by-default",
      "group": "api",
      "title": "Give the traffic split a home and bypass drafts by default",
      "plan": "<p>Move <code>withABTesting</code> into the TanStack templates as a recipe and bypass drafts by default.</p>",
      "features": [
        "ab-traffic-split"
      ],
      "docs": [],
      "pinned": false
    },
    {
      "id": "roadmap-cli--write-a-studio-compatible-schema-file",
      "group": "cli",
      "title": "Write a site-editor-compatible schema file",
      "plan": "<p>Write <code>.deco/schema.gen.json</code> in the site editor's format (<code>deco-meta@1</code>, byte-compatible with today's <code>meta.gen.json</code>, which the protocol still reads as a fallback): <code>manifest.blocks</code>, <code>schema.definitions</code> keyed by padded, path-independent btoa, and <code>schema.root</code> unions. Add <code>apps</code> and <code>actions</code> groups. For every field a variant can fill (a field of type T), emit a monomorphized multivariate definition per T (value typed as T) under the site editor's legacy per-kind keys (<code>website/flags/multivariate/section.ts</code>, <code>…/image.ts</code>, …), so the field variant UI renders the right input.</p>",
      "features": [
        "studio-schema-generation",
        "codegen-pipeline",
        "ci-and-release-workflows",
        "site-bootstrap",
        "content-storage-and-delivery",
        "app-domain-types",
        "cms-schema-upload-pipeline",
        "section-catalog",
        "app-installation-and-store-config",
        "workspace-packages",
        "matchers-and-variants",
        "editor-widgets-and-image-fields"
      ],
      "docs": [],
      "pinned": false
    },
    {
      "id": "roadmap-cli--write-static-option-lists-into-the-schema",
      "group": "cli",
      "title": "Write static option lists into the schema",
      "plan": "<p>The site editor no longer calls the site for picker options, so <code>deco schema</code> writes every static list into the schema as an <code>enum</code>: string-literal unions, TypeScript enums and <code>@options</code> literal lists, with labels where the code gives them. A picker whose options come from a function falls back to free text in this version.</p>",
      "features": [
        "icon-by-name-enums",
        "editor-widgets-and-image-fields",
        "studio-schema-generation",
        "conditional-schema-fields"
      ],
      "docs": [
        "schema",
        "how-it-works"
      ]
    },
    {
      "id": "roadmap-cli--widen-the-legacy-alias-bridge",
      "group": "cli",
      "title": "Widen the legacy alias bridge",
      "plan": "<p>Unwrap Lazy/SingleDeferred/Deferred (the site editor's legacy <code>Lazy</code> section wrapper: the wrapped block renders normally, with no client-side deferral; it is unrelated to the new <code>lazy</code> block), Seo*, <code>site/apps/site.ts</code>, <code>resolved</code>, requestToParam, secret (after the one-time re-encryption by the migration), redirect (nested shape flattened, <code>temporary</code> kept as status 307 and <code>discardQueryParameters</code> kept), the device and random matchers (a random variant's saved entry name is copied into its experiment id), the <code>multi</code> matcher (<code>website/matchers/multi.ts</code> and <code>$live/matchers/MatchMulti.ts</code>, whose and/or combinators keep the <code>{ op, matchers }</code> shape that the site editor's variant calendar and rule labels read) and Theme, and list exactly which paths. Legacy Analytics (GTM/GA) isn't aliased to the built-in <code>analytics</code> block, which stays One Dollar Stats only: the migration moves it into a site-owned tag-manager block from the template.</p>",
      "features": [
        "block-type-discriminator",
        "section-catalog",
        "lazy-deferred-sections",
        "site-config-block",
        "site-search-and-autocomplete",
        "matchers-and-variants",
        "analytics-trackers",
        "redirects",
        "theming-and-styling",
        "page-seo-blocks",
        "i18n-and-currency"
      ],
      "docs": [],
      "pinned": false
    },
    {
      "id": "roadmap-cli--add-validation-commands-to-the-cli",
      "group": "cli",
      "title": "Add <code>deco check</code>, which validates saved content against the code",
      "plan": "<p><code>deco check [--root &lt;dir&gt;]</code> reads <code>.deco/schema.gen.json</code> and <code>.deco/blocks</code> as they are (it writes nothing, generates no schema, not even in memory, loads no TypeScript and doesn't check whether <code>schema.gen.json</code> is stale; run <code>deco schema</code> first), validates every saved block against that schema, lists the problems per file and exits 1 if there is any. Saved content is valid when every block's props match its block type's schema (required fields, allowed values, limits); every <code>__resolveType</code> names a block type (built-ins and aliases included) or an existing saved block; a reference in a typed field points to a block that returns that type; no saved block is named like a block type, built-in or alias; no two entries match the same URL; and every <code>secret</code> holds well-formed ciphertext (its format only). It also warns, without failing, about a variant after an <code>always</code> rule, which can never be chosen. It catches both directions, code that breaks saved content and content that uses types or fields the code lacks, and runs on content-only changes too, the same locally and in CI. It also runs in <code>prebuild</code> (<code>deco schema &amp;&amp; deco content &amp;&amp; deco check</code>; <code>predev</code> skips it), so a broken mix of code and content fails the build and never deploys. The docs ship a CI example: <code>npx @decocms/blocks schema &amp;&amp; npx @decocms/blocks check</code>, then <code>git diff --exit-code -- .deco/schema.gen.json</code> so a stale committed schema (the file the site editor reads) fails, plus an optional “Require branches to be up to date before merging” (or a merge queue), to catch two changes that pass alone but break together at pull-request time instead of at build time. The Deco API and <code>remoteLoader</code> don't validate content at runtime. Ships with the first release.</p><p>Implementation: validate with a JSON Schema validator (such as Ajv) against <code>.deco/schema.gen.json</code> as it is on disk, not by type-checking generated TypeScript with <code>tsc</code>: it takes milliseconds, its errors name the file and field, and it enforces JSDoc constraints and exactly what the site editor allows. Dispatch on <code>__resolveType</code> instead of reporting every <code>anyOf</code> branch, check references against the return type of the function behind them, and rewrite validator errors into short plain lines.</p>",
      "features": [
        "orphan-and-dangling-blocks",
        "content-schema-drift",
        "matchers-and-variants",
        "newsletter-subscription",
        "dev-tooling-and-repo-hygiene",
        "e2e-lighthouse-config",
        "ci-and-release-workflows",
        "cms-schema-upload-pipeline",
        "page-seo-blocks",
        "site-bootstrap",
        "codegen-pipeline",
        "content-storage-and-delivery"
      ],
      "docs": [
        "checking",
        "cli"
      ],
      "pinned": false
    },
    {
      "id": "roadmap-cli--publish-the-tag-and-widget-vocabulary",
      "group": "cli",
      "title": "Publish the tag and widget vocabulary",
      "plan": "<p>A full JSDoc/@format table (image-uri, video-uri, html, rich-text, rich-text-inline, textarea, markdown, color-input, icon-select, @titleBy, @hide, @options, @label, @minItems…), with widget-alias detection kept, labelled literals, and a golden test against <code>generate-schema.ts</code>.</p>",
      "features": [
        "editor-widgets-and-image-fields",
        "app-domain-types",
        "icon-by-name-enums",
        "rich-text-html-props",
        "framework-import-surface",
        "conditional-schema-fields",
        "web-fonts",
        "theming-and-styling",
        "cms-navigation-menus"
      ],
      "docs": [],
      "pinned": false
    },
    {
      "id": "roadmap-cli--ship-migration-codemods",
      "group": "cli",
      "title": "Ship migration codemods",
      "plan": "<p><code>deco migrate block-map</code> from 7.x manifests, one 7 → next import codemod (6.x sites upgrade to 7.x first), an SEO-hoist rule, APP_REGISTRY → block maps, a requestToParam scaffold, a rule that moves legacy Analytics (GTM/GA) content into the template's tag-manager block, and a one-time re-encryption of v7 secrets for the <code>secret</code> block.</p>",
      "features": [
        "codegen-pipeline",
        "site-bootstrap",
        "framework-import-surface",
        "commerce-seo-sections-in-sections",
        "app-installation-and-store-config",
        "route-params-request-to-param",
        "section-registration-conventions"
      ],
      "docs": [],
      "pinned": false
    },
    {
      "id": "roadmap-cli--vendor-the-loaders-and-actions-a-site-uses-during-migration",
      "group": "cli",
      "title": "Vendor the loaders and actions a site uses during migration",
      "plan": "<p>The companion apps stop shipping loaders and actions, so the migration copies the ones a site actually uses (found from its saved content and imports) into the site's own code, rewritten over the thin clients, with their legacy type names kept as aliases so saved content still resolves.</p>",
      "features": [
        "site-local-loaders",
        "saved-loader-blocks",
        "commerce-platform-binding",
        "app-loader-overrides",
        "inline-loader-props"
      ],
      "docs": [
        "renames-and-migrations",
        "upstream-clients"
      ]
    },
    {
      "id": "roadmap-cli--define-the-loader-picker-rule",
      "group": "cli",
      "title": "Match fields to block functions by return type",
      "plan": "<p>A field's type is the value the function receives. <code>deco schema</code> reads each block function's awaited return type, so an object field of type T accepts a plain T or any block whose function returns <code>T</code> or <code>Promise&lt;T&gt;</code> (anyOf $refs), and the site editor's block picker offers those functions plus the saved blocks that resolve through them. <code>ReactNode</code> and <code>ReactNode[]</code> fields take only functions that return JSX (a React element, or a promise of one; the site editor's legacy <code>__SECTION_REF__</code>): <code>ReactNode</code> also includes strings, numbers and booleans, but functions returning those are left out, so matchers never fit <code>sections</code>. Simple types (<code>string</code>, <code>number</code>, <code>boolean</code>, literal unions) stay plain inputs. The generic <code>multivariate&lt;T&gt;</code> fits any field, with T taken from the field and its <code>undefined</code> (no rule matched) allowed, and functions that return <code>boolean</code> fill each rule in <code>variants</code>. The CLI follows imported app maps with last-key-wins and warns on <code>any</code>.</p>",
      "features": [
        "inline-loader-props",
        "saved-loader-blocks",
        "app-loader-overrides",
        "web-fonts",
        "product-card",
        "commerce-platform-binding",
        "route-params-request-to-param",
        "nested-section-props",
        "section-catalog"
      ],
      "docs": [
        "schema",
        "blocks"
      ],
      "pinned": false
    },
    {
      "id": "roadmap-cli--specify-the-filename-rule-and-ship-a-content-bundle",
      "group": "cli",
      "title": "Specify the filename rule and ship a content bundle",
      "plan": "<p>Use the protocol module's one filename rule: a file name is decoded exactly once, and the site editor, <code>deco serve</code> and <code>deco content</code> import the same module, with a collision report. <code>deco content</code> writes <code>.deco/blocks.gen.ts</code>, one import per JSON file, exporting the snapshot <code>{ revision, blocks }</code> with the revision computed the same way the Deco API computes it, and <code>computeRevision</code> is exported.</p>",
      "features": [
        "block-type-discriminator",
        "content-storage-and-delivery",
        "codegen-pipeline",
        "site-bootstrap",
        "dev-studio-content-loop",
        "inline-loader-props"
      ],
      "docs": [],
      "pinned": false
    },
    {
      "id": "roadmap-cli--emit-a-runtime-alias-table",
      "group": "cli",
      "title": "Emit a runtime alias table",
      "plan": "<p>The CLI emits an alias table, a list of second names for types. <code>deco content</code> writes it into the content module (the snapshot's <code>aliases</code> field) and <code>createCMS</code> reads it from there, so <code>list()</code> and <code>resolve()</code> work through aliases. The schema carries it too, mapping the short names (<code>page</code>, <code>redirect</code>, <code>always</code>, <code>never</code>, <code>multivariate</code>, <code>secret</code>) to the well-known types the site editor's special screens look for, so those screens keep working for renamed types.</p>",
      "features": [
        "cms-page-routing",
        "codegen-pipeline",
        "studio-schema-generation",
        "key-prefix-content-collections",
        "redirects",
        "block-type-discriminator"
      ],
      "docs": [
        "studio-compatibility",
        "renames-and-migrations"
      ],
      "pinned": false
    },
    {
      "id": "roadmap-cli--ship-deco-serve-a-local-server-for-studio",
      "group": "cli",
      "title": "Ship <code>deco serve</code>, a local server for the site editor",
      "plan": "<p>A command of the <code>deco</code> CLI in <code>@decocms/blocks</code>, <code>deco serve</code>, that serves the content protocol on <code>localhost:&lt;port&gt;</code> for the developer's working tree (the app root from <code>--root</code>, or the nearest folder with a <code>.deco/</code>, which it reports to the site editor in <code>describe</code>), so the site editor can edit the files on the developer's machine: the saved content in <code>.deco/blocks</code>, the schema in <code>.deco/schema.gen.json</code> (read-only) and the upload URLs for assets. It listens on the loopback address by default, prints a connect link at start (no token), answers CORS for any origin and answers Chrome's local-network permission preflight. Writes land in <code>.deco/blocks</code> and the app hot-reloads; the server regenerates the content module when a write adds or removes a file. It never commits. <code>deco schema --watch</code> and <code>deco content --watch</code> stay as they are.</p>",
      "features": [
        "dev-studio-content-loop",
        "bundler-config",
        "codegen-pipeline",
        "studio-schema-generation"
      ],
      "docs": [
        "schema",
        "cli"
      ]
    },
    {
      "id": "roadmap-cli--store-uploads-in-the-repository-with-deco-serve",
      "group": "cli",
      "title": "Store uploads in the repository with <code>deco serve</code>",
      "plan": "<p>Without the hosted Deco CMS, files uploaded in the site editor are written into the repository: <code>deco serve</code> accepts them at <code>PUT /assets/&lt;name&gt;</code> beside <code>/rpc</code>, with the same origin and <code>Host</code> checks, writes them to <code>public/assets/</code> (or the folder <code>--assets &lt;dir&gt;</code> names), never overwrites an existing file, and answers with the path the site editor stores in the field, always <code>/assets/&lt;name&gt;</code>, such as <code>/assets/summer-banner.jpg</code>. The folder is relative to the folder that contains <code>.deco</code>. <code>describe</code> reports the folder and the largest file accepted in its <code>assets</code> field, which is <code>null</code> with <code>--read-only</code>, and uploads are then refused. Vite, TanStack Start and Next already serve <code>public/assets</code> at <code>/assets/</code> with no setup; a site that picks another folder serves it there itself.</p>",
      "features": [
        "dev-studio-content-loop",
        "editor-widgets-and-image-fields",
        "image-optimization"
      ],
      "docs": [
        "cli",
        "studio-compatibility",
        "schema"
      ],
      "pinned": false
    },
    {
      "id": "roadmap-cli--ship-the-deco-command",
      "group": "cli",
      "title": "Ship the <code>deco</code> command",
      "plan": "<p>These docs run <code>npx @decocms/blocks schema</code>, but <code>@decocms/blocks</code> doesn't declare a <code>deco</code> bin yet. Ship the CLI inside <code>@decocms/blocks</code>, so <a href=\"#quickstart\">Quickstart</a> installs one package and the CLI always matches the runtime version. Declare <code>deco</code> as the package's single bin, so <code>npx @decocms/blocks &lt;command&gt;</code> runs it with nothing installed and <code>package.json</code> scripts call <code>deco &lt;command&gt;</code>; the bin is a small JavaScript file that loads the TypeScript sources, so it runs under plain Node as well as Bun. Export the commands from the <code>@decocms/blocks/cli</code> subpath, make <code>typescript</code> a required peer dependency (so npm and Bun install it) that the CLI loads only when a command runs, and keep the runtime from ever importing the <code>cli</code> subpath, so app bundles never include the CLI. It has four commands, <code>deco schema [--root &lt;dir&gt;] [--watch]</code>, <code>deco check [--root &lt;dir&gt;]</code>, <code>deco content [--root &lt;dir&gt;] [--watch]</code> and <code>deco serve [--root &lt;dir&gt;] [--port &lt;n&gt;] [--host &lt;addr&gt;] [--app-url &lt;url&gt;] [--assets &lt;dir&gt;] [--read-only]</code>. Every command works on one folder, <code>.deco/</code>, in the app root (the folder with the app's <code>package.json</code>). <code>--root</code> is the folder that contains it; by default, every command walks up from the current folder to the first folder with a <code>.deco/</code>. There are no input or output paths: <code>deco schema</code> reads <code>.deco/index.ts</code> (or <code>.deco/index.tsx</code>) and writes <code>.deco/schema.gen.json</code>; <code>deco content</code> reads <code>.deco/blocks</code> and writes <code>.deco/blocks.gen.ts</code>, and never reads the block map. The <code>.gen.</code> infix marks the two generated files. The flags of each command are in the <a href=\"/next/cli#deco-schema-and-deco-content\">CLI reference</a>.</p>",
      "features": [
        "codegen-pipeline",
        "studio-schema-generation",
        "dev-tooling-and-repo-hygiene"
      ],
      "docs": [
        "quickstart",
        "schema",
        "cli",
        "internals"
      ],
      "pinned": false
    },
    {
      "id": "roadmap-platform--build-studio-s-github-content-backend",
      "group": "studio",
      "title": "Build the site editor's GitHub content backend",
      "plan": "<p>A <code>ContentStorage</code> inside the site editor's API that reads and writes <code>.deco/blocks</code> through the GitHub API, in the folder the project's \"app root\" setting names (a monorepo subfolder included). Every write is a commit, and all files of one <code>blocks.apply</code> go in one tree and one commit, with file contents inlined in the tree request. Clients poll on window focus and about every 30 seconds with one batched conditional request. Publishing, the draft pointer and <code>?__draft=</code> links stay site editor routes outside the protocol. Drop the requirement for a deployed preview URL, so a project with no deployment can still be edited.</p><p>Reuse the existing Fast Preview Git-provider and autosave machinery behind this adapter. Bind the editor endpoint to an automatically managed draft, normalize set precedence, preserve conditional guards and durable request receipts, and coordinate saves with synchronization and publication.</p><p>Use the <a href=\"#studio-implementation\">Studio implementation handoff</a> for module ownership, durable state and APIs, operation sequencing, proposed defaults and acceptance scenarios; completion includes the documented failure and concurrency cases.</p>",
      "features": [
        "admin-protocol-endpoints",
        "content-storage-and-delivery",
        "studio-schema-generation",
        "site-bootstrap",
        "workspace-packages"
      ],
      "docs": [
        "content-protocol",
        "studio-compatibility",
        "hosted-site-editor",
        "draft-synchronization",
        "studio-implementation"
      ]
    },
    {
      "id": "roadmap-platform--move-studio-s-editor-onto-the-protocol-client",
      "group": "studio",
      "title": "Move the site editor onto the protocol client",
      "plan": "<p>Projects with a committed <code>.deco/schema.gen.json</code> or <code>.deco/meta.gen.json</code> under their folder use the protocol client, so v7 projects that commit <code>meta.gen.json</code> move to it automatically, with no per-project switch. Fresh and Deno sites, whose schema exists only at runtime, stay on the legacy path. Features that need the site's code degrade: block previews become name cards or open the real page (the local dev app, or the deployed site with a <code>?__draft=</code> link), dynamic pickers fall back to the schema's options, else free text, and Run and installing apps from the store are hidden. <code>Secret</code> fields keep working: the site editor encrypts them itself with the site's committed public key (see {{item:roadmap-platform--encrypt-secret-fields-in-the-site-editor}}). Autosave stays last-writer-wins. Add the connect flow for a local <code>deco serve</code> endpoint, with a one-line explainer before Chrome's local-network prompt.</p>",
      "features": [
        "section-catalog",
        "global-layout-sections",
        "theming-and-styling",
        "invoke-and-actions",
        "env-vars-and-secrets",
        "app-installation-and-store-config",
        "dev-studio-content-loop",
        "icon-by-name-enums",
        "studio-preview"
      ],
      "docs": [
        "content-protocol",
        "studio-compatibility"
      ]
    },
    {
      "id": "roadmap-platform--encrypt-secret-fields-in-the-site-editor",
      "group": "studio",
      "title": "Encrypt <code>Secret</code> fields in the site editor and manage keys in Deco CMS",
      "plan": "<p>The site editor shows a <code>Secret</code> field as a write-only password input: it encrypts the value in the browser with the public key committed in <code>.deco/</code> and saves only the ciphertext as a <code>secret</code> block, never showing a saved value again. It needs no action from the site, so it works on protocol projects and with <code>deco serve</code>. The hosted Deco CMS adds key management: creating the key pair, storing the private key for the site's deployments and rotating it (re-encrypting every saved secret in one commit). Without the hosted Deco CMS, developers create the key pair by hand.</p>",
      "features": [
        "env-vars-and-secrets",
        "app-installation-and-store-config",
        "invoke-and-actions"
      ],
      "docs": [
        "built-in-blocks",
        "site-editor"
      ],
      "pinned": false
    },
    {
      "id": "roadmap-platform--add-a-play-workspace-for-local-editing",
      "group": "studio",
      "title": "Add a play workspace for local editing",
      "plan": "<p>Let anyone edit the files on their machine in the site editor without a Deco account: a connect link from <code>deco serve</code> opens a play workspace in the site editor that talks only to that local server, with no site, team or sign-in. Hosted features (the site editor's GitHub backend, releases, drafts on your site, hosted asset storage and telemetry) stay with a connected site.</p>",
      "features": [
        "dev-studio-content-loop"
      ],
      "docs": [
        "how-it-works",
        "cli"
      ],
      "pinned": false
    },
    {
      "id": "roadmap-platform--store-uploads-in-deco-s-asset-storage",
      "group": "studio",
      "title": "Store uploads in Deco's asset storage",
      "plan": "<p>With the hosted Deco CMS, the site editor's GitHub backend sends uploads to Deco's asset storage (backed by Amazon S3) instead of the repository, and saves each file's CDN address in the field, so images are served from a CDN and the repository doesn't grow. A field can hold either a CDN address or a path to a file in the repository, so sites that started with local uploads keep working.</p>",
      "features": [
        "editor-widgets-and-image-fields",
        "image-optimization"
      ],
      "docs": [
        "hosted-site-editor",
        "hosted",
        "content-protocol"
      ],
      "pinned": false
    },
    {
      "id": "roadmap-platform--fix-the-add-section-gate",
      "group": "studio",
      "title": "Fix the Add Section gate",
      "plan": "<p>The site editor shows Add Section only when a preview server is configured. Gate it on having the schema and the blocks instead; otherwise adding blocks to a page disappears on protocol projects.</p>",
      "features": [
        "section-catalog",
        "per-page-section-whitelists"
      ],
      "docs": [
        "how-it-works"
      ]
    },
    {
      "id": "roadmap-platform--read-manifest-groups-for-pages-redirects-content-and-apps",
      "group": "studio",
      "title": "Read manifest groups for pages, redirects, content and apps",
      "plan": "<p>Support several page types and a content collection screen. Read flat <code>redirect</code> entries alongside the nested legacy shape, mapping temporary to status 307 and keeping <code>discardQueryParameters</code>.</p>",
      "features": [
        "cms-page-routing",
        "redirects",
        "per-page-section-whitelists",
        "page-template-targeting",
        "session-regionalization",
        "delivery-promise",
        "cart-and-minicart",
        "vtex-io-app-settings",
        "site-config-block"
      ],
      "docs": [],
      "pinned": false
    },
    {
      "id": "roadmap-platform--use-manifest-lookup-instead-of-path-heuristics",
      "group": "studio",
      "title": "Use manifest lookup instead of path heuristics",
      "plan": "<p>Replace the file-extension module-or-entry check and the site editor's legacy <code>\"/sections/\"</code> path heuristics, and refuse to save an entry whose name is a manifest key.</p>",
      "features": [
        "block-type-discriminator",
        "section-registration-conventions",
        "faststore-cli-overlay",
        "native-section-overrides",
        "codegen-pipeline",
        "favicon-generation",
        "section-catalog",
        "orphan-and-dangling-blocks"
      ],
      "docs": [],
      "pinned": false
    },
    {
      "id": "roadmap-platform--build-the-deco-api-release-service",
      "group": "studio",
      "title": "Build the Deco API release service",
      "plan": "<p>Separate GitHub ingestion and publication from a stable storage/CDN data plane: production reads, including misses and cold reads, never call GitHub. Materialize immutable, canonically hashed JSON snapshots before promoting small per-environment channel manifests. Keep authorization in a minimal independently deployed gateway when needed. Reconcile missed or out-of-order webhooks in the control plane, bound materialization memory and retain last-good releases. Publishing doesn't validate content: the runtime's route tie-break and CI's <code>deco check</code> are the safety net.</p><p>Use a durable per-channel promotion coordinator and monotonic generations. Fast rollback selects a retained compatible snapshot without builds, deploys or GitHub reads, records an audit event and pauses automatic promotion until explicitly resumed. Prevent stale jobs from undoing it, define asset retention and garbage collection, notify rendered-page caches, and document actual propagation delays. Site token lifecycle and CI code/content compatibility remain required.</p><p>Use the <a href=\"#studio-implementation\">Studio implementation handoff</a> for module ownership, durable state and APIs, operation sequencing, proposed defaults and acceptance scenarios; completion includes the documented failure and concurrency cases.</p><p>Production assets stay self-contained; draft previews use versioned overlays over local production, without a base-revision fetch. See <a href=\"#content-delivery--exact-draft-previews\">Draft overlays</a>.</p>",
      "features": [
        "content-storage-and-delivery",
        "cms-page-routing",
        "orphan-and-dangling-blocks",
        "codegen-pipeline",
        "env-vars-and-secrets",
        "hosting-and-deploy-config",
        "fast-deploy-kv"
      ],
      "docs": [
        "hosted",
        "hosted-publishing",
        "hosted-drafts",
        "content-delivery",
        "hosted-releases-internals",
        "studio-implementation"
      ],
      "pinned": false
    },
    {
      "id": "roadmap-platform--synchronize-editor-drafts",
      "group": "studio",
      "title": "Keep editor drafts current automatically",
      "plan": "<p>Manage internal draft branches automatically; editors never create branches or rebase. Extend GitHub push intake and existing Studio event delivery: synchronize open drafts in the background, inactive drafts on reopen, and reconcile before saves and publishing. Coalesce updates and retain a slow reconciliation fallback. Use file-level draft wins: compare the draft with its incorporated production tree by blob identity; untouched files adopt production, edited or deleted files keep the entire draft version. Do not merge JSON properties or download conflict bodies. Reuse existing blobs for all file sizes.</p><p>Serialize operations per draft, retry external head races without unconditional force updates, retain append-only history and recoverable incorporated-base metadata, and preserve persistence guards. Bound tree-metadata work and concurrency; schema checks remain in CI. Expose synchronization status and audited resolution summaries without overwriting unsaved form state. Publishing produces only the saved-content diff against current production.</p><p>Use the <a href=\"#studio-implementation\">Studio implementation handoff</a> for module ownership, durable state and APIs, operation sequencing, proposed defaults and acceptance scenarios; completion includes the documented failure and concurrency cases.</p>",
      "features": [
        "content-storage-and-delivery",
        "dev-studio-content-loop",
        "workspace-packages"
      ],
      "docs": [
        "draft-synchronization",
        "hosted-site-editor",
        "content-protocol",
        "studio-implementation"
      ]
    },
    {
      "id": "roadmap-platform--materialize-exact-draft-previews",
      "group": "studio",
      "title": "Serve draft overlays over local production",
      "plan": "<p>Materialize saved draft changes as private immutable overlay manifests referencing changed-block blobs, with explicit deletion tombstones. Each manifest represents the cumulative draft overrides, not an incremental replay chain. The preview client captures whatever production snapshot it already has; no baseRevision or matching-production download. Fetch only missing changed blobs, share unchanged objects, and key composed views by local production revision plus overlay version. Old pointers fix old overrides but intentionally inherit current local content on later requests.</p><p>Use saved write bodies and cached tree/blob identities; read only changed files during recovery, and handle truncated Git comparisons. Preserve exact overlay authorization, bounded memory, preview readiness and garbage collection of referenced blobs. Missing overlays fail the draft client. Production releases remain complete immutable snapshots.</p><p>Use the <a href=\"#studio-implementation\">Studio implementation handoff</a> for module ownership, sequencing and acceptance scenarios.</p>",
      "features": [
        "studio-preview",
        "content-storage-and-delivery",
        "env-vars-and-secrets"
      ],
      "docs": [
        "content-delivery",
        "hosted-drafts",
        "content-protocol",
        "studio-implementation"
      ]
    },
    {
      "id": "roadmap-platform--align-the-preview-protocol",
      "group": "studio",
      "title": "Align the preview protocol",
      "plan": "<p>Keep <code>?__draft=off</code> as the one documented way to end a draft preview, and force variants in the draft content. On protocol projects the canvas opens the real page with the draft pointer, and gallery thumbnails and in-place renders are replaced by name cards, since the protocol never runs site code.</p>",
      "features": [
        "studio-preview",
        "device-detection-and-targeting",
        "matchers-and-variants",
        "section-catalog",
        "theming-and-styling",
        "global-layout-sections"
      ],
      "docs": [
        "hosted-drafts"
      ],
      "pinned": false
    },
    {
      "id": "roadmap-platform--drop-legacy-names-from-the-blog-tab-and-experiments",
      "group": "studio",
      "title": "Drop legacy names from the Blog tab and experiments",
      "plan": "<p>Detect the blog from the manifest, and key experiments on their explicit experiment id instead of the random matcher's entry name.</p>",
      "features": [
        "studio-workspace-spaces",
        "key-prefix-content-collections",
        "analytics-trackers"
      ],
      "docs": [],
      "pinned": false
    },
    {
      "id": "roadmap-platform--label-matchers-from-their-schema-and-keep-enum-titles",
      "group": "studio",
      "title": "Label matchers from their schema and keep enum titles",
      "plan": "<p>Label short-key matchers from their schema, dedupe aliased matchers, and keep oneOf const titles.</p>",
      "features": [
        "device-detection-and-targeting",
        "matchers-and-variants",
        "icon-by-name-enums"
      ],
      "docs": [],
      "pinned": false
    },
    {
      "id": "roadmap-platform--add-a-per-page-type-catalog-and-a-page-settings-form",
      "group": "studio",
      "title": "Add a per-page-type catalog and a page settings form",
      "plan": "<p>Use the anyOf on a page type's <code>sections</code> list as the site editor's catalog, and render the remaining page props as a form.</p>",
      "features": [
        "per-page-section-whitelists",
        "page-level-settings-fields"
      ],
      "docs": [],
      "pinned": false
    },
    {
      "id": "roadmap-platform--bring-studio-s-seo-merge-to-parity-with-the-kit",
      "group": "studio",
      "title": "Bring the site editor's SEO merge to parity with the kit",
      "plan": "<p>Run the site editor's <code>mergeSeo</code> (seo-editor.tsx) against the kit's <code>mergeSiteSeo</code> test suite. The site-wide template applies to titles only: descriptions are used as written, so <code>descriptionTemplate</code> never wraps them.</p>",
      "features": [
        "site-seo-defaults",
        "page-seo-blocks"
      ],
      "docs": [],
      "pinned": false
    },
    {
      "id": "roadmap-platform--add-a-content-live-signal",
      "group": "studio",
      "title": "Add a content-live signal",
      "plan": "<p>Every response carries the served revision in <code>x-deco-revision</code>, and the CMS reports each revision it swaps in to the Deco API with the site token. The site editor polls the API and shows a publish as pending until a report for its revision arrives, then live.</p>",
      "features": [
        "fast-deploy-kv",
        "edge-html-cache-profiles"
      ],
      "docs": [],
      "pinned": false
    },
    {
      "id": "roadmap-platform--build-the-telemetry-collector",
      "group": "studio",
      "title": "Build the telemetry collector",
      "plan": "<p>An endpoint that accepts aggregated metrics and sampled error logs, authenticated by site ID and site token, for sites that set <code>telemetry: { site, token }</code>. Separately, it receives page views in the One Dollar Stats format from <code>analytics</code> blocks left on the default <code>collector</code>, telling sites apart by hostname and counting only sites connected to the hosted Deco CMS. Switching a site's telemetry off needs no special channel: it's a commit to the site's <code>Telemetry</code> saved block, from the site editor or by hand, like any content edit.</p>",
      "features": [
        "otel-observability",
        "upstream-fetch-instrumentation",
        "analytics-trackers"
      ],
      "docs": [
        "hosted-telemetry"
      ]
    },
    {
      "id": "roadmap-platform--update-the-injected-agent-rules",
      "group": "studio",
      "title": "Update the injected agent rules",
      "plan": "<p>Replace the two wrong rules for next-major sites. Content edits go only in <code>.deco/blocks/&lt;encodeURIComponent(key)&gt;.json</code>. <code>.deco/index.ts</code> and <code>.deco/blocks</code> are the site's; two files are generated and never edited by hand: <code>.deco/schema.gen.json</code>, which <code>deco schema</code> rewrites after any change to a block's types, and <code>.deco/blocks.gen.ts</code>, which <code>deco content</code> writes and git ignores. A new type needs a block-map entry. <code>remoteLoader</code> picks up prepared releases on its next background check, not on save, and with DECO_SITE and DECO_SITE_TOKEN set the dev server serves the API release, not local edits.</p>",
      "features": [
        "ai-agent-instructions"
      ],
      "docs": [],
      "pinned": false
    },
    {
      "id": "roadmap-docs--resolve-contradictions-in-these-docs",
      "group": "docs",
      "title": "Resolve contradictions in these docs",
      "plan": "<p>Make Page.seo optional (the site's SEO defaults fill in), drop array elements that resolve to <code>undefined</code> everywhere (the site editor's hidden array items), say that a hidden block resolves to <code>undefined</code> and what to render is the app's job (the core has no fallback rule), put the <code>always()</code> variant last, and fix the credential sentence in <a href=\"#hosted-drafts--who-may-preview\">Drafts on your site</a>.</p>",
      "features": [
        "cms-page-routing",
        "codegen-pipeline",
        "key-prefix-content-collections",
        "otel-observability",
        "cms-navigation-menus",
        "denormalized-collection-references",
        "product-card",
        "matchers-and-variants",
        "app-installation-and-store-config",
        "block-type-discriminator"
      ],
      "docs": [
        "api-reference",
        "routing",
        "schema",
        "hosted-releases-internals",
        "matchers-and-variants",
        "studio-compatibility",
        "blocks",
        "troubleshooting",
        "releases-and-drafts"
      ],
      "pinned": true
    },
    {
      "id": "roadmap-docs--correct-studio-compatibility",
      "group": "docs",
      "title": "Correct Site editor compatibility",
      "plan": "<p><a href=\"#studio-compatibility\">Site editor compatibility</a> now describes the content protocol, with the old site endpoints (<code>/live/_meta</code>, <code>/.decofile</code>, <code>/live/previews</code>, <code>/deco/invoke</code>, <code>/live/invoke</code>) as the legacy path. Keep it in step with the protocol as the site editor's implementation lands, and state that sites allow framing through frame-ancestors, never X-Frame-Options, as the entry wrappers do.</p>",
      "features": [
        "admin-protocol-endpoints",
        "invoke-and-actions",
        "csp-security-headers",
        "studio-schema-generation"
      ],
      "docs": [
        "studio-compatibility"
      ],
      "pinned": true
    },
    {
      "id": "roadmap-docs--fix-the-tanstack-guides",
      "group": "docs",
      "title": "Fix the TanStack guides",
      "plan": "<p>Mount <code>withDeco()</code> instead of hand-wiring the draft cookie. Add validateSearch/loaderDeps and staleTime, and state that <code>getRequest()</code> is the RPC request on SPA navigation, so the page URL comes from the request scope.</p>",
      "features": [
        "worker-server-entry",
        "env-vars-and-secrets",
        "otel-observability",
        "edge-html-cache-profiles",
        "home-route-override",
        "section-loaders",
        "request-context-cookies",
        "router-and-client-navigation",
        "plp-filters-sort-pagination",
        "studio-preview"
      ],
      "docs": [
        "tanstack-start-descriptors",
        "tanstack-start-rsc",
        "blocks"
      ],
      "pinned": false
    },
    {
      "id": "roadmap-docs--write-the-recipes-that-gate-cutover",
      "group": "docs",
      "title": "Write the recipes that gate cutover",
      "plan": "<p>Write these before the first site migrates: site-wide content and layout (header, footer, theme), site settings, 404/500, route params in blocks, per-shopper reads and actions as server functions (invoke → server fn), and regionalization cache keys.</p>",
      "features": [
        "site-config-block",
        "global-layout-sections",
        "theming-and-styling",
        "not-found-and-error-pages",
        "route-params-request-to-param",
        "site-local-loaders",
        "newsletter-subscription",
        "client-commerce-state-hooks",
        "session-regionalization"
      ],
      "docs": [],
      "pinned": false
    },
    {
      "id": "roadmap-docs--write-the-post-cutover-recipes",
      "group": "docs",
      "title": "Write the post-cutover recipes",
      "plan": "<p>Page-level data shared by a page's blocks, client data fetching, server-only upstream proxies, overriding an app's block, tabs over pre-resolved data, head tags from blocks, locale, and robots/static SEO files.</p>",
      "features": [
        "implicit-page-data-context",
        "product-comparison",
        "bff-custom-endpoints",
        "app-loader-overrides",
        "tabbed-shelf-partial",
        "head-tags-from-sections",
        "i18n-and-currency",
        "robots-and-static-seo-files"
      ],
      "docs": [],
      "pinned": false
    },
    {
      "id": "roadmap-docs--write-a-faststore-to-blocks-guide",
      "group": "docs",
      "title": "Write a FastStore-to-Blocks guide",
      "plan": "<p>Decision: port @faststore/core's UI components (FastStore calls them sections) into the site as vendored code, with no separate storefront UI kit; the commerce hooks they need come from apps-vtex. The guide covers the choice of App Router or TanStack Start (there's no Pages Router path), a core-internals → apps-vtex map, a fragment → loader map and the override → composition recipe.</p>",
      "features": [
        "faststore-cli-overlay",
        "design-system-library",
        "graphql-fragments-codegen",
        "native-section-overrides",
        "native-platform-sections",
        "framework-import-surface",
        "workspace-packages",
        "site-config-block"
      ],
      "docs": [],
      "pinned": false
    },
    {
      "id": "roadmap-docs--fix-the-next-guide",
      "group": "docs",
      "title": "Fix the Next guide",
      "plan": "<p>Mount <code>decoProxy()</code> instead of wiring the draft cookie by hand. Wrap <code>client()</code> in <code>cache()</code>, map the full PageSeo and degrade SEO errors instead of throwing, make release routes ISR/static, and cover transpilePackages and outputFileTracingIncludes.</p>",
      "features": [
        "global-layout-sections",
        "theming-and-styling",
        "page-seo-blocks",
        "hosting-and-deploy-config",
        "bundler-config",
        "faststore-cli-overlay",
        "edge-html-cache-profiles"
      ],
      "docs": [
        "nextjs"
      ],
      "pinned": false
    },
    {
      "id": "roadmap-docs--write-an-operations-page",
      "group": "docs",
      "title": "Finish the operations page",
      "plan": "<p><a href=\"#telemetry\">Telemetry</a> now covers what's sent and sampling, and <a href=\"#hosted\">The hosted Deco CMS</a> covers connecting a site. Still to add: (1) the DECO_SITE and DECO_SITE_TOKEN lifecycle (issue, rotate, scope) and a one-time warning when createCMS runs in production without them; (2) a per-host table of how the SDK schedules its once-a-minute check on Workers, Node and Vercel; (3) default bounds for every in-isolate cache and how to change them; (4) the 7.x → next env and binding table. Plus a CI job that resolves one block per app package in a Workers production build, to catch dynamic-import failures.</p>",
      "features": [
        "env-vars-and-secrets",
        "hosting-and-deploy-config",
        "in-isolate-caches",
        "upstream-fetch-instrumentation",
        "otel-observability",
        "legacy-runtime-leftovers",
        "shopify-autoconfig-workaround"
      ],
      "docs": [
        "telemetry",
        "hosted",
        "hosted-telemetry"
      ],
      "pinned": false
    },
    {
      "id": "roadmap-docs--add-notes-to-the-rendering-section",
      "group": "docs",
      "title": "Add notes to the Rendering page",
      "plan": "<p>Add four statements to <a href=\"#rendering\">Rendering</a>. Inline <code>&lt;script&gt;</code> in a streamed boundary runs on parse, before the boundary is revealed, so use effects. A legacy Lazy wrapper is unwrapped, so its block renders normally, with no client-side deferral. UI stores (drawers, minicart) are app code, created per request. Request-scope values are read during resolve and passed as props, because AsyncLocalStorage is gone by the time a streamed boundary renders.</p>",
      "features": [
        "interactive-ui-widgets",
        "core-patches",
        "global-ui-state-store",
        "lazy-deferred-sections",
        "device-detection-and-targeting"
      ],
      "docs": [
        "rendering"
      ],
      "pinned": false
    },
    {
      "id": "roadmap-docs--fill-in-the-schema-reference-details",
      "group": "docs",
      "title": "Fill in the schema reference details",
      "plan": "<p>Unions to anyOf with const discriminators, intersections and recursion, type resolution through packages, filename-to-entry encoding, and how a variant on a field of type T becomes one multivariate definition per T.</p>",
      "features": [
        "conditional-schema-fields",
        "cms-navigation-menus",
        "app-domain-types",
        "block-type-discriminator",
        "matchers-and-variants"
      ],
      "docs": [
        "schema"
      ],
      "pinned": false
    },
    {
      "id": "roadmap-later--add-real-user-monitoring",
      "group": "later",
      "title": "Add real-user monitoring",
      "plan": "<p>Real-user monitoring (RUM): what visitors experience in the browser, as a customer-facing alternative to Google Analytics, linked to A/B testing through variant exposure events. It's planned as a follow-up after the first release, not a release blocker, and it's being worked on. Its API isn't designed yet.</p>",
      "features": [
        "analytics-trackers",
        "ab-traffic-split",
        "ecommerce-analytics-events"
      ],
      "docs": [
        "matchers-and-variants",
        "analytics"
      ]
    },
    {
      "id": "roadmap-later--detect-conflicting-edits-in-studio",
      "group": "later",
      "title": "Detect conflicting edits in the site editor",
      "plan": "<p>The site editor's autosave is last-writer-wins in the first version of the protocol. Send each save with <code>ifMatch</code>, the version the form opened with, and show 'changed elsewhere: reload or overwrite' when it conflicts.</p>",
      "features": [
        "dev-studio-content-loop",
        "content-storage-and-delivery"
      ],
      "docs": [
        "studio-compatibility"
      ]
    },
    {
      "id": "roadmap-later--serve-the-content-protocol-from-the-dev-server",
      "group": "later",
      "title": "Serve the content protocol from the dev server",
      "plan": "<p>Add a few-line Vite plugin recipe to the templates that mounts the content protocol's handler in the app's own dev server, so editing the files on your machine from the site editor needs one process instead of two. <code>deco serve</code> stays for other setups.</p>",
      "features": [
        "dev-studio-content-loop",
        "bundler-config"
      ],
      "docs": [
        "content-protocol"
      ]
    },
    {
      "id": "roadmap-later--add-long-polling-for-faster-local-updates",
      "group": "later",
      "title": "Add long polling for faster local updates",
      "plan": "<p>An optional wait on the protocol's conditional reads, announced in <code>describe</code>, so a hand edit in the working tree shows in the site editor in a fraction of a second instead of on the next 2-second poll. One tab per endpoint would hold the request, so autosaves never queue behind it.</p>",
      "features": [
        "dev-studio-content-loop"
      ],
      "docs": [
        "studio-compatibility"
      ]
    }
  ],
  "sites": [
    {
      "id": "storefront-tanstack",
      "name": "TanStack storefront",
      "short": "storefront",
      "section": "roadmap-storefront",
      "description": "A storefront on TanStack Start and Cloudflare Workers, on the current <code>@decocms</code> 7.x packages.",
      "headline": "Closest to the next major. Before it can move, the proposed API needs successors for the pieces it relies on today: the worker entry (the Workers request handler that wraps the app), the edge cache, the page-URL plumbing and the endpoints the site editor calls.",
      "steps": [
        {
          "id": "roadmap-storefront--fix-dark-metrics-and-deco-invoke-404s",
          "kind": "now",
          "title": "Fix dark metrics and <code>/deco/invoke</code> 404s",
          "text": "<code>createInstrumentedFetch(\"shopify\")</code> has no onComplete, so upstream metrics are dark. Site loaders called through <code>/deco/invoke</code> 404.",
          "features": [
            "upstream-fetch-instrumentation",
            "site-local-loaders"
          ],
          "no_action": false
        },
        {
          "id": "roadmap-storefront--upgrade-7-20-7-to-the-latest-7-x",
          "kind": "pre",
          "title": "Upgrade 7.20.7 to the latest 7.x",
          "text": "The lockfile pins 7.20.7, which still sends <code>X-Frame-Options: SAMEORIGIN</code> and has no <code>?__draft</code> binding. Moving past #390 (7.20.10) and #442 (7.34.0) lets the site editor frame and preview the site today, and leaves one 7 → next step.",
          "features": [
            "csp-security-headers",
            "worker-server-entry",
            "studio-preview"
          ],
          "no_action": false
        },
        {
          "id": "roadmap-storefront--keep-studio-s-forms-previews-and-options-working-after-the-move",
          "kind": "blocker",
          "title": "Keep the site editor's forms, previews and <code>@options</code> working after the move",
          "text": "Next-major serves none of <code>/live/_meta</code>, <code>/live/previews</code> or <code>/deco/invoke</code>, and its schema file isn't what the site editor reads. Forms, previews, @options fields and the Loaders tab all stop.",
          "features": [
            "admin-protocol-endpoints",
            "studio-schema-generation",
            "codegen-pipeline"
          ],
          "no_action": false
        },
        {
          "id": "roadmap-storefront--drop-apps-shopify-s-decocms-tanstack-dependency-and-document-its-sdk-imports",
          "kind": "blocker",
          "title": "Drop <code>apps-shopify</code>'s <code>@decocms/tanstack</code> dependency and document its SDK imports",
          "text": "It imports <code>@decocms/blocks/sdk/{fetchTimeout,instrumentedFetch}</code> and depends on <code>@decocms/tanstack</code>.",
          "features": [
            "framework-import-surface",
            "commerce-platform-binding"
          ],
          "no_action": false
        },
        {
          "id": "roadmap-storefront--keep-the-edge-cache-and-a-b-split-without-createdecoworkerentry",
          "kind": "blocker",
          "title": "Keep the edge cache and A/B split without <code>createDecoWorkerEntry</code>",
          "text": "Workers Cache is on, with segment keys and degraded-page protection. The SITES_KV traffic split has no home.",
          "features": [
            "edge-html-cache-profiles",
            "ab-traffic-split"
          ],
          "no_action": false
        },
        {
          "id": "roadmap-storefront--keep-the-page-url-on-spa-navigation-plp-search-pdp",
          "kind": "blocker",
          "title": "Keep the page URL on SPA navigation (PLP, search, PDP)",
          "text": "Today's <code>x-deco-page-url</code> and <code>derivePageUrl</code> workarounds have no successor.",
          "features": [
            "section-loaders",
            "plp-filters-sort-pagination"
          ],
          "no_action": false
        },
        {
          "id": "roadmap-storefront--do-the-mechanical-port",
          "kind": "work",
          "title": "Do the mechanical port",
          "text": "Hand-write the block map (27 UI blocks, 12 loaders) with the path-shaped keys the site editor needs, add the ~8-line requestToParam shim (app code), and replace Image/Picture (~20 files). The content fixes below register types into this map.",
          "features": [
            "site-bootstrap",
            "section-registration-conventions",
            "image-optimization",
            "route-params-request-to-param"
          ],
          "no_action": false
        },
        {
          "id": "roadmap-storefront--fix-the-content-before-cutover",
          "kind": "content",
          "title": "Fix the content before cutover",
          "text": "Rewrite the Category page path <code>/*</code> to <code>/:collection</code>, guard the PDP's <code>seo: null</code> and hoist SEO blocks out of <code>sections[]</code>. Register in the block map, or remove, ~20 unknown types, including the <code>resolved</code> built-in that Header.json's searchbar uses. Fix the Newsletter and Header drift, and hold MIGRATION_NEXT_STEPS' <code>*Config</code> Props regrouping until a content validator exists.",
          "features": [
            "cms-page-routing",
            "commerce-seo-sections-in-sections",
            "orphan-and-dangling-blocks",
            "content-schema-drift"
          ],
          "no_action": false
        },
        {
          "id": "roadmap-storefront--unwrap-lazy-wrappers-and-stop-duplicate-loads",
          "kind": "content",
          "title": "Unwrap Lazy wrappers and stop duplicate loads",
          "text": "The alias bridge unwraps its 43 Lazy wrappers (21 with inline loaders): each wrapped block renders normally, with no client-side deferral. 'PLP Loader' is referenced twice per page and needs the per-client memo.",
          "features": [
            "lazy-deferred-sections",
            "inline-loader-props",
            "saved-loader-blocks"
          ],
          "no_action": false
        }
      ]
    },
    {
      "id": "blog-tanstack",
      "name": "TanStack blog",
      "short": "blog",
      "section": "roadmap-blog",
      "description": "A blog on TanStack Start and Cloudflare Workers, on an older single-package version of the framework.",
      "headline": "Simple content and a clean route tree, but it's on an older package: it upgrades to 7.x first (until then, the site editor can't open it in its preview frame), and its site editor blog workflow depends on old loader names and <code>apps-blog</code>.",
      "steps": [
        {
          "id": "roadmap-blog--upgrade-6-12-to-the-latest-7-x-first",
          "kind": "pre",
          "title": "Upgrade 6.12 to the latest 7.x first",
          "text": "Fix <code>upgrade-6-to-7</code> for the blog's specifiers (~45 symbols across 13 subpaths), then run it. That also drops <code>X-Frame-Options: SAMEORIGIN</code> and adds <code>?__draft</code>, so the site editor can frame the site. The move to next is then the same 7 → next step as the storefront's.",
          "features": [
            "framework-import-surface",
            "codegen-pipeline",
            "csp-security-headers",
            "worker-server-entry"
          ],
          "no_action": false
        },
        {
          "id": "roadmap-blog--untie-the-blog-tab-from-legacy-names-and-apps-blog",
          "kind": "blocker",
          "title": "Untie the Blog tab from legacy names and <code>apps-blog</code>",
          "text": "It keys off <code>blog/loaders/*.ts</code> and <code>@decocms/apps-blog</code> ≥ 7.53.0, but the site is on <code>@decocms/apps</code> 5.2.0. Its meta sits at <code>src/server/admin/meta.gen.json</code> with Windows-path keys, and <code>setInvokeLoaders</code> exists only for the site editor.",
          "features": [
            "key-prefix-content-collections",
            "studio-schema-generation",
            "admin-protocol-endpoints",
            "app-domain-types"
          ],
          "no_action": false
        },
        {
          "id": "roadmap-blog--stop-blog-loaders-reading-the-global-decofile",
          "kind": "blocker",
          "title": "Stop blog loaders reading the global decofile",
          "text": "apps-blog's <code>getRecordsByPath</code> uses <code>loadBlocks()</code>, so under a pure CMS drafts read the wrong revision. It needs the request scope's <code>client</code>, or the lookups move to route code.",
          "features": [
            "key-prefix-content-collections",
            "inline-loader-props",
            "page-seo-blocks"
          ],
          "no_action": false
        },
        {
          "id": "roadmap-blog--keep-the-live-publish-path",
          "kind": "blocker",
          "title": "Keep the live publish path",
          "text": "<code>regen-blocks.yml</code> (site editor publish, then a regen commit, then a Workers Build) is how content ships today. Keep it until the release service and <code>remoteLoader</code> exist.",
          "features": [
            "ci-and-release-workflows",
            "content-storage-and-delivery"
          ],
          "no_action": false
        },
        {
          "id": "roadmap-blog--return-real-404s-for-unknown-urls-and-stop-routing-404-publicly",
          "kind": "content",
          "title": "Return real 404s for unknown URLs and stop routing <code>/404</code> publicly",
          "text": "The 7 pages form a valid trie, but unknown URLs are soft 200s via <code>/:slug</code> and <code>/404</code> is publicly routable.",
          "features": [
            "not-found-and-error-pages",
            "cms-page-routing"
          ],
          "no_action": false
        },
        {
          "id": "roadmap-blog--alias-legacy-types-and-render-body-blocks",
          "kind": "content",
          "title": "Alias legacy types and render body blocks",
          "text": "25 legacy types (210 occurrences) need aliases, and body blocks become a data-only block with a renderer.",
          "features": [
            "block-type-discriminator",
            "post-body-block-renderer"
          ],
          "no_action": false
        },
        {
          "id": "roadmap-blog--handle-images-the-edge-profile-and-app-config",
          "kind": "work",
          "title": "Handle images, the edge profile and app config",
          "text": "The blog uses plain <code>&lt;img&gt;</code>, no edge profile fits posts, and app config goes through apps-blog.",
          "features": [
            "image-optimization",
            "edge-html-cache-profiles",
            "app-installation-and-store-config"
          ],
          "no_action": false
        },
        {
          "id": "roadmap-blog--two-current-bugs-go-away-with-the-migration",
          "kind": "fix",
          "title": "Two current bugs go away with the migration",
          "text": "One is in the layout cache, the other in the build-time site-globals snapshot, which never reaches draft preview. The migration replaces both.",
          "features": [
            "in-isolate-caches",
            "build-time-site-globals-snapshot"
          ],
          "no_action": true
        }
      ]
    },
    {
      "id": "faststore",
      "name": "Next.js storefront",
      "short": "Next.js store",
      "section": "roadmap-faststore",
      "description": "A Next.js storefront on another CMS. Not on Deco today, so this is a re-platform, not an upgrade.",
      "headline": "A re-platform, not an upgrade. The target framework, the content, FastStore's native sections and the session model all come before the missing pieces in the proposed API even come into play.",
      "steps": [
        {
          "id": "roadmap-faststore--pick-the-target-framework-and-move-to-it",
          "kind": "blocker",
          "title": "Pick the target framework and move to it",
          "text": "Pick the target first: the App Router (the only guide; the Pages Router has none) or TanStack Start. It decides how FastStore's native sections get rebuilt. Its code imports <code>@faststore/core</code> internals, and most of its hook files, and the files that use its translation hook, lack <code>\"use client\"</code>.",
          "features": [
            "faststore-cli-overlay",
            "framework-import-surface",
            "design-system-library",
            "i18n-and-currency"
          ],
          "no_action": false
        },
        {
          "id": "roadmap-faststore--bring-the-content-in-from-vtex-headless-cms",
          "kind": "blocker",
          "title": "Bring the content in from VTEX Headless CMS",
          "text": "The content lives in VTEX Headless CMS. The Headless CMS-to-<code>.deco/blocks</code> importer is handled outside this roadmap, the JSONC schemas must become TS types, and per-page-type whitelists of allowed blocks and singletons have no TS or site editor expression.",
          "features": [
            "faststore-cli-overlay",
            "content-storage-and-delivery",
            "block-type-discriminator",
            "per-page-section-whitelists",
            "page-template-targeting"
          ],
          "no_action": false
        },
        {
          "id": "roadmap-faststore--rebuild-what-faststore-core-renders",
          "kind": "blocker",
          "title": "Rebuild what <code>@faststore/core</code> renders",
          "text": "Its native sections (FastStore's UI components), including the stateful cart, search and session ones, the global ones (header, footer) and the 404/500 pages. No UI kit exists.",
          "features": [
            "native-platform-sections",
            "native-section-overrides",
            "global-layout-sections"
          ],
          "no_action": false
        },
        {
          "id": "roadmap-faststore--give-apps-vtex-session-region-and-delivery-promise",
          "kind": "blocker",
          "title": "Give <code>apps-vtex</code> session, region and delivery promise",
          "text": "Without a populated request scope, regionalized Intelligent Search queries also silently fall back to the default region.",
          "features": [
            "session-regionalization",
            "delivery-promise",
            "commerce-platform-binding"
          ],
          "no_action": false
        },
        {
          "id": "roadmap-faststore--rewrite-the-data-shapes-to-schema-org",
          "kind": "content",
          "title": "Rewrite the data shapes to schema.org",
          "text": "FastStore GraphQL moves to apps-commerce schema.org: the PDP view model, the product card, the BFF fields, tax prices and installments.",
          "features": [
            "code-composed-pdp",
            "product-card",
            "graphql-schema-extensions",
            "bff-custom-endpoints"
          ],
          "no_action": false
        },
        {
          "id": "roadmap-faststore--port-client-state-and-the-core-patches",
          "kind": "work",
          "title": "Port client state and the core patches",
          "text": "The useSearch state machine and the client fetches become server functions. The behaviours the core patches add are re-created as app code.",
          "features": [
            "plp-filters-sort-pagination",
            "graphql-fragments-codegen",
            "core-patches"
          ],
          "no_action": false
        },
        {
          "id": "roadmap-faststore--rename-componentkey-values-to-path-shaped-keys-and-avoid-type-name-collisions",
          "kind": "fix",
          "title": "Rename <code>$componentKey</code> values to path-shaped keys and avoid type-name collisions",
          "text": "There's no legacy Deco content, so no legacy-type aliases. The content import (handled outside this roadmap) must still rename <code>$componentKey</code> values (Navbar, Footer, Alert…) to path-shaped keys until the site editor does manifest lookups, and avoid entry names that collide with type names. The content protocol and schema fixes ({{item:roadmap-api--publish-the-content-protocol-and-its-package}}, {{item:roadmap-cli--write-a-studio-compatible-schema-file}}) gate editing.",
          "features": [
            "studio-schema-generation",
            "studio-preview",
            "block-type-discriminator"
          ],
          "no_action": false
        }
      ]
    }
  ],
  "categories": [
    {
      "id": "routing",
      "title": "Routing",
      "description": "URL routing, page matching, route params, not-found handling, non-CMS routes, redirects and client navigation."
    },
    {
      "id": "content-model",
      "title": "Content model",
      "description": "How content is shaped: block types, saved entries (named or global), collections, props, rich text, editor widgets, drift and targeting rules."
    },
    {
      "id": "sections",
      "title": "UI blocks",
      "description": "Registering blocks that render UI, the site editor's block catalog, overrides of platform-native components, and lazy, deferred or viewport-gated rendering."
    },
    {
      "id": "data",
      "title": "Data",
      "description": "Loaders, query props, legacy section loaders (server-side prop enrichment), invoke/actions/BFF endpoints and search, plus site overrides of app loaders."
    },
    {
      "id": "commerce",
      "title": "Commerce",
      "description": "Commerce platform bindings (Shopify, VTEX), cart, session, regionalization, product merchandising and commerce-specific GraphQL."
    },
    {
      "id": "seo",
      "title": "SEO",
      "description": "Page and site SEO, head tags, JSON-LD, sitemap and robots."
    },
    {
      "id": "interactivity",
      "title": "Interactivity",
      "description": "Client-side UI behaviour and client state: widgets, tabs, PLP navigation, client commerce hooks and UI stores."
    },
    {
      "id": "caching",
      "title": "Caching",
      "description": "Edge/HTML cache profiles, in-isolate and client caches, and request context (cookies, headers, device)."
    },
    {
      "id": "studio",
      "title": "Site editor",
      "description": "Admin/site editor protocol, preview, schema generation or upload, and the local dev-to-site-editor content loop."
    },
    {
      "id": "config",
      "title": "Config",
      "description": "Site bootstrap, site and app config blocks, app autoconfig, environment variables and secrets."
    },
    {
      "id": "runtime-deploy",
      "title": "Runtime & deploy",
      "description": "Worker/server entry, security headers, traffic splitting, hosting, content storage and delivery, and CI/release."
    },
    {
      "id": "build",
      "title": "Build",
      "description": "Codegen, bundler config, framework import surface, patches, dev tooling, tests and repo hygiene."
    },
    {
      "id": "design",
      "title": "Design",
      "description": "Theming, fonts, icons, images, design-system libraries and i18n."
    },
    {
      "id": "observability-analytics",
      "title": "Observability & analytics",
      "description": "OTel and logging, upstream fetch instrumentation, analytics trackers and e-commerce events."
    }
  ],
  "features": {
    "cms-page-routing": {
      "name": "CMS pages matched by URL pattern",
      "category": "routing",
      "status": "to-finish",
      "effort": "M",
      "sites": [
        "storefront-tanstack",
        "blog-tanstack",
        "faststore"
      ],
      "summary": "Paths take only literal segments and single-segment params, so the storefront's `/*` Category page never matches. One site editor commit can make `matchRoute` throw on every request, and `Page.seo` is required while stored content holds `seo: null`.",
      "docs": [
        "routing",
        "router-internals",
        "renames-and-migrations",
        "api-reference",
        "nextjs",
        "tanstack-start-descriptors",
        "studio-compatibility",
        "releases-and-deployment"
      ],
      "confidence": "high",
      "first_rated": null,
      "rated_before_review": null,
      "unconfirmed_sub_claim": false
    },
    "home-route-override": {
      "name": "Home route override (search params, page-URL header, client preload)",
      "category": "routing",
      "status": "to-finish",
      "effort": "S",
      "sites": [
        "storefront-tanstack"
      ],
      "summary": "The guide's `/$` route has no validateSearch or loaderDeps, and on `<Link>` navigation `getRequest()` returns the `/_serverFn/...` RPC URL, so search params and the page URL never reach blocks.",
      "docs": [
        "tanstack-start-descriptors",
        "router-internals",
        "blocks",
        "nextjs"
      ],
      "confidence": "high",
      "first_rated": null,
      "rated_before_review": null,
      "unconfirmed_sub_claim": false
    },
    "route-params-request-to-param": {
      "name": "Route params bound via requestToParam",
      "category": "routing",
      "status": "site-code",
      "effort": "S",
      "sites": [
        "storefront-tanstack",
        "blog-tanstack"
      ],
      "summary": "The CMS never injects `match.params`, so every migrated site repeats the same ~8-line AsyncLocalStorage shim, and every `resolve()` must start inside it. The guides don't show it.",
      "docs": [
        "routing",
        "blocks",
        "design-decisions",
        "renames-and-migrations"
      ],
      "confidence": "high",
      "first_rated": null,
      "rated_before_review": null,
      "unconfirmed_sub_claim": false
    },
    "not-found-and-error-pages": {
      "name": "Not-found and error pages",
      "category": "routing",
      "status": "to-finish",
      "effort": "M",
      "sites": [
        "storefront-tanstack",
        "blog-tanstack",
        "faststore"
      ],
      "summary": "Only URL-level not-found works as documented. Unknown single-segment URLs match a template and stream as cacheable soft 200s, and there's no story for CMS-authored 404 and 500 pages.",
      "docs": [
        "routing",
        "nextjs",
        "tanstack-start-descriptors",
        "rendering"
      ],
      "confidence": "high",
      "first_rated": null,
      "rated_before_review": null,
      "unconfirmed_sub_claim": false
    },
    "non-cms-routes-and-account-pages": {
      "name": "Code-only routes, account/login pages and external checkout",
      "category": "routing",
      "status": "site-code",
      "effort": "S",
      "sites": [
        "storefront-tanstack",
        "faststore"
      ],
      "summary": "Code routes stay in the framework's router, so account and login pages are app code. After FastStore, `/pvt/account` falls into the CMS catch-all and 404s unless the account link points at the VTEX-hosted account.",
      "docs": [
        "design-decisions",
        "routing"
      ],
      "confidence": "high",
      "first_rated": null,
      "rated_before_review": null,
      "unconfirmed_sub_claim": false
    },
    "router-and-client-navigation": {
      "name": "Router setup, Link and client navigation",
      "category": "routing",
      "status": "to-finish",
      "effort": "M",
      "sites": [
        "storefront-tanstack",
        "blog-tanstack",
        "faststore"
      ],
      "summary": "`createDecoRouter` carries three behaviours these docs never mention: URLSearchParams search serializers, the CSP nonce, and scroll and intent defaults. The TanStack guide never appends the draft cookie, so preview is lost on the first in-frame `<Link>` click.",
      "docs": [
        "tanstack-start-descriptors",
        "nextjs",
        "releases-and-drafts",
        "renames-and-migrations"
      ],
      "confidence": "high",
      "first_rated": null,
      "rated_before_review": null,
      "unconfirmed_sub_claim": false
    },
    "pwa-offline-static-assets": {
      "name": "Offline page, service worker and public static assets",
      "category": "routing",
      "status": "site-code",
      "effort": "S",
      "sites": [
        "storefront-tanstack"
      ],
      "summary": "Static assets are served before the CMS catch-all and `/offline` is an ordinary Route entry; registering `sw.js` and the web manifest takes about 3 lines. Any CSP must allow the Workbox import, and `/offline` must never be a draft render.",
      "docs": [
        "routing"
      ],
      "confidence": "high",
      "first_rated": null,
      "rated_before_review": null,
      "unconfirmed_sub_claim": false
    },
    "redirects": {
      "name": "Redirects and route handlers",
      "category": "routing",
      "status": "to-finish",
      "effort": "S",
      "sites": [
        "storefront-tanstack",
        "faststore"
      ],
      "summary": "`/old/*` globs are impossible with single-segment params, the site editor's 307 has no counterpart (only 301 or 302), and query, case and duplicate-`from` handling are unspecified. A naive legacy alias doesn't match the nested shape the site editor stores.",
      "docs": [
        "routing",
        "router-internals",
        "nextjs",
        "tanstack-start-descriptors",
        "studio-compatibility",
        "renames-and-migrations"
      ],
      "confidence": "high",
      "first_rated": null,
      "rated_before_review": null,
      "unconfirmed_sub_claim": false
    },
    "page-template-targeting": {
      "name": "PDP/PLP templates targeted by product slug or category path",
      "category": "routing",
      "status": "to-finish",
      "effort": "L",
      "sites": [
        "faststore"
      ],
      "summary": "The entries live in VTEX Headless CMS, so converting them to `.deco/blocks` comes first. There's no multi-segment catch-all, so category trees need one entry per depth, and [Routing](#routing) and [Router internals](#router-internals) disagree on when two templates are ambiguous.",
      "docs": [
        "routing",
        "router-internals",
        "api-reference"
      ],
      "confidence": "medium",
      "first_rated": null,
      "rated_before_review": null,
      "unconfirmed_sub_claim": false
    },
    "block-type-discriminator": {
      "name": "Block type naming (__resolveType / $componentKey)",
      "category": "content-model",
      "status": "to-finish",
      "effort": "M",
      "sites": [
        "storefront-tanstack",
        "blog-tanstack",
        "faststore"
      ],
      "summary": "Aliases are documented, but the filename-to-entry-name rule isn't (the site editor writes every key through `encodeURIComponent`), the site editor still classifies keys by their path shape, and an unregistered legacy type now fails its parent block with UNKNOWN_BLOCK.",
      "docs": [
        "quickstart",
        "blocks",
        "renames-and-migrations",
        "tanstack-start-descriptors",
        "studio-compatibility",
        "design-decisions"
      ],
      "confidence": "high",
      "first_rated": "done",
      "rated_before_review": null,
      "unconfirmed_sub_claim": false
    },
    "global-layout-sections": {
      "name": "Global/shared layout blocks (header, footer)",
      "category": "content-model",
      "status": "to-finish",
      "effort": "M",
      "sites": [
        "storefront-tanstack",
        "blog-tanstack",
        "faststore"
      ],
      "summary": "Saved entries with references cover the two TanStack sites, but FastStore's global singletons have no equivalent. A layout and its page can get two clients on different revisions, and Lazy-wrapped headers and footers are unwrapped and render with no client-side deferral.",
      "docs": [
        "blocks",
        "caching",
        "nextjs",
        "releases-and-deployment",
        "rendering",
        "design-decisions"
      ],
      "confidence": "medium",
      "first_rated": "done",
      "rated_before_review": null,
      "unconfirmed_sub_claim": false
    },
    "saved-loader-blocks": {
      "name": "Saved loader blocks and commerce extension wrappers",
      "category": "content-model",
      "status": "to-finish",
      "effort": "M",
      "sites": [
        "storefront-tanstack"
      ],
      "summary": "Per-request dedupe is only implied: no cache key, and nothing on shared in-flight promises. Pages that reference 'PLP Loader' or 'PDP Loader' twice would double their Shopify calls, and a failing loader now fails its whole block.",
      "docs": [
        "blocks",
        "routing",
        "api-reference",
        "troubleshooting",
        "studio-compatibility"
      ],
      "confidence": "medium",
      "first_rated": null,
      "rated_before_review": null,
      "unconfirmed_sub_claim": false
    },
    "orphan-and-dangling-blocks": {
      "name": "Unreferenced blocks and dangling references",
      "category": "content-model",
      "status": "to-finish",
      "effort": "S",
      "sites": [
        "storefront-tanstack",
        "blog-tanstack"
      ],
      "summary": "A dangling reference used to return null; now it fails the parent block, and one in `seo` throws. There's no validation tool, and one reading of the release rule would skip every storefront release because of ~20 unregistered types.",
      "docs": [
        "blocks",
        "api-reference",
        "renames-and-migrations",
        "releases-and-deployment",
        "rendering"
      ],
      "confidence": "high",
      "first_rated": null,
      "rated_before_review": null,
      "unconfirmed_sub_claim": false
    },
    "content-schema-drift": {
      "name": "Content drift from current props / schemas",
      "category": "content-model",
      "status": "to-finish",
      "effort": "M",
      "sites": [
        "storefront-tanstack",
        "blog-tanstack",
        "faststore"
      ],
      "summary": "Stored inputs pass through unvalidated, and no documented tool does the 'validate saved content' step. Drift is already live: the storefront's Newsletter content stores a flat shape its Props no longer read.",
      "docs": [
        "schema",
        "api-reference",
        "renames-and-migrations",
        "releases-and-deployment"
      ],
      "confidence": "high",
      "first_rated": null,
      "rated_before_review": null,
      "unconfirmed_sub_claim": false
    },
    "rich-text-html-props": {
      "name": "Rich text / raw HTML props",
      "category": "content-model",
      "status": "to-finish",
      "effort": "S",
      "sites": [
        "storefront-tanstack",
        "blog-tanstack",
        "faststore"
      ],
      "summary": "Only `@format rich-text` is documented; `html`, `rich-text-inline`, `textarea`, `markdown`, alias-name detection and sanitization aren't, and FastStore's Lexical fields need converting.",
      "docs": [
        "routing",
        "schema",
        "rendering"
      ],
      "confidence": "medium",
      "first_rated": null,
      "rated_before_review": "done",
      "unconfirmed_sub_claim": false
    },
    "nested-section-props": {
      "name": "Block-typed props (nested blocks)",
      "category": "content-model",
      "status": "to-finish",
      "effort": "S",
      "sites": [
        "storefront-tanstack"
      ],
      "summary": "The rule is documented: a field typed as the resolved value (`product: Product`, `sections: ReactNode[]`) takes any block whose function returns that type (`T` or `Promise<T>`). The CLI doesn't emit block pickers from return types yet; today's schema uses the site editor's legacy `__SECTION_REF__`. Nested blocks also resolve eagerly, with no Suspense boundary of their own.",
      "docs": [
        "blocks",
        "rendering",
        "api-reference"
      ],
      "confidence": "medium",
      "first_rated": null,
      "rated_before_review": null,
      "unconfirmed_sub_claim": false
    },
    "key-prefix-content-collections": {
      "name": "Content collections read by key-prefix scan",
      "category": "content-model",
      "status": "to-finish",
      "effort": "M",
      "sites": [
        "blog-tanstack"
      ],
      "summary": "Block functions get no access to the content they're resolved against, so apps-blog's loaders, which read the global `loadBlocks()`, must be rewritten around a client the app provides.",
      "docs": [
        "api-reference",
        "schema",
        "blocks",
        "routing"
      ],
      "confidence": "medium",
      "first_rated": null,
      "rated_before_review": null,
      "unconfirmed_sub_claim": false
    },
    "denormalized-collection-references": {
      "name": "Author/category collections with denormalized copies",
      "category": "content-model",
      "status": "site-code",
      "effort": "S",
      "sites": [
        "blog-tanstack"
      ],
      "summary": "The in-memory join is trivial for 4 authors and 5 categories, but it depends on the same unsolved client access as key-prefix collections.",
      "docs": [
        "api-reference",
        "blocks",
        "schema"
      ],
      "confidence": "medium",
      "first_rated": null,
      "rated_before_review": null,
      "unconfirmed_sub_claim": false
    },
    "post-body-block-renderer": {
      "name": "Nested post-body blocks rendered by a site map",
      "category": "content-model",
      "status": "site-code",
      "effort": "S",
      "sites": [
        "blog-tanstack"
      ],
      "summary": "Listing posts with `run: true`, or resolving one, turns every body block into UNKNOWN_BLOCK and fails the post; registering the types as functions loses the TOC's raw input. Rendering the stored blocks from a site-owned map works.",
      "docs": [
        "blocks",
        "api-reference"
      ],
      "confidence": "high",
      "first_rated": null,
      "rated_before_review": null,
      "unconfirmed_sub_claim": false
    },
    "per-page-section-whitelists": {
      "name": "Per-page-type block whitelists",
      "category": "content-model",
      "status": "to-build",
      "effort": "M",
      "sites": [
        "faststore"
      ],
      "summary": "A page's `sections: ReactNode[]` takes every block function that returns JSX; limiting a page type to some of them isn't designed yet. The site editor's add-section catalog (its legacy name) is the union of all UI blocks anyway.",
      "docs": [
        "routing",
        "api-reference",
        "schema",
        "studio-compatibility"
      ],
      "confidence": "high",
      "first_rated": null,
      "rated_before_review": null,
      "unconfirmed_sub_claim": false
    },
    "page-level-settings-fields": {
      "name": "Page-level settings outside the block list",
      "category": "content-model",
      "status": "to-finish",
      "effort": "S",
      "sites": [
        "faststore"
      ],
      "summary": "The site editor edits only a page's name, path, SEO and block list, so typed page settings get no form. Only one page type can carry the `website/pages/Page.tsx` alias.",
      "docs": [
        "routing",
        "schema",
        "studio-compatibility"
      ],
      "confidence": "medium",
      "first_rated": null,
      "rated_before_review": null,
      "unconfirmed_sub_claim": false
    },
    "cms-navigation-menus": {
      "name": "CMS-authored navigation and menus",
      "category": "content-model",
      "status": "to-finish",
      "effort": "M",
      "sites": [
        "storefront-tanstack",
        "blog-tanstack",
        "faststore"
      ],
      "summary": "Navigation is a data-only block, so a block can reference a saved menu entry. Nothing says whether `deco schema` handles the intersections and recursion in `SiteNavigationElement`.",
      "docs": [
        "blocks",
        "routing",
        "schema"
      ],
      "confidence": "medium",
      "first_rated": "done",
      "rated_before_review": null,
      "unconfirmed_sub_claim": false
    },
    "conditional-schema-fields": {
      "name": "Discriminated-union fields (dependencies + oneOf)",
      "category": "content-model",
      "status": "to-finish",
      "effort": "S",
      "sites": [
        "faststore"
      ],
      "summary": "Union handling isn't documented, so this relies on today's generator carrying over (literals to const, object unions to anyOf). JSON Schema `dependencies` has no TS form, and the array constraint tags aren't listed.",
      "docs": [
        "schema",
        "quickstart"
      ],
      "confidence": "medium",
      "first_rated": null,
      "rated_before_review": null,
      "unconfirmed_sub_claim": false
    },
    "editor-widgets-and-image-fields": {
      "name": "Editor widgets and image/media fields",
      "category": "content-model",
      "status": "to-finish",
      "effort": "M",
      "sites": [
        "storefront-tanstack",
        "blog-tanstack",
        "faststore"
      ],
      "summary": "Image and HTML fields depend on type-alias names these docs never mention. If `deco schema` drops alias detection, all 22 image pickers and the HTML editors become plain text inputs, and the widget vocabulary isn't listed.",
      "docs": [
        "quickstart",
        "routing",
        "schema"
      ],
      "confidence": "medium",
      "first_rated": null,
      "rated_before_review": null,
      "unconfirmed_sub_claim": false
    },
    "icon-by-name-enums": {
      "name": "Icon-by-name enums and code-embedded logo",
      "category": "design",
      "status": "to-finish",
      "effort": "M",
      "sites": [
        "faststore"
      ],
      "summary": "Union-to-enum, `@format` passthrough and widget tags aren't documented, so icon and image fields may degrade to text, and enum display labels can't be expressed. The content must first be exported from VTEX.",
      "docs": [
        "quickstart",
        "routing",
        "blocks",
        "studio-compatibility",
        "renames-and-migrations"
      ],
      "confidence": "high",
      "first_rated": null,
      "rated_before_review": null,
      "unconfirmed_sub_claim": false
    },
    "matchers-and-variants": {
      "name": "Matchers, variants and personalization",
      "category": "content-model",
      "status": "to-finish",
      "effort": "M",
      "sites": [
        "storefront-tanstack",
        "blog-tanstack",
        "faststore"
      ],
      "summary": "Every variant resolves, so a block the site editor hides with a never rule still runs its loaders, and its `undefined` renders 'temporarily unavailable'. Device and random matchers no longer ship, and which site editor matcher paths get aliases is unstated.",
      "docs": [
        "matchers-and-variants",
        "studio-compatibility",
        "tanstack-start-descriptors"
      ],
      "confidence": "medium",
      "first_rated": "site-code",
      "rated_before_review": null,
      "unconfirmed_sub_claim": false
    },
    "per-path-section-visibility": {
      "name": "Per-path block visibility",
      "category": "content-model",
      "status": "site-code",
      "effort": "S",
      "sites": [
        "faststore"
      ],
      "summary": "This is component logic. A server-side check in a Next layout won't re-evaluate on client navigation, and `router.asPath` includes the query string, so compare pathnames.",
      "docs": [
        "blocks",
        "matchers-and-variants",
        "rendering"
      ],
      "confidence": "high",
      "first_rated": null,
      "rated_before_review": null,
      "unconfirmed_sub_claim": false
    },
    "device-detection-and-targeting": {
      "name": "Device detection and device targeting",
      "category": "caching",
      "status": "to-finish",
      "effort": "M",
      "sites": [
        "storefront-tanstack",
        "blog-tanstack",
        "faststore"
      ],
      "summary": "Detection is app code, but device as a cache-key dimension, hydration of request-derived values and the site editor's device and variant preview are unaddressed. The site editor's variant tabs can't force a variant through the built-in multivariate.",
      "docs": [
        "matchers-and-variants",
        "design-decisions",
        "blocks",
        "tanstack-start-descriptors",
        "studio-compatibility",
        "renames-and-migrations"
      ],
      "confidence": "high",
      "first_rated": null,
      "rated_before_review": null,
      "unconfirmed_sub_claim": false
    },
    "section-registration-conventions": {
      "name": "UI block registration and file conventions",
      "category": "sections",
      "status": "to-finish",
      "effort": "M",
      "sites": [
        "storefront-tanstack",
        "blog-tanstack",
        "faststore"
      ],
      "summary": "Registration works, but none of the 8 legacy convention flags or section loaders has a home, so each site rebuilds the ~737-line DecoPageRenderer. And `getRequest()` gives URL-dependent loaders the wrong URL on SPA navigation.",
      "docs": [
        "blocks",
        "tanstack-start-descriptors",
        "rendering",
        "renames-and-migrations",
        "caching",
        "api-reference",
        "studio-compatibility",
        "design-decisions"
      ],
      "confidence": "high",
      "first_rated": null,
      "rated_before_review": null,
      "unconfirmed_sub_claim": false
    },
    "section-catalog": {
      "name": "Block catalog exposed to editors (the site editor's section catalog)",
      "category": "sections",
      "status": "to-finish",
      "effort": "M",
      "sites": [
        "storefront-tanstack",
        "blog-tanstack",
        "faststore"
      ],
      "summary": "The catalog works for legacy path-keyed (site/sections/…) blocks, but thumbnails and in-place render go through `/live/previews`, which these docs don't define. The alias bridge skips Lazy and the Seo* types, and a JSX type can't be kept out of the catalog.",
      "docs": [
        "studio-compatibility",
        "schema",
        "routing",
        "api-reference",
        "renames-and-migrations",
        "design-decisions"
      ],
      "confidence": "medium",
      "first_rated": null,
      "rated_before_review": null,
      "unconfirmed_sub_claim": false
    },
    "native-section-overrides": {
      "name": "Overrides of platform-native components",
      "category": "sections",
      "status": "to-finish",
      "effort": "M",
      "sites": [
        "faststore"
      ],
      "summary": "Overrides collapse into plain composition, but the core components, contexts and SDK hooks they rely on must be rebuilt. There's no page-level data context, and keys like 'Footer' can collide with saved-entry names.",
      "docs": [
        "blocks",
        "rendering",
        "renames-and-migrations",
        "troubleshooting",
        "design-decisions"
      ],
      "confidence": "medium",
      "first_rated": null,
      "rated_before_review": null,
      "unconfirmed_sub_claim": false
    },
    "native-platform-sections": {
      "name": "Native platform components used via schema only",
      "category": "sections",
      "status": "to-build",
      "effort": "L",
      "sites": [
        "faststore"
      ],
      "summary": "Nothing in the next major renders UI without site code, and UI stays out of `@decocms/apps`. Replacing the core components, the stateful ones included, is real engineering, and FastStore's global-section (its term) and 404/500 content types have no layout pattern.",
      "docs": [
        "design-decisions",
        "api-reference",
        "studio-compatibility",
        "routing"
      ],
      "confidence": "high",
      "first_rated": null,
      "rated_before_review": null,
      "unconfirmed_sub_claim": false
    },
    "code-composed-pdp": {
      "name": "Code-composed monolithic PDP block",
      "category": "sections",
      "status": "to-finish",
      "effort": "M",
      "sites": [
        "faststore"
      ],
      "summary": "The block-function part works, but a PDP block has no documented way to get route params, and it can't signal a 404 once a template route matches. Its view model also parses FastStore GraphQL shapes.",
      "docs": [
        "rendering",
        "blocks",
        "routing",
        "api-reference",
        "nextjs",
        "troubleshooting"
      ],
      "confidence": "medium",
      "first_rated": "done",
      "rated_before_review": null,
      "unconfirmed_sub_claim": false
    },
    "product-card": {
      "name": "Product card organism and CMS card config",
      "category": "commerce",
      "status": "to-finish",
      "effort": "M",
      "sites": [
        "faststore"
      ],
      "summary": "Server-side product inputs and typed card config work as documented, but apps-vtex has no tax-inclusive prices, full installment ladders or delivery badges",
      "docs": [
        "blocks",
        "schema",
        "rendering",
        "routing",
        "studio-compatibility"
      ],
      "confidence": "medium",
      "first_rated": null,
      "rated_before_review": null,
      "unconfirmed_sub_claim": false
    },
    "lazy-deferred-sections": {
      "name": "Lazy / deferred / viewport-gated blocks",
      "category": "sections",
      "status": "to-finish",
      "effort": "L",
      "sites": [
        "storefront-tanstack",
        "blog-tanstack",
        "faststore"
      ],
      "summary": "A legacy Lazy wrapper's `section` input resolves fully before Lazy runs, so Lazy defers nothing (43 wrappers on the storefront). The alias bridge unwraps legacy Lazy/SingleDeferred/Deferred wrappers, so the wrapped block renders normally, with no client-side deferral; today's per-prop deferral has no counterpart, and none is planned. Viewport-gated second hops need a revision the client doesn't expose.",
      "docs": [
        "blocks",
        "rendering",
        "tanstack-start-descriptors",
        "matchers-and-variants",
        "releases-and-deployment",
        "api-reference",
        "design-decisions"
      ],
      "confidence": "high",
      "first_rated": null,
      "rated_before_review": null,
      "unconfirmed_sub_claim": false
    },
    "section-loaders": {
      "name": "Prop-enrichment loaders (legacy section loaders)",
      "category": "data",
      "status": "to-finish",
      "effort": "M",
      "sites": [
        "storefront-tanstack",
        "blog-tanstack"
      ],
      "summary": "The documented request sources don't give blocks the page URL: on TanStack SPA navigation `getRequest()` returns the `/_serverFn` URL, Next's `headers()` has none, and `match.params` has no path to blocks or app packages.",
      "docs": [
        "blocks",
        "rendering",
        "tanstack-start-descriptors",
        "nextjs",
        "routing",
        "caching",
        "studio-compatibility",
        "renames-and-migrations"
      ],
      "confidence": "high",
      "first_rated": null,
      "rated_before_review": null,
      "unconfirmed_sub_claim": true
    },
    "tabbed-shelf-partial": {
      "name": "Tabbed shelf / server partial re-render",
      "category": "interactivity",
      "status": "site-code",
      "effort": "S",
      "sites": [
        "storefront-tanstack"
      ],
      "summary": "Client-side tabs over pre-resolved data are one `useState`. Fetching only the active tab would need a server partial re-render and a re-resolve at a pinned revision, which the proposed API lacks.",
      "docs": [
        "blocks",
        "matchers-and-variants",
        "rendering",
        "tanstack-start-descriptors",
        "nextjs",
        "renames-and-migrations",
        "api-reference",
        "studio-compatibility"
      ],
      "confidence": "high",
      "first_rated": null,
      "rated_before_review": null,
      "unconfirmed_sub_claim": true
    },
    "inline-loader-props": {
      "name": "Loaders and query descriptors in block props",
      "category": "data",
      "status": "to-finish",
      "effort": "L",
      "sites": [
        "storefront-tanstack",
        "blog-tanstack",
        "faststore"
      ],
      "summary": "There's no page-URL or search-param injection, blocks get no client (so apps-blog reads the wrong revision in preview), and both the rule for which loaders a field accepts and the memo key are unspecified. 21 of the storefront's inline loaders sit inside Lazy wrappers.",
      "docs": [
        "blocks",
        "studio-compatibility",
        "schema",
        "api-reference",
        "troubleshooting",
        "renames-and-migrations",
        "rendering"
      ],
      "confidence": "high",
      "first_rated": null,
      "rated_before_review": null,
      "unconfirmed_sub_claim": true
    },
    "app-loader-overrides": {
      "name": "Site overrides of app/platform loaders",
      "category": "data",
      "status": "to-finish",
      "effort": "M",
      "sites": [
        "storefront-tanstack",
        "blog-tanstack",
        "faststore"
      ],
      "summary": "Overriding a key is plain JS, but none of the 3 real overrides ports on the proposed API alone. There's no notion of app packages, and no rule says whose schema wins when a site overrides an app's key.",
      "docs": [
        "blocks",
        "renames-and-migrations",
        "schema"
      ],
      "confidence": "high",
      "first_rated": "done",
      "rated_before_review": null,
      "unconfirmed_sub_claim": true
    },
    "site-local-loaders": {
      "name": "Site-local loaders called from the client",
      "category": "data",
      "status": "site-code",
      "effort": "S",
      "sites": [
        "storefront-tanstack"
      ],
      "summary": "No framework feature is needed, and the migration fixes a live bug: `/deco/invoke` never finds the site's user and wishlist loaders, so those calls 404 and throw. They become server functions.",
      "docs": [
        "blocks",
        "tanstack-start-descriptors",
        "caching",
        "routing"
      ],
      "confidence": "high",
      "first_rated": null,
      "rated_before_review": null,
      "unconfirmed_sub_claim": false
    },
    "invoke-and-actions": {
      "name": "Actions, invoke endpoint and client invoke",
      "category": "data",
      "status": "to-finish",
      "effort": "M",
      "sites": [
        "storefront-tanstack",
        "blog-tanstack"
      ],
      "summary": "There's no invoke endpoint, and the plan drops it: the site editor calls `/deco/invoke` for @options fields, Run and the product picker, and `/live/invoke` to encrypt secrets. On protocol projects pickers fall back to schema options and Run is hidden.",
      "docs": [
        "studio-compatibility",
        "internals",
        "caching",
        "api-reference",
        "tanstack-start-descriptors",
        "upstream-clients",
        "content-protocol"
      ],
      "confidence": "high",
      "first_rated": null,
      "rated_before_review": null,
      "unconfirmed_sub_claim": false
    },
    "bff-custom-endpoints": {
      "name": "BFF: custom GraphQL fields proxying platform REST",
      "category": "data",
      "status": "site-code",
      "effort": "M",
      "sites": [
        "faststore"
      ],
      "summary": "Nothing framework-level is needed; API routes belong to the framework. Each handler sets Cache-Control itself once `@cacheControl` goes.",
      "docs": [
        "routing",
        "blocks",
        "caching",
        "nextjs"
      ],
      "confidence": "high",
      "first_rated": null,
      "rated_before_review": null,
      "unconfirmed_sub_claim": false
    },
    "implicit-page-data-context": {
      "name": "Route page data via page context",
      "category": "data",
      "status": "site-code",
      "effort": "M",
      "sites": [
        "faststore"
      ],
      "summary": "Routing and looking entries up by name is allowed, but the provider data moves from FastStore fragments to schema.org types. Server block functions can't read client React context, so sharing one fetch needs React cache or an app-owned AsyncLocalStorage.",
      "docs": [
        "routing",
        "nextjs"
      ],
      "confidence": "medium",
      "first_rated": null,
      "rated_before_review": null,
      "unconfirmed_sub_claim": false
    },
    "plp-filters-sort-pagination": {
      "name": "PLP/search filters, sort and pagination",
      "category": "interactivity",
      "status": "to-finish",
      "effort": "L",
      "sites": [
        "storefront-tanstack",
        "blog-tanstack",
        "faststore"
      ],
      "summary": "On SPA navigation blocks see the RPC URL, `/*` never matches, and without loaderDeps the loader doesn't re-run. Show-more needs `/deco/invoke` and a revision, the Lax draft cookie is rejected in the site editor's iframe, and FastStore's search state machine must be rebuilt.",
      "docs": [
        "blocks",
        "tanstack-start-descriptors",
        "nextjs",
        "router-internals",
        "releases-and-deployment",
        "api-reference",
        "matchers-and-variants",
        "troubleshooting"
      ],
      "confidence": "high",
      "first_rated": null,
      "rated_before_review": null,
      "unconfirmed_sub_claim": false
    },
    "site-search-and-autocomplete": {
      "name": "Site search and autocomplete",
      "category": "data",
      "status": "to-finish",
      "effort": "M",
      "sites": [
        "storefront-tanstack",
        "blog-tanstack",
        "faststore"
      ],
      "summary": "Nothing matches `Resolved<T>`: Suggestions keeps its loader unresolved and invokes it with `query`, which an eagerly resolved inline block can't do. `resolved` isn't in the alias bridge, and apps-shopify has no suggestions loader.",
      "docs": [
        "blocks",
        "matchers-and-variants",
        "api-reference",
        "tanstack-start-descriptors"
      ],
      "confidence": "medium",
      "first_rated": null,
      "rated_before_review": null,
      "unconfirmed_sub_claim": false
    },
    "graphql-fragments-codegen": {
      "name": "FastStore GraphQL fragment overrides and codegen",
      "category": "commerce",
      "status": "goes-away",
      "effort": "M",
      "sites": [
        "faststore"
      ],
      "summary": "FastStore tooling with no counterpart once @faststore/api and core are gone. Persisted-query hashes acted as an allowlist, so each replacement server function needs explicit input validation.",
      "docs": [
        "blocks",
        "api-reference",
        "rendering",
        "tanstack-start-descriptors"
      ],
      "confidence": "high",
      "first_rated": null,
      "rated_before_review": null,
      "unconfirmed_sub_claim": false
    },
    "graphql-schema-extensions": {
      "name": "GraphQL schema extensions on product/offer",
      "category": "commerce",
      "status": "to-finish",
      "effort": "M",
      "sites": [
        "faststore"
      ],
      "summary": "A wrapper block can compute the fields, but apps-vtex's lean offers drop the full installment list and interest rates that custom price fields may need. The BFF's per-field cache policy must be restated per server function.",
      "docs": [
        "rendering",
        "blocks",
        "caching"
      ],
      "confidence": "medium",
      "first_rated": null,
      "rated_before_review": null,
      "unconfirmed_sub_claim": true
    },
    "vtex-io-app-settings": {
      "name": "Merchandising data from VTEX IO app settings",
      "category": "commerce",
      "status": "site-code",
      "effort": "S",
      "sites": [
        "faststore"
      ],
      "summary": "A server endpoint plus a client hook, but apps-vtex has no persisted-query client for `/_v/public/graphql/v1`, so the site's IO client still needs porting, and it's unclear what the app should cache with.",
      "docs": [
        "blocks",
        "caching",
        "matchers-and-variants",
        "routing",
        "api-reference"
      ],
      "confidence": "medium",
      "first_rated": null,
      "rated_before_review": null,
      "unconfirmed_sub_claim": false
    },
    "commerce-platform-binding": {
      "name": "Commerce platform binding",
      "category": "commerce",
      "status": "to-finish",
      "effort": "L",
      "sites": [
        "storefront-tanstack",
        "faststore"
      ],
      "summary": "Loaders as block functions and legacy aliases are documented, but not what apps need from the runtime: apps-* import `@decocms/blocks/sdk/*` and depend on `@decocms/tanstack`, and no guide populates `RequestContext`, so apps-vtex's region and cookies silently turn off.",
      "docs": [
        "blocks",
        "routing",
        "router-internals",
        "renames-and-migrations",
        "design-decisions",
        "tanstack-start-descriptors",
        "studio-compatibility",
        "api-reference",
        "nextjs"
      ],
      "confidence": "high",
      "first_rated": null,
      "rated_before_review": null,
      "unconfirmed_sub_claim": false
    },
    "shopify-autoconfig-workaround": {
      "name": "Shopify app autoconfig static-module workaround",
      "category": "config",
      "status": "goes-away",
      "effort": "S",
      "sites": [
        "storefront-tanstack"
      ],
      "summary": "There's no registry left to patch. The package bug is still open and its root cause was never established, so in-body dynamic imports need checking in a production Workers build.",
      "docs": [
        "design-decisions",
        "blocks",
        "api-reference",
        "troubleshooting"
      ],
      "confidence": "high",
      "first_rated": null,
      "rated_before_review": null,
      "unconfirmed_sub_claim": false
    },
    "cart-customer-server-functions": {
      "name": "Cart and customer via server functions",
      "category": "commerce",
      "status": "site-code",
      "effort": "S",
      "sites": [
        "storefront-tanstack"
      ],
      "summary": "The four cart server functions touch neither the CMS nor `/deco/invoke`. They break at import time, though, if apps-shopify's `@decocms/blocks/sdk` imports and its `@decocms/tanstack` dependency don't survive.",
      "docs": [
        "caching",
        "tanstack-start-descriptors",
        "blocks"
      ],
      "confidence": "high",
      "first_rated": null,
      "rated_before_review": null,
      "unconfirmed_sub_claim": false
    },
    "client-commerce-state-hooks": {
      "name": "Client commerce state hooks and SSR prefetch",
      "category": "interactivity",
      "status": "site-code",
      "effort": "S",
      "sites": [
        "storefront-tanstack"
      ],
      "summary": "The hooks and their SSR prefetch stay app code.",
      "docs": [
        "caching",
        "blocks",
        "rendering",
        "tanstack-start-descriptors"
      ],
      "confidence": "high",
      "first_rated": null,
      "rated_before_review": null,
      "unconfirmed_sub_claim": false
    },
    "cart-and-minicart": {
      "name": "Add to cart, minicart and checkout handoff",
      "category": "commerce",
      "status": "to-finish",
      "effort": "M",
      "sites": [
        "storefront-tanstack",
        "faststore"
      ],
      "summary": "The proposed API has no cart; the Next guide's CartButton is a counter demo. The invoke-based cart is TanStack-only, the VTEX checkout proxy lives in `createDecoWorkerEntry`, and FastStore's cart rules must be rebuilt by hand.",
      "docs": [
        "rendering",
        "caching",
        "routing"
      ],
      "confidence": "medium",
      "first_rated": null,
      "rated_before_review": null,
      "unconfirmed_sub_claim": false
    },
    "session-regionalization": {
      "name": "Session and regionalization",
      "category": "commerce",
      "status": "to-build",
      "effort": "L",
      "sites": [
        "faststore"
      ],
      "summary": "There's nothing on sessions or regions beyond cache-key advice. Without a populated `RequestContext`, apps-vtex's regionalized Intelligent Search queries silently fall back to the default region.",
      "docs": [
        "blocks",
        "matchers-and-variants",
        "caching",
        "routing"
      ],
      "confidence": "medium",
      "first_rated": null,
      "rated_before_review": null,
      "unconfirmed_sub_claim": false
    },
    "delivery-promise": {
      "name": "Delivery-promise facets and region slider",
      "category": "commerce",
      "status": "to-build",
      "effort": "L",
      "sites": [
        "faststore"
      ],
      "summary": "Neither these docs nor apps-vtex have it: the Intelligent Search loaders carry no delivery-promise parameters, and the FastStore hooks and region slider it uses don't survive. It also depends on session regionalization.",
      "docs": [
        "routing",
        "blocks"
      ],
      "confidence": "medium",
      "first_rated": null,
      "rated_before_review": null,
      "unconfirmed_sub_claim": false
    },
    "global-ui-state-store": {
      "name": "Global UI state store",
      "category": "interactivity",
      "status": "site-code",
      "effort": "S",
      "sites": [
        "faststore"
      ],
      "summary": "Shared open/close state is plain React context. A module-level store must never be written during SSR, and the cart sidebar and region slider that lived in @faststore/core become site code.",
      "docs": [
        "api-reference",
        "design-decisions",
        "rendering",
        "caching"
      ],
      "confidence": "high",
      "first_rated": null,
      "rated_before_review": null,
      "unconfirmed_sub_claim": false
    },
    "newsletter-subscription": {
      "name": "Newsletter subscription",
      "category": "interactivity",
      "status": "site-code",
      "effort": "S",
      "sites": [
        "storefront-tanstack",
        "faststore"
      ],
      "summary": "A form plus a server function or Server Action is a few lines. The content drift (flat fields where Props read nested `notices` and `form`) means editors' copy is ignored today, and the public POST needs rate limiting.",
      "docs": [
        "caching",
        "matchers-and-variants",
        "blocks",
        "tanstack-start-descriptors",
        "renames-and-migrations"
      ],
      "confidence": "high",
      "first_rated": null,
      "rated_before_review": null,
      "unconfirmed_sub_claim": false
    },
    "product-comparison": {
      "name": "Product comparison",
      "category": "interactivity",
      "status": "site-code",
      "effort": "M",
      "sites": [
        "faststore"
      ],
      "summary": "One server function plus useQuery; the selection context moves unchanged. The cost is re-platforming the comparison sidebar to schema.org products, and it can't ship before ProductGallery's search state is rebuilt.",
      "docs": [
        "rendering",
        "blocks",
        "caching",
        "schema"
      ],
      "confidence": "medium",
      "first_rated": null,
      "rated_before_review": null,
      "unconfirmed_sub_claim": false
    },
    "shipping-simulation": {
      "name": "PDP shipping simulation",
      "category": "commerce",
      "status": "site-code",
      "effort": "S",
      "sites": [
        "storefront-tanstack",
        "faststore"
      ],
      "summary": "It becomes a server function around an existing apps-vtex call. Every `invoke.*` call site needs rewriting, and without `RequestContext` no segment cookie is forwarded, so regional SLAs can differ.",
      "docs": [
        "caching",
        "tanstack-start-descriptors",
        "blocks"
      ],
      "confidence": "high",
      "first_rated": null,
      "rated_before_review": null,
      "unconfirmed_sub_claim": false
    },
    "wishlist-notify-reviews": {
      "name": "Wishlist, notify-me, reviews (stubbed)",
      "category": "commerce",
      "status": "site-code",
      "effort": "S",
      "sites": [
        "storefront-tanstack",
        "faststore"
      ],
      "summary": "These are placeholders; each becomes a server function in a few lines. The site's demo wishlist code uses `RequestContext`.",
      "docs": [
        "caching",
        "tanstack-start-descriptors",
        "blocks"
      ],
      "confidence": "high",
      "first_rated": null,
      "rated_before_review": null,
      "unconfirmed_sub_claim": false
    },
    "interactive-ui-widgets": {
      "name": "Client UI widgets (sliders, drawers, modals, scripts)",
      "category": "interactivity",
      "status": "to-finish",
      "effort": "M",
      "sites": [
        "storefront-tanstack",
        "blog-tanstack",
        "faststore"
      ],
      "summary": "Plain-React widgets run unchanged, but the storefront's widgets import `@decocms/blocks/sdk/useDevice` and the Image and Picture hooks, the CSP nonce comes from `createDecoRouter`, and the blog's inline scripts misbehave in streamed boundaries.",
      "docs": [
        "rendering",
        "nextjs",
        "tanstack-start-descriptors",
        "blocks",
        "troubleshooting"
      ],
      "confidence": "high",
      "first_rated": null,
      "rated_before_review": "done",
      "unconfirmed_sub_claim": false
    },
    "design-system-library": {
      "name": "Site-owned atomic UI library",
      "category": "design",
      "status": "site-code",
      "effort": "M",
      "sites": [
        "faststore"
      ],
      "summary": "The app owns this entirely. Hook-using files without `\"use client\"` throw under Server Components, and the FastStore shell's contexts must be re-mounted.",
      "docs": [
        "how-it-works",
        "blocks",
        "rendering",
        "nextjs"
      ],
      "confidence": "medium",
      "first_rated": null,
      "rated_before_review": null,
      "unconfirmed_sub_claim": true
    },
    "page-seo-blocks": {
      "name": "Page-level SEO blocks",
      "category": "seo",
      "status": "to-finish",
      "effort": "M",
      "sites": [
        "storefront-tanstack",
        "blog-tanstack",
        "faststore"
      ],
      "summary": "`page.seo` resolves, but the guides map only title and description, so canonical, robots, OG and page JSON-LD have no owner. The blog's SEO blocks need cross-entry lookups, and `throw seoError` turns an upstream failure into a full-page error.",
      "docs": [
        "routing",
        "rendering",
        "nextjs",
        "tanstack-start-descriptors",
        "renames-and-migrations",
        "studio-compatibility",
        "blocks",
        "design-decisions"
      ],
      "confidence": "high",
      "first_rated": null,
      "rated_before_review": null,
      "unconfirmed_sub_claim": false
    },
    "site-seo-defaults": {
      "name": "Site-wide SEO defaults and title templates",
      "category": "seo",
      "status": "to-finish",
      "effort": "S",
      "sites": [
        "storefront-tanstack",
        "blog-tanstack",
        "faststore"
      ],
      "summary": "There's no site-level SEO concept, so inheritance is app code and must match the site editor's `mergeSeo` exactly. The site editor's preview applies titleTemplate but not descriptionTemplate, so production and preview already differ (descriptions are used as written, see the SEO parity item).",
      "docs": [
        "blocks",
        "schema",
        "api-reference",
        "renames-and-migrations",
        "design-decisions"
      ],
      "confidence": "high",
      "first_rated": null,
      "rated_before_review": null,
      "unconfirmed_sub_claim": false
    },
    "commerce-seo-sections-in-sections": {
      "name": "Commerce SEO blocks inside a page's block list",
      "category": "seo",
      "status": "to-finish",
      "effort": "M",
      "sites": [
        "storefront-tanstack"
      ],
      "summary": "The target, `page.seo`, is documented; what's missing is the SEO-hoist codemod and the per-client memo-key rule. The problem is live today: storefront PDP and PLP heads carry only the site title.",
      "docs": [
        "blocks",
        "rendering",
        "routing",
        "caching",
        "renames-and-migrations",
        "router-internals"
      ],
      "confidence": "high",
      "first_rated": null,
      "rated_before_review": "to-build",
      "unconfirmed_sub_claim": false
    },
    "head-tags-from-sections": {
      "name": "Head tags injected from blocks",
      "category": "seo",
      "status": "site-code",
      "effort": "S",
      "sites": [
        "faststore"
      ],
      "summary": "React 19 hoisting lets a block emit head tags, but the per-block Suspense pattern defeats in-block LCP preloads, and nothing says so. Preload from the page component instead.",
      "docs": [
        "rendering",
        "how-it-works",
        "blocks",
        "nextjs"
      ],
      "confidence": "medium",
      "first_rated": null,
      "rated_before_review": null,
      "unconfirmed_sub_claim": false
    },
    "json-ld-structured-data": {
      "name": "JSON-LD structured data",
      "category": "seo",
      "status": "to-finish",
      "effort": "S",
      "sites": [
        "storefront-tanstack",
        "blog-tanstack",
        "faststore"
      ],
      "summary": "The JSON-LD helpers live in `@decocms/blocks/hooks`, which has no documented home in a React-free SDK, and the proposed API has no `<JsonLd>` renderer.",
      "docs": [
        "blocks",
        "rendering",
        "api-reference",
        "internals"
      ],
      "confidence": "high",
      "first_rated": null,
      "rated_before_review": null,
      "unconfirmed_sub_claim": false
    },
    "sitemap": {
      "name": "Sitemap",
      "category": "seo",
      "status": "to-finish",
      "effort": "M",
      "sites": [
        "storefront-tanstack",
        "blog-tanstack",
        "faststore"
      ],
      "summary": "Only literal pages and Route-typed content are handled: templated pages have no enumeration story, and there's no XML or sitemap-index helper. Next's `app/sitemap.ts` is static by default, which defeats remote releases.",
      "docs": [
        "routing",
        "api-reference",
        "router-internals",
        "releases-and-deployment"
      ],
      "confidence": "high",
      "first_rated": null,
      "rated_before_review": null,
      "unconfirmed_sub_claim": false
    },
    "robots-and-static-seo-files": {
      "name": "robots.txt, llms.txt and favicons",
      "category": "seo",
      "status": "site-code",
      "effort": "S",
      "sites": [
        "storefront-tanstack",
        "blog-tanstack",
        "faststore"
      ],
      "summary": "Static files sit outside the CMS, so these are content fixes: the storefront's robots.txt has no Sitemap line, the blog's Sitemap URL is relative, and FastStore must recreate its robots.txt and favicon.",
      "docs": [
        "routing",
        "how-it-works"
      ],
      "confidence": "high",
      "first_rated": null,
      "rated_before_review": null,
      "unconfirmed_sub_claim": false
    },
    "edge-html-cache-profiles": {
      "name": "Edge/HTTP cache profiles",
      "category": "caching",
      "status": "to-build",
      "effort": "L",
      "sites": [
        "storefront-tanstack",
        "blog-tanstack",
        "faststore"
      ],
      "summary": "There's no HTTP or edge cache story, and the Next pattern disables static and ISR rendering. Degraded pages get cached, and there's no revision to key by. The planned once-a-minute check is eventually consistent; manifest and page-cache lifetimes also affect propagation.",
      "docs": [
        "caching",
        "releases-and-deployment",
        "api-reference",
        "hosted-releases-internals",
        "nextjs",
        "tanstack-start-descriptors",
        "rendering",
        "releases-and-drafts",
        "routing"
      ],
      "confidence": "high",
      "first_rated": null,
      "rated_before_review": null,
      "unconfirmed_sub_claim": false
    },
    "in-isolate-caches": {
      "name": "In-process caches (layout, loaders, globals)",
      "category": "caching",
      "status": "to-finish",
      "effort": "M",
      "sites": [
        "storefront-tanstack",
        "blog-tanstack",
        "faststore"
      ],
      "summary": "Cross-request caching is 'your policy', and upstream caching moves to template recipes (a caching fetch passed to a client), not yet in the templates. Draft caches are unbounded, input-keyed caches miss request state, and the site editor's bypass flag is ignored.",
      "docs": [
        "caching",
        "api-reference",
        "troubleshooting",
        "blocks"
      ],
      "confidence": "medium",
      "first_rated": null,
      "rated_before_review": null,
      "unconfirmed_sub_claim": false
    },
    "request-context-cookies": {
      "name": "Request context, cookies and headers",
      "category": "caching",
      "status": "to-finish",
      "effort": "M",
      "sites": [
        "storefront-tanstack",
        "blog-tanstack",
        "faststore"
      ],
      "summary": "Headers and cookies are available, but the page URL and params, Set-Cookie from resolution and the request port apps-* use are not. [Blocks](#blocks) says to read route params through `getRequest()`, which is wrong even on SSR.",
      "docs": [
        "blocks",
        "routing",
        "tanstack-start-descriptors",
        "nextjs",
        "rendering",
        "caching"
      ],
      "confidence": "high",
      "first_rated": null,
      "rated_before_review": null,
      "unconfirmed_sub_claim": false
    },
    "admin-protocol-endpoints": {
      "name": "Admin/site editor protocol endpoints",
      "category": "studio",
      "status": "to-build",
      "effort": "L",
      "sites": [
        "storefront-tanstack",
        "blog-tanstack"
      ],
      "summary": "Nothing serves the endpoints the site editor calls on a site today (`/live/_meta`, `/.decofile`, `/live/previews`, `/deco/invoke`). The plan replaces them with the content protocol, which needs only the committed schema and files, so features that run site code degrade. Today the blog's product picker also calls invoke in production; on protocol projects it falls back to free-text product IDs.",
      "docs": [
        "studio-compatibility",
        "internals",
        "schema",
        "blocks",
        "caching",
        "content-protocol"
      ],
      "confidence": "high",
      "first_rated": null,
      "rated_before_review": null,
      "unconfirmed_sub_claim": false
    },
    "studio-preview": {
      "name": "Site editor preview rendering",
      "category": "studio",
      "status": "to-finish",
      "effort": "M",
      "sites": [
        "storefront-tanstack",
        "blog-tanstack",
        "faststore"
      ],
      "summary": "Draft rendering maps directly, but the TanStack guides never append the draft cookie, the SameSite=Lax cookie is rejected in the site editor's iframe, and there's no no-store rule for drafts. Recovering `data-manifest-key` takes more than a wrapper.",
      "docs": [
        "releases-and-drafts",
        "api-reference",
        "nextjs",
        "tanstack-start-descriptors",
        "matchers-and-variants",
        "rendering"
      ],
      "confidence": "high",
      "first_rated": null,
      "rated_before_review": null,
      "unconfirmed_sub_claim": false
    },
    "studio-schema-generation": {
      "name": "Editor schema generation / catalog",
      "category": "studio",
      "status": "to-finish",
      "effort": "L",
      "sites": [
        "storefront-tanstack",
        "blog-tanstack",
        "faststore"
      ],
      "summary": "The CLI's documented output isn't what the site editor reads: the file name, btoa keys, `schema.root` unions and `schema.definitions` all differ. There are no `apps` or `actions` groups, and the promised JSDoc tag table doesn't exist.",
      "docs": [
        "schema",
        "studio-compatibility",
        "renames-and-migrations",
        "blocks",
        "quickstart",
        "routing",
        "caching"
      ],
      "confidence": "high",
      "first_rated": null,
      "rated_before_review": null,
      "unconfirmed_sub_claim": false
    },
    "cms-schema-upload-pipeline": {
      "name": "CMS schema generate and upload (VTEX CP)",
      "category": "studio",
      "status": "goes-away",
      "effort": "S",
      "sites": [
        "faststore"
      ],
      "summary": "The VTEX upload has no counterpart, because publishing is committing. The replacement inherits the schema-file problems.",
      "docs": [
        "schema",
        "releases-and-deployment"
      ],
      "confidence": "high",
      "first_rated": null,
      "rated_before_review": null,
      "unconfirmed_sub_claim": false
    },
    "studio-workspace-spaces": {
      "name": "Site editor workspace layout (spaces.json)",
      "category": "studio",
      "status": "goes-away",
      "effort": "S",
      "sites": [
        "blog-tanstack"
      ],
      "summary": "Nothing in the site editor reads `spaces.json`. What matters instead: the site editor's Blog tab keys off legacy blog loader names and gates scheduling on `@decocms/apps-blog` 7.53.0 or later, while the blog pins `@decocms/apps` 5.2.0.",
      "docs": [
        "studio-compatibility",
        "blocks"
      ],
      "confidence": "high",
      "first_rated": null,
      "rated_before_review": null,
      "unconfirmed_sub_claim": false
    },
    "dev-studio-content-loop": {
      "name": "Local dev daemon and site editor content loop",
      "category": "studio",
      "status": "to-finish",
      "effort": "M",
      "sites": [
        "storefront-tanstack",
        "blog-tanstack"
      ],
      "summary": "With a site and token in dev, `remoteLoader` swaps in the production release over local edits, so site editor sandbox saves vanish from the dev preview. Nothing regenerates the schema or serves the Local-mode endpoints.",
      "docs": [
        "api-reference",
        "tanstack-start-descriptors",
        "releases-and-drafts",
        "schema",
        "hosted-releases-internals",
        "quickstart",
        "content-protocol"
      ],
      "confidence": "high",
      "first_rated": null,
      "rated_before_review": null,
      "unconfirmed_sub_claim": false
    },
    "site-bootstrap": {
      "name": "Site bootstrap (createSiteSetup + admin setup)",
      "category": "config",
      "status": "to-finish",
      "effort": "L",
      "sites": [
        "storefront-tanstack",
        "blog-tanstack"
      ],
      "summary": "`createCMS` plus a block map replace registration, but nothing generates the map (27 UI blocks and 12 loaders by hand on the storefront), the revision of generated content must match the API hash (planned: <code>deco content</code> computes it the same way), and there's no lenient mode or site-editor-shaped schema.",
      "docs": [
        "quickstart",
        "blocks",
        "api-reference",
        "tanstack-start-descriptors",
        "hosted-releases-internals",
        "schema",
        "releases-and-drafts",
        "studio-compatibility",
        "renames-and-migrations"
      ],
      "confidence": "medium",
      "first_rated": null,
      "rated_before_review": null,
      "unconfirmed_sub_claim": false
    },
    "site-config-block": {
      "name": "Site config block / global settings",
      "category": "config",
      "status": "to-finish",
      "effort": "M",
      "sites": [
        "storefront-tanstack",
        "blog-tanstack",
        "faststore"
      ],
      "summary": "Today the binding resolves the site's theme, globals and page blocks into every page. In the next major, SEO-default merging and global-block injection move to app code, there's no singleton concept, and `site/apps/site.ts` isn't in the alias bridge.",
      "docs": [
        "routing",
        "schema",
        "blocks",
        "api-reference",
        "renames-and-migrations",
        "studio-compatibility"
      ],
      "confidence": "medium",
      "first_rated": null,
      "rated_before_review": null,
      "unconfirmed_sub_claim": false
    },
    "app-installation-and-store-config": {
      "name": "App installation blocks, autoconfig and store config",
      "category": "config",
      "status": "to-build",
      "effort": "L",
      "sites": [
        "storefront-tanstack",
        "blog-tanstack",
        "faststore"
      ],
      "summary": "There's no concept of an app: no config for app loaders, no autoconfig, no secret resolution, no `apps` manifest group and no encrypt endpoint, so the site editor can't save secret fields.",
      "docs": [
        "blocks",
        "design-decisions",
        "studio-compatibility",
        "schema",
        "releases-and-drafts",
        "api-reference",
        "tanstack-start-descriptors"
      ],
      "confidence": "high",
      "first_rated": null,
      "rated_before_review": null,
      "unconfirmed_sub_claim": false
    },
    "env-vars-and-secrets": {
      "name": "Environment variables and secrets",
      "category": "config",
      "status": "to-finish",
      "effort": "M",
      "sites": [
        "storefront-tanstack",
        "blog-tanstack",
        "faststore"
      ],
      "summary": "DECO_SITE_TOKEN's lifecycle is undocumented, there's no channel option, and a production deploy that forgets the token silently serves bundled content. There's no secret story either: the decryptor for secrets stored in content is off the documented surface.",
      "docs": [
        "api-reference",
        "tanstack-start-descriptors",
        "nextjs",
        "releases-and-drafts",
        "releases-and-deployment",
        "caching"
      ],
      "confidence": "medium",
      "first_rated": null,
      "rated_before_review": null,
      "unconfirmed_sub_claim": false
    },
    "build-time-site-globals-snapshot": {
      "name": "Build-time site-globals snapshot workaround",
      "category": "config",
      "status": "goes-away",
      "effort": "S",
      "sites": [
        "blog-tanstack"
      ],
      "summary": "A workaround for a current-framework bug, with nothing to port. Half its call sites are server-side, so the site needs a small AsyncLocalStorage hand-off, and the snapshot's defect (edits never reach draft preview) goes away.",
      "docs": [
        "blocks",
        "routing",
        "tanstack-start-descriptors",
        "releases-and-deployment",
        "releases-and-drafts"
      ],
      "confidence": "high",
      "first_rated": null,
      "rated_before_review": null,
      "unconfirmed_sub_claim": false
    },
    "app-domain-types": {
      "name": "App domain types driving the site editor's schema",
      "category": "config",
      "status": "to-finish",
      "effort": "M",
      "sites": [
        "blog-tanstack"
      ],
      "summary": "The mechanism is documented but not the conventions apps-blog relies on: type-alias formats like `ImageWidget`, and `@titleBy`, `@hide` and `@widget`. Without them the blog's forms degrade to plain text inputs.",
      "docs": [
        "schema",
        "quickstart",
        "routing",
        "studio-compatibility"
      ],
      "confidence": "medium",
      "first_rated": "done",
      "rated_before_review": null,
      "unconfirmed_sub_claim": false
    },
    "worker-server-entry": {
      "name": "Worker/server entry composition",
      "category": "runtime-deploy",
      "status": "to-finish",
      "effort": "M",
      "sites": [
        "storefront-tanstack",
        "blog-tanstack",
        "faststore"
      ],
      "summary": "Only the CMS and draft slice of the entry is documented. Site editor routes, the edge cache, purge, security headers and OTel have no home, the draft cookie is SameSite=Lax, and `?__draft=off` never exits preview.",
      "docs": [
        "tanstack-start-descriptors",
        "api-reference",
        "releases-and-drafts",
        "nextjs",
        "caching",
        "studio-compatibility",
        "internals"
      ],
      "confidence": "high",
      "first_rated": null,
      "rated_before_review": null,
      "unconfirmed_sub_claim": false
    },
    "csp-security-headers": {
      "name": "CSP and security headers",
      "category": "runtime-deploy",
      "status": "site-code",
      "effort": "S",
      "sites": [
        "storefront-tanstack",
        "blog-tanstack"
      ],
      "summary": "Headers are a few lines of app code, but after migration nothing sets HSTS, nosniff, Referrer-Policy or a framing policy, and the origins the site editor needs in frame-ancestors are undocumented.",
      "docs": [
        "how-it-works",
        "tanstack-start-descriptors",
        "nextjs",
        "studio-compatibility"
      ],
      "confidence": "high",
      "first_rated": null,
      "rated_before_review": null,
      "unconfirmed_sub_claim": false
    },
    "ab-traffic-split": {
      "name": "Migration A/B traffic split",
      "category": "runtime-deploy",
      "status": "to-build",
      "effort": "S",
      "sites": [
        "storefront-tanstack"
      ],
      "summary": "Nothing gives `withABTesting` a home, and it isn't few-line app code. It also needs to bypass draft requests by default.",
      "docs": [
        "api-reference",
        "internals",
        "quickstart"
      ],
      "confidence": "high",
      "first_rated": null,
      "rated_before_review": null,
      "unconfirmed_sub_claim": false
    },
    "hosting-and-deploy-config": {
      "name": "Hosting and deploy configuration",
      "category": "runtime-deploy",
      "status": "to-finish",
      "effort": "M",
      "sites": [
        "storefront-tanstack",
        "blog-tanstack",
        "faststore"
      ],
      "summary": "Only the single-instance happy path is documented: a cron or webhook `update()` refreshes one instance, serverless Node gives no staleness bound, and per-PR preview deploys swap in the production release.",
      "docs": [
        "tanstack-start-descriptors",
        "tanstack-start-rsc",
        "nextjs",
        "api-reference",
        "releases-and-deployment"
      ],
      "confidence": "medium",
      "first_rated": null,
      "rated_before_review": null,
      "unconfirmed_sub_claim": false
    },
    "content-storage-and-delivery": {
      "name": "Content storage, snapshot and delivery",
      "category": "runtime-deploy",
      "status": "to-finish",
      "effort": "L",
      "sites": [
        "storefront-tanstack",
        "blog-tanstack",
        "faststore"
      ],
      "summary": "The file model matches, but filename encoding, bundling, the release service and skew protection are unspecified. Content is meant to be validated against the code in CI by `deco check`, which isn't built yet.",
      "docs": [
        "quickstart",
        "blocks",
        "api-reference",
        "tanstack-start-descriptors",
        "releases-and-deployment",
        "hosted-releases-internals",
        "studio-compatibility"
      ],
      "confidence": "high",
      "first_rated": null,
      "rated_before_review": null,
      "unconfirmed_sub_claim": false
    },
    "fast-deploy-kv": {
      "name": "Fast Deploy (KV-first content)",
      "category": "runtime-deploy",
      "status": "to-finish",
      "effort": "S",
      "sites": [
        "storefront-tanstack",
        "blog-tanstack"
      ],
      "summary": "`remoteLoader` will check once a minute (configurable with `interval`), cold isolates serve the deployed content module until their first check, a webhook reaches one isolate, and the KV loader is a template recipe, not yet in the templates. Impact on these sites is low: none serves content from KV today.",
      "docs": [
        "api-reference",
        "releases-and-deployment",
        "hosted-releases-internals"
      ],
      "confidence": "high",
      "first_rated": "done",
      "rated_before_review": null,
      "unconfirmed_sub_claim": false
    },
    "ci-and-release-workflows": {
      "name": "CI, content regen and release workflows",
      "category": "runtime-deploy",
      "status": "to-finish",
      "effort": "M",
      "sites": [
        "storefront-tanstack",
        "blog-tanstack",
        "faststore"
      ],
      "summary": "The CI job is a few lines, but the CLI doesn't emit what the site editor reads and has no `deco check` yet. The blog's `regen-blocks.yml` is its live content-delivery path today; deleting it early freezes its content.",
      "docs": [
        "schema",
        "releases-and-deployment",
        "router-internals",
        "studio-compatibility",
        "renames-and-migrations"
      ],
      "confidence": "high",
      "first_rated": "site-code",
      "rated_before_review": null,
      "unconfirmed_sub_claim": false
    },
    "root-document-layout": {
      "name": "Root document and layout",
      "category": "runtime-deploy",
      "status": "to-finish",
      "effort": "M",
      "sites": [
        "storefront-tanstack",
        "blog-tanstack",
        "faststore"
      ],
      "summary": "The document shell is app code, but the site editor bridge and the `section[data-manifest-key]` marker it needs are neither shipped nor documented. Dropping the DECO.events bootstrap also silently breaks OneDollarStats.",
      "docs": [
        "tanstack-start-descriptors",
        "nextjs",
        "rendering",
        "studio-compatibility"
      ],
      "confidence": "high",
      "first_rated": null,
      "rated_before_review": null,
      "unconfirmed_sub_claim": false
    },
    "codegen-pipeline": {
      "name": "Codegen and build pipeline",
      "category": "build",
      "status": "to-finish",
      "effort": "M",
      "sites": [
        "storefront-tanstack",
        "blog-tanstack"
      ],
      "summary": "`deco schema` and a hand-written block map replace the registries, `deco content` (planned) generates the content module for every runtime, but there's no `deco check` to validate content against the code yet",
      "docs": [
        "schema",
        "blocks",
        "tanstack-start-descriptors",
        "releases-and-deployment",
        "hosted-releases-internals",
        "renames-and-migrations",
        "studio-compatibility"
      ],
      "confidence": "high",
      "first_rated": null,
      "rated_before_review": null,
      "unconfirmed_sub_claim": false
    },
    "bundler-config": {
      "name": "Vite config",
      "category": "build",
      "status": "to-finish",
      "effort": "S",
      "sites": [
        "storefront-tanstack",
        "blog-tanstack"
      ],
      "summary": "The guide configs build, but nothing replaces decoVitePlugin's dev and site editor duties: schema regeneration, the site editor tunnel, the build hash and block preloads. Next also needs transpilePackages and file-tracing notes.",
      "docs": [
        "how-it-works",
        "tanstack-start-descriptors",
        "tanstack-start-rsc",
        "nextjs",
        "internals"
      ],
      "confidence": "medium",
      "first_rated": null,
      "rated_before_review": null,
      "unconfirmed_sub_claim": true
    },
    "faststore-cli-overlay": {
      "name": "FastStore CLI .faststore overlay",
      "category": "build",
      "status": "to-build",
      "effort": "L",
      "sites": [
        "faststore"
      ],
      "summary": "The overlay goes away by design, but the move off it has no guide and no serverless deployment note (the content importer is handled outside this roadmap). Everything the overlay supplied becomes app code, and its imports of core internals, `@faststore/core` and `@generated` have to go.",
      "docs": [
        "nextjs",
        "how-it-works",
        "schema",
        "blocks",
        "api-reference"
      ],
      "confidence": "high",
      "first_rated": null,
      "rated_before_review": "goes-away",
      "unconfirmed_sub_claim": false
    },
    "framework-import-surface": {
      "name": "Framework import surface and compat shims",
      "category": "build",
      "status": "to-build",
      "effort": "L",
      "sites": [
        "storefront-tanstack",
        "blog-tanstack",
        "faststore"
      ],
      "summary": "These docs cover about 12 CMS-core symbols and say nothing about the 7.x sdk, hooks, admin or binding tiers that apps-* and the telemetry depend on. Widget aliases are missing, and there's no 7.x to next codemod.",
      "docs": [
        "api-reference",
        "matchers-and-variants",
        "routing",
        "blocks",
        "schema",
        "internals",
        "upstream-clients"
      ],
      "confidence": "high",
      "first_rated": null,
      "rated_before_review": null,
      "unconfirmed_sub_claim": false
    },
    "core-patches": {
      "name": "patch-package patches to core",
      "category": "build",
      "status": "site-code",
      "effort": "S",
      "sites": [
        "faststore"
      ],
      "summary": "The patches go away, but the behaviours they add must be re-created in site code.",
      "docs": [
        "nextjs",
        "rendering",
        "blocks"
      ],
      "confidence": "high",
      "first_rated": null,
      "rated_before_review": "goes-away",
      "unconfirmed_sub_claim": false
    },
    "dev-tooling-and-repo-hygiene": {
      "name": "Dev tooling, quality gates and repo hygiene",
      "category": "build",
      "status": "site-code",
      "effort": "S",
      "sites": [
        "storefront-tanstack",
        "blog-tanstack",
        "faststore"
      ],
      "summary": "Ordinary app work, and the CI drift check is one line, but there's no `deco check` content validator yet. Sites' own lint and quality scripts carry over as app scripts.",
      "docs": [
        "schema",
        "router-internals",
        "renames-and-migrations"
      ],
      "confidence": "high",
      "first_rated": null,
      "rated_before_review": null,
      "unconfirmed_sub_claim": false
    },
    "legacy-runtime-leftovers": {
      "name": "Legacy Deno/Fresh leftovers",
      "category": "build",
      "status": "goes-away",
      "effort": "S",
      "sites": [
        "storefront-tanstack",
        "blog-tanstack"
      ],
      "summary": "Dead Deno and Fresh files, with nothing to port.",
      "docs": [
        "tanstack-start-descriptors",
        "releases-and-drafts",
        "api-reference",
        "caching"
      ],
      "confidence": "high",
      "first_rated": null,
      "rated_before_review": null,
      "unconfirmed_sub_claim": false
    },
    "favicon-generation": {
      "name": "Build-time favicon generation",
      "category": "build",
      "status": "site-code",
      "effort": "S",
      "sites": [
        "faststore"
      ],
      "summary": "`metadata.icons` does it in one line.",
      "docs": [
        "rendering",
        "nextjs"
      ],
      "confidence": "high",
      "first_rated": null,
      "rated_before_review": null,
      "unconfirmed_sub_claim": false
    },
    "e2e-lighthouse-config": {
      "name": "E2E and Lighthouse config",
      "category": "build",
      "status": "site-code",
      "effort": "S",
      "sites": [
        "faststore"
      ],
      "summary": "Tests are the app's job. A 'resolve every page' smoke test needs a fake request context, and there's no framework harness, so every site writes the same test.",
      "docs": [
        "how-it-works",
        "api-reference",
        "renames-and-migrations",
        "blocks"
      ],
      "confidence": "high",
      "first_rated": null,
      "rated_before_review": null,
      "unconfirmed_sub_claim": false
    },
    "ai-agent-instructions": {
      "name": "AI-agent instructions",
      "category": "build",
      "status": "site-code",
      "effort": "S",
      "sites": [
        "storefront-tanstack",
        "faststore"
      ],
      "summary": "Agent docs are repo content, but no canonical guidance ships, and the site editor's injected rules drift from the next major.",
      "docs": [
        "how-it-works",
        "schema",
        "quickstart"
      ],
      "confidence": "high",
      "first_rated": null,
      "rated_before_review": null,
      "unconfirmed_sub_claim": false
    },
    "workspace-packages": {
      "name": "Sites and block types in workspace packages",
      "category": "build",
      "status": "to-finish",
      "effort": "M",
      "sites": [
        "faststore"
      ],
      "summary": "Undocumented: whether `deco schema` follows block types into a workspace or npm package, and how the Deco API and the site editor address a site that lives in a package inside a larger repository.",
      "docs": [
        "schema",
        "releases-and-drafts",
        "nextjs",
        "api-reference",
        "releases-and-deployment"
      ],
      "confidence": "medium",
      "first_rated": null,
      "rated_before_review": null,
      "unconfirmed_sub_claim": true
    },
    "theming-and-styling": {
      "name": "Theming and styling",
      "category": "design",
      "status": "to-finish",
      "effort": "M",
      "sites": [
        "storefront-tanstack",
        "blog-tanstack",
        "faststore"
      ],
      "summary": "There's no pattern for site-wide content resolved outside the catch-all, so the theme streams late under Suspense and shifts the layout, and the theme and the page can come from different revisions.",
      "docs": [
        "blocks",
        "renames-and-migrations",
        "schema",
        "rendering",
        "releases-and-deployment",
        "nextjs",
        "studio-compatibility"
      ],
      "confidence": "high",
      "first_rated": null,
      "rated_before_review": null,
      "unconfirmed_sub_claim": false
    },
    "web-fonts": {
      "name": "Web fonts",
      "category": "design",
      "status": "to-finish",
      "effort": "S",
      "sites": [
        "storefront-tanstack",
        "blog-tanstack",
        "faststore"
      ],
      "summary": "`googleFonts` has no home in the next-major packages and imports `sdk/fetchTimeout`. Making fonts editable also needs the CLI to link `font: Font` to loaders by return type, a rule these docs name but never define.",
      "docs": [
        "blocks",
        "renames-and-migrations",
        "caching",
        "studio-compatibility",
        "internals"
      ],
      "confidence": "high",
      "first_rated": null,
      "rated_before_review": null,
      "unconfirmed_sub_claim": false
    },
    "image-optimization": {
      "name": "Image optimization",
      "category": "design",
      "status": "to-build",
      "effort": "M",
      "sites": [
        "storefront-tanstack",
        "blog-tanstack",
        "faststore"
      ],
      "summary": "Images aren't mentioned. Without a shipped URL builder, migrated sites serve full-size originals and ignore the site editor's quality choice, and without `format: image-uri` the site editor shows a text box instead of the image picker.",
      "docs": [
        "api-reference",
        "internals",
        "design-decisions",
        "routing",
        "schema"
      ],
      "confidence": "high",
      "first_rated": null,
      "rated_before_review": null,
      "unconfirmed_sub_claim": false
    },
    "i18n-and-currency": {
      "name": "i18n and currency",
      "category": "design",
      "status": "site-code",
      "effort": "S",
      "sites": [
        "storefront-tanstack",
        "blog-tanstack",
        "faststore"
      ],
      "summary": "Locale is fixed per deployment, so the only framework need is cache keys. In the FastStore storefront, code that calls its own translation hook without `\"use client\"` throws as a Server Component until it's rewritten.",
      "docs": [
        "blocks",
        "matchers-and-variants",
        "caching",
        "nextjs"
      ],
      "confidence": "high",
      "first_rated": null,
      "rated_before_review": null,
      "unconfirmed_sub_claim": false
    },
    "otel-observability": {
      "name": "OTel and logging",
      "category": "observability-analytics",
      "status": "to-finish",
      "effort": "L",
      "sites": [
        "storefront-tanstack",
        "blog-tanstack",
        "faststore"
      ],
      "summary": "Sites trace inbound requests and page caches with their framework's own OpenTelemetry setup; the SDK's resolution, upstream and error measurements go wherever the `telemetry` option of `createCMS` points (your own OTLP collector or the hosted Deco CMS). Still open: `remoteLoader` fails silently, and spans around JSX blocks miss the upstream fetches.",
      "docs": [
        "telemetry",
        "api-reference",
        "hosted-releases-internals",
        "releases-and-drafts",
        "releases-and-deployment",
        "troubleshooting",
        "blocks",
        "tanstack-start-descriptors",
        "internals"
      ],
      "confidence": "high",
      "first_rated": null,
      "rated_before_review": null,
      "unconfirmed_sub_claim": false
    },
    "upstream-fetch-instrumentation": {
      "name": "Upstream fetch instrumentation",
      "category": "observability-analytics",
      "status": "to-finish",
      "effort": "M",
      "sites": [
        "storefront-tanstack",
        "faststore"
      ],
      "summary": "The storefront calls `createInstrumentedFetch(\"shopify\")` with no onComplete, so upstream metrics are dark today. The chokepoint rule is never stated, and there's no meter on Next or Node.",
      "docs": [
        "telemetry",
        "blocks",
        "rendering",
        "internals",
        "upstream-clients"
      ],
      "confidence": "high",
      "first_rated": null,
      "rated_before_review": null,
      "unconfirmed_sub_claim": false
    },
    "analytics-trackers": {
      "name": "Analytics trackers",
      "category": "observability-analytics",
      "status": "to-finish",
      "effort": "M",
      "sites": [
        "storefront-tanstack",
        "blog-tanstack",
        "faststore"
      ],
      "summary": "Trackers carry over, but the DECO.events bootstrap is private and copied three times, and experiment results break: the site editor keys them on the random matcher's saved-entry name, which a pure block function never sees.",
      "docs": [
        "analytics",
        "nextjs",
        "matchers-and-variants",
        "design-decisions",
        "studio-compatibility",
        "renames-and-migrations",
        "blocks",
        "quickstart",
        "rendering"
      ],
      "confidence": "medium",
      "first_rated": null,
      "rated_before_review": null,
      "unconfirmed_sub_claim": false
    },
    "ecommerce-analytics-events": {
      "name": "E-commerce events",
      "category": "observability-analytics",
      "status": "to-finish",
      "effort": "S",
      "sites": [
        "storefront-tanstack",
        "faststore"
      ],
      "summary": "Emitters are app code, but the receiving pipeline is mounted only by the bindings' DecoRootLayout, which the guides don't use, so every commerce event would drop silently.",
      "docs": [
        "rendering",
        "nextjs",
        "internals",
        "analytics"
      ],
      "confidence": "high",
      "first_rated": null,
      "rated_before_review": null,
      "unconfirmed_sub_claim": false
    }
  }
}
