@agent-native/core 0.84.9 → 0.84.12

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 (80) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +43 -0
  3. package/corpus/core/package.json +1 -1
  4. package/corpus/core/src/agent/run-store.ts +29 -6
  5. package/corpus/core/src/cli/skills.ts +6 -3
  6. package/corpus/core/src/client/agent-chat-adapter.ts +31 -11
  7. package/corpus/core/src/client/require-session.tsx +20 -1
  8. package/corpus/core/src/client/sharing/ShareButton.tsx +9 -3
  9. package/corpus/core/src/collab/storage.ts +40 -5
  10. package/corpus/core/src/server/auth.ts +6 -0
  11. package/corpus/templates/assets/.agents/skills/logo-composite/SKILL.md +7 -6
  12. package/corpus/templates/assets/AGENTS.md +15 -0
  13. package/corpus/templates/assets/actions/_helpers.ts +3 -1
  14. package/corpus/templates/assets/actions/create-generation-preset.ts +12 -1
  15. package/corpus/templates/assets/actions/generate-asset.ts +5 -1
  16. package/corpus/templates/assets/actions/generate-image-batch.ts +6 -1
  17. package/corpus/templates/assets/actions/generate-image.ts +18 -11
  18. package/corpus/templates/assets/actions/open-asset-picker.ts +4 -2
  19. package/corpus/templates/assets/actions/update-generation-preset.ts +16 -3
  20. package/corpus/templates/assets/app/i18n/zh-TW.ts +3 -0
  21. package/corpus/templates/assets/app/i18n-data.ts +30 -0
  22. package/corpus/templates/assets/app/routes/brand-kits.$id.tsx +42 -7
  23. package/corpus/templates/assets/app/routes/library.tsx +4 -2
  24. package/corpus/templates/assets/changelog/2026-07-01-generation-presets-can-now-composite-your-brand-s-canonical-.md +6 -0
  25. package/corpus/templates/assets/changelog/2026-07-01-tagging-a-preset-now-briefs-the-agent-on-that-preset-s-aesth.md +6 -0
  26. package/corpus/templates/assets/server/lib/preset-chat-context.ts +118 -0
  27. package/corpus/templates/assets/server/plugins/agent-chat.ts +5 -0
  28. package/corpus/templates/assets/shared/api.ts +1 -0
  29. package/corpus/templates/design/actions/export-html.ts +4 -2
  30. package/corpus/templates/design/actions/export-pdf.ts +3 -1
  31. package/corpus/templates/design/actions/export-svg.ts +4 -2
  32. package/corpus/templates/design/actions/export-zip.ts +10 -3
  33. package/corpus/templates/design/actions/get-design-snapshot.ts +41 -4
  34. package/corpus/templates/design/actions/present-design-variants.ts +21 -6
  35. package/corpus/templates/design/actions/update-file.ts +68 -17
  36. package/corpus/templates/design/app/components/design/DesignCanvas.tsx +3 -2
  37. package/corpus/templates/design/app/components/design/EditPanel.tsx +35 -3
  38. package/corpus/templates/design/app/components/design/MotionDock.tsx +46 -11
  39. package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +80 -30
  40. package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +60 -10
  41. package/corpus/templates/design/app/components/design/canvas-primitive-style.ts +2 -0
  42. package/corpus/templates/design/app/components/design/inspector/InspectorAiActions.tsx +4 -0
  43. package/corpus/templates/design/app/hooks/use-question-flow.ts +2 -2
  44. package/corpus/templates/design/app/hooks/useAgentEditRequest.ts +2 -2
  45. package/corpus/templates/design/app/i18n-data.ts +1 -1
  46. package/corpus/templates/design/app/pages/DesignEditor.tsx +493 -109
  47. package/corpus/templates/design/app/pages/VisualEdit.tsx +4 -16
  48. package/corpus/templates/design/changelog/2026-07-01-improved-variant-pick-follow-ups-so-selected-directions-cont.md +6 -0
  49. package/corpus/templates/design/e2e/helpers.ts +2 -2
  50. package/dist/agent/run-store.d.ts.map +1 -1
  51. package/dist/agent/run-store.js +28 -6
  52. package/dist/agent/run-store.js.map +1 -1
  53. package/dist/cli/skills.d.ts.map +1 -1
  54. package/dist/cli/skills.js +6 -3
  55. package/dist/cli/skills.js.map +1 -1
  56. package/dist/client/agent-chat-adapter.d.ts.map +1 -1
  57. package/dist/client/agent-chat-adapter.js +26 -11
  58. package/dist/client/agent-chat-adapter.js.map +1 -1
  59. package/dist/client/require-session.d.ts +14 -0
  60. package/dist/client/require-session.d.ts.map +1 -1
  61. package/dist/client/require-session.js +19 -1
  62. package/dist/client/require-session.js.map +1 -1
  63. package/dist/client/sharing/ShareButton.d.ts +4 -2
  64. package/dist/client/sharing/ShareButton.d.ts.map +1 -1
  65. package/dist/client/sharing/ShareButton.js +3 -1
  66. package/dist/client/sharing/ShareButton.js.map +1 -1
  67. package/dist/collab/routes.d.ts +1 -1
  68. package/dist/collab/storage.d.ts.map +1 -1
  69. package/dist/collab/storage.js +31 -5
  70. package/dist/collab/storage.js.map +1 -1
  71. package/dist/file-upload/actions/upload-image.d.ts +2 -2
  72. package/dist/notifications/routes.d.ts +2 -2
  73. package/dist/observability/routes.d.ts +6 -6
  74. package/dist/resources/handlers.d.ts +2 -2
  75. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  76. package/dist/server/auth.d.ts.map +1 -1
  77. package/dist/server/auth.js +7 -0
  78. package/dist/server/auth.js.map +1 -1
  79. package/dist/server/transcribe-voice.d.ts +1 -1
  80. package/package.json +1 -1
@@ -166,6 +166,7 @@ import {
166
166
  type InspectorTab,
167
167
  } from "@/components/design/EditPanel";
168
168
  import type { ExportSettingsValue } from "@/components/design/inspector";
169
+ import { InspectorAiActions } from "@/components/design/inspector/InspectorAiActions";
169
170
  import {
170
171
  LayersPanel,
171
172
  type LayersPanelFile,
@@ -1108,6 +1109,120 @@ export function hydrateMotionDockTracks(
1108
1109
  }));
1109
1110
  }
1110
1111
 
1112
+ const MOTION_KEYFRAME_TIME_EPSILON = 0.002;
1113
+
1114
+ function motionCssPropertyName(property: string): string | null {
1115
+ const trimmed = property.trim();
1116
+ if (!trimmed || trimmed.startsWith("--")) return null;
1117
+ const cssName = trimmed
1118
+ .replace(/^css/, "")
1119
+ .replace(/[A-Z]/g, (match) => `-${match.toLowerCase()}`)
1120
+ .toLowerCase();
1121
+ return /^-?[a-z][a-z0-9-]*$/i.test(cssName) ? cssName : null;
1122
+ }
1123
+
1124
+ function camelStyleProperty(property: string): string {
1125
+ return property.replace(/-([a-z])/g, (_, letter: string) =>
1126
+ letter.toUpperCase(),
1127
+ );
1128
+ }
1129
+
1130
+ function computedMotionStyleValue(
1131
+ computedStyles: Record<string, string> | undefined,
1132
+ property: string,
1133
+ ): string | undefined {
1134
+ if (!computedStyles) return undefined;
1135
+ return (
1136
+ computedStyles[property] ??
1137
+ computedStyles[camelStyleProperty(property)] ??
1138
+ computedStyles[
1139
+ property.replace(/[A-Z]/g, (match) => `-${match.toLowerCase()}`)
1140
+ ]
1141
+ );
1142
+ }
1143
+
1144
+ function defaultMotionBaseValue(property: string, nextValue: string): string {
1145
+ if (property === "opacity") return "1";
1146
+ if (property === "transform" || property === "filter") return "none";
1147
+ return nextValue;
1148
+ }
1149
+
1150
+ export function upsertMotionStyleKeyframes(args: {
1151
+ tracks: MotionDockTrack[];
1152
+ targetNodeId: string;
1153
+ label: string;
1154
+ styles: Record<string, string>;
1155
+ computedStyles?: Record<string, string>;
1156
+ playhead: number;
1157
+ defaultEase?: string;
1158
+ }): MotionDockTrack[] {
1159
+ const t = Math.max(0, Math.min(1, args.playhead));
1160
+ const ease = args.defaultEase ?? "ease";
1161
+ let nextTracks = args.tracks;
1162
+
1163
+ for (const [rawProperty, rawValue] of Object.entries(args.styles)) {
1164
+ if (rawValue === undefined) continue;
1165
+ const value = String(rawValue).trim();
1166
+ if (!value) continue;
1167
+ const property = motionCssPropertyName(rawProperty);
1168
+ if (!property) continue;
1169
+
1170
+ const existingIndex = nextTracks.findIndex(
1171
+ (track) =>
1172
+ track.targetNodeId === args.targetNodeId && track.property === property,
1173
+ );
1174
+
1175
+ if (existingIndex >= 0) {
1176
+ nextTracks = nextTracks.map((track, index) => {
1177
+ if (index !== existingIndex) return track;
1178
+ const withoutCurrentTime = track.keyframes.filter(
1179
+ (keyframe) => Math.abs(keyframe.t - t) > MOTION_KEYFRAME_TIME_EPSILON,
1180
+ );
1181
+ return {
1182
+ ...track,
1183
+ label: track.label || args.label,
1184
+ keyframes: [...withoutCurrentTime, { t, value, ease }].sort(
1185
+ (a, b) => a.t - b.t,
1186
+ ),
1187
+ };
1188
+ });
1189
+ continue;
1190
+ }
1191
+
1192
+ const baseValue =
1193
+ computedMotionStyleValue(args.computedStyles, property) ??
1194
+ computedMotionStyleValue(args.computedStyles, rawProperty) ??
1195
+ defaultMotionBaseValue(property, value);
1196
+ const keyframes =
1197
+ t <= MOTION_KEYFRAME_TIME_EPSILON
1198
+ ? [
1199
+ { t: 0, value, ease },
1200
+ { t: 1, value: baseValue, ease },
1201
+ ]
1202
+ : t >= 1 - MOTION_KEYFRAME_TIME_EPSILON
1203
+ ? [
1204
+ { t: 0, value: baseValue, ease },
1205
+ { t: 1, value, ease },
1206
+ ]
1207
+ : [
1208
+ { t: 0, value: baseValue, ease },
1209
+ { t, value, ease },
1210
+ { t: 1, value: baseValue, ease },
1211
+ ];
1212
+ nextTracks = [
1213
+ ...nextTracks,
1214
+ {
1215
+ targetNodeId: args.targetNodeId,
1216
+ property,
1217
+ keyframes,
1218
+ label: args.label,
1219
+ },
1220
+ ];
1221
+ }
1222
+
1223
+ return nextTracks;
1224
+ }
1225
+
1111
1226
  export function motionTimelineFingerprint(
1112
1227
  fileId: string,
1113
1228
  timeline: MotionTimelineRow | null | undefined,
@@ -1129,6 +1244,8 @@ function buildSignInHrefForDesignIntent(intent: PostAuthDesignIntent): string {
1129
1244
  if (typeof window === "undefined") return base;
1130
1245
 
1131
1246
  const returnUrl = new URL(window.location.href);
1247
+ returnUrl.search = "";
1248
+ returnUrl.hash = "";
1132
1249
  returnUrl.searchParams.set("intent", intent);
1133
1250
  const ret = returnUrl.pathname + returnUrl.search + returnUrl.hash;
1134
1251
  return `${base}?return=${encodeURIComponent(ret)}`;
@@ -1189,8 +1306,11 @@ export function getAvailableContentHistoryChanges(
1189
1306
  activeFileId?: string | null,
1190
1307
  ): ContentHistoryChange[] {
1191
1308
  const fileIds = new Set(availableFileIds);
1309
+ const activeFileIsAvailable = !!activeFileId && fileIds.has(activeFileId);
1192
1310
  return getContentHistoryChanges(entry).filter(
1193
- (change) => change.fileId === activeFileId || fileIds.has(change.fileId),
1311
+ (change) =>
1312
+ fileIds.has(change.fileId) ||
1313
+ (activeFileIsAvailable && change.fileId === activeFileId),
1194
1314
  );
1195
1315
  }
1196
1316
 
@@ -1325,7 +1445,7 @@ function designVariantGenerationDirectives(
1325
1445
  ...designSystemGenerationDirectives(designSystemId),
1326
1446
  "The user's prompt already asks to explore multiple directions, so DO NOT call `show-design-questions` first and DO NOT call `generate-design` first.",
1327
1447
  "Call `present-design-variants` with 2-5 concise directions (3 when unspecified). Prefer label, description, accentColor, and feature bullets; omit large content HTML when needed because the action can render compact representative screens.",
1328
- "Wait for the user's chat pick, delete the unchosen variant screens, then continue from the kept screen.",
1448
+ "Wait for the user's chat pick, delete the unchosen variant screens, call `get-design-snapshot` with `fileId` for the kept screen, then continue from that single file in a bounded pass. Prefer `edit-design`; if `generate-design` is needed, update only the kept file compactly and stop after the first successful save.",
1329
1449
  ];
1330
1450
  }
1331
1451
 
@@ -1336,7 +1456,7 @@ function designGenerationDirectives(
1336
1456
  return [
1337
1457
  `Use the \`generate-design --designId="${designId}"\` action with exactly one complete, renderable \`index.html\` file first. The design already exists - DO NOT call create-design.`,
1338
1458
  ...designSystemGenerationDirectives(designSystemId),
1339
- "If the user asked to explore variations, call `present-design-variants` with 2-5 concise directions. Prefer label, description, accentColor, and feature bullets; omit large content HTML when needed because the action can render compact representative screens. Wait for their chat pick, delete the unchosen variant screens, then continue from the kept screen. Otherwise generate one polished first direction.",
1459
+ "If the user asked to explore variations, call `present-design-variants` with 2-5 concise directions. Prefer label, description, accentColor, and feature bullets; omit large content HTML when needed because the action can render compact representative screens. Wait for their chat pick, delete the unchosen variant screens, call `get-design-snapshot` with `fileId` for the kept screen, then continue from that single file in a bounded pass. Prefer `edit-design`; if `generate-design` is needed, update only the kept file compactly and stop after the first successful save. Otherwise generate one polished first direction.",
1340
1460
  "Keep the first pass bounded enough to finish quickly: one self-contained Alpine.js + Tailwind CDN HTML document, polished but concise. Add 3-6 tweaks only when they naturally fit the design.",
1341
1461
  "After generate-design succeeds, stop and summarize what was created.",
1342
1462
  ];
@@ -1913,6 +2033,14 @@ function appendCanvasPrimitiveToHtml(
1913
2033
  const explicitPathData = primitive.pathData?.trim()
1914
2034
  ? primitive.pathData
1915
2035
  : null;
2036
+ const pathViewBoxLeft = options?.preserveNegativePosition
2037
+ ? geometry.x
2038
+ : Math.max(0, geometry.x);
2039
+ const pathViewBoxTop = options?.preserveNegativePosition
2040
+ ? geometry.y
2041
+ : Math.max(0, geometry.y);
2042
+ const pathViewBoxWidth = Math.max(1, geometry.width);
2043
+ const pathViewBoxHeight = Math.max(1, geometry.height);
1916
2044
  const points = primitive.points?.length
1917
2045
  ? primitive.points
1918
2046
  : [
@@ -1977,7 +2105,7 @@ function appendCanvasPrimitiveToHtml(
1977
2105
  svg.setAttribute(
1978
2106
  "viewBox",
1979
2107
  explicitPathData
1980
- ? `${left} ${top} ${width} ${height}`
2108
+ ? `${pathViewBoxLeft} ${pathViewBoxTop} ${pathViewBoxWidth} ${pathViewBoxHeight}`
1981
2109
  : `0 0 ${width} ${height}`,
1982
2110
  );
1983
2111
  svg.setAttribute(
@@ -2382,7 +2510,7 @@ function extractLayerPosition(
2382
2510
  function postBeginTextEditToPreviewIframes(
2383
2511
  screenId: string | null,
2384
2512
  nodeId: string,
2385
- ): boolean {
2513
+ ): "active" | "done" | false {
2386
2514
  if (typeof document === "undefined" || !nodeId) return false;
2387
2515
  const iframes = Array.from(
2388
2516
  document.querySelectorAll<HTMLIFrameElement>(
@@ -2394,16 +2522,18 @@ function postBeginTextEditToPreviewIframes(
2394
2522
  );
2395
2523
  const orderedIframes = targetIframes.length > 0 ? targetIframes : iframes;
2396
2524
  const selector = `[data-agent-native-node-id="${nodeId.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"][data-agent-native-text-editing]`;
2397
- if (
2398
- orderedIframes.some((iframe) => {
2399
- try {
2400
- return iframe.contentDocument?.querySelector(selector);
2401
- } catch {
2402
- return false;
2403
- }
2404
- })
2405
- ) {
2406
- return true;
2525
+ for (const iframe of orderedIframes) {
2526
+ try {
2527
+ const doc = iframe.contentDocument;
2528
+ const node = doc?.querySelector<HTMLElement>(
2529
+ `[data-agent-native-node-id="${nodeId.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"]`,
2530
+ );
2531
+ const editing = doc?.querySelector(selector);
2532
+ if (editing && doc?.activeElement === editing) return "active";
2533
+ if (node && (node.textContent ?? "").trim().length > 0) return "done";
2534
+ } catch {
2535
+ // Keep retrying other iframes.
2536
+ }
2407
2537
  }
2408
2538
  orderedIframes.forEach((iframe) => {
2409
2539
  iframe.contentWindow?.postMessage(
@@ -2419,15 +2549,31 @@ function scheduleBeginTextEditForScreen(
2419
2549
  nodeId: string,
2420
2550
  ) {
2421
2551
  if (typeof window === "undefined") return;
2422
- let sawEditing = false;
2423
- [50, 150, 300, 600, 900, 1200, 1800, 2400, 3200, 4200].forEach((delay) => {
2552
+ let finished = false;
2553
+ [180, 300, 600, 900, 1200, 1800, 2400, 3200, 4200].forEach((delay) => {
2424
2554
  window.setTimeout(() => {
2425
- if (sawEditing) return;
2426
- sawEditing = postBeginTextEditToPreviewIframes(screenId, nodeId);
2555
+ if (finished) return;
2556
+ finished = postBeginTextEditToPreviewIframes(screenId, nodeId) === "done";
2427
2557
  }, delay);
2428
2558
  });
2429
2559
  }
2430
2560
 
2561
+ function postShaderFillPreviewClearToPreviewIframes() {
2562
+ if (typeof document === "undefined") return;
2563
+ document
2564
+ .querySelectorAll<HTMLIFrameElement>("iframe[data-design-preview-iframe]")
2565
+ .forEach((iframe) => {
2566
+ try {
2567
+ iframe.contentWindow?.postMessage(
2568
+ { type: "shader-fill-preview-clear" },
2569
+ "*",
2570
+ );
2571
+ } catch {
2572
+ // Ignore inaccessible iframe windows; same-origin previews handle this.
2573
+ }
2574
+ });
2575
+ }
2576
+
2431
2577
  function removeElementFromHtml(
2432
2578
  content: string,
2433
2579
  selector: string,
@@ -4377,6 +4523,9 @@ export default function DesignEditor() {
4377
4523
  const [motionTimelineId, setMotionTimelineId] = useState<string | null>(null);
4378
4524
  const [motionTracks, setMotionTracks] = useState<MotionDockTrack[]>([]);
4379
4525
  const [motionDurationMs, setMotionDurationMs] = useState(1000);
4526
+ const [motionPlayhead, setMotionPlayhead] = useState(0);
4527
+ const [motionAutoKeyframeEnabled, setMotionAutoKeyframeEnabled] =
4528
+ useState(false);
4380
4529
  const [motionTracksDirty, setMotionTracksDirty] = useState(false);
4381
4530
  const [motionAutosaveRevision, setMotionAutosaveRevision] = useState(0);
4382
4531
  const [motionHydrationFingerprint, setMotionHydrationFingerprint] = useState<
@@ -4444,6 +4593,10 @@ export default function DesignEditor() {
4444
4593
  nodeId?: string;
4445
4594
  css: string;
4446
4595
  } | null>(null);
4596
+ const clearShaderFillPreview = useCallback(() => {
4597
+ setShaderFillPreview(null);
4598
+ postShaderFillPreviewClearToPreviewIframes();
4599
+ }, []);
4447
4600
 
4448
4601
  // ── Breakpoint preview state (§6.4) ─────────────────────────────────────────
4449
4602
  // Active breakpoint width for the current design (pixels). Controls which
@@ -5696,9 +5849,14 @@ export default function DesignEditor() {
5696
5849
  : undefined;
5697
5850
 
5698
5851
  // Exclude the board file — it is rendered by its own DesignCanvas instance
5699
- // in MultiScreenCanvas and must not appear as a screen frame.
5852
+ // in MultiScreenCanvas and must not appear as a screen frame. Support files
5853
+ // such as CSS are editable files, not visual screens.
5700
5854
  return files
5701
- .filter((file) => !isBoardFile(file.filename))
5855
+ .filter(
5856
+ (file) =>
5857
+ normalizedDesignFileType(file.fileType) === "html" &&
5858
+ !isBoardFile(file.filename),
5859
+ )
5702
5860
  .map((file) => {
5703
5861
  const metadata = getDesignDataRecord(metadataByFileId, file.id);
5704
5862
  const stringValue = (key: string) =>
@@ -6043,14 +6201,29 @@ export default function DesignEditor() {
6043
6201
  };
6044
6202
  }, []);
6045
6203
 
6046
- // Set active file to first file when data loads
6204
+ const defaultActiveFile =
6205
+ files.find(
6206
+ (file) =>
6207
+ normalizedDesignFileType(file.fileType) === "html" &&
6208
+ !isBoardFile(file.filename) &&
6209
+ file.filename.toLowerCase() === "index.html",
6210
+ ) ??
6211
+ files.find(
6212
+ (file) =>
6213
+ normalizedDesignFileType(file.fileType) === "html" &&
6214
+ !isBoardFile(file.filename),
6215
+ ) ??
6216
+ files[0];
6217
+
6218
+ // Set active file to the primary screen when data loads.
6047
6219
  useEffect(() => {
6048
- if (files.length > 0 && !activeFileId) {
6049
- setActiveFileId(files[0].id);
6220
+ if (defaultActiveFile && !activeFileId) {
6221
+ setActiveFileId(defaultActiveFile.id);
6050
6222
  }
6051
- }, [files, activeFileId]);
6223
+ }, [activeFileId, defaultActiveFile]);
6052
6224
 
6053
- const activeFile = files.find((f) => f.id === activeFileId) ?? files[0];
6225
+ const activeFile =
6226
+ files.find((f) => f.id === activeFileId) ?? defaultActiveFile;
6054
6227
  activeFileIdForUndoRef.current = activeFile?.id ?? null;
6055
6228
  const motionTimelineQueryParams =
6056
6229
  id && activeFile?.id
@@ -6196,9 +6369,13 @@ export default function DesignEditor() {
6196
6369
  if (target && !targetFile) return false;
6197
6370
 
6198
6371
  const inspectorTab =
6199
- command.inspectorTab === "design" || command.inspectorTab === "tweaks"
6372
+ command.inspectorTab === "design" ||
6373
+ command.inspectorTab === "tweaks" ||
6374
+ command.inspectorTab === "extensions"
6200
6375
  ? command.inspectorTab
6201
- : command.inspector === "design" || command.inspector === "tweaks"
6376
+ : command.inspector === "design" ||
6377
+ command.inspector === "tweaks" ||
6378
+ command.inspector === "extensions"
6202
6379
  ? command.inspector
6203
6380
  : undefined;
6204
6381
  if (inspectorTab) setActiveInspectorTab(inspectorTab);
@@ -7026,12 +7203,22 @@ export default function DesignEditor() {
7026
7203
  const canvasIframeRef = useMemo<React.RefObject<HTMLIFrameElement | null>>(
7027
7204
  () => ({
7028
7205
  get current() {
7029
- return document.querySelector<HTMLIFrameElement>(
7030
- "iframe[data-design-preview-iframe]",
7206
+ const iframes = Array.from(
7207
+ document.querySelectorAll<HTMLIFrameElement>(
7208
+ "iframe[data-design-preview-iframe]",
7209
+ ),
7210
+ );
7211
+ if (!activeFile?.id) return iframes[0] ?? null;
7212
+ return (
7213
+ iframes.find(
7214
+ (iframe) => iframe.dataset.screenIframeId === activeFile.id,
7215
+ ) ??
7216
+ iframes[0] ??
7217
+ null
7031
7218
  );
7032
7219
  },
7033
7220
  }),
7034
- [],
7221
+ [activeFile?.id],
7035
7222
  );
7036
7223
 
7037
7224
  const handleEditorDragStateChange = useCallback((active: boolean) => {
@@ -7423,6 +7610,8 @@ export default function DesignEditor() {
7423
7610
  setMotionTimelineId(null);
7424
7611
  setMotionTracks([]);
7425
7612
  setMotionDurationMs(1000);
7613
+ setMotionPlayhead(0);
7614
+ setMotionAutoKeyframeEnabled(false);
7426
7615
  setMotionTracksDirty(false);
7427
7616
  setMotionAutosaveRevision(0);
7428
7617
  setMotionHydrationFingerprint(null);
@@ -7564,8 +7753,36 @@ export default function DesignEditor() {
7564
7753
  }, [selectedCodeLayerNode, selectedElement]);
7565
7754
 
7566
7755
  useEffect(() => {
7567
- setShaderFillPreview(null);
7568
- }, [activeFile?.id, selectedElement?.selector, selectedElement?.sourceId]);
7756
+ clearShaderFillPreview();
7757
+ }, [
7758
+ activeFile?.id,
7759
+ clearShaderFillPreview,
7760
+ selectedElement?.selector,
7761
+ selectedElement?.sourceId,
7762
+ ]);
7763
+ useEffect(() => {
7764
+ const urlParams = new URLSearchParams(location.search);
7765
+ const urlKeepsExtensionsInspector =
7766
+ urlParams.get("inspector") === "extensions" ||
7767
+ urlParams.get("inspectorTab") === "extensions";
7768
+ if (activeInspectorTab === "extensions" && urlKeepsExtensionsInspector) {
7769
+ return;
7770
+ }
7771
+ clearShaderFillPreview();
7772
+ }, [
7773
+ activeInspectorTab,
7774
+ clearShaderFillPreview,
7775
+ location.pathname,
7776
+ location.search,
7777
+ ]);
7778
+ useEffect(() => {
7779
+ window.addEventListener("pagehide", clearShaderFillPreview);
7780
+ window.addEventListener("beforeunload", clearShaderFillPreview);
7781
+ return () => {
7782
+ window.removeEventListener("pagehide", clearShaderFillPreview);
7783
+ window.removeEventListener("beforeunload", clearShaderFillPreview);
7784
+ };
7785
+ }, [clearShaderFillPreview]);
7569
7786
 
7570
7787
  // A friendly default name for the create-component dialog, derived from the
7571
7788
  // selected element's layer name / tag.
@@ -7643,6 +7860,56 @@ export default function DesignEditor() {
7643
7860
  return motionTracks.map(({ label: _label, ...track }) => track);
7644
7861
  }, [motionDockOpen, motionTracks]);
7645
7862
 
7863
+ const upsertMotionKeyframesFromStyles = useCallback(
7864
+ (
7865
+ styles: Record<string, string>,
7866
+ elementInfo?: ElementInfo,
7867
+ selector?: string,
7868
+ ) => {
7869
+ if (!motionDockOpen || !motionAutoKeyframeEnabled) return;
7870
+ const info = elementInfo ?? selectedElement ?? undefined;
7871
+ const targetNode = info
7872
+ ? resolveCodeLayerNodeFromElementInfo(activeCodeLayerProjection, info)
7873
+ : selector
7874
+ ? resolveCodeLayerNodeFromBridge(activeCodeLayerProjection, selector)
7875
+ : selectedCodeLayerNode;
7876
+ const targetNodeId =
7877
+ targetNode?.dataAttributes["data-agent-native-node-id"]?.trim() ??
7878
+ info?.sourceId ??
7879
+ selectedCodeLayerNode?.dataAttributes[
7880
+ "data-agent-native-node-id"
7881
+ ]?.trim();
7882
+ if (!targetNodeId) return;
7883
+
7884
+ const label =
7885
+ targetNode?.layerName ||
7886
+ selectedCodeLayerNode?.layerName ||
7887
+ info?.tagName ||
7888
+ "Selected element";
7889
+ setMotionTracks((current) => {
7890
+ return upsertMotionStyleKeyframes({
7891
+ tracks: current,
7892
+ targetNodeId,
7893
+ label,
7894
+ styles,
7895
+ computedStyles:
7896
+ info?.computedStyles ?? selectedElement?.computedStyles,
7897
+ playhead: motionPlayhead,
7898
+ });
7899
+ });
7900
+ markMotionTracksDirty();
7901
+ },
7902
+ [
7903
+ activeCodeLayerProjection,
7904
+ markMotionTracksDirty,
7905
+ motionAutoKeyframeEnabled,
7906
+ motionDockOpen,
7907
+ motionPlayhead,
7908
+ selectedCodeLayerNode,
7909
+ selectedElement,
7910
+ ],
7911
+ );
7912
+
7646
7913
  const inspectCodeData = useMemo<InspectCodeData | undefined>(() => {
7647
7914
  if (!selectedElement) return undefined;
7648
7915
  // Inline/Alpine: the design HTML is the source — show the element's HTML.
@@ -8311,7 +8578,11 @@ export default function DesignEditor() {
8311
8578
  );
8312
8579
 
8313
8580
  const handlePrimitiveCreated = useCallback(
8314
- (screenId: string, nodeId: string, options?: { selectFrame?: boolean }) => {
8581
+ (
8582
+ screenId: string,
8583
+ nodeId: string,
8584
+ options?: { selectFrame?: boolean; nextTool?: "move" | "pen" },
8585
+ ) => {
8315
8586
  // B2/B4 fix: stay in overview mode after drawing a primitive. The user
8316
8587
  // drew a shape on the board — they should remain on the board with the
8317
8588
  // new primitive selected, matching Figma behaviour. We activate the
@@ -8330,7 +8601,7 @@ export default function DesignEditor() {
8330
8601
  setOverviewSelectedScreenIds(
8331
8602
  options?.selectFrame === false ? [] : [screenId],
8332
8603
  );
8333
- setActiveTool("move");
8604
+ setActiveTool(options?.nextTool ?? "move");
8334
8605
  setMode("edit");
8335
8606
  });
8336
8607
  // viewMode stays at "overview" — no setViewMode("single") call here.
@@ -8340,7 +8611,7 @@ export default function DesignEditor() {
8340
8611
  // inserted HTML over separate renders, so post directly to the target
8341
8612
  // iframe with a few short retries instead of relying on a single global
8342
8613
  // bridge callback.
8343
- const textNodeId = pendingTextEditNodeIdRef.current ?? nodeId;
8614
+ const textNodeId = pendingTextEditNodeIdRef.current;
8344
8615
  pendingTextEditNodeIdRef.current = null;
8345
8616
  if (textNodeId) {
8346
8617
  scheduleBeginTextEditForScreen(screenId, textNodeId);
@@ -8828,7 +9099,7 @@ export default function DesignEditor() {
8828
9099
  css,
8829
9100
  });
8830
9101
  },
8831
- onShaderFillPreviewClear: () => setShaderFillPreview(null),
9102
+ onShaderFillPreviewClear: clearShaderFillPreview,
8832
9103
  onShaderFillApplied: (fileId, content, updatedAt) => {
8833
9104
  applyFileContentUpdate(fileId, content, {
8834
9105
  refreshPreview: fileId === activeFile?.id,
@@ -8845,6 +9116,7 @@ export default function DesignEditor() {
8845
9116
  activeFile?.updatedAt,
8846
9117
  activeTool,
8847
9118
  applyFileContentUpdate,
9119
+ clearShaderFillPreview,
8848
9120
  design?.title,
8849
9121
  files,
8850
9122
  id,
@@ -9150,6 +9422,7 @@ export default function DesignEditor() {
9150
9422
  ([, value]) => value !== undefined,
9151
9423
  );
9152
9424
  if (entries.length === 0) return;
9425
+ upsertMotionKeyframesFromStyles(styles, options.elementInfo, selector);
9153
9426
  // Base every patch off the freshest known content, not the closed-over
9154
9427
  // render value. Handlers that fire several onStyleChange calls in one
9155
9428
  // synchronous user action (e.g. fixed-size text → width+height+whiteSpace,
@@ -9466,6 +9739,7 @@ export default function DesignEditor() {
9466
9739
  selectedElement,
9467
9740
  t,
9468
9741
  updateLiveScreenSnapshotContent,
9742
+ upsertMotionKeyframesFromStyles,
9469
9743
  ydoc,
9470
9744
  isSynced,
9471
9745
  ],
@@ -9652,8 +9926,13 @@ export default function DesignEditor() {
9652
9926
  styles,
9653
9927
  elementInfo,
9654
9928
  );
9929
+ upsertMotionKeyframesFromStyles(styles, elementInfo, selector);
9655
9930
  },
9656
- [activeFile?.id, recordPendingVisualStyleEdit],
9931
+ [
9932
+ activeFile?.id,
9933
+ recordPendingVisualStyleEdit,
9934
+ upsertMotionKeyframesFromStyles,
9935
+ ],
9657
9936
  );
9658
9937
 
9659
9938
  const handleVisualStructureChange = useCallback(
@@ -9971,8 +10250,15 @@ export default function DesignEditor() {
9971
10250
  elementInfo?: ElementInfo,
9972
10251
  ) => {
9973
10252
  recordPendingVisualStyleEdit(screenId, selector, styles, elementInfo);
10253
+ if (screenId === activeFile?.id) {
10254
+ upsertMotionKeyframesFromStyles(styles, elementInfo, selector);
10255
+ }
9974
10256
  },
9975
- [recordPendingVisualStyleEdit],
10257
+ [
10258
+ activeFile?.id,
10259
+ recordPendingVisualStyleEdit,
10260
+ upsertMotionKeyframesFromStyles,
10261
+ ],
9976
10262
  );
9977
10263
 
9978
10264
  const handleScreenVisualStructureChange = useCallback(
@@ -10471,16 +10757,15 @@ export default function DesignEditor() {
10471
10757
  ? boardFileId
10472
10758
  : activeFile?.id;
10473
10759
  if (!targetFileId || !canEditDesign || entries.length === 0) return;
10474
- const targetIsBoard = targetFileId === boardFileId;
10475
10760
  const baseContent =
10476
10761
  targetFileId === activeFile?.id
10477
10762
  ? getFreshActiveContent()
10478
10763
  : (getScreenContent(targetFileId) ?? "");
10479
10764
  if (!baseContent && targetFileId !== boardFileId) return;
10480
10765
  const layerHtmls = entries.map((entry) => entry.html);
10481
- const styleSnapshots = targetIsBoard
10482
- ? entries.map((entry) => entry.portableStyleSnapshot)
10483
- : undefined;
10766
+ const styleSnapshots = entries.map(
10767
+ (entry) => entry.portableStyleSnapshot,
10768
+ );
10484
10769
  const applyPasteContentUpdate = (nextContent: string) => {
10485
10770
  if (targetFileId === activeFile?.id) {
10486
10771
  applyLocalContentUpdate(nextContent, {
@@ -10499,7 +10784,11 @@ export default function DesignEditor() {
10499
10784
  // in normal document flow instead of being an absolutely-positioned body
10500
10785
  // child. Fall back to the old position-based clone when nothing is
10501
10786
  // selected or a "Paste here" position is provided.
10502
- if (!position && !targetIsBoard && selectedElement?.selector) {
10787
+ if (
10788
+ !position &&
10789
+ targetFileId !== boardFileId &&
10790
+ selectedElement?.selector
10791
+ ) {
10503
10792
  const selector = selectedCanvasSelector ?? selectedElement.selector;
10504
10793
  const result = insertClonedHtmlLayers(baseContent, layerHtmls, {
10505
10794
  targetSelectors: [selector],
@@ -10586,7 +10875,6 @@ export default function DesignEditor() {
10586
10875
  const baseContent = getFreshActiveContent();
10587
10876
  if (selectedElement?.boundingRect) {
10588
10877
  const { x, y } = selectedElement.boundingRect;
10589
- const targetIsBoard = activeFile.id === boardFileId;
10590
10878
  const result = insertClonedHtmlLayers(
10591
10879
  baseContent,
10592
10880
  entries.map((entry) => entry.html),
@@ -10595,9 +10883,7 @@ export default function DesignEditor() {
10595
10883
  x: x + index * 16,
10596
10884
  y: y + index * 16,
10597
10885
  })),
10598
- styleSnapshots: targetIsBoard
10599
- ? entries.map((entry) => entry.portableStyleSnapshot)
10600
- : undefined,
10886
+ styleSnapshots: entries.map((entry) => entry.portableStyleSnapshot),
10601
10887
  },
10602
10888
  );
10603
10889
  if (!result) return;
@@ -11240,14 +11526,11 @@ export default function DesignEditor() {
11240
11526
  // there is no anchor, preserve absolute mode and rebase left/top to the
11241
11527
  // release point so screen↔board moves behave like Figma absolute layers.
11242
11528
  const destNodeAttrId = result.movedNodeId ?? nodeAttrId;
11243
- const stylePreservedDest =
11244
- targetScreenId === boardFileId
11245
- ? applyPortableStyleSnapshotToHtml(
11246
- result.destHtml,
11247
- destNodeAttrId,
11248
- styleSnapshot,
11249
- )
11250
- : result.destHtml;
11529
+ const stylePreservedDest = applyPortableStyleSnapshotToHtml(
11530
+ result.destHtml,
11531
+ destNodeAttrId,
11532
+ styleSnapshot,
11533
+ );
11251
11534
  const nextDestContent = targetAnchorAttrId
11252
11535
  ? targetDropMode === "absolute-container"
11253
11536
  ? targetLocalPoint && targetAnchorRect
@@ -11398,6 +11681,59 @@ export default function DesignEditor() {
11398
11681
  removedGeometryRedoEntries,
11399
11682
  );
11400
11683
 
11684
+ const nextContentUndoStack: ContentHistoryEntry[] = [];
11685
+ let removedContentUndoEntries = 0;
11686
+ contentUndoStackRef.current.forEach((entry) => {
11687
+ const remainingChanges = getContentHistoryChanges(entry).filter(
11688
+ (change) => !deleteIds.has(change.fileId),
11689
+ );
11690
+ if (remainingChanges.length === 0) {
11691
+ removedContentUndoEntries += 1;
11692
+ return;
11693
+ }
11694
+ nextContentUndoStack.push(
11695
+ remainingChanges.length === 1
11696
+ ? remainingChanges[0]
11697
+ : { changes: remainingChanges },
11698
+ );
11699
+ });
11700
+ contentUndoStackRef.current = nextContentUndoStack;
11701
+ historyOrderRef.current = removeRecentUndoRedoOrderKinds(
11702
+ historyOrderRef.current,
11703
+ "file-content",
11704
+ removedContentUndoEntries,
11705
+ );
11706
+ const nextContentRedoStack: ContentHistoryEntry[] = [];
11707
+ let removedContentRedoEntries = 0;
11708
+ contentRedoStackRef.current.forEach((entry) => {
11709
+ const remainingChanges = getContentHistoryChanges(entry).filter(
11710
+ (change) => !deleteIds.has(change.fileId),
11711
+ );
11712
+ if (remainingChanges.length === 0) {
11713
+ removedContentRedoEntries += 1;
11714
+ return;
11715
+ }
11716
+ nextContentRedoStack.push(
11717
+ remainingChanges.length === 1
11718
+ ? remainingChanges[0]
11719
+ : { changes: remainingChanges },
11720
+ );
11721
+ });
11722
+ contentRedoStackRef.current = nextContentRedoStack;
11723
+ redoOrderRef.current = removeRecentUndoRedoOrderKinds(
11724
+ redoOrderRef.current,
11725
+ "file-content",
11726
+ removedContentRedoEntries,
11727
+ );
11728
+ localContentUndoStackRef.current =
11729
+ localContentUndoStackRef.current.filter(
11730
+ (change) => !deleteIds.has(change.fileId),
11731
+ );
11732
+ localContentRedoStackRef.current =
11733
+ localContentRedoStackRef.current.filter(
11734
+ (change) => !deleteIds.has(change.fileId),
11735
+ );
11736
+
11401
11737
  writeFrameGeometrySnapshot(nextGeometry);
11402
11738
  queryClient.setQueryData(["action", "get-design", { id }], (old: any) => {
11403
11739
  if (!old || typeof old !== "object" || !Array.isArray(old.files)) {
@@ -11541,7 +11877,7 @@ export default function DesignEditor() {
11541
11877
  if (!canEditDesign) return;
11542
11878
  const um = undoManagerRef.current;
11543
11879
  const canUseOverviewHistory = viewModeRef.current === "overview";
11544
- let prunedUndoHistory = false;
11880
+ let prunedUndoHistory = 0;
11545
11881
  const undoContent = (scope: "any" | "local" | "global" = "any") => {
11546
11882
  if (scope !== "global" && um?.canUndo()) {
11547
11883
  um.undo();
@@ -11623,7 +11959,7 @@ export default function DesignEditor() {
11623
11959
  );
11624
11960
  if (changes.length === 0) {
11625
11961
  contentUndoStackRef.current.pop();
11626
- prunedUndoHistory = true;
11962
+ prunedUndoHistory += 1;
11627
11963
  return false;
11628
11964
  }
11629
11965
  contentUndoStackRef.current.pop();
@@ -11691,14 +12027,24 @@ export default function DesignEditor() {
11691
12027
  return true;
11692
12028
  };
11693
12029
 
11694
- const undoByOrder = (preferred?: UndoRedoOrderKind) =>
11695
- preferred === "geometry"
11696
- ? undoGeometry() || undoContent()
11697
- : preferred === "file-content"
11698
- ? undoContent("global") || undoGeometry()
11699
- : preferred === "content"
11700
- ? undoContent("local") || undoContent("global") || undoGeometry()
11701
- : undoContent() || undoGeometry();
12030
+ const undoByOrder = (preferred?: UndoRedoOrderKind) => {
12031
+ if (preferred === "geometry") return undoGeometry() || undoContent();
12032
+ if (preferred === "file-content") {
12033
+ const prunedBefore = prunedUndoHistory;
12034
+ if (undoContent("global")) return true;
12035
+ if (prunedUndoHistory > prunedBefore) return false;
12036
+ return undoGeometry();
12037
+ }
12038
+ if (preferred === "content") {
12039
+ const prunedBefore = prunedUndoHistory;
12040
+ return (
12041
+ undoContent("local") ||
12042
+ undoContent("global") ||
12043
+ (prunedUndoHistory > prunedBefore ? false : undoGeometry())
12044
+ );
12045
+ }
12046
+ return undoContent() || undoGeometry();
12047
+ };
11702
12048
  let didUndo = false;
11703
12049
  if (canUseOverviewHistory) {
11704
12050
  while (!didUndo) {
@@ -11709,7 +12055,7 @@ export default function DesignEditor() {
11709
12055
  } else {
11710
12056
  didUndo = undoContent("local");
11711
12057
  }
11712
- if (didUndo || prunedUndoHistory) {
12058
+ if (didUndo || prunedUndoHistory > 0) {
11713
12059
  syncUndoRedoState();
11714
12060
  }
11715
12061
  }, [
@@ -11731,7 +12077,7 @@ export default function DesignEditor() {
11731
12077
  if (!canEditDesign) return;
11732
12078
  const um = undoManagerRef.current;
11733
12079
  const canUseOverviewHistory = viewModeRef.current === "overview";
11734
- let prunedRedoHistory = false;
12080
+ let prunedRedoHistory = 0;
11735
12081
  const redoContent = (scope: "any" | "local" | "global" = "any") => {
11736
12082
  if (scope !== "global" && um?.canRedo()) {
11737
12083
  um.redo();
@@ -11813,7 +12159,7 @@ export default function DesignEditor() {
11813
12159
  );
11814
12160
  if (changes.length === 0) {
11815
12161
  contentRedoStackRef.current.pop();
11816
- prunedRedoHistory = true;
12162
+ prunedRedoHistory += 1;
11817
12163
  return false;
11818
12164
  }
11819
12165
  contentRedoStackRef.current.pop();
@@ -11881,14 +12227,24 @@ export default function DesignEditor() {
11881
12227
  return true;
11882
12228
  };
11883
12229
 
11884
- const redoByOrder = (preferred?: UndoRedoOrderKind) =>
11885
- preferred === "geometry"
11886
- ? redoGeometry() || redoContent()
11887
- : preferred === "file-content"
11888
- ? redoContent("global") || redoGeometry()
11889
- : preferred === "content"
11890
- ? redoContent("local") || redoContent("global") || redoGeometry()
11891
- : redoContent() || redoGeometry();
12230
+ const redoByOrder = (preferred?: UndoRedoOrderKind) => {
12231
+ if (preferred === "geometry") return redoGeometry() || redoContent();
12232
+ if (preferred === "file-content") {
12233
+ const prunedBefore = prunedRedoHistory;
12234
+ if (redoContent("global")) return true;
12235
+ if (prunedRedoHistory > prunedBefore) return false;
12236
+ return redoGeometry();
12237
+ }
12238
+ if (preferred === "content") {
12239
+ const prunedBefore = prunedRedoHistory;
12240
+ return (
12241
+ redoContent("local") ||
12242
+ redoContent("global") ||
12243
+ (prunedRedoHistory > prunedBefore ? false : redoGeometry())
12244
+ );
12245
+ }
12246
+ return redoContent() || redoGeometry();
12247
+ };
11892
12248
  let didRedo = false;
11893
12249
  if (canUseOverviewHistory) {
11894
12250
  while (!didRedo) {
@@ -11899,7 +12255,7 @@ export default function DesignEditor() {
11899
12255
  } else {
11900
12256
  didRedo = redoContent("local");
11901
12257
  }
11902
- if (didRedo || prunedRedoHistory) {
12258
+ if (didRedo || prunedRedoHistory > 0) {
11903
12259
  syncUndoRedoState();
11904
12260
  }
11905
12261
  }, [
@@ -13234,7 +13590,7 @@ ${serializedHtml}
13234
13590
  };
13235
13591
 
13236
13592
  useEffect(() => {
13237
- if (viewMode === "overview") return;
13593
+ if (viewMode === "overview" && !motionDockOpen) return;
13238
13594
  if (!activeFile || !activeContent.trim()) return;
13239
13595
  const stamped = ensureCodeLayerNodeIdsInHtml(activeContent, {
13240
13596
  source: {
@@ -13246,7 +13602,14 @@ ${serializedHtml}
13246
13602
  });
13247
13603
  if (!stamped.changed || stamped.content === activeContent) return;
13248
13604
  applyLocalContentUpdate(stamped.content, { recordHistory: false });
13249
- }, [activeContent, activeFile, applyLocalContentUpdate, id, viewMode]);
13605
+ }, [
13606
+ activeContent,
13607
+ activeFile,
13608
+ applyLocalContentUpdate,
13609
+ id,
13610
+ motionDockOpen,
13611
+ viewMode,
13612
+ ]);
13250
13613
  const activeCodeLayerTree = useMemo(
13251
13614
  () => buildCodeLayerTree(activeCodeLayerProjection),
13252
13615
  [activeCodeLayerProjection],
@@ -14038,9 +14401,7 @@ ${serializedHtml}
14038
14401
 
14039
14402
  // Detect if the active screen is a localhost/local source so we can show a banner.
14040
14403
  const activeScreenIsLocalSource =
14041
- viewMode === "single" &&
14042
- Boolean(activeFile) &&
14043
- activeOverviewScreen?.sourceType === "localhost";
14404
+ Boolean(activeFile) && activeOverviewScreen?.sourceType === "localhost";
14044
14405
  const activeScreenRouteSourceFile = activeScreenIsLocalSource
14045
14406
  ? getLocalhostRouteSourceFile({
14046
14407
  sourceFile: activeOverviewScreen?.sourceFile,
@@ -15357,12 +15718,8 @@ ${serializedHtml}
15357
15718
  className="h-8 cursor-pointer gap-1.5 rounded-md bg-[var(--design-editor-panel-raised-bg)] px-3 text-sm shadow-none"
15358
15719
  aria-label={t("designEditor.signUpToSave")}
15359
15720
  >
15360
- <a
15361
- href={signInToSaveHref}
15362
- target="_blank"
15363
- rel="noopener noreferrer"
15364
- >
15365
- <span>{t("home.save")}</span>
15721
+ <a href={signInToSaveHref} role="button">
15722
+ <span>{t("designEditor.signUpToSave")}</span>
15366
15723
  </a>
15367
15724
  </Button>
15368
15725
  </TooltipTrigger>
@@ -15375,13 +15732,9 @@ ${serializedHtml}
15375
15732
  variant="default"
15376
15733
  size="sm"
15377
15734
  className="h-8 cursor-pointer gap-1.5 rounded-md !border-[var(--design-editor-accent-color)] !bg-[var(--design-editor-accent-color)] px-3 text-sm !text-[var(--design-editor-accent-contrast-color)] shadow-none hover:!border-[var(--design-editor-accent-hover-color)] hover:!bg-[var(--design-editor-accent-hover-color)] hover:!text-[var(--design-editor-accent-contrast-color)] focus-visible:ring-[var(--design-editor-accent-color)]"
15378
- aria-label={t("designEditor.signUpToShare")}
15735
+ aria-label={t("designEditor.share")}
15379
15736
  >
15380
- <a
15381
- href={signInToShareHref}
15382
- target="_blank"
15383
- rel="noopener noreferrer"
15384
- >
15737
+ <a href={signInToShareHref} role="button">
15385
15738
  <span>{t("designEditor.share")}</span>
15386
15739
  </a>
15387
15740
  </Button>
@@ -16024,20 +16377,22 @@ ${serializedHtml}
16024
16377
  activeLeftPanel === "agent" ? "flex" : "hidden",
16025
16378
  )}
16026
16379
  >
16027
- <AgentChatSurface
16028
- mode="panel"
16029
- className="min-h-0 flex-1 border-0 bg-transparent shadow-none"
16030
- storageKey={DESIGN_CHAT_STORAGE_KEY}
16031
- emptyStateText={t("chat.emptyState")}
16032
- suggestions={[
16033
- t("chat.suggestionLandingPage"),
16034
- t("chat.suggestionBrandMatch"),
16035
- t("chat.suggestionMobile"),
16036
- ]}
16037
- scope={designChatScope}
16038
- showScopeBadge={false}
16039
- browserTabId={browserTabId}
16040
- />
16380
+ {canEditDesign ? (
16381
+ <AgentChatSurface
16382
+ mode="panel"
16383
+ className="min-h-0 flex-1 border-0 bg-transparent shadow-none"
16384
+ storageKey={DESIGN_CHAT_STORAGE_KEY}
16385
+ emptyStateText={t("chat.emptyState")}
16386
+ suggestions={[
16387
+ t("chat.suggestionLandingPage"),
16388
+ t("chat.suggestionBrandMatch"),
16389
+ t("chat.suggestionMobile"),
16390
+ ]}
16391
+ scope={designChatScope}
16392
+ showScopeBadge={false}
16393
+ browserTabId={browserTabId}
16394
+ />
16395
+ ) : null}
16041
16396
  </div>
16042
16397
  <div
16043
16398
  className={cn(
@@ -16581,6 +16936,9 @@ ${serializedHtml}
16581
16936
  }
16582
16937
  fusionUrl={designFusionUrl}
16583
16938
  onComponentSourceJump={handleComponentSourceJump}
16939
+ motionTracks={
16940
+ screenIsActive ? motionTracksWire : []
16941
+ }
16584
16942
  embeddedFrame={{
16585
16943
  viewportWidth: Math.max(
16586
16944
  1,
@@ -16962,6 +17320,13 @@ ${serializedHtml}
16962
17320
  })
16963
17321
  }
16964
17322
  onRequestTweaks={handleRequestTweaks}
17323
+ extensionsPanel={
17324
+ <DesignExtensionsPanel
17325
+ context={designExtensionContext}
17326
+ hideAssetLibrary
17327
+ title={t("designEditor.extensions")}
17328
+ />
17329
+ }
16965
17330
  onStyleChange={handleStyleChange}
16966
17331
  onStylesChange={handleStylesChange}
16967
17332
  onExport={handleInspectorExport}
@@ -16980,6 +17345,21 @@ ${serializedHtml}
16980
17345
  }
16981
17346
  defaultComponentName={defaultComponentName}
16982
17347
  inspectCode={inspectCodeData}
17348
+ aiActions={
17349
+ selectedElement && selectedInspectorElements.length <= 1 ? (
17350
+ <InspectorAiActions
17351
+ selector={
17352
+ selectedCanvasSelector ?? selectedElement.selector
17353
+ }
17354
+ sourceId={selectedElement.sourceId}
17355
+ fileId={activeFile?.id}
17356
+ filename={activeFile?.filename}
17357
+ routeSourceFile={activeScreenRouteSourceFile}
17358
+ designId={id}
17359
+ canEdit={canEditDesign}
17360
+ />
17361
+ ) : undefined
17362
+ }
16983
17363
  statesPanelProps={
16984
17364
  id
16985
17365
  ? {
@@ -17082,6 +17462,10 @@ ${serializedHtml}
17082
17462
  onTracksChange={handleMotionTracksChange}
17083
17463
  onDurationChange={handleMotionDurationChange}
17084
17464
  canvasIframeRef={canvasIframeRef}
17465
+ autoKeyframe={motionAutoKeyframeEnabled}
17466
+ onAutoKeyframeChange={setMotionAutoKeyframeEnabled}
17467
+ playhead={motionPlayhead}
17468
+ onPlayheadChange={setMotionPlayhead}
17085
17469
  selectedTarget={motionSelectedTarget}
17086
17470
  applying={motionAutosavePending}
17087
17471
  />