@ohhwells/bridge 0.1.94 → 0.1.96

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.cjs CHANGED
@@ -143,6 +143,9 @@ function isRenderableTree(value) {
143
143
  // src/lib/ai-sections-store.ts
144
144
  var AI_SECTIONS_KEY = "__ohw_ai_sections";
145
145
  var AI_SLOT_KEY_PREFIX = "ai.";
146
+ function aiSlotKeyPrefixFor(sectionId) {
147
+ return `${AI_SLOT_KEY_PREFIX}${sectionId}.`;
148
+ }
146
149
  var EMPTY_AI_SECTIONS = { v: 1, sections: [] };
147
150
  function parseAiSectionsState(raw) {
148
151
  if (!raw) return EMPTY_AI_SECTIONS;
@@ -254,6 +257,33 @@ function deleteSectionFromState(state, sectionId) {
254
257
  if (removed.includes(sectionId)) return state;
255
258
  return { ...state, v: 1, sections: state.sections, removed: [...removed, sectionId] };
256
259
  }
260
+ function reapRemovedAiSections(state, store, removedIds, excludeIds = /* @__PURE__ */ new Set()) {
261
+ const generated = new Set(state.sections.map((entry) => entry.id));
262
+ const reapedIds = [...new Set(removedIds)].filter((id) => generated.has(id) && !excludeIds.has(id));
263
+ if (reapedIds.length === 0) {
264
+ return { state, store, reapedIds: [], slotPrefixes: [], changed: false };
265
+ }
266
+ const reaped = new Set(reapedIds);
267
+ const slotPrefixes = reapedIds.map(aiSlotKeyPrefixFor);
268
+ const nextState = {
269
+ ...state,
270
+ v: 1,
271
+ sections: state.sections.filter((entry) => !reaped.has(entry.id))
272
+ };
273
+ let nextStore = store;
274
+ if (store) {
275
+ const sections = {};
276
+ for (const [key, override] of Object.entries(store.sections)) {
277
+ if (!reaped.has(key)) sections[key] = override;
278
+ }
279
+ const nodes = {};
280
+ for (const [key, override] of Object.entries(store.nodes)) {
281
+ if (!slotPrefixes.some((prefix) => key.startsWith(prefix))) nodes[key] = override;
282
+ }
283
+ nextStore = { v: 1, sections, nodes };
284
+ }
285
+ return { state: nextState, store: nextStore, reapedIds, slotPrefixes, changed: true };
286
+ }
257
287
 
258
288
  // src/lib/brand-chrome.ts
259
289
  var BRAND_NAME_KEY = "__ohw_brand_name";
@@ -632,6 +662,254 @@ function applyStylesToDom(store) {
632
662
  var import_react_dom = require("react-dom");
633
663
  var import_client = require("react-dom/client");
634
664
 
665
+ // src/lib/sections.ts
666
+ var LINK_PICKER_EXCLUDED_IDS = /* @__PURE__ */ new Set(["navbar", "footer"]);
667
+ function isChromeSection(el) {
668
+ return el.matches("header, nav, footer, aside");
669
+ }
670
+ function titleCaseSectionId(id) {
671
+ return id.split("-").filter(Boolean).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
672
+ }
673
+ function parseSectionsFromRoot(root) {
674
+ const seen = /* @__PURE__ */ new Set();
675
+ const sections = [];
676
+ for (const el of root.querySelectorAll("[data-ohw-section]")) {
677
+ const id = el.getAttribute("data-ohw-section") ?? "";
678
+ if (!id || seen.has(id) || LINK_PICKER_EXCLUDED_IDS.has(id)) continue;
679
+ if (el.parentElement?.closest("[data-ohw-section]")) continue;
680
+ if (el.hasAttribute("data-ohw-ai-template-hidden") || el.hasAttribute("data-ohw-ai-removed") || el.hasAttribute("data-ohw-ai-replaced-by"))
681
+ continue;
682
+ seen.add(id);
683
+ const label = el.getAttribute("data-ohw-section-label") ?? titleCaseSectionId(id);
684
+ sections.push({ id, label });
685
+ }
686
+ return sections;
687
+ }
688
+ function collectSectionsFromDom() {
689
+ if (typeof document === "undefined") return [];
690
+ return parseSectionsFromRoot(document);
691
+ }
692
+ function parseSectionsFromHtml(html) {
693
+ const doc = new DOMParser().parseFromString(html, "text/html");
694
+ return parseSectionsFromRoot(doc);
695
+ }
696
+
697
+ // src/lib/section-instances.ts
698
+ var SECTION_ORDER_KEY = "__ohw_section_order";
699
+ var REMOVED_ATTR = "data-ohw-section-removed";
700
+ function isRemovedSection(el) {
701
+ return el.hasAttribute(REMOVED_ATTR);
702
+ }
703
+ function movableUnit(el) {
704
+ return el.closest("[data-ohw-section-container]") ?? el;
705
+ }
706
+ function sectionTypeOf(el) {
707
+ return el.getAttribute("data-ohw-section") ?? el.querySelector("[data-ohw-section]")?.getAttribute("data-ohw-section") ?? "";
708
+ }
709
+ function sectionElementOf(el) {
710
+ return el.hasAttribute("data-ohw-section") ? el : el.querySelector("[data-ohw-section]") ?? el;
711
+ }
712
+ function collectTopLevelUnits(predicate) {
713
+ const seen = /* @__PURE__ */ new Set();
714
+ const result = [];
715
+ document.querySelectorAll("[data-ohw-section]").forEach((el) => {
716
+ if (!predicate(el)) return;
717
+ const unit = movableUnit(el);
718
+ if (unit.parentElement?.closest("[data-ohw-section],[data-ohw-section-container]")) return;
719
+ if (seen.has(unit)) return;
720
+ seen.add(unit);
721
+ result.push(unit);
722
+ });
723
+ return result;
724
+ }
725
+ function topLevelSections() {
726
+ return collectTopLevelUnits((el) => !isChromeSection(el) && !isRemovedSection(movableUnit(el)));
727
+ }
728
+ function instanceIdOf(el) {
729
+ return el.dataset.ohwInstance ?? el.dataset.ohwSection ?? "";
730
+ }
731
+ function findByInstanceId(instanceId) {
732
+ const escapedId = CSS.escape(instanceId);
733
+ const direct = document.querySelector(`[data-ohw-instance="${escapedId}"]`);
734
+ if (direct) return movableUnit(direct);
735
+ const bare = document.querySelector(`[data-ohw-section="${escapedId}"]:not([data-ohw-instance])`);
736
+ return bare ? movableUnit(bare) : null;
737
+ }
738
+ function planSectionMove(instanceId, targetIndex, currentPath) {
739
+ const sections = topLevelSections();
740
+ const index = sections.findIndex((el) => instanceIdOf(el) === instanceId);
741
+ if (index === -1) return null;
742
+ const dragged = sections[index];
743
+ const others = sections.filter((_, i) => i !== index);
744
+ const clamped = Math.max(0, Math.min(targetIndex, others.length));
745
+ const reordered = [...others.slice(0, clamped), dragged, ...others.slice(clamped)];
746
+ return reordered.map((el, order) => ({
747
+ instanceId: instanceIdOf(el),
748
+ type: sectionTypeOf(el),
749
+ order,
750
+ pagePath: currentPath
751
+ }));
752
+ }
753
+ function moveSectionInstance(instanceId, direction, currentPath) {
754
+ const sections = topLevelSections();
755
+ const index = sections.findIndex((el) => instanceIdOf(el) === instanceId);
756
+ if (index === -1) return null;
757
+ const siblingIndex = direction === "up" ? index - 1 : index + 1;
758
+ if (siblingIndex < 0 || siblingIndex >= sections.length) return null;
759
+ const entries = planSectionMove(instanceId, siblingIndex, currentPath);
760
+ if (!entries) return null;
761
+ applyPersistedOrder(entries);
762
+ return entries;
763
+ }
764
+ function syncRemovedFlags(entries) {
765
+ const removedIds = new Set(entries.filter((e) => e.removed).map((e) => e.instanceId));
766
+ document.querySelectorAll(`[${REMOVED_ATTR}]`).forEach((el) => {
767
+ if (!removedIds.has(instanceIdOf(el))) {
768
+ el.style.removeProperty("display");
769
+ el.removeAttribute(REMOVED_ATTR);
770
+ }
771
+ });
772
+ for (const id of removedIds) {
773
+ const el = findByInstanceId(id);
774
+ if (el) {
775
+ el.style.display = "none";
776
+ el.setAttribute(REMOVED_ATTR, "");
777
+ }
778
+ }
779
+ }
780
+ function applyPersistedOrder(entries) {
781
+ syncRemovedFlags(entries);
782
+ if (entries.length === 0) return;
783
+ const sections = topLevelSections();
784
+ if (sections.length === 0) return;
785
+ const orderIndex = new Map(entries.map((e) => [e.instanceId, e.order]));
786
+ const ordered = [...sections].sort((a, b) => {
787
+ const aOrder = orderIndex.get(instanceIdOf(a));
788
+ const bOrder = orderIndex.get(instanceIdOf(b));
789
+ if (aOrder === void 0 && bOrder === void 0) return 0;
790
+ if (aOrder === void 0) return 1;
791
+ if (bOrder === void 0) return -1;
792
+ return aOrder - bOrder;
793
+ });
794
+ let prev = null;
795
+ for (const el of ordered) {
796
+ if (prev) prev.after(el);
797
+ prev = el;
798
+ }
799
+ }
800
+ function setSectionRemoved(instanceId, currentPath, existingEntries, removed) {
801
+ if (!findByInstanceId(instanceId)) return null;
802
+ const byId = new Map(existingEntries.map((e) => [e.instanceId, e]));
803
+ const allSections = collectTopLevelUnits((el) => !isChromeSection(el));
804
+ allSections.forEach((el, order) => {
805
+ const id = instanceIdOf(el);
806
+ if (!byId.has(id)) {
807
+ byId.set(id, { instanceId: id, type: sectionTypeOf(el), order, pagePath: currentPath });
808
+ }
809
+ });
810
+ const target = byId.get(instanceId);
811
+ if (!target) return null;
812
+ byId.set(instanceId, { ...target, removed });
813
+ const entries = Array.from(byId.values());
814
+ applyPersistedOrder(entries);
815
+ return entries;
816
+ }
817
+ function deleteSectionInstance(instanceId, currentPath, existingEntries) {
818
+ return setSectionRemoved(instanceId, currentPath, existingEntries, true);
819
+ }
820
+ function restoreSectionInstance(instanceId, currentPath, existingEntries) {
821
+ return setSectionRemoved(instanceId, currentPath, existingEntries, false);
822
+ }
823
+ function duplicateSectionInstance(instanceId, newId, currentPath, existingEntries) {
824
+ const original = findByInstanceId(instanceId);
825
+ if (!original) return null;
826
+ const clone = original.cloneNode(true);
827
+ clone.setAttribute("data-ohw-instance", newId);
828
+ const keyRekeys = rekeySectionSubtree(clone, newId);
829
+ original.insertAdjacentElement("afterend", clone);
830
+ const byId = new Map(existingEntries.map((e) => [e.instanceId, e]));
831
+ const entries = topLevelSections().map((el, order) => {
832
+ const id = instanceIdOf(el);
833
+ return {
834
+ instanceId: id,
835
+ type: sectionTypeOf(el),
836
+ order,
837
+ pagePath: currentPath,
838
+ ...byId.get(id)?.removed ? { removed: true } : {}
839
+ };
840
+ });
841
+ applyPersistedOrder(entries);
842
+ return { entries, keyRekeys };
843
+ }
844
+ function newInstanceId() {
845
+ return typeof crypto !== "undefined" && "randomUUID" in crypto ? crypto.randomUUID() : `instance-${Date.now()}-${Math.random().toString(36).slice(2)}`;
846
+ }
847
+ function getPageSectionOrderEntries(raw, currentPath) {
848
+ if (!raw) return [];
849
+ try {
850
+ const entries = JSON.parse(raw);
851
+ return entries.filter((e) => !e.pagePath || e.pagePath === currentPath);
852
+ } catch {
853
+ return [];
854
+ }
855
+ }
856
+ function mergePageSectionOrder(raw, currentPath, pageEntries) {
857
+ let all = [];
858
+ if (raw) {
859
+ try {
860
+ const parsed = JSON.parse(raw);
861
+ if (Array.isArray(parsed)) all = parsed;
862
+ } catch {
863
+ }
864
+ }
865
+ const otherPages = all.filter((e) => e && e.pagePath && e.pagePath !== currentPath);
866
+ const pageIds = new Set(pageEntries.map((e) => e.instanceId));
867
+ const removedHere = all.filter(
868
+ (e) => e && (!e.pagePath || e.pagePath === currentPath) && e.removed && !pageIds.has(e.instanceId)
869
+ );
870
+ return [...otherPages, ...removedHere, ...pageEntries];
871
+ }
872
+ function rekeySectionSubtree(root, instanceId) {
873
+ const suffix = `::${instanceId}`;
874
+ const pairs = [];
875
+ const rekey = (el, attr) => {
876
+ const current = el.getAttribute(attr);
877
+ if (!current) return;
878
+ const base = current.includes("::") ? current.slice(0, current.indexOf("::")) : current;
879
+ const next = `${base}${suffix}`;
880
+ el.setAttribute(attr, next);
881
+ pairs.push({ from: current, to: next });
882
+ };
883
+ if (root.hasAttribute("data-ohw-key")) rekey(root, "data-ohw-key");
884
+ if (root.hasAttribute("data-ohw-href-key")) rekey(root, "data-ohw-href-key");
885
+ root.querySelectorAll("[data-ohw-key]").forEach((el) => rekey(el, "data-ohw-key"));
886
+ root.querySelectorAll("[data-ohw-href-key]").forEach((el) => rekey(el, "data-ohw-href-key"));
887
+ return pairs;
888
+ }
889
+ function initSectionInstancesFromContent(content, currentPath) {
890
+ document.querySelectorAll("[data-ohw-section]:not([data-ohw-instance])").forEach((el) => {
891
+ el.setAttribute("data-ohw-instance", el.getAttribute("data-ohw-section") ?? "");
892
+ });
893
+ document.querySelectorAll("[data-ohw-section-container]:not([data-ohw-instance])").forEach((el) => {
894
+ const type = sectionTypeOf(el);
895
+ if (type) el.setAttribute("data-ohw-instance", type);
896
+ });
897
+ const entries = getPageSectionOrderEntries(content[SECTION_ORDER_KEY], currentPath);
898
+ for (const entry of entries) {
899
+ if (entry.instanceId === entry.type) continue;
900
+ if (document.querySelector(`[data-ohw-instance="${CSS.escape(entry.instanceId)}"]`)) continue;
901
+ const original = document.querySelector(
902
+ `[data-ohw-section="${CSS.escape(entry.type)}"][data-ohw-instance="${CSS.escape(entry.type)}"]`
903
+ );
904
+ if (!original) continue;
905
+ const clone = original.cloneNode(true);
906
+ clone.setAttribute("data-ohw-instance", entry.instanceId);
907
+ rekeySectionSubtree(clone, entry.instanceId);
908
+ original.insertAdjacentElement("afterend", clone);
909
+ }
910
+ applyPersistedOrder(entries);
911
+ }
912
+
635
913
  // src/ui/ai-tree/AiTreeRenderer.tsx
636
914
  var import_react = __toESM(require("react"), 1);
637
915
  var import_lucide_react = require("lucide-react");
@@ -674,6 +952,25 @@ var FEATURE_LINE_CSS = [
674
952
  `background-color:currentColor;-webkit-mask:url("${CHECK_MASK}") no-repeat 0 0/24px 24px;`,
675
953
  `mask:url("${CHECK_MASK}") no-repeat 0 0/24px 24px}`
676
954
  ].join("");
955
+ function buttonShellStyle(ctx, fullWidth) {
956
+ const bs = ctx.buttonStyle;
957
+ if (bs) {
958
+ return {
959
+ borderRadius: bs.radius,
960
+ ...bs.padding ? { padding: bs.padding } : {},
961
+ ...bs.fontFamily ? { fontFamily: bs.fontFamily } : { fontFamily: ctx.brand.fonts.body },
962
+ ...bs.fontSize ? { fontSize: bs.fontSize } : {},
963
+ ...bs.fontWeight ? { fontWeight: bs.fontWeight } : {},
964
+ ...bs.letterSpacing && bs.letterSpacing !== "normal" ? { letterSpacing: bs.letterSpacing } : {},
965
+ ...bs.textTransform && bs.textTransform !== "none" ? { textTransform: bs.textTransform } : {}
966
+ };
967
+ }
968
+ return {
969
+ borderRadius: AI_TREE_TOKENS.radiusButton,
970
+ padding: fullWidth ? `${AI_TREE_TOKENS.spacing2}px ${AI_TREE_TOKENS.spacing4}px` : `${AI_TREE_TOKENS.spacing3}px ${AI_TREE_TOKENS.spacing6}px`,
971
+ ...typeStyle(AI_TREE_TOKENS.type.button, ctx.brand.fonts.body)
972
+ };
973
+ }
677
974
  function hexLuminance(color) {
678
975
  const m = /^#([0-9a-f]{6})$/i.exec(color.trim());
679
976
  if (!m) return null;
@@ -690,6 +987,12 @@ function hexContrast(a, b) {
690
987
  const [hi, lo] = la > lb ? [la, lb] : [lb, la];
691
988
  return (hi + 0.05) / (lo + 0.05);
692
989
  }
990
+ function primaryButtonLabel(brand) {
991
+ const darkC = hexContrast(brand.palette.primary, brand.palette.dark);
992
+ const lightC = hexContrast(brand.palette.primary, AI_TREE_TOKENS.textPrimaryForeground);
993
+ if (darkC === null || lightC === null) return AI_TREE_TOKENS.textPrimaryForeground;
994
+ return darkC > lightC ? brand.palette.dark : AI_TREE_TOKENS.textPrimaryForeground;
995
+ }
693
996
  function accentBandContext(brand) {
694
997
  const p = brand.palette;
695
998
  const lightWins = (hexContrast(p.light, p.primary) ?? 99) >= (hexContrast(p.dark, p.primary) ?? 0);
@@ -789,12 +1092,10 @@ function ButtonEl({
789
1092
  width: fullWidth ? "100%" : void 0,
790
1093
  alignItems: "center",
791
1094
  justifyContent: "center",
792
- padding: fullWidth ? `${AI_TREE_TOKENS.spacing2}px ${AI_TREE_TOKENS.spacing4}px` : `${AI_TREE_TOKENS.spacing3}px ${AI_TREE_TOKENS.spacing6}px`,
793
- borderRadius: AI_TREE_TOKENS.radiusButton,
794
1095
  textDecoration: "none",
795
1096
  cursor: "pointer",
796
- ...typeStyle(AI_TREE_TOKENS.type.button, ctx.brand.fonts.body),
797
- ...secondary ? { border: `1px solid ${ctx.brand.palette.dark}`, color: ctx.brand.palette.dark } : { background: ctx.brand.palette.primary, color: ctx.buttonLabel ?? AI_TREE_TOKENS.textPrimaryForeground }
1097
+ ...buttonShellStyle(ctx, fullWidth),
1098
+ ...secondary ? { border: `1px solid ${ctx.brand.palette.dark}`, color: ctx.brand.palette.dark } : { background: ctx.brand.palette.primary, color: ctx.buttonLabel ?? primaryButtonLabel(ctx.brand) }
798
1099
  },
799
1100
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { ...path ? textAttrs(ctx, `${path}.label`) : {}, children: str(slots.label) })
800
1101
  }
@@ -1834,15 +2135,12 @@ function renderNode(node, ctx, path) {
1834
2135
  alignSelf: submitAlign,
1835
2136
  border: "none",
1836
2137
  cursor: "pointer",
1837
- padding: "12px 24px",
1838
- // Corner radius follows the host template's own buttons (measured from a template
1839
- // CTA); 8px only when the page has no template button to match.
1840
- borderRadius: ctx.buttonRadius ?? 8,
2138
+ // Shape/padding/typography follow the host template's own buttons.
2139
+ ...buttonShellStyle(ctx),
1841
2140
  // Brand-styled: primary fill, brand-derived label colour (not a fixed token) so it
1842
2141
  // reads correctly on custom palettes.
1843
2142
  background: ctx.brand.palette.primary,
1844
- color: ctx.buttonLabel ?? ctx.brand.palette.light,
1845
- ...typeStyle(AI_TREE_TOKENS.type.button, ctx.brand.fonts.body)
2143
+ color: ctx.buttonLabel ?? primaryButtonLabel(ctx.brand)
1846
2144
  },
1847
2145
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { ...textAttrs(ctx, `${path}.c${i}.label`), children: str(cs.label) })
1848
2146
  },
@@ -1877,7 +2175,7 @@ function renderNode(node, ctx, path) {
1877
2175
  function AiTreeRenderer({
1878
2176
  tree,
1879
2177
  brand,
1880
- buttonRadius,
2178
+ buttonStyle,
1881
2179
  resolveMedia,
1882
2180
  editKeyPrefix
1883
2181
  }) {
@@ -1893,7 +2191,7 @@ function AiTreeRenderer({
1893
2191
  cardSurface: blockBrand.palette.light.toUpperCase() === AI_DEFAULT_BRAND.palette.light.toUpperCase() ? "#ECECEB" : `color-mix(in srgb, ${blockBrand.palette.light} 90%, ${blockBrand.palette.dark})`,
1894
2192
  keyFor: editKeyPrefix ? (path) => `${editKeyPrefix}.${path}` : null,
1895
2193
  sectionAlignment: (tree.settings ?? {}).alignment === "center" ? "center" : "left",
1896
- buttonRadius,
2194
+ buttonStyle,
1897
2195
  ...band ? { buttonLabel: band.buttonLabel } : {}
1898
2196
  };
1899
2197
  const settings = tree.settings ?? {};
@@ -1999,7 +2297,7 @@ function AiTreeRenderer({
1999
2297
  var import_jsx_runtime2 = require("react/jsx-runtime");
2000
2298
  var CONTAINER_ATTR = "data-ohw-ai-generated";
2001
2299
  var REPLACED_ATTR = "data-ohw-ai-replaced-by";
2002
- var REMOVED_ATTR = "data-ohw-ai-removed";
2300
+ var REMOVED_ATTR2 = "data-ohw-ai-removed";
2003
2301
  var TEMPLATE_HIDDEN_ATTR = "data-ohw-ai-template-hidden";
2004
2302
  var CHROME_SECTION_IDS = /* @__PURE__ */ new Set(["navbar", "footer"]);
2005
2303
  function readRootVar(name) {
@@ -2023,13 +2321,13 @@ function deriveBrandOverride() {
2023
2321
  };
2024
2322
  }
2025
2323
  function deriveTemplateBrand() {
2026
- const dark = readRootVar("--color-dark");
2027
- const primary = readRootVar("--color-primary");
2028
- const light = readRootVar("--color-light");
2324
+ const primary = readRootVar("--brand-primary") || readRootVar("--color-primary");
2325
+ const dark = readRootVar("--brand-text") || readRootVar("--color-dark");
2326
+ const light = readRootVar("--brand-background") || readRootVar("--color-light");
2029
2327
  if (!dark || !primary || !light) return null;
2030
- const accent = readRootVar("--color-accent");
2031
- const heading = readRootVar("--font-heading") || readRootVar("--font-display");
2032
- const body = readRootVar("--font-body");
2328
+ const accent = readRootVar("--brand-accent") || readRootVar("--color-accent");
2329
+ const heading = readRootVar("--brand-font-heading") || readRootVar("--font-heading") || readRootVar("--font-display");
2330
+ const body = readRootVar("--brand-font-body") || readRootVar("--font-body");
2033
2331
  return {
2034
2332
  palette: { dark, primary, accent: accent || dark, light },
2035
2333
  fonts: {
@@ -2038,12 +2336,32 @@ function deriveTemplateBrand() {
2038
2336
  }
2039
2337
  };
2040
2338
  }
2041
- function deriveTemplateButtonRadius() {
2339
+ function deriveTemplateButtonStyle() {
2042
2340
  if (typeof document === "undefined") return null;
2043
- const btn = document.querySelector('[data-ohw-role="button"]');
2341
+ const btn = Array.from(document.querySelectorAll('[data-ohw-role="button"]')).find(
2342
+ (el) => !el.closest(`[${CONTAINER_ATTR}]`)
2343
+ );
2044
2344
  if (!btn) return null;
2045
- const radius = getComputedStyle(btn).borderTopLeftRadius;
2046
- return radius || null;
2345
+ const cs = getComputedStyle(btn);
2346
+ const corners = [
2347
+ cs.borderTopLeftRadius,
2348
+ cs.borderTopRightRadius,
2349
+ cs.borderBottomRightRadius,
2350
+ cs.borderBottomLeftRadius
2351
+ ].map((v) => v || "0px");
2352
+ const radius = corners.every((v) => v === corners[0]) ? corners[0] : corners.join(" ");
2353
+ const px = (v) => parseFloat(v) || 0;
2354
+ const padY = Math.max(px(cs.paddingTop), px(cs.paddingBottom));
2355
+ const padX = Math.max(px(cs.paddingLeft), px(cs.paddingRight));
2356
+ return {
2357
+ radius: radius || "10px",
2358
+ padding: `${padY}px ${padX}px`,
2359
+ fontFamily: cs.fontFamily || "",
2360
+ fontSize: cs.fontSize || "",
2361
+ fontWeight: cs.fontWeight || "",
2362
+ letterSpacing: cs.letterSpacing || "",
2363
+ textTransform: cs.textTransform || ""
2364
+ };
2047
2365
  }
2048
2366
  var mounted = /* @__PURE__ */ new Map();
2049
2367
  function findTemplateSection(id) {
@@ -2094,18 +2412,18 @@ function placeContainer(container, entry) {
2094
2412
  }
2095
2413
  function syncRemovedSections(state) {
2096
2414
  const removed = new Set(state.removed ?? []);
2097
- for (const el of document.querySelectorAll(`[${REMOVED_ATTR}]`)) {
2415
+ for (const el of document.querySelectorAll(`[${REMOVED_ATTR2}]`)) {
2098
2416
  const id = el.getAttribute("data-ohw-section") ?? "";
2099
2417
  if (!removed.has(id)) {
2100
2418
  el.style.removeProperty("display");
2101
- el.removeAttribute(REMOVED_ATTR);
2419
+ el.removeAttribute(REMOVED_ATTR2);
2102
2420
  }
2103
2421
  }
2104
2422
  for (const id of removed) {
2105
2423
  const section = findTemplateSection(id);
2106
2424
  if (section && !section.hasAttribute(REPLACED_ATTR)) {
2107
2425
  section.style.display = "none";
2108
- section.setAttribute(REMOVED_ATTR, "");
2426
+ section.setAttribute(REMOVED_ATTR2, "");
2109
2427
  }
2110
2428
  }
2111
2429
  }
@@ -2122,7 +2440,7 @@ function syncTemplateHidden(state, pageHasSections) {
2122
2440
  if (el.hasAttribute(CONTAINER_ATTR)) continue;
2123
2441
  if (CHROME_SECTION_IDS.has(el.getAttribute("data-ohw-section") ?? "")) continue;
2124
2442
  if (el.parentElement?.closest("[data-ohw-section]")) continue;
2125
- if (el.hasAttribute(REPLACED_ATTR) || el.hasAttribute(REMOVED_ATTR)) continue;
2443
+ if (el.hasAttribute(REPLACED_ATTR) || el.hasAttribute(REMOVED_ATTR2)) continue;
2126
2444
  el.style.display = "none";
2127
2445
  el.setAttribute(TEMPLATE_HIDDEN_ATTR, "");
2128
2446
  }
@@ -2146,18 +2464,23 @@ function syncReplacedOriginals(state) {
2146
2464
  }
2147
2465
  }
2148
2466
  var sectionOrderIndex = /* @__PURE__ */ new Map();
2467
+ var removedSectionIds = /* @__PURE__ */ new Set();
2149
2468
  function setAiSectionOrder(raw, currentPath) {
2150
2469
  const next = /* @__PURE__ */ new Map();
2470
+ const removed = /* @__PURE__ */ new Set();
2151
2471
  if (raw) {
2152
2472
  try {
2153
2473
  const entries = JSON.parse(raw);
2154
2474
  for (const entry of entries) {
2155
- if (!entry.pagePath || entry.pagePath === currentPath) next.set(entry.instanceId, entry.order);
2475
+ if (entry.pagePath && entry.pagePath !== currentPath) continue;
2476
+ next.set(entry.instanceId, entry.order);
2477
+ if (entry.removed) removed.add(entry.instanceId);
2156
2478
  }
2157
2479
  } catch {
2158
2480
  }
2159
2481
  }
2160
2482
  sectionOrderIndex = next;
2483
+ removedSectionIds = removed;
2161
2484
  }
2162
2485
  function applyExplicitOrder(entries) {
2163
2486
  if (sectionOrderIndex.size === 0) return entries;
@@ -2193,11 +2516,23 @@ function orderByChain(sections) {
2193
2516
  for (const root of roots) visit(root);
2194
2517
  return out.length === sections.length ? out : sections;
2195
2518
  }
2519
+ function syncSoftRemovedGenerated() {
2520
+ for (const [id, section] of mounted) {
2521
+ const el = section.container;
2522
+ if (removedSectionIds.has(id)) {
2523
+ el.style.display = "none";
2524
+ el.setAttribute(REMOVED_ATTR, "");
2525
+ } else if (el.hasAttribute(REMOVED_ATTR)) {
2526
+ el.style.removeProperty("display");
2527
+ el.removeAttribute(REMOVED_ATTR);
2528
+ }
2529
+ }
2530
+ }
2196
2531
  function applyAiSectionsToDom(state, options) {
2197
2532
  if (typeof document === "undefined") return;
2198
2533
  const brandOverride = deriveBrandOverride();
2199
2534
  const templateBrand = deriveTemplateBrand();
2200
- const templateButtonRadius = deriveTemplateButtonRadius();
2535
+ const templateButtonStyle = deriveTemplateButtonStyle();
2201
2536
  const brandKey = brandOverride ? JSON.stringify(brandOverride) : "";
2202
2537
  const pagePath = window.location.pathname;
2203
2538
  const pageSections = state.sections.filter((entry) => (entry.path ?? "/") === pagePath);
@@ -2237,7 +2572,7 @@ function applyAiSectionsToDom(state, options) {
2237
2572
  {
2238
2573
  tree: entry.tree,
2239
2574
  brand: brandOverride ?? entry.brand ?? options?.brand ?? templateBrand ?? void 0,
2240
- buttonRadius: templateButtonRadius,
2575
+ buttonStyle: templateButtonStyle,
2241
2576
  resolveMedia,
2242
2577
  editKeyPrefix: `ai.${entry.id}`
2243
2578
  }
@@ -2260,6 +2595,7 @@ function applyAiSectionsToDom(state, options) {
2260
2595
  syncReplacedOriginals(state);
2261
2596
  syncRemovedSections(state);
2262
2597
  syncTemplateHidden(state, pageSections.length > 0);
2598
+ syncSoftRemovedGenerated();
2263
2599
  }
2264
2600
  function unmountAllAiSections() {
2265
2601
  for (const [, section] of mounted) {
@@ -2272,9 +2608,9 @@ function unmountAllAiSections() {
2272
2608
  el.style.removeProperty("display");
2273
2609
  el.removeAttribute(REPLACED_ATTR);
2274
2610
  }
2275
- for (const el of document.querySelectorAll(`[${REMOVED_ATTR}]`)) {
2611
+ for (const el of document.querySelectorAll(`[${REMOVED_ATTR2}]`)) {
2276
2612
  el.style.removeProperty("display");
2277
- el.removeAttribute(REMOVED_ATTR);
2613
+ el.removeAttribute(REMOVED_ATTR2);
2278
2614
  }
2279
2615
  }
2280
2616
 
@@ -8235,240 +8571,6 @@ function CarouselOverlay({
8235
8571
  // src/ui/ai-section/AiSectionOverlay.tsx
8236
8572
  var import_react8 = require("react");
8237
8573
  var import_lucide_react7 = require("lucide-react");
8238
-
8239
- // src/lib/sections.ts
8240
- var LINK_PICKER_EXCLUDED_IDS = /* @__PURE__ */ new Set(["navbar", "footer"]);
8241
- function isChromeSection(el) {
8242
- return el.matches("header, nav, footer, aside");
8243
- }
8244
- function titleCaseSectionId(id) {
8245
- return id.split("-").filter(Boolean).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
8246
- }
8247
- function parseSectionsFromRoot(root) {
8248
- const seen = /* @__PURE__ */ new Set();
8249
- const sections = [];
8250
- for (const el of root.querySelectorAll("[data-ohw-section]")) {
8251
- const id = el.getAttribute("data-ohw-section") ?? "";
8252
- if (!id || seen.has(id) || LINK_PICKER_EXCLUDED_IDS.has(id)) continue;
8253
- if (el.parentElement?.closest("[data-ohw-section]")) continue;
8254
- if (el.hasAttribute("data-ohw-ai-template-hidden") || el.hasAttribute("data-ohw-ai-removed") || el.hasAttribute("data-ohw-ai-replaced-by"))
8255
- continue;
8256
- seen.add(id);
8257
- const label = el.getAttribute("data-ohw-section-label") ?? titleCaseSectionId(id);
8258
- sections.push({ id, label });
8259
- }
8260
- return sections;
8261
- }
8262
- function collectSectionsFromDom() {
8263
- if (typeof document === "undefined") return [];
8264
- return parseSectionsFromRoot(document);
8265
- }
8266
- function parseSectionsFromHtml(html) {
8267
- const doc = new DOMParser().parseFromString(html, "text/html");
8268
- return parseSectionsFromRoot(doc);
8269
- }
8270
-
8271
- // src/lib/section-instances.ts
8272
- var SECTION_ORDER_KEY = "__ohw_section_order";
8273
- var REMOVED_ATTR2 = "data-ohw-section-removed";
8274
- function isRemovedSection(el) {
8275
- return el.hasAttribute(REMOVED_ATTR2);
8276
- }
8277
- function movableUnit(el) {
8278
- return el.closest("[data-ohw-section-container]") ?? el;
8279
- }
8280
- function sectionTypeOf(el) {
8281
- return el.getAttribute("data-ohw-section") ?? el.querySelector("[data-ohw-section]")?.getAttribute("data-ohw-section") ?? "";
8282
- }
8283
- function sectionElementOf(el) {
8284
- return el.hasAttribute("data-ohw-section") ? el : el.querySelector("[data-ohw-section]") ?? el;
8285
- }
8286
- function collectTopLevelUnits(predicate) {
8287
- const seen = /* @__PURE__ */ new Set();
8288
- const result = [];
8289
- document.querySelectorAll("[data-ohw-section]").forEach((el) => {
8290
- if (!predicate(el)) return;
8291
- const unit = movableUnit(el);
8292
- if (unit.parentElement?.closest("[data-ohw-section],[data-ohw-section-container]")) return;
8293
- if (seen.has(unit)) return;
8294
- seen.add(unit);
8295
- result.push(unit);
8296
- });
8297
- return result;
8298
- }
8299
- function topLevelSections() {
8300
- return collectTopLevelUnits((el) => !isChromeSection(el) && !isRemovedSection(movableUnit(el)));
8301
- }
8302
- function instanceIdOf(el) {
8303
- return el.dataset.ohwInstance ?? el.dataset.ohwSection ?? "";
8304
- }
8305
- function findByInstanceId(instanceId) {
8306
- const escapedId = CSS.escape(instanceId);
8307
- const direct = document.querySelector(`[data-ohw-instance="${escapedId}"]`);
8308
- if (direct) return movableUnit(direct);
8309
- const bare = document.querySelector(`[data-ohw-section="${escapedId}"]:not([data-ohw-instance])`);
8310
- return bare ? movableUnit(bare) : null;
8311
- }
8312
- function planSectionMove(instanceId, targetIndex, currentPath) {
8313
- const sections = topLevelSections();
8314
- const index = sections.findIndex((el) => instanceIdOf(el) === instanceId);
8315
- if (index === -1) return null;
8316
- const dragged = sections[index];
8317
- const others = sections.filter((_, i) => i !== index);
8318
- const clamped = Math.max(0, Math.min(targetIndex, others.length));
8319
- const reordered = [...others.slice(0, clamped), dragged, ...others.slice(clamped)];
8320
- return reordered.map((el, order) => ({
8321
- instanceId: instanceIdOf(el),
8322
- type: sectionTypeOf(el),
8323
- order,
8324
- pagePath: currentPath
8325
- }));
8326
- }
8327
- function moveSectionInstance(instanceId, direction, currentPath) {
8328
- const sections = topLevelSections();
8329
- const index = sections.findIndex((el) => instanceIdOf(el) === instanceId);
8330
- if (index === -1) return null;
8331
- const siblingIndex = direction === "up" ? index - 1 : index + 1;
8332
- if (siblingIndex < 0 || siblingIndex >= sections.length) return null;
8333
- const entries = planSectionMove(instanceId, siblingIndex, currentPath);
8334
- if (!entries) return null;
8335
- applyPersistedOrder(entries);
8336
- return entries;
8337
- }
8338
- function syncRemovedFlags(entries) {
8339
- const removedIds = new Set(entries.filter((e) => e.removed).map((e) => e.instanceId));
8340
- document.querySelectorAll(`[${REMOVED_ATTR2}]`).forEach((el) => {
8341
- if (!removedIds.has(instanceIdOf(el))) {
8342
- el.style.removeProperty("display");
8343
- el.removeAttribute(REMOVED_ATTR2);
8344
- }
8345
- });
8346
- for (const id of removedIds) {
8347
- const el = findByInstanceId(id);
8348
- if (el) {
8349
- el.style.display = "none";
8350
- el.setAttribute(REMOVED_ATTR2, "");
8351
- }
8352
- }
8353
- }
8354
- function applyPersistedOrder(entries) {
8355
- syncRemovedFlags(entries);
8356
- if (entries.length === 0) return;
8357
- const sections = topLevelSections();
8358
- if (sections.length === 0) return;
8359
- const orderIndex = new Map(entries.map((e) => [e.instanceId, e.order]));
8360
- const ordered = [...sections].sort((a, b) => {
8361
- const aOrder = orderIndex.get(instanceIdOf(a));
8362
- const bOrder = orderIndex.get(instanceIdOf(b));
8363
- if (aOrder === void 0 && bOrder === void 0) return 0;
8364
- if (aOrder === void 0) return 1;
8365
- if (bOrder === void 0) return -1;
8366
- return aOrder - bOrder;
8367
- });
8368
- let prev = null;
8369
- for (const el of ordered) {
8370
- if (prev) prev.after(el);
8371
- prev = el;
8372
- }
8373
- }
8374
- function setSectionRemoved(instanceId, currentPath, existingEntries, removed) {
8375
- if (!findByInstanceId(instanceId)) return null;
8376
- const byId = new Map(existingEntries.map((e) => [e.instanceId, e]));
8377
- const allSections = collectTopLevelUnits((el) => !isChromeSection(el));
8378
- allSections.forEach((el, order) => {
8379
- const id = instanceIdOf(el);
8380
- if (!byId.has(id)) {
8381
- byId.set(id, { instanceId: id, type: sectionTypeOf(el), order, pagePath: currentPath });
8382
- }
8383
- });
8384
- const target = byId.get(instanceId);
8385
- if (!target) return null;
8386
- byId.set(instanceId, { ...target, removed });
8387
- const entries = Array.from(byId.values());
8388
- applyPersistedOrder(entries);
8389
- return entries;
8390
- }
8391
- function deleteSectionInstance(instanceId, currentPath, existingEntries) {
8392
- return setSectionRemoved(instanceId, currentPath, existingEntries, true);
8393
- }
8394
- function restoreSectionInstance(instanceId, currentPath, existingEntries) {
8395
- return setSectionRemoved(instanceId, currentPath, existingEntries, false);
8396
- }
8397
- function duplicateSectionInstance(instanceId, newId, currentPath, existingEntries) {
8398
- const original = findByInstanceId(instanceId);
8399
- if (!original) return null;
8400
- const clone = original.cloneNode(true);
8401
- clone.setAttribute("data-ohw-instance", newId);
8402
- const keyRekeys = rekeySectionSubtree(clone, newId);
8403
- original.insertAdjacentElement("afterend", clone);
8404
- const byId = new Map(existingEntries.map((e) => [e.instanceId, e]));
8405
- const entries = topLevelSections().map((el, order) => {
8406
- const id = instanceIdOf(el);
8407
- return {
8408
- instanceId: id,
8409
- type: sectionTypeOf(el),
8410
- order,
8411
- pagePath: currentPath,
8412
- ...byId.get(id)?.removed ? { removed: true } : {}
8413
- };
8414
- });
8415
- applyPersistedOrder(entries);
8416
- return { entries, keyRekeys };
8417
- }
8418
- function newInstanceId() {
8419
- return typeof crypto !== "undefined" && "randomUUID" in crypto ? crypto.randomUUID() : `instance-${Date.now()}-${Math.random().toString(36).slice(2)}`;
8420
- }
8421
- function getPageSectionOrderEntries(raw, currentPath) {
8422
- if (!raw) return [];
8423
- try {
8424
- const entries = JSON.parse(raw);
8425
- return entries.filter((e) => !e.pagePath || e.pagePath === currentPath);
8426
- } catch {
8427
- return [];
8428
- }
8429
- }
8430
- function rekeySectionSubtree(root, instanceId) {
8431
- const suffix = `::${instanceId}`;
8432
- const pairs = [];
8433
- const rekey = (el, attr) => {
8434
- const current = el.getAttribute(attr);
8435
- if (!current) return;
8436
- const base = current.includes("::") ? current.slice(0, current.indexOf("::")) : current;
8437
- const next = `${base}${suffix}`;
8438
- el.setAttribute(attr, next);
8439
- pairs.push({ from: current, to: next });
8440
- };
8441
- if (root.hasAttribute("data-ohw-key")) rekey(root, "data-ohw-key");
8442
- if (root.hasAttribute("data-ohw-href-key")) rekey(root, "data-ohw-href-key");
8443
- root.querySelectorAll("[data-ohw-key]").forEach((el) => rekey(el, "data-ohw-key"));
8444
- root.querySelectorAll("[data-ohw-href-key]").forEach((el) => rekey(el, "data-ohw-href-key"));
8445
- return pairs;
8446
- }
8447
- function initSectionInstancesFromContent(content, currentPath) {
8448
- document.querySelectorAll("[data-ohw-section]:not([data-ohw-instance])").forEach((el) => {
8449
- el.setAttribute("data-ohw-instance", el.getAttribute("data-ohw-section") ?? "");
8450
- });
8451
- document.querySelectorAll("[data-ohw-section-container]:not([data-ohw-instance])").forEach((el) => {
8452
- const type = sectionTypeOf(el);
8453
- if (type) el.setAttribute("data-ohw-instance", type);
8454
- });
8455
- const entries = getPageSectionOrderEntries(content[SECTION_ORDER_KEY], currentPath);
8456
- for (const entry of entries) {
8457
- if (entry.instanceId === entry.type) continue;
8458
- if (document.querySelector(`[data-ohw-instance="${CSS.escape(entry.instanceId)}"]`)) continue;
8459
- const original = document.querySelector(
8460
- `[data-ohw-section="${CSS.escape(entry.type)}"][data-ohw-instance="${CSS.escape(entry.type)}"]`
8461
- );
8462
- if (!original) continue;
8463
- const clone = original.cloneNode(true);
8464
- clone.setAttribute("data-ohw-instance", entry.instanceId);
8465
- rekeySectionSubtree(clone, entry.instanceId);
8466
- original.insertAdjacentElement("afterend", clone);
8467
- }
8468
- applyPersistedOrder(entries);
8469
- }
8470
-
8471
- // src/ui/ai-section/AiSectionOverlay.tsx
8472
8574
  var import_jsx_runtime17 = require("react/jsx-runtime");
8473
8575
  var findSectionElement = findByInstanceId;
8474
8576
  function readRect(instanceId) {
@@ -14251,15 +14353,17 @@ function useSectionDrag({
14251
14353
  clearSectionDragVisuals();
14252
14354
  return;
14253
14355
  }
14254
- const orderJson = JSON.stringify(entries);
14356
+ const orderJson = JSON.stringify(
14357
+ mergePageSectionOrder(editContentRef.current[SECTION_ORDER_KEY], window.location.pathname, entries)
14358
+ );
14255
14359
  editContentRef.current = { ...editContentRef.current, [SECTION_ORDER_KEY]: orderJson };
14256
14360
  setAiSectionOrder(orderJson, window.location.pathname);
14257
14361
  postToParentRef.current({ type: "ow:change", nodes: [{ key: SECTION_ORDER_KEY, text: orderJson }] });
14258
- applyPersistedOrder(entries);
14362
+ applyPersistedOrder(getPageSectionOrderEntries(orderJson, window.location.pathname));
14259
14363
  clearSectionDragVisuals();
14260
14364
  requestAnimationFrame(() => {
14261
14365
  if (editContentRef.current[SECTION_ORDER_KEY] === orderJson) {
14262
- applyPersistedOrder(entries);
14366
+ applyPersistedOrder(getPageSectionOrderEntries(orderJson, window.location.pathname));
14263
14367
  }
14264
14368
  requestAnimationFrame(() => {
14265
14369
  window.dispatchEvent(new Event("resize"));
@@ -20284,6 +20388,44 @@ function OhhwellsBridge() {
20284
20388
  if (h > 50) postToParentRef.current({ type: "ow:height", height: h });
20285
20389
  }, 400));
20286
20390
  };
20391
+ const reapCommittedAiSections = (excludeIds) => {
20392
+ const aiState = parseAiSectionsState(aiSectionsRef.current);
20393
+ if (aiState.sections.length === 0) return [];
20394
+ let orderEntries = [];
20395
+ try {
20396
+ const parsed = JSON.parse(editContentRef.current[SECTION_ORDER_KEY] ?? "[]");
20397
+ if (Array.isArray(parsed)) orderEntries = parsed;
20398
+ } catch {
20399
+ return [];
20400
+ }
20401
+ const removedIds = orderEntries.filter((entry) => entry && typeof entry.instanceId === "string" && entry.removed).map((entry) => entry.instanceId);
20402
+ if (removedIds.length === 0) return [];
20403
+ const result = reapRemovedAiSections(aiState, parseStyleStore(stylesRef.current), removedIds, excludeIds);
20404
+ if (!result.changed) return [];
20405
+ const nodes = [];
20406
+ aiSectionsRef.current = serializeAiSectionsState(result.state);
20407
+ nodes.push({ key: AI_SECTIONS_KEY, text: aiSectionsRef.current });
20408
+ const reaped = new Set(result.reapedIds);
20409
+ const nextOrderJson = JSON.stringify(orderEntries.filter((entry) => !reaped.has(entry.instanceId)));
20410
+ editContentRef.current = { ...editContentRef.current, [SECTION_ORDER_KEY]: nextOrderJson };
20411
+ setAiSectionOrder(nextOrderJson, window.location.pathname);
20412
+ nodes.push({ key: SECTION_ORDER_KEY, text: nextOrderJson });
20413
+ if (result.store) {
20414
+ stylesRef.current = JSON.stringify(result.store);
20415
+ nodes.push({ key: STYLE_STORE_KEY, text: stylesRef.current });
20416
+ }
20417
+ const nextContent = { ...editContentRef.current };
20418
+ for (const key of Object.keys(nextContent)) {
20419
+ if (key.startsWith(AI_SLOT_KEY_PREFIX) && result.slotPrefixes.some((prefix) => key.startsWith(prefix)) && nextContent[key] !== "") {
20420
+ nextContent[key] = "";
20421
+ nodes.push({ key, text: "" });
20422
+ }
20423
+ }
20424
+ editContentRef.current = nextContent;
20425
+ applyAiSectionsToDom(result.state);
20426
+ applyStylesToDom(parseStyleStore(stylesRef.current));
20427
+ return nodes;
20428
+ };
20287
20429
  const handleHydrate = (e) => {
20288
20430
  if (e.data?.type !== "ow:hydrate") return;
20289
20431
  const content = e.data.content;
@@ -20352,6 +20494,11 @@ function OhhwellsBridge() {
20352
20494
  reconcileFooterOrderFromContent(editContentRef.current);
20353
20495
  syncNavigationDragCursorAttrs();
20354
20496
  enforceLinkHrefs();
20497
+ const hydrateReapExclude = /* @__PURE__ */ new Set();
20498
+ const hydratePendingUndo = pendingDeleteUndoRef.current;
20499
+ if (hydratePendingUndo?.sectionInstanceId) hydrateReapExclude.add(hydratePendingUndo.sectionInstanceId);
20500
+ const reapNodes = reapCommittedAiSections(hydrateReapExclude);
20501
+ if (reapNodes.length > 0) postToParentRef.current({ type: "ow:change", nodes: reapNodes });
20355
20502
  const hydratedHeight = document.body.scrollHeight;
20356
20503
  if (hydratedHeight > 50) postToParentRef.current({ type: "ow:height", height: hydratedHeight });
20357
20504
  postToParentRef.current({ type: "ow:hydrate-done" });
@@ -20536,8 +20683,11 @@ function OhhwellsBridge() {
20536
20683
  if (!instanceId || !direction) return;
20537
20684
  const entries = moveSectionInstance(instanceId, direction, window.location.pathname);
20538
20685
  if (!entries) return;
20539
- const orderJson = JSON.stringify(entries);
20686
+ const orderJson = JSON.stringify(
20687
+ mergePageSectionOrder(editContentRef.current[SECTION_ORDER_KEY], window.location.pathname, entries)
20688
+ );
20540
20689
  editContentRef.current = { ...editContentRef.current, [SECTION_ORDER_KEY]: orderJson };
20690
+ applyPersistedOrder(getPageSectionOrderEntries(orderJson, window.location.pathname));
20541
20691
  setAiSectionOrder(orderJson, window.location.pathname);
20542
20692
  postToParentRef.current({ type: "ow:change", nodes: [{ key: SECTION_ORDER_KEY, text: orderJson }] });
20543
20693
  window.dispatchEvent(new Event("resize"));
@@ -20583,8 +20733,11 @@ function OhhwellsBridge() {
20583
20733
  const currentEntries = getPageSectionOrderEntries(editContentRef.current[SECTION_ORDER_KEY], window.location.pathname);
20584
20734
  const entries = deleteSectionInstance(instanceId, window.location.pathname, currentEntries);
20585
20735
  if (!entries) return;
20586
- const orderJson = JSON.stringify(entries);
20736
+ const orderJson = JSON.stringify(
20737
+ mergePageSectionOrder(editContentRef.current[SECTION_ORDER_KEY], window.location.pathname, entries)
20738
+ );
20587
20739
  editContentRef.current = { ...editContentRef.current, [SECTION_ORDER_KEY]: orderJson };
20740
+ setAiSectionOrder(orderJson, window.location.pathname);
20588
20741
  postToParentRef.current({ type: "ow:change", nodes: [{ key: SECTION_ORDER_KEY, text: orderJson }] });
20589
20742
  aiSectionApiRef.current?.clear();
20590
20743
  window.dispatchEvent(new Event("resize"));
@@ -20593,6 +20746,7 @@ function OhhwellsBridge() {
20593
20746
  const actionId = newInstanceId();
20594
20747
  pendingDeleteUndoRef.current = {
20595
20748
  actionId,
20749
+ sectionInstanceId: instanceId,
20596
20750
  restore: () => {
20597
20751
  const restoredEntries = getPageSectionOrderEntries(
20598
20752
  editContentRef.current[SECTION_ORDER_KEY],
@@ -20600,8 +20754,11 @@ function OhhwellsBridge() {
20600
20754
  );
20601
20755
  const restored = restoreSectionInstance(instanceId, window.location.pathname, restoredEntries);
20602
20756
  if (!restored) return;
20603
- const restoredJson = JSON.stringify(restored);
20757
+ const restoredJson = JSON.stringify(
20758
+ mergePageSectionOrder(editContentRef.current[SECTION_ORDER_KEY], window.location.pathname, restored)
20759
+ );
20604
20760
  editContentRef.current = { ...editContentRef.current, [SECTION_ORDER_KEY]: restoredJson };
20761
+ setAiSectionOrder(restoredJson, window.location.pathname);
20605
20762
  postToParentRef.current({ type: "ow:change", nodes: [{ key: SECTION_ORDER_KEY, text: restoredJson }] });
20606
20763
  window.dispatchEvent(new Event("resize"));
20607
20764
  const restoreHeight = document.body.scrollHeight;
@@ -20627,7 +20784,9 @@ function OhhwellsBridge() {
20627
20784
  const result = duplicateSectionInstance(instanceId, newId, window.location.pathname, currentEntries);
20628
20785
  if (!result) return;
20629
20786
  const { entries, keyRekeys } = result;
20630
- const orderJson = JSON.stringify(entries);
20787
+ const orderJson = JSON.stringify(
20788
+ mergePageSectionOrder(editContentRef.current[SECTION_ORDER_KEY], window.location.pathname, entries)
20789
+ );
20631
20790
  const nodes = [{ key: SECTION_ORDER_KEY, text: orderJson }];
20632
20791
  for (const { from, to } of keyRekeys) {
20633
20792
  const inherited = editContentRef.current[from];
@@ -20637,6 +20796,7 @@ function OhhwellsBridge() {
20637
20796
  ...editContentRef.current,
20638
20797
  ...Object.fromEntries(nodes.map(({ key, text }) => [key, text]))
20639
20798
  };
20799
+ applyPersistedOrder(getPageSectionOrderEntries(orderJson, window.location.pathname));
20640
20800
  setAiSectionOrder(orderJson, window.location.pathname);
20641
20801
  postToParentRef.current({ type: "ow:change", nodes });
20642
20802
  window.dispatchEvent(new Event("resize"));
@@ -20900,6 +21060,10 @@ function OhhwellsBridge() {
20900
21060
  };
20901
21061
  const handleSave = (e) => {
20902
21062
  if (e.data?.type !== "ow:save") return;
21063
+ const pendingUndo = pendingDeleteUndoRef.current;
21064
+ const reapExclude = /* @__PURE__ */ new Set();
21065
+ if (pendingUndo?.sectionInstanceId) reapExclude.add(pendingUndo.sectionInstanceId);
21066
+ const reapNodes = reapCommittedAiSections(reapExclude);
20903
21067
  const nodes = collectEditableNodes(editContentRef.current);
20904
21068
  const tracker = document.querySelector("[data-ohw-sections-tracker]");
20905
21069
  if (tracker?.textContent) nodes.push({ key: "__ohw_sections", type: "sections", text: tracker.textContent });
@@ -20919,6 +21083,11 @@ function OhhwellsBridge() {
20919
21083
  const text = specs.length ? JSON.stringify(specs) : editContentRef.current[fieldsKey(formKey)];
20920
21084
  if (text) nodes.push({ key: fieldsKey(formKey), type: "text", text });
20921
21085
  });
21086
+ for (const reapNode of reapNodes) {
21087
+ if (reapNode.key.startsWith(AI_SLOT_KEY_PREFIX) && !nodes.some((n) => n.key === reapNode.key)) {
21088
+ nodes.push({ key: reapNode.key, type: "text", text: reapNode.text });
21089
+ }
21090
+ }
20922
21091
  postToParentRef.current({ type: "ow:save-result", nodes });
20923
21092
  };
20924
21093
  const handleInsertSection = (e) => {
@@ -21540,7 +21709,7 @@ function OhhwellsBridge() {
21540
21709
  postToParent2({
21541
21710
  type: "ow:ready",
21542
21711
  version: "1",
21543
- bridgeVersion: "0.1.93",
21712
+ bridgeVersion: "0.1.95",
21544
21713
  path: pathname,
21545
21714
  nodes: collectEditableNodes(editContentRef.current),
21546
21715
  sections