@agent-native/core 0.82.0 → 0.83.0

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 (63) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +6 -0
  3. package/corpus/core/package.json +1 -1
  4. package/corpus/core/src/client/CommandMenu.tsx +34 -12
  5. package/corpus/templates/analytics/actions/hubspot-deals.ts +64 -4
  6. package/corpus/templates/content/.env.local.example +6 -0
  7. package/corpus/templates/content/AGENTS.md +10 -1
  8. package/corpus/templates/content/actions/_builder-cms-read-client.ts +14 -1
  9. package/corpus/templates/content/actions/_builder-cms-source-adapter.ts +43 -0
  10. package/corpus/templates/content/actions/_builder-cms-write-adapter.ts +79 -7
  11. package/corpus/templates/content/actions/_database-source-utils.ts +987 -22
  12. package/corpus/templates/content/actions/_database-utils.ts +41 -0
  13. package/corpus/templates/content/actions/_local-file-documents.ts +74 -1
  14. package/corpus/templates/content/actions/attach-content-database-source.ts +33 -8
  15. package/corpus/templates/content/actions/change-content-database-source-role.ts +14 -0
  16. package/corpus/templates/content/actions/disconnect-content-database-source.ts +3 -0
  17. package/corpus/templates/content/actions/execute-builder-source-execution.ts +71 -3
  18. package/corpus/templates/content/actions/list-content-databases.ts +41 -33
  19. package/corpus/templates/content/actions/process-builder-body-hydration.ts +47 -0
  20. package/corpus/templates/content/actions/update-document.ts +93 -0
  21. package/corpus/templates/content/app/blocks/SourceComponentBlock.tsx +277 -0
  22. package/corpus/templates/content/app/blocks/contentBlockRegistry.tsx +2 -0
  23. package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +45 -4
  24. package/corpus/templates/content/app/components/editor/VisualEditor.tsx +5 -1
  25. package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +92 -0
  26. package/corpus/templates/content/app/components/editor/database-sources/BuilderSourceReviewDialog.tsx +5 -3
  27. package/corpus/templates/content/app/components/editor/extensions/registryBlocks.ts +41 -0
  28. package/corpus/templates/content/app/components/editor/registrySlashItems.ts +4 -0
  29. package/corpus/templates/content/app/global.css +10 -3
  30. package/corpus/templates/content/app/hooks/use-content-database.ts +24 -0
  31. package/corpus/templates/content/app/i18n/zh-TW.ts +24 -0
  32. package/corpus/templates/content/app/i18n-data.ts +212 -0
  33. package/corpus/templates/content/app/lib/content-command-search.ts +64 -0
  34. package/corpus/templates/content/app/root.tsx +236 -8
  35. package/corpus/templates/content/changelog/2026-06-29-builder-cms-sources-now-sync-article-bodies-through-content.md +6 -0
  36. package/corpus/templates/content/changelog/2026-06-30-builder-article-media-now-render-as-images-and-embeds-when-r.md +6 -0
  37. package/corpus/templates/content/changelog/2026-06-30-builder-source-components-now-render-as-preserved-preview-bl.md +6 -0
  38. package/corpus/templates/content/changelog/2026-06-30-cmd-k-search-now-opens-from-the-editor-and-finds-real-content.md +6 -0
  39. package/corpus/templates/content/package.json +3 -1
  40. package/corpus/templates/content/scripts/check-native-deps.mjs +57 -0
  41. package/corpus/templates/content/scripts/dev-database.mjs +83 -0
  42. package/corpus/templates/content/scripts/seed-demo-user.mjs +107 -0
  43. package/corpus/templates/content/server/db/schema.ts +27 -0
  44. package/corpus/templates/content/server/plugins/db.ts +33 -0
  45. package/corpus/templates/content/shared/api.ts +45 -0
  46. package/corpus/templates/content/shared/builder-mdx.ts +905 -5
  47. package/corpus/templates/content/shared/nfm-registry.ts +2 -0
  48. package/corpus/templates/content/shared/source-component-block.ts +141 -0
  49. package/corpus/templates/design/app/components/design/DesignCanvas.tsx +5 -1
  50. package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +229 -131
  51. package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +103 -6
  52. package/corpus/templates/design/app/hooks/useDesignHotkeys.ts +6 -3
  53. package/corpus/templates/design/app/pages/DesignEditor.tsx +382 -72
  54. package/corpus/templates/design/changelog/2026-06-30-canvas-editing-now-keeps-undo-redo-and-cross-screen-layer-mo.md +6 -0
  55. package/dist/client/CommandMenu.d.ts +6 -2
  56. package/dist/client/CommandMenu.d.ts.map +1 -1
  57. package/dist/client/CommandMenu.js +25 -14
  58. package/dist/client/CommandMenu.js.map +1 -1
  59. package/dist/file-upload/actions/upload-image.d.ts +2 -2
  60. package/dist/notifications/routes.d.ts +2 -2
  61. package/dist/observability/routes.d.ts +8 -8
  62. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  63. package/package.json +1 -1
@@ -497,12 +497,6 @@ export function removeUndoRedoOrderKind<T extends string>(
497
497
 
498
498
  type UndoRedoOrderKind = "content" | "file-content" | "geometry";
499
499
 
500
- function isContentUndoRedoOrderKind(
501
- kind: UndoRedoOrderKind | undefined,
502
- ): kind is "content" | "file-content" {
503
- return kind === "content" || kind === "file-content";
504
- }
505
-
506
500
  function resolveZoomUpdate(update: SetStateAction<number>, current: number) {
507
501
  return typeof update === "function" ? update(current) : update;
508
502
  }
@@ -882,6 +876,32 @@ interface GeometryHistoryEntry {
882
876
  after: CanvasFrameGeometryById;
883
877
  }
884
878
 
879
+ function geometryHistoryEntryTouchesFrameIds(
880
+ entry: GeometryHistoryEntry,
881
+ frameIds: Set<string>,
882
+ ) {
883
+ for (const frameId of frameIds) {
884
+ if (entry.before[frameId] || entry.after[frameId]) return true;
885
+ }
886
+ return false;
887
+ }
888
+
889
+ function removeRecentUndoRedoOrderKinds<T extends string>(
890
+ order: T[],
891
+ kind: T,
892
+ count: number,
893
+ ): T[] {
894
+ if (count <= 0) return order;
895
+ const next = [...order];
896
+ let remaining = count;
897
+ for (let index = next.length - 1; index >= 0 && remaining > 0; index -= 1) {
898
+ if (next[index] !== kind) continue;
899
+ next.splice(index, 1);
900
+ remaining -= 1;
901
+ }
902
+ return next;
903
+ }
904
+
885
905
  export interface ContentHistoryChange {
886
906
  fileId: string;
887
907
  before: string;
@@ -911,6 +931,17 @@ export function getAvailableContentHistoryChanges(
911
931
  );
912
932
  }
913
933
 
934
+ function findLastContentHistoryChangeIndex(
935
+ stack: ContentHistoryChange[],
936
+ fileId?: string | null,
937
+ ) {
938
+ if (!fileId) return -1;
939
+ for (let index = stack.length - 1; index >= 0; index -= 1) {
940
+ if (stack[index]?.fileId === fileId) return index;
941
+ }
942
+ return -1;
943
+ }
944
+
914
945
  type PatchProofStatus =
915
946
  | "runtime"
916
947
  | "queued"
@@ -1457,14 +1488,19 @@ function primitiveLayerName(primitive: CanvasPrimitiveInsert): string {
1457
1488
  function appendCanvasPrimitiveToHtml(
1458
1489
  content: string,
1459
1490
  primitive: CanvasPrimitiveInsert,
1491
+ options?: { preserveNegativePosition?: boolean },
1460
1492
  ): string | null {
1461
1493
  if (typeof window === "undefined") return null;
1462
1494
  try {
1463
1495
  const doc = new DOMParser().parseFromString(content, "text/html");
1464
1496
  if (!doc.body) return null;
1465
1497
  const geometry = primitive.geometry;
1466
- const left = Math.max(0, Math.round(geometry.x));
1467
- const top = Math.max(0, Math.round(geometry.y));
1498
+ const left = options?.preserveNegativePosition
1499
+ ? Math.round(geometry.x)
1500
+ : Math.max(0, Math.round(geometry.x));
1501
+ const top = options?.preserveNegativePosition
1502
+ ? Math.round(geometry.y)
1503
+ : Math.max(0, Math.round(geometry.y));
1468
1504
  const width = Math.max(1, Math.round(geometry.width));
1469
1505
  const height = Math.max(1, Math.round(geometry.height));
1470
1506
  const nodeId = primitive.nodeId ?? uniqueLayerId(primitive.kind);
@@ -3973,22 +4009,50 @@ export default function DesignEditor() {
3973
4009
  const undoManagerRef = useRef<Y.UndoManager | null>(null);
3974
4010
  const contentUndoStackRef = useRef<ContentHistoryEntry[]>([]);
3975
4011
  const contentRedoStackRef = useRef<ContentHistoryEntry[]>([]);
4012
+ const localContentUndoStackRef = useRef<ContentHistoryChange[]>([]);
4013
+ const localContentRedoStackRef = useRef<ContentHistoryChange[]>([]);
4014
+ const activeFileIdForUndoRef = useRef<string | null>(null);
3976
4015
  const suppressContentHistoryRef = useRef(false);
3977
4016
  const geometryUndoStackRef = useRef<GeometryHistoryEntry[]>([]);
3978
4017
  const geometryRedoStackRef = useRef<GeometryHistoryEntry[]>([]);
3979
4018
  const historyOrderRef = useRef<UndoRedoOrderKind[]>([]);
3980
4019
  const redoOrderRef = useRef<UndoRedoOrderKind[]>([]);
4020
+ const clearRedoStacks = useCallback(() => {
4021
+ contentRedoStackRef.current = [];
4022
+ localContentRedoStackRef.current = [];
4023
+ geometryRedoStackRef.current = [];
4024
+ redoOrderRef.current = [];
4025
+ undoManagerRef.current?.clear(false, true);
4026
+ }, []);
3981
4027
  const syncUndoRedoState = useCallback(() => {
3982
4028
  const undoManager = undoManagerRef.current;
4029
+ const canUseOverviewHistory = viewModeRef.current === "overview";
4030
+ const activeHistoryFileId = activeFileIdForUndoRef.current;
4031
+ const hasLocalUndo =
4032
+ !canUseOverviewHistory &&
4033
+ findLastContentHistoryChangeIndex(
4034
+ localContentUndoStackRef.current,
4035
+ activeHistoryFileId,
4036
+ ) !== -1;
4037
+ const hasLocalRedo =
4038
+ !canUseOverviewHistory &&
4039
+ findLastContentHistoryChangeIndex(
4040
+ localContentRedoStackRef.current,
4041
+ activeHistoryFileId,
4042
+ ) !== -1;
3983
4043
  setCanUndo(
3984
4044
  Boolean(undoManager?.canUndo()) ||
3985
- contentUndoStackRef.current.length > 0 ||
3986
- geometryUndoStackRef.current.length > 0,
4045
+ hasLocalUndo ||
4046
+ (canUseOverviewHistory &&
4047
+ (contentUndoStackRef.current.length > 0 ||
4048
+ geometryUndoStackRef.current.length > 0)),
3987
4049
  );
3988
4050
  setCanRedo(
3989
4051
  Boolean(undoManager?.canRedo()) ||
3990
- contentRedoStackRef.current.length > 0 ||
3991
- geometryRedoStackRef.current.length > 0,
4052
+ hasLocalRedo ||
4053
+ (canUseOverviewHistory &&
4054
+ (contentRedoStackRef.current.length > 0 ||
4055
+ geometryRedoStackRef.current.length > 0)),
3992
4056
  );
3993
4057
  }, []);
3994
4058
  const recordContentHistoryEntry = useCallback(
@@ -3997,23 +4061,59 @@ export default function DesignEditor() {
3997
4061
  (change) => change.before !== change.after,
3998
4062
  );
3999
4063
  if (changes.length === 0) return;
4064
+ const activeHistoryFileId = activeFileIdForUndoRef.current;
4065
+ if (
4066
+ activeHistoryFileId &&
4067
+ changes.some((change) => change.fileId === activeHistoryFileId)
4068
+ ) {
4069
+ undoManagerRef.current?.clear(true, false);
4070
+ localContentUndoStackRef.current =
4071
+ localContentUndoStackRef.current.filter(
4072
+ (change) => change.fileId !== activeHistoryFileId,
4073
+ );
4074
+ localContentRedoStackRef.current =
4075
+ localContentRedoStackRef.current.filter(
4076
+ (change) => change.fileId !== activeHistoryFileId,
4077
+ );
4078
+ historyOrderRef.current = removeUndoRedoOrderKind(
4079
+ historyOrderRef.current,
4080
+ "content",
4081
+ );
4082
+ redoOrderRef.current = removeUndoRedoOrderKind(
4083
+ redoOrderRef.current,
4084
+ "content",
4085
+ );
4086
+ }
4000
4087
  contentUndoStackRef.current = [
4001
4088
  ...contentUndoStackRef.current.slice(-(MAX_DESIGN_UNDO_STACK - 1)),
4002
4089
  changes.length === 1 ? changes[0] : { changes },
4003
4090
  ];
4004
- contentRedoStackRef.current = [];
4091
+ clearRedoStacks();
4005
4092
  historyOrderRef.current = [
4006
4093
  ...historyOrderRef.current.slice(-(MAX_DESIGN_UNDO_STACK - 1)),
4007
4094
  "file-content",
4008
4095
  ];
4009
- redoOrderRef.current = [];
4010
4096
  syncUndoRedoState();
4011
4097
  },
4012
- [syncUndoRedoState],
4098
+ [clearRedoStacks, syncUndoRedoState],
4099
+ );
4100
+ const recordLocalContentHistoryEntry = useCallback(
4101
+ (change: ContentHistoryChange) => {
4102
+ if (change.before === change.after) return;
4103
+ localContentUndoStackRef.current = [
4104
+ ...localContentUndoStackRef.current.slice(-(MAX_DESIGN_UNDO_STACK - 1)),
4105
+ change,
4106
+ ];
4107
+ clearRedoStacks();
4108
+ syncUndoRedoState();
4109
+ },
4110
+ [clearRedoStacks, syncUndoRedoState],
4013
4111
  );
4014
4112
  const clearLocalUndoRedoStacks = useCallback(() => {
4015
4113
  contentUndoStackRef.current = [];
4016
4114
  contentRedoStackRef.current = [];
4115
+ localContentUndoStackRef.current = [];
4116
+ localContentRedoStackRef.current = [];
4017
4117
  geometryUndoStackRef.current = [];
4018
4118
  geometryRedoStackRef.current = [];
4019
4119
  historyOrderRef.current = [];
@@ -5160,14 +5260,11 @@ export default function DesignEditor() {
5160
5260
  after: afterSnapshot,
5161
5261
  },
5162
5262
  ];
5163
- geometryRedoStackRef.current = [];
5164
- contentRedoStackRef.current = [];
5165
- undoManagerRef.current?.clear(false, true);
5263
+ clearRedoStacks();
5166
5264
  historyOrderRef.current = [
5167
5265
  ...historyOrderRef.current.slice(-(MAX_DESIGN_UNDO_STACK - 1)),
5168
5266
  "geometry",
5169
5267
  ];
5170
- redoOrderRef.current = [];
5171
5268
  const resizedFrameIds = viewportChangedFrameIds(
5172
5269
  beforeSnapshot,
5173
5270
  afterSnapshot,
@@ -5180,7 +5277,7 @@ export default function DesignEditor() {
5180
5277
  );
5181
5278
  syncUndoRedoState();
5182
5279
  },
5183
- [syncUndoRedoState, writeFrameGeometrySnapshot],
5280
+ [clearRedoStacks, syncUndoRedoState, writeFrameGeometrySnapshot],
5184
5281
  );
5185
5282
 
5186
5283
  // §6.6 — "Make this a real app" handler.
@@ -5356,6 +5453,7 @@ export default function DesignEditor() {
5356
5453
  }, [files, activeFileId]);
5357
5454
 
5358
5455
  const activeFile = files.find((f) => f.id === activeFileId) ?? files[0];
5456
+ activeFileIdForUndoRef.current = activeFile?.id ?? null;
5359
5457
  const motionTimelineQueryParams =
5360
5458
  id && activeFile?.id
5361
5459
  ? { designId: id, sourceRef: activeFile.id }
@@ -5862,6 +5960,7 @@ export default function DesignEditor() {
5862
5960
  const [collabContentFileId, setCollabContentFileId] = useState<string | null>(
5863
5961
  null,
5864
5962
  );
5963
+ const previousDesignIdForHistoryRef = useRef<string | null>(null);
5865
5964
  const prevActiveFileIdRef = useRef<string | null>(null);
5866
5965
  // `updatedAt` of the DB content this preview currently reflects. A poll that
5867
5966
  // returns an older-or-equal value is a stale snapshot and is ignored; a newer
@@ -5907,7 +6006,16 @@ export default function DesignEditor() {
5907
6006
  };
5908
6007
  }, [awareness, ydoc]);
5909
6008
 
5910
- // Reset per-file reconcile state when switching files
6009
+ useEffect(() => {
6010
+ if (previousDesignIdForHistoryRef.current === id) return;
6011
+ previousDesignIdForHistoryRef.current = id ?? null;
6012
+ clearLocalUndoRedoStacks();
6013
+ syncUndoRedoState();
6014
+ }, [clearLocalUndoRedoStacks, id, syncUndoRedoState]);
6015
+
6016
+ // Reset per-file reconcile state when switching files.
6017
+ // Keep undo/redo content + geometry stacks intact: overview mode needs one
6018
+ // chronological history across all screens, board edits, and frame geometry.
5911
6019
  useEffect(() => {
5912
6020
  if (viewMode === "overview") {
5913
6021
  prevActiveFileIdRef.current = activeFileId;
@@ -5916,9 +6024,7 @@ export default function DesignEditor() {
5916
6024
  lastAppliedFileUpdatedAtRef.current = null;
5917
6025
  lastLocalContentRef.current = null;
5918
6026
  latestActiveContentRef.current = null;
5919
- clearLocalUndoRedoStacks();
5920
6027
  clearStaleAgentCollabRecovery();
5921
- syncUndoRedoState();
5922
6028
  return;
5923
6029
  }
5924
6030
  if (activeFileId !== prevActiveFileIdRef.current) {
@@ -5928,17 +6034,9 @@ export default function DesignEditor() {
5928
6034
  lastAppliedFileUpdatedAtRef.current = null;
5929
6035
  lastLocalContentRef.current = null;
5930
6036
  latestActiveContentRef.current = null;
5931
- clearLocalUndoRedoStacks();
5932
6037
  clearStaleAgentCollabRecovery();
5933
- syncUndoRedoState();
5934
6038
  }
5935
- }, [
5936
- activeFileId,
5937
- clearLocalUndoRedoStacks,
5938
- clearStaleAgentCollabRecovery,
5939
- syncUndoRedoState,
5940
- viewMode,
5941
- ]);
6039
+ }, [activeFileId, clearStaleAgentCollabRecovery, viewMode]);
5942
6040
 
5943
6041
  useEffect(() => {
5944
6042
  return clearStaleAgentCollabRecovery;
@@ -6102,12 +6200,19 @@ export default function DesignEditor() {
6102
6200
  });
6103
6201
 
6104
6202
  const syncState = () => syncUndoRedoState();
6105
- const handleStackItemAdded = () => {
6203
+ const handleStackItemAdded = (event: {
6204
+ origin?: unknown;
6205
+ type?: "undo" | "redo";
6206
+ }) => {
6207
+ if (event.origin !== LOCAL_EDIT_ORIGIN || event.type !== "undo") {
6208
+ syncUndoRedoState();
6209
+ return;
6210
+ }
6106
6211
  historyOrderRef.current = [
6107
6212
  ...historyOrderRef.current.slice(-(MAX_DESIGN_UNDO_STACK - 1)),
6108
6213
  "content",
6109
6214
  ];
6110
- redoOrderRef.current = [];
6215
+ clearRedoStacks();
6111
6216
  syncUndoRedoState();
6112
6217
  };
6113
6218
  um.on("stack-item-added", handleStackItemAdded);
@@ -6135,7 +6240,7 @@ export default function DesignEditor() {
6135
6240
  );
6136
6241
  syncUndoRedoState();
6137
6242
  };
6138
- }, [ydoc, isSynced, syncUndoRedoState]);
6243
+ }, [clearRedoStacks, ydoc, isSynced, syncUndoRedoState]);
6139
6244
 
6140
6245
  // Reconcile authoritative external DB content (agent edit / peer-via-SQL) into
6141
6246
  // the live preview. This is the robustness fallback the Yjs observe path can't
@@ -6887,6 +6992,7 @@ export default function DesignEditor() {
6887
6992
  immediateSave?: boolean;
6888
6993
  persist?: boolean;
6889
6994
  recordHistory?: boolean;
6995
+ historyBeforeContent?: string;
6890
6996
  updatedAt?: string;
6891
6997
  } = {},
6892
6998
  ) => {
@@ -6894,12 +7000,18 @@ export default function DesignEditor() {
6894
7000
  const shouldRecordHistory =
6895
7001
  options.recordHistory !== false && !options.updatedAt;
6896
7002
  const previousContent =
6897
- collabContentFileIdRef.current === activeFile.id &&
6898
- typeof collabContentRef.current === "string"
6899
- ? collabContentRef.current
6900
- : (activeFile.content ?? "");
7003
+ typeof options.historyBeforeContent === "string"
7004
+ ? options.historyBeforeContent
7005
+ : collabContentFileIdRef.current === activeFile.id &&
7006
+ typeof collabContentRef.current === "string"
7007
+ ? collabContentRef.current
7008
+ : (activeFile.content ?? "");
6901
7009
  const yjsHistoryAvailable = Boolean(
6902
- shouldRecordHistory && ydoc && isSynced && undoManagerRef.current,
7010
+ shouldRecordHistory &&
7011
+ viewModeRef.current !== "overview" &&
7012
+ ydoc &&
7013
+ isSynced &&
7014
+ undoManagerRef.current,
6903
7015
  );
6904
7016
  if (
6905
7017
  !suppressContentHistoryRef.current &&
@@ -6907,11 +7019,16 @@ export default function DesignEditor() {
6907
7019
  !yjsHistoryAvailable &&
6908
7020
  previousContent !== nextContent
6909
7021
  ) {
6910
- recordContentHistoryEntry({
7022
+ const change = {
6911
7023
  fileId: activeFile.id,
6912
7024
  before: previousContent,
6913
7025
  after: nextContent,
6914
- });
7026
+ };
7027
+ if (viewModeRef.current === "overview") {
7028
+ recordContentHistoryEntry(change);
7029
+ } else {
7030
+ recordLocalContentHistoryEntry(change);
7031
+ }
6915
7032
  }
6916
7033
  if (options.updatedAt) {
6917
7034
  clearPendingLocalFileContent(activeFile.id);
@@ -6972,7 +7089,7 @@ export default function DesignEditor() {
6972
7089
  ytext.delete(0, ytext.length);
6973
7090
  ytext.insert(0, nextContent);
6974
7091
  },
6975
- shouldRecordHistory ? LOCAL_EDIT_ORIGIN : TAB_ID,
7092
+ yjsHistoryAvailable ? LOCAL_EDIT_ORIGIN : TAB_ID,
6976
7093
  );
6977
7094
  }
6978
7095
  }
@@ -6996,6 +7113,7 @@ export default function DesignEditor() {
6996
7113
  queueFileContentSave,
6997
7114
  replacePreviewContent,
6998
7115
  recordContentHistoryEntry,
7116
+ recordLocalContentHistoryEntry,
6999
7117
  syncUndoRedoState,
7000
7118
  ydoc,
7001
7119
  ],
@@ -7273,7 +7391,9 @@ export default function DesignEditor() {
7273
7391
  : storedContent;
7274
7392
  })()
7275
7393
  : storedContent);
7276
- const nextContent = appendCanvasPrimitiveToHtml(baseContent, primitive);
7394
+ const nextContent = appendCanvasPrimitiveToHtml(baseContent, primitive, {
7395
+ preserveNegativePosition: targetFile.id === boardFileId,
7396
+ });
7277
7397
  if (!nextContent) {
7278
7398
  toast.error(t("designEditor.toasts.primitiveInsertFailed"));
7279
7399
  return false;
@@ -7287,7 +7407,10 @@ export default function DesignEditor() {
7287
7407
  : null;
7288
7408
 
7289
7409
  if (targetFile.id === activeFile?.id) {
7290
- applyLocalContentUpdate(nextContent, { immediateSave: true });
7410
+ applyLocalContentUpdate(nextContent, {
7411
+ historyBeforeContent: baseContent,
7412
+ immediateSave: true,
7413
+ });
7291
7414
  } else {
7292
7415
  recordContentHistoryEntry({
7293
7416
  fileId: targetFile.id,
@@ -7335,6 +7458,7 @@ export default function DesignEditor() {
7335
7458
  activeContent,
7336
7459
  activeFile?.id,
7337
7460
  applyLocalContentUpdate,
7461
+ boardFileId,
7338
7462
  canEditDesign,
7339
7463
  files,
7340
7464
  id,
@@ -8369,6 +8493,28 @@ export default function DesignEditor() {
8369
8493
  );
8370
8494
  })
8371
8495
  : null;
8496
+ const yjsHistoryAvailable = Boolean(
8497
+ viewModeRef.current !== "overview" &&
8498
+ ydoc &&
8499
+ isSynced &&
8500
+ undoManagerRef.current,
8501
+ );
8502
+ if (
8503
+ !yjsHistoryAvailable &&
8504
+ !suppressContentHistoryRef.current &&
8505
+ baseContent !== resolvedNextContent
8506
+ ) {
8507
+ const change = {
8508
+ fileId: activeFile.id,
8509
+ before: baseContent,
8510
+ after: resolvedNextContent,
8511
+ };
8512
+ if (viewModeRef.current === "overview") {
8513
+ recordContentHistoryEntry(change);
8514
+ } else {
8515
+ recordLocalContentHistoryEntry(change);
8516
+ }
8517
+ }
8372
8518
 
8373
8519
  setCollabContent(resolvedNextContent);
8374
8520
  setCollabContentFileId(activeFile.id);
@@ -8381,14 +8527,18 @@ export default function DesignEditor() {
8381
8527
  latestActiveContentRef.current = resolvedNextContent;
8382
8528
  // Write the edit into the shared Y.Doc so other open clients see it live
8383
8529
  // through Yjs (not only via the slower update-file → applyText round-trip).
8384
- // Use LOCAL_EDIT_ORIGIN so the UndoManager captures this transaction.
8530
+ // Single-screen edits use the active-file UndoManager. Overview edits are
8531
+ // tracked in the global file-content stack so all screens share one order.
8385
8532
  if (ydoc && isSynced) {
8386
8533
  const ytext = ydoc.getText("content");
8387
8534
  if (ytext.toString() !== resolvedNextContent) {
8388
- ydoc.transact(() => {
8389
- ytext.delete(0, ytext.length);
8390
- ytext.insert(0, resolvedNextContent);
8391
- }, LOCAL_EDIT_ORIGIN);
8535
+ ydoc.transact(
8536
+ () => {
8537
+ ytext.delete(0, ytext.length);
8538
+ ytext.insert(0, resolvedNextContent);
8539
+ },
8540
+ yjsHistoryAvailable ? LOCAL_EDIT_ORIGIN : TAB_ID,
8541
+ );
8392
8542
  }
8393
8543
  }
8394
8544
  queueFileContentSave(activeFile.id, resolvedNextContent, {
@@ -8427,6 +8577,8 @@ export default function DesignEditor() {
8427
8577
  activeBreakpointWidthState,
8428
8578
  canEditDesign,
8429
8579
  queueFileContentSave,
8580
+ recordContentHistoryEntry,
8581
+ recordLocalContentHistoryEntry,
8430
8582
  replacePreviewContent,
8431
8583
  selectedElement,
8432
8584
  t,
@@ -9966,14 +10118,24 @@ export default function DesignEditor() {
9966
10118
  resolvedSourceNode?.dataAttributes["data-agent-native-node-id"] ??
9967
10119
  sourceNodeId ??
9968
10120
  sourceSelector;
10121
+ const destProjection = buildCodeLayerProjection(destContent);
10122
+ const resolvedTargetAnchor = targetAnchorNodeId
10123
+ ? resolveCodeLayerNodeFromBridge(
10124
+ destProjection,
10125
+ undefined,
10126
+ targetAnchorNodeId,
10127
+ )
10128
+ : null;
10129
+ const targetAnchorAttrId =
10130
+ resolvedTargetAnchor?.dataAttributes["data-agent-native-node-id"];
9969
10131
 
9970
10132
  // Use hit-test anchor when the canvas supplied one; fall back to
9971
10133
  // top-level body append ("inside" with no anchor = existing behaviour).
9972
10134
  const result = moveNodeBetweenDocuments(sourceContent, destContent, {
9973
10135
  nodeId: nodeAttrId,
9974
- ...(targetAnchorNodeId
10136
+ ...(targetAnchorAttrId
9975
10137
  ? {
9976
- anchorNodeId: targetAnchorNodeId,
10138
+ anchorNodeId: targetAnchorAttrId,
9977
10139
  placement: targetAnchorPlacement ?? "inside",
9978
10140
  }
9979
10141
  : { placement: "inside" }),
@@ -10077,6 +10239,38 @@ export default function DesignEditor() {
10077
10239
  delete nextGeometry[file.id];
10078
10240
  });
10079
10241
 
10242
+ const nextGeometryUndoStack: GeometryHistoryEntry[] = [];
10243
+ let removedGeometryUndoEntries = 0;
10244
+ geometryUndoStackRef.current.forEach((entry) => {
10245
+ if (geometryHistoryEntryTouchesFrameIds(entry, deleteIds)) {
10246
+ removedGeometryUndoEntries += 1;
10247
+ return;
10248
+ }
10249
+ nextGeometryUndoStack.push(entry);
10250
+ });
10251
+ geometryUndoStackRef.current = nextGeometryUndoStack;
10252
+ historyOrderRef.current = removeRecentUndoRedoOrderKinds(
10253
+ historyOrderRef.current,
10254
+ "geometry",
10255
+ removedGeometryUndoEntries,
10256
+ );
10257
+
10258
+ const nextGeometryRedoStack: GeometryHistoryEntry[] = [];
10259
+ let removedGeometryRedoEntries = 0;
10260
+ geometryRedoStackRef.current.forEach((entry) => {
10261
+ if (geometryHistoryEntryTouchesFrameIds(entry, deleteIds)) {
10262
+ removedGeometryRedoEntries += 1;
10263
+ return;
10264
+ }
10265
+ nextGeometryRedoStack.push(entry);
10266
+ });
10267
+ geometryRedoStackRef.current = nextGeometryRedoStack;
10268
+ redoOrderRef.current = removeRecentUndoRedoOrderKinds(
10269
+ redoOrderRef.current,
10270
+ "geometry",
10271
+ removedGeometryRedoEntries,
10272
+ );
10273
+
10080
10274
  writeFrameGeometrySnapshot(nextGeometry);
10081
10275
  queryClient.setQueryData(["action", "get-design", { id }], (old: any) => {
10082
10276
  if (!old || typeof old !== "object" || !Array.isArray(old.files)) {
@@ -10109,7 +10303,12 @@ export default function DesignEditor() {
10109
10303
  });
10110
10304
  });
10111
10305
 
10112
- return true;
10306
+ // File-backed screen deletion is not a geometry-only edit. The screen rows
10307
+ // are hard-deleted, so suppress MultiScreenCanvas' local frame-history
10308
+ // entry; otherwise undo would restore geometry for files that no longer
10309
+ // exist.
10310
+ syncUndoRedoState();
10311
+ return false;
10113
10312
  },
10114
10313
  [
10115
10314
  activeFile,
@@ -10119,6 +10318,7 @@ export default function DesignEditor() {
10119
10318
  files,
10120
10319
  id,
10121
10320
  queryClient,
10321
+ syncUndoRedoState,
10122
10322
  t,
10123
10323
  writeFrameGeometrySnapshot,
10124
10324
  ],
@@ -10213,8 +10413,10 @@ export default function DesignEditor() {
10213
10413
  const handleUndo = useCallback(() => {
10214
10414
  if (!canEditDesign) return;
10215
10415
  const um = undoManagerRef.current;
10216
- const undoContent = () => {
10217
- if (um?.canUndo()) {
10416
+ const canUseOverviewHistory = viewModeRef.current === "overview";
10417
+ let prunedUndoHistory = false;
10418
+ const undoContent = (scope: "any" | "local" | "global" = "any") => {
10419
+ if (scope !== "global" && um?.canUndo()) {
10218
10420
  um.undo();
10219
10421
  if (ydoc && activeFile) {
10220
10422
  const next = ydoc.getText("content").toString();
@@ -10247,6 +10449,43 @@ export default function DesignEditor() {
10247
10449
  return true;
10248
10450
  }
10249
10451
 
10452
+ if (!canUseOverviewHistory && scope !== "global" && activeFile?.id) {
10453
+ const localIndex = findLastContentHistoryChangeIndex(
10454
+ localContentUndoStackRef.current,
10455
+ activeFile.id,
10456
+ );
10457
+ if (localIndex !== -1) {
10458
+ const [entry] = localContentUndoStackRef.current.splice(
10459
+ localIndex,
10460
+ 1,
10461
+ );
10462
+ if (entry) {
10463
+ localContentRedoStackRef.current = [
10464
+ ...localContentRedoStackRef.current.slice(
10465
+ -(MAX_DESIGN_UNDO_STACK - 1),
10466
+ ),
10467
+ entry,
10468
+ ];
10469
+ applyLocalContentUpdate(entry.before, {
10470
+ refreshPreview: false,
10471
+ immediateSave: true,
10472
+ recordHistory: false,
10473
+ });
10474
+ setSelectedElement((prev) => {
10475
+ if (!prev) return prev;
10476
+ return refreshElementInfoFromContent(entry.before, prev);
10477
+ });
10478
+ setHoveredElement((prev) => {
10479
+ if (!prev) return prev;
10480
+ return refreshElementInfoFromContent(entry.before, prev);
10481
+ });
10482
+ return true;
10483
+ }
10484
+ }
10485
+ }
10486
+
10487
+ if (scope === "local") return false;
10488
+ if (!canUseOverviewHistory) return false;
10250
10489
  const entry =
10251
10490
  contentUndoStackRef.current[contentUndoStackRef.current.length - 1];
10252
10491
  if (!entry) return false;
@@ -10255,7 +10494,11 @@ export default function DesignEditor() {
10255
10494
  files.map((file) => file.id),
10256
10495
  activeFile?.id,
10257
10496
  );
10258
- if (changes.length === 0) return false;
10497
+ if (changes.length === 0) {
10498
+ contentUndoStackRef.current.pop();
10499
+ prunedUndoHistory = true;
10500
+ return false;
10501
+ }
10259
10502
  contentUndoStackRef.current.pop();
10260
10503
  contentRedoStackRef.current = [
10261
10504
  ...contentRedoStackRef.current.slice(-(MAX_DESIGN_UNDO_STACK - 1)),
@@ -10300,6 +10543,7 @@ export default function DesignEditor() {
10300
10543
  return true;
10301
10544
  };
10302
10545
  const undoGeometry = () => {
10546
+ if (!canUseOverviewHistory) return false;
10303
10547
  const entry = geometryUndoStackRef.current.pop();
10304
10548
  if (!entry) return false;
10305
10549
  geometryRedoStackRef.current = [
@@ -10319,14 +10563,25 @@ export default function DesignEditor() {
10319
10563
  return true;
10320
10564
  };
10321
10565
 
10322
- const preferred = historyOrderRef.current.pop();
10323
- const didUndo =
10566
+ const undoByOrder = (preferred?: UndoRedoOrderKind) =>
10324
10567
  preferred === "geometry"
10325
10568
  ? undoGeometry() || undoContent()
10326
- : isContentUndoRedoOrderKind(preferred)
10327
- ? undoContent() || undoGeometry()
10328
- : undoContent() || undoGeometry();
10329
- if (didUndo) {
10569
+ : preferred === "file-content"
10570
+ ? undoContent("global") || undoGeometry()
10571
+ : preferred === "content"
10572
+ ? undoContent("local") || undoContent("global") || undoGeometry()
10573
+ : undoContent() || undoGeometry();
10574
+ let didUndo = false;
10575
+ if (canUseOverviewHistory) {
10576
+ while (!didUndo) {
10577
+ const preferred = historyOrderRef.current.pop();
10578
+ didUndo = undoByOrder(preferred);
10579
+ if (didUndo || preferred === undefined) break;
10580
+ }
10581
+ } else {
10582
+ didUndo = undoContent("local");
10583
+ }
10584
+ if (didUndo || prunedUndoHistory) {
10330
10585
  syncUndoRedoState();
10331
10586
  }
10332
10587
  }, [
@@ -10347,8 +10602,10 @@ export default function DesignEditor() {
10347
10602
  const handleRedo = useCallback(() => {
10348
10603
  if (!canEditDesign) return;
10349
10604
  const um = undoManagerRef.current;
10350
- const redoContent = () => {
10351
- if (um?.canRedo()) {
10605
+ const canUseOverviewHistory = viewModeRef.current === "overview";
10606
+ let prunedRedoHistory = false;
10607
+ const redoContent = (scope: "any" | "local" | "global" = "any") => {
10608
+ if (scope !== "global" && um?.canRedo()) {
10352
10609
  um.redo();
10353
10610
  if (ydoc && activeFile) {
10354
10611
  const next = ydoc.getText("content").toString();
@@ -10381,6 +10638,43 @@ export default function DesignEditor() {
10381
10638
  return true;
10382
10639
  }
10383
10640
 
10641
+ if (!canUseOverviewHistory && scope !== "global" && activeFile?.id) {
10642
+ const localIndex = findLastContentHistoryChangeIndex(
10643
+ localContentRedoStackRef.current,
10644
+ activeFile.id,
10645
+ );
10646
+ if (localIndex !== -1) {
10647
+ const [entry] = localContentRedoStackRef.current.splice(
10648
+ localIndex,
10649
+ 1,
10650
+ );
10651
+ if (entry) {
10652
+ localContentUndoStackRef.current = [
10653
+ ...localContentUndoStackRef.current.slice(
10654
+ -(MAX_DESIGN_UNDO_STACK - 1),
10655
+ ),
10656
+ entry,
10657
+ ];
10658
+ applyLocalContentUpdate(entry.after, {
10659
+ refreshPreview: false,
10660
+ immediateSave: true,
10661
+ recordHistory: false,
10662
+ });
10663
+ setSelectedElement((prev) => {
10664
+ if (!prev) return prev;
10665
+ return refreshElementInfoFromContent(entry.after, prev);
10666
+ });
10667
+ setHoveredElement((prev) => {
10668
+ if (!prev) return prev;
10669
+ return refreshElementInfoFromContent(entry.after, prev);
10670
+ });
10671
+ return true;
10672
+ }
10673
+ }
10674
+ }
10675
+
10676
+ if (scope === "local") return false;
10677
+ if (!canUseOverviewHistory) return false;
10384
10678
  const entry =
10385
10679
  contentRedoStackRef.current[contentRedoStackRef.current.length - 1];
10386
10680
  if (!entry) return false;
@@ -10389,7 +10683,11 @@ export default function DesignEditor() {
10389
10683
  files.map((file) => file.id),
10390
10684
  activeFile?.id,
10391
10685
  );
10392
- if (changes.length === 0) return false;
10686
+ if (changes.length === 0) {
10687
+ contentRedoStackRef.current.pop();
10688
+ prunedRedoHistory = true;
10689
+ return false;
10690
+ }
10393
10691
  contentRedoStackRef.current.pop();
10394
10692
  contentUndoStackRef.current = [
10395
10693
  ...contentUndoStackRef.current.slice(-(MAX_DESIGN_UNDO_STACK - 1)),
@@ -10434,6 +10732,7 @@ export default function DesignEditor() {
10434
10732
  return true;
10435
10733
  };
10436
10734
  const redoGeometry = () => {
10735
+ if (!canUseOverviewHistory) return false;
10437
10736
  const entry = geometryRedoStackRef.current.pop();
10438
10737
  if (!entry) return false;
10439
10738
  geometryUndoStackRef.current = [
@@ -10453,14 +10752,25 @@ export default function DesignEditor() {
10453
10752
  return true;
10454
10753
  };
10455
10754
 
10456
- const preferred = redoOrderRef.current.pop();
10457
- const didRedo =
10755
+ const redoByOrder = (preferred?: UndoRedoOrderKind) =>
10458
10756
  preferred === "geometry"
10459
10757
  ? redoGeometry() || redoContent()
10460
- : isContentUndoRedoOrderKind(preferred)
10461
- ? redoContent() || redoGeometry()
10462
- : redoContent() || redoGeometry();
10463
- if (didRedo) {
10758
+ : preferred === "file-content"
10759
+ ? redoContent("global") || redoGeometry()
10760
+ : preferred === "content"
10761
+ ? redoContent("local") || redoContent("global") || redoGeometry()
10762
+ : redoContent() || redoGeometry();
10763
+ let didRedo = false;
10764
+ if (canUseOverviewHistory) {
10765
+ while (!didRedo) {
10766
+ const preferred = redoOrderRef.current.pop();
10767
+ didRedo = redoByOrder(preferred);
10768
+ if (didRedo || preferred === undefined) break;
10769
+ }
10770
+ } else {
10771
+ didRedo = redoContent("local");
10772
+ }
10773
+ if (didRedo || prunedRedoHistory) {
10464
10774
  syncUndoRedoState();
10465
10775
  }
10466
10776
  }, [
@@ -11725,7 +12035,7 @@ ${serializedHtml}
11725
12035
  },
11726
12036
  });
11727
12037
  if (!stamped.changed || stamped.content === activeContent) return;
11728
- applyLocalContentUpdate(stamped.content);
12038
+ applyLocalContentUpdate(stamped.content, { recordHistory: false });
11729
12039
  }, [activeContent, activeFile, applyLocalContentUpdate, id, viewMode]);
11730
12040
  const activeCodeLayerTree = useMemo(
11731
12041
  () => buildCodeLayerTree(activeCodeLayerProjection),