@agent-native/core 0.84.10 → 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 (67) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +37 -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/client/require-session.tsx +20 -1
  6. package/corpus/core/src/client/sharing/ShareButton.tsx +9 -3
  7. package/corpus/core/src/collab/storage.ts +40 -5
  8. package/corpus/core/src/server/auth.ts +6 -0
  9. package/corpus/templates/assets/.agents/skills/logo-composite/SKILL.md +7 -6
  10. package/corpus/templates/assets/AGENTS.md +15 -0
  11. package/corpus/templates/assets/actions/_helpers.ts +3 -1
  12. package/corpus/templates/assets/actions/create-generation-preset.ts +12 -1
  13. package/corpus/templates/assets/actions/generate-asset.ts +5 -1
  14. package/corpus/templates/assets/actions/generate-image-batch.ts +6 -1
  15. package/corpus/templates/assets/actions/generate-image.ts +18 -11
  16. package/corpus/templates/assets/actions/open-asset-picker.ts +4 -2
  17. package/corpus/templates/assets/actions/update-generation-preset.ts +16 -3
  18. package/corpus/templates/assets/app/i18n/zh-TW.ts +3 -0
  19. package/corpus/templates/assets/app/i18n-data.ts +30 -0
  20. package/corpus/templates/assets/app/routes/brand-kits.$id.tsx +42 -7
  21. package/corpus/templates/assets/app/routes/library.tsx +4 -2
  22. package/corpus/templates/assets/changelog/2026-07-01-generation-presets-can-now-composite-your-brand-s-canonical-.md +6 -0
  23. package/corpus/templates/assets/changelog/2026-07-01-tagging-a-preset-now-briefs-the-agent-on-that-preset-s-aesth.md +6 -0
  24. package/corpus/templates/assets/server/lib/preset-chat-context.ts +118 -0
  25. package/corpus/templates/assets/server/plugins/agent-chat.ts +5 -0
  26. package/corpus/templates/assets/shared/api.ts +1 -0
  27. package/corpus/templates/design/actions/export-html.ts +4 -2
  28. package/corpus/templates/design/actions/export-pdf.ts +3 -1
  29. package/corpus/templates/design/actions/export-svg.ts +4 -2
  30. package/corpus/templates/design/actions/export-zip.ts +10 -3
  31. package/corpus/templates/design/actions/update-file.ts +68 -17
  32. package/corpus/templates/design/app/components/design/DesignCanvas.tsx +3 -2
  33. package/corpus/templates/design/app/components/design/EditPanel.tsx +35 -3
  34. package/corpus/templates/design/app/components/design/MotionDock.tsx +46 -11
  35. package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +80 -30
  36. package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +60 -10
  37. package/corpus/templates/design/app/components/design/canvas-primitive-style.ts +2 -0
  38. package/corpus/templates/design/app/components/design/inspector/InspectorAiActions.tsx +4 -0
  39. package/corpus/templates/design/app/hooks/useAgentEditRequest.ts +2 -2
  40. package/corpus/templates/design/app/i18n-data.ts +1 -1
  41. package/corpus/templates/design/app/pages/DesignEditor.tsx +491 -107
  42. package/corpus/templates/design/app/pages/VisualEdit.tsx +4 -16
  43. package/corpus/templates/design/e2e/helpers.ts +2 -2
  44. package/dist/agent/run-store.d.ts.map +1 -1
  45. package/dist/agent/run-store.js +28 -6
  46. package/dist/agent/run-store.js.map +1 -1
  47. package/dist/client/require-session.d.ts +14 -0
  48. package/dist/client/require-session.d.ts.map +1 -1
  49. package/dist/client/require-session.js +19 -1
  50. package/dist/client/require-session.js.map +1 -1
  51. package/dist/client/sharing/ShareButton.d.ts +4 -2
  52. package/dist/client/sharing/ShareButton.d.ts.map +1 -1
  53. package/dist/client/sharing/ShareButton.js +3 -1
  54. package/dist/client/sharing/ShareButton.js.map +1 -1
  55. package/dist/collab/routes.d.ts +1 -1
  56. package/dist/collab/storage.d.ts.map +1 -1
  57. package/dist/collab/storage.js +31 -5
  58. package/dist/collab/storage.js.map +1 -1
  59. package/dist/file-upload/actions/upload-image.d.ts +2 -2
  60. package/dist/observability/routes.d.ts +8 -8
  61. package/dist/resources/handlers.d.ts +2 -2
  62. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  63. package/dist/server/auth.d.ts.map +1 -1
  64. package/dist/server/auth.js +7 -0
  65. package/dist/server/auth.js.map +1 -1
  66. package/dist/server/transcribe-voice.d.ts +1 -1
  67. package/package.json +1 -1
@@ -138,7 +138,7 @@ import type { StatesPanelProps } from "./StatesPanel";
138
138
  import { TweaksPanelContent } from "./TweaksPanel";
139
139
  import type { ElementInfo } from "./types";
140
140
 
141
- export type InspectorTab = "design" | "tweaks";
141
+ export type InspectorTab = "design" | "tweaks" | "extensions";
142
142
 
143
143
  const MIXED_VALUE = "Mixed";
144
144
 
@@ -216,6 +216,7 @@ interface EditPanelProps {
216
216
  tweakValues?: Record<string, string | number | boolean>;
217
217
  onTweakChange?: (id: string, value: string | number | boolean) => void;
218
218
  onRequestTweaks?: (anchor: HTMLElement) => void;
219
+ extensionsPanel?: ReactNode;
219
220
  onStyleChange: (property: string, value: string) => void;
220
221
  onStylesChange?: (styles: Record<string, string>) => void;
221
222
  onExport?: (settings: ExportSettingsValue[]) => void;
@@ -283,6 +284,8 @@ interface EditPanelProps {
283
284
  defaultComponentName?: string;
284
285
  /** Code-inspection data for the "Inspect code" popover. */
285
286
  inspectCode?: InspectCodeData;
287
+ /** Optional compact AI edit controls for selected/local source elements. */
288
+ aiActions?: ReactNode;
286
289
  }
287
290
 
288
291
  /**
@@ -2960,10 +2963,12 @@ function InspectorTabsHeader({
2960
2963
  activeTab,
2961
2964
  onActiveTabChange,
2962
2965
  trailing,
2966
+ showExtensions,
2963
2967
  }: {
2964
2968
  activeTab: InspectorTab;
2965
2969
  onActiveTabChange: (tab: InspectorTab) => void;
2966
2970
  trailing?: ReactNode;
2971
+ showExtensions?: boolean;
2967
2972
  }) {
2968
2973
  const t = useT();
2969
2974
 
@@ -2986,6 +2991,14 @@ function InspectorTabsHeader({
2986
2991
  >
2987
2992
  {t("designEditor.tweaks")}
2988
2993
  </TabsTrigger>
2994
+ {showExtensions ? (
2995
+ <TabsTrigger
2996
+ value="extensions"
2997
+ className="h-6 rounded-md px-1.5 !text-[11px] font-semibold text-muted-foreground shadow-none transition-colors hover:text-foreground data-[state=active]:bg-[var(--design-editor-panel-raised-bg)] data-[state=active]:text-foreground data-[state=active]:shadow-none"
2998
+ >
2999
+ {"Extensions" /* i18n-ignore design inspector tab */}
3000
+ </TabsTrigger>
3001
+ ) : null}
2989
3002
  </TabsList>
2990
3003
  </Tabs>
2991
3004
  {trailing ? <div className="shrink-0">{trailing}</div> : null}
@@ -3282,7 +3295,7 @@ function CornerRadiusControl({
3282
3295
  value={radius}
3283
3296
  onChange={commitRadius}
3284
3297
  min={0}
3285
- precision={1}
3298
+ precision={0}
3286
3299
  />
3287
3300
  <Tooltip>
3288
3301
  <TooltipTrigger asChild>
@@ -3308,6 +3321,7 @@ function CornerRadiusControl({
3308
3321
  <>
3309
3322
  <AppearanceScrubField
3310
3323
  label={t("editPanel.labels.topLeft")}
3324
+ ariaLabel="Top left"
3311
3325
  icon={IconRadiusTopLeft}
3312
3326
  value={corners.topLeft}
3313
3327
  onChange={(value) =>
@@ -3321,6 +3335,7 @@ function CornerRadiusControl({
3321
3335
  />
3322
3336
  <AppearanceScrubField
3323
3337
  label={t("editPanel.labels.topRight")}
3338
+ ariaLabel="Top right"
3324
3339
  icon={IconRadiusTopRight}
3325
3340
  value={corners.topRight}
3326
3341
  onChange={(value) =>
@@ -3335,6 +3350,7 @@ function CornerRadiusControl({
3335
3350
  <span aria-hidden="true" />
3336
3351
  <AppearanceScrubField
3337
3352
  label={t("editPanel.labels.bottomLeft")}
3353
+ ariaLabel="Bottom left"
3338
3354
  icon={IconRadiusBottomLeft}
3339
3355
  value={corners.bottomLeft}
3340
3356
  onChange={(value) =>
@@ -3348,6 +3364,7 @@ function CornerRadiusControl({
3348
3364
  />
3349
3365
  <AppearanceScrubField
3350
3366
  label={t("editPanel.labels.bottomRight")}
3367
+ ariaLabel="Bottom right"
3351
3368
  icon={IconRadiusBottomRight}
3352
3369
  value={corners.bottomRight}
3353
3370
  onChange={(value) =>
@@ -3368,6 +3385,7 @@ function CornerRadiusControl({
3368
3385
 
3369
3386
  function AppearanceScrubField({
3370
3387
  label,
3388
+ ariaLabel,
3371
3389
  icon,
3372
3390
  value,
3373
3391
  onChange,
@@ -3379,6 +3397,7 @@ function AppearanceScrubField({
3379
3397
  precision,
3380
3398
  }: {
3381
3399
  label: string;
3400
+ ariaLabel?: string;
3382
3401
  icon: (props: { className?: string }) => ReactNode;
3383
3402
  value: number;
3384
3403
  onChange: (value: number) => void;
@@ -3392,7 +3411,7 @@ function AppearanceScrubField({
3392
3411
  return (
3393
3412
  <ScrubInput
3394
3413
  label={label}
3395
- ariaLabel={label}
3414
+ ariaLabel={ariaLabel ?? label}
3396
3415
  icon={icon}
3397
3416
  value={value}
3398
3417
  onChange={onChange}
@@ -6842,6 +6861,7 @@ export function EditPanel({
6842
6861
  tweakValues = {},
6843
6862
  onTweakChange,
6844
6863
  onRequestTweaks,
6864
+ extensionsPanel,
6845
6865
  onStyleChange,
6846
6866
  onStylesChange,
6847
6867
  onExport,
@@ -6858,6 +6878,7 @@ export function EditPanel({
6858
6878
  selectedElementAlreadyComponent = false,
6859
6879
  defaultComponentName = "Component",
6860
6880
  inspectCode,
6881
+ aiActions,
6861
6882
  }: EditPanelProps) {
6862
6883
  const t = useT();
6863
6884
  const [createComponentOpen, setCreateComponentOpen] = useState(false);
@@ -6950,6 +6971,7 @@ export function EditPanel({
6950
6971
  activeTab={activeTab}
6951
6972
  onActiveTabChange={handleActiveTabChange}
6952
6973
  trailing={headerTrailing}
6974
+ showExtensions={!!extensionsPanel}
6953
6975
  />
6954
6976
 
6955
6977
  {activeTab === "design" ? (
@@ -7058,6 +7080,12 @@ export function EditPanel({
7058
7080
  />
7059
7081
  )}
7060
7082
 
7083
+ {aiActions ? (
7084
+ <div className="border-b border-[var(--design-editor-control-border)] px-2 py-1.5">
7085
+ {aiActions}
7086
+ </div>
7087
+ ) : null}
7088
+
7061
7089
  {!inspectorElement && (
7062
7090
  <PageProperties
7063
7091
  styles={pageStyles}
@@ -7228,6 +7256,10 @@ export function EditPanel({
7228
7256
  />
7229
7257
  </div>
7230
7258
  </div>
7259
+ ) : activeTab === "extensions" && extensionsPanel ? (
7260
+ <div className="flex min-h-0 flex-1 flex-col overflow-hidden">
7261
+ {extensionsPanel}
7262
+ </div>
7231
7263
  ) : null}
7232
7264
  </div>
7233
7265
  );
@@ -117,6 +117,14 @@ export interface MotionDockProps {
117
117
  canvasIframeRef?: React.RefObject<HTMLIFrameElement | null>;
118
118
  /** Whether the parent autosave mutation is in flight. */
119
119
  applying?: boolean;
120
+ /** Controlled auto-keyframe state. */
121
+ autoKeyframe?: boolean;
122
+ /** Called when the auto-keyframe toggle changes. */
123
+ onAutoKeyframeChange?: (enabled: boolean) => void;
124
+ /** Controlled playhead position, normalized to [0, 1]. */
125
+ playhead?: number;
126
+ /** Called whenever the playhead moves. */
127
+ onPlayheadChange?: (t: number) => void;
120
128
  /**
121
129
  * The currently-selected canvas element, if any. Required to create the FIRST
122
130
  * track for a layer: the picker animates this node's
@@ -139,6 +147,10 @@ export function MotionDock({
139
147
  onDurationChange,
140
148
  canvasIframeRef,
141
149
  applying = false,
150
+ autoKeyframe: autoKeyframeProp,
151
+ onAutoKeyframeChange,
152
+ playhead: playheadProp,
153
+ onPlayheadChange,
142
154
  selectedTarget = null,
143
155
  }: MotionDockProps) {
144
156
  // Controlled / uncontrolled open state.
@@ -153,13 +165,36 @@ export function MotionDock({
153
165
  );
154
166
 
155
167
  // Playhead position: normalised [0, 1].
156
- const [playhead, setPlayhead] = useState(0);
168
+ const [playhead, setPlayhead] = useState(playheadProp ?? 0);
157
169
  const [playing, setPlaying] = useState(false);
158
170
  const playRafRef = useRef<number | null>(null);
159
171
  const playStartRef = useRef<{ wallMs: number; startT: number } | null>(null);
160
-
161
- // Auto-keyframe mode: clicking the canvas at a time scrubs without writing.
162
- const [autoKeyframe, setAutoKeyframe] = useState(false);
172
+ useEffect(() => {
173
+ if (playheadProp === undefined) return;
174
+ setPlayhead(Math.max(0, Math.min(1, playheadProp)));
175
+ }, [playheadProp]);
176
+
177
+ // Auto-keyframe mode: inspector/style edits create keyframes at the playhead.
178
+ const [autoKeyframeInternal, setAutoKeyframeInternal] = useState(false);
179
+ const autoKeyframe = autoKeyframeProp ?? autoKeyframeInternal;
180
+ const setAutoKeyframe = useCallback(
181
+ (next: boolean | ((current: boolean) => boolean)) => {
182
+ const resolved =
183
+ typeof next === "function"
184
+ ? (next as (current: boolean) => boolean)(autoKeyframe)
185
+ : next;
186
+ setAutoKeyframeInternal(resolved);
187
+ onAutoKeyframeChange?.(resolved);
188
+ },
189
+ [autoKeyframe, onAutoKeyframeChange],
190
+ );
191
+ const setPlayheadValue = useCallback(
192
+ (next: number) => {
193
+ setPlayhead(next);
194
+ onPlayheadChange?.(next);
195
+ },
196
+ [onPlayheadChange],
197
+ );
163
198
 
164
199
  // Dock height (resizable via the top drag handle).
165
200
  const [dockHeight, setDockHeight] = useState(DEFAULT_DOCK_HEIGHT);
@@ -235,7 +270,7 @@ export function MotionDock({
235
270
  if (!playStartRef.current) return;
236
271
  const elapsed = now - playStartRef.current.wallMs;
237
272
  const t = Math.min(1, playStartRef.current.startT + elapsed / durationMs);
238
- setPlayhead(t);
273
+ setPlayheadValue(t);
239
274
  sendPreview(t);
240
275
  if (t < 1) {
241
276
  playRafRef.current = requestAnimationFrame(tick);
@@ -244,7 +279,7 @@ export function MotionDock({
244
279
  }
245
280
  };
246
281
  playRafRef.current = requestAnimationFrame(tick);
247
- }, [durationMs, playhead, sendPreview, stopPlayback]);
282
+ }, [durationMs, playhead, sendPreview, setPlayheadValue, stopPlayback]);
248
283
 
249
284
  useEffect(() => {
250
285
  return () => {
@@ -263,10 +298,10 @@ export function MotionDock({
263
298
  (e.target as HTMLElement).setPointerCapture(e.pointerId);
264
299
  const rect = trackAreaRef.current.getBoundingClientRect();
265
300
  const t = Math.max(0, Math.min(1, (e.clientX - rect.left) / rect.width));
266
- setPlayhead(t);
301
+ setPlayheadValue(t);
267
302
  sendPreview(t);
268
303
  },
269
- [sendPreview, stopPlayback],
304
+ [sendPreview, setPlayheadValue, stopPlayback],
270
305
  );
271
306
 
272
307
  const handleRulerPointerMove = useCallback(
@@ -274,10 +309,10 @@ export function MotionDock({
274
309
  if (!isDraggingPlayhead.current || !trackAreaRef.current) return;
275
310
  const rect = trackAreaRef.current.getBoundingClientRect();
276
311
  const t = Math.max(0, Math.min(1, (e.clientX - rect.left) / rect.width));
277
- setPlayhead(t);
312
+ setPlayheadValue(t);
278
313
  sendPreview(t);
279
314
  },
280
- [sendPreview],
315
+ [sendPreview, setPlayheadValue],
281
316
  );
282
317
 
283
318
  const handleRulerPointerUp = useCallback(() => {
@@ -493,7 +528,7 @@ export function MotionDock({
493
528
  className="size-6 shrink-0"
494
529
  onClick={() => {
495
530
  stopPlayback();
496
- setPlayhead(0);
531
+ setPlayheadValue(0);
497
532
  sendPreview(0);
498
533
  }}
499
534
  aria-label="Reset playhead"
@@ -186,7 +186,11 @@ interface MultiScreenCanvasProps {
186
186
  screenId: string,
187
187
  primitive: CanvasPrimitiveInsert,
188
188
  ) => boolean | string;
189
- onPrimitiveCreated?: (screenId: string, nodeId: string) => void;
189
+ onPrimitiveCreated?: (
190
+ screenId: string,
191
+ nodeId: string,
192
+ options?: { nextTool?: "move" | "pen" },
193
+ ) => void;
190
194
  onPrimitiveReparent?: (args: {
191
195
  sourceNodeId: string;
192
196
  sourceScreenId: string;
@@ -1505,7 +1509,7 @@ export function MultiScreenCanvas({
1505
1509
  setTransformBadge(null);
1506
1510
  }, [clearSelectionRequest, updateSelectedDraftIds, updateSelectedIds]);
1507
1511
 
1508
- // Center the lineup on first mount so the user sees screens, not whitespace.
1512
+ // Center the lineup when the screen footprint changes so new frames stay reachable.
1509
1513
  useEffect(() => {
1510
1514
  if (!surfaceRef.current || screens.length === 0) return;
1511
1515
  const rect = surfaceRef.current.getBoundingClientRect();
@@ -1529,11 +1533,26 @@ export function MultiScreenCanvas({
1529
1533
  );
1530
1534
  const totalWidth = bounds?.width ?? SCREEN_WIDTH;
1531
1535
  const totalHeight = bounds?.height ?? SCREEN_CARD_HEIGHT;
1532
- const visualLeft = Math.max(24, (rect.width - totalWidth * scale) / 2);
1533
- const visualTop = Math.max(24, (rect.height - totalHeight * scale) / 2);
1536
+ // Leave a Figma-like board gutter beside the last frame for quick drops/draws,
1537
+ // and fit tall single frames so lower canvas interactions remain reachable.
1538
+ const widthFitScale =
1539
+ screens.length > 1 && totalWidth > 0
1540
+ ? Math.max(0.1, (rect.width - 180) / totalWidth)
1541
+ : scale;
1542
+ const heightFitScale =
1543
+ totalHeight > 0 ? Math.max(0.1, (rect.height - 96) / totalHeight) : scale;
1544
+ const nextScale = Math.min(scale, widthFitScale, heightFitScale);
1545
+ if (nextScale < scale) {
1546
+ const nextZoom = nextScale * 100;
1547
+ zoomRef.current = nextZoom;
1548
+ setCanvasZoom(nextZoom);
1549
+ onZoomChange?.(nextZoom);
1550
+ }
1551
+ const visualLeft = Math.max(24, (rect.width - totalWidth * nextScale) / 2);
1552
+ const visualTop = Math.max(24, (rect.height - totalHeight * nextScale) / 2);
1534
1553
  const nextPan = {
1535
- x: visualLeft - SURFACE_PADDING * scale,
1536
- y: visualTop - SURFACE_PADDING * scale,
1554
+ x: visualLeft - SURFACE_PADDING * nextScale,
1555
+ y: visualTop - SURFACE_PADDING * nextScale,
1537
1556
  };
1538
1557
  panRef.current = nextPan;
1539
1558
  setPan(nextPan);
@@ -2517,9 +2536,11 @@ export function MultiScreenCanvas({
2517
2536
  updateSelectedIds(() => state.baseSelectedIds);
2518
2537
  updateSelectedDraftIds(() => state.baseSelectedDraftIds);
2519
2538
  } else if (state.type === "pen-node") {
2520
- setActivePenPath(
2521
- state.pathBefore ? clonePenPath(state.pathBefore) : null,
2522
- );
2539
+ const restoredPath = state.pathBefore
2540
+ ? clonePenPath(state.pathBefore)
2541
+ : null;
2542
+ activePenPathRef.current = restoredPath;
2543
+ setActivePenPath(restoredPath);
2523
2544
  setPenGesturePreview(null);
2524
2545
  setPenPointer(null);
2525
2546
  setPenCloseHover(false);
@@ -2792,10 +2813,10 @@ export function MultiScreenCanvas({
2792
2813
  });
2793
2814
  const persisted = handler(boardPrimitive);
2794
2815
  if (!persisted) return null;
2795
- // Return a sentinel PersistedDraftPrimitive so the caller can remove
2796
- // the draft. The sentinel frameId "__board__" is detected downstream.
2816
+ // Return the board file id so the caller can run the same selection
2817
+ // and text-edit activation path used by regular screen primitives.
2797
2818
  return {
2798
- frameId: "__board__",
2819
+ frameId: boardFileId ?? "__board__",
2799
2820
  nodeId:
2800
2821
  (typeof persisted === "string"
2801
2822
  ? persisted
@@ -2836,6 +2857,7 @@ export function MultiScreenCanvas({
2836
2857
  };
2837
2858
  },
2838
2859
  [
2860
+ boardFileId,
2839
2861
  getScreenMetadata,
2840
2862
  getTargetFrameForDraft,
2841
2863
  metadataById,
@@ -2845,7 +2867,11 @@ export function MultiScreenCanvas({
2845
2867
  );
2846
2868
 
2847
2869
  const commitDraftPrimitive = useCallback(
2848
- (nextDraft: DraftPrimitive, preferredFrameId?: string) => {
2870
+ (
2871
+ nextDraft: DraftPrimitive,
2872
+ preferredFrameId?: string,
2873
+ options?: { nextTool?: "move" | "pen" },
2874
+ ) => {
2849
2875
  const persisted = persistDraftPrimitive(nextDraft, preferredFrameId);
2850
2876
  if (persisted) {
2851
2877
  updateDraftPrimitives((current) =>
@@ -2853,9 +2879,9 @@ export function MultiScreenCanvas({
2853
2879
  );
2854
2880
  updateSelectedDraftIds(() => []);
2855
2881
  updateSelectedIds(() => []);
2856
- if (persisted.frameId !== "__board__") {
2857
- onPrimitiveCreated?.(persisted.frameId, persisted.nodeId);
2858
- }
2882
+ onPrimitiveCreated?.(persisted.frameId, persisted.nodeId, {
2883
+ nextTool: options?.nextTool,
2884
+ });
2859
2885
  return;
2860
2886
  }
2861
2887
 
@@ -2937,9 +2963,10 @@ export function MultiScreenCanvas({
2937
2963
  stroke: toolProps?.stroke,
2938
2964
  strokeWidth: toolProps?.strokeWidth,
2939
2965
  }),
2966
+ undefined,
2967
+ { nextTool: "pen" },
2940
2968
  );
2941
2969
  clearActivePenPath();
2942
- onActiveToolChange?.("pen");
2943
2970
  },
2944
2971
  [clearActivePenPath, commitDraftPrimitive, onActiveToolChange, toolProps],
2945
2972
  );
@@ -3041,9 +3068,10 @@ export function MultiScreenCanvas({
3041
3068
  pathBefore: pathSnapshot,
3042
3069
  hasMoved: false,
3043
3070
  };
3044
- setPenGesturePreview(
3045
- appendPenNode(pathSnapshot, createCornerNode(anchor)),
3046
- );
3071
+ const initialPath = appendPenNode(pathSnapshot, createCornerNode(anchor));
3072
+ activePenPathRef.current = initialPath;
3073
+ setActivePenPath(initialPath);
3074
+ setPenGesturePreview(initialPath);
3047
3075
  setPenPointer(null);
3048
3076
  setPenCloseHover(false);
3049
3077
  setIsDragging(true);
@@ -3071,7 +3099,10 @@ export function MultiScreenCanvas({
3071
3099
  : handlePoint,
3072
3100
  )
3073
3101
  : createCornerNode(state.anchor);
3074
- setPenGesturePreview(appendPenNode(state.pathBefore, node));
3102
+ const nextPath = appendPenNode(state.pathBefore, node);
3103
+ activePenPathRef.current = nextPath;
3104
+ setActivePenPath(nextPath);
3105
+ setPenGesturePreview(nextPath);
3075
3106
  };
3076
3107
 
3077
3108
  const handleMouseUp = (ev: MouseEvent) => {
@@ -3090,7 +3121,9 @@ export function MultiScreenCanvas({
3090
3121
  : handlePoint,
3091
3122
  )
3092
3123
  : createCornerNode(state.anchor);
3093
- setActivePenPath(appendPenNode(state.pathBefore, node));
3124
+ const nextPath = appendPenNode(state.pathBefore, node);
3125
+ activePenPathRef.current = nextPath;
3126
+ setActivePenPath(nextPath);
3094
3127
  setPenGesturePreview(null);
3095
3128
  setPenPointer(null);
3096
3129
  setPenCloseHover(false);
@@ -3101,6 +3134,7 @@ export function MultiScreenCanvas({
3101
3134
  const cancelPenGesture = () => {
3102
3135
  const state = dragState.current;
3103
3136
  if (state?.type === "pen-node") {
3137
+ activePenPathRef.current = state.pathBefore;
3104
3138
  setActivePenPath(state.pathBefore);
3105
3139
  }
3106
3140
  setPenGesturePreview(null);
@@ -4730,6 +4764,20 @@ export function MultiScreenCanvas({
4730
4764
  frameGeometry[screen.id] ?? getInitialFrameGeometry(index, metadata),
4731
4765
  };
4732
4766
  });
4767
+ const topScreenId =
4768
+ selectedIds.find((id) =>
4769
+ canvasFrames.some(({ screen }) => screen.id === id),
4770
+ ) ??
4771
+ (activeId && canvasFrames.some(({ screen }) => screen.id === activeId)
4772
+ ? activeId
4773
+ : canvasFrames[0]?.screen.id);
4774
+ const renderCanvasFrames =
4775
+ topScreenId && canvasFrames.some(({ screen }) => screen.id === topScreenId)
4776
+ ? [
4777
+ ...canvasFrames.filter(({ screen }) => screen.id !== topScreenId),
4778
+ ...canvasFrames.filter(({ screen }) => screen.id === topScreenId),
4779
+ ]
4780
+ : canvasFrames;
4733
4781
  const screenContentById = useMemo(() => {
4734
4782
  if (!renderScreenContent) return new Map<string, ReactNode>();
4735
4783
  return new Map(
@@ -4910,7 +4958,7 @@ export function MultiScreenCanvas({
4910
4958
  );
4911
4959
  })()}
4912
4960
 
4913
- {canvasFrames.map(({ screen, metadata, geometry }) => {
4961
+ {renderCanvasFrames.map(({ screen, metadata, geometry }) => {
4914
4962
  return (
4915
4963
  <Screen
4916
4964
  key={screen.id}
@@ -7031,13 +7079,6 @@ function draftPrimitiveToInsert(
7031
7079
  x: Math.round((point.x - frameGeometry.x) * scaleX),
7032
7080
  y: Math.round((point.y - frameGeometry.y) * scaleY),
7033
7081
  });
7034
- const localGeometry = {
7035
- ...draft.geometry,
7036
- x: Math.round((draft.geometry.x - frameGeometry.x) * scaleX),
7037
- y: Math.round((draft.geometry.y - frameGeometry.y) * scaleY),
7038
- width: Math.max(1, Math.round(draft.geometry.width * scaleX)),
7039
- height: Math.max(1, Math.round(draft.geometry.height * scaleY)),
7040
- };
7041
7082
  const scaledPenPath = draft.penPath
7042
7083
  ? scalePenPathToGeometry(draft.penPath, frameGeometry, {
7043
7084
  x: 0,
@@ -7046,6 +7087,15 @@ function draftPrimitiveToInsert(
7046
7087
  height: metadata?.height ?? frameGeometry.height,
7047
7088
  })
7048
7089
  : undefined;
7090
+ const localGeometry = scaledPenPath
7091
+ ? getPenPathGeometry(scaledPenPath)
7092
+ : {
7093
+ ...draft.geometry,
7094
+ x: Math.round((draft.geometry.x - frameGeometry.x) * scaleX),
7095
+ y: Math.round((draft.geometry.y - frameGeometry.y) * scaleY),
7096
+ width: Math.max(1, Math.round(draft.geometry.width * scaleX)),
7097
+ height: Math.max(1, Math.round(draft.geometry.height * scaleY)),
7098
+ };
7049
7099
  return {
7050
7100
  kind: draft.kind,
7051
7101
  nodeId: draft.id,
@@ -58,6 +58,8 @@ declare var __DESIGN_CANVAS_BOARD_SURFACE__: boolean;
58
58
  );
59
59
  chromeTransitionStyle.textContent =
60
60
  '[data-agent-native-edit-overlay="selection"]{transition:border-width 150ms ease-out}' +
61
+ '[data-agent-native-empty-text-editing="true"] [data-agent-native-edit-overlay="selection"]{display:none!important}' +
62
+ "[data-agent-native-text-editing]{outline:none!important;outline-offset:0!important}" +
61
63
  "[data-agent-native-edge-handle],[data-agent-native-edit-handle],[data-agent-native-rotate-handle]{transition:width 150ms ease-out,height 150ms ease-out,border-width 150ms ease-out,top 150ms ease-out,bottom 150ms ease-out,left 150ms ease-out,right 150ms ease-out}" +
62
64
  "[data-agent-native-spacing-line]{position:absolute;display:none;pointer-events:none;border-radius:999px}" +
63
65
  "[data-agent-native-spacing-region]{position:absolute;display:none;box-sizing:border-box;pointer-events:auto;background-size:6px 6px}" +
@@ -2402,12 +2404,24 @@ declare var __DESIGN_CANVAS_BOARD_SURFACE__: boolean;
2402
2404
  }
2403
2405
 
2404
2406
  function refreshOverlays(): void {
2407
+ var textEditingEl =
2408
+ activeTextEditEl ||
2409
+ (document.querySelector(
2410
+ "[data-agent-native-text-editing]",
2411
+ ) as HTMLElement | null);
2405
2412
  if (hoveredEl && hoveredEl !== selectedEl) {
2406
2413
  positionOverlay(highlightOverlay, hoveredEl);
2407
2414
  } else {
2408
2415
  highlightOverlay.style.display = "none";
2409
2416
  }
2410
- if (selectedEl) {
2417
+ if (textEditingEl) {
2418
+ if (activeTextEditEl === textEditingEl) {
2419
+ updateTextEditingChrome(textEditingEl, "", "");
2420
+ }
2421
+ if (!hasTextCharacters(textEditingEl)) {
2422
+ hideSelectionOverlay();
2423
+ }
2424
+ } else if (selectedEl) {
2411
2425
  positionOverlay(selectionOverlay, selectedEl);
2412
2426
  } else {
2413
2427
  hideParentAutoLayoutOverlay();
@@ -2819,9 +2833,15 @@ declare var __DESIGN_CANVAS_BOARD_SURFACE__: boolean;
2819
2833
  originalMinWidth: string,
2820
2834
  originalMinHeight: string,
2821
2835
  ): void {
2822
- target.style.outline = "";
2823
- target.style.outlineOffset = "";
2836
+ target.style.outline = "none";
2837
+ target.style.outlineStyle = "none";
2838
+ target.style.outlineWidth = "0px";
2839
+ target.style.outlineColor = "transparent";
2840
+ target.style.outlineOffset = "0px";
2824
2841
  if (hasTextCharacters(target)) {
2842
+ document.documentElement.removeAttribute(
2843
+ "data-agent-native-empty-text-editing",
2844
+ );
2825
2845
  target.style.minWidth = originalMinWidth;
2826
2846
  target.style.minHeight = originalMinHeight;
2827
2847
  positionOverlay(selectionOverlay, target);
@@ -2830,6 +2850,10 @@ declare var __DESIGN_CANVAS_BOARD_SURFACE__: boolean;
2830
2850
  }
2831
2851
  target.style.minWidth = originalMinWidth || "1px";
2832
2852
  target.style.minHeight = originalMinHeight || "1em";
2853
+ document.documentElement.setAttribute(
2854
+ "data-agent-native-empty-text-editing",
2855
+ "true",
2856
+ );
2833
2857
  hideSelectionOverlay();
2834
2858
  setSelectionOverlayResizeChromeVisible(false);
2835
2859
  }
@@ -5021,24 +5045,37 @@ declare var __DESIGN_CANVAS_BOARD_SURFACE__: boolean;
5021
5045
  // code-layer node rather than a runtime-only descendant (which would emit a
5022
5046
  // brittle body > div:nth-of-type(...) selector that never resolves).
5023
5047
  selectedEl = selectionTargetForHit(target) || target;
5048
+ var programmaticTextEdit =
5049
+ !!e &&
5050
+ (e as unknown as { agentNativeProgrammaticTextEdit?: boolean })
5051
+ .agentNativeProgrammaticTextEdit === true;
5024
5052
  var originalText = target.textContent || "";
5025
5053
  var originalHtml = target.innerHTML || "";
5026
5054
  var originalMinWidth = target.style.minWidth;
5027
5055
  var originalMinHeight = target.style.minHeight;
5028
5056
  var originalBorderColor = target.style.borderColor;
5057
+ var originalOutline = target.style.outline;
5058
+ var originalOutlineOffset = target.style.outlineOffset;
5029
5059
  var committed = false;
5030
5060
  activeTextEditEl = target;
5031
5061
  target.setAttribute("contenteditable", "true");
5032
5062
  target.setAttribute("data-agent-native-text-editing", "true");
5033
5063
  target.style.cursor = "text";
5034
5064
  target.style.borderColor = "transparent";
5065
+ target.style.outline = "none";
5066
+ target.style.outlineStyle = "none";
5067
+ target.style.outlineWidth = "0px";
5068
+ target.style.outlineColor = "transparent";
5069
+ target.style.outlineOffset = "0px";
5035
5070
  setTextEditingPointerPassthrough(true);
5036
5071
  updateTextEditingChrome(target, originalMinWidth, originalMinHeight);
5037
- postElementSelect(target, e);
5038
- (window.parent as Window).postMessage(
5039
- { type: "element-dblclick-text", payload: getElementInfo(target) },
5040
- "*",
5041
- );
5072
+ if (!programmaticTextEdit) {
5073
+ postElementSelect(target, e);
5074
+ (window.parent as Window).postMessage(
5075
+ { type: "element-dblclick-text", payload: getElementInfo(target) },
5076
+ "*",
5077
+ );
5078
+ }
5042
5079
  postTextEditingState(target, true);
5043
5080
 
5044
5081
  function finish(commit) {
@@ -5053,9 +5090,12 @@ declare var __DESIGN_CANVAS_BOARD_SURFACE__: boolean;
5053
5090
  document.removeEventListener("selectionchange", onSelectionChange);
5054
5091
  target.removeAttribute("contenteditable");
5055
5092
  target.removeAttribute("data-agent-native-text-editing");
5093
+ document.documentElement.removeAttribute(
5094
+ "data-agent-native-empty-text-editing",
5095
+ );
5056
5096
  target.style.cursor = "";
5057
- target.style.outline = "";
5058
- target.style.outlineOffset = "";
5097
+ target.style.outline = originalOutline;
5098
+ target.style.outlineOffset = originalOutlineOffset;
5059
5099
  target.style.minWidth = originalMinWidth;
5060
5100
  target.style.minHeight = originalMinHeight;
5061
5101
  target.style.borderColor = originalBorderColor;
@@ -5077,6 +5117,15 @@ declare var __DESIGN_CANVAS_BOARD_SURFACE__: boolean;
5077
5117
  }
5078
5118
 
5079
5119
  function onBlur() {
5120
+ if (programmaticTextEdit && !(target.textContent || "").trim()) {
5121
+ window.setTimeout(function () {
5122
+ if (committed || (target.textContent || "").trim()) return;
5123
+ target.focus();
5124
+ updateTextEditingChrome(target, originalMinWidth, originalMinHeight);
5125
+ postTextEditingState(target, true);
5126
+ }, 0);
5127
+ return;
5128
+ }
5080
5129
  finish(true);
5081
5130
  }
5082
5131
 
@@ -5302,6 +5351,7 @@ declare var __DESIGN_CANVAS_BOARD_SURFACE__: boolean;
5302
5351
  clientX: bteCenterX,
5303
5352
  clientY: bteCenterY,
5304
5353
  target: textTarget,
5354
+ agentNativeProgrammaticTextEdit: true,
5305
5355
  preventDefault: function () {},
5306
5356
  stopPropagation: function () {},
5307
5357
  stopImmediatePropagation: function () {},
@@ -248,6 +248,8 @@ export function canvasPrimitiveReactStyle(
248
248
  // For text kind, clear fill-as-background since text uses `color`
249
249
  if (kind === "text") {
250
250
  style.background = "transparent";
251
+ style.outline = "none";
252
+ style.outlineOffset = 0;
251
253
  }
252
254
 
253
255
  return style;
@@ -25,6 +25,8 @@ export interface InspectorAiActionsProps {
25
25
  fileId?: string;
26
26
  /** Active file name (e.g. "index.html"). */
27
27
  filename?: string;
28
+ /** Localhost-backed source file, when the selected screen comes from a local app. */
29
+ routeSourceFile?: string;
28
30
  /** The design id. */
29
31
  designId?: string;
30
32
  /** When false, the controls are disabled. */
@@ -43,6 +45,7 @@ export function InspectorAiActions({
43
45
  sourceId,
44
46
  fileId,
45
47
  filename,
48
+ routeSourceFile,
46
49
  designId,
47
50
  canEdit,
48
51
  }: InspectorAiActionsProps) {
@@ -57,6 +60,7 @@ export function InspectorAiActions({
57
60
  sourceId,
58
61
  fileId,
59
62
  filename,
63
+ routeSourceFile,
60
64
  designId,
61
65
  };
62
66
  const disabled = !canEdit || !request.trim();