@agent-native/core 0.182.1 → 0.182.2-nightly-20260917225811

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 (119) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/clips/app/root.tsx +59 -10
  3. package/corpus/templates/clips/changelog/2026-09-17-desktop-popover-closes-when-clicking-away.md +6 -0
  4. package/corpus/templates/clips/changelog/2026-09-17-self-hosted-extension-sign-in.md +6 -0
  5. package/corpus/templates/clips/chrome-extension/src/background.ts +45 -25
  6. package/corpus/templates/clips/chrome-extension/src/content-script.ts +43 -0
  7. package/corpus/templates/clips/desktop/src-tauri/src/lib.rs +55 -6
  8. package/corpus/templates/design/.generated/bridge/editor-chrome.generated.ts +107 -19
  9. package/corpus/templates/design/.generated/bridge/hit-test.generated.ts +1 -0
  10. package/corpus/templates/design/actions/add-localhost-screens.ts +54 -22
  11. package/corpus/templates/design/actions/apply-component-prop-edit.ts +174 -14
  12. package/corpus/templates/design/actions/apply-visual-edit.ts +3 -1
  13. package/corpus/templates/design/actions/create-component.ts +173 -12
  14. package/corpus/templates/design/actions/create-design-from-template.ts +50 -35
  15. package/corpus/templates/design/actions/create-design-version.ts +21 -0
  16. package/corpus/templates/design/actions/create-file.ts +35 -33
  17. package/corpus/templates/design/actions/delete-design.ts +29 -28
  18. package/corpus/templates/design/actions/delete-file.ts +154 -23
  19. package/corpus/templates/design/actions/generate-design.ts +28 -16
  20. package/corpus/templates/design/actions/get-component-details.ts +272 -33
  21. package/corpus/templates/design/actions/get-design-version.ts +9 -1
  22. package/corpus/templates/design/actions/go-to-main-component.ts +37 -35
  23. package/corpus/templates/design/actions/index-components.ts +241 -126
  24. package/corpus/templates/design/actions/migrate-board-objects-to-file.ts +12 -9
  25. package/corpus/templates/design/actions/present-design-variants.ts +68 -72
  26. package/corpus/templates/design/actions/rename-component.ts +294 -0
  27. package/corpus/templates/design/actions/rename-screen.ts +10 -10
  28. package/corpus/templates/design/actions/update-file.ts +327 -330
  29. package/corpus/templates/design/actions/update-screen-source.ts +2 -43
  30. package/corpus/templates/design/app/components/design/EditPanel.tsx +17 -1
  31. package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +228 -24
  32. package/corpus/templates/design/app/components/design/ResponsiveInteractBar.tsx +17 -1
  33. package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +151 -32
  34. package/corpus/templates/design/app/components/design/bridge/hit-test.bridge.ts +4 -0
  35. package/corpus/templates/design/app/components/design/canvas-primitive-style.ts +1 -1
  36. package/corpus/templates/design/app/components/design/code-workbench/CodeWorkbench.tsx +8 -1
  37. package/corpus/templates/design/app/components/design/code-workbench/explorer/FileTree.tsx +55 -33
  38. package/corpus/templates/design/app/components/design/code-workbench/workspace/create-providers.ts +2 -0
  39. package/corpus/templates/design/app/components/design/code-workbench/workspace/inline-provider.ts +4 -2
  40. package/corpus/templates/design/app/components/design/edit-panel/component-section.tsx +273 -60
  41. package/corpus/templates/design/app/components/design/edit-panel/element-classification.ts +9 -2
  42. package/corpus/templates/design/app/components/design/inspector/AutoLayoutMatrix.tsx +57 -9
  43. package/corpus/templates/design/app/components/design/multi-screen/primitive-drop-target.ts +13 -0
  44. package/corpus/templates/design/app/components/design/multi-screen/types.ts +14 -0
  45. package/corpus/templates/design/app/components/design/types.ts +9 -1
  46. package/corpus/templates/design/app/hooks/use-design-versions.ts +3 -2
  47. package/corpus/templates/design/app/i18n-breakpoints.ts +0 -110
  48. package/corpus/templates/design/app/i18n-data.ts +0 -10
  49. package/corpus/templates/design/app/pages/DesignEditor.tsx +669 -161
  50. package/corpus/templates/design/app/pages/design-editor/clone-and-pen-edit.ts +20 -0
  51. package/corpus/templates/design/app/pages/design-editor/code-layer-state.ts +2 -0
  52. package/corpus/templates/design/app/pages/design-editor/commands/apply-design-editor-command.ts +21 -3
  53. package/corpus/templates/design/app/pages/design-editor/commands/apply-file-content-update.ts +2 -0
  54. package/corpus/templates/design/app/pages/design-editor/commands/create-component.ts +44 -3
  55. package/corpus/templates/design/app/pages/design-editor/commands/delete-files.ts +69 -47
  56. package/corpus/templates/design/app/pages/design-editor/commands/duplicate-screen.ts +157 -52
  57. package/corpus/templates/design/app/pages/design-editor/commands/duplicate-selection.ts +76 -1
  58. package/corpus/templates/design/app/pages/design-editor/commands/redo.ts +152 -44
  59. package/corpus/templates/design/app/pages/design-editor/commands/save-file-content.ts +131 -2
  60. package/corpus/templates/design/app/pages/design-editor/commands/screen-visual-duplicate-change.ts +5 -1
  61. package/corpus/templates/design/app/pages/design-editor/commands/undo.ts +26 -1
  62. package/corpus/templates/design/app/pages/design-editor/commands/visual-duplicate-change.ts +1 -0
  63. package/corpus/templates/design/app/pages/design-editor/editor-helpers.ts +58 -8
  64. package/corpus/templates/design/app/pages/design-editor/editor-state.ts +63 -2
  65. package/corpus/templates/design/app/pages/design-editor/history.ts +3 -0
  66. package/corpus/templates/design/app/pages/design-editor/nudge-intent.ts +74 -22
  67. package/corpus/templates/design/app/pages/design-editor/source-publication.ts +14 -2
  68. package/corpus/templates/design/changelog/2026-09-16-design-components-retain-linked-identities-when-edited-and-d.md +6 -0
  69. package/corpus/templates/design/changelog/2026-09-16-fix-design-nested-layer-dragging-and-screen-duplication-pari.md +6 -0
  70. package/corpus/templates/design/changelog/2026-09-17-design-drag-and-drop-keeps-duplicates-cross-screen-moves-and.md +6 -0
  71. package/corpus/templates/design/changelog/2026-09-17-design-preserves-reusable-component-identity-and-props-acros.md +6 -0
  72. package/corpus/templates/design/changelog/2026-09-17-open-vector-paths-no-longer-show-fills-and-interact-dimensio.md +6 -0
  73. package/corpus/templates/design/changelog/2026-09-17-screen-deletion-is-immediate-and-undoable-with-history-that-.md +6 -0
  74. package/corpus/templates/design/e2e/fixtures/design-component-react/index.html +98 -0
  75. package/corpus/templates/design/e2e/fixtures/design-component-react/inline-component.html +65 -0
  76. package/corpus/templates/design/e2e/helpers.ts +6 -1
  77. package/corpus/templates/design/server/lib/design-data-mutation.ts +9 -5
  78. package/corpus/templates/design/server/lib/design-snapshot.ts +28 -16
  79. package/corpus/templates/design/server/lib/design-versions.ts +208 -130
  80. package/corpus/templates/design/server/lib/fusion-screens.ts +46 -20
  81. package/corpus/templates/design/server/lib/import-design-files.ts +5 -1
  82. package/corpus/templates/design/server/lib/localhost-connection.ts +54 -0
  83. package/corpus/templates/design/server/source-workspace.ts +412 -356
  84. package/corpus/templates/design/shared/component-library.ts +36 -6
  85. package/corpus/templates/design/shared/component-model.ts +12 -2
  86. package/corpus/templates/design/shared/local-jsx-visual-edit.ts +231 -5
  87. package/corpus/templates/design/shared/source-mode.ts +22 -0
  88. package/dist/automation/index.d.ts +1 -1
  89. package/dist/client/AssistantChat.d.ts +4 -1
  90. package/dist/client/AssistantChat.js +26 -3
  91. package/dist/client/chat/agentkit-protocol.js +148 -34
  92. package/dist/client/chat/message-components.js +7 -5
  93. package/dist/client/chat/tool-call-display.js +4 -3
  94. package/dist/client/conversation/AgentConversation.js +4 -2
  95. package/dist/client/tool-display.d.ts +9 -0
  96. package/dist/client/tool-display.js +15 -0
  97. package/dist/collab/awareness.d.ts +2 -2
  98. package/dist/collab/routes.d.ts +1 -1
  99. package/dist/localization/core-messages/ar-SA.js +2 -0
  100. package/dist/localization/core-messages/de-DE.js +2 -0
  101. package/dist/localization/core-messages/en-US.d.ts +2 -0
  102. package/dist/localization/core-messages/en-US.js +2 -0
  103. package/dist/localization/core-messages/es-ES.js +2 -0
  104. package/dist/localization/core-messages/fr-FR.js +2 -0
  105. package/dist/localization/core-messages/hi-IN.js +2 -0
  106. package/dist/localization/core-messages/ja-JP.js +2 -0
  107. package/dist/localization/core-messages/ko-KR.js +2 -0
  108. package/dist/localization/core-messages/pt-BR.js +2 -0
  109. package/dist/localization/core-messages/zh-CN.js +2 -0
  110. package/dist/localization/core-messages/zh-TW.js +2 -0
  111. package/dist/localization/core-messages.d.ts +2 -0
  112. package/dist/notifications/routes.d.ts +3 -3
  113. package/dist/observability/routes.d.ts +3 -3
  114. package/dist/resources/handlers.d.ts +1 -1
  115. package/dist/secrets/routes.d.ts +3 -3
  116. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  117. package/dist/server/core-routes-plugin.d.ts +1 -1
  118. package/package.json +3 -3
  119. package/corpus/templates/design/app/components/design/editor/PendingScreenDeletionDialog.tsx +0 -66
@@ -252,6 +252,7 @@ export function runVisualDuplicateChange(
252
252
  applyLocalContentUpdate(nextContent, {
253
253
  refreshPreview: false,
254
254
  forcePreviewFullDocument: true,
255
+ historyBeforeContent: baseContent,
255
256
  // Covers the write landing on the NON-Yjs local fallback stack (the
256
257
  // Yjs UndoManager isn't ready yet — e.g. `!isSynced` right after a
257
258
  // fresh page load). The stampYjsUndoSelection call below covers the
@@ -21,26 +21,76 @@ export function runtimeMultiplicityForElementProvenance(
21
21
  info: ElementInfo | null | undefined,
22
22
  ): number {
23
23
  const provenance = info?.provenance;
24
- if (!provenance?.sourceFile || !provenance.line || !provenance.column) {
24
+ const runtimeComponent = info?.runtimeComponent;
25
+ const hasInvocationProvenance = Boolean(
26
+ runtimeComponent?.sourceFile &&
27
+ runtimeComponent.line &&
28
+ runtimeComponent.column,
29
+ );
30
+ const sourceFile = hasInvocationProvenance
31
+ ? runtimeComponent?.sourceFile
32
+ : provenance?.sourceFile;
33
+ const line = hasInvocationProvenance
34
+ ? runtimeComponent?.line
35
+ : provenance?.line;
36
+ const column = hasInvocationProvenance
37
+ ? runtimeComponent?.column
38
+ : provenance?.column;
39
+ const componentName = hasInvocationProvenance
40
+ ? runtimeComponent?.name
41
+ : provenance?.component;
42
+ if (!sourceFile || !line || !column) {
25
43
  return 1;
26
44
  }
27
45
  let count = 0;
46
+ const invocationKeys = hasInvocationProvenance
47
+ ? new Set<string>()
48
+ : undefined;
28
49
  for (const snapshot of Object.values(snapshots)) {
29
50
  const projection = buildCodeLayerProjection(snapshot.html);
30
51
  for (const node of projection.nodes) {
31
52
  const attrs = node.dataAttributes;
32
53
  if (
33
- attrs["data-source-file"] === provenance.sourceFile &&
34
- Number(attrs["data-source-line"]) === provenance.line &&
35
- Number(attrs["data-source-column"]) === provenance.column &&
36
- (!provenance.component ||
37
- attrs["data-component-name"] === provenance.component)
54
+ hasInvocationProvenance &&
55
+ runtimeComponent?.componentId &&
56
+ attrs["data-agent-native-runtime-component-id"] !==
57
+ runtimeComponent.componentId
58
+ ) {
59
+ continue;
60
+ }
61
+ const sourceFileAttribute = hasInvocationProvenance
62
+ ? "data-source-owner-file"
63
+ : "data-source-file";
64
+ const lineAttribute = hasInvocationProvenance
65
+ ? "data-source-owner-line"
66
+ : "data-source-line";
67
+ const columnAttribute = hasInvocationProvenance
68
+ ? "data-source-owner-column"
69
+ : "data-source-column";
70
+ if (
71
+ attrs[sourceFileAttribute] === sourceFile &&
72
+ Number(attrs[lineAttribute]) === line &&
73
+ Number(attrs[columnAttribute]) === column &&
74
+ (!componentName || attrs["data-component-name"] === componentName)
38
75
  ) {
39
- count += 1;
76
+ if (invocationKeys) {
77
+ invocationKeys.add(
78
+ JSON.stringify([
79
+ runtimeComponent?.componentId ?? "",
80
+ attrs[sourceFileAttribute],
81
+ attrs[lineAttribute],
82
+ attrs[columnAttribute],
83
+ attrs["data-source-owner-key"] ?? "",
84
+ attrs["data-component-name"] ?? "",
85
+ ]),
86
+ );
87
+ } else {
88
+ count += 1;
89
+ }
40
90
  }
41
91
  }
42
92
  }
43
- return Math.max(1, count);
93
+ return Math.max(1, invocationKeys?.size ?? count);
44
94
  }
45
95
 
46
96
  export function buildSignInHrefForDesignIntent(
@@ -599,6 +599,12 @@ export function restorePendingFileContent<
599
599
 
600
600
  export interface FileContentSaveRequest {
601
601
  identityMigrationSourceContent?: string;
602
+ /**
603
+ * CAS base for the durable latest-content replay. Normal saves keep their
604
+ * per-edit base so the serialized chain remains strict; the outbox needs the
605
+ * oldest unacknowledged base so a pagehide can replay the full snapshot.
606
+ */
607
+ unloadExpectedVersionHash?: string;
602
608
  id: string;
603
609
  content: string;
604
610
  syncCollab: boolean;
@@ -610,6 +616,19 @@ export interface FileContentSaveRequest {
610
616
  expectedVersionHash: string;
611
617
  }
612
618
 
619
+ /**
620
+ * A pagehide request must retain its own CAS base. The durable outbox may fold
621
+ * later edits onto the oldest base, but a direct keepalive can race that
622
+ * predecessor and must remain replayable in operation order.
623
+ */
624
+ export function prepareFileContentSaveKeepalive(
625
+ pending: FileContentSaveRequest,
626
+ ): FileContentSaveRequest {
627
+ return pending.unloadExpectedVersionHash === undefined
628
+ ? pending
629
+ : { ...pending, unloadExpectedVersionHash: undefined };
630
+ }
631
+
613
632
  export function coalescePendingFileContentSave(
614
633
  next: FileContentSaveRequest,
615
634
  pending: FileContentSaveRequest | undefined,
@@ -637,10 +656,8 @@ type FileContentSaveRequestsById = Readonly<
637
656
  export function shouldClearLatestUnloadSave(
638
657
  latest: FileContentSaveRequest | undefined,
639
658
  completed: FileContentSaveRequest,
640
- skippedStaleMirror = false,
641
659
  ): boolean {
642
660
  return Boolean(
643
- !skippedStaleMirror &&
644
661
  latest &&
645
662
  latest.id === completed.id &&
646
663
  latest.content === completed.content &&
@@ -650,6 +667,50 @@ export function shouldClearLatestUnloadSave(
650
667
  );
651
668
  }
652
669
 
670
+ /**
671
+ * A completed predecessor advances a newer unload replay from its old CAS
672
+ * base. Mutate the request in place so debounce slots and save chains keep the
673
+ * same request object, then let the caller re-journal its updated payload.
674
+ */
675
+ export function advanceLatestUnloadSaveBase(
676
+ latest: FileContentSaveRequest | undefined,
677
+ completed: FileContentSaveRequest,
678
+ persistedVersionHash: string,
679
+ ): boolean {
680
+ const completedBase =
681
+ completed.unloadExpectedVersionHash ?? completed.expectedVersionHash;
682
+ const latestBase =
683
+ latest?.unloadExpectedVersionHash ?? latest?.expectedVersionHash;
684
+ if (
685
+ !latest ||
686
+ latest === completed ||
687
+ latest.id !== completed.id ||
688
+ latest.operationSource !== completed.operationSource ||
689
+ latest.operationRevision <= completed.operationRevision ||
690
+ latestBase !== completedBase
691
+ ) {
692
+ return false;
693
+ }
694
+ latest.unloadExpectedVersionHash = persistedVersionHash;
695
+ return true;
696
+ }
697
+
698
+ export function shouldClearLatestUnloadSaveForOutboxEntry(
699
+ latest: FileContentSaveRequest | undefined,
700
+ entry: {
701
+ resourceId: string;
702
+ operationSource: string;
703
+ operationRevision: number;
704
+ },
705
+ ): boolean {
706
+ return Boolean(
707
+ latest &&
708
+ latest.id === entry.resourceId &&
709
+ latest.operationSource === entry.operationSource &&
710
+ latest.operationRevision === entry.operationRevision,
711
+ );
712
+ }
713
+
653
714
  /**
654
715
  * Flushes debounce slots through the ordinary serialized mutation path during
655
716
  * React cleanup (route/design changes). Real document unload is handled by the
@@ -170,6 +170,9 @@ export interface FileCreationHistoryEntry {
170
170
  content: string;
171
171
  fileType: string;
172
172
  geometry?: CanvasFrameGeometry;
173
+ preserveCamera?: boolean;
174
+ screenMetadata?: Record<string, unknown>;
175
+ localhostScreen?: Record<string, unknown>;
173
176
  }
174
177
 
175
178
  export interface FileDeletionHistorySnapshot {
@@ -421,6 +421,19 @@ function isRenderedBlockDisplay(display: string | null | undefined): boolean {
421
421
  return display === "block" || display === "list-item";
422
422
  }
423
423
 
424
+ function hasRenderedGridPlacement(
425
+ computedStyles: Record<string, string> | undefined,
426
+ ): boolean {
427
+ return [computedStyles?.gridColumn, computedStyles?.gridRow].some((value) => {
428
+ const normalized = value?.trim().replace(/\s+/g, " ").toLowerCase();
429
+ if (!normalized) return false;
430
+ return normalized.split("/").some((line) => {
431
+ const trimmed = line.trim();
432
+ return trimmed !== "auto" && !/^span(?:\s|$)/.test(trimmed);
433
+ });
434
+ });
435
+ }
436
+
424
437
  export interface ResolveElementNudgeIntentArgs {
425
438
  content: string;
426
439
  source?: CodeLayerSource;
@@ -477,14 +490,45 @@ export function resolveElementNudgeIntent(
477
490
  // round-trip has happened yet and `parentDisplay` is simply absent. An
478
491
  // element that really is inline or a flex child is handled above — the parser
479
492
  // sees those, and a rendered value always wins over this default.
480
- const rendered = args.selectedElement.parentDisplay;
481
- // A rendered grid needs its column count to map an arrow onto the next visual
482
- // cell, and `display: grid` alone does not carry it. Guessing "flex row" walks
483
- // DOM order instead, which is a different element in any multi-column grid.
484
- if (
493
+ const rendered =
494
+ args.selectedElement.parentDisplay ??
495
+ args.selectedElement.parentLayout?.display;
496
+ // A rendered grid needs its track count to map an arrow onto the next visual
497
+ // cell, and `display: grid` alone does not carry it. Reuse the computed track
498
+ // templates already reported by the bridge instead of guessing a flex row.
499
+ const renderedGrid =
485
500
  parsedContainer.kind === "none" &&
486
501
  !escapesFlow(position) &&
487
502
  (rendered === "grid" || rendered === "inline-grid")
503
+ ? describeFlowContainer({
504
+ style: {
505
+ display: rendered,
506
+ ...(args.selectedElement.parentLayout?.gridTemplateColumns
507
+ ? {
508
+ "grid-template-columns":
509
+ args.selectedElement.parentLayout.gridTemplateColumns,
510
+ }
511
+ : {}),
512
+ ...(args.selectedElement.parentLayout?.gridTemplateRows
513
+ ? {
514
+ "grid-template-rows":
515
+ args.selectedElement.parentLayout.gridTemplateRows,
516
+ }
517
+ : {}),
518
+ ...(args.selectedElement.parentLayout?.gridAutoFlow
519
+ ? {
520
+ "grid-auto-flow":
521
+ args.selectedElement.parentLayout.gridAutoFlow,
522
+ }
523
+ : {}),
524
+ },
525
+ })
526
+ : NO_FLOW_CONTAINER;
527
+ if (
528
+ parsedContainer.kind === "none" &&
529
+ !escapesFlow(position) &&
530
+ (rendered === "grid" || rendered === "inline-grid") &&
531
+ (renderedGrid.kind !== "grid" || renderedGrid.lineLength === null)
488
532
  ) {
489
533
  return { kind: "none" };
490
534
  }
@@ -496,29 +540,31 @@ export function resolveElementNudgeIntent(
496
540
  if (
497
541
  parsedContainer.kind === "none" &&
498
542
  !escapesFlow(position) &&
499
- isRenderedFlowDisplay(rendered) &&
543
+ (rendered === "flex" || rendered === "inline-flex") &&
500
544
  !renderedFlexDirection
501
545
  ) {
502
546
  return { kind: "none" };
503
547
  }
504
548
  const container: FlowContainerInfo =
505
549
  parsedContainer.kind === "none" && !escapesFlow(position)
506
- ? isRenderedFlowDisplay(rendered)
507
- ? {
508
- ...NO_FLOW_CONTAINER,
509
- kind: "flex",
510
- axis: renderedFlexDirection?.startsWith("column")
511
- ? "vertical"
512
- : "horizontal",
513
- reversed: renderedFlexDirection?.endsWith("-reverse") ?? false,
514
- }
515
- : // `parent` null means the node is a projection root: it has no flow to
516
- // reorder within, and the bridge reports `parentDisplay: undefined`
517
- // for it exactly as it does for a not-yet-measured selection.
518
- isRenderedBlockDisplay(rendered) ||
519
- (rendered === undefined && parent !== null)
520
- ? BLOCK_FLOW_CONTAINER
521
- : parsedContainer
550
+ ? renderedGrid.kind === "grid"
551
+ ? renderedGrid
552
+ : isRenderedFlowDisplay(rendered)
553
+ ? {
554
+ ...NO_FLOW_CONTAINER,
555
+ kind: "flex",
556
+ axis: renderedFlexDirection?.startsWith("column")
557
+ ? "vertical"
558
+ : "horizontal",
559
+ reversed: renderedFlexDirection?.endsWith("-reverse") ?? false,
560
+ }
561
+ : // `parent` null means the node is a projection root: it has no flow to
562
+ // reorder within, and the bridge reports `parentDisplay: undefined`
563
+ // for it exactly as it does for a not-yet-measured selection.
564
+ isRenderedBlockDisplay(rendered) ||
565
+ (rendered === undefined && parent !== null)
566
+ ? BLOCK_FLOW_CONTAINER
567
+ : parsedContainer
522
568
  : parsedContainer;
523
569
  // Flex/grid paint children by `order` and explicit grid placement, not DOM
524
570
  // position, so moving the node would write a source change that produces no
@@ -552,6 +598,12 @@ export function resolveElementNudgeIntent(
552
598
  ) {
553
599
  return { kind: "none" };
554
600
  }
601
+ if (
602
+ container.kind === "grid" &&
603
+ hasRenderedGridPlacement(args.selectedElement.computedStyles)
604
+ ) {
605
+ return { kind: "none" };
606
+ }
555
607
 
556
608
  const intent = resolveNudgeIntent({
557
609
  direction: args.direction,
@@ -102,9 +102,21 @@ export function resolveSourceBaseForPublication(args: {
102
102
  persistedContent?: string | null;
103
103
  beforeContent: string;
104
104
  }): string {
105
+ const pendingContent = args.pending?.content;
106
+ const migrationSource = args.pending?.identityMigrationSourceContent;
107
+ // Identity repair is a real full-document save. Keep its raw CAS base while
108
+ // that save is still in flight, but switch to the canonical pending bytes as
109
+ // soon as the collab or persisted mirror has acknowledged them.
110
+ if (
111
+ pendingContent !== undefined &&
112
+ migrationSource !== undefined &&
113
+ args.collabContent !== pendingContent &&
114
+ args.persistedContent !== pendingContent
115
+ ) {
116
+ return migrationSource;
117
+ }
105
118
  const raw =
106
- args.pending?.identityMigrationSourceContent ??
107
- args.pending?.content ??
119
+ pendingContent ??
108
120
  args.collabContent ??
109
121
  args.persistedContent ??
110
122
  args.beforeContent;
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: improved
3
+ date: 2026-09-16
4
+ ---
5
+
6
+ Design components retain linked identities when edited and duplicated
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-09-16
4
+ ---
5
+
6
+ Fix Design nested layer dragging and screen duplication parity
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-09-17
4
+ ---
5
+
6
+ Design drag-and-drop keeps duplicates, cross-screen moves, and undo history in sync.
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: improved
3
+ date: 2026-09-17
4
+ ---
5
+
6
+ Design preserves reusable component identity and props across inline and React screens
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-09-17
4
+ ---
5
+
6
+ Open vector paths no longer show fills, and Interact dimensions stay clear of the exit control.
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-09-17
4
+ ---
5
+
6
+ Screen deletion is immediate and undoable, with history that survives reloads.
@@ -0,0 +1,98 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <title>Design component React fixture</title>
6
+ <style>
7
+ body {
8
+ margin: 0;
9
+ min-height: 700px;
10
+ background: #111827;
11
+ color: white;
12
+ font: 16px system-ui;
13
+ }
14
+ main {
15
+ padding: 40px;
16
+ }
17
+ .card {
18
+ display: grid;
19
+ gap: 8px;
20
+ width: 260px;
21
+ padding: 20px;
22
+ border-radius: 14px;
23
+ background: #1f2937;
24
+ }
25
+ .card button {
26
+ padding: 10px 14px;
27
+ border: 0;
28
+ border-radius: 8px;
29
+ background: #6366f1;
30
+ color: white;
31
+ }
32
+ [data-agent-native-component-ref] {
33
+ outline: 1px dashed #f59e0b;
34
+ }
35
+ </style>
36
+ </head>
37
+ <body>
38
+ <main
39
+ data-agent-native-node-id="react-screen"
40
+ data-agent-native-layer-name="React Component Screen"
41
+ data-agent-native-source-kind="react"
42
+ data-agent-native-source-path="src/App.tsx"
43
+ data-agent-native-source-file="src/App.tsx"
44
+ data-agent-native-source-line="1"
45
+ data-agent-native-source-column="1"
46
+ >
47
+ <div
48
+ data-agent-native-node-id="promotion-target"
49
+ data-agent-native-layer-name="Promotion target"
50
+ >
51
+ Promote me
52
+ </div>
53
+ <section
54
+ class="card"
55
+ data-agent-native-node-id="card-main"
56
+ data-agent-native-layer-name="Card Main"
57
+ data-agent-native-component="ReusableCard"
58
+ data-agent-native-component-id="cmp-reusable-card"
59
+ data-agent-native-source-file="src/App.tsx"
60
+ data-agent-native-source-line="3"
61
+ data-agent-native-source-column="1"
62
+ data-source-framework="react"
63
+ data-source-file="src/App.tsx"
64
+ data-source-line="3"
65
+ data-source-column="1"
66
+ data-component-name="ReusableCard"
67
+ >
68
+ <strong data-agent-native-node-id="card-title">Canonical card</strong>
69
+ <button
70
+ data-agent-native-node-id="card-button"
71
+ data-agent-native-prop-label="Open"
72
+ >
73
+ Open
74
+ </button>
75
+ </section>
76
+ <section
77
+ class="card"
78
+ data-agent-native-node-id="card-instance"
79
+ data-agent-native-layer-name="Card Instance"
80
+ data-agent-native-component="ReusableCard"
81
+ data-agent-native-component-ref="cmp-reusable-card"
82
+ data-agent-native-component-overrides="%7B%22card-title%22%3A%7B%22textContent%22%3A%22Instance%20override%22%7D%7D"
83
+ >
84
+ <strong
85
+ data-agent-native-node-id="card-instance-title"
86
+ data-agent-native-component-source-node-id="card-title"
87
+ >Instance override</strong
88
+ >
89
+ <button
90
+ data-agent-native-node-id="card-instance-button"
91
+ data-agent-native-component-source-node-id="card-button"
92
+ >
93
+ Open
94
+ </button>
95
+ </section>
96
+ </main>
97
+ </body>
98
+ </html>
@@ -0,0 +1,65 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <title>Inline component fixture</title>
6
+ </head>
7
+ <body style="margin: 0; position: relative; min-height: 900px">
8
+ <aside
9
+ data-agent-native-node-id="legacy-copy"
10
+ data-agent-native-layer-name="Legacy same-name copy"
11
+ data-agent-native-component="ReusableCard"
12
+ style="
13
+ position: absolute;
14
+ left: 40px;
15
+ top: 40px;
16
+ width: 180px;
17
+ height: 80px;
18
+ background: #ddd;
19
+ "
20
+ >
21
+ Legacy
22
+ </aside>
23
+ <section
24
+ data-agent-native-node-id="card-main"
25
+ data-agent-native-layer-name="Card Main"
26
+ style="
27
+ position: absolute;
28
+ left: 300px;
29
+ top: 40px;
30
+ width: 420px;
31
+ height: 360px;
32
+ padding: 20px;
33
+ background: #1f2937;
34
+ color: white;
35
+ "
36
+ >
37
+ <div
38
+ data-agent-native-node-id="card-body"
39
+ data-agent-native-layer-name="Card Body"
40
+ style="display: grid; gap: 8px"
41
+ >
42
+ <strong data-agent-native-node-id="card-title"
43
+ ><span data-agent-native-node-id="card-title-text"
44
+ >Canonical card</span
45
+ ></strong
46
+ >
47
+ <button
48
+ data-agent-native-node-id="card-button"
49
+ data-agent-native-layer-name="Card Action"
50
+ data-agent-native-prop-label="Open"
51
+ style="width: 120px; padding: 10px; background: #6366f1; color: white"
52
+ >
53
+ Open
54
+ </button>
55
+ </div>
56
+ <div
57
+ data-agent-native-node-id="card-slot"
58
+ data-agent-native-layer-name="Card Slot"
59
+ style="min-height: 70px; padding: 10px; background: #374151"
60
+ >
61
+ Reparent target
62
+ </div>
63
+ </section>
64
+ </body>
65
+ </html>
@@ -442,7 +442,12 @@ export async function enterInteractView(
442
442
  .locator("[data-frame-full-view]")
443
443
  : page.locator("[data-frame-full-view]").last();
444
444
  await expect(fullView).toHaveCount(1);
445
- await fullView.click();
445
+ // The screen card can extend beneath the fixed inspector at narrow canvas
446
+ // widths; invoke the button without relying on the panel's overlapping
447
+ // physical hit area.
448
+ await fullView.evaluate((element) => {
449
+ (element as HTMLButtonElement).click();
450
+ });
446
451
  // The overview screen shells are the boundary that actually unmounts; the
447
452
  // toolbar's own Interact button stays mounted and merely becomes pressed.
448
453
  await expect(page.locator("[data-screen-shell]")).toHaveCount(0);
@@ -2,6 +2,10 @@ import { assertAccess } from "@agent-native/core/sharing";
2
2
  import { and, eq, isNull, sql } from "drizzle-orm";
3
3
 
4
4
  import { getDb, schema } from "../db/index.js";
5
+ import {
6
+ designSourceMutationLockKey,
7
+ lockDesignFilesTable,
8
+ } from "../source-workspace.js";
5
9
 
6
10
  const DEFAULT_MAX_ATTEMPTS = 8;
7
11
  const CONFLICT_BACKOFF_MS = 8;
@@ -199,11 +203,10 @@ async function mutateDesignDataUnlocked({
199
203
  // Keep the design-data CAS and HTML rewrites in one transaction.
200
204
  // ponytail: reuse the existing design-file lock; split by design only
201
205
  // if breakpoint edits become a measurable multi-tenant bottleneck.
202
- await (
203
- tx as unknown as {
204
- execute: (query: unknown) => Promise<unknown>;
205
- }
206
- ).execute(sql`LOCK TABLE design_files IN SHARE ROW EXCLUSIVE MODE`);
206
+ await tx.execute(
207
+ sql`SELECT pg_advisory_xact_lock(hashtextextended(${designSourceMutationLockKey(designId)}, 0::bigint))`,
208
+ );
209
+ await lockDesignFilesTable(tx);
207
210
  }
208
211
 
209
212
  const [currentRow] = await tx
@@ -233,6 +236,7 @@ async function mutateDesignDataUnlocked({
233
236
  })
234
237
  .from(schema.designFiles)
235
238
  .where(eq(schema.designFiles.designId, designId))
239
+ .for("update")
236
240
  : [];
237
241
  const revisionConditions = [
238
242
  eq(schema.designs.id, designId),