@ohhwells/bridge 0.1.94 → 0.1.95

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -70,6 +70,9 @@ function isRenderableTree(value) {
70
70
  // src/lib/ai-sections-store.ts
71
71
  var AI_SECTIONS_KEY = "__ohw_ai_sections";
72
72
  var AI_SLOT_KEY_PREFIX = "ai.";
73
+ function aiSlotKeyPrefixFor(sectionId) {
74
+ return `${AI_SLOT_KEY_PREFIX}${sectionId}.`;
75
+ }
73
76
  var EMPTY_AI_SECTIONS = { v: 1, sections: [] };
74
77
  function parseAiSectionsState(raw) {
75
78
  if (!raw) return EMPTY_AI_SECTIONS;
@@ -181,6 +184,33 @@ function deleteSectionFromState(state, sectionId) {
181
184
  if (removed.includes(sectionId)) return state;
182
185
  return { ...state, v: 1, sections: state.sections, removed: [...removed, sectionId] };
183
186
  }
187
+ function reapRemovedAiSections(state, store, removedIds, excludeIds = /* @__PURE__ */ new Set()) {
188
+ const generated = new Set(state.sections.map((entry) => entry.id));
189
+ const reapedIds = [...new Set(removedIds)].filter((id) => generated.has(id) && !excludeIds.has(id));
190
+ if (reapedIds.length === 0) {
191
+ return { state, store, reapedIds: [], slotPrefixes: [], changed: false };
192
+ }
193
+ const reaped = new Set(reapedIds);
194
+ const slotPrefixes = reapedIds.map(aiSlotKeyPrefixFor);
195
+ const nextState = {
196
+ ...state,
197
+ v: 1,
198
+ sections: state.sections.filter((entry) => !reaped.has(entry.id))
199
+ };
200
+ let nextStore = store;
201
+ if (store) {
202
+ const sections = {};
203
+ for (const [key, override] of Object.entries(store.sections)) {
204
+ if (!reaped.has(key)) sections[key] = override;
205
+ }
206
+ const nodes = {};
207
+ for (const [key, override] of Object.entries(store.nodes)) {
208
+ if (!slotPrefixes.some((prefix) => key.startsWith(prefix))) nodes[key] = override;
209
+ }
210
+ nextStore = { v: 1, sections, nodes };
211
+ }
212
+ return { state: nextState, store: nextStore, reapedIds, slotPrefixes, changed: true };
213
+ }
184
214
 
185
215
  // src/lib/brand-chrome.ts
186
216
  var BRAND_NAME_KEY = "__ohw_brand_name";
@@ -559,6 +589,254 @@ function applyStylesToDom(store) {
559
589
  import { flushSync } from "react-dom";
560
590
  import { createRoot } from "react-dom/client";
561
591
 
592
+ // src/lib/sections.ts
593
+ var LINK_PICKER_EXCLUDED_IDS = /* @__PURE__ */ new Set(["navbar", "footer"]);
594
+ function isChromeSection(el) {
595
+ return el.matches("header, nav, footer, aside");
596
+ }
597
+ function titleCaseSectionId(id) {
598
+ return id.split("-").filter(Boolean).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
599
+ }
600
+ function parseSectionsFromRoot(root) {
601
+ const seen = /* @__PURE__ */ new Set();
602
+ const sections = [];
603
+ for (const el of root.querySelectorAll("[data-ohw-section]")) {
604
+ const id = el.getAttribute("data-ohw-section") ?? "";
605
+ if (!id || seen.has(id) || LINK_PICKER_EXCLUDED_IDS.has(id)) continue;
606
+ if (el.parentElement?.closest("[data-ohw-section]")) continue;
607
+ if (el.hasAttribute("data-ohw-ai-template-hidden") || el.hasAttribute("data-ohw-ai-removed") || el.hasAttribute("data-ohw-ai-replaced-by"))
608
+ continue;
609
+ seen.add(id);
610
+ const label = el.getAttribute("data-ohw-section-label") ?? titleCaseSectionId(id);
611
+ sections.push({ id, label });
612
+ }
613
+ return sections;
614
+ }
615
+ function collectSectionsFromDom() {
616
+ if (typeof document === "undefined") return [];
617
+ return parseSectionsFromRoot(document);
618
+ }
619
+ function parseSectionsFromHtml(html) {
620
+ const doc = new DOMParser().parseFromString(html, "text/html");
621
+ return parseSectionsFromRoot(doc);
622
+ }
623
+
624
+ // src/lib/section-instances.ts
625
+ var SECTION_ORDER_KEY = "__ohw_section_order";
626
+ var REMOVED_ATTR = "data-ohw-section-removed";
627
+ function isRemovedSection(el) {
628
+ return el.hasAttribute(REMOVED_ATTR);
629
+ }
630
+ function movableUnit(el) {
631
+ return el.closest("[data-ohw-section-container]") ?? el;
632
+ }
633
+ function sectionTypeOf(el) {
634
+ return el.getAttribute("data-ohw-section") ?? el.querySelector("[data-ohw-section]")?.getAttribute("data-ohw-section") ?? "";
635
+ }
636
+ function sectionElementOf(el) {
637
+ return el.hasAttribute("data-ohw-section") ? el : el.querySelector("[data-ohw-section]") ?? el;
638
+ }
639
+ function collectTopLevelUnits(predicate) {
640
+ const seen = /* @__PURE__ */ new Set();
641
+ const result = [];
642
+ document.querySelectorAll("[data-ohw-section]").forEach((el) => {
643
+ if (!predicate(el)) return;
644
+ const unit = movableUnit(el);
645
+ if (unit.parentElement?.closest("[data-ohw-section],[data-ohw-section-container]")) return;
646
+ if (seen.has(unit)) return;
647
+ seen.add(unit);
648
+ result.push(unit);
649
+ });
650
+ return result;
651
+ }
652
+ function topLevelSections() {
653
+ return collectTopLevelUnits((el) => !isChromeSection(el) && !isRemovedSection(movableUnit(el)));
654
+ }
655
+ function instanceIdOf(el) {
656
+ return el.dataset.ohwInstance ?? el.dataset.ohwSection ?? "";
657
+ }
658
+ function findByInstanceId(instanceId) {
659
+ const escapedId = CSS.escape(instanceId);
660
+ const direct = document.querySelector(`[data-ohw-instance="${escapedId}"]`);
661
+ if (direct) return movableUnit(direct);
662
+ const bare = document.querySelector(`[data-ohw-section="${escapedId}"]:not([data-ohw-instance])`);
663
+ return bare ? movableUnit(bare) : null;
664
+ }
665
+ function planSectionMove(instanceId, targetIndex, currentPath) {
666
+ const sections = topLevelSections();
667
+ const index = sections.findIndex((el) => instanceIdOf(el) === instanceId);
668
+ if (index === -1) return null;
669
+ const dragged = sections[index];
670
+ const others = sections.filter((_, i) => i !== index);
671
+ const clamped = Math.max(0, Math.min(targetIndex, others.length));
672
+ const reordered = [...others.slice(0, clamped), dragged, ...others.slice(clamped)];
673
+ return reordered.map((el, order) => ({
674
+ instanceId: instanceIdOf(el),
675
+ type: sectionTypeOf(el),
676
+ order,
677
+ pagePath: currentPath
678
+ }));
679
+ }
680
+ function moveSectionInstance(instanceId, direction, currentPath) {
681
+ const sections = topLevelSections();
682
+ const index = sections.findIndex((el) => instanceIdOf(el) === instanceId);
683
+ if (index === -1) return null;
684
+ const siblingIndex = direction === "up" ? index - 1 : index + 1;
685
+ if (siblingIndex < 0 || siblingIndex >= sections.length) return null;
686
+ const entries = planSectionMove(instanceId, siblingIndex, currentPath);
687
+ if (!entries) return null;
688
+ applyPersistedOrder(entries);
689
+ return entries;
690
+ }
691
+ function syncRemovedFlags(entries) {
692
+ const removedIds = new Set(entries.filter((e) => e.removed).map((e) => e.instanceId));
693
+ document.querySelectorAll(`[${REMOVED_ATTR}]`).forEach((el) => {
694
+ if (!removedIds.has(instanceIdOf(el))) {
695
+ el.style.removeProperty("display");
696
+ el.removeAttribute(REMOVED_ATTR);
697
+ }
698
+ });
699
+ for (const id of removedIds) {
700
+ const el = findByInstanceId(id);
701
+ if (el) {
702
+ el.style.display = "none";
703
+ el.setAttribute(REMOVED_ATTR, "");
704
+ }
705
+ }
706
+ }
707
+ function applyPersistedOrder(entries) {
708
+ syncRemovedFlags(entries);
709
+ if (entries.length === 0) return;
710
+ const sections = topLevelSections();
711
+ if (sections.length === 0) return;
712
+ const orderIndex = new Map(entries.map((e) => [e.instanceId, e.order]));
713
+ const ordered = [...sections].sort((a, b) => {
714
+ const aOrder = orderIndex.get(instanceIdOf(a));
715
+ const bOrder = orderIndex.get(instanceIdOf(b));
716
+ if (aOrder === void 0 && bOrder === void 0) return 0;
717
+ if (aOrder === void 0) return 1;
718
+ if (bOrder === void 0) return -1;
719
+ return aOrder - bOrder;
720
+ });
721
+ let prev = null;
722
+ for (const el of ordered) {
723
+ if (prev) prev.after(el);
724
+ prev = el;
725
+ }
726
+ }
727
+ function setSectionRemoved(instanceId, currentPath, existingEntries, removed) {
728
+ if (!findByInstanceId(instanceId)) return null;
729
+ const byId = new Map(existingEntries.map((e) => [e.instanceId, e]));
730
+ const allSections = collectTopLevelUnits((el) => !isChromeSection(el));
731
+ allSections.forEach((el, order) => {
732
+ const id = instanceIdOf(el);
733
+ if (!byId.has(id)) {
734
+ byId.set(id, { instanceId: id, type: sectionTypeOf(el), order, pagePath: currentPath });
735
+ }
736
+ });
737
+ const target = byId.get(instanceId);
738
+ if (!target) return null;
739
+ byId.set(instanceId, { ...target, removed });
740
+ const entries = Array.from(byId.values());
741
+ applyPersistedOrder(entries);
742
+ return entries;
743
+ }
744
+ function deleteSectionInstance(instanceId, currentPath, existingEntries) {
745
+ return setSectionRemoved(instanceId, currentPath, existingEntries, true);
746
+ }
747
+ function restoreSectionInstance(instanceId, currentPath, existingEntries) {
748
+ return setSectionRemoved(instanceId, currentPath, existingEntries, false);
749
+ }
750
+ function duplicateSectionInstance(instanceId, newId, currentPath, existingEntries) {
751
+ const original = findByInstanceId(instanceId);
752
+ if (!original) return null;
753
+ const clone = original.cloneNode(true);
754
+ clone.setAttribute("data-ohw-instance", newId);
755
+ const keyRekeys = rekeySectionSubtree(clone, newId);
756
+ original.insertAdjacentElement("afterend", clone);
757
+ const byId = new Map(existingEntries.map((e) => [e.instanceId, e]));
758
+ const entries = topLevelSections().map((el, order) => {
759
+ const id = instanceIdOf(el);
760
+ return {
761
+ instanceId: id,
762
+ type: sectionTypeOf(el),
763
+ order,
764
+ pagePath: currentPath,
765
+ ...byId.get(id)?.removed ? { removed: true } : {}
766
+ };
767
+ });
768
+ applyPersistedOrder(entries);
769
+ return { entries, keyRekeys };
770
+ }
771
+ function newInstanceId() {
772
+ return typeof crypto !== "undefined" && "randomUUID" in crypto ? crypto.randomUUID() : `instance-${Date.now()}-${Math.random().toString(36).slice(2)}`;
773
+ }
774
+ function getPageSectionOrderEntries(raw, currentPath) {
775
+ if (!raw) return [];
776
+ try {
777
+ const entries = JSON.parse(raw);
778
+ return entries.filter((e) => !e.pagePath || e.pagePath === currentPath);
779
+ } catch {
780
+ return [];
781
+ }
782
+ }
783
+ function mergePageSectionOrder(raw, currentPath, pageEntries) {
784
+ let all = [];
785
+ if (raw) {
786
+ try {
787
+ const parsed = JSON.parse(raw);
788
+ if (Array.isArray(parsed)) all = parsed;
789
+ } catch {
790
+ }
791
+ }
792
+ const otherPages = all.filter((e) => e && e.pagePath && e.pagePath !== currentPath);
793
+ const pageIds = new Set(pageEntries.map((e) => e.instanceId));
794
+ const removedHere = all.filter(
795
+ (e) => e && (!e.pagePath || e.pagePath === currentPath) && e.removed && !pageIds.has(e.instanceId)
796
+ );
797
+ return [...otherPages, ...removedHere, ...pageEntries];
798
+ }
799
+ function rekeySectionSubtree(root, instanceId) {
800
+ const suffix = `::${instanceId}`;
801
+ const pairs = [];
802
+ const rekey = (el, attr) => {
803
+ const current = el.getAttribute(attr);
804
+ if (!current) return;
805
+ const base = current.includes("::") ? current.slice(0, current.indexOf("::")) : current;
806
+ const next = `${base}${suffix}`;
807
+ el.setAttribute(attr, next);
808
+ pairs.push({ from: current, to: next });
809
+ };
810
+ if (root.hasAttribute("data-ohw-key")) rekey(root, "data-ohw-key");
811
+ if (root.hasAttribute("data-ohw-href-key")) rekey(root, "data-ohw-href-key");
812
+ root.querySelectorAll("[data-ohw-key]").forEach((el) => rekey(el, "data-ohw-key"));
813
+ root.querySelectorAll("[data-ohw-href-key]").forEach((el) => rekey(el, "data-ohw-href-key"));
814
+ return pairs;
815
+ }
816
+ function initSectionInstancesFromContent(content, currentPath) {
817
+ document.querySelectorAll("[data-ohw-section]:not([data-ohw-instance])").forEach((el) => {
818
+ el.setAttribute("data-ohw-instance", el.getAttribute("data-ohw-section") ?? "");
819
+ });
820
+ document.querySelectorAll("[data-ohw-section-container]:not([data-ohw-instance])").forEach((el) => {
821
+ const type = sectionTypeOf(el);
822
+ if (type) el.setAttribute("data-ohw-instance", type);
823
+ });
824
+ const entries = getPageSectionOrderEntries(content[SECTION_ORDER_KEY], currentPath);
825
+ for (const entry of entries) {
826
+ if (entry.instanceId === entry.type) continue;
827
+ if (document.querySelector(`[data-ohw-instance="${CSS.escape(entry.instanceId)}"]`)) continue;
828
+ const original = document.querySelector(
829
+ `[data-ohw-section="${CSS.escape(entry.type)}"][data-ohw-instance="${CSS.escape(entry.type)}"]`
830
+ );
831
+ if (!original) continue;
832
+ const clone = original.cloneNode(true);
833
+ clone.setAttribute("data-ohw-instance", entry.instanceId);
834
+ rekeySectionSubtree(clone, entry.instanceId);
835
+ original.insertAdjacentElement("afterend", clone);
836
+ }
837
+ applyPersistedOrder(entries);
838
+ }
839
+
562
840
  // src/ui/ai-tree/AiTreeRenderer.tsx
563
841
  import React from "react";
564
842
  import { ArrowLeft, ArrowRight, ChevronDown, icons as lucideIcons } from "lucide-react";
@@ -1926,7 +2204,7 @@ function AiTreeRenderer({
1926
2204
  import { jsx as jsx2 } from "react/jsx-runtime";
1927
2205
  var CONTAINER_ATTR = "data-ohw-ai-generated";
1928
2206
  var REPLACED_ATTR = "data-ohw-ai-replaced-by";
1929
- var REMOVED_ATTR = "data-ohw-ai-removed";
2207
+ var REMOVED_ATTR2 = "data-ohw-ai-removed";
1930
2208
  var TEMPLATE_HIDDEN_ATTR = "data-ohw-ai-template-hidden";
1931
2209
  var CHROME_SECTION_IDS = /* @__PURE__ */ new Set(["navbar", "footer"]);
1932
2210
  function readRootVar(name) {
@@ -2021,18 +2299,18 @@ function placeContainer(container, entry) {
2021
2299
  }
2022
2300
  function syncRemovedSections(state) {
2023
2301
  const removed = new Set(state.removed ?? []);
2024
- for (const el of document.querySelectorAll(`[${REMOVED_ATTR}]`)) {
2302
+ for (const el of document.querySelectorAll(`[${REMOVED_ATTR2}]`)) {
2025
2303
  const id = el.getAttribute("data-ohw-section") ?? "";
2026
2304
  if (!removed.has(id)) {
2027
2305
  el.style.removeProperty("display");
2028
- el.removeAttribute(REMOVED_ATTR);
2306
+ el.removeAttribute(REMOVED_ATTR2);
2029
2307
  }
2030
2308
  }
2031
2309
  for (const id of removed) {
2032
2310
  const section = findTemplateSection(id);
2033
2311
  if (section && !section.hasAttribute(REPLACED_ATTR)) {
2034
2312
  section.style.display = "none";
2035
- section.setAttribute(REMOVED_ATTR, "");
2313
+ section.setAttribute(REMOVED_ATTR2, "");
2036
2314
  }
2037
2315
  }
2038
2316
  }
@@ -2049,7 +2327,7 @@ function syncTemplateHidden(state, pageHasSections) {
2049
2327
  if (el.hasAttribute(CONTAINER_ATTR)) continue;
2050
2328
  if (CHROME_SECTION_IDS.has(el.getAttribute("data-ohw-section") ?? "")) continue;
2051
2329
  if (el.parentElement?.closest("[data-ohw-section]")) continue;
2052
- if (el.hasAttribute(REPLACED_ATTR) || el.hasAttribute(REMOVED_ATTR)) continue;
2330
+ if (el.hasAttribute(REPLACED_ATTR) || el.hasAttribute(REMOVED_ATTR2)) continue;
2053
2331
  el.style.display = "none";
2054
2332
  el.setAttribute(TEMPLATE_HIDDEN_ATTR, "");
2055
2333
  }
@@ -2073,18 +2351,23 @@ function syncReplacedOriginals(state) {
2073
2351
  }
2074
2352
  }
2075
2353
  var sectionOrderIndex = /* @__PURE__ */ new Map();
2354
+ var removedSectionIds = /* @__PURE__ */ new Set();
2076
2355
  function setAiSectionOrder(raw, currentPath) {
2077
2356
  const next = /* @__PURE__ */ new Map();
2357
+ const removed = /* @__PURE__ */ new Set();
2078
2358
  if (raw) {
2079
2359
  try {
2080
2360
  const entries = JSON.parse(raw);
2081
2361
  for (const entry of entries) {
2082
- if (!entry.pagePath || entry.pagePath === currentPath) next.set(entry.instanceId, entry.order);
2362
+ if (entry.pagePath && entry.pagePath !== currentPath) continue;
2363
+ next.set(entry.instanceId, entry.order);
2364
+ if (entry.removed) removed.add(entry.instanceId);
2083
2365
  }
2084
2366
  } catch {
2085
2367
  }
2086
2368
  }
2087
2369
  sectionOrderIndex = next;
2370
+ removedSectionIds = removed;
2088
2371
  }
2089
2372
  function applyExplicitOrder(entries) {
2090
2373
  if (sectionOrderIndex.size === 0) return entries;
@@ -2120,6 +2403,18 @@ function orderByChain(sections) {
2120
2403
  for (const root of roots) visit(root);
2121
2404
  return out.length === sections.length ? out : sections;
2122
2405
  }
2406
+ function syncSoftRemovedGenerated() {
2407
+ for (const [id, section] of mounted) {
2408
+ const el = section.container;
2409
+ if (removedSectionIds.has(id)) {
2410
+ el.style.display = "none";
2411
+ el.setAttribute(REMOVED_ATTR, "");
2412
+ } else if (el.hasAttribute(REMOVED_ATTR)) {
2413
+ el.style.removeProperty("display");
2414
+ el.removeAttribute(REMOVED_ATTR);
2415
+ }
2416
+ }
2417
+ }
2123
2418
  function applyAiSectionsToDom(state, options) {
2124
2419
  if (typeof document === "undefined") return;
2125
2420
  const brandOverride = deriveBrandOverride();
@@ -2187,6 +2482,7 @@ function applyAiSectionsToDom(state, options) {
2187
2482
  syncReplacedOriginals(state);
2188
2483
  syncRemovedSections(state);
2189
2484
  syncTemplateHidden(state, pageSections.length > 0);
2485
+ syncSoftRemovedGenerated();
2190
2486
  }
2191
2487
  function unmountAllAiSections() {
2192
2488
  for (const [, section] of mounted) {
@@ -2199,9 +2495,9 @@ function unmountAllAiSections() {
2199
2495
  el.style.removeProperty("display");
2200
2496
  el.removeAttribute(REPLACED_ATTR);
2201
2497
  }
2202
- for (const el of document.querySelectorAll(`[${REMOVED_ATTR}]`)) {
2498
+ for (const el of document.querySelectorAll(`[${REMOVED_ATTR2}]`)) {
2203
2499
  el.style.removeProperty("display");
2204
- el.removeAttribute(REMOVED_ATTR);
2500
+ el.removeAttribute(REMOVED_ATTR2);
2205
2501
  }
2206
2502
  }
2207
2503
 
@@ -8162,240 +8458,6 @@ function CarouselOverlay({
8162
8458
  // src/ui/ai-section/AiSectionOverlay.tsx
8163
8459
  import { useCallback as useCallback2, useEffect as useEffect4, useRef as useRef4, useState as useState5 } from "react";
8164
8460
  import { Check, X } from "lucide-react";
8165
-
8166
- // src/lib/sections.ts
8167
- var LINK_PICKER_EXCLUDED_IDS = /* @__PURE__ */ new Set(["navbar", "footer"]);
8168
- function isChromeSection(el) {
8169
- return el.matches("header, nav, footer, aside");
8170
- }
8171
- function titleCaseSectionId(id) {
8172
- return id.split("-").filter(Boolean).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
8173
- }
8174
- function parseSectionsFromRoot(root) {
8175
- const seen = /* @__PURE__ */ new Set();
8176
- const sections = [];
8177
- for (const el of root.querySelectorAll("[data-ohw-section]")) {
8178
- const id = el.getAttribute("data-ohw-section") ?? "";
8179
- if (!id || seen.has(id) || LINK_PICKER_EXCLUDED_IDS.has(id)) continue;
8180
- if (el.parentElement?.closest("[data-ohw-section]")) continue;
8181
- if (el.hasAttribute("data-ohw-ai-template-hidden") || el.hasAttribute("data-ohw-ai-removed") || el.hasAttribute("data-ohw-ai-replaced-by"))
8182
- continue;
8183
- seen.add(id);
8184
- const label = el.getAttribute("data-ohw-section-label") ?? titleCaseSectionId(id);
8185
- sections.push({ id, label });
8186
- }
8187
- return sections;
8188
- }
8189
- function collectSectionsFromDom() {
8190
- if (typeof document === "undefined") return [];
8191
- return parseSectionsFromRoot(document);
8192
- }
8193
- function parseSectionsFromHtml(html) {
8194
- const doc = new DOMParser().parseFromString(html, "text/html");
8195
- return parseSectionsFromRoot(doc);
8196
- }
8197
-
8198
- // src/lib/section-instances.ts
8199
- var SECTION_ORDER_KEY = "__ohw_section_order";
8200
- var REMOVED_ATTR2 = "data-ohw-section-removed";
8201
- function isRemovedSection(el) {
8202
- return el.hasAttribute(REMOVED_ATTR2);
8203
- }
8204
- function movableUnit(el) {
8205
- return el.closest("[data-ohw-section-container]") ?? el;
8206
- }
8207
- function sectionTypeOf(el) {
8208
- return el.getAttribute("data-ohw-section") ?? el.querySelector("[data-ohw-section]")?.getAttribute("data-ohw-section") ?? "";
8209
- }
8210
- function sectionElementOf(el) {
8211
- return el.hasAttribute("data-ohw-section") ? el : el.querySelector("[data-ohw-section]") ?? el;
8212
- }
8213
- function collectTopLevelUnits(predicate) {
8214
- const seen = /* @__PURE__ */ new Set();
8215
- const result = [];
8216
- document.querySelectorAll("[data-ohw-section]").forEach((el) => {
8217
- if (!predicate(el)) return;
8218
- const unit = movableUnit(el);
8219
- if (unit.parentElement?.closest("[data-ohw-section],[data-ohw-section-container]")) return;
8220
- if (seen.has(unit)) return;
8221
- seen.add(unit);
8222
- result.push(unit);
8223
- });
8224
- return result;
8225
- }
8226
- function topLevelSections() {
8227
- return collectTopLevelUnits((el) => !isChromeSection(el) && !isRemovedSection(movableUnit(el)));
8228
- }
8229
- function instanceIdOf(el) {
8230
- return el.dataset.ohwInstance ?? el.dataset.ohwSection ?? "";
8231
- }
8232
- function findByInstanceId(instanceId) {
8233
- const escapedId = CSS.escape(instanceId);
8234
- const direct = document.querySelector(`[data-ohw-instance="${escapedId}"]`);
8235
- if (direct) return movableUnit(direct);
8236
- const bare = document.querySelector(`[data-ohw-section="${escapedId}"]:not([data-ohw-instance])`);
8237
- return bare ? movableUnit(bare) : null;
8238
- }
8239
- function planSectionMove(instanceId, targetIndex, currentPath) {
8240
- const sections = topLevelSections();
8241
- const index = sections.findIndex((el) => instanceIdOf(el) === instanceId);
8242
- if (index === -1) return null;
8243
- const dragged = sections[index];
8244
- const others = sections.filter((_, i) => i !== index);
8245
- const clamped = Math.max(0, Math.min(targetIndex, others.length));
8246
- const reordered = [...others.slice(0, clamped), dragged, ...others.slice(clamped)];
8247
- return reordered.map((el, order) => ({
8248
- instanceId: instanceIdOf(el),
8249
- type: sectionTypeOf(el),
8250
- order,
8251
- pagePath: currentPath
8252
- }));
8253
- }
8254
- function moveSectionInstance(instanceId, direction, currentPath) {
8255
- const sections = topLevelSections();
8256
- const index = sections.findIndex((el) => instanceIdOf(el) === instanceId);
8257
- if (index === -1) return null;
8258
- const siblingIndex = direction === "up" ? index - 1 : index + 1;
8259
- if (siblingIndex < 0 || siblingIndex >= sections.length) return null;
8260
- const entries = planSectionMove(instanceId, siblingIndex, currentPath);
8261
- if (!entries) return null;
8262
- applyPersistedOrder(entries);
8263
- return entries;
8264
- }
8265
- function syncRemovedFlags(entries) {
8266
- const removedIds = new Set(entries.filter((e) => e.removed).map((e) => e.instanceId));
8267
- document.querySelectorAll(`[${REMOVED_ATTR2}]`).forEach((el) => {
8268
- if (!removedIds.has(instanceIdOf(el))) {
8269
- el.style.removeProperty("display");
8270
- el.removeAttribute(REMOVED_ATTR2);
8271
- }
8272
- });
8273
- for (const id of removedIds) {
8274
- const el = findByInstanceId(id);
8275
- if (el) {
8276
- el.style.display = "none";
8277
- el.setAttribute(REMOVED_ATTR2, "");
8278
- }
8279
- }
8280
- }
8281
- function applyPersistedOrder(entries) {
8282
- syncRemovedFlags(entries);
8283
- if (entries.length === 0) return;
8284
- const sections = topLevelSections();
8285
- if (sections.length === 0) return;
8286
- const orderIndex = new Map(entries.map((e) => [e.instanceId, e.order]));
8287
- const ordered = [...sections].sort((a, b) => {
8288
- const aOrder = orderIndex.get(instanceIdOf(a));
8289
- const bOrder = orderIndex.get(instanceIdOf(b));
8290
- if (aOrder === void 0 && bOrder === void 0) return 0;
8291
- if (aOrder === void 0) return 1;
8292
- if (bOrder === void 0) return -1;
8293
- return aOrder - bOrder;
8294
- });
8295
- let prev = null;
8296
- for (const el of ordered) {
8297
- if (prev) prev.after(el);
8298
- prev = el;
8299
- }
8300
- }
8301
- function setSectionRemoved(instanceId, currentPath, existingEntries, removed) {
8302
- if (!findByInstanceId(instanceId)) return null;
8303
- const byId = new Map(existingEntries.map((e) => [e.instanceId, e]));
8304
- const allSections = collectTopLevelUnits((el) => !isChromeSection(el));
8305
- allSections.forEach((el, order) => {
8306
- const id = instanceIdOf(el);
8307
- if (!byId.has(id)) {
8308
- byId.set(id, { instanceId: id, type: sectionTypeOf(el), order, pagePath: currentPath });
8309
- }
8310
- });
8311
- const target = byId.get(instanceId);
8312
- if (!target) return null;
8313
- byId.set(instanceId, { ...target, removed });
8314
- const entries = Array.from(byId.values());
8315
- applyPersistedOrder(entries);
8316
- return entries;
8317
- }
8318
- function deleteSectionInstance(instanceId, currentPath, existingEntries) {
8319
- return setSectionRemoved(instanceId, currentPath, existingEntries, true);
8320
- }
8321
- function restoreSectionInstance(instanceId, currentPath, existingEntries) {
8322
- return setSectionRemoved(instanceId, currentPath, existingEntries, false);
8323
- }
8324
- function duplicateSectionInstance(instanceId, newId, currentPath, existingEntries) {
8325
- const original = findByInstanceId(instanceId);
8326
- if (!original) return null;
8327
- const clone = original.cloneNode(true);
8328
- clone.setAttribute("data-ohw-instance", newId);
8329
- const keyRekeys = rekeySectionSubtree(clone, newId);
8330
- original.insertAdjacentElement("afterend", clone);
8331
- const byId = new Map(existingEntries.map((e) => [e.instanceId, e]));
8332
- const entries = topLevelSections().map((el, order) => {
8333
- const id = instanceIdOf(el);
8334
- return {
8335
- instanceId: id,
8336
- type: sectionTypeOf(el),
8337
- order,
8338
- pagePath: currentPath,
8339
- ...byId.get(id)?.removed ? { removed: true } : {}
8340
- };
8341
- });
8342
- applyPersistedOrder(entries);
8343
- return { entries, keyRekeys };
8344
- }
8345
- function newInstanceId() {
8346
- return typeof crypto !== "undefined" && "randomUUID" in crypto ? crypto.randomUUID() : `instance-${Date.now()}-${Math.random().toString(36).slice(2)}`;
8347
- }
8348
- function getPageSectionOrderEntries(raw, currentPath) {
8349
- if (!raw) return [];
8350
- try {
8351
- const entries = JSON.parse(raw);
8352
- return entries.filter((e) => !e.pagePath || e.pagePath === currentPath);
8353
- } catch {
8354
- return [];
8355
- }
8356
- }
8357
- function rekeySectionSubtree(root, instanceId) {
8358
- const suffix = `::${instanceId}`;
8359
- const pairs = [];
8360
- const rekey = (el, attr) => {
8361
- const current = el.getAttribute(attr);
8362
- if (!current) return;
8363
- const base = current.includes("::") ? current.slice(0, current.indexOf("::")) : current;
8364
- const next = `${base}${suffix}`;
8365
- el.setAttribute(attr, next);
8366
- pairs.push({ from: current, to: next });
8367
- };
8368
- if (root.hasAttribute("data-ohw-key")) rekey(root, "data-ohw-key");
8369
- if (root.hasAttribute("data-ohw-href-key")) rekey(root, "data-ohw-href-key");
8370
- root.querySelectorAll("[data-ohw-key]").forEach((el) => rekey(el, "data-ohw-key"));
8371
- root.querySelectorAll("[data-ohw-href-key]").forEach((el) => rekey(el, "data-ohw-href-key"));
8372
- return pairs;
8373
- }
8374
- function initSectionInstancesFromContent(content, currentPath) {
8375
- document.querySelectorAll("[data-ohw-section]:not([data-ohw-instance])").forEach((el) => {
8376
- el.setAttribute("data-ohw-instance", el.getAttribute("data-ohw-section") ?? "");
8377
- });
8378
- document.querySelectorAll("[data-ohw-section-container]:not([data-ohw-instance])").forEach((el) => {
8379
- const type = sectionTypeOf(el);
8380
- if (type) el.setAttribute("data-ohw-instance", type);
8381
- });
8382
- const entries = getPageSectionOrderEntries(content[SECTION_ORDER_KEY], currentPath);
8383
- for (const entry of entries) {
8384
- if (entry.instanceId === entry.type) continue;
8385
- if (document.querySelector(`[data-ohw-instance="${CSS.escape(entry.instanceId)}"]`)) continue;
8386
- const original = document.querySelector(
8387
- `[data-ohw-section="${CSS.escape(entry.type)}"][data-ohw-instance="${CSS.escape(entry.type)}"]`
8388
- );
8389
- if (!original) continue;
8390
- const clone = original.cloneNode(true);
8391
- clone.setAttribute("data-ohw-instance", entry.instanceId);
8392
- rekeySectionSubtree(clone, entry.instanceId);
8393
- original.insertAdjacentElement("afterend", clone);
8394
- }
8395
- applyPersistedOrder(entries);
8396
- }
8397
-
8398
- // src/ui/ai-section/AiSectionOverlay.tsx
8399
8461
  import { Fragment as Fragment5, jsx as jsx17, jsxs as jsxs9 } from "react/jsx-runtime";
8400
8462
  var findSectionElement = findByInstanceId;
8401
8463
  function readRect(instanceId) {
@@ -14184,15 +14246,17 @@ function useSectionDrag({
14184
14246
  clearSectionDragVisuals();
14185
14247
  return;
14186
14248
  }
14187
- const orderJson = JSON.stringify(entries);
14249
+ const orderJson = JSON.stringify(
14250
+ mergePageSectionOrder(editContentRef.current[SECTION_ORDER_KEY], window.location.pathname, entries)
14251
+ );
14188
14252
  editContentRef.current = { ...editContentRef.current, [SECTION_ORDER_KEY]: orderJson };
14189
14253
  setAiSectionOrder(orderJson, window.location.pathname);
14190
14254
  postToParentRef.current({ type: "ow:change", nodes: [{ key: SECTION_ORDER_KEY, text: orderJson }] });
14191
- applyPersistedOrder(entries);
14255
+ applyPersistedOrder(getPageSectionOrderEntries(orderJson, window.location.pathname));
14192
14256
  clearSectionDragVisuals();
14193
14257
  requestAnimationFrame(() => {
14194
14258
  if (editContentRef.current[SECTION_ORDER_KEY] === orderJson) {
14195
- applyPersistedOrder(entries);
14259
+ applyPersistedOrder(getPageSectionOrderEntries(orderJson, window.location.pathname));
14196
14260
  }
14197
14261
  requestAnimationFrame(() => {
14198
14262
  window.dispatchEvent(new Event("resize"));
@@ -20217,6 +20281,44 @@ function OhhwellsBridge() {
20217
20281
  if (h > 50) postToParentRef.current({ type: "ow:height", height: h });
20218
20282
  }, 400));
20219
20283
  };
20284
+ const reapCommittedAiSections = (excludeIds) => {
20285
+ const aiState = parseAiSectionsState(aiSectionsRef.current);
20286
+ if (aiState.sections.length === 0) return [];
20287
+ let orderEntries = [];
20288
+ try {
20289
+ const parsed = JSON.parse(editContentRef.current[SECTION_ORDER_KEY] ?? "[]");
20290
+ if (Array.isArray(parsed)) orderEntries = parsed;
20291
+ } catch {
20292
+ return [];
20293
+ }
20294
+ const removedIds = orderEntries.filter((entry) => entry && typeof entry.instanceId === "string" && entry.removed).map((entry) => entry.instanceId);
20295
+ if (removedIds.length === 0) return [];
20296
+ const result = reapRemovedAiSections(aiState, parseStyleStore(stylesRef.current), removedIds, excludeIds);
20297
+ if (!result.changed) return [];
20298
+ const nodes = [];
20299
+ aiSectionsRef.current = serializeAiSectionsState(result.state);
20300
+ nodes.push({ key: AI_SECTIONS_KEY, text: aiSectionsRef.current });
20301
+ const reaped = new Set(result.reapedIds);
20302
+ const nextOrderJson = JSON.stringify(orderEntries.filter((entry) => !reaped.has(entry.instanceId)));
20303
+ editContentRef.current = { ...editContentRef.current, [SECTION_ORDER_KEY]: nextOrderJson };
20304
+ setAiSectionOrder(nextOrderJson, window.location.pathname);
20305
+ nodes.push({ key: SECTION_ORDER_KEY, text: nextOrderJson });
20306
+ if (result.store) {
20307
+ stylesRef.current = JSON.stringify(result.store);
20308
+ nodes.push({ key: STYLE_STORE_KEY, text: stylesRef.current });
20309
+ }
20310
+ const nextContent = { ...editContentRef.current };
20311
+ for (const key of Object.keys(nextContent)) {
20312
+ if (key.startsWith(AI_SLOT_KEY_PREFIX) && result.slotPrefixes.some((prefix) => key.startsWith(prefix)) && nextContent[key] !== "") {
20313
+ nextContent[key] = "";
20314
+ nodes.push({ key, text: "" });
20315
+ }
20316
+ }
20317
+ editContentRef.current = nextContent;
20318
+ applyAiSectionsToDom(result.state);
20319
+ applyStylesToDom(parseStyleStore(stylesRef.current));
20320
+ return nodes;
20321
+ };
20220
20322
  const handleHydrate = (e) => {
20221
20323
  if (e.data?.type !== "ow:hydrate") return;
20222
20324
  const content = e.data.content;
@@ -20285,6 +20387,11 @@ function OhhwellsBridge() {
20285
20387
  reconcileFooterOrderFromContent(editContentRef.current);
20286
20388
  syncNavigationDragCursorAttrs();
20287
20389
  enforceLinkHrefs();
20390
+ const hydrateReapExclude = /* @__PURE__ */ new Set();
20391
+ const hydratePendingUndo = pendingDeleteUndoRef.current;
20392
+ if (hydratePendingUndo?.sectionInstanceId) hydrateReapExclude.add(hydratePendingUndo.sectionInstanceId);
20393
+ const reapNodes = reapCommittedAiSections(hydrateReapExclude);
20394
+ if (reapNodes.length > 0) postToParentRef.current({ type: "ow:change", nodes: reapNodes });
20288
20395
  const hydratedHeight = document.body.scrollHeight;
20289
20396
  if (hydratedHeight > 50) postToParentRef.current({ type: "ow:height", height: hydratedHeight });
20290
20397
  postToParentRef.current({ type: "ow:hydrate-done" });
@@ -20469,8 +20576,11 @@ function OhhwellsBridge() {
20469
20576
  if (!instanceId || !direction) return;
20470
20577
  const entries = moveSectionInstance(instanceId, direction, window.location.pathname);
20471
20578
  if (!entries) return;
20472
- const orderJson = JSON.stringify(entries);
20579
+ const orderJson = JSON.stringify(
20580
+ mergePageSectionOrder(editContentRef.current[SECTION_ORDER_KEY], window.location.pathname, entries)
20581
+ );
20473
20582
  editContentRef.current = { ...editContentRef.current, [SECTION_ORDER_KEY]: orderJson };
20583
+ applyPersistedOrder(getPageSectionOrderEntries(orderJson, window.location.pathname));
20474
20584
  setAiSectionOrder(orderJson, window.location.pathname);
20475
20585
  postToParentRef.current({ type: "ow:change", nodes: [{ key: SECTION_ORDER_KEY, text: orderJson }] });
20476
20586
  window.dispatchEvent(new Event("resize"));
@@ -20516,8 +20626,11 @@ function OhhwellsBridge() {
20516
20626
  const currentEntries = getPageSectionOrderEntries(editContentRef.current[SECTION_ORDER_KEY], window.location.pathname);
20517
20627
  const entries = deleteSectionInstance(instanceId, window.location.pathname, currentEntries);
20518
20628
  if (!entries) return;
20519
- const orderJson = JSON.stringify(entries);
20629
+ const orderJson = JSON.stringify(
20630
+ mergePageSectionOrder(editContentRef.current[SECTION_ORDER_KEY], window.location.pathname, entries)
20631
+ );
20520
20632
  editContentRef.current = { ...editContentRef.current, [SECTION_ORDER_KEY]: orderJson };
20633
+ setAiSectionOrder(orderJson, window.location.pathname);
20521
20634
  postToParentRef.current({ type: "ow:change", nodes: [{ key: SECTION_ORDER_KEY, text: orderJson }] });
20522
20635
  aiSectionApiRef.current?.clear();
20523
20636
  window.dispatchEvent(new Event("resize"));
@@ -20526,6 +20639,7 @@ function OhhwellsBridge() {
20526
20639
  const actionId = newInstanceId();
20527
20640
  pendingDeleteUndoRef.current = {
20528
20641
  actionId,
20642
+ sectionInstanceId: instanceId,
20529
20643
  restore: () => {
20530
20644
  const restoredEntries = getPageSectionOrderEntries(
20531
20645
  editContentRef.current[SECTION_ORDER_KEY],
@@ -20533,8 +20647,11 @@ function OhhwellsBridge() {
20533
20647
  );
20534
20648
  const restored = restoreSectionInstance(instanceId, window.location.pathname, restoredEntries);
20535
20649
  if (!restored) return;
20536
- const restoredJson = JSON.stringify(restored);
20650
+ const restoredJson = JSON.stringify(
20651
+ mergePageSectionOrder(editContentRef.current[SECTION_ORDER_KEY], window.location.pathname, restored)
20652
+ );
20537
20653
  editContentRef.current = { ...editContentRef.current, [SECTION_ORDER_KEY]: restoredJson };
20654
+ setAiSectionOrder(restoredJson, window.location.pathname);
20538
20655
  postToParentRef.current({ type: "ow:change", nodes: [{ key: SECTION_ORDER_KEY, text: restoredJson }] });
20539
20656
  window.dispatchEvent(new Event("resize"));
20540
20657
  const restoreHeight = document.body.scrollHeight;
@@ -20560,7 +20677,9 @@ function OhhwellsBridge() {
20560
20677
  const result = duplicateSectionInstance(instanceId, newId, window.location.pathname, currentEntries);
20561
20678
  if (!result) return;
20562
20679
  const { entries, keyRekeys } = result;
20563
- const orderJson = JSON.stringify(entries);
20680
+ const orderJson = JSON.stringify(
20681
+ mergePageSectionOrder(editContentRef.current[SECTION_ORDER_KEY], window.location.pathname, entries)
20682
+ );
20564
20683
  const nodes = [{ key: SECTION_ORDER_KEY, text: orderJson }];
20565
20684
  for (const { from, to } of keyRekeys) {
20566
20685
  const inherited = editContentRef.current[from];
@@ -20570,6 +20689,7 @@ function OhhwellsBridge() {
20570
20689
  ...editContentRef.current,
20571
20690
  ...Object.fromEntries(nodes.map(({ key, text }) => [key, text]))
20572
20691
  };
20692
+ applyPersistedOrder(getPageSectionOrderEntries(orderJson, window.location.pathname));
20573
20693
  setAiSectionOrder(orderJson, window.location.pathname);
20574
20694
  postToParentRef.current({ type: "ow:change", nodes });
20575
20695
  window.dispatchEvent(new Event("resize"));
@@ -20833,6 +20953,10 @@ function OhhwellsBridge() {
20833
20953
  };
20834
20954
  const handleSave = (e) => {
20835
20955
  if (e.data?.type !== "ow:save") return;
20956
+ const pendingUndo = pendingDeleteUndoRef.current;
20957
+ const reapExclude = /* @__PURE__ */ new Set();
20958
+ if (pendingUndo?.sectionInstanceId) reapExclude.add(pendingUndo.sectionInstanceId);
20959
+ const reapNodes = reapCommittedAiSections(reapExclude);
20836
20960
  const nodes = collectEditableNodes(editContentRef.current);
20837
20961
  const tracker = document.querySelector("[data-ohw-sections-tracker]");
20838
20962
  if (tracker?.textContent) nodes.push({ key: "__ohw_sections", type: "sections", text: tracker.textContent });
@@ -20852,6 +20976,11 @@ function OhhwellsBridge() {
20852
20976
  const text = specs.length ? JSON.stringify(specs) : editContentRef.current[fieldsKey(formKey)];
20853
20977
  if (text) nodes.push({ key: fieldsKey(formKey), type: "text", text });
20854
20978
  });
20979
+ for (const reapNode of reapNodes) {
20980
+ if (reapNode.key.startsWith(AI_SLOT_KEY_PREFIX) && !nodes.some((n) => n.key === reapNode.key)) {
20981
+ nodes.push({ key: reapNode.key, type: "text", text: reapNode.text });
20982
+ }
20983
+ }
20855
20984
  postToParentRef.current({ type: "ow:save-result", nodes });
20856
20985
  };
20857
20986
  const handleInsertSection = (e) => {
@@ -21473,7 +21602,7 @@ function OhhwellsBridge() {
21473
21602
  postToParent2({
21474
21603
  type: "ow:ready",
21475
21604
  version: "1",
21476
- bridgeVersion: "0.1.93",
21605
+ bridgeVersion: "0.1.94",
21477
21606
  path: pathname,
21478
21607
  nodes: collectEditableNodes(editContentRef.current),
21479
21608
  sections