From 858c5213e1a5fd8373e1248f0a1c973c204620a5 Mon Sep 17 00:00:00 2001
From: Sumit Jain <266980249+sumitjain-cstk@users.noreply.github.com>
Date: Fri, 30 Oct 2026 20:53:57 +0530
Subject: [PATCH 1/3] Updated
---
.claude-plugin/marketplace.json | 2 +-
.claude-plugin/plugin.json | 2 +-
.cursor-plugin/plugin.json | 2 +-
CHANGELOG.md | 12 ++++
gemini-extension.json | 2 +-
plugin.json | 2 +-
skills/studio/author-composition-entry.md | 2 +-
.../studio/author-composition-thumbnails.md | 4 +-
skills/studio/byoc-end-to-end.md | 2 +-
skills/studio/configure-csr-vs-ssr.md | 69 ++++++++++++-------
skills/studio/configure-slot-defaults.md | 2 +-
skills/studio/decompose-design.md | 9 +--
skills/studio/decompose-site.md | 10 +--
skills/studio/design-component-library.md | 6 +-
skills/studio/gate-third-party-scripts.md | 1 +
skills/studio/match-existing-pattern.md | 5 +-
skills/studio/plan-studio-architecture.md | 10 +--
skills/studio/provision-studio-project.md | 4 +-
skills/studio/provision-studio-stack.md | 3 +
skills/studio/register-component.md | 6 +-
skills/studio/skills-index.md | 8 +--
skills/studio/troubleshoot-canvas.md | 6 +-
skills/studio/troubleshoot-data-binding.md | 2 +-
skills/studio/troubleshoot-ssr-rendering.md | 4 +-
skills/studio/troubleshoot.md | 3 +-
skills/studio/use-compositions-api.md | 10 ++-
skills/studio/verify-setup.md | 2 +-
27 files changed, 117 insertions(+), 73 deletions(-)
diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json
index 4dc816b..73bc675 100644
--- a/.claude-plugin/marketplace.json
+++ b/.claude-plugin/marketplace.json
@@ -8,7 +8,7 @@
"name": "contentstack-skills",
"source": "./",
"description": "Contentstack agent skills for CMS modeling, Delivery SDK, Live Preview, Studio, Brand Kit, Developer Hub apps, Launch, and migrations.",
- "version": "0.2.0",
+ "version": "0.2.1",
"category": "development",
"tags": ["contentstack", "cms", "headless-cms", "studio"]
}
diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json
index 7c519d1..984ef38 100644
--- a/.claude-plugin/plugin.json
+++ b/.claude-plugin/plugin.json
@@ -1,6 +1,6 @@
{
"name": "contentstack-skills",
- "version": "0.2.0",
+ "version": "0.2.1",
"description": "Contentstack agent skills for CMS modeling, Delivery SDK, Live Preview, Studio, Brand Kit, Developer Hub apps, Launch, and migrations.",
"author": { "name": "Contentstack", "url": "https://www.contentstack.com" },
"homepage": "https://developers.contentstack.com",
diff --git a/.cursor-plugin/plugin.json b/.cursor-plugin/plugin.json
index f80d783..bb024d5 100644
--- a/.cursor-plugin/plugin.json
+++ b/.cursor-plugin/plugin.json
@@ -1,6 +1,6 @@
{
"name": "contentstack-skills",
- "version": "0.2.0",
+ "version": "0.2.1",
"description": "Contentstack agent skills for CMS modeling, Delivery SDK, Live Preview, Studio, Brand Kit, Developer Hub apps, Launch, and migrations.",
"author": { "name": "Contentstack", "url": "https://www.contentstack.com" },
"skills": "./skills/"
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e6ce1a5..07bb031 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,18 @@
All notable changes to the Contentstack agent skills bundle. Versions match the `version` field in every plugin manifest that declares one.
+## 0.2.1
+
+### Changed skills
+
+- **`studio` matches Studio SDK 2.0.** Brought in the Studio team's latest guide fixes and checked them against the released SDK.
+ - Missing built-ins now surface as `COMPONENT_NOT_REGISTERED` ("Studio's own components are not registered"). The fix is to import `@contentstack/studio-react` above the fetch, not to upgrade. Updated in `troubleshoot-canvas`, `troubleshoot-ssr-rendering` and `byoc-end-to-end`.
+ - Key and token failures are named `STACK_NOT_FOUND_ON_HOST`. Entry lookups that miss are `URL_MATCHED_NO_ENTRY`. Log lines start with `[Contentstack Studio]`.
+ - `configure-csr-vs-ssr`: server styles come from `getSSRStyles`, computed on the server only. `isCompositionNotFound` is imported from `@contentstack/studio-react`. The Pages Router recipe reads `resolvedUrl`, not `req.url`. The RSC entry renders ``, and its components register through `@contentstack/studio-react/rsc` from a server module.
+ - The four-question framework is defined once, in `design-component-library`. Other guides link to it.
+ - Router triggers added for pinned Site Settings entries, Repeater recipes by API and App Router SSR crashes. `troubleshoot` stops after its clarifying question. `provision-studio-stack` waits for an explicit go on plan-only requests. `gate-third-party-scripts` covers analytics loader tags.
+ - `use-compositions-api` says Studio API service and drops two endpoints the service doesn't have.
+
## 0.2.0
### Install anywhere, from one source
diff --git a/gemini-extension.json b/gemini-extension.json
index 9538e1b..c9232e3 100644
--- a/gemini-extension.json
+++ b/gemini-extension.json
@@ -1,5 +1,5 @@
{
"name": "contentstack-skills",
- "version": "0.2.0",
+ "version": "0.2.1",
"description": "Contentstack agent skills for CMS modeling, Delivery SDK, Live Preview, Studio, Brand Kit, Developer Hub apps, Launch, and migrations."
}
diff --git a/plugin.json b/plugin.json
index 0c8c924..c254aa1 100644
--- a/plugin.json
+++ b/plugin.json
@@ -1,7 +1,7 @@
{
"$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
"name": "contentstack-skills",
- "version": "0.2.0",
+ "version": "0.2.1",
"description": "Contentstack agent skills for CMS modeling, Delivery SDK, Live Preview, Studio, Brand Kit, Developer Hub apps, Launch, and migrations.",
"author": { "name": "Contentstack", "url": "https://www.contentstack.com" },
"homepage": "https://developers.contentstack.com",
diff --git a/skills/studio/author-composition-entry.md b/skills/studio/author-composition-entry.md
index e897939..97e3b7d 100644
--- a/skills/studio/author-composition-entry.md
+++ b/skills/studio/author-composition-entry.md
@@ -60,7 +60,7 @@ A published entry returns an array with one object per environment/locale. `[]`
| Composition | `url` | `url_source` | Notes |
|---|---|---|---|
| **Connected** template, CT is a page type (`is_page: true` + CT `url_pattern`) | `//{{entry.title}}` | `content_type_url_pattern` | **The default for the API path.** The CT pattern is the derivation, so Studio re-derives correctly on every editor load. Pair with CT `url_prefix: "//"`. See [`build-connected-template` § Single-entry vs multi-entry URL pattern rule](build-connected-template.md#single-entry-vs-multi-entry-url-pattern-rule) |
-| **Connected** template, CT **has** a `url_pattern` (entries get a real `url`) | `//{{entry.title}}` | `content_type_url_pattern` | Resolution keys on **`entry.url`**: the SDK finds the entry whose stored `url` equals the request path, so write the template `url` in the shape of a real entry's `url` (read one first; a CT `url_prefix` the stored `url` lacks misses with `PREVIEW_ENTRY_NOT_FOUND`). The composition `url` is NOT variable-substituted under this source (see below). Requires every entry to carry a populated `url` |
+| **Connected** template, CT **has** a `url_pattern` (entries get a real `url`) | `//{{entry.title}}` | `content_type_url_pattern` | Resolution keys on **`entry.url`**: the SDK finds the entry whose stored `url` equals the request path, so write the template `url` in the shape of a real entry's `url` (read one first; a CT `url_prefix` the stored `url` lacks misses with `URL_MATCHED_NO_ENTRY`). The composition `url` is NOT variable-substituted under this source (see below). Requires every entry to carry a populated `url` |
| **Connected** template, **singleton** CT with no `url_pattern` (homepage, pricing, a one-entry rewards page) | `/rewards` (literal) | `content_type_url_pattern`, **but only if you populate `url` on the entry by hand** | The source's real requirement is a non-empty `url` field on at least one entry, not a CT `url_pattern`. A singleton has no pattern to generate one, so set `url: "/rewards"` on the entry yourself. Skip that and the editor fails with `MISSING_CT_PATTERN_URL_FIELD`, shown as "No entry has a URL field populated yet", while SSR renders the page fine |
| **Connected** template whose URL derives from a field **other than** `url` (`{{entry.slug}}`, `{{entry.author.slug}}`) | `/blog/{{entry.slug}}` | `user_specified_pattern` | This is the only source that extracts and matches on pattern variables. `content_type_url_pattern` would ignore the pattern and look for `url` instead |
| **Freeform** template, identity URL | `//` | `default_url_pattern` | The Studio-generated default. Use this unless the path was deliberately hand-chosen |
diff --git a/skills/studio/author-composition-thumbnails.md b/skills/studio/author-composition-thumbnails.md
index 1b84cfc..0a098a0 100644
--- a/skills/studio/author-composition-thumbnails.md
+++ b/skills/studio/author-composition-thumbnails.md
@@ -14,13 +14,13 @@ Part of the API-authoring set. Start at [`author-composition-via-api`](author-co
**Symptom:** every API-authored Section shows a blank/placeholder tile in Studio's **Sections** accordion, while UI-authored ones show a picture of the section. Nothing errors.
-**Why.** Studio's Sections accordion renders `section.ui_preview.url` (`ui_preview` is a `file` field holding an asset). When an author saves a Section **in the Studio UI**, the app screenshots the canvas node, converts the base64 to a blob, uploads it as an asset, and PUTs `{ entry: { ui_preview: } }` on the composition entry, fire-and-forget, master locale, then invalidates the sections query so the tile appears without a reload. **That whole chain lives in the editor.** A composition entry created by CMA has `ui_preview` empty forever.
+**Why.** Studio's Sections accordion renders `section.ui_preview.url` (`ui_preview` is a `file` field holding an asset). When an author saves a Section **in the Studio UI**, or opens one that has no thumbnail, the app screenshots the canvas node, converts the base64 to a blob, uploads it as an asset, and PUTs `{ entry: { ui_preview: } }` on the composition entry, fire-and-forget, master locale, then invalidates the sections query so the tile appears without a reload. **That whole chain lives in the editor.** A Section created through the API or the CMA has `ui_preview` empty until someone opens or saves it in Studio.
Same asymmetry family as `linked_sections`: the UI populates a field behind the scenes, so API authoring must do it explicitly.
### Fix A: zero-script backfill (best when the sections already exist)
-Open each Section in Studio and hit **Save**, no edits needed. The screenshot/upload/PUT chain runs on save and backfills `ui_preview` with a real render of the section. Cheapest correct fix, and the thumbnail is an actual screenshot rather than a placeholder. Only worth scripting instead when the section count makes clicking impractical.
+Open each Section in Studio, no edits needed. A Section with no thumbnail gets one once the canvas has rendered it, and **Save** takes one too. A Section whose props bind to entry data gets none on open until you pick a preview entry for it. The screenshot/upload/PUT chain backfills `ui_preview` with a real render of the section. Cheapest correct fix, and the thumbnail is an actual screenshot rather than a placeholder. Only worth scripting instead when the section count makes clicking impractical.
### Fix B: pick an image in the Edit Section modal (no canvas render, no script)
diff --git a/skills/studio/byoc-end-to-end.md b/skills/studio/byoc-end-to-end.md
index 8bcffd7..05a24fd 100644
--- a/skills/studio/byoc-end-to-end.md
+++ b/skills/studio/byoc-end-to-end.md
@@ -122,7 +122,7 @@ Phase 0 includes project bootstrap. Recommended: Next.js App Router (catch-all r
| Studio canvas blank, no console error | env Base URL empty, or Canvas URL set to a full URL instead of a path | `setup-section-preview` + `understand-canvas-url` |
| Components don't show up in the palette | Registration file isn't imported at app startup | `register-component`: `import "./register-components"` from the app entry |
| Sections panel is empty even after Phase 2 finished | Wrong project selected in Studio top-bar | switch project, OR re-run `provision-studio-project` if it was provisioned against a different stack |
-| "Internal components missing: page" on the visitor route | SDK predates the release that auto-registers built-ins on the visitor render path | `upgrade-studio-sdk`: bump to the patch that ships the visitor-path auto-register |
+| `COMPONENT_NOT_REGISTERED` naming `page` on the visitor route | The visitor bundle never imports `@contentstack/studio-react` | `troubleshoot-ssr-rendering`: add that import above the fetch |
| Template renders in Studio but not on the live route | The visitor route isn't mounting ``, OR is mounting `` by mistake | `understand-canvas-vs-component` + `setup-template-preview-routes` |
| "registered as lazy but hasn't been loaded yet" | `fetchSpec` / `fetchCompositionData` wasn't awaited before `` mounted | `configure-csr-vs-ssr`: the SSR / RSC patterns show the right await placement |
| `useData()` warning in dev console only | Benign. Basic component hit the SSR pass before `DataCtxProvider` attached. Disappears post-hydration. | `troubleshoot-ssr-rendering`: confirm it's a warning, not an error |
diff --git a/skills/studio/configure-csr-vs-ssr.md b/skills/studio/configure-csr-vs-ssr.md
index 0811f3f..51a09d8 100644
--- a/skills/studio/configure-csr-vs-ssr.md
+++ b/skills/studio/configure-csr-vs-ssr.md
@@ -22,7 +22,7 @@ Pick your recipe from the framework recipes chapter and copy it verbatim:
- **Next.js App Router: RSC entry**: CI-tested on Next 14 + Next 16. Smallest possible client bundle.
- **Next.js App Router: client-component SSR**: main-entry path, simplest wiring, full SEO.
-Same three-call contract everywhere (`fetchCompositionData`, then ``, then `getSSRStyleTags` or ``), plus `getCompositionMetadata` for SEO. Sections below cover the render-strategy decision itself. The mechanics live in the recipes above.
+Same contract everywhere: `fetchCompositionData`, `` (`` on the RSC entry), then the server styles (`getSSRStyles` in React, `getSSRStyleTags` outside React, or `` on the RSC entry), plus `getCompositionMetadata` for SEO. Sections below cover the render-strategy decision itself. The mechanics live in the recipes above.
## Context
@@ -44,18 +44,18 @@ const specOptions = await sdk.fetchCompositionData({ url, searchQuery }, { local
## The render-pipeline rule (read this first)
-What determines **true SSR** (the composition DOM in the server HTML) is whether `` renders through a **`renderToString` pipeline** (Pages Router / Vite custom server / Remix: server React dispatcher present, built-ins execute server-side) OR through **React Server Components** (App Router: built-ins are `"use client"` and the SDK renderer detects them as `Symbol.for("react.client.reference")` and so emits `data-cs-defer-builtin` placeholders, deferring real render to client hydration). The data-fetch function (`fetchCompositionData` vs `useCompositionData`) is orthogonal and does **not** by itself produce SSR.
+What determines **true SSR** (the composition DOM in the server HTML) is whether `` renders through a **`renderToString` pipeline** (Pages Router / Vite custom server / Remix) or, on App Router, through a **`"use client"` wrapper**, which Next also renders to HTML on the server. The RSC entry (`@contentstack/studio-react/rsc`) is the other App Router path: its `` renders in a Server Component. `@contentstack/studio-react` ships no `"use client"` directive and its renderer calls hooks, so a Server Component cannot render it directly. The data-fetch function (`fetchCompositionData` vs `useCompositionData`) is orthogonal and does **not** by itself produce SSR.
-**Net:** every mainstream host, including Next App Router (main entry and RSC entry), Pages Router, Remix, Astro, Gatsby, and plain Node, renders the composition into the server HTML via the SDK's three-call contract. See [`install-studio`](install-studio.md) for the per-host wiring. The historical `data-cs-defer-builtin` placeholder path was removed in a later SDK release.
+**Net:** every mainstream host, including Next App Router (main entry and RSC entry), Pages Router, Remix, Astro, Gatsby, and plain Node, renders the composition into the server HTML via the SDK's contract. See [`install-studio`](install-studio.md) for the per-host wiring. The main entry's `` can't render straight from a Server Component (its renderer calls hooks): put it behind a `"use client"` wrapper, or render `` from `@contentstack/studio-react/rsc`.
## Decision table
| Situation | Path | True SSR? (composition DOM in server HTML) |
| --- | --- | --- |
| Vite / React SPA, no SEO | **CSR**: `useCompositionData` | No (client only) |
-| **Next.js Pages Router** | **SSR**: `sdk.fetchCompositionData` inside `getServerSideProps` | **Yes**: `renderToString` |
-| **Next.js App Router (main entry)** | `sdk.fetchCompositionData` in a Server Component, then a client wrapper renders `` | **Yes**: Next server-renders the client component's initial output |
-| **Next.js App Router (RSC entry)** | `sdk.fetchCompositionData` in a Server Component, then `` plus `` from `@contentstack/studio-react/rsc` | **Yes**: ~142B client bundle for structural content. Custom components register as client islands automatically |
+| **Next.js Pages Router** | **SSR**: `sdk.fetchCompositionData` and `getSSRStyles` inside `getServerSideProps` | **Yes**: `renderToString` |
+| **Next.js App Router (main entry)** | `sdk.fetchCompositionData` and `getSSRStyles` in a Server Component, then a client wrapper renders `` | **Yes**: Next server-renders the client component's initial output |
+| **Next.js App Router (RSC entry)** | `sdk.fetchCompositionData` in a Server Component, then `` plus `` from `@contentstack/studio-react/rsc` | **Yes**: ~142B client bundle for structural content. Custom components register as client islands automatically |
| Vite custom-server SSR | `renderToString` + `hydrateRoot` + `sdk.fetchCompositionData` | **Yes** |
| Remix | `sdk.fetchCompositionData` inside the `loader` | **Yes**: `renderToString` |
| Static / rarely changing | **SSG/ISR**: `getStaticProps` + `revalidate` | **Yes** (at build/revalidate. Via `renderToString`) |
@@ -71,7 +71,7 @@ On a miss, `sdk.fetchCompositionData` **throws**. It does not resolve with `hasS
What it throws is a `ComposableStudioError`. **Do not branch on a list of names.** Call `isCompositionNotFound(err)`, exported for exactly this decision:
```ts
-import { isCompositionNotFound } from "@contentstack/studio-client";
+import { isCompositionNotFound } from "@contentstack/studio-react";
```
It answers *did we establish that nothing is here?* and is the only thing that should decide a 404. A list of names has to be kept in step with the SDK, and the cost of drift is one-way: mistake a setup failure for a missing page and a wrong api key or a delivery outage returns **404 on pages that exist**, de-indexing a working site.
@@ -80,7 +80,7 @@ Also on the error: `err.code` (the precise case, for logs and support) and `err.
```ts
// lib/resolve-composition.ts
-import { isCompositionNotFound } from "@contentstack/studio-client";
+import { isCompositionNotFound } from "@contentstack/studio-react";
export async function resolveComposition(sdk, args, opts = {}) {
try {
@@ -139,8 +139,7 @@ export function createStudioSdk() {
2. **CSR (Vite / SPA, also the universal fallback on any framework, including App Router).**
```tsx
"use client";
- import { useCompositionData, StudioComponent } from "@contentstack/studio-react";
- import { isCompositionNotFound } from "@contentstack/studio-client";
+ import { useCompositionData, StudioComponent, isCompositionNotFound } from "@contentstack/studio-react";
// Loading, ErrorState and NotFound are your app's own components.
import { Loading, ErrorState, NotFound } from "@/components/states";
@@ -164,9 +163,10 @@ export function createStudioSdk() {
**Live Preview wiring is separate.** If this route needs to reflect author edits in real time, add the `onEntryChange` bridge from `install-live-preview` (step 7). CSR pages call the hook's `refetchData` inside the loop-safe callback — it re-fetches just the bound data, which is all a live edit changes.
-3. **App Router (main entry): SSR through a client wrapper.** A Server Component fetches, a `"use client"` wrapper renders, and Next puts that output in the initial HTML (verified with `curl` in the App Router recipe). Initialize the sdk in a server-only module (no `"use client"`). Register components in a `"use client"` module the wrapper imports. Rendering `` directly in a Server Component makes built-ins `react.client.reference` proxies: the SDK emits `data-cs-defer-builtin` placeholders, filled only on hydration.
+3. **App Router: SSR through a client wrapper.** A Server Component fetches and computes the styles, a `"use client"` wrapper renders, and Next puts that output in the initial HTML (verified with `curl` in the App Router recipe). Initialize the sdk in a server-only module (no `"use client"`). Register components in a `"use client"` module the wrapper imports. Rendering `` directly in a Server Component fails with a Next.js client-component error: the renderer calls hooks.
```tsx
// app/[[...slug]]/page.tsx — NO "use client"
+ import { getSSRStyles } from "@contentstack/studio-react";
import { sdk } from "@/lib/studio.server";
import { resolveComposition } from "@/lib/resolve-composition";
import { ClientRender } from "./ClientRender";
@@ -188,7 +188,13 @@ export function createStudioSdk() {
// 404s every freeform page however it is published. `miss` already carries
// the only not-found answer there is.
if (miss) notFound();
- return ;
+ // getSSRStyles here, never in ClientRender: hydration mismatch on the >);
+ return ;
}
```
- **Next.js Pages Router** (`pages/.tsx`), same SSR-mismatch reason, wrap in `dynamic({ ssr: false })`:
+ **Next.js Pages Router** (`pages/.tsx`), same body without the directive:
```tsx
- import dynamic from "next/dynamic";
- const StudioCanvas = dynamic(
- () => import("@contentstack/studio-react").then((m) => m.StudioCanvas),
- { ssr: false },
- );
+ import { StudioCanvas } from "@contentstack/studio-react";
export default function CanvasRoute() {
return ;
}
```
+ No `dynamic({ ssr: false })` needed on studio-react 2.x: `StudioCanvas` is a client component that renders `null` on the server and on the first client render, and builds the canvas only after mount, so there is nothing to mismatch. Older snippets that wrap it in `dynamic()` still work; they're just unnecessary.
+
**Vite + React Router** (`src/routes/CanvasRoute.tsx` + router config update), Vite is SPA-only, so a plain import is safe:
```tsx
import { StudioCanvas } from "@contentstack/studio-react";
@@ -112,7 +150,7 @@ Only once 1 to 3 hold, continue.
}
```
- **Every framework: render `` beside ``.** In studio-react 1.9.0 `StudioCanvas` writes no design-token `:root` variables, so token styles (padding, max-width, colors) resolve to nothing on the canvas.
+ **No token `` to a client canvas page: the server and the browser hold different token registries, so the string differs between them and React reports a hydration error on every load. Only on studio-react 1.9.x, where `StudioCanvas` wrote no token variables, add them **after mount**: `const [css, setCss] = useState(""); useEffect(() => setCss(getSSRStyles(null)), []);`, then render `{css ? : null}`.
Convert `` to the framework's expected filename (e.g. `/canvas` becomes `canvas/page.tsx` for App Router, or `canvas.tsx` for Pages Router).
@@ -158,7 +196,6 @@ Only once 1 to 3 hold, continue.
const canvasHeaders = [
// Studio runs on *.contentstack.com (all regions). Add your own origin if you embed the canvas elsewhere.
{ key: 'Content-Security-Policy', value: "frame-ancestors 'self' https://*.contentstack.com;" },
- { key: 'Access-Control-Allow-Origin', value: '*' },
];
return [
{ source: '/canvas', headers: canvasHeaders },
@@ -168,7 +205,7 @@ Only once 1 to 3 hold, continue.
};
```
- The Vite blocks above are for local use only and allow framing by Studio's origin, nothing wider. Don't add `frame-ancestors *` or `Access-Control-Allow-Origin: *`, and don't ship a global framing policy to production: scope it to the canvas route.
+ The Vite blocks above are for local use only and allow framing by Studio's origin, nothing wider. Don't add `frame-ancestors *` or `Access-Control-Allow-Origin: *`: framing is governed by `frame-ancestors` alone, and CORS headers on the page play no part in it. Don't ship a global framing policy to production either: scope it to the canvas route.
**Why it matters:** the `frame-ancestors` CSP header tells the browser Studio's origin may embed the page; without it, Studio's iframe stays blank with no console error, a particularly hard failure to diagnose. If you reach the server through a non-localhost hostname (e.g. a tunnel) and Vite returns "host not allowed" (403), list that hostname in `allowedHosts`; never set it to `true`.
@@ -187,10 +224,11 @@ Only once 1 to 3 hold, continue.
- [ ] BEFORE creating the route: confirmed the targeted environment has a non-empty per-locale URL equal to the dev origin, AND the Studio project targets that environment
- [ ] Canvas route file exists in the right location for the detected framework
-- [ ] The file mounts `` and the token `` on the canvas page: it now only causes a hydration error. `grep -rn 'getSSRStyles(null)' src app` | [`setup-section-preview`](setup-section-preview.md) |
+| Internal packages | `studio-client` 3.2, `studio-registry` 1.10 | `studio-client` 4.1, `studio-registry` 2.0 | Nothing, unless `package.json` pins one of them directly: then bump it in the same change (step 3) or you get two registries | Step 3 |
+
+### Also check: rules older guides and code got wrong
+
+These behave the same on 1.9.0 and 2.0.1, so they are not 2.0 changes. Code copied from older versions of these guides often breaks them, and an upgrade is a good time to fix it. TypeScript reports the first four.
+
+| Rule | Wrong | Right | Find it | Details |
+|---|---|---|---|---|
+| `object` props need `properties` | `{ type: "object" }` | `{ type: "object", properties: { … } }` | `grep -rn 'type: "object"' src` | [`register-component`](register-component.md) step 3 |
+| `array` props need `items` | `{ type: "array" }` | `{ type: "array", items: { type: "any" } }` (or the per-item prop) | `grep -rn 'type: "array"' src` | [`register-component`](register-component.md) step 3 |
+| Lazy components register through `registerLazyComponent` | `registerComponent({ …, component: () => import("./X") })` (runs, but doesn't type-check) | `registerLazyComponent(config, () => import("./X").then((m) => m.X))`; the loader resolves to the component, not the module | `grep -rn 'component: () => import' src` | [`register-component`](register-component.md) § 5b |
+| The hook takes no `searchQuery` | `useCompositionData({ url, searchQuery })` | `useCompositionData({ url })`; it reads `window.location` itself | `grep -rn 'useCompositionData(' src app` | [`setup-template-preview-routes`](setup-template-preview-routes.md) |
+| `styles` is an object of style groups | `styles: [ … ]` | `styles: { default: …, : … }`; `default` arrives as `className` | `grep -rn 'styles: \[' src` | [`register-component`](register-component.md) § 5c |
+| `extendQuery` is deprecated | relied on silently | Still honoured, warns in development; no documented replacement for its `only` half | `grep -rn 'extendQuery' src app` | [`register-component-prop-contracts`](register-component-prop-contracts.md) |
+| `refetchData` needs a connected composition | `refetchData` on every page | Fall back to `refetchSpec` on freeform compositions and before the first load | `grep -rn 'refetchData' src app` | [`csr-and-ssr-requirements`](csr-and-ssr-requirements.md) |
+
+**After the bump:** restart the dev server (a running server keeps the old registrations), run the type check, then the smoke tests in step 4. Check the Components panel on the canvas: a registration that fails at runtime drops out of the palette without an error on the page.
+
## Knowing what actually changed
-Each package publishes a CHANGELOG derived from Conventional Commits. Read the CHANGELOG for every package in your delta before upgrading.
+There is no public changelog for these packages (step 2). Read the compiler output and § Upgrading from 1.x to 2.0. Within a major, compare the installed `.d.ts` files before and after (`diff -r` two installs of `node_modules/@contentstack/studio-react/dist`) when you need more than the type check gives you.
-A non-exhaustive list of changes that historically required consumer-side action (read each from its CHANGELOG before claiming it applies to your delta):
+A non-exhaustive list of older changes that required consumer-side action (confirm each applies to your delta before claiming it does):
- **Registry singleton moved to `globalThis`** (`studio-registry` / `studio-react` coordinated bump): required for RSC server-side fetch to see registered components. If you're upgrading FROM a pre-singleton version TO a post-singleton version, server-side `fetchSpec` calls that used to return "component not registered" will now resolve. No consumer code change beyond the upgrade.
- **Basics moved behind a `"use client"` isolation boundary** (`studio-react`): fixes "Attempted to call Page() from the server" in Next App Router. No consumer code change required. The renderer pulls in the client-only basics automatically.
- **Tolerant `useData` fallback** (`studio-react-components`): replaces a throw with an empty-data fallback plus a dev-only warning. SSR pass of basics no longer crashes outside a `DataCtxProvider`. Consumer-side: dev console may show one new warning, production unchanged.
- **`fetchCompositionData` requires `searchQuery` on the server**: see [`configure-csr-vs-ssr.md`](configure-csr-vs-ssr.md). Required for locale/variant/preview to flow from Studio's iframe params. Pre-existing as an integration step.
-Do NOT extrapolate from this list. **Only the actual CHANGELOG entries for your delta** tell you which of these apply.
+Do NOT extrapolate from this list. Confirm each against the installed types or a smoke test before claiming it applies.
## What this skill is NOT
- Not a guide to first-time installation (use [`install-studio`](install-studio.md)).
- Not a guide to SDK-internal development.
-- Not a substitute for reading the CHANGELOG. Always read the actual CHANGELOG entries for your version delta.
+- Not a substitute for the type check and smoke test. Run both for every version delta.
- Not a guide to upgrading the Contentstack Delivery SDK (`@contentstack/delivery-sdk`) or Live Preview Utils (`@contentstack/live-preview-utils`): those are separate packages with their own CHANGELOGs, only loosely coupled to the Studio SDK.
diff --git a/skills/studio/wire-studio-state.md b/skills/studio/wire-studio-state.md
index de787ba..434fd34 100644
--- a/skills/studio/wire-studio-state.md
+++ b/skills/studio/wire-studio-state.md
@@ -26,7 +26,7 @@ Do **not** use `node -e "require('@contentstack/studio-react/package.json')"`: t
**Availability has two independent halves**: the SDK in your app and the Studio editor. The editor half (declared variables in the Data Picker, the `action` prop picker) is **not yet rolled out on every data center**, so a stack whose editor lacks it can't author BYOS even with a correct SDK build. Check the DC alongside the export: [`analyze-project-fit`](analyze-project-fit.md) § 2b.
-**`BYOS ABSENT` means stop.** Releases up to `1.7.0` are ABSENT; `1.9.0` ships it. This chapter applies only to a build that ships it. Either obtain such a build, or fall back to component-local state (a plain `useState` inside one registered component, no author binding, no cross-component sharing) and say so explicitly. Don't emit `useStudioState` against a package that can't import it.
+**`BYOS ABSENT` means stop.** Releases up to `1.7.0` are ABSENT; `1.8.0` and later ship it (checked on npm: 1.8.0's `index.d.ts` exports `registerGlobalStateVariables`, `studioState` and `useStudioState`). This chapter applies only to a build that ships it. Either obtain such a build, or fall back to component-local state (a plain `useState` inside one registered component, no author binding, no cross-component sharing) and say so explicitly. Don't emit `useStudioState` against a package that can't import it.
## Context
@@ -146,7 +146,7 @@ Registered components give Studio your markup. **BYOS gives it your behavior.**
## Acceptance
-- [ ] **Preflight passed**: the installed `@contentstack/studio-react` actually exports the state APIs (≥ 1.7.0). Nothing was written against a version that can't import them.
+- [ ] **Preflight passed**: the installed `@contentstack/studio-react` actually exports the state APIs (1.8.0 or later; 1.7.0 does not). Nothing was written against a version that can't import them.
- [ ] Every variable authors need is **declared** (init or component tier): nothing relies on an ad-hoc key being bindable.
- [ ] The tier choice is deliberate, and no key is declared in both places expecting init to win.
- [ ] `storage` is set explicitly, and for `custom` both `getState` and `setState` exist.