@agent-native/core 0.181.0 → 0.181.1-nightly-20260916144018

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/clips/app/components/player/create-account-dialog.tsx +2 -1
  3. package/corpus/templates/clips/changelog/2026-09-15-google-sign-up-now-opens-reliably-in-embedded-browsers.md +6 -0
  4. package/corpus/templates/content/actions/resolve-preview-document-draft.ts +320 -60
  5. package/corpus/templates/content/app/components/editor/DocumentBlockFields.tsx +3 -1
  6. package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +349 -104
  7. package/corpus/templates/content/app/components/editor/DocumentReconcileRecovery.tsx +109 -0
  8. package/corpus/templates/content/app/components/editor/PageDraftRecovery.tsx +31 -153
  9. package/corpus/templates/content/app/components/editor/RecoveryComparison.tsx +230 -0
  10. package/corpus/templates/content/app/components/editor/VisualEditor.tsx +46 -19
  11. package/corpus/templates/content/app/components/editor/editor-draft-save.ts +13 -0
  12. package/corpus/templates/content/app/components/editor/useDocumentReconcileRecovery.ts +248 -0
  13. package/corpus/templates/content/app/components/sidebar/NotionButton.tsx +2 -1
  14. package/corpus/templates/content/app/i18n-data.ts +13 -0
  15. package/corpus/templates/content/changelog/2026-09-15-oauth-connections-now-open-reliably-in-embedded-browsers.md +6 -0
  16. package/corpus/templates/content/docs/solutions/2026-09-02-content-suggested-edits-parity-shape.md +84 -2
  17. package/corpus/templates/design/.generated/bridge/editor-chrome.generated.ts +27 -7
  18. package/corpus/templates/design/app/components/design/DesignCanvas.tsx +45 -15
  19. package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +35 -8
  20. package/corpus/templates/design/app/components/design/design-canvas/element-payload.ts +14 -0
  21. package/corpus/templates/design/app/pages/DesignEditor.tsx +18 -5
  22. package/corpus/templates/design/app/pages/design-editor/commands/record-pending-live-structure-edit.ts +9 -4
  23. package/corpus/templates/design/app/pages/design-editor/commands/screen-visual-structure-change.ts +3 -0
  24. package/corpus/templates/design/app/pages/design-editor/commands/start-sidebar-resize.ts +7 -2
  25. package/corpus/templates/design/app/pages/design-editor/commands/visual-structure-change.ts +2 -0
  26. package/corpus/templates/design/app/pages/design-editor/pending-edits.ts +4 -0
  27. package/corpus/templates/design/app/pages/design-editor/pending-structure-verification.ts +197 -147
  28. package/corpus/templates/design/changelog/2026-09-16-design-editor-agent-chat-uses-shared-conversation-tabs-and-s.md +6 -0
  29. package/corpus/templates/design/changelog/2026-09-16-visual-edits-stay-applied-after-replacements.md +5 -0
  30. package/corpus/templates/mail/app/components/GoogleConnectBanner.tsx +2 -1
  31. package/corpus/templates/mail/changelog/2026-09-15-google-connections-now-open-reliably-in-embedded-browsers.md +6 -0
  32. package/dist/client/AgentPanel.d.ts +1 -1
  33. package/dist/client/AgentPanel.js +4 -4
  34. package/dist/client/auth/AuthPage.js +6 -1
  35. package/dist/client/oauth-popup.d.ts +7 -0
  36. package/dist/client/oauth-popup.js +14 -0
  37. package/dist/client/ocean/OceanBackground.d.ts +3 -1
  38. package/dist/client/ocean/OceanBackground.js +4 -3
  39. package/dist/client/resources/mcp-integration-catalog.js +7 -3
  40. package/dist/client/settings/useBuilderStatus.js +4 -3
  41. package/dist/collab/routes.d.ts +1 -1
  42. package/dist/collab/struct-routes.d.ts +1 -1
  43. package/dist/file-upload/actions/upload-image.d.ts +2 -2
  44. package/dist/mcp/screen-memory-stdio.d.ts +7 -7
  45. package/dist/notifications/routes.d.ts +3 -3
  46. package/dist/observability/routes.d.ts +3 -3
  47. package/dist/org/app-roles.d.ts +8 -0
  48. package/dist/org/app-roles.js +7 -0
  49. package/dist/org/index.d.ts +1 -1
  50. package/dist/org/index.js +1 -1
  51. package/dist/provider-api/actions/custom-provider-registration.d.ts +4 -4
  52. package/dist/secrets/routes.d.ts +6 -6
  53. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  54. package/dist/server/auth.js +3 -0
  55. package/dist/server/core-routes-plugin.d.ts +3 -0
  56. package/dist/server/core-routes-plugin.js +17 -0
  57. package/dist/server/realtime-token.d.ts +1 -1
  58. package/package.json +5 -4
@@ -103,6 +103,7 @@ import {
103
103
  import {
104
104
  isComputedStyleMap,
105
105
  isElementInfoPayload,
106
+ parseRuntimeSnapshotHtml,
106
107
  } from "./design-canvas/element-payload";
107
108
  import {
108
109
  embeddedContentOffsetCss,
@@ -698,6 +699,7 @@ interface DesignCanvasProps {
698
699
  replacementSelector?: string;
699
700
  replacementSourceId?: string;
700
701
  replacementElementInfo?: ElementInfo;
702
+ replacementSnapshotHtml?: string;
701
703
  },
702
704
  ) => boolean | "pending" | void;
703
705
  onVisualDuplicateChange?: (
@@ -3298,24 +3300,34 @@ export function DesignCanvas({
3298
3300
  allowedOrigins: canvasBridgeAllowedOrigins,
3299
3301
  });
3300
3302
  if (trustedRuntimeVerificationFrame) {
3303
+ if (e.data?.type === "agent-native:runtime-layer-snapshot-error") {
3304
+ onRuntimeStructureInsertRejected?.(
3305
+ e.data.payload?.reason === "snapshot-too-large"
3306
+ ? "verification-snapshot-too-large"
3307
+ : "verification-snapshot-unavailable",
3308
+ );
3309
+ return;
3310
+ }
3301
3311
  if (e.data?.type !== "agent-native:runtime-layer-snapshot") return;
3302
3312
  const payload = e.data.payload;
3303
- if (
3304
- payload &&
3305
- typeof payload.html === "string" &&
3306
- payload.html.length <= 2_000_000 &&
3307
- Number.isFinite(payload.nodeCount)
3308
- ) {
3309
- onRuntimeVerificationSnapshot?.({
3310
- requestId: runtimeVerificationRequest.requestId,
3311
- html: payload.html,
3312
- nodeCount: Math.max(0, Math.floor(payload.nodeCount)),
3313
- documentId:
3314
- typeof payload.documentId === "string"
3315
- ? payload.documentId
3316
- : undefined,
3317
- });
3313
+ const snapshot = parseRuntimeSnapshotHtml(payload?.html);
3314
+ if (!snapshot.ok || !Number.isFinite(payload?.nodeCount)) {
3315
+ onRuntimeStructureInsertRejected?.(
3316
+ snapshot.ok
3317
+ ? "verification-snapshot-unavailable"
3318
+ : `verification-${snapshot.reason}`,
3319
+ );
3320
+ return;
3318
3321
  }
3322
+ onRuntimeVerificationSnapshot?.({
3323
+ requestId: runtimeVerificationRequest.requestId,
3324
+ html: snapshot.html,
3325
+ nodeCount: Math.max(0, Math.floor(payload.nodeCount)),
3326
+ documentId:
3327
+ typeof payload.documentId === "string"
3328
+ ? payload.documentId
3329
+ : undefined,
3330
+ });
3319
3331
  return;
3320
3332
  }
3321
3333
  const lateReadyRecovery =
@@ -3593,6 +3605,21 @@ export function DesignCanvas({
3593
3605
  });
3594
3606
  const requestId =
3595
3607
  typeof e.data.requestId === "string" ? e.data.requestId : undefined;
3608
+ const replacementSnapshot = replaced
3609
+ ? parseRuntimeSnapshotHtml(e.data.replacementSnapshotHtml)
3610
+ : undefined;
3611
+ if (replacementSnapshot?.ok === false) {
3612
+ if (requestId) {
3613
+ iframeRef.current?.contentWindow?.postMessage(
3614
+ { type: "visual-structure-ack", requestId, applied: false },
3615
+ "*",
3616
+ );
3617
+ }
3618
+ onRuntimeStructureInsertRejected?.(
3619
+ `replacement-${replacementSnapshot.reason}`,
3620
+ );
3621
+ return;
3622
+ }
3596
3623
  const sourceId =
3597
3624
  typeof e.data.sourceId === "string" ? e.data.sourceId : undefined;
3598
3625
  const anchorSourceId =
@@ -3667,6 +3694,9 @@ export function DesignCanvas({
3667
3694
  replaced: true as const,
3668
3695
  replacementSelector: selector,
3669
3696
  replacementSourceId: sourceId,
3697
+ replacementSnapshotHtml: replacementSnapshot?.ok
3698
+ ? replacementSnapshot.html
3699
+ : undefined,
3670
3700
  replacementElementInfo: isElementInfoPayload(e.data.payload)
3671
3701
  ? e.data.payload
3672
3702
  : undefined,
@@ -1557,11 +1557,12 @@ declare var __INITIAL_SOURCE_HEAD__: string;
1557
1557
  );
1558
1558
  }
1559
1559
 
1560
- function serializeRuntimeLayerSnapshot(): {
1561
- html: string;
1562
- nodeCount: number;
1563
- } | null {
1564
- if (!document.body) return null;
1560
+ function serializeRuntimeLayerSnapshot(
1561
+ excludedRoot?: Element,
1562
+ ):
1563
+ | { ok: true; html: string; nodeCount: number; documentId: string }
1564
+ | { ok: false; reason: "snapshot-unavailable" | "snapshot-too-large" } {
1565
+ if (!document.body) return { ok: false, reason: "snapshot-unavailable" };
1565
1566
  // Keep this list export-focused and bounded. The runtime snapshot is also
1566
1567
  // the hosted/cross-origin Design→Figma fallback: inlining the resolved
1567
1568
  // paint/layout values lets the parent reconstruct the already-rendered
@@ -1666,7 +1667,10 @@ declare var __INITIAL_SOURCE_HEAD__: string;
1666
1667
  ) {
1667
1668
  var sourceNode = sourceNodes[index];
1668
1669
  var cloneNode = cloneNodes[index];
1669
- if (!isRuntimeLayerVisualNode(sourceNode)) {
1670
+ if (
1671
+ excludedRoot?.contains(sourceNode) ||
1672
+ !isRuntimeLayerVisualNode(sourceNode)
1673
+ ) {
1670
1674
  cloneNode.setAttribute("data-an-runtime-layer-remove", "true");
1671
1675
  continue;
1672
1676
  }
@@ -1787,8 +1791,10 @@ declare var __INITIAL_SOURCE_HEAD__: string;
1787
1791
  inlineSnapshotComputedStyle(document.body, cloneBody);
1788
1792
  cloneBody.setAttribute("data-an-runtime-layer-snapshot", "true");
1789
1793
  var html = "<!doctype html><html>" + cloneBody.outerHTML + "</html>"; // i18n-ignore serialized runtime-layer HTML payload, not visible UI copy
1790
- if (html.length > 2_000_000) return null;
1794
+ if (html.length > 2_000_000)
1795
+ return { ok: false, reason: "snapshot-too-large" };
1791
1796
  return {
1797
+ ok: true,
1792
1798
  html: html,
1793
1799
  nodeCount: nodeCount,
1794
1800
  documentId: runtimeDocumentId,
@@ -1805,7 +1811,17 @@ declare var __INITIAL_SOURCE_HEAD__: string;
1805
1811
  runtimeLayerSnapshotTimer = null;
1806
1812
  runtimeLayerSnapshotMaxTimer = null;
1807
1813
  var snapshot = serializeRuntimeLayerSnapshot();
1808
- if (!snapshot || snapshot.html === lastRuntimeLayerSnapshotHtml) return;
1814
+ if (!snapshot.ok) {
1815
+ (window.parent as Window).postMessage(
1816
+ {
1817
+ type: "agent-native:runtime-layer-snapshot-error",
1818
+ payload: snapshot,
1819
+ },
1820
+ "*",
1821
+ );
1822
+ return;
1823
+ }
1824
+ if (snapshot.html === lastRuntimeLayerSnapshotHtml) return;
1809
1825
  lastRuntimeLayerSnapshotHtml = snapshot.html;
1810
1826
  (window.parent as Window).postMessage(
1811
1827
  {
@@ -13603,6 +13619,7 @@ declare var __INITIAL_SOURCE_HEAD__: string;
13603
13619
  origin,
13604
13620
  insertedHtml?,
13605
13621
  replaced?,
13622
+ replacementSnapshotHtml?: string,
13606
13623
  ) {
13607
13624
  if (!el || !target || !target.anchor) return;
13608
13625
  dndLog("post:structure-change", {
@@ -13636,6 +13653,7 @@ declare var __INITIAL_SOURCE_HEAD__: string;
13636
13653
  insertedHtml:
13637
13654
  typeof insertedHtml === "string" ? insertedHtml : undefined,
13638
13655
  replaced: replaced === true ? true : undefined,
13656
+ replacementSnapshotHtml: replacementSnapshotHtml,
13639
13657
  sourceRect: rectInfoForElement(el),
13640
13658
  anchorRect: rectInfoForElement(target.anchor),
13641
13659
  payload: getElementInfo(el),
@@ -20146,6 +20164,14 @@ declare var __INITIAL_SOURCE_HEAD__: string;
20146
20164
  }
20147
20165
  var replaceNextSibling = insertAnchor.nextSibling;
20148
20166
  replaceParent.insertBefore(parsedInsertEl, insertAnchor);
20167
+ // Capture while the original still supplies its selector/provenance,
20168
+ // and reject before publishing a pending operation if proof is unavailable.
20169
+ var replacementSnapshot = serializeRuntimeLayerSnapshot(insertAnchor);
20170
+ if (!replacementSnapshot.ok) {
20171
+ parsedInsertEl.remove();
20172
+ rejectInsert("replacement-" + replacementSnapshot.reason);
20173
+ return;
20174
+ }
20149
20175
  selectedEl = parsedInsertEl;
20150
20176
  positionOverlay(selectionOverlay, selectedEl);
20151
20177
  refreshOverlays();
@@ -20160,6 +20186,7 @@ declare var __INITIAL_SOURCE_HEAD__: string;
20160
20186
  },
20161
20187
  parsedInsertEl.outerHTML,
20162
20188
  true,
20189
+ replacementSnapshot.html,
20163
20190
  );
20164
20191
  replaceParent.removeChild(insertAnchor);
20165
20192
  refreshOverlays();
@@ -36,3 +36,17 @@ export function isElementInfoPayload(value: unknown): value is ElementInfo {
36
36
  typeof value.isFlexContainer === "boolean"
37
37
  );
38
38
  }
39
+
40
+ export function parseRuntimeSnapshotHtml(
41
+ value: unknown,
42
+ ):
43
+ | { ok: true; html: string }
44
+ | { ok: false; reason: "snapshot-unavailable" | "snapshot-too-large" } {
45
+ if (typeof value !== "string" || !value) {
46
+ return { ok: false, reason: "snapshot-unavailable" };
47
+ }
48
+ if (value.length > 2_000_000) {
49
+ return { ok: false, reason: "snapshot-too-large" };
50
+ }
51
+ return { ok: true, html: value };
52
+ }
@@ -1721,7 +1721,7 @@ function DesignEditor() {
1721
1721
  const initialUrlSelectionHydratedForIdRef = useRef<string | null>(null);
1722
1722
  // Figma's 56px workspace rail (plus its 1px divider) and 280px Layers/Pages
1723
1723
  // pane place the content divider at x=337. Keep that total while honoring
1724
- // resizable 220–420px content range.
1724
+ // the resizable 220–420px content range, with 320px reserved for Agent chat.
1725
1725
  const [leftSidebarWidth, setLeftSidebarWidth] = useState(280);
1726
1726
  const [rightSidebarWidth, setRightSidebarWidth] = useState(240);
1727
1727
  // Cmd/Ctrl+\ hides the sidebars while leaving the bottom tools available.
@@ -7828,6 +7828,7 @@ function DesignEditor() {
7828
7828
  replacementSelector?: string;
7829
7829
  replacementSourceId?: string;
7830
7830
  replacementElementInfo?: ElementInfo;
7831
+ replacementSnapshotHtml?: string;
7831
7832
  /** This change DELETED the subject; it has no anchor. */
7832
7833
  removed?: true;
7833
7834
  },
@@ -12092,6 +12093,7 @@ function DesignEditor() {
12092
12093
  replacementSelector?: string;
12093
12094
  replacementSourceId?: string;
12094
12095
  replacementElementInfo?: ElementInfo;
12096
+ replacementSnapshotHtml?: string;
12095
12097
  },
12096
12098
  ) =>
12097
12099
  runVisualStructureChange(
@@ -12334,6 +12336,7 @@ function DesignEditor() {
12334
12336
  replacementSelector?: string;
12335
12337
  replacementSourceId?: string;
12336
12338
  replacementElementInfo?: ElementInfo;
12339
+ replacementSnapshotHtml?: string;
12337
12340
  },
12338
12341
  ) =>
12339
12342
  runScreenVisualStructureChange(
@@ -14461,6 +14464,11 @@ function DesignEditor() {
14461
14464
 
14462
14465
  const handleRuntimeStructureInsertRejected = useCallback(
14463
14466
  (reason: string) => {
14467
+ if (reason.startsWith("verification-")) {
14468
+ cancelPendingStructureVerification("conflict");
14469
+ toast.error(t("designEditor.pendingVisualStyles.conflictToast"));
14470
+ return;
14471
+ }
14464
14472
  // Never swallow this: a rejected insert leaves nothing on screen and
14465
14473
  // nothing in the pending list, so a silent return is indistinguishable
14466
14474
  // from the drop never having happened.
@@ -14469,7 +14477,7 @@ function DesignEditor() {
14469
14477
  }
14470
14478
  toast.error(t("designEditor.toasts.layerMoveFailed"), { duration: 4000 });
14471
14479
  },
14472
- [t],
14480
+ [cancelPendingStructureVerification, t],
14473
14481
  );
14474
14482
 
14475
14483
  const handleCutSelection = useCallback(async () => {
@@ -23398,7 +23406,10 @@ function DesignEditor() {
23398
23406
  const leftContentWidth =
23399
23407
  activeLeftPanel === "code"
23400
23408
  ? Math.max(leftSidebarWidth, 640)
23401
- : Math.max(Math.min(leftSidebarWidth, 420), 220);
23409
+ : Math.max(
23410
+ Math.min(leftSidebarWidth, 420),
23411
+ activeLeftPanel === "agent" ? 320 : 220,
23412
+ );
23402
23413
  const leftSidebarVisible = !hostOwnsChrome && !uiHidden && !minimalUi;
23403
23414
  // These focused surfaces need a clear viewport beside the absolute rail.
23404
23415
  const leftChromeOverlayInset = leftSidebarVisible
@@ -23712,7 +23723,7 @@ function DesignEditor() {
23712
23723
  <div
23713
23724
  data-design-agent-panel
23714
23725
  className={cn(
23715
- "min-h-0 flex-1 flex-col overflow-hidden",
23726
+ "min-h-0 min-w-0 flex-1 flex-col overflow-hidden",
23716
23727
  activeLeftPanel === "agent" ? "flex" : "hidden",
23717
23728
  )}
23718
23729
  >
@@ -23721,7 +23732,9 @@ function DesignEditor() {
23721
23732
  ) : canEditDesign ? (
23722
23733
  <AgentChatSurface
23723
23734
  mode="panel"
23724
- className="min-h-0 flex-1 border-0 bg-transparent shadow-none"
23735
+ className="min-h-0 min-w-0 flex-1 border-0 bg-transparent shadow-none"
23736
+ chatOnly={true}
23737
+ onCollapse={() => setActiveLeftPanel(null)}
23725
23738
  storageKey={DESIGN_CHAT_STORAGE_KEY}
23726
23739
  emptyStateText={t("chat.emptyState")}
23727
23740
  suggestions={designAgentSuggestions}
@@ -23,7 +23,10 @@ import {
23
23
  reactSourceAnchorForPendingEdit,
24
24
  runtimeStructureNodeSignature,
25
25
  } from "@/pages/design-editor/pending-edits";
26
- import { isPendingStructureDropNoOp } from "@/pages/design-editor/pending-structure-verification";
26
+ import {
27
+ isPendingStructureDropNoOp,
28
+ runtimeStructureSnapshotSignature,
29
+ } from "@/pages/design-editor/pending-structure-verification";
27
30
  import type { DesignFile } from "@/pages/design-editor/types";
28
31
 
29
32
  export interface RecordPendingLiveStructureEditArgs {
@@ -86,6 +89,7 @@ export function runRecordPendingLiveStructureEdit(
86
89
  replacementSelector?: string;
87
90
  replacementSourceId?: string;
88
91
  replacementElementInfo?: ElementInfo;
92
+ replacementSnapshotHtml?: string;
89
93
  /** This change DELETED the subject; it has no anchor. */
90
94
  removed?: true;
91
95
  },
@@ -108,9 +112,7 @@ export function runRecordPendingLiveStructureEdit(
108
112
  const subjectInfo = details?.replaced
109
113
  ? details.anchorElementInfo
110
114
  : elementInfo;
111
- const subjectSourceId = details?.replaced
112
- ? details.anchorSourceId
113
- : (details?.sourceId ?? elementInfo?.sourceId);
115
+ const subjectSourceId = details?.sourceId ?? subjectInfo?.sourceId;
114
116
  const nextEdit: PendingLiveStructureEdit = {
115
117
  kind: "structure",
116
118
  screenId,
@@ -164,6 +166,9 @@ export function runRecordPendingLiveStructureEdit(
164
166
  replaced: true as const,
165
167
  replacementSelector: details.replacementSelector,
166
168
  replacementSourceId: details.replacementSourceId,
169
+ replacementSnapshotSignature: details.replacementSnapshotHtml
170
+ ? runtimeStructureSnapshotSignature(details.replacementSnapshotHtml)
171
+ : undefined,
167
172
  }
168
173
  : {}),
169
174
  ...(details?.removed ? { removed: true as const } : {}),
@@ -48,6 +48,7 @@ export interface ScreenVisualStructureChangeArgs {
48
48
  replacementSelector?: string;
49
49
  replacementSourceId?: string;
50
50
  replacementElementInfo?: ElementInfo;
51
+ replacementSnapshotHtml?: string;
51
52
  },
52
53
  ) => boolean | "pending";
53
54
  overviewScreens: OverviewScreen[];
@@ -71,6 +72,7 @@ export interface ScreenVisualStructureChangeArgs {
71
72
  replacementSelector?: string;
72
73
  replacementSourceId?: string;
73
74
  replacementElementInfo?: ElementInfo;
75
+ replacementSnapshotHtml?: string;
74
76
  removed?: true;
75
77
  },
76
78
  ) => void;
@@ -117,6 +119,7 @@ export function runScreenVisualStructureChange(
117
119
  replacementSelector?: string;
118
120
  replacementSourceId?: string;
119
121
  replacementElementInfo?: ElementInfo;
122
+ replacementSnapshotHtml?: string;
120
123
  },
121
124
  ) {
122
125
  if (screenId === activeFile?.id) {
@@ -35,14 +35,19 @@ export function runStartSidebarResize(
35
35
  event.currentTarget.setPointerCapture?.(event.pointerId);
36
36
  const startX = event.clientX;
37
37
  const codePanelOpen = side === "left" && activeLeftPanel === "code";
38
+ const leftPanelMinWidth = codePanelOpen
39
+ ? 520
40
+ : activeLeftPanel === "agent"
41
+ ? 320
42
+ : 220;
38
43
  const startWidth =
39
44
  side === "left"
40
45
  ? codePanelOpen
41
46
  ? Math.max(leftSidebarWidth, 640)
42
- : Math.min(leftSidebarWidth, 420)
47
+ : Math.max(Math.min(leftSidebarWidth, 420), leftPanelMinWidth)
43
48
  : rightSidebarWidth;
44
49
  const setWidth = side === "left" ? setLeftSidebarWidth : setRightSidebarWidth;
45
- const minWidth = side === "left" ? (codePanelOpen ? 520 : 220) : 240;
50
+ const minWidth = side === "left" ? leftPanelMinWidth : 240;
46
51
  const maxWidth = side === "left" ? (codePanelOpen ? 1100 : 420) : 390;
47
52
  const target =
48
53
  side === "left"
@@ -73,6 +73,7 @@ export interface VisualStructureChangeArgs {
73
73
  replacementSelector?: string;
74
74
  replacementSourceId?: string;
75
75
  replacementElementInfo?: ElementInfo;
76
+ replacementSnapshotHtml?: string;
76
77
  removed?: true;
77
78
  },
78
79
  ) => void;
@@ -114,6 +115,7 @@ export function runVisualStructureChange(
114
115
  replacementSelector?: string;
115
116
  replacementSourceId?: string;
116
117
  replacementElementInfo?: ElementInfo;
118
+ replacementSnapshotHtml?: string;
117
119
  },
118
120
  ) {
119
121
  dndHostLog("persist:begin", {
@@ -326,6 +326,10 @@ export interface PendingLiveStructureEdit {
326
326
  replacementSelector?: string;
327
327
  replacementSourceId?: string | null;
328
328
  replacementSignature?: RuntimeStructureNodeSignature;
329
+ /** Expected visual tree captured by the bridge while it still owns the
330
+ * original DOM element. Runtime ids and computed styles are excluded.
331
+ * Missing evidence or later context changes cannot prove replacement. */
332
+ replacementSnapshotSignature?: string;
329
333
  /**
330
334
  * This edit DELETED the subject from the running app. A removal has no
331
335
  * anchor — `anchorSelector`/`placement` carry no meaning for it — so every