@agent-native/core 0.163.3 → 0.164.1

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 (202) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/analytics/app/hooks/use-dashboard-chat-context.ts +29 -0
  3. package/corpus/templates/analytics/app/pages/sessions/SessionsPage.tsx +1 -2
  4. package/corpus/templates/analytics/netlify.toml +2 -0
  5. package/corpus/templates/assets/netlify.toml +2 -0
  6. package/corpus/templates/brain/netlify.toml +2 -0
  7. package/corpus/templates/calendar/app/components/calendar/DayView.tsx +2 -1
  8. package/corpus/templates/calendar/changelog/2026-08-19-calendar-page-loads-stay-fast-after-idle-periods.md +6 -0
  9. package/corpus/templates/calendar/netlify.toml +2 -0
  10. package/corpus/templates/chat/netlify.toml +2 -0
  11. package/corpus/templates/clips/app/components/player/player-controls.tsx +3 -3
  12. package/corpus/templates/clips/app/components/player/reaction-emojis.ts +12 -0
  13. package/corpus/templates/clips/app/components/player/reactions-tray.tsx +90 -9
  14. package/corpus/templates/clips/app/components/player/scrubber.tsx +43 -10
  15. package/corpus/templates/clips/app/components/player/video-player.tsx +105 -15
  16. package/corpus/templates/clips/app/routes/_app.settings._index.tsx +1 -2
  17. package/corpus/templates/clips/app/routes/_index.tsx +10 -2
  18. package/corpus/templates/clips/app/routes/r.$recordingId.tsx +13 -8
  19. package/corpus/templates/clips/app/routes/share.$shareId.tsx +15 -8
  20. package/corpus/templates/clips/netlify.toml +2 -0
  21. package/corpus/templates/clips/server/routes/[...page].head.ts +5 -0
  22. package/corpus/templates/content/netlify.toml +2 -0
  23. package/corpus/templates/design/.agents/skills/design-editor-architecture/SKILL.md +103 -0
  24. package/corpus/templates/design/.agents/skills/design-generation/SKILL.md +2 -1
  25. package/corpus/templates/design/AGENTS.md +3 -1
  26. package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +3 -2
  27. package/corpus/templates/design/app/components/design/editor/AgentNativeMenuMark.tsx +25 -0
  28. package/corpus/templates/design/app/components/design/editor/DesignBottomToolbar.tsx +411 -0
  29. package/corpus/templates/design/app/components/design/editor/DesignCollaborators.tsx +153 -0
  30. package/corpus/templates/design/app/components/design/editor/DesignWorkspaceRail.tsx +231 -0
  31. package/corpus/templates/design/app/components/design/editor/MakeRealDialog.tsx +223 -0
  32. package/corpus/templates/design/app/components/design/editor/PendingScreenDeletionDialog.tsx +58 -0
  33. package/corpus/templates/design/app/components/design/editor/PendingVisualStyleWarningDialog.tsx +56 -0
  34. package/corpus/templates/design/app/components/design/editor/ReadOnlyEditorPanel.tsx +23 -0
  35. package/corpus/templates/design/app/components/design/editor/SaveTemplateDialog.tsx +161 -0
  36. package/corpus/templates/design/app/components/design/editor/toolbar-controls.tsx +185 -0
  37. package/corpus/templates/design/app/pages/DesignEditor.tsx +20913 -0
  38. package/corpus/templates/design/app/pages/design-editor/command-types.ts +120 -0
  39. package/corpus/templates/design/app/pages/design-editor/commands/README.md +193 -0
  40. package/corpus/templates/design/app/pages/design-editor/commands/add-auto-layout.ts +416 -0
  41. package/corpus/templates/design/app/pages/design-editor/commands/add-screen.ts +112 -0
  42. package/corpus/templates/design/app/pages/design-editor/commands/align-selection.ts +183 -0
  43. package/corpus/templates/design/app/pages/design-editor/commands/apply-design-editor-command.ts +243 -0
  44. package/corpus/templates/design/app/pages/design-editor/commands/apply-file-content-update.ts +236 -0
  45. package/corpus/templates/design/app/pages/design-editor/commands/apply-local-content-update.ts +304 -0
  46. package/corpus/templates/design/app/pages/design-editor/commands/apply-pending-visual-styles-with-agent.ts +376 -0
  47. package/corpus/templates/design/app/pages/design-editor/commands/apply-to-source.ts +132 -0
  48. package/corpus/templates/design/app/pages/design-editor/commands/can-move-layer.ts +117 -0
  49. package/corpus/templates/design/app/pages/design-editor/commands/change-selected-z-index.ts +212 -0
  50. package/corpus/templates/design/app/pages/design-editor/commands/commit-relative-style-delta-to-selected-layers.ts +176 -0
  51. package/corpus/templates/design/app/pages/design-editor/commands/commit-styles-to-selected-layers.ts +175 -0
  52. package/corpus/templates/design/app/pages/design-editor/commands/commit-visual-styles.ts +710 -0
  53. package/corpus/templates/design/app/pages/design-editor/commands/confirm-make-real.ts +99 -0
  54. package/corpus/templates/design/app/pages/design-editor/commands/copy-as-figma-svg.ts +95 -0
  55. package/corpus/templates/design/app/pages/design-editor/commands/copy-selection.ts +156 -0
  56. package/corpus/templates/design/app/pages/design-editor/commands/create-primitive.ts +291 -0
  57. package/corpus/templates/design/app/pages/design-editor/commands/create-screen-frame.ts +131 -0
  58. package/corpus/templates/design/app/pages/design-editor/commands/cross-screen-element-drop.ts +577 -0
  59. package/corpus/templates/design/app/pages/design-editor/commands/delete-files.ts +364 -0
  60. package/corpus/templates/design/app/pages/design-editor/commands/delete-selection.ts +521 -0
  61. package/corpus/templates/design/app/pages/design-editor/commands/detach-instance-menu-action.ts +88 -0
  62. package/corpus/templates/design/app/pages/design-editor/commands/distribute-selection.ts +114 -0
  63. package/corpus/templates/design/app/pages/design-editor/commands/download-all-screens-pdf.ts +116 -0
  64. package/corpus/templates/design/app/pages/design-editor/commands/download-pdf.ts +103 -0
  65. package/corpus/templates/design/app/pages/design-editor/commands/download-svg.ts +159 -0
  66. package/corpus/templates/design/app/pages/design-editor/commands/duplicate-screen.ts +201 -0
  67. package/corpus/templates/design/app/pages/design-editor/commands/duplicate-selection.ts +279 -0
  68. package/corpus/templates/design/app/pages/design-editor/commands/editor-paste.ts +81 -0
  69. package/corpus/templates/design/app/pages/design-editor/commands/enter-hotkey.ts +155 -0
  70. package/corpus/templates/design/app/pages/design-editor/commands/enter-single-screen.ts +168 -0
  71. package/corpus/templates/design/app/pages/design-editor/commands/escape-hotkey.ts +212 -0
  72. package/corpus/templates/design/app/pages/design-editor/commands/frame-selection.ts +119 -0
  73. package/corpus/templates/design/app/pages/design-editor/commands/geometry-commit.ts +169 -0
  74. package/corpus/templates/design/app/pages/design-editor/commands/get-selected-layer-snapshots.ts +177 -0
  75. package/corpus/templates/design/app/pages/design-editor/commands/group-selection.ts +127 -0
  76. package/corpus/templates/design/app/pages/design-editor/commands/iframe-context-menu.ts +121 -0
  77. package/corpus/templates/design/app/pages/design-editor/commands/import-figma-clipboard-into-design.ts +119 -0
  78. package/corpus/templates/design/app/pages/design-editor/commands/layer-marquee-selection-change.ts +163 -0
  79. package/corpus/templates/design/app/pages/design-editor/commands/layer-move-to-screen.ts +331 -0
  80. package/corpus/templates/design/app/pages/design-editor/commands/layer-move.ts +517 -0
  81. package/corpus/templates/design/app/pages/design-editor/commands/layer-rename.ts +247 -0
  82. package/corpus/templates/design/app/pages/design-editor/commands/layer-selection-change.ts +161 -0
  83. package/corpus/templates/design/app/pages/design-editor/commands/mode-change.ts +122 -0
  84. package/corpus/templates/design/app/pages/design-editor/commands/nudge-selection.ts +213 -0
  85. package/corpus/templates/design/app/pages/design-editor/commands/overview-primitive-reparent.ts +318 -0
  86. package/corpus/templates/design/app/pages/design-editor/commands/paste-copied-screens.ts +114 -0
  87. package/corpus/templates/design/app/pages/design-editor/commands/paste-over-selection.ts +70 -0
  88. package/corpus/templates/design/app/pages/design-editor/commands/paste-selection.ts +436 -0
  89. package/corpus/templates/design/app/pages/design-editor/commands/paste-to-replace.ts +135 -0
  90. package/corpus/templates/design/app/pages/design-editor/commands/pasted-image-files.ts +209 -0
  91. package/corpus/templates/design/app/pages/design-editor/commands/persist-frame-geometry-save.ts +113 -0
  92. package/corpus/templates/design/app/pages/design-editor/commands/persist-tweak-save.ts +160 -0
  93. package/corpus/templates/design/app/pages/design-editor/commands/primitive-created.ts +168 -0
  94. package/corpus/templates/design/app/pages/design-editor/commands/record-pending-live-layer-state-edit.ts +149 -0
  95. package/corpus/templates/design/app/pages/design-editor/commands/record-pending-live-structure-edit.ts +200 -0
  96. package/corpus/templates/design/app/pages/design-editor/commands/record-pending-live-text-edit.ts +152 -0
  97. package/corpus/templates/design/app/pages/design-editor/commands/record-pending-visual-style-edit.ts +247 -0
  98. package/corpus/templates/design/app/pages/design-editor/commands/redo.ts +1086 -0
  99. package/corpus/templates/design/app/pages/design-editor/commands/render-png-blob.ts +200 -0
  100. package/corpus/templates/design/app/pages/design-editor/commands/save-file-content.ts +207 -0
  101. package/corpus/templates/design/app/pages/design-editor/commands/screen-element-select.ts +262 -0
  102. package/corpus/templates/design/app/pages/design-editor/commands/screen-text-content-change.ts +216 -0
  103. package/corpus/templates/design/app/pages/design-editor/commands/screen-visual-duplicate-change.ts +108 -0
  104. package/corpus/templates/design/app/pages/design-editor/commands/screen-visual-structure-change.ts +280 -0
  105. package/corpus/templates/design/app/pages/design-editor/commands/screen-visual-style-change.ts +151 -0
  106. package/corpus/templates/design/app/pages/design-editor/commands/send-overview-annotations.ts +88 -0
  107. package/corpus/templates/design/app/pages/design-editor/commands/send-runtime-layer-move-semantic-handoff.ts +122 -0
  108. package/corpus/templates/design/app/pages/design-editor/commands/send-runtime-layer-semantic-handoff.ts +143 -0
  109. package/corpus/templates/design/app/pages/design-editor/commands/send-runtime-layer-state-semantic-handoff.ts +115 -0
  110. package/corpus/templates/design/app/pages/design-editor/commands/start-retry-generation.ts +152 -0
  111. package/corpus/templates/design/app/pages/design-editor/commands/start-sidebar-resize.ts +99 -0
  112. package/corpus/templates/design/app/pages/design-editor/commands/style-change.ts +152 -0
  113. package/corpus/templates/design/app/pages/design-editor/commands/styles-change.ts +155 -0
  114. package/corpus/templates/design/app/pages/design-editor/commands/suggest-auto-layout.ts +125 -0
  115. package/corpus/templates/design/app/pages/design-editor/commands/text-content-change.ts +201 -0
  116. package/corpus/templates/design/app/pages/design-editor/commands/tidy-up.ts +123 -0
  117. package/corpus/templates/design/app/pages/design-editor/commands/toggle-layer-hidden.ts +167 -0
  118. package/corpus/templates/design/app/pages/design-editor/commands/toggle-layer-locked.ts +175 -0
  119. package/corpus/templates/design/app/pages/design-editor/commands/toggle-motion-keyframe.ts +99 -0
  120. package/corpus/templates/design/app/pages/design-editor/commands/tweak-prompt-submit.ts +84 -0
  121. package/corpus/templates/design/app/pages/design-editor/commands/undo.ts +906 -0
  122. package/corpus/templates/design/app/pages/design-editor/commands/ungroup-selection.ts +170 -0
  123. package/corpus/templates/design/app/pages/design-editor/commands/visual-duplicate-change.ts +119 -0
  124. package/corpus/templates/design/app/pages/design-editor/commands/visual-structure-change.ts +274 -0
  125. package/corpus/templates/design/app/pages/design-editor/commands/write-frame-geometry-snapshot.ts +99 -0
  126. package/corpus/templates/design/app/pages/design-editor/derive/design-breakpoints.ts +56 -0
  127. package/corpus/templates/design/app/pages/design-editor/derive/overview-screens.ts +146 -0
  128. package/corpus/templates/design/app/pages/design-editor/domains/use-tweaks.ts +326 -0
  129. package/corpus/templates/design/app/pages/design-editor/editor-constants.ts +54 -0
  130. package/corpus/templates/design/app/pages/design-editor/editor-helpers.ts +183 -0
  131. package/corpus/templates/design/app/pages/design-editor/effects/adopt-db-file-content.ts +270 -0
  132. package/corpus/templates/design/app/pages/design-editor/effects/mirror-selection-to-agent-chat.ts +134 -0
  133. package/corpus/templates/design/app/pages/design-editor/effects/motion-autosave.ts +335 -0
  134. package/corpus/templates/design/app/pages/design-editor/effects/observe-collab-text.ts +183 -0
  135. package/corpus/templates/design/app/pages/design-editor/effects/publish-agent-selection-context.ts +252 -0
  136. package/corpus/templates/design/app/pages/design-editor/effects/resume-pending-generation.ts +178 -0
  137. package/corpus/templates/design/app/pages/design-editor/effects/seed-collab-content.ts +157 -0
  138. package/corpus/templates/design/app/pages/design-editor/preview-html.ts +19 -0
  139. package/corpus/templates/design/netlify.toml +2 -0
  140. package/corpus/templates/design/shared/board-file.ts +2 -1
  141. package/corpus/templates/dispatch/netlify.toml +2 -0
  142. package/corpus/templates/forms/netlify.toml +2 -0
  143. package/corpus/templates/mail/netlify.toml +2 -0
  144. package/corpus/templates/plan/netlify.toml +2 -0
  145. package/corpus/templates/slides/netlify.toml +2 -0
  146. package/dist/agent/engine/builder-engine.d.ts +6 -4
  147. package/dist/agent/engine/builder-engine.js +91 -26
  148. package/dist/agent/engine/registry.js +16 -0
  149. package/dist/cli/code-agent-agent-tools.d.ts +2 -0
  150. package/dist/cli/code-agent-agent-tools.js +188 -0
  151. package/dist/cli/code-agent-collaboration.d.ts +34 -0
  152. package/dist/cli/code-agent-collaboration.js +104 -0
  153. package/dist/cli/code-agent-executor.js +6 -0
  154. package/dist/cli/code-agent-schedules.d.ts +54 -0
  155. package/dist/cli/code-agent-schedules.js +198 -0
  156. package/dist/client/AgentPanel.js +1 -1
  157. package/dist/client/ConnectBuilderCard.js +5 -2
  158. package/dist/client/EnvironmentBadge.js +6 -5
  159. package/dist/client/chat/run-recovery.js +3 -2
  160. package/dist/client/chat/tool-call-display.js +2 -3
  161. package/dist/client/components/CodeRequiredDialog.js +4 -2
  162. package/dist/client/i18n.js +5 -4
  163. package/dist/client/settings/SettingsPanel.js +1 -1
  164. package/dist/client/settings/VoiceTranscriptionSection.js +1 -1
  165. package/dist/client/settings/useBuilderStatus.d.ts +6 -1
  166. package/dist/client/settings/useBuilderStatus.js +22 -33
  167. package/dist/client/transcription/BuilderTranscriptionCta.js +1 -1
  168. package/dist/code-agents/index.d.ts +2 -0
  169. package/dist/code-agents/index.js +2 -0
  170. package/dist/collab/routes.d.ts +1 -1
  171. package/dist/localization/shared.d.ts +2 -0
  172. package/dist/localization/shared.js +5 -0
  173. package/dist/mcp-client/oauth-client.d.ts +1 -0
  174. package/dist/mcp-client/oauth-client.js +50 -1
  175. package/dist/observability/routes.d.ts +1 -1
  176. package/dist/provider-api/actions/provider-api.d.ts +2 -2
  177. package/dist/resources/handlers.d.ts +1 -1
  178. package/dist/server/auth-marketing-locales.d.ts +7 -0
  179. package/dist/server/auth-marketing-locales.js +1322 -0
  180. package/dist/server/auth-marketing.d.ts +1 -0
  181. package/dist/server/auth-marketing.js +35 -6
  182. package/dist/server/auth.js +4 -17
  183. package/dist/server/builder-browser.d.ts +22 -0
  184. package/dist/server/builder-browser.js +77 -1
  185. package/dist/server/builder-oauth.d.ts +50 -0
  186. package/dist/server/builder-oauth.js +263 -0
  187. package/dist/server/core-routes-plugin.d.ts +11 -0
  188. package/dist/server/core-routes-plugin.js +291 -415
  189. package/dist/server/credential-provider.d.ts +1 -1
  190. package/dist/server/credential-provider.js +1 -1
  191. package/dist/server/csrf.js +2 -2
  192. package/dist/server/onboarding-html.js +52 -120
  193. package/dist/server/realtime-token.d.ts +1 -1
  194. package/dist/server/transcribe-voice.d.ts +1 -1
  195. package/dist/settings/store.d.ts +9 -0
  196. package/dist/settings/store.js +19 -0
  197. package/dist/templates/chat/netlify.toml +2 -0
  198. package/dist/vite/client.js +35 -37
  199. package/docs/content/deployment.mdx +2 -0
  200. package/docs/content/multi-app-workspace.mdx +1 -1
  201. package/package.json +2 -2
  202. package/src/templates/chat/netlify.toml +2 -0
@@ -0,0 +1,416 @@
1
+ import { getFrameGroupBounds } from "@shared/canvas-math";
2
+ import type { CodeLayerNode, CodeLayerTreeNode } from "@shared/code-layer";
3
+ import { applyVisualEdit, buildCodeLayerProjection } from "@shared/code-layer";
4
+ import { normalizeDesignSourceType } from "@shared/source-mode";
5
+ import type { Dispatch, RefObject, SetStateAction } from "react";
6
+ import { toast } from "sonner";
7
+
8
+ import { trace } from "@/components/design/design-trace";
9
+ import type { ElementInfo } from "@/components/design/types";
10
+ import type { ClipboardContentMutationPublication } from "@/lib/clipboard-content-lineage";
11
+ import type { EffectiveCodeLayerState } from "@/pages/design-editor/code-layer-state";
12
+ import {
13
+ codeLayerPatchMessage,
14
+ elementInfoFromCodeLayerNode,
15
+ } from "@/pages/design-editor/code-layer-state";
16
+ import type { RuntimeLayerSnapshot } from "@/pages/design-editor/command-types";
17
+ import type { OverviewScreen } from "@/pages/design-editor/derive/overview-screens";
18
+ import type { AlignableRect } from "@/pages/design-editor/layout-operations";
19
+ import { inferAutoLayoutFromChildren } from "@/pages/design-editor/layout-operations";
20
+ import {
21
+ enableInlineScreenAutoLayout,
22
+ getRuntimeScreenAutoLayoutSubjectIds,
23
+ } from "@/pages/design-editor/screen-auto-layout";
24
+ import { overviewSelectionTargetsElement } from "@/pages/design-editor/selection-state";
25
+ import type { DesignFile } from "@/pages/design-editor/types";
26
+
27
+ export interface AddAutoLayoutArgs {
28
+ activeFile: DesignFile;
29
+ applyFileContentUpdate: (
30
+ fileId: string,
31
+ nextContent: string,
32
+ options?: {
33
+ refreshPreview?: boolean;
34
+ skipPreview?: boolean;
35
+ forcePreviewFullDocument?: boolean;
36
+ persist?: boolean;
37
+ recordHistory?: boolean;
38
+ updatedAt?: string;
39
+ clipboardMutation?: ClipboardContentMutationPublication;
40
+ },
41
+ ) => void;
42
+ applyLocalContentUpdate: (
43
+ nextContent: string,
44
+ options?: {
45
+ refreshPreview?: boolean;
46
+ skipPreview?: boolean;
47
+ forcePreviewFullDocument?: boolean;
48
+ immediateSave?: boolean;
49
+ persist?: boolean;
50
+ recordHistory?: boolean;
51
+ historyBeforeContent?: string;
52
+ updatedAt?: string;
53
+ clipboardMutation?: ClipboardContentMutationPublication;
54
+ },
55
+ ) => void;
56
+ canEditDesign: boolean;
57
+ codeLayerOwnerByNodeIdRef: RefObject<
58
+ Map<
59
+ string,
60
+ {
61
+ fileId: string;
62
+ node: CodeLayerNode;
63
+ tree: CodeLayerTreeNode[];
64
+ runtimeOnly: boolean;
65
+ }
66
+ >
67
+ >;
68
+ designSourceType: "inline" | "localhost" | "fusion";
69
+ effectiveCodeLayerStateRef: RefObject<EffectiveCodeLayerState>;
70
+ files: DesignFile[];
71
+ getActiveFileSelectedNodeIds: (content: string) => string[];
72
+ getFreshActiveContent: () => string;
73
+ getScreenContent: (screenId: string) => string;
74
+ overviewScreens: OverviewScreen[];
75
+ overviewSelectedScreenIds: string[];
76
+ rectFromCodeLayerNode: (node: CodeLayerNode) => AlignableRect;
77
+ runtimeLayerSnapshotsById: Record<string, RuntimeLayerSnapshot>;
78
+ selectedElement: ElementInfo | null;
79
+ selectedLayerIdsState: string[];
80
+ sendRuntimeLayerSemanticHandoff: (
81
+ operation: "group" | "ungroup" | "auto-layout",
82
+ layerIds: readonly string[],
83
+ options?: {
84
+ desiredChange?: string;
85
+ description?: string;
86
+ commandContext?: string;
87
+ },
88
+ ) => boolean;
89
+ setSelectedElement: Dispatch<SetStateAction<ElementInfo | null>>;
90
+ setSelectedLayerIdsState: Dispatch<SetStateAction<string[]>>;
91
+ t: (key: string, options?: Record<string, unknown>) => string;
92
+ viewModeRef: RefObject<"single" | "overview">;
93
+ }
94
+
95
+ export function runAddAutoLayout({
96
+ activeFile,
97
+ applyFileContentUpdate,
98
+ applyLocalContentUpdate,
99
+ canEditDesign,
100
+ codeLayerOwnerByNodeIdRef,
101
+ designSourceType,
102
+ effectiveCodeLayerStateRef,
103
+ files,
104
+ getActiveFileSelectedNodeIds,
105
+ getFreshActiveContent,
106
+ getScreenContent,
107
+ overviewScreens,
108
+ overviewSelectedScreenIds,
109
+ rectFromCodeLayerNode,
110
+ runtimeLayerSnapshotsById,
111
+ selectedElement,
112
+ selectedLayerIdsState,
113
+ sendRuntimeLayerSemanticHandoff,
114
+ setSelectedElement,
115
+ setSelectedLayerIdsState,
116
+ t,
117
+ viewModeRef,
118
+ }: AddAutoLayoutArgs) {
119
+ trace("structure", "add-auto-layout", {
120
+ layers: selectedLayerIdsState.length,
121
+ view: viewModeRef.current,
122
+ });
123
+ if (!canEditDesign) return;
124
+
125
+ // Overview handles whole screens; a layer selection must still reach the
126
+ // element path below, as Figma applies Shift+A to whatever is selected.
127
+ if (
128
+ viewModeRef.current === "overview" &&
129
+ !overviewSelectionTargetsElement({
130
+ selectedElement,
131
+ selectedLayerIds: selectedLayerIdsState,
132
+ fileIds: files.map((file) => file.id),
133
+ })
134
+ ) {
135
+ if (overviewSelectedScreenIds.length === 0) return;
136
+ if (overviewSelectedScreenIds.length !== 1 || selectedElement !== null) {
137
+ toast(t("designEditor.toasts.autoLayoutScreensUnsupported"));
138
+ return;
139
+ }
140
+ const screenId = overviewSelectedScreenIds[0]!;
141
+ if (
142
+ effectiveCodeLayerStateRef.current.lockedIds.has(screenId) ||
143
+ effectiveCodeLayerStateRef.current.hiddenIds.has(screenId)
144
+ ) {
145
+ return;
146
+ }
147
+ const screen = overviewScreens.find(
148
+ (candidate) => candidate.id === screenId,
149
+ );
150
+ if (!screen) return;
151
+ const screenSourceType =
152
+ normalizeDesignSourceType(screen.sourceType) ?? designSourceType;
153
+
154
+ if (screenSourceType === "localhost") {
155
+ const snapshot = runtimeLayerSnapshotsById[screenId];
156
+ if (!snapshot) {
157
+ toast.error(t("designEditor.toasts.reactSourceAnchorsLoading"));
158
+ return;
159
+ }
160
+ const runtimeSubjectIds = getRuntimeScreenAutoLayoutSubjectIds(
161
+ buildCodeLayerProjection(snapshot.html),
162
+ );
163
+ if (runtimeSubjectIds.length === 0) {
164
+ toast.error(t("designEditor.toasts.reactSourceAnchorsLoading"));
165
+ return;
166
+ }
167
+ sendRuntimeLayerSemanticHandoff("auto-layout", runtimeSubjectIds, {
168
+ desiredChange:
169
+ runtimeSubjectIds.length > 1
170
+ ? "Wrap the selected screen's top-level React roots in one inferred auto-layout container while preserving their visual order and all unrelated behavior."
171
+ : "Enable inferred auto layout on the selected screen's editable React root container while preserving its children and all unrelated behavior.",
172
+ description: `enable auto layout on the editable root of screen ${screenId}`,
173
+ commandContext:
174
+ "Apply this selected-screen auto-layout command to the connected React source.",
175
+ });
176
+ return;
177
+ }
178
+
179
+ if (screenSourceType === "inline") {
180
+ const baseContent = getScreenContent(screenId);
181
+ if (!baseContent) return;
182
+ const result = enableInlineScreenAutoLayout({
183
+ content: baseContent,
184
+ width: screen.width,
185
+ height: screen.height,
186
+ });
187
+ if (result.status === "unsupported") {
188
+ toast(t("designEditor.toasts.autoLayoutScreensUnsupported"));
189
+ return;
190
+ }
191
+ if (result.status === "failed") {
192
+ toast.error(
193
+ codeLayerPatchMessage(
194
+ result.message,
195
+ t("designEditor.toasts.layerMoveFailed"),
196
+ ),
197
+ { duration: 4000 },
198
+ );
199
+ return;
200
+ }
201
+ if (result.status === "applied") {
202
+ applyFileContentUpdate(screenId, result.content, {
203
+ forcePreviewFullDocument: true,
204
+ });
205
+ }
206
+ return;
207
+ }
208
+
209
+ toast(t("designEditor.toasts.autoLayoutScreensUnsupported"));
210
+ return;
211
+ }
212
+
213
+ if (!activeFile) return;
214
+ const selectedRuntimeLayerIds = selectedLayerIdsState.filter(
215
+ (layerId) => codeLayerOwnerByNodeIdRef.current.get(layerId)?.runtimeOnly,
216
+ );
217
+ if (selectedRuntimeLayerIds.length > 0) {
218
+ sendRuntimeLayerSemanticHandoff("auto-layout", selectedRuntimeLayerIds);
219
+ return;
220
+ }
221
+ const baseContent = getFreshActiveContent();
222
+ const nodeIds = getActiveFileSelectedNodeIds(baseContent);
223
+ if (nodeIds.length === 0) return;
224
+ const projection = buildCodeLayerProjection(baseContent);
225
+ const nodesById = new Map(projection.nodes.map((node) => [node.id, node]));
226
+
227
+ if (nodeIds.length >= 2) {
228
+ // (b) multi-selection: wrap siblings into a new inferred flex
229
+ // container in one call (wrapNodes already strips each child's own
230
+ // position/left/top/right/bottom when autoLayout is true).
231
+ const selectedNodes = nodeIds
232
+ .map((nodeId) => nodesById.get(nodeId))
233
+ .filter((node): node is CodeLayerNode => Boolean(node));
234
+ if (selectedNodes.length < 2) return;
235
+ const selectedRects = selectedNodes.map(rectFromCodeLayerNode);
236
+ const bounds = getFrameGroupBounds(selectedRects);
237
+ const inferred = inferAutoLayoutFromChildren(
238
+ bounds
239
+ ? {
240
+ x: bounds.left,
241
+ y: bounds.top,
242
+ width: bounds.width,
243
+ height: bounds.height,
244
+ }
245
+ : { x: 0, y: 0, width: 0, height: 0 },
246
+ selectedRects,
247
+ );
248
+ const patch = applyVisualEdit(baseContent, {
249
+ kind: "wrapNodes",
250
+ targetIds: nodeIds,
251
+ autoLayout: true,
252
+ });
253
+ if (patch.result.status !== "applied") {
254
+ toast.error(
255
+ codeLayerPatchMessage(
256
+ patch.result.message,
257
+ t("designEditor.toasts.layerMoveFailed"),
258
+ ),
259
+ { duration: 4000 },
260
+ );
261
+ return;
262
+ }
263
+ let nextContent = patch.content;
264
+ const wrapperId = patch.result.wrapperNodeId;
265
+ if (wrapperId) {
266
+ const gapPatch = applyVisualEdit(nextContent, {
267
+ kind: "style",
268
+ target: { nodeId: wrapperId },
269
+ property: "flex-direction",
270
+ value: inferred.direction,
271
+ });
272
+ if (gapPatch.result.status === "applied") {
273
+ nextContent = gapPatch.content;
274
+ const paddingPatch = applyVisualEdit(nextContent, {
275
+ kind: "style",
276
+ target: { nodeId: wrapperId },
277
+ property: "gap",
278
+ value: `${inferred.gap}px`,
279
+ });
280
+ if (paddingPatch.result.status === "applied") {
281
+ nextContent = paddingPatch.content;
282
+ }
283
+ }
284
+ }
285
+ applyLocalContentUpdate(nextContent, { skipPreview: true });
286
+ if (wrapperId) {
287
+ const taggedProjection = buildCodeLayerProjection(nextContent);
288
+ const wrapperNode = taggedProjection.nodes.find(
289
+ (n) => n.dataAttributes["data-agent-native-node-id"] === wrapperId,
290
+ );
291
+ if (wrapperNode) {
292
+ setSelectedLayerIdsState([wrapperNode.id]);
293
+ setSelectedElement(elementInfoFromCodeLayerNode(wrapperNode));
294
+ }
295
+ }
296
+ return;
297
+ }
298
+
299
+ // (a) single selected element. Figma wraps a leaf in a new vertical,
300
+ // zero-padding auto-layout frame; an existing container is converted.
301
+ const soleNode = nodesById.get(nodeIds[0]!);
302
+ if (!soleNode) return;
303
+ // An empty frame is a container, not a leaf: Figma converts it in place
304
+ // and only wraps a true leaf (text, shape) in a new auto-layout frame.
305
+ const soleIsFrame = soleNode.dataAttributes["data-an-primitive"] === "frame";
306
+ if (soleNode.children.length === 0 && !soleIsFrame) {
307
+ const wrapped = applyVisualEdit(baseContent, {
308
+ kind: "wrapNodes",
309
+ targetIds: [soleNode.id],
310
+ autoLayout: true,
311
+ });
312
+ if (wrapped.result.status !== "applied") {
313
+ toast.error(
314
+ codeLayerPatchMessage(
315
+ wrapped.result.message,
316
+ t("designEditor.toasts.layerMoveFailed"),
317
+ ),
318
+ { duration: 4000 },
319
+ );
320
+ return;
321
+ }
322
+ let nextContent = wrapped.content;
323
+ const wrapperId = wrapped.result.wrapperNodeId;
324
+ if (wrapperId) {
325
+ for (const [property, value] of [
326
+ ["flex-direction", "column"],
327
+ ["gap", "10px"],
328
+ ["padding", "0px"],
329
+ ["width", "fit-content"],
330
+ ["height", "fit-content"],
331
+ ] as const) {
332
+ const styled = applyVisualEdit(nextContent, {
333
+ kind: "style",
334
+ target: { nodeId: wrapperId },
335
+ property,
336
+ value,
337
+ });
338
+ if (styled.result.status === "applied") nextContent = styled.content;
339
+ }
340
+ }
341
+ applyLocalContentUpdate(nextContent, { skipPreview: true });
342
+ if (wrapperId) {
343
+ const projectionAfter = buildCodeLayerProjection(nextContent);
344
+ const wrapperNode = projectionAfter.nodes.find(
345
+ (node) =>
346
+ node.dataAttributes["data-agent-native-node-id"] === wrapperId,
347
+ );
348
+ if (wrapperNode) {
349
+ setSelectedLayerIdsState([wrapperNode.id]);
350
+ setSelectedElement(elementInfoFromCodeLayerNode(wrapperNode));
351
+ }
352
+ }
353
+ return;
354
+ }
355
+ const childNodes = soleNode.children
356
+ .map((childId) => nodesById.get(childId))
357
+ .filter((node): node is CodeLayerNode => Boolean(node));
358
+ // An empty frame still takes auto layout in Figma, and
359
+ // inferAutoLayoutFromChildren already defaults that case to a column.
360
+ const containerRect = rectFromCodeLayerNode(soleNode);
361
+ const childRects = childNodes.map(rectFromCodeLayerNode);
362
+ const inferred = inferAutoLayoutFromChildren(containerRect, childRects);
363
+ const patch = applyVisualEdit(baseContent, {
364
+ kind: "autoLayout",
365
+ targetId: soleNode.id,
366
+ enabled: true,
367
+ direction: inferred.direction,
368
+ gap: `${inferred.gap}px`,
369
+ });
370
+ if (patch.result.status !== "applied") {
371
+ toast.error(
372
+ codeLayerPatchMessage(
373
+ patch.result.message,
374
+ t("designEditor.toasts.layerMoveFailed"),
375
+ ),
376
+ { duration: 4000 },
377
+ );
378
+ return;
379
+ }
380
+ let nextContent = patch.content;
381
+ // Figma reflows children when auto layout is enabled; opting one out is the
382
+ // explicit "ignore auto layout" toggle. wrapNodes already strips these on
383
+ // the multi-selection path — do the same for a single container.
384
+ // An empty value is rejected by isSafeStyleValue, so neutralise with CSS
385
+ // initial values rather than trying to remove the declarations.
386
+ const reflowResets: Array<[string, string]> = [
387
+ ["position", "static"],
388
+ ["left", "auto"],
389
+ ["top", "auto"],
390
+ ["right", "auto"],
391
+ ["bottom", "auto"],
392
+ ];
393
+ for (const childNode of childNodes) {
394
+ for (const [property, value] of reflowResets) {
395
+ const stripped = applyVisualEdit(nextContent, {
396
+ kind: "style",
397
+ target: { nodeId: childNode.id },
398
+ property,
399
+ value,
400
+ });
401
+ if (stripped.result.status === "applied") nextContent = stripped.content;
402
+ }
403
+ }
404
+ if (inferred.padding > 0) {
405
+ const paddingPatch = applyVisualEdit(nextContent, {
406
+ kind: "style",
407
+ target: { nodeId: soleNode.id },
408
+ property: "padding",
409
+ value: `${inferred.padding}px`,
410
+ });
411
+ if (paddingPatch.result.status === "applied") {
412
+ nextContent = paddingPatch.content;
413
+ }
414
+ }
415
+ applyLocalContentUpdate(nextContent, { skipPreview: true });
416
+ }
@@ -0,0 +1,112 @@
1
+ import { useActionMutation } from "@agent-native/core/client/hooks";
2
+ import type { CanvasFrameGeometryById } from "@shared/canvas-frames";
3
+ import type { QueryClient } from "@tanstack/react-query";
4
+ import { toast } from "sonner";
5
+
6
+ import { getInitialFrameGeometry } from "@/components/design/multi-screen/frame-geometry";
7
+ import type { FrameGeometry } from "@/components/design/multi-screen/types";
8
+ import { prettyScreenName } from "@/lib/screen-names";
9
+ import {
10
+ blankScreenHtml,
11
+ nextBlankScreenFilename,
12
+ } from "@/pages/design-editor/canvas-primitive-insert";
13
+ import type { OverviewScreen } from "@/pages/design-editor/derive/overview-screens";
14
+ import type { FileCreationHistoryEntry } from "@/pages/design-editor/history";
15
+ import type { DesignFile } from "@/pages/design-editor/types";
16
+
17
+ export interface AddScreenArgs {
18
+ canEditDesign: boolean;
19
+ canvasFrameGeometryById: CanvasFrameGeometryById;
20
+ createFileMutation: ReturnType<
21
+ typeof useActionMutation<undefined, undefined, "create-file">
22
+ >;
23
+ files: DesignFile[];
24
+ focusCreatedScreen: (screenId: string, geometry: FrameGeometry) => void;
25
+ id: string | undefined;
26
+ optimisticallyInsertCreatedFile: (args: {
27
+ fileId: string;
28
+ filename: string;
29
+ fileType: DesignFile["fileType"];
30
+ content: string;
31
+ result?: Record<string, unknown> | null;
32
+ }) => void;
33
+ overviewScreens: OverviewScreen[];
34
+ queryClient: QueryClient;
35
+ recordFileCreationHistoryEntry: (entry: FileCreationHistoryEntry) => void;
36
+ t: (key: string, options?: Record<string, unknown>) => string;
37
+ writeFrameGeometrySnapshot: (
38
+ geometryById: CanvasFrameGeometryById,
39
+ options?: { syncViewportFrameIds?: string[]; pinHeightFrameIds?: string[] },
40
+ ) => void;
41
+ }
42
+
43
+ export function runAddScreen({
44
+ canEditDesign,
45
+ canvasFrameGeometryById,
46
+ createFileMutation,
47
+ files,
48
+ focusCreatedScreen,
49
+ id,
50
+ optimisticallyInsertCreatedFile,
51
+ overviewScreens,
52
+ queryClient,
53
+ recordFileCreationHistoryEntry,
54
+ t,
55
+ writeFrameGeometrySnapshot,
56
+ }: AddScreenArgs) {
57
+ if (!id || !canEditDesign) return;
58
+ const filename = nextBlankScreenFilename(files);
59
+ const content = blankScreenHtml(prettyScreenName(filename));
60
+ const nextGeometry = getInitialFrameGeometry(overviewScreens.length, {
61
+ width: 1280,
62
+ height: 2560,
63
+ });
64
+ createFileMutation.mutate(
65
+ {
66
+ designId: id,
67
+ filename,
68
+ content,
69
+ fileType: "html",
70
+ } as any,
71
+ {
72
+ onSuccess: (result: any) => {
73
+ const nextId = typeof result?.id === "string" ? result.id : null;
74
+ if (nextId) {
75
+ optimisticallyInsertCreatedFile({
76
+ fileId: nextId,
77
+ filename,
78
+ fileType: "html",
79
+ content,
80
+ result,
81
+ });
82
+ writeFrameGeometrySnapshot({
83
+ ...canvasFrameGeometryById,
84
+ [nextId]: nextGeometry,
85
+ });
86
+ focusCreatedScreen(nextId, nextGeometry);
87
+ recordFileCreationHistoryEntry({
88
+ filename,
89
+ content,
90
+ fileType: "html",
91
+ geometry: nextGeometry,
92
+ });
93
+ }
94
+ // Refetch only when there is no created id to insert optimistically:
95
+ // a whole-design refetch re-downloads every screen's HTML, which is
96
+ // what made adding a frame feel slow.
97
+ if (!nextId) {
98
+ queryClient.invalidateQueries({
99
+ queryKey: ["action", "get-design"],
100
+ });
101
+ }
102
+ },
103
+ onError: (error) => {
104
+ toast.error(
105
+ error instanceof Error
106
+ ? error.message
107
+ : t("designEditor.toasts.screenDuplicateError"),
108
+ );
109
+ },
110
+ },
111
+ );
112
+ }