@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.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");
@@ -1999,7 +2277,7 @@ function AiTreeRenderer({
1999
2277
  var import_jsx_runtime2 = require("react/jsx-runtime");
2000
2278
  var CONTAINER_ATTR = "data-ohw-ai-generated";
2001
2279
  var REPLACED_ATTR = "data-ohw-ai-replaced-by";
2002
- var REMOVED_ATTR = "data-ohw-ai-removed";
2280
+ var REMOVED_ATTR2 = "data-ohw-ai-removed";
2003
2281
  var TEMPLATE_HIDDEN_ATTR = "data-ohw-ai-template-hidden";
2004
2282
  var CHROME_SECTION_IDS = /* @__PURE__ */ new Set(["navbar", "footer"]);
2005
2283
  function readRootVar(name) {
@@ -2094,18 +2372,18 @@ function placeContainer(container, entry) {
2094
2372
  }
2095
2373
  function syncRemovedSections(state) {
2096
2374
  const removed = new Set(state.removed ?? []);
2097
- for (const el of document.querySelectorAll(`[${REMOVED_ATTR}]`)) {
2375
+ for (const el of document.querySelectorAll(`[${REMOVED_ATTR2}]`)) {
2098
2376
  const id = el.getAttribute("data-ohw-section") ?? "";
2099
2377
  if (!removed.has(id)) {
2100
2378
  el.style.removeProperty("display");
2101
- el.removeAttribute(REMOVED_ATTR);
2379
+ el.removeAttribute(REMOVED_ATTR2);
2102
2380
  }
2103
2381
  }
2104
2382
  for (const id of removed) {
2105
2383
  const section = findTemplateSection(id);
2106
2384
  if (section && !section.hasAttribute(REPLACED_ATTR)) {
2107
2385
  section.style.display = "none";
2108
- section.setAttribute(REMOVED_ATTR, "");
2386
+ section.setAttribute(REMOVED_ATTR2, "");
2109
2387
  }
2110
2388
  }
2111
2389
  }
@@ -2122,7 +2400,7 @@ function syncTemplateHidden(state, pageHasSections) {
2122
2400
  if (el.hasAttribute(CONTAINER_ATTR)) continue;
2123
2401
  if (CHROME_SECTION_IDS.has(el.getAttribute("data-ohw-section") ?? "")) continue;
2124
2402
  if (el.parentElement?.closest("[data-ohw-section]")) continue;
2125
- if (el.hasAttribute(REPLACED_ATTR) || el.hasAttribute(REMOVED_ATTR)) continue;
2403
+ if (el.hasAttribute(REPLACED_ATTR) || el.hasAttribute(REMOVED_ATTR2)) continue;
2126
2404
  el.style.display = "none";
2127
2405
  el.setAttribute(TEMPLATE_HIDDEN_ATTR, "");
2128
2406
  }
@@ -2146,18 +2424,23 @@ function syncReplacedOriginals(state) {
2146
2424
  }
2147
2425
  }
2148
2426
  var sectionOrderIndex = /* @__PURE__ */ new Map();
2427
+ var removedSectionIds = /* @__PURE__ */ new Set();
2149
2428
  function setAiSectionOrder(raw, currentPath) {
2150
2429
  const next = /* @__PURE__ */ new Map();
2430
+ const removed = /* @__PURE__ */ new Set();
2151
2431
  if (raw) {
2152
2432
  try {
2153
2433
  const entries = JSON.parse(raw);
2154
2434
  for (const entry of entries) {
2155
- if (!entry.pagePath || entry.pagePath === currentPath) next.set(entry.instanceId, entry.order);
2435
+ if (entry.pagePath && entry.pagePath !== currentPath) continue;
2436
+ next.set(entry.instanceId, entry.order);
2437
+ if (entry.removed) removed.add(entry.instanceId);
2156
2438
  }
2157
2439
  } catch {
2158
2440
  }
2159
2441
  }
2160
2442
  sectionOrderIndex = next;
2443
+ removedSectionIds = removed;
2161
2444
  }
2162
2445
  function applyExplicitOrder(entries) {
2163
2446
  if (sectionOrderIndex.size === 0) return entries;
@@ -2193,6 +2476,18 @@ function orderByChain(sections) {
2193
2476
  for (const root of roots) visit(root);
2194
2477
  return out.length === sections.length ? out : sections;
2195
2478
  }
2479
+ function syncSoftRemovedGenerated() {
2480
+ for (const [id, section] of mounted) {
2481
+ const el = section.container;
2482
+ if (removedSectionIds.has(id)) {
2483
+ el.style.display = "none";
2484
+ el.setAttribute(REMOVED_ATTR, "");
2485
+ } else if (el.hasAttribute(REMOVED_ATTR)) {
2486
+ el.style.removeProperty("display");
2487
+ el.removeAttribute(REMOVED_ATTR);
2488
+ }
2489
+ }
2490
+ }
2196
2491
  function applyAiSectionsToDom(state, options) {
2197
2492
  if (typeof document === "undefined") return;
2198
2493
  const brandOverride = deriveBrandOverride();
@@ -2260,6 +2555,7 @@ function applyAiSectionsToDom(state, options) {
2260
2555
  syncReplacedOriginals(state);
2261
2556
  syncRemovedSections(state);
2262
2557
  syncTemplateHidden(state, pageSections.length > 0);
2558
+ syncSoftRemovedGenerated();
2263
2559
  }
2264
2560
  function unmountAllAiSections() {
2265
2561
  for (const [, section] of mounted) {
@@ -2272,9 +2568,9 @@ function unmountAllAiSections() {
2272
2568
  el.style.removeProperty("display");
2273
2569
  el.removeAttribute(REPLACED_ATTR);
2274
2570
  }
2275
- for (const el of document.querySelectorAll(`[${REMOVED_ATTR}]`)) {
2571
+ for (const el of document.querySelectorAll(`[${REMOVED_ATTR2}]`)) {
2276
2572
  el.style.removeProperty("display");
2277
- el.removeAttribute(REMOVED_ATTR);
2573
+ el.removeAttribute(REMOVED_ATTR2);
2278
2574
  }
2279
2575
  }
2280
2576
 
@@ -8235,240 +8531,6 @@ function CarouselOverlay({
8235
8531
  // src/ui/ai-section/AiSectionOverlay.tsx
8236
8532
  var import_react8 = require("react");
8237
8533
  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
8534
  var import_jsx_runtime17 = require("react/jsx-runtime");
8473
8535
  var findSectionElement = findByInstanceId;
8474
8536
  function readRect(instanceId) {
@@ -14251,15 +14313,17 @@ function useSectionDrag({
14251
14313
  clearSectionDragVisuals();
14252
14314
  return;
14253
14315
  }
14254
- const orderJson = JSON.stringify(entries);
14316
+ const orderJson = JSON.stringify(
14317
+ mergePageSectionOrder(editContentRef.current[SECTION_ORDER_KEY], window.location.pathname, entries)
14318
+ );
14255
14319
  editContentRef.current = { ...editContentRef.current, [SECTION_ORDER_KEY]: orderJson };
14256
14320
  setAiSectionOrder(orderJson, window.location.pathname);
14257
14321
  postToParentRef.current({ type: "ow:change", nodes: [{ key: SECTION_ORDER_KEY, text: orderJson }] });
14258
- applyPersistedOrder(entries);
14322
+ applyPersistedOrder(getPageSectionOrderEntries(orderJson, window.location.pathname));
14259
14323
  clearSectionDragVisuals();
14260
14324
  requestAnimationFrame(() => {
14261
14325
  if (editContentRef.current[SECTION_ORDER_KEY] === orderJson) {
14262
- applyPersistedOrder(entries);
14326
+ applyPersistedOrder(getPageSectionOrderEntries(orderJson, window.location.pathname));
14263
14327
  }
14264
14328
  requestAnimationFrame(() => {
14265
14329
  window.dispatchEvent(new Event("resize"));
@@ -20284,6 +20348,44 @@ function OhhwellsBridge() {
20284
20348
  if (h > 50) postToParentRef.current({ type: "ow:height", height: h });
20285
20349
  }, 400));
20286
20350
  };
20351
+ const reapCommittedAiSections = (excludeIds) => {
20352
+ const aiState = parseAiSectionsState(aiSectionsRef.current);
20353
+ if (aiState.sections.length === 0) return [];
20354
+ let orderEntries = [];
20355
+ try {
20356
+ const parsed = JSON.parse(editContentRef.current[SECTION_ORDER_KEY] ?? "[]");
20357
+ if (Array.isArray(parsed)) orderEntries = parsed;
20358
+ } catch {
20359
+ return [];
20360
+ }
20361
+ const removedIds = orderEntries.filter((entry) => entry && typeof entry.instanceId === "string" && entry.removed).map((entry) => entry.instanceId);
20362
+ if (removedIds.length === 0) return [];
20363
+ const result = reapRemovedAiSections(aiState, parseStyleStore(stylesRef.current), removedIds, excludeIds);
20364
+ if (!result.changed) return [];
20365
+ const nodes = [];
20366
+ aiSectionsRef.current = serializeAiSectionsState(result.state);
20367
+ nodes.push({ key: AI_SECTIONS_KEY, text: aiSectionsRef.current });
20368
+ const reaped = new Set(result.reapedIds);
20369
+ const nextOrderJson = JSON.stringify(orderEntries.filter((entry) => !reaped.has(entry.instanceId)));
20370
+ editContentRef.current = { ...editContentRef.current, [SECTION_ORDER_KEY]: nextOrderJson };
20371
+ setAiSectionOrder(nextOrderJson, window.location.pathname);
20372
+ nodes.push({ key: SECTION_ORDER_KEY, text: nextOrderJson });
20373
+ if (result.store) {
20374
+ stylesRef.current = JSON.stringify(result.store);
20375
+ nodes.push({ key: STYLE_STORE_KEY, text: stylesRef.current });
20376
+ }
20377
+ const nextContent = { ...editContentRef.current };
20378
+ for (const key of Object.keys(nextContent)) {
20379
+ if (key.startsWith(AI_SLOT_KEY_PREFIX) && result.slotPrefixes.some((prefix) => key.startsWith(prefix)) && nextContent[key] !== "") {
20380
+ nextContent[key] = "";
20381
+ nodes.push({ key, text: "" });
20382
+ }
20383
+ }
20384
+ editContentRef.current = nextContent;
20385
+ applyAiSectionsToDom(result.state);
20386
+ applyStylesToDom(parseStyleStore(stylesRef.current));
20387
+ return nodes;
20388
+ };
20287
20389
  const handleHydrate = (e) => {
20288
20390
  if (e.data?.type !== "ow:hydrate") return;
20289
20391
  const content = e.data.content;
@@ -20352,6 +20454,11 @@ function OhhwellsBridge() {
20352
20454
  reconcileFooterOrderFromContent(editContentRef.current);
20353
20455
  syncNavigationDragCursorAttrs();
20354
20456
  enforceLinkHrefs();
20457
+ const hydrateReapExclude = /* @__PURE__ */ new Set();
20458
+ const hydratePendingUndo = pendingDeleteUndoRef.current;
20459
+ if (hydratePendingUndo?.sectionInstanceId) hydrateReapExclude.add(hydratePendingUndo.sectionInstanceId);
20460
+ const reapNodes = reapCommittedAiSections(hydrateReapExclude);
20461
+ if (reapNodes.length > 0) postToParentRef.current({ type: "ow:change", nodes: reapNodes });
20355
20462
  const hydratedHeight = document.body.scrollHeight;
20356
20463
  if (hydratedHeight > 50) postToParentRef.current({ type: "ow:height", height: hydratedHeight });
20357
20464
  postToParentRef.current({ type: "ow:hydrate-done" });
@@ -20536,8 +20643,11 @@ function OhhwellsBridge() {
20536
20643
  if (!instanceId || !direction) return;
20537
20644
  const entries = moveSectionInstance(instanceId, direction, window.location.pathname);
20538
20645
  if (!entries) return;
20539
- const orderJson = JSON.stringify(entries);
20646
+ const orderJson = JSON.stringify(
20647
+ mergePageSectionOrder(editContentRef.current[SECTION_ORDER_KEY], window.location.pathname, entries)
20648
+ );
20540
20649
  editContentRef.current = { ...editContentRef.current, [SECTION_ORDER_KEY]: orderJson };
20650
+ applyPersistedOrder(getPageSectionOrderEntries(orderJson, window.location.pathname));
20541
20651
  setAiSectionOrder(orderJson, window.location.pathname);
20542
20652
  postToParentRef.current({ type: "ow:change", nodes: [{ key: SECTION_ORDER_KEY, text: orderJson }] });
20543
20653
  window.dispatchEvent(new Event("resize"));
@@ -20583,8 +20693,11 @@ function OhhwellsBridge() {
20583
20693
  const currentEntries = getPageSectionOrderEntries(editContentRef.current[SECTION_ORDER_KEY], window.location.pathname);
20584
20694
  const entries = deleteSectionInstance(instanceId, window.location.pathname, currentEntries);
20585
20695
  if (!entries) return;
20586
- const orderJson = JSON.stringify(entries);
20696
+ const orderJson = JSON.stringify(
20697
+ mergePageSectionOrder(editContentRef.current[SECTION_ORDER_KEY], window.location.pathname, entries)
20698
+ );
20587
20699
  editContentRef.current = { ...editContentRef.current, [SECTION_ORDER_KEY]: orderJson };
20700
+ setAiSectionOrder(orderJson, window.location.pathname);
20588
20701
  postToParentRef.current({ type: "ow:change", nodes: [{ key: SECTION_ORDER_KEY, text: orderJson }] });
20589
20702
  aiSectionApiRef.current?.clear();
20590
20703
  window.dispatchEvent(new Event("resize"));
@@ -20593,6 +20706,7 @@ function OhhwellsBridge() {
20593
20706
  const actionId = newInstanceId();
20594
20707
  pendingDeleteUndoRef.current = {
20595
20708
  actionId,
20709
+ sectionInstanceId: instanceId,
20596
20710
  restore: () => {
20597
20711
  const restoredEntries = getPageSectionOrderEntries(
20598
20712
  editContentRef.current[SECTION_ORDER_KEY],
@@ -20600,8 +20714,11 @@ function OhhwellsBridge() {
20600
20714
  );
20601
20715
  const restored = restoreSectionInstance(instanceId, window.location.pathname, restoredEntries);
20602
20716
  if (!restored) return;
20603
- const restoredJson = JSON.stringify(restored);
20717
+ const restoredJson = JSON.stringify(
20718
+ mergePageSectionOrder(editContentRef.current[SECTION_ORDER_KEY], window.location.pathname, restored)
20719
+ );
20604
20720
  editContentRef.current = { ...editContentRef.current, [SECTION_ORDER_KEY]: restoredJson };
20721
+ setAiSectionOrder(restoredJson, window.location.pathname);
20605
20722
  postToParentRef.current({ type: "ow:change", nodes: [{ key: SECTION_ORDER_KEY, text: restoredJson }] });
20606
20723
  window.dispatchEvent(new Event("resize"));
20607
20724
  const restoreHeight = document.body.scrollHeight;
@@ -20627,7 +20744,9 @@ function OhhwellsBridge() {
20627
20744
  const result = duplicateSectionInstance(instanceId, newId, window.location.pathname, currentEntries);
20628
20745
  if (!result) return;
20629
20746
  const { entries, keyRekeys } = result;
20630
- const orderJson = JSON.stringify(entries);
20747
+ const orderJson = JSON.stringify(
20748
+ mergePageSectionOrder(editContentRef.current[SECTION_ORDER_KEY], window.location.pathname, entries)
20749
+ );
20631
20750
  const nodes = [{ key: SECTION_ORDER_KEY, text: orderJson }];
20632
20751
  for (const { from, to } of keyRekeys) {
20633
20752
  const inherited = editContentRef.current[from];
@@ -20637,6 +20756,7 @@ function OhhwellsBridge() {
20637
20756
  ...editContentRef.current,
20638
20757
  ...Object.fromEntries(nodes.map(({ key, text }) => [key, text]))
20639
20758
  };
20759
+ applyPersistedOrder(getPageSectionOrderEntries(orderJson, window.location.pathname));
20640
20760
  setAiSectionOrder(orderJson, window.location.pathname);
20641
20761
  postToParentRef.current({ type: "ow:change", nodes });
20642
20762
  window.dispatchEvent(new Event("resize"));
@@ -20900,6 +21020,10 @@ function OhhwellsBridge() {
20900
21020
  };
20901
21021
  const handleSave = (e) => {
20902
21022
  if (e.data?.type !== "ow:save") return;
21023
+ const pendingUndo = pendingDeleteUndoRef.current;
21024
+ const reapExclude = /* @__PURE__ */ new Set();
21025
+ if (pendingUndo?.sectionInstanceId) reapExclude.add(pendingUndo.sectionInstanceId);
21026
+ const reapNodes = reapCommittedAiSections(reapExclude);
20903
21027
  const nodes = collectEditableNodes(editContentRef.current);
20904
21028
  const tracker = document.querySelector("[data-ohw-sections-tracker]");
20905
21029
  if (tracker?.textContent) nodes.push({ key: "__ohw_sections", type: "sections", text: tracker.textContent });
@@ -20919,6 +21043,11 @@ function OhhwellsBridge() {
20919
21043
  const text = specs.length ? JSON.stringify(specs) : editContentRef.current[fieldsKey(formKey)];
20920
21044
  if (text) nodes.push({ key: fieldsKey(formKey), type: "text", text });
20921
21045
  });
21046
+ for (const reapNode of reapNodes) {
21047
+ if (reapNode.key.startsWith(AI_SLOT_KEY_PREFIX) && !nodes.some((n) => n.key === reapNode.key)) {
21048
+ nodes.push({ key: reapNode.key, type: "text", text: reapNode.text });
21049
+ }
21050
+ }
20922
21051
  postToParentRef.current({ type: "ow:save-result", nodes });
20923
21052
  };
20924
21053
  const handleInsertSection = (e) => {
@@ -21540,7 +21669,7 @@ function OhhwellsBridge() {
21540
21669
  postToParent2({
21541
21670
  type: "ow:ready",
21542
21671
  version: "1",
21543
- bridgeVersion: "0.1.93",
21672
+ bridgeVersion: "0.1.94",
21544
21673
  path: pathname,
21545
21674
  nodes: collectEditableNodes(editContentRef.current),
21546
21675
  sections