@agent-native/core 0.84.0 → 0.84.2

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 (152) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +13 -0
  3. package/corpus/core/docs/content/evals.mdx +34 -12
  4. package/corpus/core/docs/content/locales/ar-SA/evals.mdx +7 -0
  5. package/corpus/core/docs/content/locales/de-DE/evals.mdx +8 -0
  6. package/corpus/core/docs/content/locales/es-ES/evals.mdx +8 -0
  7. package/corpus/core/docs/content/locales/fr-FR/evals.mdx +8 -0
  8. package/corpus/core/docs/content/locales/hi-IN/evals.mdx +8 -0
  9. package/corpus/core/docs/content/locales/ja-JP/evals.mdx +7 -0
  10. package/corpus/core/docs/content/locales/ko-KR/evals.mdx +7 -0
  11. package/corpus/core/docs/content/locales/pt-BR/evals.mdx +8 -0
  12. package/corpus/core/docs/content/locales/zh-CN/evals.mdx +7 -0
  13. package/corpus/core/docs/content/locales/zh-TW/evals.mdx +7 -0
  14. package/corpus/core/docs/design/durable-agent-runs.md +34 -35
  15. package/corpus/core/package.json +1 -1
  16. package/corpus/core/src/agent/engine/builder-engine.ts +81 -9
  17. package/corpus/core/src/agent/run-manager.ts +8 -7
  18. package/corpus/core/src/cli/design-connect.ts +175 -0
  19. package/corpus/core/src/cli/skills.ts +5 -7
  20. package/corpus/core/src/client/AgentPanel.tsx +8 -0
  21. package/corpus/core/src/client/MultiTabAssistantChat.tsx +4 -1
  22. package/corpus/core/src/client/agent-chat-adapter.ts +45 -15
  23. package/corpus/core/src/client/sse-event-processor.ts +31 -4
  24. package/corpus/core/src/eval/define-eval.ts +4 -1
  25. package/corpus/core/src/eval/report.ts +23 -8
  26. package/corpus/core/src/eval/runner.ts +41 -6
  27. package/corpus/core/src/eval/types.ts +11 -0
  28. package/corpus/core/src/templates/workspace-core/.agents/skills/reliable-mutations/SKILL.md +15 -17
  29. package/corpus/templates/content/AGENTS.md +9 -2
  30. package/corpus/templates/content/actions/_database-row-batch.ts +230 -0
  31. package/corpus/templates/content/actions/_database-utils.ts +17 -9
  32. package/corpus/templates/content/actions/_notion-action-utils.ts +26 -0
  33. package/corpus/templates/content/actions/connect-notion-status.ts +13 -4
  34. package/corpus/templates/content/actions/create-and-link-notion-page.ts +23 -0
  35. package/corpus/templates/content/actions/delete-database-items.ts +55 -0
  36. package/corpus/templates/content/actions/delete-document.ts +4 -4
  37. package/corpus/templates/content/actions/disconnect-notion.ts +16 -0
  38. package/corpus/templates/content/actions/duplicate-database-item.ts +1 -1
  39. package/corpus/templates/content/actions/duplicate-database-items.ts +191 -0
  40. package/corpus/templates/content/actions/link-notion-page.ts +10 -7
  41. package/corpus/templates/content/actions/list-notion-links.ts +3 -4
  42. package/corpus/templates/content/actions/pull-notion-page.ts +7 -9
  43. package/corpus/templates/content/actions/push-notion-page.ts +7 -9
  44. package/corpus/templates/content/actions/refresh-notion-sync-status.ts +24 -0
  45. package/corpus/templates/content/actions/resolve-notion-sync-conflict.ts +23 -0
  46. package/corpus/templates/content/actions/search-notion-pages.ts +67 -0
  47. package/corpus/templates/content/actions/unlink-notion-page.ts +23 -0
  48. package/corpus/templates/content/app/components/editor/DocumentDatabase.tsx +27 -34
  49. package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +16 -13
  50. package/corpus/templates/content/app/components/editor/DocumentToolbar.tsx +10 -10
  51. package/corpus/templates/content/app/components/editor/NotionConflictBanner.tsx +1 -1
  52. package/corpus/templates/content/app/components/editor/NotionSyncBar.tsx +18 -9
  53. package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +27 -34
  54. package/corpus/templates/content/app/components/editor/database/navigation-state.ts +10 -4
  55. package/corpus/templates/content/app/components/sidebar/NotionButton.tsx +32 -17
  56. package/corpus/templates/content/app/hooks/use-content-database.ts +35 -0
  57. package/corpus/templates/content/app/hooks/use-notion.ts +108 -99
  58. package/corpus/templates/content/package.json +3 -0
  59. package/corpus/templates/content/parity/README.md +69 -0
  60. package/corpus/templates/content/parity/eval-scenarios.ts +118 -0
  61. package/corpus/templates/content/parity/exceptions.allowlist.ts +17 -0
  62. package/corpus/templates/content/parity/matrix.md +28 -0
  63. package/corpus/templates/content/parity/matrix.ts +606 -0
  64. package/corpus/templates/content/parity/matrix.types.ts +42 -0
  65. package/corpus/templates/content/parity/parity-evals.eval.ts +4 -0
  66. package/corpus/templates/content/parity/render-matrix.ts +90 -0
  67. package/corpus/templates/content/parity/scenario-to-eval.ts +57 -0
  68. package/corpus/templates/content/shared/api.ts +11 -0
  69. package/corpus/templates/design/actions/apply-motion-edit.ts +50 -18
  70. package/corpus/templates/design/actions/get-motion-timeline.ts +4 -14
  71. package/corpus/templates/design/app/components/design/DesignCanvas.tsx +156 -12
  72. package/corpus/templates/design/app/components/design/MotionDock.tsx +2 -3
  73. package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +217 -75
  74. package/corpus/templates/design/app/components/design/QuestionFlow.tsx +39 -39
  75. package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +558 -37
  76. package/corpus/templates/design/app/components/design/bridge/hit-test.bridge.ts +75 -3
  77. package/corpus/templates/design/app/components/design/types.ts +13 -0
  78. package/corpus/templates/design/app/components/layout/Layout.tsx +1 -0
  79. package/corpus/templates/design/app/i18n/zh-TW.ts +17 -0
  80. package/corpus/templates/design/app/i18n-data.ts +245 -0
  81. package/corpus/templates/design/app/pages/DesignEditor.tsx +762 -153
  82. package/corpus/templates/design/changelog/2026-06-30-copying-or-dragging-screen-elements-onto-the-infinite-canvas.md +6 -0
  83. package/corpus/templates/design/changelog/2026-06-30-design-questions-now-use-tighter-editor-typography-and-contr.md +6 -0
  84. package/corpus/templates/design/changelog/2026-06-30-element-drags-can-be-cancelled-with-escape-before-they-commi.md +6 -0
  85. package/corpus/templates/design/changelog/2026-06-30-pending-visual-style-edits-now-warn-before-you-leave-the-edi.md +6 -0
  86. package/corpus/templates/design/changelog/2026-06-30-visual-style-drags-stay-live-while-pending-edits-can-be-appl.md +6 -0
  87. package/corpus/templates/design/changelog/2026-07-01-design-chat-no-longer-shows-a-redundant-context-tab-above-the-composer.md +6 -0
  88. package/corpus/templates/design/changelog/2026-07-01-motion-track-creation-no-longer-fails-in-local-editors.md +6 -0
  89. package/corpus/templates/design/server/plugins/db.ts +10 -0
  90. package/dist/agent/engine/builder-engine.d.ts.map +1 -1
  91. package/dist/agent/engine/builder-engine.js +61 -10
  92. package/dist/agent/engine/builder-engine.js.map +1 -1
  93. package/dist/agent/run-manager.d.ts +8 -7
  94. package/dist/agent/run-manager.d.ts.map +1 -1
  95. package/dist/agent/run-manager.js +8 -7
  96. package/dist/agent/run-manager.js.map +1 -1
  97. package/dist/cli/design-connect.d.ts +2 -0
  98. package/dist/cli/design-connect.d.ts.map +1 -1
  99. package/dist/cli/design-connect.js +140 -0
  100. package/dist/cli/design-connect.js.map +1 -1
  101. package/dist/cli/skills.d.ts.map +1 -1
  102. package/dist/cli/skills.js +5 -7
  103. package/dist/cli/skills.js.map +1 -1
  104. package/dist/client/AgentPanel.d.ts +5 -1
  105. package/dist/client/AgentPanel.d.ts.map +1 -1
  106. package/dist/client/AgentPanel.js +4 -4
  107. package/dist/client/AgentPanel.js.map +1 -1
  108. package/dist/client/MultiTabAssistantChat.d.ts +3 -1
  109. package/dist/client/MultiTabAssistantChat.d.ts.map +1 -1
  110. package/dist/client/MultiTabAssistantChat.js +2 -2
  111. package/dist/client/MultiTabAssistantChat.js.map +1 -1
  112. package/dist/client/agent-chat-adapter.d.ts.map +1 -1
  113. package/dist/client/agent-chat-adapter.js +37 -14
  114. package/dist/client/agent-chat-adapter.js.map +1 -1
  115. package/dist/client/sse-event-processor.d.ts +14 -1
  116. package/dist/client/sse-event-processor.d.ts.map +1 -1
  117. package/dist/client/sse-event-processor.js +21 -5
  118. package/dist/client/sse-event-processor.js.map +1 -1
  119. package/dist/collab/routes.d.ts +1 -1
  120. package/dist/eval/define-eval.d.ts.map +1 -1
  121. package/dist/eval/define-eval.js +2 -1
  122. package/dist/eval/define-eval.js.map +1 -1
  123. package/dist/eval/report.d.ts.map +1 -1
  124. package/dist/eval/report.js +19 -4
  125. package/dist/eval/report.js.map +1 -1
  126. package/dist/eval/runner.d.ts.map +1 -1
  127. package/dist/eval/runner.js +38 -6
  128. package/dist/eval/runner.js.map +1 -1
  129. package/dist/eval/types.d.ts +11 -0
  130. package/dist/eval/types.d.ts.map +1 -1
  131. package/dist/eval/types.js.map +1 -1
  132. package/dist/file-upload/actions/upload-image.d.ts +2 -2
  133. package/dist/observability/routes.d.ts +2 -2
  134. package/dist/progress/routes.d.ts +1 -1
  135. package/dist/resources/handlers.d.ts +3 -3
  136. package/dist/server/transcribe-voice.d.ts +1 -1
  137. package/dist/templates/workspace-core/.agents/skills/reliable-mutations/SKILL.md +15 -17
  138. package/docs/content/evals.mdx +34 -12
  139. package/docs/content/locales/ar-SA/evals.mdx +7 -0
  140. package/docs/content/locales/de-DE/evals.mdx +8 -0
  141. package/docs/content/locales/es-ES/evals.mdx +8 -0
  142. package/docs/content/locales/fr-FR/evals.mdx +8 -0
  143. package/docs/content/locales/hi-IN/evals.mdx +8 -0
  144. package/docs/content/locales/ja-JP/evals.mdx +7 -0
  145. package/docs/content/locales/ko-KR/evals.mdx +7 -0
  146. package/docs/content/locales/pt-BR/evals.mdx +8 -0
  147. package/docs/content/locales/zh-CN/evals.mdx +7 -0
  148. package/docs/content/locales/zh-TW/evals.mdx +7 -0
  149. package/docs/design/durable-agent-runs.md +34 -35
  150. package/package.json +1 -1
  151. package/src/templates/workspace-core/.agents/skills/reliable-mutations/SKILL.md +15 -17
  152. package/corpus/templates/content/server/routes/api/notion/disconnect.post.ts +0 -12
@@ -60,6 +60,7 @@ import {
60
60
  type DeviceFrameType,
61
61
  type ElementInfo,
62
62
  type ElementSelectionIntent,
63
+ type PortableStyleSnapshot,
63
64
  } from "./types";
64
65
 
65
66
  // Re-export so consumers of MultiScreenCanvas can use the same script without
@@ -241,12 +242,18 @@ interface MultiScreenCanvasProps {
241
242
  targetAnchorNodeId?: string;
242
243
  /** DOM insertion placement relative to the anchor node. */
243
244
  targetAnchorPlacement?: "before" | "after" | "inside";
245
+ /** Whether the target should receive an in-flow insert or an absolute child. */
246
+ targetDropMode?: CrossScreenDropMode;
247
+ /** Target anchor rect in the destination iframe/content coordinate space. */
248
+ targetAnchorRect?: CrossScreenHitTestAnchorRect;
244
249
  /** Final drop point in logical overview canvas coordinates. */
245
250
  targetCanvasPoint?: Point;
246
251
  /** Final drop point in the destination iframe/content coordinate space. */
247
252
  targetLocalPoint?: Point;
248
253
  /** Pointer offset from the dragged element's top-left in source iframe px. */
249
254
  sourcePointerOffset?: Point;
255
+ /** Portable computed styles captured in the source iframe before the move. */
256
+ styleSnapshot?: PortableStyleSnapshot;
250
257
  }) => void;
251
258
  // ── Board file (new model) ───────────────────────────────────────────────
252
259
  /**
@@ -338,6 +345,9 @@ interface MultiScreenCanvasProps {
338
345
  sourceId?: string;
339
346
  anchorSourceId?: string;
340
347
  requestId?: string;
348
+ dropMode?: "flow-insert" | "absolute-container";
349
+ sourceRect?: { x: number; y: number; width: number; height: number };
350
+ anchorRect?: { x: number; y: number; width: number; height: number };
341
351
  },
342
352
  ) => boolean | void;
343
353
  /**
@@ -480,7 +490,7 @@ export function hasBoardSurfaceContent(html: string | undefined) {
480
490
  return content.replace(/<!--[\s\S]*?-->/g, "").trim().length > 0;
481
491
  }
482
492
 
483
- const BOARD_SURFACE_RENDER_STYLE = `<style data-agent-native-board-surface-render>html,body{background:transparent!important;background-color:transparent!important;background-image:none!important;}body{margin:0!important;position:relative;overflow:visible;}body>:not([data-agent-native-node-id]):not(style):not(script),body>[data-agent-native-node-id]:not([data-an-primitive]):has([data-agent-native-node-id]),body>[data-agent-native-node-id="body"],body>[data-agent-native-node-id="Body"],body>[data-agent-native-layer-name="body"],body>[data-agent-native-layer-name="Body"],body>[data-agent-native-layer-name="<body>"]{background:transparent!important;background-color:transparent!important;background-image:none!important;box-shadow:none!important;}[data-agent-native-board-backdrop-candidate="true"]{display:none!important;pointer-events:none!important;}</style>`;
493
+ const BOARD_SURFACE_RENDER_STYLE = `<style data-agent-native-board-surface-render>html,body{background:transparent!important;background-color:transparent!important;background-image:none!important;}body{margin:0!important;position:relative;overflow:visible;}body>:not([data-agent-native-node-id]):not(style):not(script),body>[data-agent-native-node-id]:not([data-an-primitive]):not([data-agent-native-preserve-styles="true"]):has([data-agent-native-node-id]),body>[data-agent-native-node-id="body"],body>[data-agent-native-node-id="Body"],body>[data-agent-native-layer-name="body"],body>[data-agent-native-layer-name="Body"],body>[data-agent-native-layer-name="<body>"]{background:transparent!important;background-color:transparent!important;background-image:none!important;box-shadow:none!important;}[data-agent-native-board-backdrop-candidate="true"]{display:none!important;pointer-events:none!important;}</style>`;
484
494
  const BOARD_SURFACE_BACKGROUND = "hsl(0 0% 10%)";
485
495
 
486
496
  const BOARD_SURFACE_BACKDROP_MIN_EDGE_PX = 2400;
@@ -678,7 +688,7 @@ export function getBoardContentKey(args: {
678
688
  boardFileContent: string;
679
689
  boardIsActive: boolean;
680
690
  }) {
681
- return `${args.boardFileId}:layers:${getBoardContentLayerSignature(args.boardFileContent)}`;
691
+ return `${args.boardFileId}:surface`;
682
692
  }
683
693
 
684
694
  function getChromeHandleTransition(chromeSettling: boolean) {
@@ -746,6 +756,7 @@ export interface Point {
746
756
 
747
757
  export type CrossScreenDropPlacement = "before" | "after" | "inside";
748
758
  export type CrossScreenDropAxis = "x" | "y";
759
+ export type CrossScreenDropMode = "flow-insert" | "absolute-container";
749
760
 
750
761
  export interface CrossScreenHitTestAnchorRect {
751
762
  left: number;
@@ -758,6 +769,7 @@ export interface CrossScreenHitTestResult {
758
769
  anchorNodeId?: string;
759
770
  placement?: CrossScreenDropPlacement;
760
771
  axis?: CrossScreenDropAxis;
772
+ dropMode?: CrossScreenDropMode;
761
773
  anchorRect?: CrossScreenHitTestAnchorRect;
762
774
  }
763
775
 
@@ -774,6 +786,14 @@ function isFinitePoint(value: unknown): value is Point {
774
786
  return Number.isFinite(point.x) && Number.isFinite(point.y);
775
787
  }
776
788
 
789
+ function isPortableStyleSnapshot(
790
+ value: unknown,
791
+ ): value is PortableStyleSnapshot {
792
+ if (!value || typeof value !== "object") return false;
793
+ const snapshot = value as Record<string, unknown>;
794
+ return snapshot.version === 1 && Array.isArray(snapshot.nodes);
795
+ }
796
+
777
797
  interface CanvasLayerMarqueeCandidate {
778
798
  screenId: string;
779
799
  info: ElementInfo;
@@ -802,6 +822,10 @@ function isCrossScreenDropAxis(value: unknown): value is CrossScreenDropAxis {
802
822
  return value === "x" || value === "y";
803
823
  }
804
824
 
825
+ function isCrossScreenDropMode(value: unknown): value is CrossScreenDropMode {
826
+ return value === "flow-insert" || value === "absolute-container";
827
+ }
828
+
805
829
  function isCrossScreenHitTestAnchorRect(
806
830
  value: unknown,
807
831
  ): value is CrossScreenHitTestAnchorRect {
@@ -1183,6 +1207,9 @@ export function MultiScreenCanvas({
1183
1207
  /** Board-space point where the ghost is shown (follows the cursor). */
1184
1208
  boardX: number;
1185
1209
  boardY: number;
1210
+ width?: number;
1211
+ height?: number;
1212
+ dimmed?: boolean;
1186
1213
  }
1187
1214
  interface CrossScreenDragTarget {
1188
1215
  /** The screen frame that is the candidate drop target. */
@@ -1206,6 +1233,8 @@ export function MultiScreenCanvas({
1206
1233
  selector: string;
1207
1234
  sourceId?: string;
1208
1235
  sourcePointerOffset?: Point;
1236
+ sourceElementSize?: { width: number; height: number };
1237
+ styleSnapshot?: PortableStyleSnapshot;
1209
1238
  } | null>(null);
1210
1239
  const crossScreenParentDragCleanupRef = useRef<(() => void) | null>(null);
1211
1240
  /** Board-space point from the last cross-screen-drag "move" message. */
@@ -1715,6 +1744,9 @@ export function MultiScreenCanvas({
1715
1744
  axis: isCrossScreenDropAxis(ev.data.axis)
1716
1745
  ? ev.data.axis
1717
1746
  : undefined,
1747
+ dropMode: isCrossScreenDropMode(ev.data.dropMode)
1748
+ ? ev.data.dropMode
1749
+ : undefined,
1718
1750
  anchorRect: isCrossScreenHitTestAnchorRect(ev.data.anchorRect)
1719
1751
  ? ev.data.anchorRect
1720
1752
  : undefined,
@@ -1767,26 +1799,23 @@ export function MultiScreenCanvas({
1767
1799
  const requestCrossScreenDropGuide = (
1768
1800
  candidate: CrossScreenDragTarget,
1769
1801
  boardPoint: Point,
1770
- sourceIsBoard: boolean,
1771
1802
  ) => {
1772
1803
  const requestSeq = ++crossScreenHitTestSeqRef.current;
1773
- void runHitTest(candidate, boardPoint, { preview: sourceIsBoard }).then(
1774
- (hit) => {
1775
- if (crossScreenHitTestSeqRef.current !== requestSeq) return;
1776
- if (crossScreenTargetRef.current?.id !== candidate.id) return;
1777
- const targetScreen = screensRef.current.find(
1778
- (s) => s.id === candidate.id,
1779
- );
1780
- const guide = targetScreen
1781
- ? getCrossScreenDropGuideForHitTest({
1782
- hit,
1783
- targetGeometry: candidate.geometry,
1784
- targetMetadata: getResolvedMetadata(targetScreen),
1785
- })
1786
- : null;
1787
- setCrossScreenDropGuide(sourceIsBoard ? null : guide);
1788
- },
1789
- );
1804
+ void runHitTest(candidate, boardPoint).then((hit) => {
1805
+ if (crossScreenHitTestSeqRef.current !== requestSeq) return;
1806
+ if (crossScreenTargetRef.current?.id !== candidate.id) return;
1807
+ const targetScreen = screensRef.current.find(
1808
+ (s) => s.id === candidate.id,
1809
+ );
1810
+ const guide = targetScreen
1811
+ ? getCrossScreenDropGuideForHitTest({
1812
+ hit,
1813
+ targetGeometry: candidate.geometry,
1814
+ targetMetadata: getResolvedMetadata(targetScreen),
1815
+ })
1816
+ : null;
1817
+ setCrossScreenDropGuide(guide);
1818
+ });
1790
1819
  };
1791
1820
 
1792
1821
  const updateCrossScreenTargetFromBoardPoint = (
@@ -1796,9 +1825,6 @@ export function MultiScreenCanvas({
1796
1825
  crossScreenLastBoardPointRef.current = boardPoint;
1797
1826
  const sourceIsBoard = sourceScreenId === boardFileId;
1798
1827
  setCrossScreenSourceIsBoard(sourceIsBoard);
1799
- setCrossScreenGhost(
1800
- sourceIsBoard ? null : { boardX: boardPoint.x, boardY: boardPoint.y },
1801
- );
1802
1828
  const target = getFrameEntryAtPoint(boardPoint);
1803
1829
  if (target && target.id !== sourceScreenId) {
1804
1830
  const nextTarget = { id: target.id, geometry: target.geometry };
@@ -1807,11 +1833,30 @@ export function MultiScreenCanvas({
1807
1833
  }
1808
1834
  crossScreenTargetRef.current = nextTarget;
1809
1835
  setCrossScreenTarget(nextTarget);
1810
- requestCrossScreenDropGuide(nextTarget, boardPoint, sourceIsBoard);
1836
+ const dragPayload = crossScreenDragMsgRef.current;
1837
+ const sourceElementSize = dragPayload?.sourceElementSize;
1838
+ const sourcePointerOffset = dragPayload?.sourcePointerOffset;
1839
+ setCrossScreenGhost(
1840
+ sourceIsBoard && sourceElementSize && sourcePointerOffset
1841
+ ? {
1842
+ boardX: boardPoint.x - sourcePointerOffset.x,
1843
+ boardY: boardPoint.y - sourcePointerOffset.y,
1844
+ width: sourceElementSize.width,
1845
+ height: sourceElementSize.height,
1846
+ dimmed: true,
1847
+ }
1848
+ : sourceIsBoard
1849
+ ? null
1850
+ : { boardX: boardPoint.x, boardY: boardPoint.y },
1851
+ );
1852
+ requestCrossScreenDropGuide(nextTarget, boardPoint);
1811
1853
  } else {
1812
1854
  clearCrossScreenPreviewGuide();
1813
1855
  crossScreenTargetRef.current = null;
1814
1856
  setCrossScreenTarget(null);
1857
+ setCrossScreenGhost(
1858
+ sourceIsBoard ? null : { boardX: boardPoint.x, boardY: boardPoint.y },
1859
+ );
1815
1860
  clearCrossScreenDropGuide();
1816
1861
  }
1817
1862
  };
@@ -1823,6 +1868,7 @@ export function MultiScreenCanvas({
1823
1868
  selector: string;
1824
1869
  sourceId?: string;
1825
1870
  sourcePointerOffset?: Point;
1871
+ styleSnapshot?: PortableStyleSnapshot;
1826
1872
  },
1827
1873
  lastBoardPoint: Point | null,
1828
1874
  ) => {
@@ -1847,12 +1893,13 @@ export function MultiScreenCanvas({
1847
1893
  targetCanvasPoint: lastBoardPoint,
1848
1894
  targetLocalPoint: lastBoardPoint,
1849
1895
  sourcePointerOffset: payload.sourcePointerOffset,
1896
+ styleSnapshot: payload.styleSnapshot,
1850
1897
  });
1851
1898
  return;
1852
1899
  }
1853
1900
 
1854
1901
  void runHitTest(targetCandidate, lastBoardPoint).then(
1855
- ({ anchorNodeId, placement }) => {
1902
+ ({ anchorNodeId, placement, dropMode, anchorRect }) => {
1856
1903
  const targetAnchorPlacement = isCrossScreenDropPlacement(placement)
1857
1904
  ? placement
1858
1905
  : undefined;
@@ -1867,9 +1914,12 @@ export function MultiScreenCanvas({
1867
1914
  targetScreenId: targetCandidate.id,
1868
1915
  targetAnchorNodeId: anchorNodeId,
1869
1916
  targetAnchorPlacement,
1917
+ targetDropMode: dropMode,
1918
+ targetAnchorRect: anchorRect,
1870
1919
  targetCanvasPoint: lastBoardPoint,
1871
1920
  targetLocalPoint: targetLocalPoint ?? undefined,
1872
1921
  sourcePointerOffset: payload.sourcePointerOffset,
1922
+ styleSnapshot: payload.styleSnapshot,
1873
1923
  });
1874
1924
  },
1875
1925
  );
@@ -1891,7 +1941,22 @@ export function MultiScreenCanvas({
1891
1941
  viewportH?: number;
1892
1942
  elementRect?: CrossScreenDragElementRect;
1893
1943
  pointerOffset?: Point;
1944
+ styleSnapshot?: unknown;
1894
1945
  };
1946
+ const sourcePointerOffset = isFinitePoint(msg.pointerOffset)
1947
+ ? msg.pointerOffset
1948
+ : undefined;
1949
+ const sourceElementSize =
1950
+ msg.elementRect &&
1951
+ Number.isFinite(msg.elementRect.width) &&
1952
+ Number.isFinite(msg.elementRect.height) &&
1953
+ msg.elementRect.width > 0 &&
1954
+ msg.elementRect.height > 0
1955
+ ? { width: msg.elementRect.width, height: msg.elementRect.height }
1956
+ : undefined;
1957
+ const styleSnapshot = isPortableStyleSnapshot(msg.styleSnapshot)
1958
+ ? msg.styleSnapshot
1959
+ : undefined;
1895
1960
 
1896
1961
  if (msg.phase === "cancel") {
1897
1962
  clearCrossScreenDrag();
@@ -1919,9 +1984,9 @@ export function MultiScreenCanvas({
1919
1984
  crossScreenDragMsgRef.current = {
1920
1985
  selector: msg.selector ?? "",
1921
1986
  sourceId: msg.sourceId,
1922
- sourcePointerOffset: isFinitePoint(msg.pointerOffset)
1923
- ? msg.pointerOffset
1924
- : undefined,
1987
+ sourcePointerOffset,
1988
+ sourceElementSize,
1989
+ styleSnapshot,
1925
1990
  };
1926
1991
  stopParentCrossScreenDrag();
1927
1992
  const restorePreviewPointerEvents = mutePreviewIframePointerEvents(
@@ -1944,9 +2009,9 @@ export function MultiScreenCanvas({
1944
2009
  const payload = crossScreenDragMsgRef.current ?? {
1945
2010
  selector: msg.selector ?? "",
1946
2011
  sourceId: msg.sourceId,
1947
- sourcePointerOffset: isFinitePoint(msg.pointerOffset)
1948
- ? msg.pointerOffset
1949
- : undefined,
2012
+ sourcePointerOffset,
2013
+ sourceElementSize,
2014
+ styleSnapshot,
1950
2015
  };
1951
2016
  const lastBoardPoint = crossScreenLastBoardPointRef.current;
1952
2017
  finalizeCrossScreenDrop(
@@ -1993,9 +2058,14 @@ export function MultiScreenCanvas({
1993
2058
  crossScreenDragMsgRef.current = {
1994
2059
  selector: selector ?? "",
1995
2060
  sourceId,
1996
- sourcePointerOffset: isFinitePoint(msg.pointerOffset)
1997
- ? msg.pointerOffset
1998
- : (crossScreenDragMsgRef.current?.sourcePointerOffset ?? undefined),
2061
+ sourcePointerOffset:
2062
+ sourcePointerOffset ??
2063
+ crossScreenDragMsgRef.current?.sourcePointerOffset,
2064
+ sourceElementSize:
2065
+ sourceElementSize ??
2066
+ crossScreenDragMsgRef.current?.sourceElementSize,
2067
+ styleSnapshot:
2068
+ styleSnapshot ?? crossScreenDragMsgRef.current?.styleSnapshot,
1999
2069
  };
2000
2070
 
2001
2071
  const pointerInsideSourceIframe =
@@ -2060,9 +2130,9 @@ export function MultiScreenCanvas({
2060
2130
  const payload = crossScreenDragMsgRef.current ?? {
2061
2131
  selector: msg.selector ?? "",
2062
2132
  sourceId: msg.sourceId,
2063
- sourcePointerOffset: isFinitePoint(msg.pointerOffset)
2064
- ? msg.pointerOffset
2065
- : undefined,
2133
+ sourcePointerOffset,
2134
+ sourceElementSize,
2135
+ styleSnapshot,
2066
2136
  };
2067
2137
  const lastBoardPoint = crossScreenLastBoardPointRef.current;
2068
2138
  finalizeCrossScreenDrop(
@@ -2341,15 +2411,62 @@ export function MultiScreenCanvas({
2341
2411
  draggedNodeId: string | null,
2342
2412
  ): PrimitiveDropTarget | null => {
2343
2413
  if (!onPrimitiveReparentRef.current) return null;
2414
+ const screensForPrimitiveHitTest =
2415
+ boardFileId && boardFileContent !== undefined && boardFrameGeometry
2416
+ ? [
2417
+ ...screensRef.current,
2418
+ {
2419
+ id: boardFileId,
2420
+ filename: "__board__.html",
2421
+ content: boardFileContent,
2422
+ },
2423
+ ]
2424
+ : screensRef.current;
2425
+ const frameGeometryForPrimitiveHitTest =
2426
+ boardFileId && boardFrameGeometry
2427
+ ? {
2428
+ ...frameGeometryRef.current,
2429
+ [boardFileId]: boardFrameGeometry,
2430
+ }
2431
+ : frameGeometryRef.current;
2344
2432
  return getPrimitiveDropTargetForPoint(
2345
2433
  point,
2346
2434
  draggedNodeId,
2347
- screensRef.current,
2348
- frameGeometryRef.current,
2349
- getResolvedMetadata,
2435
+ screensForPrimitiveHitTest,
2436
+ frameGeometryForPrimitiveHitTest,
2437
+ (screen) =>
2438
+ screen.id === boardFileId && boardFrameGeometry
2439
+ ? {
2440
+ width: Math.max(1, boardFrameGeometry.width),
2441
+ height: Math.max(1, boardFrameGeometry.height),
2442
+ }
2443
+ : getResolvedMetadata(screen),
2444
+ {
2445
+ identityCoordinateScreenIds: boardFileId
2446
+ ? new Set([boardFileId])
2447
+ : undefined,
2448
+ },
2350
2449
  );
2351
2450
  },
2352
- [getResolvedMetadata],
2451
+ [boardFileContent, boardFileId, boardFrameGeometry, getResolvedMetadata],
2452
+ );
2453
+
2454
+ const resolvePrimitiveScreenId = useCallback(
2455
+ (nodeId: string): string | null => {
2456
+ const screensForPrimitiveLookup =
2457
+ boardFileId && boardFileContent !== undefined
2458
+ ? [
2459
+ ...screensRef.current,
2460
+ {
2461
+ id: boardFileId,
2462
+ filename: "__board__.html",
2463
+ content: boardFileContent,
2464
+ },
2465
+ ]
2466
+ : screensRef.current;
2467
+ return resolveNodeScreenId(nodeId, screensForPrimitiveLookup);
2468
+ },
2469
+ [boardFileContent, boardFileId],
2353
2470
  );
2354
2471
 
2355
2472
  const finishDrag = useCallback(() => {
@@ -3613,10 +3730,7 @@ export function MultiScreenCanvas({
3613
3730
  (targetId) => !currentFrameIds.includes(targetId),
3614
3731
  );
3615
3732
  if (allCommitted && dropTarget) {
3616
- const sourceScreenId = resolveNodeScreenId(
3617
- state.primaryId,
3618
- screensRef.current,
3619
- );
3733
+ const sourceScreenId = resolvePrimitiveScreenId(state.primaryId);
3620
3734
  if (sourceScreenId) {
3621
3735
  onPrimitiveReparentRef.current?.({
3622
3736
  sourceNodeId: state.primaryId,
@@ -3652,6 +3766,7 @@ export function MultiScreenCanvas({
3652
3766
  getCurrentFrameEntries,
3653
3767
  installDragListeners,
3654
3768
  onPick,
3769
+ resolvePrimitiveScreenId,
3655
3770
  updateFrameGeometry,
3656
3771
  updatePrimitiveDropTarget,
3657
3772
  updateSelectedDraftIds,
@@ -4711,6 +4826,8 @@ export function MultiScreenCanvas({
4711
4826
  boardFileContent,
4712
4827
  boardIsActive,
4713
4828
  });
4829
+ const boardLayerSignature =
4830
+ getBoardContentLayerSignature(boardFileContent);
4714
4831
  const boardRenderContent =
4715
4832
  getBoardSurfaceRenderContent(boardFileContent);
4716
4833
  return (
@@ -4727,6 +4844,8 @@ export function MultiScreenCanvas({
4727
4844
  <DesignCanvas
4728
4845
  content={boardRenderContent}
4729
4846
  contentKey={boardContentKey}
4847
+ runtimeReplacementContent={boardRenderContent}
4848
+ runtimeReplacementKey={`${boardFileId}:layers:${boardLayerSignature}`}
4730
4849
  screenId={boardFileId}
4731
4850
  zoom={100}
4732
4851
  deviceFrame="none"
@@ -5060,22 +5179,27 @@ export function MultiScreenCanvas({
5060
5179
  />
5061
5180
  ) : null}
5062
5181
 
5063
- {/* Cross-screen element drag: small ghost following the board-space cursor. */}
5182
+ {/* Cross-screen element drag: ghost follows the board-space cursor. */}
5064
5183
  {crossScreenGhost &&
5065
- !crossScreenSourceIsBoard &&
5066
- !crossScreenDropGuide ? (
5184
+ (crossScreenSourceIsBoard || !crossScreenDropGuide) ? (
5067
5185
  <span
5068
5186
  data-cross-screen-drag-ghost
5069
- className="pointer-events-none absolute z-50 rounded border border-[var(--design-editor-accent-color)] bg-[var(--design-editor-accent-color)]/20 shadow"
5187
+ className="pointer-events-none absolute z-40 rounded border border-[var(--design-editor-accent-color)] bg-[var(--design-editor-accent-color)]/20 shadow"
5070
5188
  style={{
5071
- // A fixed-size ghost (16×16 canvas-px) centred on the board point.
5072
- // Surface position = pan + (SURFACE_PADDING + canvasCoord) * scale
5189
+ // Board-origin drags use the real layer size/top-left so the
5190
+ // proxy stays above screen iframes; screen-origin drags keep the
5191
+ // older compact cursor ghost.
5073
5192
  left:
5074
- pan.x + (SURFACE_PADDING + crossScreenGhost.boardX) * scale - 8,
5193
+ pan.x +
5194
+ (SURFACE_PADDING + crossScreenGhost.boardX) * scale -
5195
+ (crossScreenGhost.width ? 0 : 8),
5075
5196
  top:
5076
- pan.y + (SURFACE_PADDING + crossScreenGhost.boardY) * scale - 8,
5077
- width: 16,
5078
- height: 16,
5197
+ pan.y +
5198
+ (SURFACE_PADDING + crossScreenGhost.boardY) * scale -
5199
+ (crossScreenGhost.height ? 0 : 8),
5200
+ width: Math.max(1, (crossScreenGhost.width ?? 16) * scale),
5201
+ height: Math.max(1, (crossScreenGhost.height ?? 16) * scale),
5202
+ opacity: crossScreenGhost.dimmed ? 0.4 : 1,
5079
5203
  }}
5080
5204
  />
5081
5205
  ) : null}
@@ -7334,6 +7458,9 @@ export function parsePrimitivesFromScreen(
7334
7458
  const htmlEl = el as HTMLElement;
7335
7459
  const style = htmlEl.style;
7336
7460
  const tag = el.tagName.toLowerCase();
7461
+ const primitiveKind = (
7462
+ el.getAttribute("data-an-primitive") || ""
7463
+ ).toLowerCase();
7337
7464
 
7338
7465
  // Only block elements with explicit absolute positioning are considered
7339
7466
  // positioned primitives drawn by appendCanvasPrimitiveToHtml.
@@ -7347,9 +7474,9 @@ export function parsePrimitivesFromScreen(
7347
7474
  // Validity: must have non-zero size
7348
7475
  if (width <= 0 || height <= 0) return;
7349
7476
 
7350
- // Only explicit auto-layout primitives should accept drops. Plain absolute
7351
- // rectangles are visual layers, not flow containers; treating every div as a
7352
- // container made top-level rectangles disappear into each other on drag.
7477
+ // Only explicit layout primitives and canvas-created rectangles accept
7478
+ // drops. Avoid treating every absolute div as a container, because imported
7479
+ // app markup can contain structural wrappers that should stay inert here.
7353
7480
  const isDiv = tag === "div";
7354
7481
  const isEllipse =
7355
7482
  style.borderRadius === "50%" ||
@@ -7360,8 +7487,13 @@ export function parsePrimitivesFromScreen(
7360
7487
  style.display === "inline-flex" ||
7361
7488
  style.display === "grid" ||
7362
7489
  style.display === "inline-grid";
7490
+ const isCanvasRectangle =
7491
+ isDiv && (primitiveKind === "rectangle" || primitiveKind === "rect");
7363
7492
  const isContainer =
7364
- isDiv && !isEllipse && !isTextAutoSize && isAutoLayout;
7493
+ isDiv &&
7494
+ !isEllipse &&
7495
+ !isTextAutoSize &&
7496
+ (isAutoLayout || isCanvasRectangle);
7365
7497
 
7366
7498
  result.push({
7367
7499
  nodeId,
@@ -7429,7 +7561,31 @@ export function getPrimitiveDropTargetForPoint(
7429
7561
  screens: ScreenFile[],
7430
7562
  frameGeometryById: FrameGeometryById,
7431
7563
  getMetadata: (screen: ScreenFile) => { width: number; height: number },
7564
+ options: { identityCoordinateScreenIds?: ReadonlySet<string> } = {},
7432
7565
  ): PrimitiveDropTarget | null {
7566
+ const toBoardRect = (
7567
+ prim: ParsedScreenPrimitive,
7568
+ frameGeometry: FrameGeometry,
7569
+ metadata: { width: number; height: number },
7570
+ ): FrameGeometry => {
7571
+ if (options.identityCoordinateScreenIds?.has(prim.screenId)) {
7572
+ return {
7573
+ x: prim.localLeft,
7574
+ y: prim.localTop,
7575
+ width: Math.max(1, prim.localWidth),
7576
+ height: Math.max(1, prim.localHeight),
7577
+ };
7578
+ }
7579
+ return primitiveLocalToBoardRect(
7580
+ prim.localLeft,
7581
+ prim.localTop,
7582
+ prim.localWidth,
7583
+ prim.localHeight,
7584
+ frameGeometry,
7585
+ metadata,
7586
+ );
7587
+ };
7588
+
7433
7589
  // Pre-compute the dragged node's board rect so we can exclude its descendants.
7434
7590
  let draggedBoardRect: FrameGeometry | null = null;
7435
7591
  if (draggedNodeId) {
@@ -7440,14 +7596,7 @@ export function getPrimitiveDropTargetForPoint(
7440
7596
  const primitives = parsePrimitivesFromScreen(screen);
7441
7597
  for (const prim of primitives) {
7442
7598
  if (prim.nodeId === draggedNodeId) {
7443
- draggedBoardRect = primitiveLocalToBoardRect(
7444
- prim.localLeft,
7445
- prim.localTop,
7446
- prim.localWidth,
7447
- prim.localHeight,
7448
- frameGeometry,
7449
- metadata,
7450
- );
7599
+ draggedBoardRect = toBoardRect(prim, frameGeometry, metadata);
7451
7600
  break outer;
7452
7601
  }
7453
7602
  }
@@ -7477,14 +7626,7 @@ export function getPrimitiveDropTargetForPoint(
7477
7626
  if (!prim.isContainer) continue;
7478
7627
  if (draggedNodeId && prim.nodeId === draggedNodeId) continue;
7479
7628
 
7480
- const boardRect = primitiveLocalToBoardRect(
7481
- prim.localLeft,
7482
- prim.localTop,
7483
- prim.localWidth,
7484
- prim.localHeight,
7485
- frameGeometry,
7486
- metadata,
7487
- );
7629
+ const boardRect = toBoardRect(prim, frameGeometry, metadata);
7488
7630
 
7489
7631
  // Exclude geometric descendants: a primitive whose board rect is fully
7490
7632
  // contained within the dragged node's board rect is a child/descendant