@weaverse/next 0.1.0-alpha.2 → 0.1.0-alpha.20

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.
Files changed (107) hide show
  1. package/README.md +586 -123
  2. package/dist/chunk-LH6AAEIK.mjs +326 -0
  3. package/dist/chunk-LH6AAEIK.mjs.map +1 -0
  4. package/dist/index.js +784 -60
  5. package/dist/index.js.map +1 -1
  6. package/dist/index.mjs +588 -58
  7. package/dist/index.mjs.map +1 -1
  8. package/dist/server.js +537 -76
  9. package/dist/server.js.map +1 -1
  10. package/dist/server.mjs +363 -27
  11. package/dist/server.mjs.map +1 -1
  12. package/dist/types/client.d.ts +16 -0
  13. package/dist/types/client.d.ts.map +1 -0
  14. package/dist/types/hooks.d.ts +20 -0
  15. package/dist/types/hooks.d.ts.map +1 -0
  16. package/dist/types/index.d.ts +34 -0
  17. package/dist/types/index.d.ts.map +1 -0
  18. package/dist/types/item.d.ts +60 -0
  19. package/dist/types/item.d.ts.map +1 -0
  20. package/dist/types/loader.d.ts +25 -0
  21. package/dist/types/loader.d.ts.map +1 -0
  22. package/dist/types/main.d.ts +11 -0
  23. package/dist/types/main.d.ts.map +1 -0
  24. package/dist/types/provider.d.ts +82 -0
  25. package/dist/types/provider.d.ts.map +1 -0
  26. package/dist/types/registry.d.ts +15 -0
  27. package/dist/types/registry.d.ts.map +1 -0
  28. package/dist/types/renderer.d.ts +21 -0
  29. package/dist/types/renderer.d.ts.map +1 -0
  30. package/dist/types/request-info.d.ts +19 -0
  31. package/dist/types/request-info.d.ts.map +1 -0
  32. package/dist/types/revalidate-item.d.ts +82 -0
  33. package/dist/types/revalidate-item.d.ts.map +1 -0
  34. package/dist/types/root-provider.d.ts +62 -0
  35. package/dist/types/root-provider.d.ts.map +1 -0
  36. package/dist/types/runtime.d.ts +113 -0
  37. package/dist/types/runtime.d.ts.map +1 -0
  38. package/dist/types/seo.d.ts +55 -0
  39. package/dist/types/seo.d.ts.map +1 -0
  40. package/dist/types/server/configs.d.ts +27 -0
  41. package/dist/types/server/configs.d.ts.map +1 -0
  42. package/dist/types/server/normalize-page-url.d.ts +19 -0
  43. package/dist/types/server/normalize-page-url.d.ts.map +1 -0
  44. package/dist/types/server/revalidate-handler.d.ts +55 -0
  45. package/dist/types/server/revalidate-handler.d.ts.map +1 -0
  46. package/dist/types/server/server-client.d.ts +21 -0
  47. package/dist/types/server/server-client.d.ts.map +1 -0
  48. package/dist/types/server.d.ts +19 -0
  49. package/dist/types/server.d.ts.map +1 -0
  50. package/dist/types/studio-bridge.d.ts +16 -0
  51. package/dist/types/studio-bridge.d.ts.map +1 -0
  52. package/dist/types/studio-connect.d.ts +27 -0
  53. package/dist/types/studio-connect.d.ts.map +1 -0
  54. package/dist/types/studio-router.d.ts +50 -0
  55. package/dist/types/studio-router.d.ts.map +1 -0
  56. package/dist/types/studio-script-src.d.ts +12 -0
  57. package/dist/types/studio-script-src.d.ts.map +1 -0
  58. package/dist/types/theme-settings-store.d.ts +18 -0
  59. package/dist/types/theme-settings-store.d.ts.map +1 -0
  60. package/dist/types/translation-context.d.ts +156 -0
  61. package/dist/types/translation-context.d.ts.map +1 -0
  62. package/dist/types/translation-store.d.ts +44 -0
  63. package/dist/types/translation-store.d.ts.map +1 -0
  64. package/dist/types/types.d.ts +665 -0
  65. package/dist/types/types.d.ts.map +1 -0
  66. package/dist/types/use-weaverse-next-studio.d.ts +34 -0
  67. package/dist/types/use-weaverse-next-studio.d.ts.map +1 -0
  68. package/dist/types/utils.d.ts +12 -0
  69. package/dist/types/utils.d.ts.map +1 -0
  70. package/package.json +12 -8
  71. package/src/client.ts +102 -0
  72. package/src/hooks.ts +50 -0
  73. package/src/index.ts +153 -0
  74. package/src/item.ts +167 -0
  75. package/src/loader.ts +135 -0
  76. package/src/main.tsx +45 -0
  77. package/src/provider.tsx +225 -0
  78. package/src/registry.ts +63 -0
  79. package/src/renderer.tsx +100 -0
  80. package/src/request-info.ts +124 -0
  81. package/src/revalidate-item.ts +211 -0
  82. package/src/root-provider.tsx +182 -0
  83. package/src/runtime.ts +555 -0
  84. package/src/seo.ts +157 -0
  85. package/src/server/configs.ts +178 -0
  86. package/src/server/normalize-page-url.ts +73 -0
  87. package/src/server/revalidate-handler.ts +357 -0
  88. package/src/server/server-client.ts +728 -0
  89. package/src/server.ts +67 -0
  90. package/src/studio-bridge.tsx +82 -0
  91. package/src/studio-connect.tsx +88 -0
  92. package/src/studio-router.ts +85 -0
  93. package/src/studio-script-src.ts +128 -0
  94. package/src/theme-settings-store.ts +47 -0
  95. package/src/translation-context.tsx +299 -0
  96. package/src/translation-store.ts +69 -0
  97. package/src/types.ts +753 -0
  98. package/src/use-weaverse-next-studio.tsx +106 -0
  99. package/src/utils.ts +37 -0
  100. package/dist/chunk-SS6QUKYY.mjs +0 -149
  101. package/dist/chunk-SS6QUKYY.mjs.map +0 -1
  102. package/dist/index.d.mts +0 -250
  103. package/dist/index.d.ts +0 -250
  104. package/dist/server.d.mts +0 -60
  105. package/dist/server.d.ts +0 -60
  106. package/dist/types-DvEeLX6L.d.mts +0 -321
  107. package/dist/types-DvEeLX6L.d.ts +0 -321
package/dist/index.js CHANGED
@@ -27,13 +27,101 @@ function generateDataFromSchema(schema) {
27
27
  }
28
28
 
29
29
  // src/item.ts
30
+ function flattenItemData(update) {
31
+ let { data, ...rest } = update;
32
+ if (!(data && typeof data === "object" && !Array.isArray(data))) {
33
+ return rest;
34
+ }
35
+ return { ...data, ...rest };
36
+ }
37
+ var deferredItemUpdates = null;
38
+ function collectDeferredItemUpdates(create) {
39
+ let previousQueue = deferredItemUpdates;
40
+ let queue = /* @__PURE__ */ new Set();
41
+ deferredItemUpdates = queue;
42
+ try {
43
+ return [create(), [...queue]];
44
+ } finally {
45
+ deferredItemUpdates = previousQueue;
46
+ }
47
+ }
30
48
  var WeaverseNextItem = class extends react.WeaverseItemStore {
49
+ /**
50
+ * Cached merged snapshot (base `_store` + translations). Reset whenever the
51
+ * `_store` ref or the item's translation entry ref changes, so
52
+ * `useSyncExternalStore` sees a stable object between unrelated re-renders.
53
+ */
54
+ _cachedSnapshot = null;
55
+ _snapshotStoreRef = null;
56
+ _snapshotTranslationRef = null;
57
+ /** Create an item store from serialized component data and its runtime. */
31
58
  constructor(initialData, weaverse) {
32
59
  super(initialData, weaverse);
33
- let { data, ...rest } = initialData;
34
60
  let schemaData = generateDataFromSchema(this.Element?.schema);
35
- Object.assign(this._store, schemaData, data, rest);
61
+ Object.assign(this._store, schemaData, flattenItemData(initialData));
36
62
  }
63
+ /**
64
+ * Flatten nested `data` on updates exactly like the constructor does. Core's
65
+ * `initProject()` calls `setData(item)` when a new runtime serves a page
66
+ * whose item instances already exist (e.g. locale/client navigation).
67
+ *
68
+ * A complete serialized item (it carries `id` and `type`; core's
69
+ * `initProject()` always passes one) replaces the store the way the
70
+ * constructor builds it: the schema defaults of the item's own `type` — the
71
+ * Builder can replace a section in place, keeping its id — then the item's
72
+ * settings. A payload omits a setting — or the whole optional `data` — when
73
+ * its value equals the schema default, so merging into the previous store
74
+ * would keep the previous locale's value visible. Partial updates still
75
+ * merge. Either way the `_store` ref changes, which invalidates the memoized
76
+ * snapshot below. The subscriber emit is queued instead of fired when a
77
+ * render-phase deferral is active (see `collectDeferredItemUpdates`).
78
+ */
79
+ setData = (update) => {
80
+ if ("id" in update && "type" in update) {
81
+ let { data, type } = update;
82
+ let schema = this.weaverse.elementRegistry.get(type)?.schema;
83
+ this._store = {
84
+ ...generateDataFromSchema(schema),
85
+ ...data === void 0 ? {} : { data },
86
+ ...flattenItemData(update)
87
+ };
88
+ } else {
89
+ this.data = flattenItemData(update);
90
+ }
91
+ if (deferredItemUpdates) {
92
+ deferredItemUpdates.add(this);
93
+ } else {
94
+ this.triggerUpdate();
95
+ }
96
+ return this.data;
97
+ };
98
+ /**
99
+ * Overlay item-level translations onto the base store when the runtime is in
100
+ * translation mode. Mirrors Hydrogen's `WeaverseHydrogenItem.getSnapShot()`:
101
+ * memoized by `_store` ref + translation entry ref so the returned object is
102
+ * referentially stable, and a fast path that returns the base store untouched
103
+ * when the item has no translations.
104
+ */
105
+ getSnapShot = () => {
106
+ let base = this._store;
107
+ let translations = this.weaverse.translationMap[this._id];
108
+ if (!translations) {
109
+ return base;
110
+ }
111
+ if (this._cachedSnapshot && this._snapshotStoreRef === base && this._snapshotTranslationRef === translations) {
112
+ return this._cachedSnapshot;
113
+ }
114
+ let merged = { ...base };
115
+ for (let [key, entry] of Object.entries(translations)) {
116
+ if (entry.translatedValue !== void 0) {
117
+ merged[key] = entry.translatedValue;
118
+ }
119
+ }
120
+ this._cachedSnapshot = merged;
121
+ this._snapshotStoreRef = base;
122
+ this._snapshotTranslationRef = translations;
123
+ return merged;
124
+ };
37
125
  };
38
126
  function ensureNextItemConstructor() {
39
127
  if (!react.Weaverse.ItemConstructor || react.Weaverse.ItemConstructor === react.WeaverseItemStore || react.Weaverse.ItemConstructor === WeaverseNextItem) {
@@ -158,30 +246,264 @@ function createWeaverseNextThemeSettingsStore(options = {}) {
158
246
  };
159
247
  return store;
160
248
  }
249
+ function getNestedKey(obj, path, fallback) {
250
+ let keys = path.split(".");
251
+ let current = obj;
252
+ for (let key of keys) {
253
+ if (current == null || typeof current !== "object") {
254
+ return fallback;
255
+ }
256
+ current = current[key];
257
+ }
258
+ if (typeof current === "string") {
259
+ return current;
260
+ }
261
+ return fallback;
262
+ }
263
+ function interpolate(template, variables) {
264
+ if (!variables) {
265
+ return template;
266
+ }
267
+ return template.replace(/\{\{(\w+)\}\}/g, (match, key) => {
268
+ let value = variables[key];
269
+ return value === void 0 ? match : String(value);
270
+ });
271
+ }
272
+ var TranslationContext = react$1.createContext(null);
273
+ TranslationContext.displayName = "WeaverseNextTranslationContext";
274
+ function createTranslate(sources) {
275
+ let { staticContent, merchantOverrides, designOverrides, externalT } = sources;
276
+ return (key, variables) => {
277
+ if (externalT) {
278
+ let external = externalT(key, variables);
279
+ if (external && external !== key) {
280
+ return external;
281
+ }
282
+ }
283
+ let design = designOverrides && Object.hasOwn(designOverrides, key) ? designOverrides[key] : void 0;
284
+ let override = merchantOverrides ? getNestedKey(merchantOverrides, key) : void 0;
285
+ let staticValue = getNestedKey(staticContent, key);
286
+ let raw = design ?? override ?? staticValue ?? key;
287
+ return interpolate(raw, variables);
288
+ };
289
+ }
290
+ var EMPTY = {};
291
+ var NOOP_SUBSCRIBE = () => () => {
292
+ };
293
+ var NOOP_SNAPSHOT = () => EMPTY;
294
+ function TranslationProvider({
295
+ staticContent,
296
+ merchantOverrides,
297
+ t: externalT,
298
+ translationStore,
299
+ themeTextStore,
300
+ children
301
+ }) {
302
+ let store = translationStore ?? themeTextStore ?? null;
303
+ let designOverrides = react.useSafeExternalStore(
304
+ store?.subscribe ?? NOOP_SUBSCRIBE,
305
+ store?.getSnapshot ?? NOOP_SNAPSHOT,
306
+ store?.getServerSnapshot ?? NOOP_SNAPSHOT
307
+ );
308
+ let value = react$1.useMemo(
309
+ () => ({
310
+ t: createTranslate({
311
+ staticContent,
312
+ merchantOverrides,
313
+ designOverrides,
314
+ externalT
315
+ }),
316
+ translationStore: store,
317
+ // Deprecated alias — same instance, kept for backward compatibility.
318
+ themeTextStore: store,
319
+ merchantOverrides: merchantOverrides ?? null
320
+ }),
321
+ [staticContent, merchantOverrides, designOverrides, externalT, store]
322
+ );
323
+ return /* @__PURE__ */ jsxRuntime.jsx(TranslationContext.Provider, { value, children });
324
+ }
325
+ function useTranslation() {
326
+ let ctx = react$1.useContext(TranslationContext);
327
+ if (!ctx) {
328
+ throw new Error(
329
+ "useTranslation must be used within <TranslationProvider>. Wrap your app with <WeaverseNextRootProvider>."
330
+ );
331
+ }
332
+ return ctx;
333
+ }
334
+ var ThemeTextProvider = TranslationProvider;
335
+ var useThemeText = useTranslation;
336
+
337
+ // src/translation-store.ts
338
+ var TranslationStore = class {
339
+ /** Current flat translation overrides keyed by dot path. */
340
+ overrides = {};
341
+ listeners = /* @__PURE__ */ new Set();
342
+ /** Replace every override and notify subscribers. */
343
+ setOverrides = (overrides) => {
344
+ this.overrides = { ...overrides };
345
+ this.emit();
346
+ };
347
+ /** Merge changed overrides into the current values and notify subscribers. */
348
+ updateOverrides = (newOverrides) => {
349
+ this.overrides = { ...this.overrides, ...newOverrides };
350
+ this.emit();
351
+ };
352
+ /** Return the current client-side override snapshot. */
353
+ getSnapshot = () => this.overrides;
354
+ /** Return the override snapshot used during server rendering. */
355
+ getServerSnapshot = () => this.overrides;
356
+ /** Subscribe to override changes and return an unsubscribe function. */
357
+ subscribe = (callback) => {
358
+ this.listeners.add(callback);
359
+ return () => {
360
+ this.listeners.delete(callback);
361
+ };
362
+ };
363
+ emit = () => {
364
+ for (const listener of Array.from(this.listeners)) {
365
+ try {
366
+ listener();
367
+ } catch (error) {
368
+ console.error("TranslationStore: Listener error", error);
369
+ }
370
+ }
371
+ };
372
+ };
373
+ var ThemeTextStore = TranslationStore;
374
+ var WeaverseNextRootContext = react$1.createContext(null);
375
+ function WeaverseNextRootProvider(props) {
376
+ let {
377
+ children,
378
+ initialThemeSettings,
379
+ merchantOverrides,
380
+ staticContent,
381
+ t,
382
+ themeSchema,
383
+ publicEnv
384
+ } = props;
385
+ let storeRef = react$1.useRef(null);
386
+ if (!storeRef.current) {
387
+ storeRef.current = createWeaverseNextThemeSettingsStore({
388
+ publicEnv,
389
+ schema: themeSchema,
390
+ settings: initialThemeSettings
391
+ });
392
+ }
393
+ let themeSettingsStore = storeRef.current;
394
+ let translationStoreRef = react$1.useRef(null);
395
+ if (!translationStoreRef.current) {
396
+ translationStoreRef.current = new TranslationStore();
397
+ }
398
+ let translationStore = translationStoreRef.current;
399
+ let appliedThemeSettingsRef = react$1.useRef(initialThemeSettings);
400
+ react$1.useEffect(() => {
401
+ if (initialThemeSettings && appliedThemeSettingsRef.current !== initialThemeSettings) {
402
+ themeSettingsStore.updateThemeSettings(initialThemeSettings);
403
+ appliedThemeSettingsRef.current = initialThemeSettings;
404
+ }
405
+ }, [initialThemeSettings, themeSettingsStore]);
406
+ let resolvedStaticContent = staticContent ?? getSchemaStaticContent(themeSchema);
407
+ let rootContextValue = react$1.useMemo(
408
+ () => ({
409
+ merchantOverrides,
410
+ staticContent: resolvedStaticContent,
411
+ themeSettingsStore,
412
+ translationStore
413
+ }),
414
+ [
415
+ merchantOverrides,
416
+ resolvedStaticContent,
417
+ themeSettingsStore,
418
+ translationStore
419
+ ]
420
+ );
421
+ let contextValue = react$1.useMemo(
422
+ () => ({
423
+ merchantOverrides,
424
+ themeSettings: themeSettingsStore.getSnapshot(),
425
+ themeSettingsStore,
426
+ translationStore
427
+ }),
428
+ [merchantOverrides, themeSettingsStore, translationStore]
429
+ );
430
+ return /* @__PURE__ */ jsxRuntime.jsx(WeaverseNextRootContext.Provider, { value: rootContextValue, children: /* @__PURE__ */ jsxRuntime.jsx(WeaverseNextContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsxRuntime.jsx(
431
+ TranslationProvider,
432
+ {
433
+ merchantOverrides,
434
+ staticContent: resolvedStaticContent,
435
+ t,
436
+ translationStore,
437
+ children
438
+ }
439
+ ) }) });
440
+ }
161
441
  var EMPTY_THEME_SETTINGS = {};
442
+ var EMPTY_STATIC_CONTENT = {};
443
+ function getSchemaStaticContent(schema) {
444
+ let i18n = schema?.i18n;
445
+ return i18n?.staticContent ?? EMPTY_STATIC_CONTENT;
446
+ }
162
447
  var WeaverseNextContext = react$1.createContext(null);
163
448
  function WeaverseNextProvider(props) {
164
449
  let { children, client, rootData, pageData, commerce, themeSettings } = props;
450
+ let rootContext = react$1.useContext(WeaverseNextRootContext);
165
451
  let themeSettingsValue = themeSettings ?? client?.themeSettings ?? EMPTY_THEME_SETTINGS;
166
- let themeSettingsStore = react$1.useMemo(
167
- () => createWeaverseNextThemeSettingsStore({
452
+ let ownTranslationStoreRef = react$1.useRef(null);
453
+ if (!(rootContext || ownTranslationStoreRef.current)) {
454
+ ownTranslationStoreRef.current = new TranslationStore();
455
+ }
456
+ let translationStore = rootContext?.translationStore ?? ownTranslationStoreRef.current;
457
+ let staticContent = props.staticContent ?? rootContext?.staticContent ?? getSchemaStaticContent(client?.themeSchema);
458
+ let merchantOverrides = props.merchantOverrides ?? rootContext?.merchantOverrides;
459
+ let ownThemeSettingsStore = react$1.useMemo(() => {
460
+ if (rootContext) {
461
+ return null;
462
+ }
463
+ return createWeaverseNextThemeSettingsStore({
168
464
  schema: client?.themeSchema,
169
465
  settings: themeSettingsValue
170
- }),
171
- [client?.themeSchema, themeSettingsValue]
172
- );
466
+ });
467
+ }, [rootContext, client?.themeSchema, themeSettingsValue]);
468
+ let themeSettingsStore = rootContext?.themeSettingsStore ?? ownThemeSettingsStore;
469
+ let mergedIntoRootRef = react$1.useRef(void 0);
470
+ react$1.useEffect(() => {
471
+ if (rootContext && themeSettingsValue !== EMPTY_THEME_SETTINGS && mergedIntoRootRef.current !== themeSettingsValue) {
472
+ rootContext.themeSettingsStore.updateThemeSettings(themeSettingsValue);
473
+ mergedIntoRootRef.current = themeSettingsValue;
474
+ }
475
+ }, [rootContext, themeSettingsValue]);
173
476
  let value = react$1.useMemo(
174
477
  () => ({
175
478
  client,
176
479
  rootData,
177
480
  pageData,
178
481
  commerce: commerce ?? client?.commerce,
482
+ merchantOverrides,
179
483
  themeSettings: themeSettingsStore.getSnapshot(),
180
- themeSettingsStore
484
+ themeSettingsStore,
485
+ translationStore
181
486
  }),
182
- [client, rootData, pageData, commerce, themeSettingsStore]
487
+ [
488
+ client,
489
+ rootData,
490
+ pageData,
491
+ commerce,
492
+ merchantOverrides,
493
+ themeSettingsStore,
494
+ translationStore
495
+ ]
183
496
  );
184
- return /* @__PURE__ */ jsxRuntime.jsx(WeaverseNextContext.Provider, { value, children });
497
+ return /* @__PURE__ */ jsxRuntime.jsx(WeaverseNextContext.Provider, { value, children: /* @__PURE__ */ jsxRuntime.jsx(
498
+ TranslationProvider,
499
+ {
500
+ merchantOverrides,
501
+ staticContent,
502
+ t: props.t,
503
+ translationStore,
504
+ children
505
+ }
506
+ ) });
185
507
  }
186
508
 
187
509
  // src/hooks.ts
@@ -302,13 +624,22 @@ function getSearchParamsFromContext(context) {
302
624
  }
303
625
  return new URLSearchParams();
304
626
  }
627
+ function withPathPrefix(pathname, i18n) {
628
+ let raw = typeof i18n?.pathPrefix === "string" ? i18n.pathPrefix : "";
629
+ let segments = raw.split("/").filter(Boolean);
630
+ let prefix = segments.length > 0 ? `/${segments.join("/")}` : "";
631
+ if (prefix === "" || pathname === prefix || pathname.startsWith(`${prefix}/`)) {
632
+ return pathname;
633
+ }
634
+ return pathname === "/" ? prefix : `${prefix}${pathname}`;
635
+ }
305
636
  function getPathnameFromContext(context) {
306
637
  if (context?.pathname) {
307
- return context.pathname;
638
+ return withPathPrefix(context.pathname, context.i18n);
308
639
  }
309
640
  if (context?.url) {
310
641
  let url = toUrl(context.url);
311
- return url.pathname || "/";
642
+ return withPathPrefix(url.pathname || "/", context.i18n);
312
643
  }
313
644
  return "/";
314
645
  }
@@ -328,14 +659,30 @@ function toQueryRecord(searchParams) {
328
659
  }
329
660
  return queries;
330
661
  }
662
+ var TRANSIENT_STUDIO_PARAMS = /* @__PURE__ */ new Set([
663
+ "weaverseDraftItem",
664
+ // Legacy name from the original design-mode draft-item spike. Keep it out of
665
+ // runtime requestInfo too, in case an older Builder bundle is in front of a
666
+ // newer Next storefront.
667
+ "__weaverseDraftItem"
668
+ ]);
669
+ function getRequestInfoSearchParams(context) {
670
+ let searchParams = new URLSearchParams(getSearchParamsFromContext(context));
671
+ for (let key of TRANSIENT_STUDIO_PARAMS) {
672
+ searchParams.delete(key);
673
+ }
674
+ return searchParams;
675
+ }
331
676
  function buildWeaverseNextRequestInfo(context) {
332
- let searchParams = getSearchParamsFromContext(context);
677
+ let searchParams = getRequestInfoSearchParams(context);
333
678
  let search = searchParams.toString();
334
679
  return {
335
680
  pathname: getPathnameFromContext(context),
336
681
  search: search ? `?${search}` : "",
337
682
  queries: toQueryRecord(searchParams),
338
- ...context?.i18n ? { i18n: context.i18n } : {}
683
+ ...context?.i18n ? { i18n: context.i18n } : {},
684
+ ...context?.pageType ? { pageType: context.pageType } : {},
685
+ ...context?.handle ? { handle: context.handle } : {}
339
686
  };
340
687
  }
341
688
 
@@ -352,6 +699,12 @@ function getConfigString(configs, key) {
352
699
  let value = configs?.[key];
353
700
  return typeof value === "string" ? value : void 0;
354
701
  }
702
+ function resolveModeFlag(config, key) {
703
+ return config.client?.requestContext?.[key] === true || config.data.configs?.[key] === true;
704
+ }
705
+ function resolveSectionType(config) {
706
+ return config.client?.requestContext?.sectionType || getConfigString(config.data.configs, "sectionType") || void 0;
707
+ }
355
708
  function getRecordString(record, key) {
356
709
  let value = record?.[key];
357
710
  return typeof value === "string" ? value : void 0;
@@ -388,38 +741,87 @@ function registerRuntime(runtime) {
388
741
  runtimeWindow.__weaverses[runtime.pageId] = runtime;
389
742
  runtimeWindow.__weaverse = runtime;
390
743
  }
744
+ function rebindPageItemsToRuntime(runtime) {
745
+ let items = runtime.data?.items;
746
+ if (!items) {
747
+ return;
748
+ }
749
+ for (let { id } of items) {
750
+ let instance = runtime.itemInstances.get(id);
751
+ if (instance && instance.weaverse !== runtime) {
752
+ instance.weaverse = runtime;
753
+ }
754
+ }
755
+ }
391
756
  var WeaverseNextRuntime = class extends react.Weaverse {
757
+ /** Stable ID of the page owned by this runtime. */
392
758
  pageId;
759
+ /** Mutable callbacks and stores consumed by the Studio bridge. */
393
760
  internal;
761
+ /** Path, query, and locale metadata for Studio navigation. */
394
762
  requestInfo;
763
+ /** Whether this runtime renders a component preview. */
395
764
  isPreviewMode;
765
+ /** Whether this runtime renders a saved revision. */
396
766
  isRevisionPreview;
767
+ /** Component type rendered in section preview mode. */
397
768
  sectionType;
769
+ // ─── Item-level translation sidecar (design mode only) ─────────────
770
+ /**
771
+ * Translation map received from Builder in design mode, keyed by item ID.
772
+ * `WeaverseNextItem.getSnapShot()` overlays these onto the data handed to
773
+ * components, so translations render without mutating the base `_store`.
774
+ */
398
775
  translationMap = {};
399
- translationLocale = "";
400
- translationLanguageId = "";
776
+ /** Locale associated with the item-level translation sidecar. */
777
+ translationLocale = String();
778
+ /** Weaverse language ID used when saving translation changes. */
779
+ translationLanguageId = String();
780
+ /**
781
+ * Fresh page data captured while React was rendering. Runtime reuse happens
782
+ * inside the renderer's render phase (`useMemo`), where applying it through
783
+ * `setProjectData()` would emit item updates to `useSyncExternalStore`
784
+ * subscribers mid-render. The renderer applies it post-commit via
785
+ * `flushRenderPhaseUpdates()`. Never set on design-mode runtimes — the live
786
+ * Studio tree owns the page data there.
787
+ */
788
+ pendingProjectData;
789
+ /**
790
+ * Reused item instances whose data was refreshed while this runtime was
791
+ * constructed during render (core `initProject()` calls `setData()` on
792
+ * them). Their data is already fresh; only the subscriber notification was
793
+ * deferred to `flushRenderPhaseUpdates()`.
794
+ */
795
+ pendingItemUpdates = [];
796
+ /** Create a browser runtime from a loaded page and its request context. */
401
797
  constructor(config) {
402
798
  let { client, data } = config;
403
799
  let page = getRenderablePage(data);
404
800
  let configs = data.configs;
405
801
  let requestContext = client?.requestContext;
802
+ let isDesignMode = resolveModeFlag(config, "isDesignMode");
803
+ let isPreviewMode = resolveModeFlag(config, "isPreviewMode");
804
+ let isRevisionPreview = resolveModeFlag(config, "isRevisionPreview");
805
+ let sectionType = resolveSectionType(config);
406
806
  let projectId = resolveProjectId(client, data, page.id);
407
807
  ensureNextItemConstructor();
408
808
  super({
409
809
  projectId,
410
810
  data: page,
411
- isDesignMode: requestContext?.isDesignMode ?? false,
811
+ isDesignMode,
412
812
  weaverseHost: getConfigString(configs, "weaverseHost"),
413
813
  weaverseVersion: getConfigString(configs, "weaverseVersion")
414
814
  });
415
815
  this.pageId = page.id;
416
816
  this.dataContext = resolveDataContext(config);
417
817
  this.requestInfo = buildWeaverseNextRequestInfo(requestContext);
418
- this.isDesignMode = requestContext?.isDesignMode ?? false;
419
- this.isPreviewMode = requestContext?.isPreviewMode ?? false;
420
- this.isRevisionPreview = requestContext?.isRevisionPreview ?? false;
421
- this.sectionType = requestContext?.sectionType;
818
+ this.isDesignMode = isDesignMode;
819
+ this.isPreviewMode = isPreviewMode;
820
+ this.isRevisionPreview = isRevisionPreview;
821
+ this.sectionType = sectionType;
822
+ let translationStore = config.translationStore ?? new TranslationStore();
422
823
  this.internal = {
824
+ merchantOverrides: config.merchantOverrides,
423
825
  navigate: config.navigate,
424
826
  pageAssignment: data.pageAssignment,
425
827
  project: data.project,
@@ -427,17 +829,119 @@ var WeaverseNextRuntime = class extends react.Weaverse {
427
829
  themeSettingsStore: config.themeSettingsStore ?? createWeaverseNextThemeSettingsStore({
428
830
  schema: client?.themeSchema,
429
831
  settings: client?.themeSettings
430
- })
832
+ }),
833
+ translationStore,
834
+ themeTextStore: translationStore
431
835
  };
836
+ this.extractTranslationSidecar();
432
837
  }
433
- extractTranslationSidecar = () => void 0;
838
+ /**
839
+ * Pull `translationMap` / `translationLocale` / `translationLanguageId` off
840
+ * the current page data (Builder attaches them in design mode). Unlike
841
+ * Hydrogen, this also clears the sidecar to defaults when the new page data
842
+ * carries none, so a reused runtime / project-data swap can't leave a stale
843
+ * map overlaying the fresh tree.
844
+ */
845
+ extractTranslationSidecar = () => {
846
+ let pageData = this.data;
847
+ let translationMap = pageData?.translationMap;
848
+ if (translationMap) {
849
+ this.translationMap = translationMap;
850
+ this.translationLocale = pageData?.translationLocale || "";
851
+ this.translationLanguageId = pageData?.translationLanguageId || "";
852
+ } else {
853
+ this.translationMap = {};
854
+ this.translationLocale = "";
855
+ this.translationLanguageId = "";
856
+ }
857
+ };
858
+ /**
859
+ * Replace the translation sidecar (Builder pushes this when translation data
860
+ * arrives or the active language changes) and refresh every item so their
861
+ * merged snapshots recompute.
862
+ */
434
863
  setTranslationSidecar = (map = {}, locale = "", languageId = "") => {
435
864
  this.translationMap = map;
436
865
  this.translationLocale = locale;
437
866
  this.translationLanguageId = languageId;
867
+ this.refreshAllItems();
868
+ };
869
+ /**
870
+ * Update a single translatable field for one item. Assigns a new per-item
871
+ * entry object so `WeaverseNextItem.getSnapShot()`'s translation-ref cache
872
+ * invalidates, then triggers only that item's subscribers.
873
+ */
874
+ updateTranslation = (itemId, key, originalValue, translatedValue) => {
875
+ if (!this.translationMap[itemId]) {
876
+ this.translationMap[itemId] = {};
877
+ }
878
+ this.translationMap[itemId] = {
879
+ ...this.translationMap[itemId],
880
+ [key]: { originalValue, translatedValue }
881
+ };
882
+ let instance = this.itemInstances.get(itemId);
883
+ if (instance) {
884
+ instance.triggerUpdate();
885
+ }
886
+ };
887
+ /**
888
+ * Collect translation changes for the save flow. Returns `undefined` unless
889
+ * both the locale and language ID are set and at least one entry exists;
890
+ * each entry key is namespaced as `data.<field>` to match Builder.
891
+ */
892
+ getTranslationChanges = () => {
893
+ let languageId = this.translationLanguageId;
894
+ if (!(this.translationLocale && languageId)) {
895
+ return;
896
+ }
897
+ let entries = [];
898
+ for (let [itemId, fields] of Object.entries(this.translationMap)) {
899
+ for (let [key, entry] of Object.entries(fields)) {
900
+ entries.push({
901
+ itemId,
902
+ key: `data.${key}`,
903
+ originalValue: entry.originalValue,
904
+ translatedValue: entry.translatedValue
905
+ });
906
+ }
907
+ }
908
+ if (entries.length === 0) {
909
+ return;
910
+ }
911
+ return { languageId, entries };
912
+ };
913
+ /**
914
+ * Re-extract the translation sidecar whenever project data is replaced (route
915
+ * change / non-design reuse), before rebuilding item stores. Takes the
916
+ * renderable page shape (`rootId` resolved) that `getRenderablePage()` and
917
+ * the reuse branch pass in.
918
+ */
919
+ setProjectData = (data) => {
920
+ this.data = data;
921
+ this.extractTranslationSidecar();
922
+ this.initProject();
923
+ };
924
+ /**
925
+ * Apply updates deferred off the render phase: swap in any pending page data
926
+ * captured on runtime reuse, then notify items whose data was refreshed
927
+ * during construction. The renderer calls this from a layout effect right
928
+ * after commit, so subscribers re-render before paint (no content flash) and
929
+ * without React's setState-in-render warning.
930
+ */
931
+ flushRenderPhaseUpdates = () => {
932
+ let pendingData = this.pendingProjectData;
933
+ if (pendingData) {
934
+ this.pendingProjectData = void 0;
935
+ this.setProjectData(pendingData);
936
+ }
937
+ let pendingItems = this.pendingItemUpdates;
938
+ if (pendingItems.length > 0) {
939
+ this.pendingItemUpdates = [];
940
+ for (let item of pendingItems) {
941
+ item.triggerUpdate();
942
+ }
943
+ }
438
944
  };
439
- updateTranslation = () => void 0;
440
- getTranslationChanges = () => void 0;
441
945
  };
442
946
  function createWeaverseNextRuntime(config) {
443
947
  let requestInfo = buildWeaverseNextRequestInfo(config.client?.requestContext);
@@ -446,19 +950,26 @@ function createWeaverseNextRuntime(config) {
446
950
  let existing = runtimeWindow?.__weaverses?.[page.id];
447
951
  let requestKey = getRuntimeKey(page.id, requestInfo);
448
952
  if (existing?.__weaverseNextRequestKey === requestKey) {
449
- let nextIsDesignMode = config.client?.requestContext?.isDesignMode ?? false;
450
- if (!(existing.isDesignMode || nextIsDesignMode)) {
451
- existing.setProjectData(page);
953
+ let configs = config.data.configs;
954
+ let nextIsDesignMode = resolveModeFlag(config, "isDesignMode");
955
+ let nextIsPreviewMode = resolveModeFlag(config, "isPreviewMode");
956
+ let nextIsRevisionPreview = resolveModeFlag(config, "isRevisionPreview");
957
+ let nextSectionType = resolveSectionType(config);
958
+ if (existing.isDesignMode || nextIsDesignMode) {
959
+ existing.pendingProjectData = void 0;
960
+ } else {
961
+ existing.pendingProjectData = page;
452
962
  }
963
+ existing.__weaverseNextLatestData = page;
453
964
  existing.dataContext = resolveDataContext(config);
454
965
  existing.requestInfo = requestInfo;
455
966
  existing.projectId = resolveProjectId(config.client, config.data, page.id);
456
967
  existing.isDesignMode = nextIsDesignMode;
457
- existing.isPreviewMode = config.client?.requestContext?.isPreviewMode ?? false;
458
- existing.isRevisionPreview = config.client?.requestContext?.isRevisionPreview ?? false;
459
- existing.sectionType = config.client?.requestContext?.sectionType;
460
- existing.weaverseHost = getConfigString(config.data.configs, "weaverseHost") ?? existing.weaverseHost;
461
- existing.weaverseVersion = getConfigString(config.data.configs, "weaverseVersion") ?? existing.weaverseVersion;
968
+ existing.isPreviewMode = nextIsPreviewMode;
969
+ existing.isRevisionPreview = nextIsRevisionPreview;
970
+ existing.sectionType = nextSectionType;
971
+ existing.weaverseHost = getConfigString(configs, "weaverseHost") ?? existing.weaverseHost;
972
+ existing.weaverseVersion = getConfigString(configs, "weaverseVersion") ?? existing.weaverseVersion;
462
973
  existing.internal.pageAssignment = config.data.pageAssignment;
463
974
  existing.internal.project = config.data.project;
464
975
  if (config.navigate) {
@@ -470,11 +981,21 @@ function createWeaverseNextRuntime(config) {
470
981
  if (config.themeSettingsStore) {
471
982
  existing.internal.themeSettingsStore = config.themeSettingsStore;
472
983
  }
984
+ if (config.translationStore) {
985
+ existing.internal.translationStore = config.translationStore;
986
+ existing.internal.themeTextStore = config.translationStore;
987
+ }
988
+ existing.internal.merchantOverrides = config.merchantOverrides;
473
989
  registerRuntime(existing);
474
990
  return existing;
475
991
  }
476
- let runtime = new WeaverseNextRuntime(config);
992
+ let [runtime, refreshedItems] = collectDeferredItemUpdates(
993
+ () => new WeaverseNextRuntime(config)
994
+ );
995
+ runtime.pendingItemUpdates = refreshedItems;
996
+ rebindPageItemsToRuntime(runtime);
477
997
  runtime.__weaverseNextRequestKey = requestKey;
998
+ runtime.__weaverseNextLatestData = page;
478
999
  registerRuntime(runtime);
479
1000
  return runtime;
480
1001
  }
@@ -487,22 +1008,110 @@ function bindWeaverseNextStudioRuntime(runtime) {
487
1008
  return false;
488
1009
  }
489
1010
  let boundRuntime = runtime;
490
- if (!boundRuntime.__weaverseNextStudioBound) {
1011
+ let activeRuntime = studio.weaverse;
1012
+ let activeRequestInfo = activeRuntime?.requestInfo;
1013
+ let isActiveUrl = !activeRequestInfo || activeRequestInfo.pathname === runtime.requestInfo.pathname && activeRequestInfo.search === runtime.requestInfo.search;
1014
+ if (!(boundRuntime.__weaverseNextStudioBound && isActiveUrl)) {
491
1015
  studio.init?.(runtime);
492
1016
  boundRuntime.__weaverseNextStudioBound = true;
493
1017
  return true;
494
1018
  }
495
1019
  studio.refreshStudio?.({
496
- data: runtime.data,
1020
+ data: boundRuntime.__weaverseNextLatestData ?? runtime.data,
497
1021
  pageId: runtime.pageId,
498
1022
  requestInfo: runtime.requestInfo
499
1023
  });
500
1024
  return true;
501
1025
  }
1026
+ var DEFAULT_REVALIDATE_ENDPOINT = "/api/weaverse/revalidate";
1027
+ var MAX_ROUTE_CONTEXT_HANDLE_LENGTH = 512;
1028
+ var DENIED_ROUTE_CONTEXT_PARAMS = [
1029
+ "weaverseprojectid",
1030
+ "weaversehost",
1031
+ "weaverseapikey",
1032
+ "weaverseapibase",
1033
+ "weaversepublicapibase",
1034
+ "weaverseversion",
1035
+ "projectid",
1036
+ "weaversedraftitem",
1037
+ "__weaversedraftitem",
1038
+ "_rsc"
1039
+ ];
1040
+ var ROUTE_CONTEXT_I18N_KEYS = [
1041
+ "country",
1042
+ "label",
1043
+ "language",
1044
+ "locale",
1045
+ "pathPrefix"
1046
+ ];
1047
+ function sanitizeRouteContextSearch(search) {
1048
+ let params = new URLSearchParams(search);
1049
+ let denied = new Set(DENIED_ROUTE_CONTEXT_PARAMS);
1050
+ for (let key of [...new Set(params.keys())]) {
1051
+ if (denied.has(key.toLowerCase())) {
1052
+ params.delete(key);
1053
+ }
1054
+ }
1055
+ let serialized = params.toString();
1056
+ return serialized ? `?${serialized}` : "";
1057
+ }
1058
+ function buildWeaverseNextRevalidateRouteContext(requestInfo) {
1059
+ if (!requestInfo || typeof requestInfo.pathname !== "string" || !requestInfo.pathname) {
1060
+ return;
1061
+ }
1062
+ let routeContext = {
1063
+ pathname: requestInfo.pathname,
1064
+ search: sanitizeRouteContextSearch(requestInfo.search ?? "")
1065
+ };
1066
+ let { i18n } = requestInfo;
1067
+ if (i18n && typeof i18n === "object") {
1068
+ let picked = {};
1069
+ for (let key of ROUTE_CONTEXT_I18N_KEYS) {
1070
+ let value = i18n[key];
1071
+ if (typeof value === "string") {
1072
+ picked[key] = value;
1073
+ }
1074
+ }
1075
+ if (Object.keys(picked).length) {
1076
+ routeContext.i18n = picked;
1077
+ }
1078
+ }
1079
+ if (requestInfo.pageType && schema.PageTypeSchema.safeParse(requestInfo.pageType).success) {
1080
+ routeContext.pageType = requestInfo.pageType;
1081
+ }
1082
+ if (typeof requestInfo.handle === "string" && requestInfo.handle && requestInfo.handle.length <= MAX_ROUTE_CONTEXT_HANDLE_LENGTH) {
1083
+ routeContext.handle = requestInfo.handle;
1084
+ }
1085
+ return routeContext;
1086
+ }
1087
+ async function revalidateWeaverseNextItem(runtime, draftItem, endpoint = DEFAULT_REVALIDATE_ENDPOINT) {
1088
+ let instance = runtime.itemInstances.get(draftItem.id);
1089
+ if (!instance) {
1090
+ throw new Error(
1091
+ `[weaverse-next] No live item instance for "${draftItem.id}".`
1092
+ );
1093
+ }
1094
+ let routeContext = buildWeaverseNextRevalidateRouteContext(
1095
+ runtime.requestInfo
1096
+ );
1097
+ let body = routeContext ? { draftItem, routeContext } : { draftItem };
1098
+ let response = await fetch(endpoint, {
1099
+ method: "POST",
1100
+ headers: { "Content-Type": "application/json" },
1101
+ body: JSON.stringify(body)
1102
+ });
1103
+ if (!response.ok) {
1104
+ throw new Error(
1105
+ `[weaverse-next] Revalidate endpoint responded ${response.status}.`
1106
+ );
1107
+ }
1108
+ let { loaderData } = await response.json();
1109
+ instance.setData({ loaderData: loaderData ?? null });
1110
+ }
502
1111
  var STUDIO_BIND_RETRY_INTERVAL_MS = 50;
503
1112
  var STUDIO_BIND_MAX_ATTEMPTS = 100;
504
1113
  function WeaverseNextStudioBridge(props) {
505
- let { runtime, navigate, revalidate } = props;
1114
+ let { runtime, navigate, revalidate, revalidateItem } = props;
506
1115
  let lastRuntimeRef = react$1.useRef(runtime);
507
1116
  let lastRuntimeDataRef = react$1.useRef(runtime.data);
508
1117
  let lastRequestInfoRef = react$1.useRef(runtime.requestInfo);
@@ -513,6 +1122,9 @@ function WeaverseNextStudioBridge(props) {
513
1122
  if (revalidate) {
514
1123
  runtime.internal.revalidate = revalidate;
515
1124
  }
1125
+ if (revalidateItem) {
1126
+ runtime.internal.revalidateItem = revalidateItem;
1127
+ }
516
1128
  if (bindWeaverseNextStudioRuntime(runtime) || !runtime.isDesignMode) {
517
1129
  return;
518
1130
  }
@@ -524,7 +1136,7 @@ function WeaverseNextStudioBridge(props) {
524
1136
  }
525
1137
  }, STUDIO_BIND_RETRY_INTERVAL_MS);
526
1138
  return () => window.clearInterval(interval);
527
- }, [runtime, navigate, revalidate]);
1139
+ }, [runtime, navigate, revalidate, revalidateItem]);
528
1140
  react$1.useEffect(() => {
529
1141
  if (lastRuntimeRef.current !== runtime) {
530
1142
  lastRuntimeRef.current = runtime;
@@ -577,18 +1189,29 @@ function useWeaverseNextStudioInternals(options) {
577
1189
  return ref.current.internals;
578
1190
  }
579
1191
  function WeaverseNextStudio(props) {
580
- let { runtime, replace } = props;
1192
+ let { runtime, replace, revalidateEndpoint } = props;
581
1193
  let { navigate, revalidate } = useWeaverseNextStudioInternals({ replace });
1194
+ let endpoint = revalidateEndpoint ?? DEFAULT_REVALIDATE_ENDPOINT;
1195
+ let revalidateItemRef = react$1.useRef(null);
1196
+ if (revalidateItemRef.current?.endpoint !== endpoint || revalidateItemRef.current.runtime !== runtime) {
1197
+ revalidateItemRef.current = {
1198
+ endpoint,
1199
+ revalidateItem: (draftItem) => revalidateWeaverseNextItem(runtime, draftItem, endpoint),
1200
+ runtime
1201
+ };
1202
+ }
582
1203
  return /* @__PURE__ */ jsxRuntime.jsx(
583
1204
  WeaverseNextStudioBridge,
584
1205
  {
585
1206
  navigate,
586
1207
  revalidate,
1208
+ revalidateItem: revalidateItemRef.current.revalidateItem,
587
1209
  runtime
588
1210
  }
589
1211
  );
590
1212
  }
591
1213
  var EMPTY_DATA_CONTEXT = {};
1214
+ var useIsomorphicLayoutEffect = typeof window === "undefined" ? react$1.useEffect : react$1.useLayoutEffect;
592
1215
  function getRenderablePage2(data) {
593
1216
  let page = data?.page;
594
1217
  if (!page) {
@@ -611,9 +1234,22 @@ var WeaverseNextRenderer = react$1.memo(function WeaverseNextRendererComponent(p
611
1234
  client,
612
1235
  data: { ...data, page },
613
1236
  dataContext,
614
- themeSettingsStore: context?.themeSettingsStore
1237
+ merchantOverrides: context?.merchantOverrides,
1238
+ themeSettingsStore: context?.themeSettingsStore,
1239
+ translationStore: context?.translationStore
615
1240
  });
616
- }, [client, page, data, dataContext, context?.themeSettingsStore]);
1241
+ }, [
1242
+ client,
1243
+ page,
1244
+ data,
1245
+ dataContext,
1246
+ context?.merchantOverrides,
1247
+ context?.themeSettingsStore,
1248
+ context?.translationStore
1249
+ ]);
1250
+ useIsomorphicLayoutEffect(() => {
1251
+ weaverse?.flushRenderPhaseUpdates();
1252
+ }, [weaverse]);
617
1253
  if (!weaverse) {
618
1254
  return null;
619
1255
  }
@@ -623,6 +1259,71 @@ var WeaverseNextRenderer = react$1.memo(function WeaverseNextRendererComponent(p
623
1259
  ] });
624
1260
  });
625
1261
 
1262
+ // src/seo.ts
1263
+ function hasOpenGraphContent(seo) {
1264
+ return Boolean(seo?.title || seo?.description || seo?.image || seo?.type);
1265
+ }
1266
+ function hasTwitterContent(seo) {
1267
+ return Boolean(seo?.title || seo?.description || seo?.image || seo?.cardType);
1268
+ }
1269
+ function normalizeTwitterCard(cardType) {
1270
+ return cardType === "summary_large_image" ? "summary_large_image" : "summary";
1271
+ }
1272
+ function normalizeOpenGraphType(type) {
1273
+ if (type === "article" || type === "profile" || type === "video.other") {
1274
+ return type;
1275
+ }
1276
+ return "website";
1277
+ }
1278
+ function formatWeaverseNextSeoMetadata(seo) {
1279
+ let index = seo?.robots?.index !== false;
1280
+ let follow = seo?.robots?.follow !== false;
1281
+ let metadata = {
1282
+ robots: { index, follow }
1283
+ };
1284
+ if (!seo) {
1285
+ return metadata;
1286
+ }
1287
+ if (seo.title) {
1288
+ metadata.title = seo.title;
1289
+ }
1290
+ if (seo.description) {
1291
+ metadata.description = seo.description;
1292
+ }
1293
+ if (seo.keywords) {
1294
+ metadata.keywords = seo.keywords;
1295
+ }
1296
+ if (seo.canonicalUrl) {
1297
+ metadata.alternates = { canonical: seo.canonicalUrl };
1298
+ }
1299
+ let openGraph = seo.openGraph;
1300
+ if (hasOpenGraphContent(openGraph)) {
1301
+ metadata.openGraph = {
1302
+ title: openGraph?.title,
1303
+ description: openGraph?.description,
1304
+ images: openGraph?.image ? [openGraph.image] : void 0,
1305
+ type: normalizeOpenGraphType(openGraph?.type)
1306
+ };
1307
+ }
1308
+ let twitter = seo.twitter;
1309
+ if (hasTwitterContent(twitter)) {
1310
+ metadata.twitter = {
1311
+ card: normalizeTwitterCard(twitter?.cardType),
1312
+ title: twitter?.title,
1313
+ description: twitter?.description,
1314
+ images: twitter?.image ? [twitter.image] : void 0
1315
+ };
1316
+ }
1317
+ return metadata;
1318
+ }
1319
+ function getWeaverseNextSeoMetadata(data) {
1320
+ if (!data) {
1321
+ return formatWeaverseNextSeoMetadata(null);
1322
+ }
1323
+ let page = "items" in data && "id" in data ? data : data.page ?? null;
1324
+ return formatWeaverseNextSeoMetadata(page?.seo ?? null);
1325
+ }
1326
+
626
1327
  // src/studio-script-src.ts
627
1328
  var TRUSTED_STUDIO_DOMAINS = ["weaverse.io", "weaverse.dev"];
628
1329
  function isLoopbackHostname(hostname) {
@@ -686,10 +1387,9 @@ function resolveWeaverseNextStudioScriptSrc(context, options = {}) {
686
1387
  if (!isTrustedStudioOrigin(origin, storefrontHostname)) {
687
1388
  return null;
688
1389
  }
689
- let framework = options.framework ?? "hydrogen";
690
- let bundle = isDesignMode ? "index.js" : "preview.js";
1390
+ let bundle = isPreviewMode || isRevisionPreview ? "preview.js" : "index.js";
691
1391
  let version = last(searchParams, "weaverseVersion");
692
- let url = `${origin}/static/studio/${framework}/${bundle}`;
1392
+ let url = `${origin}/static/studio/next/${bundle}`;
693
1393
  return version ? `${url}?v=${encodeURIComponent(version)}` : url;
694
1394
  }
695
1395
 
@@ -713,21 +1413,26 @@ function loadScript(src) {
713
1413
  document.head.appendChild(script);
714
1414
  loadedScriptSrcs.add(src);
715
1415
  }
1416
+ function loadWeaverseNextStudioScript(context, options = {}) {
1417
+ let resolvedContext = context;
1418
+ if (!resolvedContext && typeof window !== "undefined") {
1419
+ resolvedContext = {
1420
+ searchParams: new URLSearchParams(window.location.search),
1421
+ url: window.location.href
1422
+ };
1423
+ }
1424
+ let src = resolveWeaverseNextStudioScriptSrc(resolvedContext, {
1425
+ storefrontHostname: options.storefrontHostname ?? (typeof window === "undefined" ? void 0 : window.location.hostname)
1426
+ });
1427
+ if (src) {
1428
+ loadScript(src);
1429
+ }
1430
+ }
716
1431
  function WeaverseNextStudioConnect(props) {
717
1432
  react$1.useEffect(() => {
718
- let context = props.context;
719
- if (!context && typeof window !== "undefined") {
720
- context = {
721
- searchParams: new URLSearchParams(window.location.search),
722
- url: window.location.href
723
- };
724
- }
725
- let src = resolveWeaverseNextStudioScriptSrc(context, {
726
- storefrontHostname: props.storefrontHostname ?? (typeof window === "undefined" ? void 0 : window.location.hostname)
1433
+ loadWeaverseNextStudioScript(props.context, {
1434
+ storefrontHostname: props.storefrontHostname
727
1435
  });
728
- if (src) {
729
- loadScript(src);
730
- }
731
1436
  }, [props.context, props.storefrontHostname]);
732
1437
  return null;
733
1438
  }
@@ -756,28 +1461,47 @@ Object.defineProperty(exports, "useWeaverse", {
756
1461
  enumerable: true,
757
1462
  get: function () { return react.useWeaverse; }
758
1463
  });
759
- Object.defineProperty(exports, "createSchema", {
760
- enumerable: true,
761
- get: function () { return schema.createSchema; }
762
- });
1464
+ exports.DEFAULT_REVALIDATE_ENDPOINT = DEFAULT_REVALIDATE_ENDPOINT;
1465
+ exports.ThemeTextProvider = ThemeTextProvider;
1466
+ exports.ThemeTextStore = ThemeTextStore;
1467
+ exports.TranslationProvider = TranslationProvider;
1468
+ exports.TranslationStore = TranslationStore;
763
1469
  exports.WeaverseNextProvider = WeaverseNextProvider;
764
1470
  exports.WeaverseNextRenderer = WeaverseNextRenderer;
1471
+ exports.WeaverseNextRootProvider = WeaverseNextRootProvider;
765
1472
  exports.WeaverseNextRuntime = WeaverseNextRuntime;
766
1473
  exports.WeaverseNextStudio = WeaverseNextStudio;
767
1474
  exports.WeaverseNextStudioBridge = WeaverseNextStudioBridge;
768
1475
  exports.WeaverseNextStudioConnect = WeaverseNextStudioConnect;
769
1476
  exports.bindWeaverseNextStudioRuntime = bindWeaverseNextStudioRuntime;
770
1477
  exports.buildWeaverseNextRequestInfo = buildWeaverseNextRequestInfo;
1478
+ exports.buildWeaverseNextRevalidateRouteContext = buildWeaverseNextRevalidateRouteContext;
1479
+ exports.createTranslate = createTranslate;
771
1480
  exports.createWeaverseNextClient = createWeaverseNextClient;
772
1481
  exports.createWeaverseNextRuntime = createWeaverseNextRuntime;
773
1482
  exports.createWeaverseNextStudioInternals = createWeaverseNextStudioInternals;
774
1483
  exports.createWeaverseNextThemeSettingsStore = createWeaverseNextThemeSettingsStore;
1484
+ exports.formatWeaverseNextSeoMetadata = formatWeaverseNextSeoMetadata;
1485
+ exports.generateDataFromSchema = generateDataFromSchema;
1486
+ exports.getNestedKey = getNestedKey;
1487
+ exports.getWeaverseNextSeoMetadata = getWeaverseNextSeoMetadata;
1488
+ exports.interpolate = interpolate;
1489
+ exports.loadWeaverseNextStudioScript = loadWeaverseNextStudioScript;
775
1490
  exports.resolveWeaverseNextStudioScriptSrc = resolveWeaverseNextStudioScriptSrc;
1491
+ exports.revalidateWeaverseNextItem = revalidateWeaverseNextItem;
776
1492
  exports.runWeaverseComponentLoaders = runWeaverseComponentLoaders;
777
1493
  exports.useThemeSettings = useThemeSettings;
1494
+ exports.useThemeText = useThemeText;
1495
+ exports.useTranslation = useTranslation;
778
1496
  exports.useWeaverseCommerce = useWeaverseCommerce;
779
1497
  exports.useWeaverseNextStudioInternals = useWeaverseNextStudioInternals;
780
1498
  exports.useWeaversePageData = useWeaversePageData;
781
1499
  exports.useWeaverseRootData = useWeaverseRootData;
1500
+ Object.keys(schema).forEach(function (k) {
1501
+ if (k !== 'default' && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
1502
+ enumerable: true,
1503
+ get: function () { return schema[k]; }
1504
+ });
1505
+ });
782
1506
  //# sourceMappingURL=index.js.map
783
1507
  //# sourceMappingURL=index.js.map