@agent-native/core 0.80.9 → 0.80.11

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 (148) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +13 -0
  3. package/corpus/core/package.json +1 -1
  4. package/corpus/core/src/agent/production-agent.ts +108 -15
  5. package/corpus/core/src/cli/design-connect.ts +28 -2
  6. package/corpus/core/src/cli/skills.ts +45 -24
  7. package/corpus/core/src/client/AssistantChat.tsx +123 -26
  8. package/corpus/core/src/client/sse-event-processor.ts +9 -0
  9. package/corpus/templates/assets/.agents/skills/image-generation/SKILL.md +9 -2
  10. package/corpus/templates/assets/AGENTS.md +4 -0
  11. package/corpus/templates/assets/actions/_tool-activity.ts +46 -0
  12. package/corpus/templates/assets/actions/generate-image-batch.ts +26 -5
  13. package/corpus/templates/assets/actions/generate-image.ts +118 -67
  14. package/corpus/templates/assets/actions/list-draft-assets.ts +50 -0
  15. package/corpus/templates/assets/actions/rerun-generation-run.ts +52 -31
  16. package/corpus/templates/assets/app/components/create/RecentDraftsSection.tsx +100 -0
  17. package/corpus/templates/assets/app/i18n/zh-TW.ts +7 -0
  18. package/corpus/templates/assets/app/i18n-data.ts +61 -0
  19. package/corpus/templates/assets/app/routes/_index.tsx +4 -0
  20. package/corpus/templates/assets/app/routes/library.tsx +145 -38
  21. package/corpus/templates/assets/changelog/2026-06-29-image-generation-can-now-render-requested-text-and-respect-b.md +6 -0
  22. package/corpus/templates/assets/changelog/2026-06-30-image-generation-no-longer-looks-stuck-while-the-provider-is-still-working.md +6 -0
  23. package/corpus/templates/assets/changelog/2026-06-30-tagged-presets-now-control-the-image-aspect-ratio.md +6 -0
  24. package/corpus/templates/assets/server/lib/generation.ts +135 -11
  25. package/corpus/templates/assets/shared/api.ts +4 -0
  26. package/corpus/templates/clips/actions/list-ai-requests.ts +60 -0
  27. package/corpus/templates/clips/app/hooks/use-auto-title.ts +24 -25
  28. package/corpus/templates/design/DESIGN-STUDIO-PLAN.md +717 -0
  29. package/corpus/templates/design/actions/add-breakpoint.ts +143 -0
  30. package/corpus/templates/design/actions/add-localhost-screens.ts +5 -0
  31. package/corpus/templates/design/actions/apply-a11y-fix.ts +317 -0
  32. package/corpus/templates/design/actions/apply-component-prop-edit.ts +441 -0
  33. package/corpus/templates/design/actions/apply-design-state.ts +213 -0
  34. package/corpus/templates/design/actions/apply-design-token-edit.ts +202 -0
  35. package/corpus/templates/design/actions/apply-motion-edit.ts +413 -0
  36. package/corpus/templates/design/actions/apply-shader-fill.ts +404 -0
  37. package/corpus/templates/design/actions/apply-visual-edit.ts +191 -5
  38. package/corpus/templates/design/actions/capture-design-state.ts +224 -0
  39. package/corpus/templates/design/actions/connect-builder-app.ts +162 -0
  40. package/corpus/templates/design/actions/create-component.ts +447 -0
  41. package/corpus/templates/design/actions/create-design-branch.ts +263 -0
  42. package/corpus/templates/design/actions/create-design-state.ts +162 -0
  43. package/corpus/templates/design/actions/delete-design-state.ts +55 -0
  44. package/corpus/templates/design/actions/delete-design.ts +20 -0
  45. package/corpus/templates/design/actions/deploy-design-preview.ts +275 -0
  46. package/corpus/templates/design/actions/get-component-details.ts +251 -0
  47. package/corpus/templates/design/actions/get-design-branch-diff.ts +362 -0
  48. package/corpus/templates/design/actions/get-design-review.ts +314 -0
  49. package/corpus/templates/design/actions/get-design-surface-index.ts +582 -0
  50. package/corpus/templates/design/actions/get-motion-timeline.ts +99 -0
  51. package/corpus/templates/design/actions/index-components.ts +258 -0
  52. package/corpus/templates/design/actions/index-design-tokens.ts +277 -0
  53. package/corpus/templates/design/actions/list-design-extensions.ts +309 -0
  54. package/corpus/templates/design/actions/list-design-source-capabilities.ts +153 -0
  55. package/corpus/templates/design/actions/list-design-states.ts +90 -0
  56. package/corpus/templates/design/actions/migrate-inline-design-to-app.ts +298 -0
  57. package/corpus/templates/design/actions/open-component-source.ts +242 -0
  58. package/corpus/templates/design/actions/preview-component-prop-edit.ts +260 -0
  59. package/corpus/templates/design/actions/preview-design-token-edit.ts +113 -0
  60. package/corpus/templates/design/actions/preview-shader-fill.ts +187 -0
  61. package/corpus/templates/design/actions/remove-breakpoint.ts +103 -0
  62. package/corpus/templates/design/actions/remove-motion-timeline.ts +196 -0
  63. package/corpus/templates/design/actions/run-design-audit.ts +436 -0
  64. package/corpus/templates/design/actions/run-design-extension-action.ts +284 -0
  65. package/corpus/templates/design/actions/set-active-breakpoint.ts +39 -0
  66. package/corpus/templates/design/app/components/design/DesignCanvas.tsx +1550 -82
  67. package/corpus/templates/design/app/components/design/DesignExtensionsPanel.tsx +649 -43
  68. package/corpus/templates/design/app/components/design/DrawOverlay.tsx +1 -1
  69. package/corpus/templates/design/app/components/design/EditPanel.tsx +1788 -52
  70. package/corpus/templates/design/app/components/design/LayersPanel.tsx +114 -33
  71. package/corpus/templates/design/app/components/design/LocalSourceEditBanner.tsx +157 -0
  72. package/corpus/templates/design/app/components/design/MotionDock.tsx +1071 -0
  73. package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +1053 -99
  74. package/corpus/templates/design/app/components/design/ReviewPanel.tsx +766 -0
  75. package/corpus/templates/design/app/components/design/StatesPanel.tsx +581 -0
  76. package/corpus/templates/design/app/components/design/TokensPanel.tsx +578 -0
  77. package/corpus/templates/design/app/components/design/canvas-primitive-style.ts +241 -0
  78. package/corpus/templates/design/app/components/design/index.ts +16 -0
  79. package/corpus/templates/design/app/components/design/inspector/AutoLayoutMatrix.tsx +15 -6
  80. package/corpus/templates/design/app/components/design/inspector/DesignColorPicker.tsx +66 -3
  81. package/corpus/templates/design/app/components/design/inspector/InspectorAiActions.tsx +145 -0
  82. package/corpus/templates/design/app/components/design/inspector/SHADER_INTEGRATION.md +42 -12
  83. package/corpus/templates/design/app/components/visual-editor/DrawOverlay.tsx +1 -1
  84. package/corpus/templates/design/app/hooks/useAgentEditRequest.ts +131 -0
  85. package/corpus/templates/design/app/hooks/useDesignHotkeys.ts +3 -1
  86. package/corpus/templates/design/app/i18n/zh-TW.ts +32 -0
  87. package/corpus/templates/design/app/i18n-data.ts +280 -0
  88. package/corpus/templates/design/app/pages/DesignEditor.tsx +3087 -490
  89. package/corpus/templates/design/changelog/2026-06-29-added-a-review-panel-in-the-design-editor-s-inspector-with-a.md +6 -0
  90. package/corpus/templates/design/changelog/2026-06-29-copying-and-pasting-layers-no-longer-shows-success-notificat.md +6 -0
  91. package/corpus/templates/design/changelog/2026-06-29-device-presets-in-all-screens-view-resize-the-selected-previ.md +6 -0
  92. package/corpus/templates/design/changelog/2026-06-29-layer-and-canvas-drags-keep-the-layer-list-stable-while-chan.md +6 -0
  93. package/corpus/templates/design/changelog/2026-06-29-layer-moves-can-be-undone-and-redone-without-flashing-the-ca.md +6 -0
  94. package/corpus/templates/design/changelog/2026-06-29-screen-previews-use-a-single-blue-hover-border-in-all-screen.md +6 -0
  95. package/corpus/templates/design/changelog/2026-06-29-selected-containers-show-draggable-padding-and-gap-guides-on.md +6 -0
  96. package/corpus/templates/design/changelog/2026-06-29-the-design-editor-adds-a-studio-layer-with-tokens-respon.md +6 -0
  97. package/corpus/templates/design/changelog/2026-06-30-accessibility-findings-now-offer-a-one-click-fix.md +6 -0
  98. package/corpus/templates/design/changelog/2026-06-30-component-instances-now-have-editable-props-in-the-inspe.md +6 -0
  99. package/corpus/templates/design/changelog/2026-06-30-design-token-edits-now-stay-visible-in-previews-and-token-li.md +6 -0
  100. package/corpus/templates/design/changelog/2026-06-30-inspect-code-now-shows-the-elements-opening-tag-at-a-gla.md +6 -0
  101. package/corpus/templates/design/changelog/2026-06-30-shader-fill-presets-can-now-be-applied-to-an-element.md +6 -0
  102. package/corpus/templates/design/changelog/2026-06-30-the-motion-timeline-can-now-add-a-track-to-any-element-a.md +6 -0
  103. package/corpus/templates/design/changelog/2026-06-30-visual-editor-selection-layer-paint-and-drawing-controls-are-more-reliable.md +6 -0
  104. package/corpus/templates/design/e2e/global-setup.ts +94 -2
  105. package/corpus/templates/design/e2e/helpers.ts +10 -4
  106. package/corpus/templates/design/server/db/schema.ts +123 -0
  107. package/corpus/templates/design/server/plugins/db.ts +90 -0
  108. package/corpus/templates/design/shared/builder-app.ts +297 -0
  109. package/corpus/templates/design/shared/capability-resolver.ts +123 -0
  110. package/corpus/templates/design/shared/capture-sanitize.ts +70 -0
  111. package/corpus/templates/design/shared/code-layer.ts +1016 -71
  112. package/corpus/templates/design/shared/component-model.ts +274 -0
  113. package/corpus/templates/design/shared/design-review.ts +275 -0
  114. package/corpus/templates/design/shared/design-source-capabilities.ts +286 -0
  115. package/corpus/templates/design/shared/design-state.ts +112 -0
  116. package/corpus/templates/design/shared/design-surface-index.ts +258 -0
  117. package/corpus/templates/design/shared/motion-compiler.ts +349 -0
  118. package/corpus/templates/design/shared/motion-timeline.ts +193 -0
  119. package/corpus/templates/design/shared/resolve-tweaks.ts +32 -9
  120. package/corpus/templates/design/shared/responsive-classes.ts +452 -0
  121. package/corpus/templates/design/shared/shader-fill.ts +323 -0
  122. package/corpus/templates/design/shared/source-mode.ts +245 -0
  123. package/corpus/templates/plan/app/components/plan/CanvasArea.tsx +6 -2
  124. package/corpus/templates/plan/changelog/2026-06-29-plan-canvases-open-without-an-initial-pan-and-zoom-flicker.md +6 -0
  125. package/dist/agent/production-agent.d.ts.map +1 -1
  126. package/dist/agent/production-agent.js +94 -10
  127. package/dist/agent/production-agent.js.map +1 -1
  128. package/dist/cli/design-connect.d.ts.map +1 -1
  129. package/dist/cli/design-connect.js +28 -2
  130. package/dist/cli/design-connect.js.map +1 -1
  131. package/dist/cli/skills.d.ts.map +1 -1
  132. package/dist/cli/skills.js +37 -21
  133. package/dist/cli/skills.js.map +1 -1
  134. package/dist/client/AssistantChat.d.ts +14 -0
  135. package/dist/client/AssistantChat.d.ts.map +1 -1
  136. package/dist/client/AssistantChat.js +115 -22
  137. package/dist/client/AssistantChat.js.map +1 -1
  138. package/dist/client/sse-event-processor.d.ts.map +1 -1
  139. package/dist/client/sse-event-processor.js +10 -0
  140. package/dist/client/sse-event-processor.js.map +1 -1
  141. package/dist/collab/routes.d.ts +1 -1
  142. package/dist/file-upload/actions/upload-image.d.ts +2 -2
  143. package/dist/notifications/routes.d.ts +2 -2
  144. package/dist/observability/routes.d.ts +8 -8
  145. package/dist/resources/handlers.d.ts +2 -2
  146. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  147. package/dist/server/transcribe-voice.d.ts +1 -1
  148. package/package.json +1 -1
@@ -170,7 +170,7 @@ export interface LayersPanelProps {
170
170
  canMoveLayer?: (intent: LayersPanelMoveIntent) => boolean;
171
171
  }
172
172
 
173
- interface FlatLayerRow {
173
+ export interface FlatLayerRow {
174
174
  node: LayersPanelNode;
175
175
  rowKey: string;
176
176
  depth: number;
@@ -412,6 +412,65 @@ function collectAncestorIds(
412
412
  return Array.from(ancestors);
413
413
  }
414
414
 
415
+ export function getLayerSelectionAnchorFromExternalSelection(args: {
416
+ selectedIds: readonly string[];
417
+ selectableVisibleIds: readonly string[];
418
+ }): string | null {
419
+ const selectableVisibleIdSet = new Set(args.selectableVisibleIds);
420
+ return (
421
+ [...args.selectedIds]
422
+ .reverse()
423
+ .find((id) => selectableVisibleIdSet.has(id)) ?? null
424
+ );
425
+ }
426
+
427
+ export function getTreeOrderedLayerIds(
428
+ ids: readonly string[],
429
+ visibleRows: readonly FlatLayerRow[],
430
+ ): string[] {
431
+ const idSet = new Set(ids.filter((id) => id && !id.startsWith("__")));
432
+ const orderedIds = visibleRows
433
+ .map((row) => row.node.id)
434
+ .filter((id) => idSet.has(id));
435
+ const orderedIdSet = new Set(orderedIds);
436
+ return [
437
+ ...orderedIds,
438
+ ...ids.filter((id) => id && !id.startsWith("__") && !orderedIdSet.has(id)),
439
+ ];
440
+ }
441
+
442
+ export function getDraggedLayerIdsForRows(args: {
443
+ selectedIds: readonly string[];
444
+ nodeId: string;
445
+ visibleRows: readonly FlatLayerRow[];
446
+ }): string[] {
447
+ const rawDraggedIds = args.selectedIds.includes(args.nodeId)
448
+ ? getTreeOrderedLayerIds(args.selectedIds, args.visibleRows)
449
+ : [args.nodeId];
450
+ const draggedIdSet = new Set(rawDraggedIds);
451
+ return rawDraggedIds.filter((id) => {
452
+ const rowForId = args.visibleRows.find((row) => row.node.id === id);
453
+ return !rowForId?.ancestorIds.some((ancestorId) =>
454
+ draggedIdSet.has(ancestorId),
455
+ );
456
+ });
457
+ }
458
+
459
+ export function shouldResyncLayerSelectionAnchor(args: {
460
+ selectionSignature: string;
461
+ lastPanelSelectionSignature: string;
462
+ currentAnchor: string | null;
463
+ selectableVisibleIds: readonly string[];
464
+ }) {
465
+ const anchorStillVisible =
466
+ args.currentAnchor !== null &&
467
+ args.selectableVisibleIds.includes(args.currentAnchor);
468
+ return (
469
+ args.selectionSignature !== args.lastPanelSelectionSignature ||
470
+ !anchorStillVisible
471
+ );
472
+ }
473
+
415
474
  function layerCanShowBadge(node: LayersPanelNode) {
416
475
  return (
417
476
  node.type === "file" ||
@@ -451,6 +510,7 @@ export function LayersPanel({
451
510
  const labels = useMemo(() => mergeLabels(labelsProp, t), [labelsProp, t]);
452
511
  const selectedIdSet = useMemo(() => new Set(selectedIds), [selectedIds]);
453
512
  const selectedIdsRef = useRef<readonly string[]>(selectedIds);
513
+ const lastPanelSelectionSignatureRef = useRef(selectedIds.join("\0"));
454
514
  const expandedIdSet = useMemo(() => new Set(expandedIds), [expandedIds]);
455
515
  const lastSelectionAnchorRef = useRef<string | null>(selectedIds[0] ?? null);
456
516
  const searchInputRef = useRef<HTMLInputElement>(null);
@@ -486,9 +546,35 @@ export function LayersPanel({
486
546
  [roots, selectedIdSet],
487
547
  );
488
548
 
549
+ const selectableVisibleIds = useMemo(
550
+ () =>
551
+ visibleRows
552
+ .map(({ node }) => node)
553
+ .filter((node) => node.selectable !== false)
554
+ .map((node) => node.id),
555
+ [visibleRows],
556
+ );
557
+
489
558
  useLayoutEffect(() => {
490
559
  selectedIdsRef.current = selectedIds;
491
- }, [selectedIds]);
560
+ const signature = selectedIds.join("\0");
561
+ if (
562
+ !shouldResyncLayerSelectionAnchor({
563
+ selectionSignature: signature,
564
+ lastPanelSelectionSignature: lastPanelSelectionSignatureRef.current,
565
+ currentAnchor: lastSelectionAnchorRef.current,
566
+ selectableVisibleIds,
567
+ })
568
+ ) {
569
+ return;
570
+ }
571
+ lastPanelSelectionSignatureRef.current = signature;
572
+ lastSelectionAnchorRef.current =
573
+ getLayerSelectionAnchorFromExternalSelection({
574
+ selectedIds,
575
+ selectableVisibleIds,
576
+ });
577
+ }, [selectableVisibleIds, selectedIds]);
492
578
 
493
579
  useEffect(() => {
494
580
  if (selectedAncestorIds.length === 0) return;
@@ -522,15 +608,6 @@ export function LayersPanel({
522
608
  return () => window.cancelAnimationFrame(frame);
523
609
  }, [selectedScrollRowKey]);
524
610
 
525
- const selectableVisibleIds = useMemo(
526
- () =>
527
- visibleRows
528
- .map(({ node }) => node)
529
- .filter((node) => node.selectable !== false)
530
- .map((node) => node.id),
531
- [visibleRows],
532
- );
533
-
534
611
  const selectNode = useCallback(
535
612
  (
536
613
  id: string,
@@ -582,6 +659,7 @@ export function LayersPanel({
582
659
  if (!options.range) {
583
660
  lastSelectionAnchorRef.current = id;
584
661
  }
662
+ lastPanelSelectionSignatureRef.current = nextIds.join("\0");
585
663
  onSelectionChange(nextIds, { id, selectedIds: nextIds, ...options });
586
664
  },
587
665
  [onSelectionChange, selectableVisibleIds],
@@ -909,7 +987,8 @@ function LayerRow({
909
987
  const selectable = node.selectable !== false;
910
988
  const lockable = node.lockable !== false && Boolean(onToggleLocked);
911
989
  const hideable = node.hideable !== false && Boolean(onToggleHidden);
912
- const draggable = selectable && Boolean(onMoveLayer);
990
+ const dragEligible = selectable && !node.locked && !node.hidden;
991
+ const draggable = dragEligible && Boolean(onMoveLayer);
913
992
  const canDropInside = layerCanDropInside(node, hasChildren);
914
993
  const activeDrop =
915
994
  dropIndicator?.targetId === node.id ? dropIndicator.placement : null;
@@ -947,12 +1026,15 @@ function LayerRow({
947
1026
  };
948
1027
 
949
1028
  const handleKeyDown = (event: KeyboardEvent<HTMLButtonElement>) => {
950
- const focusVisibleButton = (nextIndex: number) => {
1029
+ const getFocusableButtons = () => {
951
1030
  const tree = event.currentTarget.closest('[role="tree"]');
952
- if (!tree) return false;
953
- const buttons = Array.from(
1031
+ if (!tree) return [];
1032
+ return Array.from(
954
1033
  tree.querySelectorAll<HTMLButtonElement>("[data-layer-row-button]"),
955
1034
  ).filter((button) => !button.disabled);
1035
+ };
1036
+ const focusVisibleButton = (nextIndex: number) => {
1037
+ const buttons = getFocusableButtons();
956
1038
  const target =
957
1039
  buttons[Math.max(0, Math.min(buttons.length - 1, nextIndex))];
958
1040
  target?.focus();
@@ -970,6 +1052,8 @@ function LayerRow({
970
1052
  const currentIndex = visibleRows.findIndex(
971
1053
  (visibleRow) => visibleRow.rowKey === row.rowKey,
972
1054
  );
1055
+ const focusableButtons = getFocusableButtons();
1056
+ const currentFocusableIndex = focusableButtons.indexOf(event.currentTarget);
973
1057
 
974
1058
  if (event.key === "Enter" || event.key === " " || event.key === "Space") {
975
1059
  event.preventDefault();
@@ -989,12 +1073,12 @@ function LayerRow({
989
1073
  }
990
1074
  if (event.key === "ArrowDown") {
991
1075
  event.preventDefault();
992
- focusVisibleButton(currentIndex + 1);
1076
+ focusVisibleButton(currentFocusableIndex + 1);
993
1077
  return;
994
1078
  }
995
1079
  if (event.key === "ArrowUp") {
996
1080
  event.preventDefault();
997
- focusVisibleButton(currentIndex - 1);
1081
+ focusVisibleButton(currentFocusableIndex - 1);
998
1082
  return;
999
1083
  }
1000
1084
  if (event.key === "Home") {
@@ -1004,7 +1088,7 @@ function LayerRow({
1004
1088
  }
1005
1089
  if (event.key === "End") {
1006
1090
  event.preventDefault();
1007
- focusVisibleButton(visibleRows.length - 1);
1091
+ focusVisibleButton(focusableButtons.length - 1);
1008
1092
  return;
1009
1093
  }
1010
1094
  if (event.key === "ArrowRight" && hasChildren && !isExpanded) {
@@ -1014,9 +1098,13 @@ function LayerRow({
1014
1098
  }
1015
1099
  if (event.key === "ArrowRight" && hasChildren && isExpanded) {
1016
1100
  event.preventDefault();
1017
- const child = visibleRows[currentIndex + 1];
1101
+ const nextButton = focusableButtons[currentFocusableIndex + 1];
1102
+ const childId = nextButton?.dataset.layerNodeId;
1103
+ const child = childId
1104
+ ? visibleRows.find((visibleRow) => visibleRow.node.id === childId)
1105
+ : null;
1018
1106
  if (child?.ancestorIds.includes(node.id)) {
1019
- focusVisibleButton(currentIndex + 1);
1107
+ focusVisibleButton(currentFocusableIndex + 1);
1020
1108
  }
1021
1109
  return;
1022
1110
  }
@@ -1037,17 +1125,10 @@ function LayerRow({
1037
1125
  event.preventDefault();
1038
1126
  return;
1039
1127
  }
1040
- const rawDraggedIds = selectedIds.includes(node.id)
1041
- ? selectedIds.filter((id) => !id.startsWith("__"))
1042
- : [node.id];
1043
- // Remove any node whose ancestor is also being dragged to avoid double-moves.
1044
- // When a parent is moved, its children move with it automatically.
1045
- const draggedIdSet = new Set(rawDraggedIds);
1046
- const draggedIds = rawDraggedIds.filter((id) => {
1047
- const rowForId = visibleRows.find((r) => r.node.id === id);
1048
- return !rowForId?.ancestorIds.some((ancestorId) =>
1049
- draggedIdSet.has(ancestorId),
1050
- );
1128
+ const draggedIds = getDraggedLayerIdsForRows({
1129
+ selectedIds,
1130
+ nodeId: node.id,
1131
+ visibleRows,
1051
1132
  });
1052
1133
  event.dataTransfer.effectAllowed = "move";
1053
1134
  event.dataTransfer.setData("application/x-design-layer-id", node.id);
@@ -1061,7 +1142,7 @@ function LayerRow({
1061
1142
  };
1062
1143
 
1063
1144
  const handleDragOver = (event: DragEvent<HTMLDivElement>) => {
1064
- if (!onMoveLayer || !selectable) return;
1145
+ if (!onMoveLayer || !dragEligible) return;
1065
1146
  // dataTransfer.getData() always returns "" during dragover per spec.
1066
1147
  // Read from the module-level activeDragState set in handleDragStart instead.
1067
1148
  if (!activeDragState) return;
@@ -1084,7 +1165,7 @@ function LayerRow({
1084
1165
  };
1085
1166
 
1086
1167
  const handleDrop = (event: DragEvent<HTMLDivElement>) => {
1087
- if (!onMoveLayer || !selectable) {
1168
+ if (!onMoveLayer || !dragEligible) {
1088
1169
  onDropIndicatorChange(null);
1089
1170
  return;
1090
1171
  }
@@ -0,0 +1,157 @@
1
+ import { useT } from "@agent-native/core/client";
2
+ import {
3
+ IconInfoCircle,
4
+ IconMessageBolt,
5
+ IconClipboard,
6
+ IconX,
7
+ } from "@tabler/icons-react";
8
+ import { useState } from "react";
9
+
10
+ import { Alert, AlertDescription } from "@/components/ui/alert";
11
+ import { Button } from "@/components/ui/button";
12
+ import {
13
+ Dialog,
14
+ DialogContent,
15
+ DialogDescription,
16
+ DialogFooter,
17
+ DialogHeader,
18
+ DialogTitle,
19
+ } from "@/components/ui/dialog";
20
+ import { Textarea } from "@/components/ui/textarea";
21
+ import { useAgentEditRequest } from "@/hooks/useAgentEditRequest";
22
+
23
+ export interface LocalSourceEditBannerProps {
24
+ /** The design id for agent context. */
25
+ designId: string;
26
+ /** The active file id for agent context, if known. */
27
+ fileId?: string;
28
+ /** Path to the localhost source file that should receive edits, if known. */
29
+ routeSourceFile?: string;
30
+ /** Called when the user dismisses the banner. */
31
+ onDismiss?: () => void;
32
+ }
33
+
34
+ /**
35
+ * Dismissible informational banner shown for screens connected to a local
36
+ * source (localhost / React dev server). Explains that edits are routed
37
+ * through the AI agent rather than applied directly, and provides quick
38
+ * "Ask AI to edit" and "Copy prompt" affordances.
39
+ */
40
+ export function LocalSourceEditBanner({
41
+ designId,
42
+ fileId,
43
+ routeSourceFile,
44
+ onDismiss,
45
+ }: LocalSourceEditBannerProps) {
46
+ const t = useT();
47
+ const { sendEdit, copyPrompt } = useAgentEditRequest();
48
+ const [promptOpen, setPromptOpen] = useState(false);
49
+ const [request, setRequest] = useState("");
50
+
51
+ const baseArgs = { designId, fileId, routeSourceFile };
52
+ const submitArgs = { ...baseArgs, message: request };
53
+ const trimmed = request.trim();
54
+
55
+ return (
56
+ <>
57
+ <Alert className="relative flex items-start gap-2 rounded-none border-x-0 border-t-0 py-2 px-3 text-xs">
58
+ <IconInfoCircle className="mt-0.5 size-3.5 shrink-0 text-muted-foreground" />
59
+ <AlertDescription className="flex flex-1 flex-col gap-1.5 text-xs leading-snug">
60
+ <span>{t("designEditor.localSourceEdit.bannerNotice")}</span>
61
+ <div className="flex flex-wrap gap-1.5">
62
+ <Button
63
+ size="sm"
64
+ variant="outline"
65
+ className="h-6 gap-1 px-2 text-[11px]"
66
+ onClick={() => setPromptOpen(true)}
67
+ >
68
+ <IconMessageBolt className="size-3 shrink-0" />
69
+ {t("designEditor.localSourceEdit.askAiToEdit")}
70
+ </Button>
71
+ <Button
72
+ size="sm"
73
+ variant="ghost"
74
+ className="h-6 gap-1 px-2 text-[11px] text-muted-foreground"
75
+ onClick={() => {
76
+ void copyPrompt({
77
+ ...baseArgs,
78
+ message: routeSourceFile
79
+ ? `Edit the connected source file "${routeSourceFile}" as requested.`
80
+ : "Edit the connected source code as requested.",
81
+ });
82
+ }}
83
+ >
84
+ <IconClipboard className="size-3 shrink-0" />
85
+ {t("designEditor.localSourceEdit.copyPrompt")}
86
+ </Button>
87
+ </div>
88
+ </AlertDescription>
89
+ {onDismiss && (
90
+ <button
91
+ type="button"
92
+ aria-label={t("designEditor.dismiss")}
93
+ onClick={onDismiss}
94
+ className="absolute end-2 top-2 rounded p-0.5 text-muted-foreground hover:bg-accent hover:text-foreground transition-colors"
95
+ >
96
+ <IconX className="size-3.5" />
97
+ </button>
98
+ )}
99
+ </Alert>
100
+
101
+ <Dialog open={promptOpen} onOpenChange={setPromptOpen}>
102
+ <DialogContent className="max-w-md">
103
+ <DialogHeader>
104
+ <DialogTitle>
105
+ {t("designEditor.localSourceEdit.dialogTitle")}
106
+ </DialogTitle>
107
+ <DialogDescription>
108
+ {routeSourceFile
109
+ ? t("designEditor.localSourceEdit.dialogDescriptionFile", {
110
+ file: routeSourceFile,
111
+ })
112
+ : t("designEditor.localSourceEdit.dialogDescription")}
113
+ </DialogDescription>
114
+ </DialogHeader>
115
+ <Textarea
116
+ autoFocus
117
+ value={request}
118
+ onChange={(e) => setRequest(e.target.value)}
119
+ placeholder={t("designEditor.localSourceEdit.requestPlaceholder")}
120
+ className="min-h-[96px] resize-none"
121
+ onKeyDown={(e) => {
122
+ if (e.key === "Enter" && (e.metaKey || e.ctrlKey) && trimmed) {
123
+ e.preventDefault();
124
+ void sendEdit(submitArgs);
125
+ setRequest("");
126
+ setPromptOpen(false);
127
+ }
128
+ }}
129
+ />
130
+ <DialogFooter className="gap-2">
131
+ <Button
132
+ variant="ghost"
133
+ onClick={() => {
134
+ void copyPrompt(submitArgs);
135
+ }}
136
+ disabled={!trimmed}
137
+ >
138
+ <IconClipboard className="size-4" />
139
+ {t("designEditor.localSourceEdit.copyPrompt")}
140
+ </Button>
141
+ <Button
142
+ onClick={() => {
143
+ void sendEdit(submitArgs);
144
+ setRequest("");
145
+ setPromptOpen(false);
146
+ }}
147
+ disabled={!trimmed}
148
+ >
149
+ <IconMessageBolt className="size-4" />
150
+ {t("designEditor.localSourceEdit.askAiToEdit")}
151
+ </Button>
152
+ </DialogFooter>
153
+ </DialogContent>
154
+ </Dialog>
155
+ </>
156
+ );
157
+ }