@weaverse/hydrogen 6.1.1 → 6.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +6 -5
- package/dist/index.mjs +2 -1
- package/dist/internal.js +17 -16
- package/dist/internal.mjs +3 -2
- package/dist/{translation-store-BpPWqZ0B.js → nested-key-CR3-YMFi.js} +1 -68
- package/dist/nested-key-CR3-YMFi.js.map +1 -0
- package/dist/{translation-store-C42QdI0e.mjs → nested-key-_k9aW_Wd.mjs} +2 -57
- package/dist/nested-key-_k9aW_Wd.mjs.map +1 -0
- package/dist/next.js +14 -11
- package/dist/next.mjs +4 -5
- package/dist/react-router.js +13 -6
- package/dist/react-router.mjs +5 -4
- package/dist/react.js +6 -0
- package/dist/react.mjs +2 -2
- package/dist/translation-store-4fAV771j.js +69 -0
- package/dist/translation-store-4fAV771j.js.map +1 -0
- package/dist/translation-store-BBrw0FOk.mjs +58 -0
- package/dist/translation-store-BBrw0FOk.mjs.map +1 -0
- package/dist/types/WeaverseHydrogenRoot.d.ts.map +1 -1
- package/dist/types/next.d.ts +7 -3
- package/dist/types/next.d.ts.map +1 -1
- package/dist/types/react.d.ts +1 -1
- package/dist/types/react.d.ts.map +1 -1
- package/dist/types/types.d.ts +7 -1
- package/dist/types/types.d.ts.map +1 -1
- package/dist/types/utils/use-studio.d.ts.map +1 -1
- package/dist/{weaverse-client-CNPZ6ueD.js → weaverse-client-C9IgIH7O.js} +69 -55
- package/dist/weaverse-client-C9IgIH7O.js.map +1 -0
- package/dist/{weaverse-client-6ztuUmvu.mjs → weaverse-client-D59mBW0j.mjs} +24 -10
- package/dist/weaverse-client-D59mBW0j.mjs.map +1 -0
- package/package.json +5 -4
- package/src/WeaverseHydrogenRoot.tsx +15 -9
- package/src/next.ts +23 -4
- package/src/react.ts +2 -0
- package/src/types.ts +7 -0
- package/src/utils/use-studio.ts +20 -2
- package/dist/translation-store-BpPWqZ0B.js.map +0 -1
- package/dist/translation-store-C42QdI0e.mjs.map +0 -1
- package/dist/weaverse-client-6ztuUmvu.mjs.map +0 -1
- package/dist/weaverse-client-CNPZ6ueD.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"weaverse-client-D59mBW0j.mjs","names":["getSelectedProductOptions","hydrogen_getSelectedProductOptions","MainComponent","mainSchema","isPlainObject"],"sources":["../src/hooks/translation-context.tsx","../src/seo.ts","../src/utils/product-options.ts","../src/components/main.tsx","../src/components/index.ts","../src/hooks/use-weaverse-data-context.ts","../src/utils/pick-weaverse-data.ts","../src/utils/studio-request-info.ts","../src/utils/is-same-loader-payload.ts","../src/utils/sync-reused-instance.ts","../src/utils/use-theme-settings-store.ts","../src/utils/use-studio.ts","../src/WeaverseHydrogenRoot.tsx","../src/utils/normalize-page-url.ts","../src/weaverse-client.ts"],"sourcesContent":["import { useSafeExternalStore } from '@weaverse/react'\nimport { createContext, type ReactNode, useContext, useMemo } from 'react'\nimport { getNestedKey } from '../utils/nested-key'\n\nexport { getNestedKey }\n\nimport type { TranslationStore } from '../utils/translation-store'\n\n/**\n * Replace `{{variable}}` patterns in a string with provided values.\n *\n * @example\n * interpolate('Hello {{name}}!', { name: 'World' }) // => 'Hello World!'\n * interpolate('-{{percentage}}% Off', { percentage: 15 }) // => '-15% Off'\n */\nexport function interpolate(\n template: string,\n variables?: Record<string, string | number>\n): string {\n if (!variables) {\n return template\n }\n\n return template.replace(/\\{\\{(\\w+)\\}\\}/g, (match, key: string) => {\n const value = variables[key]\n return value === undefined ? match : String(value)\n })\n}\n\n// ---------------------------------------------------------------------------\n// Context & Provider\n// ---------------------------------------------------------------------------\n\n/**\n * Signature for a translation function.\n */\nexport type TranslateFunction = (\n key: string,\n variables?: Record<string, string | number>\n) => string\n\n/** Translation function, stores, and locale overrides exposed by the provider. */\nexport type TranslationValue = {\n /**\n * Translate a key with optional variable interpolation.\n *\n * Priority chain (highest wins):\n * 1. External `t` (passed into `withWeaverse` / `TranslationProvider`)\n * 2. `merchantOverrides` (DB overrides for the current locale + design overrides)\n * 3. `staticContent` (theme default JSON)\n * 4. The key itself (fallback)\n *\n * @example\n * t('cart.title') // => \"Cart\"\n * t('badge.sale', { percentage: 15 }) // => \"-15% Off\"\n */\n t: TranslateFunction\n /** Exposed for useStudio to attach to weaverse.internal. Null in production. */\n translationStore: TranslationStore | null\n /**\n * @deprecated Use {@link TranslationValue.translationStore} instead. Kept as a\n * backward-compatible alias pointing at the same store instance.\n */\n themeTextStore: TranslationStore | null\n /** Active-locale translated keys loaded from the storefront API. */\n merchantOverrides: Record<string, unknown> | null\n}\n\nconst TranslationContext = createContext<TranslationValue | null>(null)\nTranslationContext.displayName = 'WeaverseTranslationContext'\n\n/** Inputs accepted by {@link TranslationProvider}. */\nexport type TranslationProviderProps = {\n /** Theme-provided default-locale translation content. */\n staticContent: Record<string, unknown>\n /** Active-locale translations published by the merchant. */\n merchantOverrides?: Record<string, unknown>\n /**\n * Optional external translation function.\n * When provided it becomes the **highest-priority** source:\n * if it returns a non-empty string that differs from the raw key,\n * that value is used; otherwise the internal resolution chain continues.\n */\n t?: TranslateFunction\n /** Optional store for live design-mode text overrides from the builder. */\n translationStore?: TranslationStore | null\n /**\n * @deprecated Use {@link TranslationProviderProps.translationStore} instead.\n * Still accepted for backward compatibility; ignored when `translationStore`\n * is also provided.\n */\n themeTextStore?: TranslationStore | null\n /** React content that can consume the translation context. */\n children: ReactNode\n}\n\n/**\n * Build the `t()` translation function over a fixed set of sources.\n *\n * Resolution order, highest priority first:\n * 1. `externalT` – host i18n (e.g. Shopify), unless it echoes the key\n * 2. `designOverrides` – live design-mode edits, flat dot-path keys\n * 3. `merchantOverrides` – locale overrides from the API, nested\n * 4. `staticContent` – the theme's default locale JSON\n * 5. the key itself\n *\n * Design overrides are looked up per-key (own properties only) rather than\n * merged into `merchantOverrides`: a flat key like `cart.title` must override\n * exactly that leaf without wiping sibling keys (`cart.subtitle`), and a\n * per-key own-property lookup never builds nested objects from\n * merchant-controlled keys, so it cannot pollute `Object.prototype`.\n */\nexport function createTranslate(sources: {\n staticContent: Record<string, unknown>\n merchantOverrides?: Record<string, unknown>\n designOverrides?: Record<string, string>\n externalT?: TranslateFunction\n}): TranslateFunction {\n const { staticContent, merchantOverrides, designOverrides, externalT } =\n sources\n return (key, variables) => {\n // Priority 1: external t (e.g. Shopify i18n, third-party library).\n if (externalT) {\n const external = externalT(key, variables)\n // Accept the external value unless it echoed the key unchanged.\n if (external && external !== key) {\n return external\n }\n }\n\n // Priority 2: live design-mode override (flat, keyed by exact dot-path,\n // own properties only so inherited keys never resolve to functions).\n const design =\n designOverrides && Object.hasOwn(designOverrides, key)\n ? designOverrides[key]\n : undefined\n // Priority 3: merchant overrides (nested). Priority 4: static content.\n const override = merchantOverrides\n ? getNestedKey(merchantOverrides, key)\n : undefined\n const staticValue = getNestedKey(staticContent, key)\n\n // Priority 5: the key itself as the final fallback.\n const raw = design ?? override ?? staticValue ?? key\n return interpolate(raw, variables)\n }\n}\n\n// No-op fallback for useSafeExternalStore when store is null (production).\n// Module-level constants — stable references, zero allocation per render.\nconst EMPTY: Record<string, string> = {}\nconst NOOP_SUBSCRIBE = () => () => {}\nconst NOOP_SNAPSHOT = () => EMPTY\n\n/**\n * Provider that wraps the app to expose the `t()` function.\n *\n * - `staticContent` – the theme's default locale JSON (from themeSchema.i18n)\n * - `merchantOverrides` – locale-specific overrides fetched from the API\n * - `translationStore` – optional store for live design-mode text overrides\n * (the deprecated `themeTextStore` prop is accepted as a fallback)\n */\nexport function TranslationProvider({\n staticContent,\n merchantOverrides,\n t: externalT,\n translationStore,\n themeTextStore,\n children,\n}: TranslationProviderProps) {\n // Canonical store, falling back to the deprecated `themeTextStore` prop.\n const store = translationStore ?? themeTextStore ?? null\n\n // Subscribe to design-mode overrides. No-op when store is null (production).\n const designOverrides = useSafeExternalStore(\n store?.subscribe ?? NOOP_SUBSCRIBE,\n store?.getSnapshot ?? NOOP_SNAPSHOT,\n store?.getServerSnapshot ?? NOOP_SNAPSHOT\n )\n\n const value = useMemo<TranslationValue>(\n () => ({\n t: createTranslate({\n staticContent,\n merchantOverrides,\n designOverrides,\n externalT,\n }),\n translationStore: store,\n // Deprecated alias — same instance, kept for backward compatibility.\n themeTextStore: store,\n merchantOverrides: merchantOverrides ?? null,\n }),\n [staticContent, merchantOverrides, designOverrides, externalT, store]\n )\n\n return (\n <TranslationContext.Provider value={value}>\n {children}\n </TranslationContext.Provider>\n )\n}\n\n/**\n * Hook to access the translation `t()` function.\n *\n * Must be used inside a component wrapped by `withWeaverse` or `TranslationProvider`.\n *\n * @example\n * ```tsx\n * function CartTitle() {\n * const { t } = useTranslation()\n * return <h2>{t('cart.title')}</h2>\n * }\n * ```\n */\nexport function useTranslation(): TranslationValue {\n const ctx = useContext(TranslationContext)\n if (!ctx) {\n throw new Error(\n 'useTranslation must be used within <TranslationProvider>. Wrap your app with withWeaverse().'\n )\n }\n return ctx\n}\n\n// ---------------------------------------------------------------------------\n// Deprecated aliases (backward compatibility — kept exported indefinitely)\n// ---------------------------------------------------------------------------\n\n/**\n * Legacy name for {@link TranslationProvider}.\n * @deprecated Use {@link TranslationProvider} instead.\n */\nexport const ThemeTextProvider = TranslationProvider\n/**\n * Legacy name for {@link useTranslation}.\n * @deprecated Use {@link useTranslation} instead.\n */\nexport const useThemeText = useTranslation\n/**\n * Legacy name for {@link TranslationValue}.\n * @deprecated Use {@link TranslationValue} instead.\n */\nexport type ThemeTextValue = TranslationValue\n/**\n * Legacy name for {@link TranslationProviderProps}.\n * @deprecated Use {@link TranslationProviderProps} instead.\n */\nexport type ThemeTextProviderProps = TranslationProviderProps\n","import type { PageSEOData } from '@weaverse/schema'\nimport type { MetaDescriptor } from 'react-router'\nimport type { WeaverseLoaderData, WeaversePageData } from './types'\n\n/** Object containing an optional Weaverse page for SEO extraction. */\nexport interface WeaverseSeoPageContainer {\n /** Page whose published SEO fields should be formatted. */\n page?: WeaversePageData | null\n}\n\n/** Input accepted by {@link getWeaverseSeoMeta}. */\nexport type WeaverseSeoInput =\n | WeaverseLoaderData\n | WeaversePageData\n | WeaverseSeoPageContainer\n | null\n | undefined\n\n/**\n * Convert a Weaverse page-level SEO payload into React Router\n * `MetaDescriptor[]`. Pure, synchronous, tree-shakeable — does not fetch.\n *\n * Behavior:\n * - `title` → top-level `{ title }` descriptor.\n * - `description` / `keywords` → `<meta name=\"...\">`.\n * - `canonicalUrl` → `<link rel=\"canonical\">`.\n * - `openGraph.*` → `<meta property=\"og:*\">`. `og:type` defaults to\n * `'website'` when any other OG field is present.\n * - `twitter.*` → `<meta name=\"twitter:*\">`. `twitter:card` defaults to\n * `'summary'` when any other Twitter field is present.\n * - `robots` is always emitted (defaults to `'index,follow'`) so themes\n * stacking multiple SEO sources have a deterministic last word on\n * indexing for Weaverse-managed pages.\n * - Empty strings / missing fields are filtered before return so empty\n * descriptors don't override earlier sources (e.g. Hydrogen\n * `getSeoMeta`).\n */\nexport function formatMetaDescriptors(\n seo: PageSEOData | null | undefined\n): MetaDescriptor[] {\n if (!seo) {\n return [{ name: 'robots', content: 'index,follow' }]\n }\n\n let descriptors: MetaDescriptor[] = []\n\n if (seo.title) {\n descriptors.push({ title: seo.title })\n }\n if (seo.description) {\n descriptors.push({ name: 'description', content: seo.description })\n }\n if (seo.keywords) {\n descriptors.push({ name: 'keywords', content: seo.keywords })\n }\n if (seo.canonicalUrl) {\n descriptors.push({\n tagName: 'link',\n rel: 'canonical',\n href: seo.canonicalUrl,\n })\n }\n\n let og = seo.openGraph\n if (og && (og.title || og.description || og.image || og.type)) {\n if (og.title) {\n descriptors.push({ property: 'og:title', content: og.title })\n }\n if (og.description) {\n descriptors.push({ property: 'og:description', content: og.description })\n }\n if (og.image) {\n descriptors.push({ property: 'og:image', content: og.image })\n }\n descriptors.push({ property: 'og:type', content: og.type || 'website' })\n }\n\n let tw = seo.twitter\n if (tw && (tw.title || tw.description || tw.image || tw.cardType)) {\n descriptors.push({\n name: 'twitter:card',\n content: tw.cardType || 'summary',\n })\n if (tw.title) {\n descriptors.push({ name: 'twitter:title', content: tw.title })\n }\n if (tw.description) {\n descriptors.push({ name: 'twitter:description', content: tw.description })\n }\n if (tw.image) {\n descriptors.push({ name: 'twitter:image', content: tw.image })\n }\n }\n\n let index = seo.robots?.index !== false\n let follow = seo.robots?.follow !== false\n descriptors.push({\n name: 'robots',\n content: `${index ? 'index' : 'noindex'},${follow ? 'follow' : 'nofollow'}`,\n })\n\n return descriptors\n}\n\n/**\n * Convenience accessor: pull `page.seo` out of a `WeaverseLoaderData`\n * (or `WeaversePageData`) and emit `MetaDescriptor[]`. Null-safe.\n *\n * @example\n * ```ts\n * export const meta: MetaFunction<typeof loader> = ({ data }) => {\n * return getWeaverseSeoMeta(data?.weaverseData)\n * }\n * ```\n */\nexport function getWeaverseSeoMeta(data: WeaverseSeoInput): MetaDescriptor[] {\n if (!data) {\n return formatMetaDescriptors(null)\n }\n let page =\n 'items' in data && 'id' in data\n ? (data as WeaversePageData)\n : ((data as WeaverseSeoPageContainer).page ?? null)\n return formatMetaDescriptors(page?.seo ?? null)\n}\n","import {\n getSelectedProductOptions as hydrogen_getSelectedProductOptions,\n type SelectedOption,\n} from '@shopify/hydrogen'\n\n/**\n * Filter out Weaverse-specific query parameters from product options.\n * Removes `isDesignMode` and any parameters starting with `weaverse` prefix.\n *\n * @param request - The incoming HTTP request\n * @returns Filtered product options for Shopify Hydrogen\n *\n * @example\n * ```typescript\n * export async function loader({ request }: LoaderFunctionArgs) {\n * const selectedOptions = getSelectedProductOptions(request)\n * // Use selectedOptions for product queries without Weaverse params\n * }\n * ```\n */\nexport function getSelectedProductOptions(request: Request): SelectedOption[] {\n let options = hydrogen_getSelectedProductOptions({\n searchParams: new URL(request.url).searchParams,\n })\n return options.filter(\n ({ name }) => name !== 'isDesignMode' && !name.startsWith('weaverse')\n )\n}\n","import { createSchema, type SchemaType } from '@weaverse/schema'\nimport type { HTMLAttributes } from 'react'\nimport type { WeaverseComponentProps } from '../types'\n\n/*\n Main is the default Weaverse component that is used to render the main content.\n This component wraps all the sections/components inside a Weaverse page.\n*/\nfunction Main(props: WeaverseComponentProps & HTMLAttributes<HTMLDivElement>) {\n let { children, dangerouslySetInnerHTML, ...rest } = props\n\n return (\n <div\n {...rest}\n // biome-ignore lint/correctness/noChildrenProp: <explanation>\n children={children}\n // biome-ignore lint/security/noDangerouslySetInnerHtmlWithChildren: <explanation>\n dangerouslySetInnerHTML={children ? undefined : dangerouslySetInnerHTML}\n />\n )\n}\n\nexport default Main\n\nexport let schema: SchemaType = createSchema({\n type: 'main',\n title: 'Main',\n})\n","import type { WeaverseComponent } from '../types'\nimport MainComponent, { schema as mainSchema } from './main'\n\n/** Built-in components required to render the root of every Weaverse page. */\nexport let defaultComponents: WeaverseComponent[] = [\n {\n default: MainComponent,\n schema: mainSchema,\n },\n]\n","import { type UIMatch, useMatches } from 'react-router'\n\n/** Loader data indexed by React Router's conventional route identifiers. */\nexport interface RouteKeyedDataContext {\n /** Root route loader data, when the root loader returned an object. */\n root?: Record<string, unknown>\n /** Loader data for a non-root route keyed by its route identifier. */\n [routeKey: `routes/${string}`]: Record<string, unknown>\n}\n\n/**\n * Flat loader-data map keyed by React Router route ID.\n * Use route IDs such as `root` or `routes/products.$handle` in data bindings.\n */\nexport type WeaverseDataContext = RouteKeyedDataContext &\n Record<string, unknown>\n\n/**\n * Creates flat route-keyed structure from matches\n * Target: {root: {...rootData}, \"routes/product\": {...productData}}\n * Eliminates all duplication by using route IDs as keys\n */\nfunction createFlatDataContext(\n matches: UIMatch<unknown, unknown>[]\n): WeaverseDataContext {\n const context: WeaverseDataContext = {}\n\n // Process matches from root to leaf, creating route-keyed structure\n for (const match of matches) {\n if (match.data && typeof match.data === 'object') {\n // Use route ID as key (e.g., \"root\", \"routes/product\")\n context[match.id] = match.data\n }\n }\n\n return context\n}\n\n/**\n * Hook to get flat route-keyed data context from matches\n * Returns: {root: {...}, \"routes/xxx\": {...}}\n * No more useLoaderData dependency - everything comes from matches\n */\nexport function useWeaverseDataContext(): WeaverseDataContext {\n const matches = useMatches()\n\n // Create flat route-keyed structure - eliminates duplication\n return createFlatDataContext(matches)\n}\n\n/**\n * Helper function to create flat route-keyed data context from useMatches() result\n * Returns: {root: {...}, \"routes/xxx\": {...}}\n */\nexport function createWeaverseDataContext(\n matches: UIMatch<unknown, unknown>[]\n): WeaverseDataContext {\n // Create flat route-keyed structure - clean and efficient\n return createFlatDataContext(matches)\n}\n\n/**\n * Type guard to check if data is WeaverseDataContext (simplified)\n */\nexport function isWeaverseDataContext(\n data: unknown\n): data is WeaverseDataContext {\n return data !== null && typeof data === 'object'\n}\n","import type { UIMatch } from 'react-router'\nimport type { WeaverseLoaderData } from '../types'\n\n/**\n * Weaverse page data accepted by {@link WeaverseHydrogenRoot}, including\n * deferred React Router loader results and an explicit no-render value.\n */\nexport type WeaverseDataValue =\n | WeaverseLoaderData\n | Promise<WeaverseLoaderData>\n | null\n\n/**\n * Resolve the `weaverseData` payload for a `<WeaverseHydrogenRoot>` instance.\n *\n * Resolution order (first match wins):\n *\n * 1. **Explicit prop** — the caller passed `data` directly. Used as-is,\n * including when it is a `Promise` (handled upstream via `<Await>`) or\n * an explicit `null` (suppresses rendering).\n * 2. **Own route's loader data** — the loader data for the route that\n * rendered this component, obtained via `useLoaderData()`. This is\n * React Router's route-scoped primitive and is what makes nested\n * layout + child Weaverse pages work: each `<WeaverseContent />`\n * resolves to *its own* route's `weaverseData` regardless of siblings.\n * 3. **Ancestor match fallback** — walk `useMatches()` from leaf to root\n * and return the first ancestor whose `loaderData` exposes a\n * `weaverseData` key. This preserves the long-standing behaviour where\n * a child route with no loader (e.g. an `index.tsx` that omits\n * `weaverseData`) still renders its layout's Weaverse page.\n *\n * The helper is intentionally pure (no hook calls) so it can be unit-tested\n * without a React Router context. Hook calls happen in the component.\n *\n * ## Back-compat contract\n *\n * Themes that pre-date the `data` prop addition pass `dataProp === undefined`,\n * so resolution starts at Tier 2. For the canonical single-instance pattern\n * (one `<WeaverseContent />` inside a route whose loader returns\n * `{ weaverseData }`), Tier 2 returns the same value the old `useMatches()`\n * walk did. For the index-without-loader pattern (leaf route exposes no\n * `weaverseData`, layout does), Tier 3's leaf→root walk produces an\n * identical result to the pre-fix code. The only intentional behaviour\n * change is the issue #451 case: two `<WeaverseContent />` instances on the\n * same URL (layout + child, both with their own `weaverseData`) now each\n * render their own page instead of both rendering the deepest one.\n */\nexport function pickWeaverseData(\n dataProp: WeaverseDataValue | undefined,\n ownLoaderData: unknown,\n matches: ReadonlyArray<Pick<UIMatch, 'loaderData'>>\n): WeaverseDataValue {\n // Tier 1: explicit prop wins (including explicit `null`).\n if (dataProp !== undefined) {\n return dataProp\n }\n\n // Tier 2: own route's loader data — the route-scoped primitive.\n if (hasWeaverseData(ownLoaderData)) {\n return ownLoaderData.weaverseData as WeaverseDataValue\n }\n\n // Tier 3: ancestor walk (leaf → root) for legacy / index-without-loader cases.\n for (let i = matches.length - 1; i >= 0; i--) {\n const loaderData = matches[i].loaderData\n if (hasWeaverseData(loaderData)) {\n return loaderData.weaverseData as WeaverseDataValue\n }\n }\n\n return null\n}\n\nfunction hasWeaverseData(\n value: unknown\n): value is Record<string, unknown> & { weaverseData: unknown } {\n // The `in` check alone returns true for `{ weaverseData: undefined }`,\n // which would let Tier 2 short-circuit and return `undefined` — bypassing\n // the Tier 3 ancestor walk that would otherwise resolve the missing data.\n // Require the value to be present (not `undefined`).\n if (\n typeof value !== 'object' ||\n value === null ||\n !('weaverseData' in (value as Record<string, unknown>))\n ) {\n return false\n }\n return (value as Record<string, unknown>).weaverseData !== undefined\n}\n","import type { WeaverseHydrogenParams } from '../types'\n\ninterface BrowserLocationLike {\n pathname: string\n search: string\n}\n\nfunction getBrowserLocation(): BrowserLocationLike | undefined {\n if (typeof window === 'undefined') {\n return\n }\n return window.location\n}\n\nfunction getQueriesFromSearch(\n search: string\n): Record<string, string | boolean> {\n let queries: Record<string, string | boolean> = {}\n let params = new URLSearchParams(search)\n for (let [key, value] of params.entries()) {\n if (value === 'true') {\n queries[key] = true\n } else if (value === 'false') {\n queries[key] = false\n } else {\n queries[key] = value\n }\n }\n return queries\n}\n\n/**\n * React Router v7 single-fetch revalidation can render client data returned\n * from a `*.data` request while the browser is still on the canonical URL.\n * Instance identity must follow the browser URL, not the transient data\n * endpoint, otherwise the SDK creates a fresh Weaverse instance mid-session:\n * in design mode that drops the existing Studio bridge/interactions, and in\n * the live theme it resets every section's runtime state after any\n * revalidation (cart mutations, locale switches, `revalidate()`). See #451.\n */\nexport function normalizeRequestInfo(\n params: WeaverseHydrogenParams,\n browserLocation: BrowserLocationLike | undefined = getBrowserLocation()\n): WeaverseHydrogenParams {\n if (!browserLocation) {\n return params\n }\n\n let { pathname, search } = browserLocation\n if (\n params.requestInfo.pathname === pathname &&\n params.requestInfo.search === search\n ) {\n return params\n }\n\n return {\n ...params,\n requestInfo: {\n ...params.requestInfo,\n pathname,\n queries: getQueriesFromSearch(search),\n search,\n },\n }\n}\n","/**\n * Structural comparison used to detect whether a reused Weaverse instance\n * must adopt a fresh loader payload.\n *\n * `JSON.stringify` cannot do this job on the client:\n *\n * - Route loader data reaches `dataContext` unserialized, so deferred values\n * are still `Promise`s. React 19 development builds hang an enumerable,\n * self-referencing `_debugInfo` off those promises, and stringifying one\n * throws `TypeError: Converting circular structure to JSON` during render\n * (issue #511).\n * - In production every promise stringifies to `{}`, so a *fresh* deferred\n * value looks unchanged and the reused instance keeps serving the previous\n * render's promise. Tokenizing promises to a constant (`'[[promise]]'`) has\n * the same defect — it is stable by construction, therefore blind to\n * identity.\n *\n * Semantics, chosen deliberately:\n *\n * - **Promises/thenables are atomic**: compared by identity, never traversed.\n * A fresh promise is a change; the same promise is not. Debug metadata is\n * invisible to the comparison.\n * - **Opaque objects are atomic**: anything that is not a plain object, an\n * array, or `toJSON`-serializable (`Map`, `Set`, `URL`, `RegExp`, streams,\n * async iterators, cross-realm promises, class instances) is compared by\n * identity. `JSON.stringify` flattens all of them to `{}`, which would\n * silently collapse two distinct async values into \"unchanged\".\n * - **Cyclic graphs are compared coinductively**: a pair already being\n * compared is assumed equal, so equal self/mutual cycles are equal while a\n * differing leaf anywhere still reports a change. Nothing throws.\n * - **JSON-visible fields only**: `undefined`/function/symbol properties are\n * omitted from objects and read as `null` in arrays, and NaN/±Infinity read\n * as `null` — matching the previous `JSON.stringify` behavior for plain wire\n * payloads. Key order is irrelevant, which removes the old \"false mismatch\n * costs a re-render\" caveat.\n */\n\n/** `JSON.stringify` drops these from objects and nulls them inside arrays. */\nfunction isJsonOmitted(value: unknown): boolean {\n return (\n value === undefined ||\n typeof value === 'function' ||\n typeof value === 'symbol'\n )\n}\n\nfunction isObjectLike(value: unknown): value is Record<string, unknown> {\n return value !== null && typeof value === 'object'\n}\n\n/** Duck-typed so cross-realm promises and hand-rolled thenables also match. */\nfunction isThenable(value: Record<string, unknown>): boolean {\n return typeof value.then === 'function'\n}\n\nfunction isPlainObject(value: Record<string, unknown>): boolean {\n const proto = Object.getPrototypeOf(value)\n return proto === Object.prototype || proto === null\n}\n\n/** Returns the `toJSON()` projection, or the value itself when there is none. */\nfunction toJsonValue(value: Record<string, unknown>): unknown {\n return typeof value.toJSON === 'function'\n ? (value.toJSON as () => unknown)()\n : value\n}\n\n/** Maps NaN/±Infinity to `null`, matching how `JSON.stringify` renders them. */\nfunction toFiniteOrNull(value: unknown): unknown {\n return typeof value === 'number' && !Number.isFinite(value) ? null : value\n}\n\n/** Compares two loader payloads for JSON-visible structural equality. */\nexport function isSameLoaderPayload(left: unknown, right: unknown): boolean {\n // Pairs currently being compared, flattened as [a0, b0, a1, b1, ...].\n // Re-entering a pair means a cycle closed on both sides; assuming equality\n // there is what makes equal cyclic graphs compare equal (bisimulation)\n // while a differing leaf anywhere still reports a change.\n //\n // Scoped to the active path rather than every visited pair, so memory stays\n // proportional to graph depth (single digits for loader payloads) instead of\n // node count, and the common all-equal walk allocates one array.\n //\n // Tradeoff: a graph that shares one subtree through many nested parents\n // re-walks it per path. Wire-serialized loader payloads are trees, so this\n // does not arise; memoizing equal pairs instead measured ~2x slower on a\n // realistic 400-item page, which is the case that actually runs per render.\n const activePairs: object[] = []\n\n function isSame(rawA: unknown, rawB: unknown): boolean {\n if (Object.is(rawA, rawB)) {\n return true\n }\n // `JSON.stringify` renders NaN/±Infinity as `null`; normalizing here keeps\n // a non-finite number equal to the `null` a previous wire payload carried.\n const a = toFiniteOrNull(rawA)\n const b = toFiniteOrNull(rawB)\n if (a === b) {\n // Also accepts 0/-0, which `JSON.stringify` renders identically.\n return true\n }\n if (!(isObjectLike(a) && isObjectLike(b))) {\n return false\n }\n if (isThenable(a) || isThenable(b)) {\n // Identity comparison above already failed: distinct async values.\n return false\n }\n for (let index = 0; index < activePairs.length; index += 2) {\n if (activePairs[index] === a && activePairs[index + 1] === b) {\n return true\n }\n }\n\n activePairs.push(a, b)\n try {\n return isSameOpenPair(a, b)\n } finally {\n activePairs.length -= 2\n }\n }\n\n function isSameOpenPair(\n a: Record<string, unknown>,\n b: Record<string, unknown>\n ): boolean {\n const aJson = toJsonValue(a)\n const bJson = toJsonValue(b)\n if (aJson !== a || bJson !== b) {\n return isSame(aJson, bJson)\n }\n\n if (Array.isArray(a) !== Array.isArray(b)) {\n return false\n }\n if (Array.isArray(a) && Array.isArray(b)) {\n return isSameArray(a, b)\n }\n if (!(isPlainObject(a) && isPlainObject(b))) {\n // Opaque object with no JSON projection — identity is the only honest\n // signal, and it already failed.\n return false\n }\n return isSameObject(a, b)\n }\n\n function isSameArray(a: unknown[], b: unknown[]): boolean {\n if (a.length !== b.length) {\n return false\n }\n for (let index = 0; index < a.length; index++) {\n const aItem = isJsonOmitted(a[index]) ? null : a[index]\n const bItem = isJsonOmitted(b[index]) ? null : b[index]\n if (!isSame(aItem, bItem)) {\n return false\n }\n }\n return true\n }\n\n function isSameObject(\n a: Record<string, unknown>,\n b: Record<string, unknown>\n ): boolean {\n let aKeyCount = 0\n for (const key of Object.keys(a)) {\n if (isJsonOmitted(a[key])) {\n continue\n }\n aKeyCount++\n // A JSON-visible value never compares equal to an omitted one, so a\n // missing/undefined counterpart falls out of `isSame` as a change.\n if (!isSame(a[key], b[key])) {\n return false\n }\n }\n let bKeyCount = 0\n for (const key of Object.keys(b)) {\n if (!isJsonOmitted(b[key])) {\n bKeyCount++\n }\n }\n return aKeyCount === bKeyCount\n }\n\n return isSame(left, right)\n}\n","import type { WeaverseHydrogenParams } from '../types'\nimport type { WeaverseHydrogen } from '../WeaverseHydrogenRoot'\nimport { isSameLoaderPayload } from './is-same-loader-payload'\n\n/**\n * Apply a fresh loader payload to a reused Weaverse instance.\n *\n * With requestInfo normalized to the browser URL, same-URL revalidations\n * (cart mutations, `useRevalidator()`, locale-neutral re-fetches) reuse the\n * cached `window.__weaverses[pageId]` instance instead of constructing a new\n * one — which preserves section state, but would silently drop the fresh\n * `data`/`dataContext` the loaders just returned. Sync them here.\n *\n * Design mode is excluded by the caller: Studio owns the instance data there\n * (unsaved drafts must not be clobbered) and applies updates via\n * `refreshStudio` instead.\n *\n * Change detection uses {@link isSameLoaderPayload}: a structural comparison\n * that treats promises/thenables and other opaque objects as atomic values\n * compared by identity, and tolerates cyclic graphs. `dataContext` carries\n * live route loader data — including unresolved deferred promises whose React\n * development metadata is cyclic — so it cannot be JSON-serialized here.\n */\nexport function syncReusedInstance(\n weaverse: WeaverseHydrogen,\n params: WeaverseHydrogenParams\n) {\n weaverse.requestInfo = params.requestInfo\n weaverse.internal = params.internal\n\n let dataChanged = !isSameLoaderPayload(weaverse.data, params.data)\n let contextChanged = !isSameLoaderPayload(\n weaverse.dataContext ?? null,\n params.dataContext ?? null\n )\n\n if (contextChanged) {\n // Assign before notifying any item store: components resolve data\n // connectors against `weaverse.dataContext` at render time, so the\n // fresh context must be in place when re-renders flush.\n weaverse.dataContext = params.dataContext ?? null\n }\n if (dataChanged) {\n // State-preserving: existing item stores receive `setData` (and emit),\n // missing ones are created — no full instance teardown.\n weaverse.setProjectData(params.data)\n } else if (contextChanged) {\n // Context-only change: memoized item components subscribe to their item\n // store snapshot, and snapshot identity follows the `_store` reference —\n // a bare `triggerUpdate()` would notify with an identical snapshot and\n // `useSyncExternalStore` would skip the re-render. `setData({})` swaps\n // the store reference so connectors re-resolve against the new context.\n for (const { id } of params.data?.items || []) {\n weaverse.itemInstances.get(id)?.setData({})\n }\n }\n if (dataChanged || contextChanged) {\n weaverse.triggerUpdate()\n }\n}\n","import { isBrowser } from '@weaverse/react'\nimport { createContext, useContext, useEffect, useRef } from 'react'\nimport { useRouteLoaderData } from 'react-router'\nimport type {\n PublicEnv,\n WeaverseThemeSchema,\n WeaverseThemeSettings,\n} from '../types'\n\n/** Root-loader theme data used to initialize a {@link ThemeSettingsStore}. */\nexport interface WeaverseThemeData {\n /** Storefront-safe environment values, when available. */\n publicEnv?: PublicEnv\n /** Theme schema exposed to Studio, when available. */\n schema?: WeaverseThemeSchema\n /** Published theme setting values. */\n theme: WeaverseThemeSettings\n}\n\n/** Mutable, subscribable store for theme settings received from the root loader. */\nexport class ThemeSettingsStore {\n /** Current merged theme setting values. */\n settings: WeaverseThemeSettings = {}\n /** Theme schema exposed to Studio, when available. */\n schema?: WeaverseThemeSchema\n /** Storefront-safe environment values, when available. */\n publicEnv?: PublicEnv\n private listeners: Set<() => void> = new Set()\n private hasWarnedListenerCount = false\n private isDestroyed = false\n\n /** Creates a theme settings store from root-loader theme data. */\n constructor(data: WeaverseThemeData) {\n const { theme, schema, publicEnv } = data || {}\n this.settings = { ...theme }\n this.schema = schema\n this.publicEnv = publicEnv\n if (isBrowser) {\n window.__weaverseThemeSettingsStore = this\n }\n }\n\n /** Merges setting changes into the current snapshot and notifies subscribers. */\n updateThemeSettings = (newSettings: WeaverseThemeSettings) => {\n if (this.isDestroyed) {\n console.warn('ThemeSettingsStore: Cannot update destroyed store')\n return\n }\n\n this.settings = {\n ...this.settings,\n ...newSettings,\n }\n this.emit()\n }\n\n /** Returns the current client snapshot for `useSyncExternalStore`. */\n getSnapshot = () => this.settings\n\n /** Returns the current server snapshot for `useSyncExternalStore`. */\n getServerSnapshot = () => this.settings\n\n /** Subscribes to setting changes and returns an unsubscribe callback. */\n subscribe = (callback: () => void) => {\n if (this.isDestroyed) {\n console.warn('ThemeSettingsStore: Cannot subscribe to destroyed store')\n return () => {}\n }\n\n this.listeners.add(callback)\n\n if (!this.hasWarnedListenerCount && this.listeners.size > 500) {\n this.hasWarnedListenerCount = true\n console.warn(\n `ThemeSettingsStore: ${this.listeners.size} listeners detected. ` +\n 'This may indicate a performance issue. Consider using fewer useThemeSettings() calls.'\n )\n }\n\n return () => {\n this.listeners.delete(callback)\n }\n }\n\n private emit = () => {\n if (this.isDestroyed) {\n return\n }\n\n const currentListeners = Array.from(this.listeners)\n\n for (const listener of currentListeners) {\n try {\n listener()\n } catch (error) {\n console.error('ThemeSettingsStore: Listener error', error)\n }\n }\n }\n\n /** Clears subscribers and detaches the browser-global store reference. */\n destroy = () => {\n if (this.isDestroyed) {\n return\n }\n\n this.listeners.clear()\n this.isDestroyed = true\n\n if (isBrowser && window.__weaverseThemeSettingsStore === this) {\n window.__weaverseThemeSettingsStore = undefined\n }\n }\n}\n\nexport const ThemeSettingsStoreContext =\n createContext<ThemeSettingsStore | null>(null)\nThemeSettingsStoreContext.displayName = 'ThemeSettingsStoreContext'\n\nexport function useCreateThemeSettingsStore(): ThemeSettingsStore {\n const data = useRouteLoaderData('root') as {\n weaverseTheme: WeaverseThemeData\n }\n const storeRef = useRef<ThemeSettingsStore | null>(null)\n\n if (!storeRef.current) {\n storeRef.current = new ThemeSettingsStore(data?.weaverseTheme)\n }\n\n const theme = data?.weaverseTheme?.theme\n useEffect(() => {\n if (storeRef.current && theme) {\n storeRef.current.updateThemeSettings(theme)\n }\n }, [theme])\n\n return storeRef.current\n}\n\nexport function useThemeSettingsStore(): ThemeSettingsStore {\n const fromContext = useContext(ThemeSettingsStoreContext)\n const data = useRouteLoaderData('root') as {\n weaverseTheme: WeaverseThemeData\n }\n const fallbackRef = useRef<ThemeSettingsStore | null>(null)\n\n if (!(fallbackRef.current || fromContext)) {\n fallbackRef.current = new ThemeSettingsStore(data?.weaverseTheme)\n }\n\n const store = fromContext ?? fallbackRef.current!\n const theme = data?.weaverseTheme?.theme\n useEffect(() => {\n if (!fromContext && theme) {\n store.updateThemeSettings(theme)\n }\n }, [fromContext, store, theme])\n\n return store\n}\n","import { loadScript, StudioSelectionStoreContext } from '@weaverse/react'\nimport { useContext, useEffect } from 'react'\nimport {\n useLocation,\n useNavigate,\n useNavigation,\n useRevalidator,\n} from 'react-router'\nimport { useTranslation } from '../hooks/translation-context'\nimport type { WeaverseHydrogen } from '../internal'\nimport { hasWeaverseStudio } from '../types'\nimport { getStudioScriptSrc, resolveStudioScriptSrc } from './studio-script-src'\nimport { useThemeSettingsStore } from './use-theme-settings-store'\n\nconst STUDIO_READY_POLL_MS = 50\nconst STUDIO_READY_TIMEOUT_MS = 5000\n\n/**\n * Resolve once `window.weaverseStudio` is available, or `null` on timeout.\n * The studio script sets the global when it *executes*; `loadScript` can\n * resolve earlier for tags it did not create (e.g. SSR-injected markup),\n * so never assume readiness right after the script promise settles.\n */\nfunction waitForStudio(): Promise<Window['weaverseStudio'] | null> {\n return new Promise((resolve) => {\n let startedAt = Date.now()\n function check() {\n if (hasWeaverseStudio(window)) {\n return resolve(window.weaverseStudio)\n }\n if (Date.now() - startedAt >= STUDIO_READY_TIMEOUT_MS) {\n console.warn('[Weaverse] Studio script did not initialize in time')\n return resolve(null)\n }\n setTimeout(check, STUDIO_READY_POLL_MS)\n }\n check()\n })\n}\n\n/**\n * Load the Studio bridge script on every design/preview render so Studio's\n * `checkWeaversePage()` handshake is answered instead of timing out — crucially\n * on routes where the page-scoped {@link useStudio} never mounts: 404s, error\n * boundaries, and non-Weaverse routes. The bridge registers its RPC endpoint\n * and `window.weaverseStudio` the moment the script executes, independent of any\n * page binding. Without this, a content-less route never loads the script and\n * Studio reports a false \"Connection lost\" instead of \"this page has no Weaverse\n * content set up\".\n *\n * `loadScript` is idempotent, so on content routes this coexists with the\n * page-scoped {@link useStudio} (same src) — that hook still binds the page via\n * `init`. Gating this on whether a route \"has Weaverse data\" is deliberately\n * avoided: route matches can't tell whether the page-scoped bridge actually\n * mounts (an `ErrorBoundary` replacing a data route, or data passed via the\n * `data` prop), and suppressing the script there would reintroduce the timeout\n * this fixes. Loading the script is harmless; it only registers the responder.\n *\n * Known limitation: on a route whose `weaverseData` is a deferred Promise, the\n * script can load before the page streams in, so `checkWeaversePage()` may\n * briefly observe `NOT_WEAVERSE_PAGE`. The complete fix is a \"loading\" state in\n * `checkWeaversePage` so the handshake retries rather than latching — that lives\n * in the Studio script, not here. Tracked in Weaverse/builder#2515.\n *\n * Mounted via {@link withWeaverse}, which must be the root route's `Layout`\n * export so the bridge renders even when a route's `ErrorBoundary` replaces the\n * page. Outside Studio (no design/preview params) it is a no-op.\n */\nexport function useStudioConnect() {\n let { search } = useLocation()\n useEffect(() => {\n // Loopback Studio hosts are only honored when the storefront itself is\n // loopback (see resolveStudioScriptSrc), so pass the document's hostname.\n let hostname = typeof window === 'undefined' ? '' : window.location.hostname\n let src = resolveStudioScriptSrc(search, hostname)\n if (src) {\n loadScript(src).catch(console.error)\n }\n }, [search])\n}\n\nlet warnedMissingSelectionStore = false\n\nfunction warnMissingSelectionStore() {\n if (warnedMissingSelectionStore) {\n return\n }\n warnedMissingSelectionStore = true\n console.warn(\n '[Weaverse] useStudioSelection() needs the root Layout wrapped with withWeaverse(); Studio selections will not reach the theme.'\n )\n}\n\nexport function useStudio(weaverse: WeaverseHydrogen) {\n let { revalidate } = useRevalidator()\n let navigation = useNavigation()\n let { pathname, search } = useLocation()\n let navigate = useNavigate()\n let themeSettingsStore = useThemeSettingsStore()\n let studioSelectionStore = useContext(StudioSelectionStoreContext)\n let { translationStore, themeTextStore, merchantOverrides } = useTranslation()\n let {\n isDesignMode,\n weaverseHost,\n weaverseVersion,\n isPreviewMode,\n isRevisionPreview,\n } = weaverse\n\n // biome-ignore lint/correctness/useExhaustiveDependencies: only revalidate on pathname/search changes\n useEffect(() => {\n if (navigation.state !== 'idle') {\n return\n }\n let src = getStudioScriptSrc({\n isDesignMode,\n isPreviewMode,\n isRevisionPreview,\n weaverseHost,\n weaverseVersion,\n })\n if (!src) {\n return\n }\n if (isRevisionPreview || isPreviewMode) {\n loadScript(src).catch(console.error)\n } else {\n if (!studioSelectionStore) {\n warnMissingSelectionStore()\n }\n weaverse.internal = {\n ...weaverse.internal,\n navigate,\n revalidate,\n merchantOverrides,\n themeSettingsStore,\n // Must stay one instance per root: the effect deps above exclude it.\n studioSelectionStore,\n translationStore,\n // Deprecated alias for existing Studio bridge integrations.\n themeTextStore,\n }\n loadScript(src)\n .then(waitForStudio)\n .then((studio) => studio?.init(weaverse))\n .catch(console.error)\n }\n }, [pathname, search, navigation.state])\n}\n","import type { ElementData } from '@weaverse/react'\nimport {\n isBrowser,\n StudioSelectionStore,\n StudioSelectionStoreContext,\n useSafeExternalStore,\n Weaverse,\n WeaverseItemStore,\n WeaverseRoot,\n} from '@weaverse/react'\nimport {\n type ComponentType,\n type JSX,\n memo,\n Suspense,\n useMemo,\n useRef,\n useState,\n} from 'react'\nimport { ErrorBoundary } from 'react-error-boundary'\nimport {\n Await,\n useLoaderData,\n useMatches,\n useRouteLoaderData,\n} from 'react-router'\nimport { defaultComponents } from './components'\nimport {\n type TranslateFunction,\n TranslationProvider,\n} from './hooks/translation-context'\nimport { createWeaverseDataContext } from './hooks/use-weaverse-data-context'\nimport type {\n HydrogenElement,\n TranslationChanges,\n TranslationEntry,\n TranslationItemEntry,\n TranslationMap,\n WeaverseComponentData,\n WeaverseInternal,\n WeaverseLoaderRequestInfo,\n WeaversePageData,\n WeaverseThemeSettings,\n} from './internal'\nimport type {\n WeaverseComponent,\n WeaverseHydrogenParams,\n WeaverseLoaderData,\n} from './types'\nimport { hasWeaverseStudio } from './types'\nimport { generateDataFromSchema } from './utils'\nimport {\n pickWeaverseData,\n type WeaverseDataValue,\n} from './utils/pick-weaverse-data'\nimport { normalizeRequestInfo } from './utils/studio-request-info'\nimport { syncReusedInstance } from './utils/sync-reused-instance'\nimport { TranslationStore } from './utils/translation-store'\nimport { useStudio, useStudioConnect } from './utils/use-studio'\nimport {\n ThemeSettingsStoreContext,\n useCreateThemeSettingsStore,\n useThemeSettingsStore,\n} from './utils/use-theme-settings-store'\n\n/*\n=== IMPORTANT DESIGN PRINCIPLE ===\nCONSTRUCTOR TIMING AND DEFERRED PROCESSING\n\nWhen working with React component systems and registries:\n- Keep constructors SIMPLE - don't do heavy work in them\n- Defer processing until RENDER TIME, not initialization\n- Avoid registry lookups during object construction (components may not be registered yet)\n- Simple code is more reliable than complex upfront optimization\n- Respect the initialization sequence: registration → instance creation → rendering\n\nHistory: Attempting to do schema processing in the constructor (commit 495e1220) broke\nthe pilot template because it tried to access the component registry before components were registered.\nThe fix was to defer that work until the component is actually rendered.\n====================================\n*/\n\n/** Runtime store for one rendered Hydrogen component instance. */\nexport class WeaverseHydrogenItem extends WeaverseItemStore {\n /** Owning Hydrogen runtime instance. */\n declare weaverse: WeaverseHydrogen\n\n /**\n * Cached merged snapshot (base + translations).\n * Reset to `null` whenever `_store` or the item's translation entry changes.\n */\n private _cachedSnapshot: ElementData | null = null\n private _snapshotStoreRef: ElementData | null = null\n private _snapshotTranslationRef: TranslationItemEntry | null = null\n\n /** Creates an item store from serialized component data and its owner. */\n constructor(initialData: WeaverseComponentData, weaverse: WeaverseHydrogen) {\n super(initialData, weaverse)\n const { data, ...rest } = initialData\n if (!this.Element?.schema) {\n console.error('Element is missing schema or not found!')\n return\n }\n const schemaData = generateDataFromSchema(this.Element.schema)\n Object.assign(this._store, schemaData, data, rest)\n }\n\n /** Registered component definition for this item's type. */\n get Element(): HydrogenElement {\n return super.Element\n }\n\n /**\n * Override setData to flatten serialized `data` the same way the constructor\n * does. Core's `initProject()` reuses item stores keyed by item id (identical\n * ids across locales), and the inherited setData only shallow-merges the raw\n * item — fresh settings would land under `_store.data` while the stale\n * flattened values keep rendering.\n *\n * Schema defaults are re-applied ahead of incoming settings because a\n * serialized payload can omit fields — or the optional `data` object itself —\n * when values equal schema defaults. The stored nested `data` is also replaced\n * so the public serialized state does not retain the previous locale payload.\n *\n * An empty update is the context-only refresh from `syncReusedInstance`: swap\n * the store reference so memoized subscribers re-render, and leave the\n * settings untouched.\n */\n setData = (update: Omit<ElementData, 'id' | 'type'>) => {\n let source = update ?? {}\n let { data, ...rest } = source\n let isEmptyUpdate = Object.keys(source).length === 0\n\n if (isEmptyUpdate) {\n this._store = { ...this._store }\n } else {\n let type = source.type ?? this._store.type\n let schema = this.weaverse.elementRegistry.get(type)?.schema\n let isSerializedItem = 'id' in source && 'type' in source\n let shouldNormalize = data !== undefined || isSerializedItem\n\n if (schema && shouldNormalize) {\n let { data: _previousData, ...store } = this._store\n let schemaData = generateDataFromSchema(schema)\n this._store = {\n ...store,\n ...(data === undefined ? {} : { data }),\n ...schemaData,\n ...data,\n ...rest,\n }\n } else {\n this._store = { ...this._store, ...source }\n }\n }\n\n this.triggerUpdate()\n return this.data\n }\n\n /**\n * Override getSnapShot to merge translation data when in translation mode.\n * Memoizes the result: returns the same object reference as long as\n * both `_store` and the translation entry haven't changed.\n * This is critical for `useSyncExternalStore` stability.\n */\n getSnapShot = (): ElementData => {\n const base = this._store\n const translations = this.weaverse.translationMap?.[this._id]\n\n // No translations → return base directly (fast path)\n if (!translations) {\n return base\n }\n\n // Check if cache is still valid (same _store ref + same translation entry ref)\n if (\n this._cachedSnapshot &&\n this._snapshotStoreRef === base &&\n this._snapshotTranslationRef === translations\n ) {\n return this._cachedSnapshot\n }\n\n // Build merged snapshot\n const merged = { ...base }\n for (const [key, entry] of Object.entries(translations)) {\n if (entry.translatedValue !== undefined) {\n merged[key] = entry.translatedValue\n }\n }\n\n this._cachedSnapshot = merged\n this._snapshotStoreRef = base\n this._snapshotTranslationRef = translations\n return merged\n }\n}\n\nWeaverse.ItemConstructor = WeaverseHydrogenItem\n\n/**\n * Hydrogen-specific Weaverse runtime that owns page data, component stores,\n * Studio state, and translation sidecars for one rendered page.\n */\nexport class WeaverseHydrogen extends Weaverse {\n /** Identifier of the rendered page. */\n pageId: string\n /** Runtime services and project metadata exposed to integrations. */\n internal: Partial<WeaverseInternal>\n /** Normalized URL and locale information for the current request. */\n requestInfo: WeaverseLoaderRequestInfo\n /** Identifier of the project supplying the page. */\n projectId: string\n /** Origin used for Studio assets and editor communication. */\n weaverseHost: string\n /** Base URL used for public content API requests. */\n weaverseApiBase: string\n /**\n * API key read from environment/configuration for storefront requests or\n * supplied by Studio requests while editing and previewing.\n */\n weaverseApiKey: string\n /** Studio asset version supplied by the preview request. */\n weaverseVersion: string\n /** Whether the page is running inside the visual editor. */\n isDesignMode: boolean\n /** Whether a single component preview is being rendered. */\n isPreviewMode: boolean\n /** Whether a saved page revision is being previewed. */\n isRevisionPreview: boolean\n /** Component type requested by section preview mode. */\n sectionType: string\n /** Component item store constructor used by the core runtime. */\n declare ItemConstructor: typeof WeaverseHydrogenItem\n /** Current page content owned by the runtime. */\n declare data: WeaversePageData\n /** Component item stores indexed by instance ID. */\n declare static itemInstances: Map<string, WeaverseHydrogenItem>\n /** Registered component definitions indexed by component type. */\n declare static elementRegistry: Map<string, HydrogenElement>\n\n // ─── Translation sidecar (design mode only) ────────────────────────\n /**\n * Translation map received from the builder in design mode.\n * Keyed by item ID, each entry maps field names to translated values.\n * `WeaverseHydrogenItem.getSnapShot()` merges these into the data\n * returned to components, so translations render without monkey-patching.\n */\n translationMap: TranslationMap = {}\n /** Locale code for the active translation sidecar. */\n translationLocale = ''\n /** Builder language identifier for the active translation sidecar. */\n translationLanguageId = ''\n\n /** Creates a page runtime from resolved loader data and request settings. */\n constructor(params: WeaverseHydrogenParams) {\n const { internal, pageId, requestInfo, ...coreParams } = params\n super({ ...coreParams })\n this.internal = internal\n this.pageId = pageId\n this.requestInfo = requestInfo\n // Assign configuration properties\n this.projectId = params.projectId\n this.weaverseHost = params.weaverseHost\n this.weaverseApiBase = params.weaverseApiBase\n this.weaverseApiKey = params.weaverseApiKey\n this.weaverseVersion = params.weaverseVersion || ''\n this.isDesignMode = params.isDesignMode ?? false\n this.isPreviewMode = params.isPreviewMode ?? false\n this.isRevisionPreview = params.isRevisionPreview ?? false\n this.sectionType = params.sectionType || ''\n\n // Extract translation sidecar from page data if present (design mode)\n this.extractTranslationSidecar()\n }\n\n /**\n * Extract translation sidecar data from `this.data` (the page data).\n * The builder attaches `translationMap`, `translationLocale`, and\n * `translationLanguageId` to the page data in design mode.\n */\n extractTranslationSidecar = () => {\n const pageData = this.data as Record<string, any>\n if (pageData?.translationMap) {\n this.translationMap = pageData.translationMap\n this.translationLocale = pageData.translationLocale || ''\n this.translationLanguageId = pageData.translationLanguageId || ''\n }\n }\n\n /**\n * Set the translation sidecar data.\n * Called by the studio bridge when translation data arrives or changes.\n * Triggers a re-render of all items so translated snapshots are recalculated.\n */\n setTranslationSidecar = (\n translationMap: TranslationMap,\n locale: string,\n languageId: string\n ) => {\n this.translationMap = translationMap\n this.translationLocale = locale\n this.translationLanguageId = languageId\n // Invalidate all item snapshot caches and trigger re-render\n this.refreshAllItems()\n }\n\n /**\n * Update a single translation entry for a specific item field.\n * Used by the studio bridge when the user edits a translatable field.\n */\n updateTranslation = (\n itemId: string,\n key: string,\n originalValue: string,\n translatedValue: string\n ) => {\n if (!this.translationMap[itemId]) {\n this.translationMap[itemId] = {}\n }\n // Create a new entry reference so getSnapShot detects the change\n this.translationMap[itemId] = {\n ...this.translationMap[itemId],\n [key]: { originalValue, translatedValue },\n }\n // Trigger re-render for this specific item\n const instance = this.itemInstances.get(itemId)\n if (instance) {\n instance.triggerUpdate()\n }\n }\n\n /**\n * Collect all translation changes across all weaverse instances.\n * Used by the save flow to persist translation edits.\n */\n getTranslationChanges = (): TranslationChanges | undefined => {\n const entries: TranslationEntry[] = []\n const languageId = this.translationLanguageId\n if (!(this.translationLocale && languageId)) {\n return\n }\n\n for (const [itemId, fields] of Object.entries(this.translationMap)) {\n for (const [key, entry] of Object.entries(fields)) {\n entries.push({\n itemId,\n key: `data.${key}`,\n originalValue: entry.originalValue,\n translatedValue: entry.translatedValue,\n })\n }\n }\n\n if (entries.length === 0) {\n return\n }\n return { languageId, entries }\n }\n\n /**\n * Override setProjectData to re-extract translation sidecar from new page data.\n */\n setProjectData = (data: WeaversePageData) => {\n this.data = data\n this.extractTranslationSidecar()\n this.initProject()\n }\n}\n\nfunction createWeaverseInstance(\n params: WeaverseHydrogenParams\n): WeaverseHydrogen {\n const normalizedParams = normalizeRequestInfo(params)\n if (isBrowser) {\n const runtimeWindow = window as unknown as {\n __weaverses?: Record<string, WeaverseHydrogen>\n }\n runtimeWindow.__weaverses = runtimeWindow.__weaverses || {}\n const existing = runtimeWindow.__weaverses[normalizedParams.pageId]\n // Reuse the instance only while the browser stays on the same URL —\n // navigation to a different pathname/search builds a fresh instance.\n const isReused =\n existing?.requestInfo?.pathname ===\n normalizedParams.requestInfo.pathname &&\n existing?.requestInfo?.search === normalizedParams.requestInfo.search\n const weaverse = isReused\n ? existing\n : new WeaverseHydrogen(normalizedParams)\n runtimeWindow.__weaverses[normalizedParams.pageId] = weaverse\n if (weaverse.isDesignMode) {\n weaverse.requestInfo = normalizedParams.requestInfo\n if (hasWeaverseStudio(window)) {\n window.weaverseStudio.refreshStudio(normalizedParams)\n }\n } else if (isReused) {\n // Same-URL revalidation in live/preview mode: the reused instance\n // must adopt the fresh loader payload (see syncReusedInstance).\n syncReusedInstance(weaverse, normalizedParams)\n }\n return weaverse\n }\n return new WeaverseHydrogen(normalizedParams)\n}\n\nconst StudioBridge = memo(({ context }: { context: WeaverseHydrogen }) => {\n useStudio(context)\n return null\n})\n\nconst StudioConnect = memo(() => {\n useStudioConnect()\n return null\n})\n\n/**\n * Component lists already pushed into the element registry. Registration is\n * idempotent (the registry checks `has(type)`), but walking the full list\n * allocates and loops on every render — theme component arrays are\n * module-level constants, so register each list identity once.\n */\nconst registeredComponentLists = new WeakSet<WeaverseComponent[]>()\n\nfunction RenderRoot(props: {\n data: WeaverseLoaderData\n components: WeaverseComponent[]\n dataContext?: Record<string, any>\n}) {\n const { data, components, dataContext } = props\n if (!registeredComponentLists.has(components)) {\n for (const comp of [...components, ...defaultComponents]) {\n if (comp?.schema?.type) {\n registerComponent({\n type: comp.schema.type,\n Component: comp.default,\n schema: comp.schema,\n loader: comp.loader,\n })\n }\n }\n registeredComponentLists.add(components)\n }\n const { page, configs, project, pageAssignment } = data || {}\n\n // Keep original page data - processing will happen at component level in renderer\n\n const weaverse = createWeaverseInstance({\n ...configs,\n dataContext,\n data: page,\n pageId: page?.id,\n internal: { project, pageAssignment },\n })\n return (\n <>\n <WeaverseRoot context={weaverse} />\n <StudioBridge context={weaverse} />\n </>\n )\n}\n\n/**\n * Register a Weaverse component with the global element registry.\n * Must be called before rendering components in WeaverseHydrogenRoot.\n *\n * @param element - Component configuration with schema, Component, and optional loader\n *\n * @example\n * ```typescript\n * import Hero from './sections/Hero'\n * import { schema } from './sections/Hero'\n *\n * registerComponent({\n * type: 'hero',\n * Component: Hero,\n * schema,\n * loader: async ({ data, weaverse }) => {\n * return { someData: 'value' }\n * }\n * })\n * ```\n */\nexport function registerComponent(element: HydrogenElement) {\n WeaverseHydrogen.registerElement(element)\n}\n\n/** Props passed to a custom Weaverse page error component. */\nexport interface WeaverseHydrogenRootErrorProps {\n /** Error raised while resolving or rendering the Weaverse page. */\n error: Error | unknown\n}\n\n/** Props accepted by {@link WeaverseHydrogenRoot}. */\nexport interface WeaverseHydrogenRootProps {\n /** Theme components available to the page renderer. */\n components: WeaverseComponent[]\n /**\n * Optional explicit `weaverseData` payload. When provided, bypasses the\n * automatic route-scoped resolution. Accepts a resolved value, a\n * `Promise` (handled via `<Await>`), or `null` to suppress rendering.\n *\n * Recommended for nested layout + child route compositions where two\n * `<WeaverseHydrogenRoot>` instances need to render different Weaverse\n * pages on the same URL. See issue #451.\n */\n data?: WeaverseDataValue\n /** Component rendered when page data cannot be resolved or awaited. */\n errorComponent?: React.FC<WeaverseHydrogenRootErrorProps>\n}\n\n/**\n * Renders the Weaverse page for the current React Router route.\n *\n * Pass `data` to override automatic route-loader resolution. A promise is\n * handled with React Router's `Await`, while explicit `null` suppresses output.\n */\nexport const WeaverseHydrogenRoot = memo(\n ({\n components,\n data: dataProp,\n errorComponent: ErrorComponent = ({ error }) => (\n <div>\n {error instanceof Error\n ? error.message\n : 'An unexpected error occurred'}\n </div>\n ),\n }: WeaverseHydrogenRootProps) => {\n const matches = useMatches()\n\n // Flat route-keyed data context for data-connector template resolution\n // (e.g. {{root.layout.shop.name}}). Legitimately needs all matches;\n // memoized so same-matches re-renders don't allocate a fresh context.\n const enhancedDataContext = useMemo(\n () => createWeaverseDataContext(matches),\n [matches]\n )\n\n // Route-scoped weaverseData lookup. `useLoaderData()` returns the\n // closest route's loader data, which is what makes nested layout +\n // child routes work — each <WeaverseHydrogenRoot> instance resolves\n // to its own route's weaverseData. See pickWeaverseData() for the\n // three-tier fallback policy.\n const ownLoaderData = useLoaderData() as unknown\n const weaverseData = useMemo(\n () => pickWeaverseData(dataProp, ownLoaderData, matches),\n [dataProp, ownLoaderData, matches]\n )\n\n // Explicit `data={null}` is the documented signal for \"suppress rendering\"\n // (e.g. a route conditionally disables Weaverse without unmounting the\n // component). This is distinct from auto-resolution returning `null`,\n // which means \"data was expected but not found\" — the error path below.\n if (dataProp === null) {\n return null\n }\n\n if (weaverseData) {\n if (weaverseData instanceof Promise) {\n return (\n <ErrorBoundary fallbackRender={ErrorComponent as any}>\n <Suspense>\n <Await resolve={weaverseData}>\n {(resolvedData) => (\n <RenderRoot\n components={components}\n data={resolvedData}\n dataContext={enhancedDataContext}\n />\n )}\n </Await>\n </Suspense>\n </ErrorBoundary>\n )\n }\n return (\n <RenderRoot\n components={components}\n data={weaverseData}\n dataContext={enhancedDataContext}\n />\n )\n }\n return (\n <ErrorComponent\n error={{ message: 'No Weaverse data found in route matches!' }}\n />\n )\n }\n)\n\n/**\n * Options for the `withWeaverse` HOC.\n */\nexport type WithWeaverseOptions = {\n /**\n * Optional external translation function (e.g. from Shopify i18n).\n * When provided it becomes the **highest-priority** source\n * in the `t()` resolution chain exposed by `useTranslation()`.\n */\n t?: TranslateFunction\n}\n\n/**\n * Higher-order component that wraps your app with Weaverse theme settings and\n * translation context, and mounts the Studio connect bridge.\n *\n * Export the result as your root route's **`Layout`** (not the default route\n * `Component`). In React Router the root `Layout` wraps both the matched route\n * and its `ErrorBoundary`, so the Studio bridge keeps rendering on 404s and\n * loader/render errors — exactly the pages where Studio must still answer its\n * handshake instead of reporting a false \"Connection lost\". A default-component\n * wrapper (`export default withWeaverse(App)`) is replaced by the error\n * boundary on those routes and would drop the bridge.\n *\n * @param Component - The root layout component to wrap\n * @param options - Optional configuration (e.g. an external `t` function)\n * @returns Wrapped component with theme settings + translation providers\n *\n * @example\n * ```typescript\n * import { withWeaverse } from '@weaverse/hydrogen'\n *\n * // Basic usage — export as the root route Layout\n * export const Layout = withWeaverse(RootLayout)\n *\n * // With an external translation function\n * export const Layout = withWeaverse(RootLayout, { t: myI18nFunction })\n * ```\n */\nexport function withWeaverse(\n Component: ComponentType<any>,\n options?: WithWeaverseOptions\n) {\n return (props: JSX.IntrinsicAttributes) => {\n const themeSettingsStore = useCreateThemeSettingsStore()\n const translationData = useTranslationData()\n const translationStoreRef = useRef<TranslationStore | null>(null)\n if (!translationStoreRef.current) {\n translationStoreRef.current = new TranslationStore()\n }\n let [studioSelectionStore] = useState(() => new StudioSelectionStore())\n return (\n <ThemeSettingsStoreContext.Provider value={themeSettingsStore}>\n <StudioSelectionStoreContext.Provider value={studioSelectionStore}>\n <TranslationProvider\n merchantOverrides={translationData.merchantOverrides}\n staticContent={translationData.staticContent}\n t={options?.t}\n translationStore={translationStoreRef.current}\n >\n <StudioConnect />\n <Component {...props} />\n </TranslationProvider>\n </StudioSelectionStoreContext.Provider>\n </ThemeSettingsStoreContext.Provider>\n )\n }\n}\n\n/**\n * Internal hook to read staticContent and merchantOverrides from root loader data.\n * Similar pattern to useThemeSettingsStore – reads from the 'root' route loader.\n */\nfunction useTranslationData(): {\n staticContent: Record<string, unknown>\n merchantOverrides?: Record<string, unknown>\n} {\n const data = useRouteLoaderData('root') as {\n weaverseTheme?: {\n staticContent?: Record<string, unknown>\n merchantOverrides?: Record<string, unknown>\n }\n }\n return {\n staticContent: data?.weaverseTheme?.staticContent ?? {},\n merchantOverrides: data?.weaverseTheme?.merchantOverrides,\n }\n}\n\n/**\n * React hook to access Weaverse theme settings from anywhere in your component tree.\n * Must be used within a component wrapped by `withWeaverse` or inside `WeaverseHydrogenRoot`.\n *\n * @returns Current theme settings object\n *\n * @example\n * ```typescript\n * function MyComponent() {\n * const theme = useThemeSettings()\n * const primaryColor = theme.colors?.primary\n *\n * return <div style={{ color: primaryColor }}>Styled content</div>\n * }\n * ```\n */\nexport function useThemeSettings<T = WeaverseThemeSettings>() {\n const themeSettingsStore = useThemeSettingsStore()\n const settings = useSafeExternalStore(\n themeSettingsStore.subscribe,\n themeSettingsStore.getSnapshot,\n themeSettingsStore.getServerSnapshot\n )\n return settings as T\n}\n","// Query params that influence Weaverse page resolution on the Builder:\n// revision previews, design-mode flags, and `weaverse*` control params\n// (host, version, project id). Everything else — utm_*, fbclid, gclid,\n// storefront filters — is irrelevant to which page the Builder returns.\nconst KEPT_PARAM_RE = /^(?:__revisionId|isDesignMode|weaverse)/\n\n/**\n * Normalize the storefront URL sent in the Builder page API request body.\n *\n * The POST body participates in Hydrogen's cached fetch key, so a raw\n * `request.url` fragments the cache by every marketing/tracking param:\n * each distinct `?utm_…`/`fbclid` URL is a guaranteed Builder round-trip\n * even when the resolved page is identical — precisely the paid/social\n * traffic that needs the cache most (builder#2450).\n *\n * The Builder reads only the pathname (locale/type/handle fallback) and\n * the params matched above from this URL, so keep exactly those. Params\n * are sorted for a stable cache key regardless of their order in the\n * incoming URL.\n */\nexport function normalizePageUrl(url: string): string {\n let parsed: URL\n try {\n parsed = new URL(url)\n } catch {\n // Not an absolute URL (shouldn't happen for request.url) — send as-is.\n return url\n }\n const kept = [...parsed.searchParams].filter(([key]) =>\n KEPT_PARAM_RE.test(key)\n )\n kept.sort(([a], [b]) => a.localeCompare(b))\n const search = new URLSearchParams(kept).toString()\n // React Router single-fetch requests loader data from `/<path>.data`, which\n // resolves to the same Builder page as `/<path>`. Strip the suffix so a\n // client navigation and a document load hash to one edge-cache key (the\n // api.weaverse.io worker keys on this url) instead of two — keeping a page's\n // first client navigation off the cold-MISS path.\n let { pathname } = parsed\n if (pathname.endsWith('.data')) {\n pathname = pathname.slice(0, -'.data'.length)\n }\n return `${parsed.origin}${pathname}${search ? `?${search}` : ''}`\n}\n","import { Cache, createFetchWithCache } from '@shopify/hydrogen'\nimport type {\n CachingStrategy,\n CustomPageEntry,\n FetchProjectPayload,\n FetchProjectRequestBody,\n LoadPageParams,\n PageType,\n ProjectIdValidationResult,\n WeaverseClientArgs,\n WeaverseComponent,\n WeaverseComponentData,\n WeaverseEnv,\n WeaverseI18n,\n WeaverseLoaderData,\n WeaversePageData,\n WeaverseProjectConfigs,\n WeaverseStudioQueries,\n WeaverseThemeSettingsResponse,\n WithCacheFetchResponse,\n} from './types'\nimport {\n hasError,\n isProjectIdFunction,\n WeaverseError,\n WeaverseLoadError,\n type WeaverseLoadFailureKind,\n} from './types'\nimport {\n generateDataFromSchema,\n getPreviewData,\n getRequestQueries,\n getWeaverseConfigs,\n} from './utils'\nimport { normalizePageUrl } from './utils/normalize-page-url'\n\n/**\n * Cache duration constants (in seconds).\n * These values optimize CDN hit rates while maintaining fresh content.\n */\nconst CACHE_DURATIONS = {\n /** Freshness for content that can change in Builder */\n SHORT: 60,\n /** Long stale window so slow Builder responses do not block storefront rendering */\n LONG: 86_400,\n} as const\n\n/**\n * Network timeout for fetch requests (in milliseconds).\n * Default is 10 seconds to balance reliability and user experience.\n */\nconst DEFAULT_FETCH_TIMEOUT_MS = 10_000\n\n/**\n * Bounded retry for the public API fetch. A single slow/cold origin response,\n * a transient 5xx, or a network blip would otherwise make loadPage return null\n * — rendering the \"Please add new section\" placeholder or throwing a 404 in\n * the route. Two attempts (one retry) is enough: by the time the first attempt\n * times out, the edge proxy's single-flight leader has usually populated its\n * versioned cache, so the retry lands on a warm HIT.\n */\nconst MAX_FETCH_ATTEMPTS = 2\nconst FETCH_RETRY_BACKOFF_MS = 300\n\n/**\n * Retry only transient failures: request timeouts, network errors, and origin\n * 5xx. A 4xx is deterministic (e.g. a genuine 404) — retrying just wastes time.\n */\nfunction isRetryableFetchError(error: unknown): boolean {\n if (!(error instanceof WeaverseError)) {\n return false\n }\n // A successful status with an unparsable body was classified as a network\n // error before it got its own code; it keeps the same single retry.\n if (\n error.code === 'TIMEOUT_ERROR' ||\n error.code === 'NETWORK_ERROR' ||\n error.code === 'INVALID_RESPONSE'\n ) {\n return true\n }\n return error.code === 'FETCH_ERROR' && (error.statusCode ?? 0) >= 500\n}\n\n/** Parses a JSON body, or returns the raw text when it is not JSON. */\nfunction parseResponseText(text: string): unknown {\n if (!text) {\n return text\n }\n try {\n return JSON.parse(text)\n } catch {\n return text\n }\n}\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\n/**\n * Whether a successful `/project` body is a page envelope.\n *\n * `page: null` is Builder's authoritative \"nothing assigned at this URL\" and\n * stays valid; anything without a `page` field, with an `error` field, or with\n * a page that has no item list is not an answer about this URL at all.\n */\nfunction isProjectEnvelope(payload: unknown): boolean {\n if (!isPlainObject(payload) || hasError(payload) || !('page' in payload)) {\n return false\n }\n const { page, project } = payload\n const validPage =\n page === null || (isPlainObject(page) && Array.isArray(page.items))\n return validPage && (project == null || isPlainObject(project))\n}\n\n/**\n * Whether a successful `/project_configs` body carries theme settings.\n *\n * Builder always sends the `theme` field; `null` means the merchant never\n * saved settings, so schema defaults are the real configuration.\n */\nfunction isThemeConfigsEnvelope(payload: unknown): boolean {\n return (\n isPlainObject(payload) &&\n !hasError(payload) &&\n 'theme' in payload &&\n (payload.theme === null || isPlainObject(payload.theme))\n )\n}\n\n/**\n * Classifies a page-load failure. `'absent'` is Builder's 404 for a project\n * that does not exist, which keeps resolving to `null` like before.\n */\nfunction classifyPageLoadFailure(\n error: unknown\n): WeaverseLoadFailureKind | 'absent' {\n if (error instanceof WeaverseLoadError) {\n return error.kind\n }\n if (!(error instanceof WeaverseError)) {\n // Thrown while turning a response into page data: the body was not\n // the shape it claimed to be.\n return 'invalid-response'\n }\n if (error.code === 'TIMEOUT_ERROR' || error.code === 'NETWORK_ERROR') {\n return 'unavailable'\n }\n if (error.code === 'INVALID_RESPONSE') {\n return 'invalid-response'\n }\n if (error.code === 'FETCH_ERROR') {\n const status = error.statusCode ?? 0\n if (status === 404) {\n return 'absent'\n }\n if (status === 408 || status === 425 || status === 429 || status >= 500) {\n return 'unavailable'\n }\n }\n // Refusals (400/401/403) and project-ID errors: retrying cannot succeed.\n return 'configuration'\n}\n\n/**\n * Validators for Builder requests made by this client itself, keyed on the\n * exact options object (same reasoning as `bodyFreeCacheRequests`). On the\n * cached path such a request rejects a non-2xx status instead of resolving\n * to `null`, and only stores a body its validator accepts. Every other\n * `fetchWithCache` caller keeps the existing behavior.\n */\nconst builderPayloadValidators = new WeakMap<\n object,\n (payload: unknown) => boolean\n>()\n\n/**\n * Gives a rejection from Hydrogen's cached fetch the code `fetchOnce` gives\n * the same failure on the direct path.\n *\n * Hydrogen calls the platform `fetch` itself, so a request that never got a\n * response rejects with the platform's error instead of a `WeaverseError`.\n * The Fetch standard rejects a network failure with a `TypeError` and an\n * aborted request with the signal's `AbortError`/`TimeoutError`; those are\n * outages. Anything else is returned unchanged, so it is not reported as one.\n */\nfunction cachedTransportError(error: unknown, url: string): unknown {\n if (error instanceof WeaverseError || !(error instanceof Error)) {\n return error\n }\n if (error.name === 'AbortError' || error.name === 'TimeoutError') {\n return new WeaverseError('Request timed out', 'TIMEOUT_ERROR', undefined, {\n url,\n })\n }\n if (error instanceof TypeError) {\n return new WeaverseError(\n 'Network error during fetch',\n 'NETWORK_ERROR',\n undefined,\n { url, cause: error.message }\n )\n }\n return error\n}\n\n/**\n * Weaverse API version path\n */\nconst API_PATH = 'v1' as const\n\n/**\n * Cache strategy for Weaverse API requests: fresh for a minute, then served\n * stale for a day while it refreshes or while Builder errors.\n *\n * Every Builder resource uses it. Hydrogen 2026.4's subrequest cache only read\n * `max-age` for freshness, so the old per-resource `sMaxAge` values never\n * changed how long an entry stayed fresh.\n */\nconst DEFAULT_CACHE_STRATEGY: CachingStrategy = Cache({\n maxAge: CACHE_DURATIONS.SHORT,\n staleWhileRevalidate: CACHE_DURATIONS.LONG,\n staleIfError: CACHE_DURATIONS.LONG,\n})\n\n/** Builder API resource whose freshness policy should be used for a request. */\nexport type BuilderApiCacheTarget =\n | 'custom-pages'\n | 'merchant-overrides'\n | 'project'\n | 'theme-settings'\n\n/** Fetch options accepted by {@link WeaverseClient.fetchWithCache}. */\nexport interface WeaverseFetchWithCacheOptions extends RequestInit {\n /** Builder API resource the request reads; part of the cache key. */\n cacheTarget?: BuilderApiCacheTarget\n /** Explicit Hydrogen caching strategy overriding the default. */\n strategy?: CachingStrategy\n}\n\n/** Pagination and locale options for {@link WeaverseClient.fetchCustomPages}. */\nexport interface FetchCustomPagesOptions {\n /** Maximum number of custom pages requested per API page. */\n limit?: number\n /** Locale whose published custom pages should be returned. */\n locale?: string\n}\n\n/**\n * Options objects created by the internal `project_configs` request whose\n * subrequest cache identity deliberately excludes the body. Theme settings\n * carry the storefront origin so Builder can attribute hostnames; hashing the\n * body would give every domain of one project its own entry.\n *\n * The SUCCESSFUL theme payload is the same for every storefront of a project,\n * which is what makes one shared entry correct. This is not a claim that\n * Builder can never answer differently per host: a hostname-blocked request\n * returns a 403 `{ error }` body, and `shouldCacheResponse` rejects anything\n * `hasError()` matches, so a refusal is never stored under (or served from)\n * the shared entry. If a future response shape ever varies by host WITHOUT an\n * `error` field, this exclusion has to be revisited.\n *\n * Keyed on the exact options object, NOT on `cacheTarget`: `fetchWithCache`\n * is public API, so gating on the target would silently collapse the cache of\n * any consumer who selects `theme-settings` with varying bodies. An outside\n * caller's options object is never in this set and keeps a body-derived key.\n *\n * The WeakSet-membership slot in the cache key (`has(options)`) is the actual\n * discriminator — no caller-supplied value can reach it. The sentinel is not\n * load-bearing for that; it only keeps the omitted body legible in cache\n * debugging instead of a bare `undefined`.\n */\nconst bodyFreeCacheRequests = new WeakSet<object>()\nconst BODY_FREE_CACHE_SENTINEL = '\\u0000weaverse:body-free'\n\n/**\n * Request-scoped client for loading Weaverse pages and theme settings in Hydrogen.\n *\n * Create one client from the Hydrogen app context, then call\n * {@link WeaverseClient.loadPage} from route loaders and\n * {@link WeaverseClient.loadThemeSettings} from the root loader.\n */\nexport class WeaverseClient {\n /** Builder API version used by this client. */\n API = API_PATH\n /** Stable project configuration shared by page requests. */\n basePageConfigs: Omit<WeaverseProjectConfigs, 'requestInfo'>\n /** Stable request body fields shared by page requests. */\n basePageRequestBody: Omit<FetchProjectRequestBody, 'url'>\n /** Resolved project and rendering-mode configuration. */\n configs: WeaverseProjectConfigs\n /** Hydrogen cached fetch used for Builder API requests. */\n private cachedFetch: ReturnType<typeof createFetchWithCache>\n\n /** Incoming request associated with this client instance. */\n request: WeaverseClientArgs['request']\n /** Customer Account API client, when the storefront passes one. */\n customerAccount: WeaverseClientArgs['customerAccount']\n /** Hydrogen Storefront API client and locale context. */\n storefront: WeaverseClientArgs['storefront']\n /** Components registered for page rendering and component loaders. */\n components: WeaverseClientArgs['components']\n /** Theme schema used for defaults and Studio editing. */\n themeSchema: WeaverseClientArgs['themeSchema']\n /** Runtime environment variables. */\n env: WeaverseClientArgs['env']\n /** Cache storage supplied by the Hydrogen runtime. */\n cache: WeaverseClientArgs['cache']\n /** Background execution hook supplied by the runtime. */\n waitUntil: WeaverseClientArgs['waitUntil']\n\n // Performance optimizations\n private readonly parsedUrl: URL\n private readonly componentsByType: Map<string, WeaverseComponent>\n\n // Configuration\n private readonly locale: WeaverseI18n\n private readonly fetchTimeoutMs: number\n private readonly throwPageLoadFailures: boolean\n\n /** Creates a request-scoped client from the storefront's request context. */\n constructor(args: WeaverseClientArgs) {\n // Assign required dependencies\n this.request = args.request\n this.storefront = args.storefront\n this.customerAccount = args.customerAccount\n this.components = args.components\n this.themeSchema = args.themeSchema\n this.env = args.env\n this.cache = args.cache\n this.waitUntil = args.waitUntil\n\n // Performance optimizations\n this.parsedUrl = new URL(args.request.url)\n this.componentsByType = this.buildComponentLookupMap(args.components)\n\n // Configuration\n this.locale = args.locale ?? args.storefront.i18n\n this.fetchTimeoutMs = args.fetchTimeoutMs || DEFAULT_FETCH_TIMEOUT_MS\n this.throwPageLoadFailures = args.throwPageLoadFailures === true\n\n // Initialize cache and configs\n this.initializeCache(args)\n this.initializeConfigs(args)\n }\n\n /**\n * Initialize Hydrogen cache with proper configuration.\n * Logs warning if waitUntil is missing.\n */\n private initializeCache(args: WeaverseClientArgs): void {\n if (!args.waitUntil) {\n console.warn(\n '⚠️ WeaverseClient: waitUntil not provided, background operations may not complete'\n )\n }\n\n this.cachedFetch = createFetchWithCache({\n cache: args.cache,\n waitUntil: args.waitUntil,\n })\n }\n\n /**\n * Initialize configs with projectId resolution and fail-fast validation.\n * Throws WeaverseError if projectId cannot be resolved.\n */\n private initializeConfigs(args: WeaverseClientArgs): void {\n // Get base configs once to avoid duplicate calls\n const baseConfigs = getWeaverseConfigs(args.request, args.env)\n\n // Resolve projectId with multi-project support\n const resolvedProjectId = this.resolveProjectIdSync(\n args.projectId,\n args.request,\n args.env,\n baseConfigs\n )\n\n // Fail-fast validation: throw if projectId is empty\n if (!resolvedProjectId || resolvedProjectId.trim() === '') {\n throw new WeaverseError(\n 'Failed to initialize WeaverseClient: No valid projectId found. ' +\n 'Set WEAVERSE_PROJECT_ID environment variable or provide projectId parameter.',\n 'MISSING_PROJECT_ID'\n )\n }\n\n // Initialize configs with resolved projectId\n this.configs = {\n ...baseConfigs,\n projectId: resolvedProjectId,\n }\n\n // Initialize base configurations\n this.basePageConfigs = {\n projectId: this.configs.projectId,\n weaverseHost: this.configs.weaverseHost,\n weaverseApiBase: this.configs.weaverseApiBase,\n weaverseApiKey: this.configs.weaverseApiKey,\n weaverseVersion: this.configs.weaverseVersion,\n isDesignMode: this.configs.isDesignMode,\n isPreviewMode: this.configs.isPreviewMode,\n isRevisionPreview: this.configs.isRevisionPreview,\n }\n\n this.basePageRequestBody = {\n isDesignMode: this.configs.isDesignMode,\n i18n: this.locale,\n projectId: this.configs.projectId,\n }\n\n // Freeze configs to prevent accidental mutations\n Object.freeze(this.configs)\n Object.freeze(this.basePageConfigs)\n Object.freeze(this.basePageRequestBody)\n }\n\n /**\n * Build component lookup map for O(1) access by type.\n * Significantly improves performance for large component lists.\n */\n private buildComponentLookupMap(\n components: WeaverseComponent[]\n ): Map<string, WeaverseComponent> {\n return new Map(components.map((comp) => [comp.schema?.type, comp]))\n }\n\n /**\n * Extract comprehensive error information from unknown error type.\n * Preserves error context, codes, and stack traces for debugging.\n *\n * @param error - Unknown error to extract information from\n * @returns Structured error information with message, code, stack, and context\n *\n * @example\n * ```typescript\n * try {\n * // ... operation\n * } catch (error) {\n * const errorInfo = this.extractErrorInfo(error)\n * console.error('Operation failed:', errorInfo.message, errorInfo.context)\n * }\n * ```\n */\n private extractErrorInfo(error: unknown): {\n message: string\n code?: string\n stack?: string\n context?: Record<string, unknown>\n } {\n if (error instanceof WeaverseError) {\n return {\n message: error.message,\n code: error.code,\n context: error.context,\n stack: error.stack,\n }\n }\n\n if (error instanceof Error) {\n return {\n message: error.message,\n stack: error.stack,\n }\n }\n\n return {\n message: String(error),\n }\n }\n\n /**\n * Extract error message only (for backward compatibility).\n * Use extractErrorInfo() for more detailed error information.\n *\n * @deprecated Use extractErrorInfo() instead to preserve error context\n */\n private extractErrorMessage(error: unknown): string {\n return this.extractErrorInfo(error).message\n }\n\n /**\n * Validates projectId format (alphanumeric, hyphens, underscores only).\n * Returns detailed validation result with error message.\n *\n * @param projectId - Project identifier to validate\n * @returns Validation result with error message if invalid\n */\n private validateProjectId(projectId: string): ProjectIdValidationResult {\n if (!projectId || projectId.trim() === '') {\n return {\n valid: false,\n error: 'Project ID cannot be empty',\n }\n }\n\n // Only allow alphanumeric characters, hyphens, and underscores\n const validPattern = /^[a-zA-Z0-9_-]+$/\n if (!validPattern.test(projectId)) {\n return {\n valid: false,\n error: `Invalid format: \"${projectId}\". Only alphanumeric characters, hyphens, and underscores are allowed.`,\n }\n }\n\n return { valid: true }\n }\n\n /**\n * Resolves projectId with multi-project architecture support.\n * Uses priority chain to determine the correct project ID.\n *\n * @param projectId - Project identifier (string, function, or undefined)\n * @param request - HTTP request for context access\n * @param env - Hydrogen environment with variables\n * @param baseConfigs - Pre-fetched base configs to avoid duplicate calls\n * @returns Resolved project ID string (empty string if none found)\n */\n private resolveProjectIdSync(\n projectId: string | (() => string) | (() => Promise<string>) | undefined,\n request: Request,\n env: WeaverseEnv,\n baseConfigs?: WeaverseProjectConfigs\n ): string {\n return (\n this.resolveFromQueryParams(request) ||\n this.resolveFromFunction(projectId) ||\n this.resolveFromString(projectId) ||\n this.resolveFromEnvironment(baseConfigs, request, env) ||\n ''\n )\n }\n\n /**\n * Priority 1: Resolve projectId from URL query parameters.\n * Used for design mode and preview mode overrides.\n */\n private resolveFromQueryParams(request: Request): string | null {\n const queries = getRequestQueries<WeaverseStudioQueries>(request)\n return queries.weaverseProjectId || null\n }\n\n /**\n * Priority 2: Resolve projectId from function parameter.\n * Detects and rejects async functions with helpful error.\n */\n private resolveFromFunction(\n projectId: string | (() => string) | (() => Promise<string>) | undefined\n ): string | null {\n if (!isProjectIdFunction(projectId)) {\n return null\n }\n\n // Check if it's an async function BEFORE calling\n if (projectId.constructor.name === 'AsyncFunction') {\n throw new WeaverseError(\n 'Async projectId functions must be awaited before passing to WeaverseClient. ' +\n 'Use: projectId: await getProjectAsync() instead of: projectId: getProjectAsync',\n 'ASYNC_PROJECT_ID'\n )\n }\n\n try {\n const result = projectId()\n\n // Safety net: check if result is a Promise\n if (result instanceof Promise) {\n throw new WeaverseError(\n 'Async projectId functions must be awaited before passing to WeaverseClient. ' +\n 'Use: projectId: await getProjectAsync() instead of: projectId: getProjectAsync',\n 'ASYNC_PROJECT_ID'\n )\n }\n\n if (result && result.trim() !== '') {\n const trimmed = result.trim()\n const validation = this.validateProjectId(trimmed)\n\n if (validation.valid) {\n return trimmed\n }\n\n // Throw on validation failure - fail-fast for explicit projectId\n throw new WeaverseError(\n validation.error || 'Invalid projectId format',\n 'INVALID_PROJECT_ID'\n )\n }\n } catch (err) {\n // Re-throw WeaverseError (async function errors, validation errors)\n if (err instanceof WeaverseError) {\n throw err\n }\n\n // Wrap unexpected errors\n throw new WeaverseError(\n `Failed to evaluate projectId function: ${err instanceof Error ? err.message : String(err)}`,\n 'PROJECT_ID_FUNCTION_ERROR',\n undefined,\n { cause: err instanceof Error ? err.stack : String(err) }\n )\n }\n\n return null\n }\n\n /**\n * Priority 3: Resolve projectId from explicit string parameter.\n */\n private resolveFromString(\n projectId: string | (() => string) | (() => Promise<string>) | undefined\n ): string | null {\n if (typeof projectId !== 'string') {\n return null\n }\n\n const trimmed = projectId.trim()\n if (trimmed === '') {\n return null\n }\n\n const validation = this.validateProjectId(trimmed)\n if (validation.valid) {\n return trimmed\n }\n\n // Throw on validation failure - fail-fast for explicit projectId\n throw new WeaverseError(\n validation.error || 'Invalid projectId format',\n 'INVALID_PROJECT_ID'\n )\n }\n\n /**\n * Priority 4: Resolve projectId from environment variables.\n */\n private resolveFromEnvironment(\n baseConfigs: WeaverseProjectConfigs | undefined,\n request: Request,\n env: WeaverseEnv\n ): string | null {\n const configs = baseConfigs || getWeaverseConfigs(request, env)\n\n if (configs.projectId && configs.projectId.trim() !== '') {\n return configs.projectId.trim()\n }\n\n return null\n }\n\n /**\n * Helper method for direct fetching in design mode.\n * Includes 10-second timeout to prevent hanging requests.\n */\n private readonly fetchOnce = async <T>(\n url: string,\n options: RequestInit,\n timeoutMs: number\n ): Promise<WithCacheFetchResponse<T>> => {\n const controller = new AbortController()\n const timeoutId: ReturnType<typeof setTimeout> | undefined = setTimeout(\n () => controller.abort(),\n timeoutMs\n )\n\n try {\n const response = await fetch(url, {\n ...options,\n signal: controller.signal,\n })\n\n if (!response.ok) {\n const errorText = await response.text()\n throw new WeaverseError(\n `HTTP ${response.status}: ${response.statusText}`,\n 'FETCH_ERROR',\n response.status,\n { url, errorText }\n )\n }\n\n let data: T\n try {\n data = (await response.json()) as T\n } catch (parseError) {\n // A cut-short body still times out; anything else is a successful\n // status whose body is not JSON, which is not a network failure.\n if (parseError?.name === 'AbortError') {\n throw parseError\n }\n throw new WeaverseError(\n 'Response body is not valid JSON',\n 'INVALID_RESPONSE',\n response.status,\n { url }\n )\n }\n return { data, response }\n } catch (err) {\n if (err instanceof WeaverseError) {\n throw err\n }\n\n if (err.name === 'AbortError') {\n throw new WeaverseError(\n `Request timeout after ${timeoutMs}ms`,\n 'TIMEOUT_ERROR',\n undefined,\n { url, timeoutMs }\n )\n }\n\n throw new WeaverseError(\n 'Network error during fetch',\n 'NETWORK_ERROR',\n undefined,\n { url, cause: err instanceof Error ? err.message : String(err) }\n )\n } finally {\n if (timeoutId !== undefined) {\n clearTimeout(timeoutId)\n }\n }\n }\n\n private readonly directFetch = async <T>(\n url: string,\n options: RequestInit = {},\n timeoutMs = this.fetchTimeoutMs\n ): Promise<WithCacheFetchResponse<T>> => {\n let lastError: unknown\n for (let attempt = 1; attempt <= MAX_FETCH_ATTEMPTS; attempt++) {\n try {\n return await this.fetchOnce<T>(url, options, timeoutMs)\n } catch (err) {\n lastError = err\n if (attempt >= MAX_FETCH_ATTEMPTS || !isRetryableFetchError(err)) {\n throw err\n }\n await new Promise((resolve) =>\n setTimeout(resolve, FETCH_RETRY_BACKOFF_MS * attempt)\n )\n }\n }\n throw lastError\n }\n\n // Helper method for building API URLs consistently\n private getApiUrl(endpoint: string): string {\n const { weaverseApiBase } = this.configs\n return `${weaverseApiBase}/api/public/${endpoint}`\n }\n\n private isExternalPublicApiUrl(url: string): boolean {\n const { weaverseApiBase, weaverseHost } = this.configs\n return (\n weaverseApiBase !== weaverseHost &&\n url.startsWith(`${weaverseApiBase}/api/public/`)\n )\n }\n\n /**\n * Fetch data with caching support for both design mode and production.\n * Uses direct fetch in design mode, Hydrogen's cached fetch in production.\n * Automatically includes projectId in cache key for multi-project isolation.\n *\n * @param url - The URL to fetch from\n * @param options - Fetch options including optional cache strategy\n * @returns Promise resolving to the fetched data\n *\n * @example\n * ```typescript\n * const data = await weaverse.fetchWithCache<MyData>(\n * 'https://api.example.com/data',\n * { method: 'POST', body: JSON.stringify({ key: 'value' }) }\n * )\n * ```\n */\n public fetchWithCache = async <T = unknown>(\n url: string,\n options: WeaverseFetchWithCacheOptions = {}\n ): Promise<T> => {\n const { cacheTarget, strategy: strategyOverride, ...fetchOptions } = options\n const { body } = fetchOptions\n // Only a string or `URLSearchParams` body stringifies to its content for\n // the cache key. Any other body (FormData, Blob, ArrayBuffer, stream)\n // becomes a constant such as `[object FormData]` and would share one entry\n // across different bodies, so such requests skip the shared cache.\n const hasUncacheableBody =\n body != null &&\n typeof body !== 'string' &&\n !(body instanceof URLSearchParams)\n const strategy = hasUncacheableBody\n ? Cache.none()\n : strategyOverride || DEFAULT_CACHE_STRATEGY\n\n // Update cache key to include method, body content, and projectId for multi-project isolation\n // Prefix for easier debugging in cache systems\n //\n // The body is omitted only for options objects marked internally (see\n // `bodyFreeCacheRequests`): the storefront origin must reach Builder for\n // hostname attribution, but two domains of one project have to share one\n // entry. Every other caller keeps a body-derived key.\n const cacheKey = [\n 'weaverse-fetch',\n url,\n options.method || 'GET',\n bodyFreeCacheRequests.has(options)\n ? BODY_FREE_CACHE_SENTINEL\n : body == null\n ? null\n : String(body),\n // WeakSet-derived discriminator: this slot cannot be produced by any\n // caller-supplied shape, so no external call can reach the marked\n // identity — even one that happens to send the sentinel string as its\n // body.\n bodyFreeCacheRequests.has(options),\n this.configs.projectId,\n cacheTarget || 'default',\n ]\n\n // Bypass the shared Hydrogen subrequest cache for design/revision modes\n // and for the Cloudflare public API proxy. The proxy owns freshness with\n // versioned cache keys; keeping Hydrogen's URL/body cache in front would\n // keep serving a stale response even after Builder bumps the proxy's\n // project version.\n const validateBuilderPayload = builderPayloadValidators.get(options)\n\n if (\n this.configs.isDesignMode ||\n this.configs.isRevisionPreview ||\n this.isExternalPublicApiUrl(url)\n ) {\n const result = await this.directFetch<T>(url, fetchOptions)\n return result.data\n }\n\n let response: Response\n let text: string\n try {\n response = await this.cachedFetch(url, fetchOptions, {\n key: cacheKey,\n strategy,\n shouldCacheResponse: async ({ text: readText }) => {\n // An aborted request never caches: Hydrogen turns a body read cut\n // short by the abort into an empty body, not an error.\n if (fetchOptions.signal?.aborted) {\n return false\n }\n const payload = parseResponseText(await readText())\n if (validateBuilderPayload && !validateBuilderPayload(payload)) {\n return false\n }\n // Cache any non-error response (for both Weaverse API and third-party APIs)\n return !hasError(payload) && payload !== null && payload !== ''\n },\n })\n text = await response.text()\n } catch (error) {\n // Only this client's own Builder requests are normalized; a\n // third-party `fetchWithCache` caller keeps the platform error.\n throw validateBuilderPayload ? cachedTransportError(error, url) : error\n }\n\n if (!response.ok) {\n // For this client's own Builder requests a non-2xx status must not read\n // as \"nothing published\"; surface the status the direct path already\n // throws instead. Other callers keep the `null` body they always got.\n if (validateBuilderPayload) {\n throw new WeaverseError(\n `HTTP ${response.status}: ${response.statusText}`,\n 'FETCH_ERROR',\n response.status,\n { url }\n )\n }\n return null as T\n }\n\n return parseResponseText(text) as T\n }\n\n /**\n * Safe origin of the incoming storefront request: scheme, host and explicit\n * port. Userinfo, path, query and fragment are dropped, so no customer\n * route or credential-bearing value leaves the storefront. Returns undefined\n * for a non-http(s) request so the field is simply omitted.\n */\n private safeStorefrontOrigin(): string | undefined {\n const { protocol, host } = this.parsedUrl\n if (protocol !== 'http:' && protocol !== 'https:') {\n return\n }\n return host ? `${protocol}//${host}` : undefined\n }\n\n /**\n * Load theme settings from Weaverse API with fallback to schema defaults.\n * Merges remote settings with local theme schema and handles design mode serialization.\n *\n * @param strategy - Optional cache strategy override\n * @returns Promise resolving to theme settings response\n *\n * @example\n * ```typescript\n * const settings = await weaverse.loadThemeSettings()\n * const primaryColor = settings.theme.colors?.primary\n * ```\n */\n loadThemeSettings = async (\n strategy?: CachingStrategy\n ): Promise<WeaverseThemeSettingsResponse> => {\n const defaultThemeSettings = generateDataFromSchema(this.themeSchema)\n try {\n const { configs } = this\n const { projectId, isDesignMode } = configs\n if (!projectId) {\n throw new Error('Missing Weaverse projectId!')\n }\n\n const url = this.getApiUrl('project_configs')\n\n // Theme/config reads carry no route, so Builder could not tell which\n // storefront asked. `storefrontUrl` is the request's safe origin only —\n // scheme, host and explicit port, never a path, query, fragment or\n // userinfo — so hosted-content attribution works without sending\n // customer-visible URL data. Older Builder deployments ignore the extra\n // field. The response does not vary by storefront, so the origin stays\n // out of BOTH cache identities: the edge excludes it from its selectors,\n // and this request is marked in `bodyFreeCacheRequests` so Hydrogen's\n // own subrequest cache key omits the body — hashing it would otherwise\n // give every domain of one project its own entry.\n const body = JSON.stringify({\n isDesignMode,\n projectId,\n storefrontUrl: this.safeStorefrontOrigin(),\n })\n\n // Fetch theme settings and merchant overrides in parallel. The options\n // object is registered in `bodyFreeCacheRequests` so `fetchWithCache`\n // keeps the storefront origin out of the subrequest cache identity for\n // exactly this internal request — see the marker's doc comment.\n const themeSettingsOptions: WeaverseFetchWithCacheOptions = {\n method: 'POST',\n cacheTarget: 'theme-settings',\n strategy,\n body,\n headers: {\n 'Content-Type': 'application/json',\n Accept: 'application/json',\n 'Accept-Encoding': 'gzip, deflate, br',\n },\n }\n bodyFreeCacheRequests.add(themeSettingsOptions)\n builderPayloadValidators.set(themeSettingsOptions, isThemeConfigsEnvelope)\n\n const [data, merchantOverrides] = await Promise.all([\n this.fetchWithCache<WeaverseThemeSettingsResponse>(\n url,\n themeSettingsOptions\n ),\n this.fetchMerchantOverrides(strategy),\n ])\n\n // A body without a `theme` field (corrupted content, an error body, an\n // empty object) is not the merchant's configuration. Report it like any\n // other failure so schema defaults are never presented as loaded.\n if (!isThemeConfigsEnvelope(data)) {\n throw new WeaverseError(\n 'Invalid theme settings response format',\n 'INVALID_RESPONSE'\n )\n }\n\n let result = data\n\n if (this.themeSchema?.settings || this.themeSchema?.inspector) {\n // Ensure result.theme exists and is an object before merging\n if (!result.theme || typeof result.theme !== 'object') {\n result.theme = defaultThemeSettings\n } else {\n result.theme = {\n ...defaultThemeSettings,\n ...result.theme,\n }\n }\n }\n\n // Always include staticContent from themeSchema.i18n\n const staticContent = this.themeSchema?.i18n?.staticContent\n if (staticContent) {\n result.staticContent = staticContent\n }\n\n // Include merchant overrides (locale-specific translations from DB)\n if (merchantOverrides) {\n result.merchantOverrides = merchantOverrides\n }\n\n if (this.configs.isDesignMode) {\n result = {\n ...result,\n schema:\n this.themeSchema && typeof this.themeSchema === 'object'\n ? {\n ...this.themeSchema,\n settings: this.themeSchema?.settings?.map((group) => ({\n ...group,\n inputs: group?.inputs?.map((input) => {\n if (typeof input?.condition === 'function') {\n return {\n ...input,\n condition: input.condition.toString(),\n }\n }\n return input\n }),\n })),\n }\n : undefined,\n publicEnv: this.configs.publicEnv,\n }\n }\n return result\n } catch (error) {\n const errorMessage = 'Unable to load theme settings'\n const errorDetails = this.extractErrorMessage(error)\n\n console.error(errorMessage, errorDetails)\n\n return {\n theme: defaultThemeSettings,\n staticContent: this.themeSchema?.i18n?.staticContent,\n _error: errorDetails,\n _loadFailed: true,\n }\n }\n }\n\n /**\n * Fetch locale-specific merchant overrides from the translation API.\n * Returns a nested JSON object with translated values.\n * Falls back to empty object on error to avoid blocking theme rendering.\n */\n private fetchMerchantOverrides = async (\n strategy?: CachingStrategy\n ): Promise<Record<string, unknown> | undefined> => {\n try {\n const { projectId, weaverseHost } = this.configs\n const i18n = this.locale\n\n // Skip entirely when the theme hasn't opted into i18n. Merchant overrides\n // are locale-specific translations; a theme with no `i18n` schema has no\n // translatable surface, so the fetch would always come back empty. Gating\n // here keeps non-i18n storefronts from making a translation API call on\n // every SSR — same `themeSchema.i18n` condition that guards `staticContent`.\n if (!this.themeSchema?.i18n) {\n return\n }\n\n if (!(projectId && weaverseHost)) {\n return\n }\n\n // Build locale code from the market's locale (e.g. \"en-us\")\n const language = (i18n?.language || 'en').toLowerCase()\n const country = (i18n?.country || 'us').toLowerCase()\n const locale = `${language}-${country}`\n\n const url = `${weaverseHost}/api/translation/static?projectId=${projectId}&locale=${locale}`\n\n // Overrides are optional, and `loadThemeSettings` awaits them next to\n // the main settings. Hydrogen's cached fetch adds no deadline of its\n // own, so bound the request and its body with the client fetch timeout;\n // on abort the catch below falls back to theme defaults. Design and\n // revision modes ignore this signal and keep `directFetch`'s own\n // timeout and retry.\n const overrides = await this.fetchWithCache<Record<string, unknown>>(\n url,\n {\n method: 'GET',\n cacheTarget: 'merchant-overrides',\n strategy,\n headers: {\n Accept: 'application/json',\n },\n signal: AbortSignal.timeout(this.fetchTimeoutMs),\n }\n )\n\n // Validate it's a proper object\n if (typeof overrides === 'object' && overrides !== null) {\n return overrides\n }\n\n return\n } catch (error) {\n // Don't let merchant overrides failure block theme rendering\n console.warn(\n 'Unable to load merchant overrides, using theme defaults:',\n this.extractErrorMessage(error)\n )\n return\n }\n }\n\n /**\n * Generate fallback page with default structure when page data is unavailable.\n * Used internally when API returns no page or when page needs default content.\n *\n * @param message - Custom message to display in the fallback page\n * @returns WeaversePageData with single main component containing the message\n */\n generateFallbackPage = (message: string): WeaversePageData => {\n const rootId = crypto.randomUUID()\n return {\n id: `fallback_page_${Date.now()}`,\n rootId,\n name: 'Default Page',\n items: [\n {\n type: 'main',\n id: rootId,\n data: { dangerouslySetInnerHTML: { __html: message } },\n },\n ],\n }\n }\n\n /**\n * Fetch all published custom pages for sitemap generation.\n * Paginates automatically, never throws, returns accumulated results on partial failure.\n */\n async fetchCustomPages(\n opts?: FetchCustomPagesOptions\n ): Promise<CustomPageEntry[]> {\n const { weaverseApiBase, projectId } = this.configs\n const entries: CustomPageEntry[] = []\n const MAX_PAGES = 100\n\n let cursor: string | null = null\n\n for (let i = 0; i < MAX_PAGES; i++) {\n const params = new URLSearchParams()\n if (opts?.locale) {\n params.set('locale', opts.locale)\n }\n if (opts?.limit) {\n params.set('limit', String(opts.limit))\n }\n if (cursor) {\n params.set('cursor', cursor)\n }\n\n const qs = params.toString()\n const url = `${weaverseApiBase}/api/public/v1/projects/${projectId}/custom-pages${qs ? `?${qs}` : ''}`\n\n try {\n const result = await this.fetchWithCache<{\n data: CustomPageEntry[]\n nextCursor: string | null\n }>(url, { cacheTarget: 'custom-pages' })\n entries.push(...(result.data || []))\n\n if (!result.nextCursor) {\n return entries\n }\n cursor = result.nextCursor\n } catch (err: any) {\n const status = err?.status\n const msg = err?.message || String(err)\n console.warn(\n entries.length === 0\n ? `[Weaverse] Failed to fetch custom pages: ${msg}${status ? ` (HTTP ${status})` : ''}`\n : `[Weaverse] Custom pages pagination interrupted: ${msg}`\n )\n return entries\n }\n }\n\n console.warn(\n '[Weaverse] Custom pages pagination cap reached (100 iterations)'\n )\n return entries\n }\n\n /**\n * Load page data from Weaverse API with optional route-level project override.\n * Fetches page content, executes component loaders, and handles empty items fallback.\n *\n * @param params - Optional page loading parameters\n * @param params.type - Page type (INDEX, PAGE, PRODUCT, COLLECTION, etc.)\n * @param params.handle - Page handle for dynamic routes\n * @param params.locale - Locale override\n * @param params.projectId - Route-level project ID override (for multi-project)\n * @param params.strategy - Custom cache strategy override\n * @returns Promise resolving to page data or null on error\n *\n * @example\n * ```typescript\n * // Basic usage\n * const data = await weaverse.loadPage()\n *\n * // With page type\n * const data = await weaverse.loadPage({ type: 'INDEX' })\n *\n * // Route-level project override\n * const data = await weaverse.loadPage({\n * type: 'PAGE',\n * handle: 'about',\n * projectId: 'campaign-project-123'\n * })\n * ```\n */\n loadPage = async (\n params: LoadPageParams = {}\n ): Promise<WeaverseLoaderData | null> => {\n // Validate route-level projectId if provided\n if (params.projectId !== undefined) {\n const message =\n typeof params.projectId === 'string'\n ? this.validateProjectId(params.projectId).error\n : `must be a string. Received: ${typeof params.projectId}`\n if (message) {\n console.error(`Route-level projectId validation failed: ${message}`)\n if (this.throwPageLoadFailures) {\n throw new WeaverseLoadError(\n 'Route-level projectId validation failed',\n 'configuration'\n )\n }\n return null\n }\n }\n\n try {\n const { request, basePageRequestBody, basePageConfigs } = this\n const {\n projectId: clientProjectId,\n weaverseHost,\n isPreviewMode,\n sectionType,\n } = this.configs\n\n // Override client-level projectId with route-level projectId if provided\n const effectiveProjectId = params.projectId || clientProjectId\n\n if (isPreviewMode) {\n return getPreviewData(sectionType || '', this.components, weaverseHost)\n }\n\n if (!effectiveProjectId) {\n throw new WeaverseLoadError(\n 'Missing Weaverse projectId!',\n 'configuration'\n )\n }\n\n const { strategy, ...pageLoadParams } = params\n const body: {\n projectId: string\n url: string\n i18n?: WeaverseI18n\n params?: {\n type?: PageType\n locale?: string\n handle?: string\n }\n isDesignMode?: boolean\n } = {\n ...basePageRequestBody,\n projectId: effectiveProjectId, // Use effective (potentially overridden) projectId\n params: pageLoadParams,\n // Stripped to pathname + Weaverse params — tracking params would\n // fragment the subrequest cache key (see normalizePageUrl).\n url: normalizePageUrl(request.url),\n }\n\n const reqInit: RequestInit = {\n method: 'POST',\n body: JSON.stringify(body),\n headers: {\n 'Content-Type': 'application/json',\n Accept: 'application/json',\n 'Accept-Encoding': 'gzip, deflate, br',\n 'X-Visitor-UA': request.headers.get('user-agent') || '',\n },\n }\n const url = this.getApiUrl('project')\n\n const projectOptions: WeaverseFetchWithCacheOptions = {\n ...reqInit,\n cacheTarget: 'project',\n strategy,\n }\n builderPayloadValidators.set(projectOptions, isProjectEnvelope)\n const projectData: FetchProjectPayload =\n await this.fetchWithCache<FetchProjectPayload>(url, projectOptions)\n\n if (hasError(projectData)) {\n throw new WeaverseLoadError(\n 'Weaverse API returned an error body',\n 'invalid-response'\n )\n }\n\n if (!isProjectEnvelope(projectData)) {\n // A body that does not answer for this URL is not \"nothing assigned\n // here\". Without the opt-in, keep the historical fallback page.\n if (this.throwPageLoadFailures) {\n throw new WeaverseLoadError(\n 'Invalid project data structure received from API',\n 'invalid-response'\n )\n }\n console.warn(\n 'Invalid project data structure received from API, using fallback data',\n { projectId: effectiveProjectId }\n )\n }\n\n // Extract what we can; a legacy-mode malformed body may lack any field.\n const responseData = projectData as Partial<FetchProjectPayload> | null\n let page: WeaversePageData | undefined = responseData?.page ?? undefined\n let project: WeaverseLoaderData['project'] | undefined =\n responseData?.project\n const pageAssignment: WeaverseLoaderData['pageAssignment'] | undefined =\n responseData?.pageAssignment\n\n if (!project) {\n console.warn('Weaverse project not found. Id:', effectiveProjectId)\n project = {\n id: effectiveProjectId,\n name: 'Weaverse project not found.',\n weaverseShopId: '',\n }\n }\n\n if (!page) {\n page = this.generateFallbackPage(\n '<div style=\"text-align: center;\">Please add new section to start.</div>'\n )\n }\n\n if (page?.items?.length === 0) {\n page.items.push({\n type: 'main',\n id: page.rootId || crypto.randomUUID(),\n data: {},\n })\n }\n\n if (page?.items) {\n const items = page.items\n page.items = await Promise.all(items.map(this.execComponentLoader))\n }\n return {\n page,\n project,\n pageAssignment,\n configs: {\n ...basePageConfigs,\n requestInfo: {\n i18n: this.locale,\n queries: getRequestQueries<WeaverseStudioQueries>(request),\n pathname: this.parsedUrl.pathname,\n search: this.parsedUrl.search,\n },\n },\n }\n } catch (error) {\n const failure = classifyPageLoadFailure(error)\n const errorMessage = '❌ Page load failed'\n const errorContext: Record<string, unknown> = {\n projectId: params.projectId || this.configs.projectId,\n }\n\n if (error instanceof WeaverseError) {\n console.error(errorMessage, {\n code: error.code,\n message: error.message,\n ...errorContext,\n ...error.context,\n })\n } else if (error instanceof Error) {\n console.error(errorMessage, {\n message: error.message,\n stack: error.stack,\n ...errorContext,\n })\n } else {\n console.error(errorMessage, {\n error: String(error),\n ...errorContext,\n })\n }\n\n if (failure === 'absent' || !this.throwPageLoadFailures) {\n return null\n }\n if (error instanceof WeaverseLoadError) {\n throw error\n }\n // Fixed message: the cause can carry request-derived detail.\n throw new WeaverseLoadError(\n 'Unable to load Weaverse page',\n failure,\n error instanceof WeaverseError ? error.statusCode : undefined\n )\n }\n }\n\n /** Executes the registered loader for one component and attaches its result. */\n execComponentLoader = async (item: WeaverseComponentData) => {\n const { data = {}, type, id } = item\n const component = this.componentsByType.get(type)\n const loader = component?.loader\n if (typeof loader === 'function' && component) {\n try {\n return {\n ...item,\n loaderData: await Promise.resolve(\n loader({\n data: { ...generateDataFromSchema(component.schema), ...data },\n weaverse: this,\n })\n ),\n }\n } catch (error) {\n console.warn('❌ Item loader run failed.', type, id, error)\n return item\n }\n }\n return item\n }\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@weaverse/hydrogen",
|
|
3
3
|
"author": "Weaverse Team",
|
|
4
4
|
"description": "Components, hooks, and utilities for building Shopify Hydrogen websites with Weaverse",
|
|
5
|
-
"version": "6.1.
|
|
5
|
+
"version": "6.1.3",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"main": "dist/index.js",
|
|
8
8
|
"typings": "dist/types/index.d.ts",
|
|
@@ -66,11 +66,12 @@
|
|
|
66
66
|
"@shopify/hydrogen": ">=2026.10.0"
|
|
67
67
|
},
|
|
68
68
|
"devDependencies": {
|
|
69
|
-
"@shopify/hydrogen": "^2026.10.2"
|
|
69
|
+
"@shopify/hydrogen": "^2026.10.2",
|
|
70
|
+
"happy-dom": "^21.0.4"
|
|
70
71
|
},
|
|
71
72
|
"dependencies": {
|
|
72
|
-
"@weaverse/next": "1.1.
|
|
73
|
-
"@weaverse/react": "6.1.
|
|
73
|
+
"@weaverse/next": "1.1.4",
|
|
74
|
+
"@weaverse/react": "6.1.3",
|
|
74
75
|
"@weaverse/schema": "0.20.0",
|
|
75
76
|
"react": ">=19",
|
|
76
77
|
"react-dom": ">=19",
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { ElementData } from '@weaverse/react'
|
|
2
2
|
import {
|
|
3
3
|
isBrowser,
|
|
4
|
+
StudioSelectionStore,
|
|
5
|
+
StudioSelectionStoreContext,
|
|
4
6
|
useSafeExternalStore,
|
|
5
7
|
Weaverse,
|
|
6
8
|
WeaverseItemStore,
|
|
@@ -13,6 +15,7 @@ import {
|
|
|
13
15
|
Suspense,
|
|
14
16
|
useMemo,
|
|
15
17
|
useRef,
|
|
18
|
+
useState,
|
|
16
19
|
} from 'react'
|
|
17
20
|
import { ErrorBoundary } from 'react-error-boundary'
|
|
18
21
|
import {
|
|
@@ -637,17 +640,20 @@ export function withWeaverse(
|
|
|
637
640
|
if (!translationStoreRef.current) {
|
|
638
641
|
translationStoreRef.current = new TranslationStore()
|
|
639
642
|
}
|
|
643
|
+
let [studioSelectionStore] = useState(() => new StudioSelectionStore())
|
|
640
644
|
return (
|
|
641
645
|
<ThemeSettingsStoreContext.Provider value={themeSettingsStore}>
|
|
642
|
-
<
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
646
|
+
<StudioSelectionStoreContext.Provider value={studioSelectionStore}>
|
|
647
|
+
<TranslationProvider
|
|
648
|
+
merchantOverrides={translationData.merchantOverrides}
|
|
649
|
+
staticContent={translationData.staticContent}
|
|
650
|
+
t={options?.t}
|
|
651
|
+
translationStore={translationStoreRef.current}
|
|
652
|
+
>
|
|
653
|
+
<StudioConnect />
|
|
654
|
+
<Component {...props} />
|
|
655
|
+
</TranslationProvider>
|
|
656
|
+
</StudioSelectionStoreContext.Provider>
|
|
651
657
|
</ThemeSettingsStoreContext.Provider>
|
|
652
658
|
)
|
|
653
659
|
}
|
package/src/next.ts
CHANGED
|
@@ -7,16 +7,24 @@
|
|
|
7
7
|
* @packageDocumentation
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
//
|
|
11
|
-
//
|
|
10
|
+
// Server Components import this entry (root layouts, section schemas), so no
|
|
11
|
+
// runtime value here may load `@weaverse/react`, whose module scope calls
|
|
12
|
+
// `createContext`. Hooks and stores come from the Next adapter itself, which
|
|
13
|
+
// keeps React behind its own 'use client' boundary and shares its contexts;
|
|
14
|
+
// the rest comes from `@weaverse/schema` and React-free local modules, and the
|
|
15
|
+
// core and React entries contribute types only. `package:check` guards this.
|
|
12
16
|
export {
|
|
13
17
|
buildWeaverseNextRequestInfo,
|
|
14
18
|
createWeaverseNextClient,
|
|
19
|
+
isBrowser,
|
|
20
|
+
isIframe,
|
|
21
|
+
type StudioSelection,
|
|
15
22
|
TranslationProvider,
|
|
16
23
|
TranslationStore,
|
|
17
24
|
useChildInstances,
|
|
18
25
|
useItemInstance,
|
|
19
26
|
useParentInstance,
|
|
27
|
+
useStudioSelection,
|
|
20
28
|
useThemeSettings,
|
|
21
29
|
useTranslation,
|
|
22
30
|
useWeaverse,
|
|
@@ -31,5 +39,16 @@ export {
|
|
|
31
39
|
type WeaverseNextThemeSchema,
|
|
32
40
|
type WeaverseNextThemeSchemaGroup,
|
|
33
41
|
} from '@weaverse/next'
|
|
34
|
-
export
|
|
35
|
-
|
|
42
|
+
export {
|
|
43
|
+
type BasicInput,
|
|
44
|
+
createSchema,
|
|
45
|
+
IMAGES_PLACEHOLDERS,
|
|
46
|
+
type InspectorGroup,
|
|
47
|
+
type PageType,
|
|
48
|
+
type SchemaType,
|
|
49
|
+
} from '@weaverse/schema'
|
|
50
|
+
export type * from './index'
|
|
51
|
+
export type { WeaverseComponentProps } from './types'
|
|
52
|
+
export { WeaverseError, WeaverseLoadError } from './types'
|
|
53
|
+
export { generateDataFromSchema } from './utils'
|
|
54
|
+
export { getNestedKey } from './utils/nested-key'
|
package/src/react.ts
CHANGED
package/src/types.ts
CHANGED
|
@@ -7,6 +7,7 @@ import type {
|
|
|
7
7
|
import type {
|
|
8
8
|
ElementData,
|
|
9
9
|
PositionInputValue,
|
|
10
|
+
StudioSelectionStore,
|
|
10
11
|
WeaverseCoreParams,
|
|
11
12
|
WeaverseElement,
|
|
12
13
|
WeaverseImage,
|
|
@@ -173,6 +174,12 @@ export type WeaverseInternal = {
|
|
|
173
174
|
revalidate: () => void
|
|
174
175
|
/** Mutable theme settings store used by Studio. */
|
|
175
176
|
themeSettingsStore: ThemeSettingsStore
|
|
177
|
+
/**
|
|
178
|
+
* Studio selection store the Studio bridge writes; read by `useStudioSelection`.
|
|
179
|
+
* One store per root layout, shared by every Weaverse root on the document:
|
|
180
|
+
* Studio has one selection and writes it only through the root it is editing.
|
|
181
|
+
*/
|
|
182
|
+
studioSelectionStore?: StudioSelectionStore | null
|
|
176
183
|
/** Mutable translation store used by Studio, when translations are enabled. */
|
|
177
184
|
translationStore?: TranslationStore | null
|
|
178
185
|
/** @deprecated Use translationStore instead. */
|
package/src/utils/use-studio.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { loadScript } from '@weaverse/react'
|
|
2
|
-
import { useEffect } from 'react'
|
|
1
|
+
import { loadScript, StudioSelectionStoreContext } from '@weaverse/react'
|
|
2
|
+
import { useContext, useEffect } from 'react'
|
|
3
3
|
import {
|
|
4
4
|
useLocation,
|
|
5
5
|
useNavigate,
|
|
@@ -79,12 +79,25 @@ export function useStudioConnect() {
|
|
|
79
79
|
}, [search])
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
+
let warnedMissingSelectionStore = false
|
|
83
|
+
|
|
84
|
+
function warnMissingSelectionStore() {
|
|
85
|
+
if (warnedMissingSelectionStore) {
|
|
86
|
+
return
|
|
87
|
+
}
|
|
88
|
+
warnedMissingSelectionStore = true
|
|
89
|
+
console.warn(
|
|
90
|
+
'[Weaverse] useStudioSelection() needs the root Layout wrapped with withWeaverse(); Studio selections will not reach the theme.'
|
|
91
|
+
)
|
|
92
|
+
}
|
|
93
|
+
|
|
82
94
|
export function useStudio(weaverse: WeaverseHydrogen) {
|
|
83
95
|
let { revalidate } = useRevalidator()
|
|
84
96
|
let navigation = useNavigation()
|
|
85
97
|
let { pathname, search } = useLocation()
|
|
86
98
|
let navigate = useNavigate()
|
|
87
99
|
let themeSettingsStore = useThemeSettingsStore()
|
|
100
|
+
let studioSelectionStore = useContext(StudioSelectionStoreContext)
|
|
88
101
|
let { translationStore, themeTextStore, merchantOverrides } = useTranslation()
|
|
89
102
|
let {
|
|
90
103
|
isDesignMode,
|
|
@@ -112,12 +125,17 @@ export function useStudio(weaverse: WeaverseHydrogen) {
|
|
|
112
125
|
if (isRevisionPreview || isPreviewMode) {
|
|
113
126
|
loadScript(src).catch(console.error)
|
|
114
127
|
} else {
|
|
128
|
+
if (!studioSelectionStore) {
|
|
129
|
+
warnMissingSelectionStore()
|
|
130
|
+
}
|
|
115
131
|
weaverse.internal = {
|
|
116
132
|
...weaverse.internal,
|
|
117
133
|
navigate,
|
|
118
134
|
revalidate,
|
|
119
135
|
merchantOverrides,
|
|
120
136
|
themeSettingsStore,
|
|
137
|
+
// Must stay one instance per root: the effect deps above exclude it.
|
|
138
|
+
studioSelectionStore,
|
|
121
139
|
translationStore,
|
|
122
140
|
// Deprecated alias for existing Studio bridge integrations.
|
|
123
141
|
themeTextStore,
|