@agent-native/core 0.163.4 → 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 (200) 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/progress/routes.d.ts +1 -1
  177. package/dist/provider-api/actions/provider-api.d.ts +2 -2
  178. package/dist/resources/handlers.d.ts +1 -1
  179. package/dist/server/auth-marketing-locales.d.ts +7 -0
  180. package/dist/server/auth-marketing-locales.js +1322 -0
  181. package/dist/server/auth-marketing.d.ts +1 -0
  182. package/dist/server/auth-marketing.js +35 -6
  183. package/dist/server/auth.js +4 -17
  184. package/dist/server/builder-browser.d.ts +22 -0
  185. package/dist/server/builder-browser.js +77 -1
  186. package/dist/server/builder-oauth.d.ts +50 -0
  187. package/dist/server/builder-oauth.js +263 -0
  188. package/dist/server/core-routes-plugin.d.ts +11 -0
  189. package/dist/server/core-routes-plugin.js +291 -415
  190. package/dist/server/credential-provider.d.ts +1 -1
  191. package/dist/server/credential-provider.js +1 -1
  192. package/dist/server/csrf.js +2 -2
  193. package/dist/server/onboarding-html.js +52 -120
  194. package/dist/settings/store.d.ts +9 -0
  195. package/dist/settings/store.js +19 -0
  196. package/dist/templates/chat/netlify.toml +2 -0
  197. package/docs/content/deployment.mdx +2 -0
  198. package/docs/content/multi-app-workspace.mdx +1 -1
  199. package/package.json +2 -2
  200. package/src/templates/chat/netlify.toml +2 -0
@@ -0,0 +1,152 @@
1
+ import type { AgentChatMessage } from "@agent-native/core/client/agent-chat";
2
+ import type { PromptComposerSubmitOptions } from "@agent-native/core/client/composer";
3
+ import type { Dispatch, RefObject, SetStateAction } from "react";
4
+
5
+ import type { UploadedFile } from "@/components/editor/PromptDialog";
6
+ import { patchPendingGeneration } from "@/lib/pending-generation";
7
+ import type { RetryablePrompt } from "@/pages/design-editor/command-types";
8
+ import { MAX_GENERATION_ATTEMPTS } from "@/pages/design-editor/editor-constants";
9
+ import {
10
+ designGenerationDirectives,
11
+ designTemplateRefinementDirectives,
12
+ formatUploadedFileContext,
13
+ imageAttachmentsFromUploadedFiles,
14
+ loadDesignSystemGenerationContext,
15
+ } from "@/pages/design-editor/generation-prompt-directives";
16
+ import type { DesignData } from "@/pages/design-editor/types";
17
+
18
+ export interface StartRetryGenerationArgs {
19
+ agentSubmit: (
20
+ message: string,
21
+ context: string,
22
+ options?: Omit<AgentChatMessage, "message" | "context">,
23
+ ) => string;
24
+ canEditDesign: boolean;
25
+ clearAutoRetryTimer: () => void;
26
+ clearGenerationCompleteTimer: () => void;
27
+ design: DesignData | null;
28
+ generationModelRef: RefObject<{
29
+ model?: string;
30
+ engine?: string;
31
+ effort?: PromptComposerSubmitOptions["effort"];
32
+ } | null>;
33
+ id: string | undefined;
34
+ setGenerationChatTabId: Dispatch<SetStateAction<string | null>>;
35
+ setGenerationIssue: Dispatch<SetStateAction<string | null>>;
36
+ setHasPendingGeneration: Dispatch<SetStateAction<boolean>>;
37
+ setRetryablePrompt: Dispatch<
38
+ SetStateAction<{
39
+ prompt: string;
40
+ files: UploadedFile[];
41
+ model?: PromptComposerSubmitOptions["model"];
42
+ engine?: PromptComposerSubmitOptions["engine"];
43
+ effort?: PromptComposerSubmitOptions["effort"];
44
+ designSystemId?: string | null;
45
+ attempt?: number;
46
+ source?: string;
47
+ templateId?: string;
48
+ templateBaselineFiles?: Array<{ id: string; contentHash: string }>;
49
+ } | null>
50
+ >;
51
+ }
52
+
53
+ export async function runStartRetryGeneration(
54
+ {
55
+ agentSubmit,
56
+ canEditDesign,
57
+ clearAutoRetryTimer,
58
+ clearGenerationCompleteTimer,
59
+ design,
60
+ generationModelRef,
61
+ id,
62
+ setGenerationChatTabId,
63
+ setGenerationIssue,
64
+ setHasPendingGeneration,
65
+ setRetryablePrompt,
66
+ }: StartRetryGenerationArgs,
67
+ promptState: RetryablePrompt,
68
+ attempt: number,
69
+ mode: "manual" | "auto",
70
+ ) {
71
+ if (!id || !design || !canEditDesign) return;
72
+ clearAutoRetryTimer();
73
+ const fileContext = formatUploadedFileContext(promptState.files);
74
+ const images = imageAttachmentsFromUploadedFiles(promptState.files);
75
+ const designSystemContext = await loadDesignSystemGenerationContext(
76
+ promptState.designSystemId,
77
+ );
78
+ const retryLine =
79
+ mode === "auto"
80
+ ? `(Automatically retrying attempt ${attempt} of ${MAX_GENERATION_ATTEMPTS} — the previous attempt did not complete.)`
81
+ : "(Retrying — the previous attempt did not complete.)";
82
+ const context = [
83
+ promptState.templateId
84
+ ? `The user picked the "${promptState.source ?? "template"}" template (id: "${promptState.templateId}").`
85
+ : `The user has design "${id}" (title: "${design.title}") open and wants to fill it with design files.`,
86
+ `User request: "${promptState.prompt}"`,
87
+ promptState.designSystemId
88
+ ? `Design system id: "${promptState.designSystemId}"`
89
+ : "",
90
+ designSystemContext,
91
+ fileContext,
92
+ "",
93
+ retryLine,
94
+ ...(promptState.templateId
95
+ ? designTemplateRefinementDirectives(
96
+ id,
97
+ promptState.templateId,
98
+ promptState.designSystemId,
99
+ )
100
+ : designGenerationDirectives(id, promptState.designSystemId)),
101
+ ].join("\n");
102
+ clearGenerationCompleteTimer();
103
+ setGenerationIssue(null);
104
+ const startedAt = Date.now();
105
+ patchPendingGeneration(id, {
106
+ prompt: promptState.prompt,
107
+ files: promptState.files,
108
+ title: design.title,
109
+ designSystemId: promptState.designSystemId,
110
+ model: promptState.model,
111
+ engine: promptState.engine,
112
+ effort: promptState.effort,
113
+ source: promptState.source,
114
+ templateId: promptState.templateId,
115
+ templateBaselineFiles: promptState.templateBaselineFiles,
116
+ attempt,
117
+ startedAt,
118
+ });
119
+ setHasPendingGeneration(true);
120
+ setRetryablePrompt(null);
121
+ generationModelRef.current = {
122
+ model: promptState.model,
123
+ engine: promptState.engine,
124
+ effort: promptState.effort,
125
+ };
126
+ const runTabId = agentSubmit(
127
+ `Generate design for "${design.title}": ${promptState.prompt}`,
128
+ context,
129
+ {
130
+ model: promptState.model,
131
+ engine: promptState.engine,
132
+ effort: promptState.effort,
133
+ images,
134
+ },
135
+ );
136
+ setGenerationChatTabId(runTabId);
137
+ patchPendingGeneration(id, {
138
+ prompt: promptState.prompt,
139
+ files: promptState.files,
140
+ title: design.title,
141
+ designSystemId: promptState.designSystemId,
142
+ model: promptState.model,
143
+ engine: promptState.engine,
144
+ effort: promptState.effort,
145
+ source: promptState.source,
146
+ templateId: promptState.templateId,
147
+ templateBaselineFiles: promptState.templateBaselineFiles,
148
+ attempt,
149
+ runTabId,
150
+ startedAt,
151
+ });
152
+ }
@@ -0,0 +1,99 @@
1
+ import type {
2
+ Dispatch,
3
+ PointerEvent as ReactPointerEvent,
4
+ RefObject,
5
+ SetStateAction,
6
+ } from "react";
7
+
8
+ import type { DesignLeftPanel } from "@/pages/design-editor/types";
9
+
10
+ export interface StartSidebarResizeArgs {
11
+ activeLeftPanel: DesignLeftPanel;
12
+ leftSidebarContentRef: RefObject<HTMLDivElement | null>;
13
+ leftSidebarWidth: number;
14
+ rightSidebarContentRef: RefObject<HTMLDivElement | null>;
15
+ rightSidebarWidth: number;
16
+ setLeftSidebarWidth: Dispatch<SetStateAction<number>>;
17
+ setRightSidebarWidth: Dispatch<SetStateAction<number>>;
18
+ }
19
+
20
+ export function runStartSidebarResize(
21
+ {
22
+ activeLeftPanel,
23
+ leftSidebarContentRef,
24
+ leftSidebarWidth,
25
+ rightSidebarContentRef,
26
+ rightSidebarWidth,
27
+ setLeftSidebarWidth,
28
+ setRightSidebarWidth,
29
+ }: StartSidebarResizeArgs,
30
+ side: "left" | "right",
31
+ event: ReactPointerEvent<HTMLDivElement>,
32
+ ) {
33
+ event.preventDefault();
34
+ event.stopPropagation();
35
+ event.currentTarget.setPointerCapture?.(event.pointerId);
36
+ const startX = event.clientX;
37
+ const codePanelOpen = side === "left" && activeLeftPanel === "code";
38
+ const startWidth =
39
+ side === "left"
40
+ ? codePanelOpen
41
+ ? Math.max(leftSidebarWidth, 640)
42
+ : Math.min(leftSidebarWidth, 420)
43
+ : rightSidebarWidth;
44
+ const setWidth = side === "left" ? setLeftSidebarWidth : setRightSidebarWidth;
45
+ const minWidth = side === "left" ? (codePanelOpen ? 520 : 220) : 240;
46
+ const maxWidth = side === "left" ? (codePanelOpen ? 1100 : 420) : 390;
47
+ const target =
48
+ side === "left"
49
+ ? leftSidebarContentRef.current
50
+ : rightSidebarContentRef.current;
51
+ const previousTransition = target?.style.transition ?? "";
52
+ if (target) target.style.transition = "none";
53
+ let latestWidth = startWidth;
54
+ const previousCursor = document.body.style.cursor;
55
+ const previousUserSelect = document.body.style.userSelect;
56
+ const dragShield = document.createElement("div");
57
+ dragShield.setAttribute("data-design-sidebar-resize-shield", side);
58
+ dragShield.style.cssText =
59
+ "position:fixed;inset:0;z-index:2147483647;cursor:col-resize;background:transparent;pointer-events:auto;";
60
+ document.body.style.cursor = "col-resize";
61
+ document.body.style.userSelect = "none";
62
+ document.body.appendChild(dragShield);
63
+
64
+ const handleMove = (moveEvent: PointerEvent) => {
65
+ moveEvent.preventDefault();
66
+ const delta =
67
+ side === "left" ? moveEvent.clientX - startX : startX - moveEvent.clientX;
68
+ const next = Math.min(maxWidth, Math.max(minWidth, startWidth + delta));
69
+ latestWidth = next;
70
+ if (target) {
71
+ target.style.width = `${next}px`;
72
+ } else {
73
+ // No ref available (shouldn't normally happen) — fall back to the
74
+ // old per-move state update so resizing still works.
75
+ setWidth(next);
76
+ }
77
+ };
78
+ const cleanup = () => {
79
+ dragShield.removeEventListener("pointermove", handleMove);
80
+ dragShield.removeEventListener("pointerup", cleanup);
81
+ dragShield.removeEventListener("pointercancel", cleanup);
82
+ window.removeEventListener("pointermove", handleMove);
83
+ window.removeEventListener("pointerup", cleanup);
84
+ window.removeEventListener("pointercancel", cleanup);
85
+ dragShield.remove();
86
+ document.body.style.cursor = previousCursor;
87
+ document.body.style.userSelect = previousUserSelect;
88
+ if (target) target.style.transition = previousTransition;
89
+ // Commit the final width to state once, on release.
90
+ setWidth(latestWidth);
91
+ };
92
+
93
+ dragShield.addEventListener("pointermove", handleMove);
94
+ dragShield.addEventListener("pointerup", cleanup);
95
+ dragShield.addEventListener("pointercancel", cleanup);
96
+ window.addEventListener("pointermove", handleMove);
97
+ window.addEventListener("pointerup", cleanup);
98
+ window.addEventListener("pointercancel", cleanup);
99
+ }
@@ -0,0 +1,152 @@
1
+ import type { InteractionState } from "@shared/interaction-states";
2
+ import type { RefObject } from "react";
3
+
4
+ import type { StyleChangeMeta } from "@/components/design/EditPanel";
5
+ import type { ElementInfo } from "@/components/design/types";
6
+ import type { SelectedLayerTarget } from "@/pages/design-editor/code-layer-state";
7
+ import { shouldSkipVisualStyleCommitForPreview } from "@/pages/design-editor/editor-state";
8
+
9
+ export interface StyleChangeArgs {
10
+ commitInteractionStateStyles: (
11
+ state: InteractionState,
12
+ styles: Record<string, string>,
13
+ ) => boolean;
14
+ commitRelativeStyleDeltaToSelectedLayers: (
15
+ property: string,
16
+ delta: number,
17
+ ) => boolean;
18
+ commitStylesToSelectedLayers: (styles: Record<string, string>) => boolean;
19
+ commitVisualStyles: (
20
+ selector: string,
21
+ styles: Record<string, string>,
22
+ options?: {
23
+ runtimeApplied?: boolean;
24
+ elementInfo?: ElementInfo;
25
+ originalStyles?: Record<string, string>;
26
+ },
27
+ ) => void;
28
+ handleClearBreakpointOverride: (
29
+ property: string,
30
+ maxWidthPx: number,
31
+ ) => boolean;
32
+ previewInteractionStateStyles: (
33
+ state: InteractionState,
34
+ styles: Record<string, string>,
35
+ ) => void;
36
+ selectedCanvasSelectorCandidates: string[];
37
+ selectedElement: ElementInfo | null;
38
+ selectedLayerTargetsRef: RefObject<SelectedLayerTarget[]>;
39
+ textEditingState: { active: boolean; selector?: string; hasRange?: boolean };
40
+ }
41
+
42
+ export function runStyleChange(
43
+ {
44
+ commitInteractionStateStyles,
45
+ commitRelativeStyleDeltaToSelectedLayers,
46
+ commitStylesToSelectedLayers,
47
+ commitVisualStyles,
48
+ handleClearBreakpointOverride,
49
+ previewInteractionStateStyles,
50
+ selectedCanvasSelectorCandidates,
51
+ selectedElement,
52
+ selectedLayerTargetsRef,
53
+ textEditingState,
54
+ }: StyleChangeArgs,
55
+ property: string,
56
+ value: string,
57
+ meta?: StyleChangeMeta,
58
+ ) {
59
+ if (meta?.interactionState) {
60
+ if (meta.phase === "preview") {
61
+ previewInteractionStateStyles(meta.interactionState, {
62
+ [property]: value,
63
+ });
64
+ return;
65
+ }
66
+ if (
67
+ commitInteractionStateStyles(meta.interactionState, {
68
+ [property]: value,
69
+ })
70
+ ) {
71
+ return;
72
+ }
73
+ }
74
+ if (meta?.breakpointReset) {
75
+ handleClearBreakpointOverride(
76
+ meta.breakpointReset.property,
77
+ meta.breakpointReset.maxWidthPx,
78
+ );
79
+ return;
80
+ }
81
+ const selector = selectedElement?.selector ?? "body";
82
+ if (
83
+ textEditingState.active &&
84
+ textEditingState.hasRange &&
85
+ textEditingState.selector === selector
86
+ ) {
87
+ const sendStyleChange = (window as any).__designCanvasSendStyle;
88
+ if (typeof sendStyleChange === "function") {
89
+ sendStyleChange(selector, property, value, {
90
+ selectorCandidates: selectedCanvasSelectorCandidates,
91
+ nodeId: selectedElement?.sourceId,
92
+ });
93
+ return;
94
+ }
95
+ }
96
+ // PF12: a mid-gesture scrub/color-drag preview tick (ScrubInput's
97
+ // `phase: "preview"`, DesignColorPicker's per-tick `onChange`) is cheap
98
+ // to show live but must NOT run the expensive source commit
99
+ // (projection parse + HTML patch + history entry) on every tick — only
100
+ // the gesture's final "commit" (or a caller that never passes meta at
101
+ // all, e.g. keyboard/agent edits) does that. Route preview ticks
102
+ // through the same cheap iframe postMessage path the text-range case
103
+ // above already uses, and skip commitVisualStyles entirely so there is
104
+ // no source write — and therefore no history entry — for any preview
105
+ // tick. Multi-layer-selection commits (commitStylesToSelectedLayers)
106
+ // have no equivalent cheap multi-element preview channel, so previews
107
+ // for that case conservatively fall through to the existing full-commit
108
+ // behavior below (unchanged from before PF12).
109
+ if (
110
+ shouldSkipVisualStyleCommitForPreview({
111
+ phase: meta?.phase,
112
+ selectedLayerCount: selectedLayerTargetsRef.current.length,
113
+ })
114
+ ) {
115
+ const sendStyleChange = (window as any).__designCanvasSendStyle;
116
+ if (typeof sendStyleChange === "function") {
117
+ sendStyleChange(selector, property, value, {
118
+ selectorCandidates: selectedCanvasSelectorCandidates,
119
+ nodeId: selectedElement?.sourceId,
120
+ });
121
+ }
122
+ // No live bridge available for this preview tick (e.g. inactive
123
+ // screen) — nothing cheap to do; wait for the gesture's "commit".
124
+ return;
125
+ }
126
+ // Mixed-value arrow-step parity (item 7): ScrubInput's own
127
+ // ScrubInputChangeMeta now carries `relativeDelta` (set on a mixed-
128
+ // selection arrow nudge), and EditPanel forwards that meta object
129
+ // straight through to onStyleChange — but StyleChangeMeta (this
130
+ // parameter's declared type) doesn't declare the field yet, so it's
131
+ // read defensively through a local cast rather than a direct property
132
+ // access. This works today (the field is present on the actual object
133
+ // at runtime) and degrades safely to "absent" if that ever changes —
134
+ // either way behavior falls through to the existing absolute-value
135
+ // paths below unchanged. Only routes through the per-node relative
136
+ // path for an actual multi-selection; commitRelativeStyleDeltaToSelectedLayers
137
+ // itself also no-ops (returns false) for a single target.
138
+ const relativeDelta = (meta as { relativeDelta?: number } | undefined)
139
+ ?.relativeDelta;
140
+ if (typeof relativeDelta === "number") {
141
+ if (commitRelativeStyleDeltaToSelectedLayers(property, relativeDelta))
142
+ return;
143
+ }
144
+ // Page properties render only when there is no concrete DOM element
145
+ // selection. Screen/layer ids can remain in the broader selection ref
146
+ // while Escape exposes Page (especially after overview/breakpoint
147
+ // navigation); never let that stale structural selection hijack a page
148
+ // background/font edit away from the body.
149
+ if (selectedElement && commitStylesToSelectedLayers({ [property]: value }))
150
+ return;
151
+ commitVisualStyles(selector, { [property]: value });
152
+ }
@@ -0,0 +1,155 @@
1
+ import type { InteractionState } from "@shared/interaction-states";
2
+ import type { RefObject } from "react";
3
+
4
+ import type { StyleChangeMeta } from "@/components/design/EditPanel";
5
+ import type { ElementInfo } from "@/components/design/types";
6
+ import type { SelectedLayerTarget } from "@/pages/design-editor/code-layer-state";
7
+ import { shouldSkipVisualStyleCommitForPreview } from "@/pages/design-editor/editor-state";
8
+
9
+ export interface StylesChangeArgs {
10
+ commitInteractionStateStyles: (
11
+ state: InteractionState,
12
+ styles: Record<string, string>,
13
+ ) => boolean;
14
+ commitRelativeStyleDeltaToSelectedLayers: (
15
+ property: string,
16
+ delta: number,
17
+ ) => boolean;
18
+ commitStylesToSelectedLayers: (styles: Record<string, string>) => boolean;
19
+ commitVisualStyles: (
20
+ selector: string,
21
+ styles: Record<string, string>,
22
+ options?: {
23
+ runtimeApplied?: boolean;
24
+ elementInfo?: ElementInfo;
25
+ originalStyles?: Record<string, string>;
26
+ },
27
+ ) => void;
28
+ handleClearBreakpointOverride: (
29
+ property: string,
30
+ maxWidthPx: number,
31
+ ) => boolean;
32
+ previewInteractionStateStyles: (
33
+ state: InteractionState,
34
+ styles: Record<string, string>,
35
+ ) => void;
36
+ selectedCanvasSelectorCandidates: string[];
37
+ selectedElement: ElementInfo | null;
38
+ selectedLayerTargetsRef: RefObject<SelectedLayerTarget[]>;
39
+ textEditingState: { active: boolean; selector?: string; hasRange?: boolean };
40
+ }
41
+
42
+ export function runStylesChange(
43
+ {
44
+ commitInteractionStateStyles,
45
+ commitRelativeStyleDeltaToSelectedLayers,
46
+ commitStylesToSelectedLayers,
47
+ commitVisualStyles,
48
+ handleClearBreakpointOverride,
49
+ previewInteractionStateStyles,
50
+ selectedCanvasSelectorCandidates,
51
+ selectedElement,
52
+ selectedLayerTargetsRef,
53
+ textEditingState,
54
+ }: StylesChangeArgs,
55
+ styles: Record<string, string>,
56
+ meta?: StyleChangeMeta,
57
+ ) {
58
+ // Interaction-states phase 2 — see handleStyleChange's matching branch
59
+ // (and commitInteractionStateStyles's doc comment) for the full
60
+ // contract. Batched form: every property in this one commit lands in
61
+ // the SAME managed-block write (one applyFileContentUpdate call), so a
62
+ // multi-property commit made while a state is active (e.g. a shadow
63
+ // popover's X/Y/blur/spread) is still exactly one history step.
64
+ if (meta?.interactionState) {
65
+ if (meta.phase === "preview") {
66
+ previewInteractionStateStyles(meta.interactionState, styles);
67
+ return;
68
+ }
69
+ if (commitInteractionStateStyles(meta.interactionState, styles)) {
70
+ return;
71
+ }
72
+ }
73
+ // Item 14 — see handleStyleChange's matching branch for the full
74
+ // breakpointReset contract. EditPanel's BreakpointOverrideIndicator
75
+ // reset currently only fires through onStyleChange (a single
76
+ // property), but StylesChangeHandler shares the same StyleChangeMeta
77
+ // type, so this guards defensively for any batched caller too —
78
+ // breakpointReset only ever targets its own `property`, so only that
79
+ // one key of `styles` is relevant here.
80
+ if (meta?.breakpointReset) {
81
+ handleClearBreakpointOverride(
82
+ meta.breakpointReset.property,
83
+ meta.breakpointReset.maxWidthPx,
84
+ );
85
+ return;
86
+ }
87
+ const selector = selectedElement?.selector ?? "body";
88
+ const entries = Object.entries(styles).filter(([, value]) => Boolean(value));
89
+ if (entries.length === 0) return;
90
+ // T10: mirror handleStyleChange's text-range routing here. Without
91
+ // this, a multi-property style commit (e.g. EditPanel's typography
92
+ // controls, which batch fontSize/lineHeight/etc into one call) while a
93
+ // text RANGE is selected mid-edit would restyle the whole element
94
+ // instead of just the selected range — handleStyleChange (the
95
+ // single-property path) already special-cases this; handleStylesChange
96
+ // just never got the same treatment.
97
+ if (
98
+ textEditingState.active &&
99
+ textEditingState.hasRange &&
100
+ textEditingState.selector === selector
101
+ ) {
102
+ const sendStyleChange = (window as any).__designCanvasSendStyle;
103
+ if (typeof sendStyleChange === "function") {
104
+ entries.forEach(([property, value]) => {
105
+ sendStyleChange(selector, property, value, {
106
+ selectorCandidates: selectedCanvasSelectorCandidates,
107
+ nodeId: selectedElement?.sourceId,
108
+ });
109
+ });
110
+ return;
111
+ }
112
+ }
113
+ // PF12: same preview/commit split as handleStyleChange — see its
114
+ // comment for the full undo-safety rationale. A batched multi-property
115
+ // preview tick (e.g. EditPanel's shadow X/Y/blur/spread popover) is
116
+ // still just a live preview: send every property to the cheap iframe
117
+ // bridge and skip the expensive multi-property commitVisualStyles call.
118
+ if (
119
+ shouldSkipVisualStyleCommitForPreview({
120
+ phase: meta?.phase,
121
+ selectedLayerCount: selectedLayerTargetsRef.current.length,
122
+ })
123
+ ) {
124
+ const sendStyleChange = (window as any).__designCanvasSendStyle;
125
+ if (typeof sendStyleChange === "function") {
126
+ entries.forEach(([property, value]) => {
127
+ sendStyleChange(selector, property, value, {
128
+ selectorCandidates: selectedCanvasSelectorCandidates,
129
+ nodeId: selectedElement?.sourceId,
130
+ });
131
+ });
132
+ }
133
+ return;
134
+ }
135
+ // Mixed-value arrow-step parity (item 7): see handleStyleChange's
136
+ // matching comment for the full defensive-read rationale. A relative
137
+ // delta is inherently single-valued (one scrub gesture on one field),
138
+ // so this only applies when the batched patch has exactly one entry —
139
+ // a multi-property patch (e.g. a shadow popover's X+Y+blur+spread all
140
+ // at once) has no single delta to apply per-node and falls through to
141
+ // the existing absolute-value paths unchanged.
142
+ const relativeDelta = (meta as { relativeDelta?: number } | undefined)
143
+ ?.relativeDelta;
144
+ if (typeof relativeDelta === "number" && entries.length === 1) {
145
+ const [singleProperty] = entries[0]!;
146
+ if (commitRelativeStyleDeltaToSelectedLayers(singleProperty, relativeDelta))
147
+ return;
148
+ }
149
+ if (
150
+ selectedElement &&
151
+ commitStylesToSelectedLayers(Object.fromEntries(entries))
152
+ )
153
+ return;
154
+ commitVisualStyles(selector, Object.fromEntries(entries));
155
+ }
@@ -0,0 +1,125 @@
1
+ import type { CodeLayerNode } from "@shared/code-layer";
2
+ import { buildCodeLayerProjection } from "@shared/code-layer";
3
+ import { sourceContentHash } from "@shared/source-workspace";
4
+ import type { Dispatch, RefObject, SetStateAction } from "react";
5
+ import { toast } from "sonner";
6
+
7
+ import type { AutoLayoutSuggestion } from "@/pages/design-editor/auto-layout-suggestion";
8
+ import {
9
+ hasMeaningfulCssTransform,
10
+ inferAutoLayoutSuggestion,
11
+ } from "@/pages/design-editor/auto-layout-suggestion";
12
+ import type { RuntimeLayerSnapshot } from "@/pages/design-editor/command-types";
13
+ import type { AlignableRect } from "@/pages/design-editor/layout-operations";
14
+ import type { DesignFile } from "@/pages/design-editor/types";
15
+
16
+ export interface SuggestAutoLayoutArgs {
17
+ activeCanvasSourceType: "inline" | "localhost" | "fusion";
18
+ activeFile: DesignFile;
19
+ canEditDesign: boolean;
20
+ designSourceType: "inline" | "localhost" | "fusion";
21
+ getActiveFileSelectedNodeIds: (content: string) => string[];
22
+ getFreshActiveContent: () => string;
23
+ liveComputedLayoutForNode: (nodeId: string) => {
24
+ display: string;
25
+ transform: string;
26
+ rotate: string;
27
+ scale: string;
28
+ } | null;
29
+ rectFromCodeLayerNode: (node: CodeLayerNode) => AlignableRect;
30
+ runtimeLayerSnapshotsById: Record<string, RuntimeLayerSnapshot>;
31
+ setAutoLayoutSuggestionPreview: Dispatch<
32
+ SetStateAction<{
33
+ suggestion: AutoLayoutSuggestion;
34
+ sourceType: "inline" | "localhost";
35
+ contentHash: string;
36
+ screenId: string;
37
+ } | null>
38
+ >;
39
+ t: (key: string, options?: Record<string, unknown>) => string;
40
+ viewModeRef: RefObject<"single" | "overview">;
41
+ }
42
+
43
+ export function runSuggestAutoLayout({
44
+ activeCanvasSourceType,
45
+ activeFile,
46
+ canEditDesign,
47
+ designSourceType,
48
+ getActiveFileSelectedNodeIds,
49
+ getFreshActiveContent,
50
+ liveComputedLayoutForNode,
51
+ rectFromCodeLayerNode,
52
+ runtimeLayerSnapshotsById,
53
+ setAutoLayoutSuggestionPreview,
54
+ t,
55
+ viewModeRef,
56
+ }: SuggestAutoLayoutArgs) {
57
+ if (!canEditDesign || !activeFile || viewModeRef.current !== "single") {
58
+ return;
59
+ }
60
+ const resolvedSourceType = activeCanvasSourceType ?? designSourceType;
61
+ if (resolvedSourceType !== "inline" && resolvedSourceType !== "localhost") {
62
+ return;
63
+ }
64
+ const sourceType =
65
+ resolvedSourceType === "localhost" ? "localhost" : "inline";
66
+ const sourceContent =
67
+ sourceType === "localhost"
68
+ ? runtimeLayerSnapshotsById[activeFile.id]?.html
69
+ : getFreshActiveContent();
70
+ if (!sourceContent) {
71
+ toast.error(t("designEditor.toasts.reactSourceAnchorsLoading"));
72
+ return;
73
+ }
74
+ const projection = buildCodeLayerProjection(sourceContent);
75
+ const nodesById = new Map(projection.nodes.map((node) => [node.id, node]));
76
+ const selectedIds = getActiveFileSelectedNodeIds(sourceContent);
77
+ if (selectedIds.length !== 1) {
78
+ toast(t("designEditor.autoLayoutSuggestion.selectContainer"));
79
+ return;
80
+ }
81
+ const container = nodesById.get(selectedIds[0]!);
82
+ if (!container || container.children.length === 0) {
83
+ toast(t("designEditor.autoLayoutSuggestion.selectContainer"));
84
+ return;
85
+ }
86
+ const containerMeasured = rectFromCodeLayerNode(container);
87
+ const children = container.children
88
+ .map((childId) => nodesById.get(childId))
89
+ .filter((child): child is CodeLayerNode => Boolean(child))
90
+ .map((child) => {
91
+ const computed = liveComputedLayoutForNode(child.id);
92
+ return {
93
+ ...rectFromCodeLayerNode(child),
94
+ transformed: hasMeaningfulCssTransform({
95
+ transform: computed?.transform ?? child.style.transform,
96
+ rotate: computed?.rotate ?? child.style.rotate,
97
+ scale: computed?.scale ?? child.style.scale,
98
+ classes: child.classes,
99
+ }),
100
+ };
101
+ });
102
+ const suggestion = inferAutoLayoutSuggestion({
103
+ // Child measurements are parent-relative, so the container's own local
104
+ // content box begins at 0,0 even when the container is positioned in its
105
+ // parent. This prevents outer-canvas placement from becoming padding.
106
+ container: {
107
+ id: container.id,
108
+ x: 0,
109
+ y: 0,
110
+ width: containerMeasured.width,
111
+ height: containerMeasured.height,
112
+ },
113
+ children,
114
+ });
115
+ if (!suggestion) {
116
+ toast(t("designEditor.autoLayoutSuggestion.selectContainer"));
117
+ return;
118
+ }
119
+ setAutoLayoutSuggestionPreview({
120
+ suggestion,
121
+ sourceType,
122
+ contentHash: sourceContentHash(sourceContent),
123
+ screenId: activeFile.id,
124
+ });
125
+ }