@agent-native/core 0.163.4 → 0.164.3

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 (228) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/analytics/app/components/agents/FeatureFlagsFleetPanel.tsx +78 -19
  3. package/corpus/templates/analytics/app/hooks/use-dashboard-chat-context.ts +29 -0
  4. package/corpus/templates/analytics/app/lib/use-auto-focus-select.ts +4 -4
  5. package/corpus/templates/analytics/app/pages/adhoc/explorer-dashboard/index.tsx +3 -0
  6. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +6 -0
  7. package/corpus/templates/analytics/app/pages/sessions/SessionsPage.tsx +1 -2
  8. package/corpus/templates/analytics/app/routes/_index.tsx +3 -2
  9. package/corpus/templates/analytics/app/routes/adhoc.$id.tsx +7 -2
  10. package/corpus/templates/analytics/app/routes/dashboard.tsx +3 -2
  11. package/corpus/templates/analytics/app/routes/overview.tsx +3 -2
  12. package/corpus/templates/analytics/app/routes/traffic.tsx +3 -2
  13. package/corpus/templates/analytics/changelog/2026-08-18-verify-cross-app-feature-flag-changes.md +6 -0
  14. package/corpus/templates/analytics/netlify.toml +2 -0
  15. package/corpus/templates/analytics/server/lib/workspace-feature-flags.ts +196 -48
  16. package/corpus/templates/analytics/server/plugins/feature-flags.ts +5 -0
  17. package/corpus/templates/analytics/shared/feature-flags.ts +10 -0
  18. package/corpus/templates/assets/app/routes/brand-kits.$id.tsx +5 -1
  19. package/corpus/templates/assets/app/routes/brand-kits._index.tsx +4 -1
  20. package/corpus/templates/assets/app/routes/libraries.tsx +4 -1
  21. package/corpus/templates/assets/app/routes/picker.tsx +4 -1
  22. package/corpus/templates/assets/netlify.toml +2 -0
  23. package/corpus/templates/brain/netlify.toml +2 -0
  24. package/corpus/templates/calendar/app/components/calendar/DayView.tsx +2 -1
  25. package/corpus/templates/calendar/changelog/2026-08-19-calendar-page-loads-stay-fast-after-idle-periods.md +6 -0
  26. package/corpus/templates/calendar/netlify.toml +2 -0
  27. package/corpus/templates/chat/netlify.toml +2 -0
  28. package/corpus/templates/clips/app/components/player/player-controls.tsx +3 -3
  29. package/corpus/templates/clips/app/components/player/reaction-emojis.ts +12 -0
  30. package/corpus/templates/clips/app/components/player/reactions-tray.tsx +90 -9
  31. package/corpus/templates/clips/app/components/player/scrubber.tsx +43 -10
  32. package/corpus/templates/clips/app/components/player/video-player.tsx +105 -15
  33. package/corpus/templates/clips/app/routes/_app.settings._index.tsx +1 -2
  34. package/corpus/templates/clips/app/routes/_app.wispr.tsx +2 -1
  35. package/corpus/templates/clips/app/routes/_index.tsx +7 -3
  36. package/corpus/templates/clips/app/routes/r.$recordingId.tsx +13 -8
  37. package/corpus/templates/clips/app/routes/share.$shareId.tsx +15 -8
  38. package/corpus/templates/clips/netlify.toml +2 -0
  39. package/corpus/templates/clips/server/routes/[...page].head.ts +5 -0
  40. package/corpus/templates/content/netlify.toml +2 -0
  41. package/corpus/templates/design/.agents/skills/design-editor-architecture/SKILL.md +103 -0
  42. package/corpus/templates/design/.agents/skills/design-generation/SKILL.md +2 -1
  43. package/corpus/templates/design/AGENTS.md +3 -1
  44. package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +3 -2
  45. package/corpus/templates/design/app/components/design/editor/AgentNativeMenuMark.tsx +25 -0
  46. package/corpus/templates/design/app/components/design/editor/DesignBottomToolbar.tsx +411 -0
  47. package/corpus/templates/design/app/components/design/editor/DesignCollaborators.tsx +153 -0
  48. package/corpus/templates/design/app/components/design/editor/DesignWorkspaceRail.tsx +231 -0
  49. package/corpus/templates/design/app/components/design/editor/MakeRealDialog.tsx +223 -0
  50. package/corpus/templates/design/app/components/design/editor/PendingScreenDeletionDialog.tsx +58 -0
  51. package/corpus/templates/design/app/components/design/editor/PendingVisualStyleWarningDialog.tsx +56 -0
  52. package/corpus/templates/design/app/components/design/editor/ReadOnlyEditorPanel.tsx +23 -0
  53. package/corpus/templates/design/app/components/design/editor/SaveTemplateDialog.tsx +161 -0
  54. package/corpus/templates/design/app/components/design/editor/toolbar-controls.tsx +185 -0
  55. package/corpus/templates/design/app/pages/DesignEditor.tsx +20913 -0
  56. package/corpus/templates/design/app/pages/design-editor/command-types.ts +120 -0
  57. package/corpus/templates/design/app/pages/design-editor/commands/README.md +193 -0
  58. package/corpus/templates/design/app/pages/design-editor/commands/add-auto-layout.ts +416 -0
  59. package/corpus/templates/design/app/pages/design-editor/commands/add-screen.ts +112 -0
  60. package/corpus/templates/design/app/pages/design-editor/commands/align-selection.ts +183 -0
  61. package/corpus/templates/design/app/pages/design-editor/commands/apply-design-editor-command.ts +243 -0
  62. package/corpus/templates/design/app/pages/design-editor/commands/apply-file-content-update.ts +236 -0
  63. package/corpus/templates/design/app/pages/design-editor/commands/apply-local-content-update.ts +304 -0
  64. package/corpus/templates/design/app/pages/design-editor/commands/apply-pending-visual-styles-with-agent.ts +376 -0
  65. package/corpus/templates/design/app/pages/design-editor/commands/apply-to-source.ts +132 -0
  66. package/corpus/templates/design/app/pages/design-editor/commands/can-move-layer.ts +117 -0
  67. package/corpus/templates/design/app/pages/design-editor/commands/change-selected-z-index.ts +212 -0
  68. package/corpus/templates/design/app/pages/design-editor/commands/commit-relative-style-delta-to-selected-layers.ts +176 -0
  69. package/corpus/templates/design/app/pages/design-editor/commands/commit-styles-to-selected-layers.ts +175 -0
  70. package/corpus/templates/design/app/pages/design-editor/commands/commit-visual-styles.ts +710 -0
  71. package/corpus/templates/design/app/pages/design-editor/commands/confirm-make-real.ts +99 -0
  72. package/corpus/templates/design/app/pages/design-editor/commands/copy-as-figma-svg.ts +95 -0
  73. package/corpus/templates/design/app/pages/design-editor/commands/copy-selection.ts +156 -0
  74. package/corpus/templates/design/app/pages/design-editor/commands/create-primitive.ts +291 -0
  75. package/corpus/templates/design/app/pages/design-editor/commands/create-screen-frame.ts +131 -0
  76. package/corpus/templates/design/app/pages/design-editor/commands/cross-screen-element-drop.ts +577 -0
  77. package/corpus/templates/design/app/pages/design-editor/commands/delete-files.ts +364 -0
  78. package/corpus/templates/design/app/pages/design-editor/commands/delete-selection.ts +521 -0
  79. package/corpus/templates/design/app/pages/design-editor/commands/detach-instance-menu-action.ts +88 -0
  80. package/corpus/templates/design/app/pages/design-editor/commands/distribute-selection.ts +114 -0
  81. package/corpus/templates/design/app/pages/design-editor/commands/download-all-screens-pdf.ts +116 -0
  82. package/corpus/templates/design/app/pages/design-editor/commands/download-pdf.ts +103 -0
  83. package/corpus/templates/design/app/pages/design-editor/commands/download-svg.ts +159 -0
  84. package/corpus/templates/design/app/pages/design-editor/commands/duplicate-screen.ts +201 -0
  85. package/corpus/templates/design/app/pages/design-editor/commands/duplicate-selection.ts +279 -0
  86. package/corpus/templates/design/app/pages/design-editor/commands/editor-paste.ts +81 -0
  87. package/corpus/templates/design/app/pages/design-editor/commands/enter-hotkey.ts +155 -0
  88. package/corpus/templates/design/app/pages/design-editor/commands/enter-single-screen.ts +168 -0
  89. package/corpus/templates/design/app/pages/design-editor/commands/escape-hotkey.ts +212 -0
  90. package/corpus/templates/design/app/pages/design-editor/commands/frame-selection.ts +119 -0
  91. package/corpus/templates/design/app/pages/design-editor/commands/geometry-commit.ts +169 -0
  92. package/corpus/templates/design/app/pages/design-editor/commands/get-selected-layer-snapshots.ts +177 -0
  93. package/corpus/templates/design/app/pages/design-editor/commands/group-selection.ts +127 -0
  94. package/corpus/templates/design/app/pages/design-editor/commands/iframe-context-menu.ts +121 -0
  95. package/corpus/templates/design/app/pages/design-editor/commands/import-figma-clipboard-into-design.ts +119 -0
  96. package/corpus/templates/design/app/pages/design-editor/commands/layer-marquee-selection-change.ts +163 -0
  97. package/corpus/templates/design/app/pages/design-editor/commands/layer-move-to-screen.ts +331 -0
  98. package/corpus/templates/design/app/pages/design-editor/commands/layer-move.ts +517 -0
  99. package/corpus/templates/design/app/pages/design-editor/commands/layer-rename.ts +247 -0
  100. package/corpus/templates/design/app/pages/design-editor/commands/layer-selection-change.ts +161 -0
  101. package/corpus/templates/design/app/pages/design-editor/commands/mode-change.ts +122 -0
  102. package/corpus/templates/design/app/pages/design-editor/commands/nudge-selection.ts +213 -0
  103. package/corpus/templates/design/app/pages/design-editor/commands/overview-primitive-reparent.ts +318 -0
  104. package/corpus/templates/design/app/pages/design-editor/commands/paste-copied-screens.ts +114 -0
  105. package/corpus/templates/design/app/pages/design-editor/commands/paste-over-selection.ts +70 -0
  106. package/corpus/templates/design/app/pages/design-editor/commands/paste-selection.ts +436 -0
  107. package/corpus/templates/design/app/pages/design-editor/commands/paste-to-replace.ts +135 -0
  108. package/corpus/templates/design/app/pages/design-editor/commands/pasted-image-files.ts +209 -0
  109. package/corpus/templates/design/app/pages/design-editor/commands/persist-frame-geometry-save.ts +113 -0
  110. package/corpus/templates/design/app/pages/design-editor/commands/persist-tweak-save.ts +160 -0
  111. package/corpus/templates/design/app/pages/design-editor/commands/primitive-created.ts +168 -0
  112. package/corpus/templates/design/app/pages/design-editor/commands/record-pending-live-layer-state-edit.ts +149 -0
  113. package/corpus/templates/design/app/pages/design-editor/commands/record-pending-live-structure-edit.ts +200 -0
  114. package/corpus/templates/design/app/pages/design-editor/commands/record-pending-live-text-edit.ts +152 -0
  115. package/corpus/templates/design/app/pages/design-editor/commands/record-pending-visual-style-edit.ts +247 -0
  116. package/corpus/templates/design/app/pages/design-editor/commands/redo.ts +1086 -0
  117. package/corpus/templates/design/app/pages/design-editor/commands/render-png-blob.ts +200 -0
  118. package/corpus/templates/design/app/pages/design-editor/commands/save-file-content.ts +207 -0
  119. package/corpus/templates/design/app/pages/design-editor/commands/screen-element-select.ts +262 -0
  120. package/corpus/templates/design/app/pages/design-editor/commands/screen-text-content-change.ts +216 -0
  121. package/corpus/templates/design/app/pages/design-editor/commands/screen-visual-duplicate-change.ts +108 -0
  122. package/corpus/templates/design/app/pages/design-editor/commands/screen-visual-structure-change.ts +280 -0
  123. package/corpus/templates/design/app/pages/design-editor/commands/screen-visual-style-change.ts +151 -0
  124. package/corpus/templates/design/app/pages/design-editor/commands/send-overview-annotations.ts +88 -0
  125. package/corpus/templates/design/app/pages/design-editor/commands/send-runtime-layer-move-semantic-handoff.ts +122 -0
  126. package/corpus/templates/design/app/pages/design-editor/commands/send-runtime-layer-semantic-handoff.ts +143 -0
  127. package/corpus/templates/design/app/pages/design-editor/commands/send-runtime-layer-state-semantic-handoff.ts +115 -0
  128. package/corpus/templates/design/app/pages/design-editor/commands/start-retry-generation.ts +152 -0
  129. package/corpus/templates/design/app/pages/design-editor/commands/start-sidebar-resize.ts +99 -0
  130. package/corpus/templates/design/app/pages/design-editor/commands/style-change.ts +152 -0
  131. package/corpus/templates/design/app/pages/design-editor/commands/styles-change.ts +155 -0
  132. package/corpus/templates/design/app/pages/design-editor/commands/suggest-auto-layout.ts +125 -0
  133. package/corpus/templates/design/app/pages/design-editor/commands/text-content-change.ts +201 -0
  134. package/corpus/templates/design/app/pages/design-editor/commands/tidy-up.ts +123 -0
  135. package/corpus/templates/design/app/pages/design-editor/commands/toggle-layer-hidden.ts +167 -0
  136. package/corpus/templates/design/app/pages/design-editor/commands/toggle-layer-locked.ts +175 -0
  137. package/corpus/templates/design/app/pages/design-editor/commands/toggle-motion-keyframe.ts +99 -0
  138. package/corpus/templates/design/app/pages/design-editor/commands/tweak-prompt-submit.ts +84 -0
  139. package/corpus/templates/design/app/pages/design-editor/commands/undo.ts +906 -0
  140. package/corpus/templates/design/app/pages/design-editor/commands/ungroup-selection.ts +170 -0
  141. package/corpus/templates/design/app/pages/design-editor/commands/visual-duplicate-change.ts +119 -0
  142. package/corpus/templates/design/app/pages/design-editor/commands/visual-structure-change.ts +274 -0
  143. package/corpus/templates/design/app/pages/design-editor/commands/write-frame-geometry-snapshot.ts +99 -0
  144. package/corpus/templates/design/app/pages/design-editor/derive/design-breakpoints.ts +56 -0
  145. package/corpus/templates/design/app/pages/design-editor/derive/overview-screens.ts +146 -0
  146. package/corpus/templates/design/app/pages/design-editor/domains/use-tweaks.ts +326 -0
  147. package/corpus/templates/design/app/pages/design-editor/editor-constants.ts +54 -0
  148. package/corpus/templates/design/app/pages/design-editor/editor-helpers.ts +183 -0
  149. package/corpus/templates/design/app/pages/design-editor/effects/adopt-db-file-content.ts +270 -0
  150. package/corpus/templates/design/app/pages/design-editor/effects/mirror-selection-to-agent-chat.ts +134 -0
  151. package/corpus/templates/design/app/pages/design-editor/effects/motion-autosave.ts +335 -0
  152. package/corpus/templates/design/app/pages/design-editor/effects/observe-collab-text.ts +183 -0
  153. package/corpus/templates/design/app/pages/design-editor/effects/publish-agent-selection-context.ts +252 -0
  154. package/corpus/templates/design/app/pages/design-editor/effects/resume-pending-generation.ts +178 -0
  155. package/corpus/templates/design/app/pages/design-editor/effects/seed-collab-content.ts +157 -0
  156. package/corpus/templates/design/app/pages/design-editor/preview-html.ts +19 -0
  157. package/corpus/templates/design/app/routes/examples.tsx +2 -1
  158. package/corpus/templates/design/netlify.toml +2 -0
  159. package/corpus/templates/design/shared/board-file.ts +2 -1
  160. package/corpus/templates/dispatch/netlify.toml +2 -0
  161. package/corpus/templates/forms/app/routes/_index.tsx +3 -2
  162. package/corpus/templates/forms/netlify.toml +2 -0
  163. package/corpus/templates/mail/app/routes/_index.tsx +3 -2
  164. package/corpus/templates/mail/netlify.toml +2 -0
  165. package/corpus/templates/plan/netlify.toml +2 -0
  166. package/corpus/templates/slides/netlify.toml +2 -0
  167. package/corpus/templates/tasks/app/routes/_index.tsx +2 -1
  168. package/dist/agent/engine/builder-engine.d.ts +6 -4
  169. package/dist/agent/engine/builder-engine.js +91 -26
  170. package/dist/agent/engine/registry.js +16 -0
  171. package/dist/cli/code-agent-agent-tools.d.ts +2 -0
  172. package/dist/cli/code-agent-agent-tools.js +188 -0
  173. package/dist/cli/code-agent-collaboration.d.ts +34 -0
  174. package/dist/cli/code-agent-collaboration.js +104 -0
  175. package/dist/cli/code-agent-executor.js +6 -0
  176. package/dist/cli/code-agent-schedules.d.ts +54 -0
  177. package/dist/cli/code-agent-schedules.js +198 -0
  178. package/dist/client/AgentPanel.js +1 -1
  179. package/dist/client/ConnectBuilderCard.js +5 -2
  180. package/dist/client/EnvironmentBadge.js +6 -5
  181. package/dist/client/chat/run-recovery.js +3 -2
  182. package/dist/client/chat/tool-call-display.js +2 -3
  183. package/dist/client/components/CodeRequiredDialog.js +4 -2
  184. package/dist/client/i18n.js +5 -4
  185. package/dist/client/settings/SettingsPanel.js +1 -1
  186. package/dist/client/settings/VoiceTranscriptionSection.js +1 -1
  187. package/dist/client/settings/useBuilderStatus.d.ts +6 -1
  188. package/dist/client/settings/useBuilderStatus.js +22 -33
  189. package/dist/client/transcription/BuilderTranscriptionCta.js +1 -1
  190. package/dist/code-agents/index.d.ts +2 -0
  191. package/dist/code-agents/index.js +2 -0
  192. package/dist/collab/awareness.d.ts +2 -2
  193. package/dist/collab/routes.d.ts +1 -1
  194. package/dist/deploy/build.js +10 -4
  195. package/dist/localization/shared.d.ts +2 -0
  196. package/dist/localization/shared.js +5 -0
  197. package/dist/mcp-client/oauth-client.d.ts +1 -0
  198. package/dist/mcp-client/oauth-client.js +50 -1
  199. package/dist/notifications/routes.d.ts +3 -3
  200. package/dist/observability/routes.d.ts +1 -1
  201. package/dist/provider-api/actions/provider-api.d.ts +2 -2
  202. package/dist/server/auth-marketing-locales.d.ts +7 -0
  203. package/dist/server/auth-marketing-locales.js +1322 -0
  204. package/dist/server/auth-marketing.d.ts +1 -0
  205. package/dist/server/auth-marketing.js +35 -6
  206. package/dist/server/auth.js +4 -17
  207. package/dist/server/builder-browser.d.ts +22 -0
  208. package/dist/server/builder-browser.js +77 -1
  209. package/dist/server/builder-oauth.d.ts +50 -0
  210. package/dist/server/builder-oauth.js +263 -0
  211. package/dist/server/core-routes-plugin.d.ts +11 -0
  212. package/dist/server/core-routes-plugin.js +291 -415
  213. package/dist/server/credential-provider.d.ts +1 -1
  214. package/dist/server/credential-provider.js +1 -1
  215. package/dist/server/csrf.js +2 -2
  216. package/dist/server/onboarding-html.js +52 -120
  217. package/dist/server/ssr-handler.js +13 -4
  218. package/dist/settings/store.d.ts +9 -0
  219. package/dist/settings/store.js +19 -0
  220. package/dist/shared/cache-control.d.ts +22 -0
  221. package/dist/shared/cache-control.js +25 -0
  222. package/dist/shared/index.d.ts +1 -0
  223. package/dist/shared/index.js +1 -0
  224. package/dist/templates/chat/netlify.toml +2 -0
  225. package/docs/content/deployment.mdx +2 -0
  226. package/docs/content/multi-app-workspace.mdx +1 -1
  227. package/package.json +2 -2
  228. package/src/templates/chat/netlify.toml +2 -0
@@ -0,0 +1,200 @@
1
+ import type { CanvasFrameGeometryById } from "@shared/canvas-frames";
2
+ import { normalizeDesignSourceType } from "@shared/source-mode";
3
+
4
+ import type { ExportSettingsValue } from "@/components/design/inspector";
5
+ import type { ElementInfo } from "@/components/design/types";
6
+ import type { OverviewScreen } from "@/pages/design-editor/derive/overview-screens";
7
+ import {
8
+ getExportCompositeBounds,
9
+ resolveRasterExportScale,
10
+ } from "@/pages/design-editor/export-capture";
11
+ import type { PngCaptureScope } from "@/pages/design-editor/png-export-render";
12
+ import {
13
+ PngCaptureError,
14
+ cropCanvasToRect,
15
+ renderExportDocumentCanvas,
16
+ resolveExportCropRect,
17
+ } from "@/pages/design-editor/png-export-render";
18
+
19
+ export interface RenderPngBlobArgs {
20
+ activeCanvasSourceType: "inline" | "localhost" | "fusion";
21
+ canEditDesign: boolean;
22
+ canvasFrameGeometryById: CanvasFrameGeometryById;
23
+ overviewScreens: OverviewScreen[];
24
+ resolvePngCaptureTarget: (scope: PngCaptureScope) => {
25
+ cropSelection: ElementInfo | readonly ElementInfo[] | null;
26
+ doc: Document;
27
+ iframe: HTMLIFrameElement;
28
+ };
29
+ selectedScreenIds: string[];
30
+ viewMode: "single" | "overview";
31
+ }
32
+
33
+ export async function runRenderPngBlob(
34
+ {
35
+ activeCanvasSourceType,
36
+ canEditDesign,
37
+ canvasFrameGeometryById,
38
+ overviewScreens,
39
+ resolvePngCaptureTarget,
40
+ selectedScreenIds,
41
+ viewMode,
42
+ }: RenderPngBlobArgs,
43
+ {
44
+ scope,
45
+ settings,
46
+ format = "png",
47
+ }: {
48
+ scope: PngCaptureScope;
49
+ settings?: Partial<ExportSettingsValue>;
50
+ format?: "png" | "jpg" | "webp";
51
+ },
52
+ ): Promise<Blob> {
53
+ const html2canvas = (await import("html2canvas")).default;
54
+ const requestedExportScale =
55
+ settings?.scale ?? Math.max(2, window.devicePixelRatio || 1);
56
+ let outputCanvas: HTMLCanvasElement;
57
+
58
+ if (
59
+ scope === "screens" &&
60
+ viewMode === "overview" &&
61
+ selectedScreenIds.length > 0
62
+ ) {
63
+ const captures = selectedScreenIds
64
+ .map((screenId, order) => {
65
+ const iframe = document.querySelector<HTMLIFrameElement>(
66
+ `iframe[data-design-preview-iframe][data-screen-iframe-id="${CSS.escape(screenId)}"]`,
67
+ );
68
+ if (!iframe) throw new PngCaptureError("no-preview");
69
+ let doc: Document | null = null;
70
+ try {
71
+ doc = iframe.contentDocument;
72
+ if (!doc?.documentElement) doc = null;
73
+ } catch {
74
+ doc = null;
75
+ }
76
+ if (!doc) {
77
+ const sourceType =
78
+ normalizeDesignSourceType(iframe.dataset.designSourceType) ??
79
+ activeCanvasSourceType;
80
+ if (sourceType !== "inline") {
81
+ throw new PngCaptureError("external-preview");
82
+ }
83
+ if (!canEditDesign) {
84
+ throw new PngCaptureError("read-only-preview");
85
+ }
86
+ throw new PngCaptureError("no-preview");
87
+ }
88
+ const screen = overviewScreens.find(
89
+ (candidate) => candidate.id === screenId,
90
+ );
91
+ const geometry = canvasFrameGeometryById[screenId] ?? {};
92
+ return {
93
+ doc,
94
+ frame: {
95
+ x: geometry.x ?? order * ((screen?.width ?? 1440) + 80),
96
+ y: geometry.y ?? 0,
97
+ width: Math.max(
98
+ 1,
99
+ geometry.width ?? screen?.width ?? iframe.clientWidth,
100
+ ),
101
+ height: Math.max(
102
+ 1,
103
+ geometry.height ?? screen?.height ?? iframe.clientHeight,
104
+ ),
105
+ rotation: geometry.rotation ?? 0,
106
+ },
107
+ iframe,
108
+ order,
109
+ z: geometry.z ?? order,
110
+ };
111
+ })
112
+ .sort((left, right) => left.z - right.z || left.order - right.order);
113
+ const bounds = getExportCompositeBounds(
114
+ captures.map((capture) => capture.frame),
115
+ );
116
+ if (!bounds) throw new PngCaptureError("no-preview");
117
+ const exportScale = resolveRasterExportScale({
118
+ width: bounds.width,
119
+ height: bounds.height,
120
+ requestedScale: requestedExportScale,
121
+ });
122
+ outputCanvas = document.createElement("canvas");
123
+ outputCanvas.width = Math.max(1, Math.ceil(bounds.width * exportScale));
124
+ outputCanvas.height = Math.max(1, Math.ceil(bounds.height * exportScale));
125
+ const context = outputCanvas.getContext("2d");
126
+ if (!context) throw new PngCaptureError("blob-failed");
127
+
128
+ for (const capture of captures) {
129
+ const rendered = await renderExportDocumentCanvas({
130
+ doc: capture.doc,
131
+ iframe: capture.iframe,
132
+ exportScale,
133
+ render: html2canvas,
134
+ });
135
+ const view = capture.doc.defaultView;
136
+ const viewportCanvas = cropCanvasToRect(
137
+ rendered.canvas,
138
+ {
139
+ x: view?.scrollX ?? 0,
140
+ y: view?.scrollY ?? 0,
141
+ width: Math.max(1, capture.iframe.clientWidth),
142
+ height: Math.max(1, capture.iframe.clientHeight),
143
+ },
144
+ rendered.scale,
145
+ );
146
+ const frame = capture.frame;
147
+ context.save();
148
+ context.translate(
149
+ (frame.x + frame.width / 2 - bounds.x) * exportScale,
150
+ (frame.y + frame.height / 2 - bounds.y) * exportScale,
151
+ );
152
+ context.rotate(((frame.rotation ?? 0) * Math.PI) / 180);
153
+ context.drawImage(
154
+ viewportCanvas ?? rendered.canvas,
155
+ (-frame.width / 2) * exportScale,
156
+ (-frame.height / 2) * exportScale,
157
+ frame.width * exportScale,
158
+ frame.height * exportScale,
159
+ );
160
+ context.restore();
161
+ }
162
+ } else {
163
+ const { cropSelection, doc, iframe } = resolvePngCaptureTarget(scope);
164
+ const rendered = await renderExportDocumentCanvas({
165
+ doc,
166
+ iframe,
167
+ exportScale: requestedExportScale,
168
+ render: html2canvas,
169
+ });
170
+ const cropRect = resolveExportCropRect(doc, cropSelection);
171
+ const cropped = cropRect
172
+ ? cropCanvasToRect(rendered.canvas, cropRect, rendered.scale)
173
+ : null;
174
+ // An element capture that silently widens to the whole document is a
175
+ // preview of something the user did not ask to export, and nothing
176
+ // downstream can tell it apart from a real one.
177
+ if (scope === "element" && !cropped) {
178
+ throw new PngCaptureError("no-preview");
179
+ }
180
+ // Render the whole page first, then crop, so ancestor backgrounds show
181
+ // through every selected frame exactly as they do on screen.
182
+ outputCanvas = cropped ?? rendered.canvas;
183
+ }
184
+ const mimeType =
185
+ format === "jpg"
186
+ ? "image/jpeg"
187
+ : format === "webp"
188
+ ? "image/webp"
189
+ : "image/png";
190
+ return await new Promise<Blob>((resolve, reject) => {
191
+ outputCanvas.toBlob(
192
+ (blob) => {
193
+ if (blob) resolve(blob);
194
+ else reject(new PngCaptureError("blob-failed"));
195
+ },
196
+ mimeType,
197
+ mimeType === "image/png" ? undefined : 0.95,
198
+ );
199
+ });
200
+ }
@@ -0,0 +1,207 @@
1
+ import { useActionMutation } from "@agent-native/core/client/hooks";
2
+ import { sourceContentHash } from "@shared/source-workspace";
3
+ import type { QueryClient } from "@tanstack/react-query";
4
+ import type { Dispatch, RefObject, SetStateAction } from "react";
5
+ import { toast } from "sonner";
6
+
7
+ import type { DesignSaveOutboxEntry } from "@/lib/design-save-outbox";
8
+ import { updateFileResultPersistedContent } from "@/lib/design-save-outbox";
9
+ import type { PatchProofState } from "@/pages/design-editor/command-types";
10
+ import type { FileContentSaveRequest } from "@/pages/design-editor/editor-state";
11
+ import { shouldClearLatestUnloadSave } from "@/pages/design-editor/editor-state";
12
+ import {
13
+ classifyDesignSaveFailure,
14
+ designSaveErrorMessage,
15
+ } from "@/pages/design-editor/save-failure";
16
+
17
+ export interface SaveFileContentArgs {
18
+ acknowledgeOutboxEntry: (entry: DesignSaveOutboxEntry) => Promise<void>;
19
+ canEditDesignRef: RefObject<boolean>;
20
+ createFileSaveOutboxEntry: (
21
+ pending: FileContentSaveRequest,
22
+ expectedVersionHash?: string | undefined,
23
+ ) => DesignSaveOutboxEntry | null;
24
+ fileSaveChainsRef: RefObject<Record<string, Promise<void>>>;
25
+ journalOutboxEntry: (entry: DesignSaveOutboxEntry) => Promise<boolean>;
26
+ lastAckedFileContentHashRef: RefObject<Record<string, string>>;
27
+ latestFileSaveForUnloadRef: RefObject<Record<string, FileContentSaveRequest>>;
28
+ markPendingLocalFileContent: (
29
+ fileId: string,
30
+ content: string,
31
+ baseUpdatedAt?: string | null,
32
+ ) => void;
33
+ queryClient: QueryClient;
34
+ setPatchProof: Dispatch<SetStateAction<PatchProofState | null>>;
35
+ t: (key: string, options?: Record<string, unknown>) => string;
36
+ updateFileMutation: ReturnType<
37
+ typeof useActionMutation<undefined, undefined, "update-file">
38
+ >;
39
+ warnChangesWillRetry: () => void;
40
+ }
41
+
42
+ export function runSaveFileContent(
43
+ {
44
+ acknowledgeOutboxEntry,
45
+ canEditDesignRef,
46
+ createFileSaveOutboxEntry,
47
+ fileSaveChainsRef,
48
+ journalOutboxEntry,
49
+ lastAckedFileContentHashRef,
50
+ latestFileSaveForUnloadRef,
51
+ markPendingLocalFileContent,
52
+ queryClient,
53
+ setPatchProof,
54
+ t,
55
+ updateFileMutation,
56
+ warnChangesWillRetry,
57
+ }: SaveFileContentArgs,
58
+ pending: FileContentSaveRequest,
59
+ ) {
60
+ if (!canEditDesignRef.current) return;
61
+ markPendingLocalFileContent(pending.id, pending.content);
62
+ // Stamp the CURRENTLY-known acked hash onto the object kept for the
63
+ // pagehide/unload keepalive only — that path has no "resolve at send
64
+ // time" luxury (unload can fire the instant this runs), so best-effort
65
+ // now is all it gets. Does NOT feed the real mutateAsync call below,
66
+ // which still re-resolves the hash fresh at send time; see that call's
67
+ // comment for why an already-set pending.expectedVersionHash must not
68
+ // be allowed to shadow a fresher ref read there.
69
+ latestFileSaveForUnloadRef.current[pending.id] =
70
+ pending.expectedVersionHash !== undefined
71
+ ? pending
72
+ : {
73
+ ...pending,
74
+ ...(lastAckedFileContentHashRef.current[pending.id]
75
+ ? {
76
+ expectedVersionHash:
77
+ lastAckedFileContentHashRef.current[pending.id],
78
+ }
79
+ : {}),
80
+ };
81
+ const queuedOutboxEntry = createFileSaveOutboxEntry(
82
+ latestFileSaveForUnloadRef.current[pending.id],
83
+ );
84
+ if (queuedOutboxEntry) void journalOutboxEntry(queuedOutboxEntry);
85
+ const previous = fileSaveChainsRef.current[pending.id] ?? Promise.resolve();
86
+ const current = previous
87
+ .catch(() => {})
88
+ .then(async () => {
89
+ try {
90
+ // Resolve the optimistic-concurrency hash at SEND time (after any
91
+ // earlier chained save for this file has landed and refreshed the
92
+ // acked hash), not at queue time — the freshest ref value always
93
+ // wins here regardless of anything queue-time code may have
94
+ // stamped onto `pending` for the unload path above. Attached
95
+ // whenever a hash is known, on BOTH syncCollab true and false
96
+ // saves — see lastAckedFileContentHashRef's doc comment. Never
97
+ // invent a hash when one isn't known yet; omit as before.
98
+ const expectedVersionHash =
99
+ lastAckedFileContentHashRef.current[pending.id] ??
100
+ pending.expectedVersionHash;
101
+ const outboxEntry = createFileSaveOutboxEntry(
102
+ pending,
103
+ expectedVersionHash,
104
+ );
105
+ if (outboxEntry) await journalOutboxEntry(outboxEntry);
106
+ const result = await updateFileMutation.mutateAsync({
107
+ id: pending.id,
108
+ content: pending.content,
109
+ syncCollab: pending.syncCollab,
110
+ operationSource: pending.operationSource,
111
+ operationRevision: pending.operationRevision,
112
+ ...(expectedVersionHash ? { expectedVersionHash } : {}),
113
+ } as any);
114
+ const resultInfo = result as
115
+ | {
116
+ skippedStaleMirror?: boolean;
117
+ skippedStaleOperation?: boolean;
118
+ versionHash?: string;
119
+ }
120
+ | undefined;
121
+ const skippedStaleMirror = Boolean(resultInfo?.skippedStaleMirror);
122
+ // skippedStaleMirror: the server intentionally left the SQL
123
+ // mirror column untouched because our expectedVersionHash no
124
+ // longer matched the live collab text (a live editor moved past
125
+ // the base this write was computed from). The mirror was NOT
126
+ // updated to pending.content, so recording pending.content's hash
127
+ // as "acked" here would be wrong — it would make a later guarded
128
+ // save believe the server holds content it doesn't. Leave the
129
+ // previously-acked hash in place instead; the DB-reconcile effect
130
+ // (activeFile watcher) will pick up the true live content and
131
+ // refresh the acked hash from that.
132
+ if (!skippedStaleMirror) {
133
+ lastAckedFileContentHashRef.current[pending.id] =
134
+ resultInfo?.versionHash ?? sourceContentHash(pending.content);
135
+ }
136
+ const persistedContentMatches = updateFileResultPersistedContent(
137
+ resultInfo,
138
+ pending.content,
139
+ );
140
+ if (persistedContentMatches && outboxEntry) {
141
+ await acknowledgeOutboxEntry(outboxEntry);
142
+ } else if (!persistedContentMatches) {
143
+ // A stale/no-op save result is a source conflict, not a lost
144
+ // connection. Refetch below rebases the editor; never promise
145
+ // that simply reconnecting will save this obsolete snapshot.
146
+ queryClient.invalidateQueries({
147
+ queryKey: ["action", "get-design"],
148
+ });
149
+ }
150
+ if (
151
+ shouldClearLatestUnloadSave(
152
+ latestFileSaveForUnloadRef.current[pending.id],
153
+ pending,
154
+ !persistedContentMatches,
155
+ )
156
+ ) {
157
+ delete latestFileSaveForUnloadRef.current[pending.id];
158
+ }
159
+ setPatchProof((prev) =>
160
+ prev && prev.fileId === pending.id && prev.status === "queued"
161
+ ? { ...prev, status: "applied" }
162
+ : prev,
163
+ );
164
+ } catch (error) {
165
+ // Drop the (evidently wrong) acked hash so the failure is
166
+ // one-shot: the DB-reconcile effect pulls the fresh server
167
+ // content, and the next save proceeds unguarded from that
168
+ // rebased state instead of failing forever on a dead hash.
169
+ delete lastAckedFileContentHashRef.current[pending.id];
170
+ queryClient.invalidateQueries({
171
+ queryKey: ["action", "get-design"],
172
+ });
173
+ const failureKind = classifyDesignSaveFailure(error, navigator.onLine);
174
+ if (failureKind === "offline") {
175
+ warnChangesWillRetry();
176
+ } else if (
177
+ failureKind !== "intentional-abort" &&
178
+ failureKind !== "conflict"
179
+ ) {
180
+ // Conflicts already rebased above (acked-hash reset + get-design
181
+ // invalidation); a red toast for a routine rebase is just noise.
182
+ toast.error(
183
+ designSaveErrorMessage(error) ?? t("common.genericError"),
184
+ { id: `design-save-error:${pending.id}` },
185
+ );
186
+ }
187
+ setPatchProof((prev) =>
188
+ prev && prev.fileId === pending.id && prev.status === "queued"
189
+ ? {
190
+ ...prev,
191
+ status: "failed",
192
+ error:
193
+ error instanceof Error
194
+ ? error.message
195
+ : t("common.genericError"),
196
+ }
197
+ : prev,
198
+ );
199
+ }
200
+ });
201
+ fileSaveChainsRef.current[pending.id] = current;
202
+ void current.finally(() => {
203
+ if (fileSaveChainsRef.current[pending.id] === current) {
204
+ delete fileSaveChainsRef.current[pending.id];
205
+ }
206
+ });
207
+ }
@@ -0,0 +1,262 @@
1
+ import type { CodeLayerProjection } from "@shared/code-layer";
2
+ import {
3
+ applyVisualEdit,
4
+ ensureCodeLayerNodeIdsInHtml,
5
+ } from "@shared/code-layer";
6
+ import type { Dispatch, RefObject, SetStateAction } from "react";
7
+ import { toast } from "sonner";
8
+
9
+ import type {
10
+ ElementInfo,
11
+ ElementSelectionIntent,
12
+ } from "@/components/design/types";
13
+ import type { ClipboardContentMutationPublication } from "@/lib/clipboard-content-lineage";
14
+ import {
15
+ canonicalizeElementInfoFromProjection,
16
+ resolveCodeLayerNodeFromElementInfo,
17
+ } from "@/pages/design-editor/code-layer-state";
18
+ import {
19
+ dedupeStringIds,
20
+ isScreenRootElementInfo,
21
+ shouldIgnoreOverviewLayerCreationEcho,
22
+ } from "@/pages/design-editor/selection-state";
23
+ import type { DesignTool, EditorMode } from "@/pages/design-editor/types";
24
+
25
+ export interface ScreenElementSelectArgs {
26
+ activeBreakpointWidthStateRef: RefObject<number | undefined>;
27
+ applyFileContentUpdate: (
28
+ fileId: string,
29
+ nextContent: string,
30
+ options?: {
31
+ refreshPreview?: boolean;
32
+ skipPreview?: boolean;
33
+ forcePreviewFullDocument?: boolean;
34
+ persist?: boolean;
35
+ recordHistory?: boolean;
36
+ updatedAt?: string;
37
+ clipboardMutation?: ClipboardContentMutationPublication;
38
+ },
39
+ ) => void;
40
+ clearPendingOverviewLayerSelectionTimer: () => void;
41
+ focusDesignInspectorForSelection: () => void;
42
+ getCodeLayerProjectionForScreen: (
43
+ screenId: string,
44
+ ) => CodeLayerProjection | null;
45
+ getScreenContent: (screenId: string) => string;
46
+ handleBreakpointBarSelect: (widthPx: number | undefined) => void;
47
+ id: string | undefined;
48
+ pendingOverviewLayerSelectionRef: RefObject<string | null>;
49
+ pendingOverviewScreenSelectionRef: RefObject<string | null>;
50
+ selectedLayerIdsState: string[];
51
+ setActiveFileId: Dispatch<SetStateAction<string | null>>;
52
+ setActiveTool: Dispatch<SetStateAction<DesignTool>>;
53
+ setCreatedOverviewLayerSelection: Dispatch<
54
+ SetStateAction<{ screenId: string; layerId: string } | null>
55
+ >;
56
+ setHoveredElement: Dispatch<SetStateAction<ElementInfo | null>>;
57
+ setHoveredElementScreenId: Dispatch<SetStateAction<string | null>>;
58
+ setMode: Dispatch<SetStateAction<EditorMode>>;
59
+ setOverviewSelectedScreenIds: Dispatch<SetStateAction<string[]>>;
60
+ setSelectedElement: Dispatch<SetStateAction<ElementInfo | null>>;
61
+ setSelectedLayerIdsState: Dispatch<SetStateAction<string[]>>;
62
+ shouldPreserveBlockedOverviewLayerSelectionRef: RefObject<
63
+ (screenId: string) => boolean
64
+ >;
65
+ t: (key: string, options?: Record<string, unknown>) => string;
66
+ viewModeRef: RefObject<"single" | "overview">;
67
+ }
68
+
69
+ export function runScreenElementSelect(
70
+ {
71
+ activeBreakpointWidthStateRef,
72
+ applyFileContentUpdate,
73
+ clearPendingOverviewLayerSelectionTimer,
74
+ focusDesignInspectorForSelection,
75
+ getCodeLayerProjectionForScreen,
76
+ getScreenContent,
77
+ handleBreakpointBarSelect,
78
+ id,
79
+ pendingOverviewLayerSelectionRef,
80
+ pendingOverviewScreenSelectionRef,
81
+ selectedLayerIdsState,
82
+ setActiveFileId,
83
+ setActiveTool,
84
+ setCreatedOverviewLayerSelection,
85
+ setHoveredElement,
86
+ setHoveredElementScreenId,
87
+ setMode,
88
+ setOverviewSelectedScreenIds,
89
+ setSelectedElement,
90
+ setSelectedLayerIdsState,
91
+ shouldPreserveBlockedOverviewLayerSelectionRef,
92
+ t,
93
+ viewModeRef,
94
+ }: ScreenElementSelectArgs,
95
+ screenId: string,
96
+ info: ElementInfo,
97
+ intent?: ElementSelectionIntent,
98
+ options: {
99
+ persistPendingNodeId?: boolean;
100
+ breakpointWidthPx?: number;
101
+ } = {},
102
+ ) {
103
+ const pendingLayerId = pendingOverviewLayerSelectionRef.current;
104
+ const pendingScreenId = pendingOverviewScreenSelectionRef.current;
105
+ const projection = getCodeLayerProjectionForScreen(screenId);
106
+ const canonical = projection
107
+ ? canonicalizeElementInfoFromProjection(projection, info)
108
+ : info;
109
+ const node = projection
110
+ ? resolveCodeLayerNodeFromElementInfo(projection, canonical)
111
+ : null;
112
+ if (
113
+ shouldIgnoreOverviewLayerCreationEcho({
114
+ pendingLayerId,
115
+ pendingScreenId,
116
+ screenId,
117
+ info: canonical,
118
+ resolvedLayerId: node?.id,
119
+ event: "select",
120
+ })
121
+ ) {
122
+ return;
123
+ }
124
+ pendingOverviewScreenSelectionRef.current = null;
125
+ pendingOverviewLayerSelectionRef.current = null;
126
+ clearPendingOverviewLayerSelectionTimer();
127
+ setCreatedOverviewLayerSelection(null);
128
+ if (
129
+ shouldPreserveBlockedOverviewLayerSelectionRef.current(screenId) &&
130
+ (isScreenRootElementInfo(canonical) ||
131
+ !node ||
132
+ selectedLayerIdsState.includes(node.id))
133
+ ) {
134
+ return;
135
+ }
136
+ // Node-id integrity (id-on-demand): AI-generated/duplicated screens
137
+ // frequently ship elements with a missing or empty-string
138
+ // `data-agent-native-node-id` — every id-keyed operation on that
139
+ // element (move/reorder, style commits that resolve a targetNode,
140
+ // motion tracks, scrub) then silently no-ops or throws "Node with
141
+ // data-agent-native-node-id=\"\" not found in sourceHtml". The bridge
142
+ // (editor-chrome.bridge.ts's getElementInfo) mints a durable
143
+ // `pendingNodeId` on the SELECTION payload whenever it can't resolve a
144
+ // stable id for the element (`!sourceId`) and exposes it as
145
+ // `canonical.pendingNodeId`; persist it as the element's real
146
+ // `data-agent-native-node-id` right now via the same deterministic,
147
+ // guarded write path every other edit uses (applyVisualEdit's new
148
+ // "attribute" intent + applyFileContentUpdate), so every subsequent
149
+ // id-keyed op against this element resolves normally afterward. This is
150
+ // more reliable than resolving through the host's own static-HTML
151
+ // projection (`node`, below) — the bridge already knows the live DOM
152
+ // element and its working selector candidates even when the host's
153
+ // positional-selector projection match drifts.
154
+ const pendingNodeId = (canonical as { pendingNodeId?: string }).pendingNodeId;
155
+ if (
156
+ options.persistPendingNodeId !== false &&
157
+ !isScreenRootElementInfo(canonical) &&
158
+ pendingNodeId &&
159
+ !canonical.sourceId &&
160
+ canonical.selector
161
+ ) {
162
+ const rawContent = getScreenContent(screenId);
163
+ if (rawContent) {
164
+ const result = applyVisualEdit(
165
+ rawContent,
166
+ {
167
+ kind: "attribute",
168
+ target: { selector: canonical.selector },
169
+ name: "data-agent-native-node-id",
170
+ value: pendingNodeId,
171
+ },
172
+ {
173
+ source: { kind: "design-file", designId: id, fileId: screenId },
174
+ },
175
+ );
176
+ if (result.result.status === "applied" && result.content !== rawContent) {
177
+ applyFileContentUpdate(screenId, result.content, {
178
+ recordHistory: false,
179
+ });
180
+ }
181
+ }
182
+ } else if (
183
+ options.persistPendingNodeId !== false &&
184
+ // Fallback sweep: an element the bridge didn't mint a pendingNodeId
185
+ // for (older bridge instance, or a node resolved only through the
186
+ // host's own projection) but that still lacks a stable id per the
187
+ // host's own projection match. Runs the whole-document stamp helper
188
+ // so any other id-less siblings pick up ids in the same pass too.
189
+ !isScreenRootElementInfo(canonical) &&
190
+ node &&
191
+ !node.dataAttributes["data-agent-native-node-id"]?.trim()
192
+ ) {
193
+ const rawContent = getScreenContent(screenId);
194
+ if (rawContent) {
195
+ const stamped = ensureCodeLayerNodeIdsInHtml(rawContent, {
196
+ source: { kind: "design-file", designId: id, fileId: screenId },
197
+ });
198
+ if (stamped.changed && stamped.content !== rawContent) {
199
+ applyFileContentUpdate(screenId, stamped.content, {
200
+ recordHistory: false,
201
+ });
202
+ }
203
+ }
204
+ }
205
+ // Known limitation: elements rendered from a `<template x-for>`
206
+ // repeater (common in AI-generated Alpine.js list/task UIs) have no
207
+ // per-instance static DOM node in the SOURCE HTML at all — neither
208
+ // resolveCodeLayerNodeFromElementInfo nor a selector-based
209
+ // applyVisualEdit resolution can find a unique per-instance node to
210
+ // stamp. Fixing that requires the code-layer projection itself to
211
+ // model `<template>` repeater children as selectable/attributable
212
+ // nodes, which is out of scope for this selection-time fix.
213
+ const additiveSelection = Boolean(
214
+ node &&
215
+ (intent?.additive ||
216
+ intent?.range ||
217
+ intent?.shiftKey ||
218
+ intent?.metaKey ||
219
+ intent?.ctrlKey),
220
+ );
221
+ setActiveFileId(screenId);
222
+ setSelectedElement(canonical);
223
+ setHoveredElement(null);
224
+ setHoveredElementScreenId(null);
225
+ if (node && additiveSelection) {
226
+ setSelectedLayerIdsState((current) => {
227
+ const removeExisting =
228
+ Boolean(intent?.metaKey || intent?.ctrlKey) &&
229
+ !intent?.shiftKey &&
230
+ current.includes(node.id);
231
+ if (removeExisting) {
232
+ const next = current.filter((layerId) => layerId !== node.id);
233
+ return next.length > 0 ? next : [node.id];
234
+ }
235
+ return dedupeStringIds([...current, node.id]);
236
+ });
237
+ } else {
238
+ setSelectedLayerIdsState(node ? [node.id] : []);
239
+ }
240
+ if (viewModeRef.current === "overview") {
241
+ setOverviewSelectedScreenIds([]);
242
+ // A responsive sub-frame now owns a full editor bridge, so selection
243
+ // carries its exact width into the edit scope. Primary-frame clicks
244
+ // still return to Base. This prevents two identical selectors in the
245
+ // base and responsive runtimes from racing for one global scope.
246
+ if (options.breakpointWidthPx !== undefined) {
247
+ handleBreakpointBarSelect(options.breakpointWidthPx);
248
+ const guidanceKey = `design-responsive-edit-guidance:${id}:${screenId}`;
249
+ if (window.localStorage.getItem(guidanceKey) !== "shown") {
250
+ window.localStorage.setItem(guidanceKey, "shown");
251
+ toast.info(t("designEditor.breakpointBar.scope.firstEditGuidance"), {
252
+ duration: 6000,
253
+ });
254
+ }
255
+ } else if (activeBreakpointWidthStateRef.current !== undefined) {
256
+ handleBreakpointBarSelect(undefined);
257
+ }
258
+ }
259
+ setActiveTool("move");
260
+ setMode("edit");
261
+ focusDesignInspectorForSelection();
262
+ }