@ohhwells/bridge 0.1.29-next.20 → 0.1.29-next.21

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/README.md CHANGED
@@ -184,6 +184,64 @@ Add `data-ohw-editable` and `data-ohw-key` to any element the studio owner shoul
184
184
 
185
185
  ---
186
186
 
187
+ ## SchedulingWidget (vibe-coder placement)
188
+
189
+ `SchedulingWidget` lets a template builder embed a scheduling/booking section directly in their template JSX, without going through the "Add Section" flow. The canvas editor treats it exactly like a dynamically-inserted scheduling widget — the studio owner can connect a schedule, switch it, and clear it.
190
+
191
+ ### Basic usage
192
+
193
+ ```tsx
194
+ import { SchedulingWidget } from '@ohhwells/bridge'
195
+
196
+ export default function ClassesPage() {
197
+ return (
198
+ <>
199
+ <PageHeader ... />
200
+ <ClassLibrary ... />
201
+ <SchedulingWidget />
202
+ <WordmarkBand ... />
203
+ </>
204
+ )
205
+ }
206
+ ```
207
+
208
+ No props are required. The widget auto-generates a stable identity via React's `useId()` so the bridge can track which schedule is connected to it across saves.
209
+
210
+ ### Props
211
+
212
+ | Prop | Type | Default | Description |
213
+ |---|---|---|---|
214
+ | `insertAfter` | `string` | auto | Stable identifier used as the tracker key. Only set this manually if you need two `SchedulingWidget`s on the same page. |
215
+ | `initialScheduleId` | `string \| null` | `undefined` | Set by the bridge internally after hydration. Do not pass this yourself. |
216
+ | `notifyOnConnect` | `boolean` | `false` | Set by the bridge internally. Do not pass this yourself. |
217
+
218
+ ### How it works
219
+
220
+ 1. The widget renders immediately with a loading skeleton.
221
+ 2. It sends `ow:request-schedule-config` to the bridge (running in the same window).
222
+ 3. The bridge looks up the tracker for a saved `scheduleId` for this widget:
223
+ - **Found** → responds with `ow:schedule-config { scheduleId }` → widget loads that schedule.
224
+ - **Not found (first time)** → bridge adopts the widget (adds it to the tracker, notifies the canvas editor), responds with `scheduleId: null` → widget shows empty state with an "Add Schedule" button.
225
+ 4. In the canvas editor, the studio owner clicks "Add Schedule" → selects a schedule → the widget updates and the connection is saved.
226
+ 5. On the live site, the widget fetches the saved schedule by ID and renders it.
227
+
228
+ ### Multiple widgets on one page
229
+
230
+ Each `SchedulingWidget` must have a unique `insertAfter` to be tracked independently:
231
+
232
+ ```tsx
233
+ <SchedulingWidget insertAfter="classes-morning" />
234
+ <SchedulingWidget insertAfter="classes-evening" />
235
+ ```
236
+
237
+ If you omit `insertAfter` on both, `useId()` auto-generates different stable IDs for each, so they are tracked separately anyway.
238
+
239
+ ### Empty state on live site
240
+
241
+ If the studio owner has never connected a schedule to this widget, it renders nothing on the live site (no empty placeholder is shown to visitors).
242
+
243
+ ---
244
+
187
245
  ## Editable states (advanced)
188
246
 
189
247
  For elements with multiple display states (e.g. a contact form with default/success/error views), wrap each state in a `data-ohw-state-view` and mark the container with `data-ohw-editable-state`:
package/dist/index.cjs CHANGED
@@ -212,13 +212,6 @@ function EmailCaptureModal({ title, subtitle, onSubmit, onClose }) {
212
212
  var import_jsx_runtime2 = require("react/jsx-runtime");
213
213
  var API_URL = process.env.NEXT_PUBLIC_FLOWOPS_API_URL ?? "https://flowops-backend-staging-2yfdh7pwpq-as.a.run.app";
214
214
  var DAY_ABBR = ["SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT"];
215
- function getApiDomain() {
216
- const h = window.location.hostname;
217
- for (const base of ["ohhwells.site", "theflowops-staging.com", "theflowops.com"]) {
218
- if (h.endsWith(`.${base}`)) return h.slice(0, -(base.length + 1));
219
- }
220
- return h;
221
- }
222
215
  function classRunsOnDate(cls, date, type) {
223
216
  if (type === "FIXED") {
224
217
  const d = String(date.getDate()).padStart(2, "0");
@@ -264,10 +257,10 @@ function buildTimezoneLabel(tz) {
264
257
  return tz;
265
258
  }
266
259
  }
267
- function EmptyState({ inEditor }) {
260
+ function EmptyState({ inEditor, insertAfter }) {
268
261
  const handleAddSchedule = () => {
269
262
  if (inEditor) {
270
- window.parent.postMessage({ type: "ow:scheduling-not-connected" }, "*");
263
+ window.parent.postMessage({ type: "ow:scheduling-not-connected", insertAfter }, "*");
271
264
  }
272
265
  };
273
266
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex flex-col items-center justify-center gap-6 p-12 bg-[#FAFAF9] border-2 border-dashed border-[#E7E5E4] rounded-[10px] h-[264px] w-full box-border", children: [
@@ -492,18 +485,21 @@ function ScheduleView({ schedule, dates, selectedIdx, onSelectDate, onOpenModal
492
485
  const booked = getBookingsOnDate(cls, selectedDate);
493
486
  const available = cls.maxParticipants - booked;
494
487
  const isFull = available <= 0;
488
+ const isPrivate = cls.maxParticipants === 1;
495
489
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { children: [
496
490
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex gap-4 py-4 sm:items-center sm:gap-[60px] box-border", children: [
497
491
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex flex-col gap-2 shrink-0 sm:contents", children: [
498
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "sm:w-[120px] sm:shrink-0", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "font-body text-base font-bold text-(--color-dark,#200C02) block", children: formatClassTime(cls) }) }),
499
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
500
- "span",
501
- {
502
- className: "inline-flex items-center px-2 py-0.5 rounded-full border text-xs font-medium sm:hidden",
503
- style: { borderColor: "#0885FE", color: "#0885FE" },
504
- children: "GROUP"
505
- }
506
- ),
492
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex flex-col gap-2 sm:w-[120px] sm:shrink-0", children: [
493
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "font-body text-base font-bold text-(--color-dark,#200C02) block", children: formatClassTime(cls) }),
494
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
495
+ "span",
496
+ {
497
+ className: "inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium w-fit",
498
+ style: isPrivate ? { background: "#DDFBE3", color: "#199130", border: "1px solid #199130", fontWeight: 500 } : { background: "#EEEDFF", color: "#5953FF", border: "1px solid #5953FF", fontWeight: 500 },
499
+ children: isPrivate ? "PRIVATE" : "GROUP"
500
+ }
501
+ )
502
+ ] }),
507
503
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "sm:hidden flex flex-col gap-px", children: isFull ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: "Full" }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
508
504
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: [
509
505
  available,
@@ -576,7 +572,9 @@ function CalendarFoldIcon() {
576
572
  }
577
573
  );
578
574
  }
579
- function SchedulingWidget({ notifyOnConnect = false, initialScheduleId, insertAfter }) {
575
+ function SchedulingWidget({ notifyOnConnect = false, initialScheduleId, insertAfter: insertAfterProp }) {
576
+ const autoId = (0, import_react2.useId)();
577
+ const insertAfter = insertAfterProp ?? autoId;
580
578
  const [schedule, setSchedule] = (0, import_react2.useState)(null);
581
579
  const [loading, setLoading] = (0, import_react2.useState)(true);
582
580
  const [inEditor, setInEditor] = (0, import_react2.useState)(false);
@@ -606,88 +604,99 @@ function SchedulingWidget({ notifyOnConnect = false, initialScheduleId, insertAf
606
604
  if (typeof window === "undefined") return;
607
605
  const isInEditor = window.self !== window.top;
608
606
  setInEditor(isInEditor);
609
- if (isInEditor) {
610
- const startEmpty = initialScheduleId === null;
611
- if (startEmpty) setLoading(false);
612
- let initialized = startEmpty;
613
- const timer = !startEmpty ? setTimeout(() => {
614
- if (!initialized) {
607
+ function loadWithId(effectiveId) {
608
+ if (isInEditor) {
609
+ const startEmpty = effectiveId === null;
610
+ if (startEmpty) setLoading(false);
611
+ let initialized = startEmpty;
612
+ const timer = !startEmpty ? setTimeout(() => {
613
+ if (!initialized) {
614
+ initialized = true;
615
+ setLoading(false);
616
+ }
617
+ }, 5e3) : null;
618
+ const handler = (e) => {
619
+ if (e.data?.type === "ow:clear-scheduling-widget") {
620
+ if (!e.data.insertAfter || e.data.insertAfter !== insertAfter) return;
621
+ setSchedule(null);
622
+ setLoading(false);
623
+ return;
624
+ }
625
+ if (e.data?.type === "ow:switch-schedule") {
626
+ if (!e.data.insertAfter || e.data.insertAfter !== insertAfter) return;
627
+ switchScheduleIdRef.current = e.data.scheduleId ?? null;
628
+ initialized = false;
629
+ setLoading(true);
630
+ window.parent.postMessage({ type: "ow:request-user-schedules", insertAfter }, "*");
631
+ return;
632
+ }
633
+ if (e.data?.type !== "ow:user-schedules-response") return;
634
+ if (e.data.insertAfter && e.data.insertAfter !== insertAfter) return;
635
+ if (initialized && switchScheduleIdRef.current === null) return;
615
636
  initialized = true;
637
+ if (timer) clearTimeout(timer);
638
+ const schedules = e.data.schedules ?? [];
639
+ const isSwitching = switchScheduleIdRef.current !== null;
640
+ const target = isSwitching ? schedules.find((s) => s.id === switchScheduleIdRef.current) ?? schedules[0] ?? null : effectiveId ? schedules.find((s) => s.id === effectiveId) ?? schedules[0] ?? null : schedules[0] ?? null;
641
+ switchScheduleIdRef.current = null;
642
+ setSchedule(target);
616
643
  setLoading(false);
644
+ if (notifyOnConnect && target && !isSwitching) {
645
+ window.parent.postMessage({
646
+ type: "ow:schedule-connected",
647
+ schedule: { id: target.id, name: target.name },
648
+ insertAfter
649
+ }, "*");
650
+ window.postMessage({ type: "ow:schedule-linked", scheduleId: target.id, insertAfter }, "*");
651
+ }
652
+ };
653
+ window.addEventListener("message", handler);
654
+ if (!startEmpty) window.parent.postMessage({ type: "ow:request-user-schedules", insertAfter }, "*");
655
+ return () => {
656
+ window.removeEventListener("message", handler);
657
+ if (timer) clearTimeout(timer);
658
+ };
659
+ }
660
+ if (!effectiveId) {
661
+ setLoading(false);
662
+ return;
663
+ }
664
+ ;
665
+ (async () => {
666
+ try {
667
+ const res = await fetch(`${API_URL}/api/schedule/id/${effectiveId}`);
668
+ const data = await res.json();
669
+ setSchedule(data?.id ? data : null);
670
+ } catch {
617
671
  }
618
- }, 5e3) : null;
619
- const handler = (e) => {
620
- if (e.data?.type === "ow:clear-scheduling-widget") {
621
- if (!e.data.insertAfter || e.data.insertAfter !== insertAfter) return;
622
- setSchedule(null);
623
- setLoading(false);
624
- return;
625
- }
626
- if (e.data?.type === "ow:switch-schedule") {
627
- if (!e.data.insertAfter || e.data.insertAfter !== insertAfter) return;
628
- switchScheduleIdRef.current = e.data.scheduleId ?? null;
629
- initialized = false;
630
- setLoading(true);
631
- window.parent.postMessage({ type: "ow:request-user-schedules", insertAfter }, "*");
632
- return;
633
- }
634
- if (e.data?.type !== "ow:user-schedules-response") return;
635
- if (e.data.insertAfter && e.data.insertAfter !== insertAfter) return;
636
- if (initialized && switchScheduleIdRef.current === null) return;
637
- initialized = true;
638
- if (timer) clearTimeout(timer);
639
- const schedules = e.data.schedules ?? [];
640
- const isSwitching = switchScheduleIdRef.current !== null;
641
- const target = isSwitching ? schedules.find((s) => s.id === switchScheduleIdRef.current) ?? schedules[0] ?? null : initialScheduleId ? schedules.find((s) => s.id === initialScheduleId) ?? schedules[0] ?? null : schedules[0] ?? null;
642
- switchScheduleIdRef.current = null;
643
- setSchedule(target);
644
672
  setLoading(false);
645
- if (notifyOnConnect && target && !isSwitching) {
646
- window.parent.postMessage({
647
- type: "ow:schedule-connected",
648
- schedule: { id: target.id, name: target.name },
649
- insertAfter
650
- }, "*");
651
- window.postMessage({ type: "ow:schedule-linked", scheduleId: target.id, insertAfter }, "*");
673
+ })();
674
+ }
675
+ if (initialScheduleId === void 0) {
676
+ let done = false;
677
+ let innerCleanup;
678
+ const timer = setTimeout(() => {
679
+ if (!done) {
680
+ done = true;
681
+ setLoading(false);
652
682
  }
683
+ }, 2e3);
684
+ const configHandler = (e) => {
685
+ if (e.data?.type !== "ow:schedule-config" || e.data.insertAfter !== insertAfter || done) return;
686
+ done = true;
687
+ clearTimeout(timer);
688
+ window.removeEventListener("message", configHandler);
689
+ innerCleanup = loadWithId(e.data.scheduleId ?? null);
653
690
  };
654
- window.addEventListener("message", handler);
655
- if (!startEmpty) window.parent.postMessage({ type: "ow:request-user-schedules", insertAfter }, "*");
691
+ window.addEventListener("message", configHandler);
692
+ window.postMessage({ type: "ow:request-schedule-config", insertAfter }, "*");
656
693
  return () => {
657
- window.removeEventListener("message", handler);
658
- if (timer) clearTimeout(timer);
694
+ window.removeEventListener("message", configHandler);
695
+ clearTimeout(timer);
696
+ innerCleanup?.();
659
697
  };
660
698
  }
661
- if (initialScheduleId === null) {
662
- setLoading(false);
663
- return;
664
- }
665
- ;
666
- (async () => {
667
- try {
668
- if (initialScheduleId) {
669
- const res = await fetch(`${API_URL}/api/schedule/id/${initialScheduleId}`);
670
- const data = await res.json();
671
- setSchedule(data?.id ? data : null);
672
- } else {
673
- const domain = getApiDomain();
674
- const sitemapRes = await fetch(`${API_URL}/api/schedule/sitemap/${domain}`);
675
- const slugs = await sitemapRes.json();
676
- if (!Array.isArray(slugs) || !slugs.length) {
677
- setLoading(false);
678
- return;
679
- }
680
- const { slug } = [...slugs].sort(
681
- (a, b) => new Date(b.updated_at).getTime() - new Date(a.updated_at).getTime()
682
- )[0];
683
- const res = await fetch(`${API_URL}/api/schedule/${domain}/${slug}`);
684
- const data = await res.json();
685
- setSchedule(data?.id ? data : null);
686
- }
687
- } catch {
688
- }
689
- setLoading(false);
690
- })();
699
+ return loadWithId(initialScheduleId) ?? void 0;
691
700
  }, []);
692
701
  const timezoneLabel = schedule?.timezone ? (() => {
693
702
  try {
@@ -722,6 +731,7 @@ function SchedulingWidget({ notifyOnConnect = false, initialScheduleId, insertAf
722
731
  window.parent.postMessage({ type: "ow:scheduling-not-connected", insertAfter }, "*");
723
732
  }
724
733
  };
734
+ if (!inEditor && !loading && !schedule) return null;
725
735
  const sectionId = `scheduling-${insertAfter}`;
726
736
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
727
737
  "section",
@@ -767,7 +777,7 @@ function SchedulingWidget({ notifyOnConnect = false, initialScheduleId, insertAf
767
777
  ] }),
768
778
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "w-full flex flex-col items-end gap-3", children: [
769
779
  timezoneLabel && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "font-body text-sm font-normal text-(--color-accent,#A89B83)", children: timezoneLabel }),
770
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "w-full p-0 sm:p-10 box-border", children: loading ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(LoadingSkeleton, {}) : !schedule ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(EmptyState, { inEditor }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
780
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "w-full p-0 sm:p-10 box-border", children: loading ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(LoadingSkeleton, {}) : !schedule ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(EmptyState, { inEditor, insertAfter }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
771
781
  ScheduleView,
772
782
  {
773
783
  schedule,
@@ -5325,9 +5335,14 @@ function initSectionsFromContent(content, removeExisting = false) {
5325
5335
  const inEditor = typeof window !== "undefined" && window.self !== window.top;
5326
5336
  if (inEditor) getSectionsTracker().textContent = raw;
5327
5337
  const pageEntries = getPageSchedulingEntries(raw);
5328
- mountSchedulingEntries(pageEntries, false);
5329
- requestAnimationFrame(() => retryMissingSchedulingMounts(pageEntries, false));
5330
- setTimeout(() => retryMissingSchedulingMounts(pageEntries, false), 250);
5338
+ const preExisting = pageEntries.filter((e) => !isSchedulingWidgetMissing(e));
5339
+ const notifyForEntry = inEditor ? (e) => !e.scheduleId : false;
5340
+ mountSchedulingEntries(pageEntries, notifyForEntry);
5341
+ requestAnimationFrame(() => retryMissingSchedulingMounts(pageEntries, notifyForEntry));
5342
+ setTimeout(() => retryMissingSchedulingMounts(pageEntries, notifyForEntry), 250);
5343
+ for (const entry of preExisting) {
5344
+ window.postMessage({ type: "ow:schedule-config", insertAfter: entry.insertAfter, scheduleId: entry.scheduleId ?? null }, "*");
5345
+ }
5331
5346
  } catch {
5332
5347
  }
5333
5348
  }
@@ -5386,8 +5401,9 @@ function mountSchedulingEntries(entries, notifyOnConnect = false) {
5386
5401
  const pending = entries.filter((e) => e.type === "scheduling").sort((a, b) => schedulingMountDepth(a.insertAfter) - schedulingMountDepth(b.insertAfter));
5387
5402
  for (let attempt = 0; attempt < pending.length + 1 && pending.length > 0; attempt++) {
5388
5403
  for (let i = pending.length - 1; i >= 0; i--) {
5389
- const { insertAfter, scheduleId } = pending[i];
5390
- if (mountSchedulingWidget(insertAfter, notifyOnConnect, scheduleId)) {
5404
+ const entry = pending[i];
5405
+ const shouldNotify = typeof notifyOnConnect === "function" ? notifyOnConnect(entry) : notifyOnConnect;
5406
+ if (mountSchedulingWidget(entry.insertAfter, shouldNotify, entry.scheduleId)) {
5391
5407
  pending.splice(i, 1);
5392
5408
  }
5393
5409
  }
@@ -5865,6 +5881,8 @@ function OhhwellsBridge() {
5865
5881
  });
5866
5882
  const postToParentRef = (0, import_react5.useRef)(postToParent);
5867
5883
  postToParentRef.current = postToParent;
5884
+ const sectionsLoadedRef = (0, import_react5.useRef)(false);
5885
+ const pendingScheduleConfigRequests = (0, import_react5.useRef)([]);
5868
5886
  const [toolbarRect, setToolbarRect] = (0, import_react5.useState)(null);
5869
5887
  const [toggleState, setToggleState] = (0, import_react5.useState)(null);
5870
5888
  const [maxBadge, setMaxBadge] = (0, import_react5.useState)(null);
@@ -5970,6 +5988,29 @@ function OhhwellsBridge() {
5970
5988
  const refreshStateRules = (0, import_react5.useCallback)(() => {
5971
5989
  editStylesRef.current?.forceHover && (editStylesRef.current.forceHover.textContent = collectStateRules());
5972
5990
  }, []);
5991
+ const processConfigRequest = (0, import_react5.useCallback)((insertAfterVal) => {
5992
+ const tracker = getSectionsTracker();
5993
+ let entries = [];
5994
+ try {
5995
+ entries = JSON.parse(tracker.textContent || "[]");
5996
+ } catch {
5997
+ }
5998
+ const path = window.location.pathname;
5999
+ const entry = entries.find(
6000
+ (e) => e.type === "scheduling" && e.insertAfter === insertAfterVal && (!e.pagePath || e.pagePath === path)
6001
+ );
6002
+ if (entry) {
6003
+ window.postMessage({ type: "ow:schedule-config", insertAfter: insertAfterVal, scheduleId: entry.scheduleId ?? null }, "*");
6004
+ return;
6005
+ }
6006
+ const newEntry = { type: "scheduling", insertAfter: insertAfterVal, pagePath: path };
6007
+ entries.push(newEntry);
6008
+ tracker.textContent = JSON.stringify(entries);
6009
+ if (isEditMode) {
6010
+ postToParentRef.current({ type: "ow:change", nodes: [{ key: "__ohw_sections", text: tracker.textContent }] });
6011
+ }
6012
+ window.postMessage({ type: "ow:schedule-config", insertAfter: insertAfterVal, scheduleId: null }, "*");
6013
+ }, [isEditMode]);
5973
6014
  const deactivate = (0, import_react5.useCallback)(() => {
5974
6015
  const el = activeElRef.current;
5975
6016
  if (!el) return;
@@ -6046,6 +6087,8 @@ function OhhwellsBridge() {
6046
6087
  applyLinkByKey(key, val);
6047
6088
  }
6048
6089
  initSectionsFromContent(content, true);
6090
+ sectionsLoadedRef.current = true;
6091
+ pendingScheduleConfigRequests.current.splice(0).forEach(processConfigRequest);
6049
6092
  return imageLoads.length > 0 ? Promise.all(imageLoads).then(() => {
6050
6093
  }) : Promise.resolve();
6051
6094
  };
@@ -6742,6 +6785,8 @@ function OhhwellsBridge() {
6742
6785
  }
6743
6786
  if (sectionsJson) {
6744
6787
  initSectionsFromContent({ __ohw_sections: sectionsJson }, true);
6788
+ sectionsLoadedRef.current = true;
6789
+ pendingScheduleConfigRequests.current.splice(0).forEach(processConfigRequest);
6745
6790
  }
6746
6791
  postToParentRef.current({ type: "ow:hydrate-done" });
6747
6792
  };
@@ -7014,6 +7059,22 @@ function OhhwellsBridge() {
7014
7059
  if (imageShowTimerRef.current) clearTimeout(imageShowTimerRef.current);
7015
7060
  };
7016
7061
  }, [isEditMode, refreshStateRules]);
7062
+ (0, import_react5.useEffect)(() => {
7063
+ const handler = (e) => {
7064
+ if (e.data?.type !== "ow:request-schedule-config") return;
7065
+ const insertAfterVal = e.data.insertAfter;
7066
+ if (!insertAfterVal) return;
7067
+ if (!sectionsLoadedRef.current) {
7068
+ if (!pendingScheduleConfigRequests.current.includes(insertAfterVal)) {
7069
+ pendingScheduleConfigRequests.current.push(insertAfterVal);
7070
+ }
7071
+ return;
7072
+ }
7073
+ processConfigRequest(insertAfterVal);
7074
+ };
7075
+ window.addEventListener("message", handler);
7076
+ return () => window.removeEventListener("message", handler);
7077
+ }, [processConfigRequest]);
7017
7078
  (0, import_react5.useEffect)(() => {
7018
7079
  if (!isEditMode) return;
7019
7080
  document.querySelectorAll("[data-ohw-active-state]").forEach((el) => {