@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,212 @@
1
+ import type { CodeLayerNode, CodeLayerTreeNode } from "@shared/code-layer";
2
+ import type { Dispatch, RefObject, SetStateAction } from "react";
3
+
4
+ import type { ElementInfo } from "@/components/design/types";
5
+ import {
6
+ collectCodeLayerAncestors,
7
+ elementInfoFromCodeLayerNode,
8
+ } from "@/pages/design-editor/code-layer-state";
9
+ import {
10
+ hasSelectableCodeLayerParent,
11
+ resolveEscapePopSelectionAction,
12
+ shouldEscapeToOverview,
13
+ } from "@/pages/design-editor/selection-state";
14
+ import type { DesignTool, EditorMode } from "@/pages/design-editor/types";
15
+
16
+ export interface EscapeHotkeyArgs {
17
+ activeBreakpointWidthStateRef: RefObject<number | undefined>;
18
+ activeTool: DesignTool;
19
+ cancelActiveEditorDrag: () => boolean;
20
+ codeLayerOwnerByNodeIdRef: RefObject<
21
+ Map<
22
+ string,
23
+ {
24
+ fileId: string;
25
+ node: CodeLayerNode;
26
+ tree: CodeLayerTreeNode[];
27
+ runtimeOnly: boolean;
28
+ }
29
+ >
30
+ >;
31
+ drawMode: boolean;
32
+ enterOverviewFromZoom: (nextMode?: EditorMode) => void;
33
+ focusedAnnotationSending: boolean;
34
+ handleBreakpointBarSelect: (widthPx: number | undefined) => void;
35
+ handleCloseKeyboardShortcuts: () => void;
36
+ handleExitFocusedDrawMode: () => void;
37
+ handleExitOverviewDrawMode: () => void;
38
+ keyboardShortcutsOpen: boolean;
39
+ mode: EditorMode;
40
+ overviewAnnotationSending: boolean;
41
+ pinMode: boolean;
42
+ selectedElement: ElementInfo | null;
43
+ selectedLayerIdsState: string[];
44
+ setActiveFileId: Dispatch<SetStateAction<string | null>>;
45
+ setActiveTool: Dispatch<SetStateAction<DesignTool>>;
46
+ setDrawMode: Dispatch<SetStateAction<boolean>>;
47
+ setExpandedLayerIds: Dispatch<SetStateAction<string[]>>;
48
+ setHoveredElement: Dispatch<SetStateAction<ElementInfo | null>>;
49
+ setMode: Dispatch<SetStateAction<EditorMode>>;
50
+ setOverviewClearSelectionRequest: Dispatch<SetStateAction<number>>;
51
+ setOverviewSelectedScreenIds: Dispatch<SetStateAction<string[]>>;
52
+ setPinMode: Dispatch<SetStateAction<boolean>>;
53
+ setSelectedElement: Dispatch<SetStateAction<ElementInfo | null>>;
54
+ setSelectedLayerIdsState: Dispatch<SetStateAction<string[]>>;
55
+ viewMode: "single" | "overview";
56
+ }
57
+
58
+ export function runEscapeHotkey({
59
+ activeBreakpointWidthStateRef,
60
+ activeTool,
61
+ cancelActiveEditorDrag,
62
+ codeLayerOwnerByNodeIdRef,
63
+ drawMode,
64
+ enterOverviewFromZoom,
65
+ focusedAnnotationSending,
66
+ handleBreakpointBarSelect,
67
+ handleCloseKeyboardShortcuts,
68
+ handleExitFocusedDrawMode,
69
+ handleExitOverviewDrawMode,
70
+ keyboardShortcutsOpen,
71
+ mode,
72
+ overviewAnnotationSending,
73
+ pinMode,
74
+ selectedElement,
75
+ selectedLayerIdsState,
76
+ setActiveFileId,
77
+ setActiveTool,
78
+ setDrawMode,
79
+ setExpandedLayerIds,
80
+ setHoveredElement,
81
+ setMode,
82
+ setOverviewClearSelectionRequest,
83
+ setOverviewSelectedScreenIds,
84
+ setPinMode,
85
+ setSelectedElement,
86
+ setSelectedLayerIdsState,
87
+ viewMode,
88
+ }: EscapeHotkeyArgs) {
89
+ if (keyboardShortcutsOpen) {
90
+ handleCloseKeyboardShortcuts();
91
+ return;
92
+ }
93
+ if (cancelActiveEditorDrag()) return;
94
+ // A delivery-confirmation wait freezes the complete annotation batch.
95
+ // Escape must not clear it while the submitted snapshot is in flight.
96
+ if (overviewAnnotationSending || focusedAnnotationSending) return;
97
+ // Escape is a deliberate annotate-mode exit, same as the overlay's X.
98
+ // Route it through the same per-surface clear semantics instead of merely
99
+ // hiding the overlay and leaving a stale batch to reappear later.
100
+ if (drawMode && mode === "annotate") {
101
+ if (viewMode === "overview") handleExitOverviewDrawMode();
102
+ else handleExitFocusedDrawMode();
103
+ return;
104
+ }
105
+ // ReviewCanvasPins owns Escape while comment mode is active so it can
106
+ // dismiss in context: first an open draft/thread, then pin mode itself.
107
+ // Letting this global handler continue would exit the tool on the same
108
+ // keypress that only meant to close the composer.
109
+ if (pinMode) return;
110
+ // BP-DEEP item 5 — Framer-style click-to-target: Escape's first job when
111
+ // a breakpoint is the active edit target is to return to Base, matching
112
+ // "click the base frame / empty canvas" — mirrors the other early-return
113
+ // priority checks above/below (cancelActiveEditorDrag,
114
+ // shouldEscapeToOverview) that let one Escape press consume the single
115
+ // most contextually-relevant action instead of stacking every effect.
116
+ // Gated on the ref (not the state) so this callback doesn't need
117
+ // activeBreakpointWidthState as a dep and doesn't get recreated (and
118
+ // re-registered with useDesignHotkeys) on every breakpoint switch.
119
+ if (activeBreakpointWidthStateRef.current !== undefined) {
120
+ handleBreakpointBarSelect(undefined);
121
+ return;
122
+ }
123
+ if (
124
+ shouldEscapeToOverview({
125
+ activeTool,
126
+ drawMode,
127
+ mode,
128
+ pinMode,
129
+ selectedElement,
130
+ viewMode,
131
+ })
132
+ ) {
133
+ enterOverviewFromZoom();
134
+ return;
135
+ }
136
+ // Figma parity — pop the plain-canvas selection one level at a time
137
+ // (child layer -> parent layer -> containing screen/frame -> fully
138
+ // deselected) instead of deselecting everything on the first Escape.
139
+ // The decision itself is a pure, unit-tested function
140
+ // (resolveEscapePopSelectionAction in design-editor/selection-state.ts);
141
+ // this reuses the same codeLayerOwnerByNodeIdRef/parentId ancestor walk
142
+ // handleSelectParentLayer (Shift+Enter / "\\") already uses, inlined
143
+ // here (rather than calling selectCodeLayerNodesForHotkey directly)
144
+ // since that helper is declared later in this component and referencing
145
+ // it from this callback's deps would hit its temporal-dead-zone before
146
+ // this render's declarations run.
147
+ const escapeSelectedLayerId =
148
+ selectedLayerIdsState[selectedLayerIdsState.length - 1];
149
+ const escapeOwner = escapeSelectedLayerId
150
+ ? codeLayerOwnerByNodeIdRef.current.get(escapeSelectedLayerId)
151
+ : undefined;
152
+ // BUG-ESCAPE-SHELL: the flat codeLayerOwnerByNodeIdRef map (built from
153
+ // projection.nodes) still contains <html>/<body> entries with real
154
+ // parentId links even though the VISUAL layers tree collapses them away
155
+ // (shared/code-layer.ts's isCollapsibleDocumentShellNode). A bare
156
+ // Boolean(escapeOwner?.node.parentId) check treats a top-level layer's
157
+ // collapsed <body> ancestor as a selectable parent, so popping from the
158
+ // screen root would select <body> then <html> instead of stopping at the
159
+ // screen/frame level. hasSelectableCodeLayerParent excludes those shell
160
+ // nodes so the pop walk matches what the layers panel shows.
161
+ const escapeParentOwner = escapeOwner?.node.parentId
162
+ ? codeLayerOwnerByNodeIdRef.current.get(escapeOwner.node.parentId)
163
+ : undefined;
164
+ const popAction = resolveEscapePopSelectionAction({
165
+ hasSelectedLayer: Boolean(escapeOwner),
166
+ hasLayerParent: hasSelectableCodeLayerParent({
167
+ parentNode: escapeParentOwner?.node,
168
+ }),
169
+ viewMode,
170
+ });
171
+ if (popAction.kind === "pop-to-parent-layer" && escapeOwner) {
172
+ const parentOwner = escapeParentOwner;
173
+ if (parentOwner && parentOwner.fileId === escapeOwner.fileId) {
174
+ setActiveFileId(parentOwner.fileId);
175
+ setOverviewSelectedScreenIds([]);
176
+ setSelectedLayerIdsState([parentOwner.node.id]);
177
+ setSelectedElement(elementInfoFromCodeLayerNode(parentOwner.node));
178
+ setExpandedLayerIds((current) => {
179
+ const next = new Set(current);
180
+ next.add(parentOwner.fileId);
181
+ collectCodeLayerAncestors(
182
+ parentOwner.tree,
183
+ parentOwner.node.id,
184
+ ).forEach((ancestorId) => next.add(ancestorId));
185
+ return next.size === current.length ? current : Array.from(next);
186
+ });
187
+ return;
188
+ }
189
+ // Parent couldn't be resolved (e.g. cross-file) — fall through to a
190
+ // full deselect below rather than silently doing nothing.
191
+ } else if (popAction.kind === "pop-to-screen-frame" && escapeOwner) {
192
+ setSelectedElement(null);
193
+ setSelectedLayerIdsState([]);
194
+ setOverviewSelectedScreenIds([escapeOwner.fileId]);
195
+ setOverviewClearSelectionRequest((request) => request + 1);
196
+ return;
197
+ }
198
+ setSelectedElement(null);
199
+ setHoveredElement(null);
200
+ setOverviewSelectedScreenIds([]);
201
+ // Item 10 — Escape must also clear a multi-layer selection
202
+ // (selectedLayerIdsState), not just the single selectedElement /
203
+ // overviewSelectedScreenIds above. Without this, pressing Escape after a
204
+ // multi-layer marquee/shift-click selection left the layers panel
205
+ // showing stale selected rows even though the canvas selection was gone.
206
+ setSelectedLayerIdsState([]);
207
+ setOverviewClearSelectionRequest((request) => request + 1);
208
+ setDrawMode(false);
209
+ setPinMode(false);
210
+ setActiveTool("move");
211
+ setMode("edit");
212
+ }
@@ -0,0 +1,119 @@
1
+ import { applyVisualEdit, buildCodeLayerProjection } from "@shared/code-layer";
2
+ import type { Dispatch, SetStateAction } from "react";
3
+ import { toast } from "sonner";
4
+
5
+ import type { ElementInfo } from "@/components/design/types";
6
+ import type { ClipboardContentMutationPublication } from "@/lib/clipboard-content-lineage";
7
+ import {
8
+ codeLayerPatchMessage,
9
+ elementInfoFromCodeLayerNode,
10
+ } from "@/pages/design-editor/code-layer-state";
11
+ import { setCodeLayerAttributeInHtml } from "@/pages/design-editor/html-layer-positioning";
12
+ import { buildActiveFileNodeIdSet } from "@/pages/design-editor/selection-state";
13
+ import type { DesignFile } from "@/pages/design-editor/types";
14
+
15
+ export interface FrameSelectionArgs {
16
+ activeFile: DesignFile;
17
+ applyLocalContentUpdate: (
18
+ nextContent: string,
19
+ options?: {
20
+ refreshPreview?: boolean;
21
+ skipPreview?: boolean;
22
+ forcePreviewFullDocument?: boolean;
23
+ immediateSave?: boolean;
24
+ persist?: boolean;
25
+ recordHistory?: boolean;
26
+ historyBeforeContent?: string;
27
+ updatedAt?: string;
28
+ clipboardMutation?: ClipboardContentMutationPublication;
29
+ },
30
+ ) => void;
31
+ canEditDesign: boolean;
32
+ files: DesignFile[];
33
+ getFreshActiveContent: () => string;
34
+ selectedLayerIdsState: string[];
35
+ setSelectedElement: Dispatch<SetStateAction<ElementInfo | null>>;
36
+ setSelectedLayerIdsState: Dispatch<SetStateAction<string[]>>;
37
+ t: (key: string, options?: Record<string, unknown>) => string;
38
+ }
39
+
40
+ export function runFrameSelection({
41
+ activeFile,
42
+ applyLocalContentUpdate,
43
+ canEditDesign,
44
+ files,
45
+ getFreshActiveContent,
46
+ selectedLayerIdsState,
47
+ setSelectedElement,
48
+ setSelectedLayerIdsState,
49
+ t,
50
+ }: FrameSelectionArgs) {
51
+ if (!canEditDesign || !activeFile) return;
52
+ const baseContent = getFreshActiveContent();
53
+ const fileIds = new Set(files.map((f) => f.id));
54
+ const activeNodeIdSet = buildActiveFileNodeIdSet(
55
+ buildCodeLayerProjection(baseContent),
56
+ );
57
+ const nodeIds = selectedLayerIdsState.filter(
58
+ (id) => !id.startsWith("__") && !fileIds.has(id) && activeNodeIdSet.has(id),
59
+ );
60
+ if (nodeIds.length < 1) return;
61
+ const patch = applyVisualEdit(baseContent, {
62
+ kind: "wrapNodes",
63
+ targetIds: nodeIds,
64
+ autoLayout: false,
65
+ });
66
+ if (patch.result.status !== "applied") {
67
+ toast.error(
68
+ codeLayerPatchMessage(
69
+ patch.result.message,
70
+ t("designEditor.toasts.layerMoveFailed"),
71
+ ),
72
+ { duration: 4000 },
73
+ );
74
+ return;
75
+ }
76
+ let nextContent = patch.content;
77
+ let wrapperNode = patch.result.wrapperNodeId
78
+ ? patch.projection.nodes.find(
79
+ (n) =>
80
+ n.dataAttributes["data-agent-native-node-id"] ===
81
+ patch.result.wrapperNodeId,
82
+ )
83
+ : undefined;
84
+ if (wrapperNode) {
85
+ const renamed = setCodeLayerAttributeInHtml(
86
+ nextContent,
87
+ wrapperNode,
88
+ "data-agent-native-layer-name",
89
+ "Frame",
90
+ );
91
+ if (renamed) nextContent = renamed;
92
+ const taggedProjection = buildCodeLayerProjection(nextContent);
93
+ const taggedNode = taggedProjection.nodes.find(
94
+ (n) =>
95
+ n.dataAttributes["data-agent-native-node-id"] ===
96
+ patch.result.wrapperNodeId,
97
+ );
98
+ if (taggedNode) {
99
+ const tagged = setCodeLayerAttributeInHtml(
100
+ nextContent,
101
+ taggedNode,
102
+ "data-an-primitive",
103
+ "frame",
104
+ );
105
+ if (tagged) nextContent = tagged;
106
+ }
107
+ wrapperNode =
108
+ buildCodeLayerProjection(nextContent).nodes.find(
109
+ (n) =>
110
+ n.dataAttributes["data-agent-native-node-id"] ===
111
+ patch.result.wrapperNodeId,
112
+ ) ?? wrapperNode;
113
+ }
114
+ applyLocalContentUpdate(nextContent, { skipPreview: true });
115
+ if (wrapperNode) {
116
+ setSelectedLayerIdsState([wrapperNode.id]);
117
+ setSelectedElement(elementInfoFromCodeLayerNode(wrapperNode));
118
+ }
119
+ }
@@ -0,0 +1,169 @@
1
+ import type { CanvasFrameGeometryById } from "@shared/canvas-frames";
2
+ import type { QueryClient } from "@tanstack/react-query";
3
+ import type { RefObject } from "react";
4
+
5
+ import {
6
+ cloneCanvasFrameGeometry,
7
+ viewportChangedFrameIds,
8
+ } from "@/pages/design-editor/design-data-geometry-utils";
9
+ import type { UndoRedoOrderKind } from "@/pages/design-editor/editor-state";
10
+ import {
11
+ geometrySnapshotsEqual,
12
+ sanitizeCanvasFrameGeometryForPersist,
13
+ } from "@/pages/design-editor/geometry-persistence";
14
+ import type {
15
+ GeometryHistoryEntry,
16
+ GeometryHistorySelection,
17
+ } from "@/pages/design-editor/history";
18
+ import { MAX_DESIGN_UNDO_STACK } from "@/pages/design-editor/history";
19
+
20
+ export interface GeometryCommitArgs {
21
+ boardFileId: string | undefined;
22
+ captureCurrentSelection: () => GeometryHistorySelection;
23
+ clearRedoStacks: () => void;
24
+ designDataJsonRef: RefObject<Record<string, unknown>>;
25
+ geometryUndoStackRef: RefObject<GeometryHistoryEntry[]>;
26
+ historyOrderRef: RefObject<UndoRedoOrderKind[]>;
27
+ id: string | undefined;
28
+ lastGeometryCommitAtRef: RefObject<number>;
29
+ locallyPinnedHeightIdsRef: RefObject<Set<string>>;
30
+ queryClient: QueryClient;
31
+ queueFrameGeometrySave: (geometryById: CanvasFrameGeometryById) => void;
32
+ syncUndoRedoState: () => void;
33
+ writeFrameGeometrySnapshot: (
34
+ geometryById: CanvasFrameGeometryById,
35
+ options?: { syncViewportFrameIds?: string[]; pinHeightFrameIds?: string[] },
36
+ ) => void;
37
+ }
38
+
39
+ export function runGeometryCommit(
40
+ {
41
+ boardFileId,
42
+ captureCurrentSelection,
43
+ clearRedoStacks,
44
+ designDataJsonRef,
45
+ geometryUndoStackRef,
46
+ historyOrderRef,
47
+ id,
48
+ lastGeometryCommitAtRef,
49
+ locallyPinnedHeightIdsRef,
50
+ queryClient,
51
+ queueFrameGeometrySave,
52
+ syncUndoRedoState,
53
+ writeFrameGeometrySnapshot,
54
+ }: GeometryCommitArgs,
55
+ before: CanvasFrameGeometryById,
56
+ after: CanvasFrameGeometryById,
57
+ options?: { source?: "pointer" | "keyboard" },
58
+ ) {
59
+ const beforeSnapshot = cloneCanvasFrameGeometry(before);
60
+ // Geometry-persist guard: refuse absurd committed geometry HERE (not
61
+ // only inside the save functions) so the undo stack and the mid-gesture
62
+ // query-cache write below stay consistent with what actually persists —
63
+ // an insane frame falls back to its own pre-gesture geometry, and a
64
+ // commit whose every change was refused becomes a no-op.
65
+ const { geometryById: afterSnapshot } = sanitizeCanvasFrameGeometryForPersist(
66
+ cloneCanvasFrameGeometry(after),
67
+ beforeSnapshot,
68
+ boardFileId ? [boardFileId] : [],
69
+ );
70
+ if (geometrySnapshotsEqual(beforeSnapshot, afterSnapshot)) {
71
+ return;
72
+ }
73
+ // U9: keyboard nudge (arrow-key auto-repeat) fires one onGeometryCommit
74
+ // per tick, each previously pushing its own undo entry AND its own
75
+ // immediate (non-debounced) server write — a held arrow key could evict
76
+ // the 50-entry undo cap in well under a second and hammer the server.
77
+ // Coalesce consecutive commits into one undo entry (mirroring Yjs's own
78
+ // captureTimeout) when the new commit continues straight from the last
79
+ // one (same "before" as the prior "after") within a ~800ms window, and
80
+ // route the write through the same debounced save queue used for drags
81
+ // instead of firing an immediate mutation per tick.
82
+ //
83
+ // This coalescing must only apply to KEYBOARD nudge auto-repeat, not to
84
+ // two independent pointer gestures (e.g. two separate drags) that
85
+ // happen to land within the same 800ms window — those are discrete
86
+ // user actions and each must be its own undo step, matching Figma.
87
+ // MultiScreenCanvas's onGeometryCommit callback (a real pointer drag)
88
+ // omits `options`, so it defaults to "pointer" and never coalesces;
89
+ // only handleNudgeSelection's overview branch passes "keyboard".
90
+ const source = options?.source ?? "pointer";
91
+ const now = Date.now();
92
+ const lastEntry =
93
+ geometryUndoStackRef.current[geometryUndoStackRef.current.length - 1];
94
+ const continuesLastGesture =
95
+ source === "keyboard" &&
96
+ lastEntry &&
97
+ now - lastGeometryCommitAtRef.current < 800 &&
98
+ geometrySnapshotsEqual(lastEntry.after, beforeSnapshot);
99
+ lastGeometryCommitAtRef.current = now;
100
+ // Figma-parity undo/redo selection restore: selectionAfter always
101
+ // reflects the CURRENT selection at this commit tick (so redo restores
102
+ // whatever was selected when the gesture finished), while
103
+ // selectionBefore is only captured on the FIRST tick of a gesture and
104
+ // then carried forward unchanged through every coalesced continuation —
105
+ // otherwise a held arrow key would keep overwriting selectionBefore
106
+ // with the selection at the START of each individual tick instead of
107
+ // the whole gesture's actual starting selection.
108
+ const selectionAfter = captureCurrentSelection();
109
+ if (continuesLastGesture) {
110
+ geometryUndoStackRef.current = [
111
+ ...geometryUndoStackRef.current.slice(0, -1),
112
+ {
113
+ before: lastEntry.before,
114
+ after: afterSnapshot,
115
+ selectionBefore: lastEntry.selectionBefore,
116
+ selectionAfter,
117
+ },
118
+ ];
119
+ } else {
120
+ geometryUndoStackRef.current = [
121
+ ...geometryUndoStackRef.current.slice(-(MAX_DESIGN_UNDO_STACK - 1)),
122
+ {
123
+ before: beforeSnapshot,
124
+ after: afterSnapshot,
125
+ selectionBefore: selectionAfter,
126
+ selectionAfter,
127
+ },
128
+ ];
129
+ }
130
+ clearRedoStacks();
131
+ historyOrderRef.current = continuesLastGesture
132
+ ? historyOrderRef.current
133
+ : [
134
+ ...historyOrderRef.current.slice(-(MAX_DESIGN_UNDO_STACK - 1)),
135
+ "geometry",
136
+ ];
137
+ const resizedFrameIds = viewportChangedFrameIds(
138
+ beforeSnapshot,
139
+ afterSnapshot,
140
+ );
141
+ if (continuesLastGesture) {
142
+ // Mid-gesture tick: keep the query cache current for a responsive
143
+ // canvas, but debounce the actual network write so a held key
144
+ // doesn't send one mutation per tick.
145
+ queryClient.setQueryData(["action", "get-design", { id }], (old: any) => {
146
+ if (!old || typeof old !== "object") return old;
147
+ const nextData = {
148
+ ...designDataJsonRef.current,
149
+ canvasFrames: afterSnapshot,
150
+ };
151
+ return { ...old, data: JSON.stringify(nextData) };
152
+ });
153
+ queueFrameGeometrySave(afterSnapshot);
154
+ } else {
155
+ writeFrameGeometrySnapshot(
156
+ afterSnapshot,
157
+ resizedFrameIds.length > 0
158
+ ? (resizedFrameIds.forEach((frameId) =>
159
+ locallyPinnedHeightIdsRef.current.add(frameId),
160
+ ),
161
+ {
162
+ syncViewportFrameIds: resizedFrameIds,
163
+ pinHeightFrameIds: resizedFrameIds,
164
+ })
165
+ : undefined,
166
+ );
167
+ }
168
+ syncUndoRedoState();
169
+ }
@@ -0,0 +1,177 @@
1
+ import {
2
+ buildCodeLayerProjection,
3
+ buildCodeLayerTree,
4
+ } from "@shared/code-layer";
5
+
6
+ import type { ElementInfo } from "@/components/design/types";
7
+ import { extractDesignClipboardManagedStyles } from "@/lib/design-clipboard-managed-styles";
8
+ import { resolveClipboardLayerSourceHtml } from "@/pages/design-editor/clipboard-layer-source";
9
+ import { getElementOuterHtml } from "@/pages/design-editor/clone-and-pen-edit";
10
+ import {
11
+ collectCodeLayerAncestors,
12
+ resolveCodeLayerNodeFromElementInfo,
13
+ } from "@/pages/design-editor/code-layer-state";
14
+ import type {
15
+ LiveScreenSnapshot,
16
+ RuntimeLayerSnapshot,
17
+ SelectedCanvasLayerSnapshot,
18
+ } from "@/pages/design-editor/command-types";
19
+ import type { OverviewScreen } from "@/pages/design-editor/derive/overview-screens";
20
+ import { shouldUseRuntimeLayerProjection } from "@/pages/design-editor/pending-edits";
21
+ import type { DesignFile } from "@/pages/design-editor/types";
22
+
23
+ export interface GetSelectedLayerSnapshotsArgs {
24
+ activeFile: DesignFile;
25
+ designSourceType: "inline" | "localhost" | "fusion";
26
+ files: DesignFile[];
27
+ getFreshActiveContent: () => string;
28
+ getScreenContent: (screenId: string) => string;
29
+ liveScreenSnapshotsById: Record<string, LiveScreenSnapshot>;
30
+ overviewScreens: OverviewScreen[];
31
+ runtimeLayerSnapshotsById: Record<string, RuntimeLayerSnapshot>;
32
+ selectedElement: ElementInfo | null;
33
+ selectedElementLayerId: string | null;
34
+ selectedLayerIdsState: string[];
35
+ }
36
+
37
+ export function runGetSelectedLayerSnapshots({
38
+ activeFile,
39
+ designSourceType,
40
+ files,
41
+ getFreshActiveContent,
42
+ getScreenContent,
43
+ liveScreenSnapshotsById,
44
+ overviewScreens,
45
+ runtimeLayerSnapshotsById,
46
+ selectedElement,
47
+ selectedElementLayerId,
48
+ selectedLayerIdsState,
49
+ }: GetSelectedLayerSnapshotsArgs) {
50
+ const fileIds = new Set(files.map((file) => file.id));
51
+ const candidateIds = selectedLayerIdsState.filter(
52
+ (layerId) => layerId && !layerId.startsWith("__") && !fileIds.has(layerId),
53
+ );
54
+ if (
55
+ selectedElementLayerId &&
56
+ !candidateIds.includes(selectedElementLayerId)
57
+ ) {
58
+ candidateIds.push(selectedElementLayerId);
59
+ }
60
+
61
+ const snapshots: SelectedCanvasLayerSnapshot[] = [];
62
+ for (const file of files) {
63
+ // A hydrated localhost app has two snapshots: `/snapshot` is the source
64
+ // or SSR shell, while the runtime layer snapshot is the DOM the user can
65
+ // actually see and select. Layers already prefers that rendered tree, so
66
+ // Copy must resolve against the same id namespace or client-rendered
67
+ // React/Vue/Svelte nodes silently produce an empty clipboard.
68
+ const runtimeProjectionEligible = shouldUseRuntimeLayerProjection({
69
+ screen: overviewScreens.find((screen) => screen.id === file.id),
70
+ fallbackSourceType: designSourceType,
71
+ content: file.content ?? "",
72
+ });
73
+ const runtimeSnapshot = runtimeProjectionEligible
74
+ ? runtimeLayerSnapshotsById[file.id]
75
+ : undefined;
76
+ const content = resolveClipboardLayerSourceHtml({
77
+ runtimeProjectionEligible,
78
+ runtimeSnapshot,
79
+ liveSnapshotHtml: liveScreenSnapshotsById[file.id]?.html,
80
+ storedContent: getScreenContent(file.id),
81
+ });
82
+ if (!content) continue;
83
+ const projection = buildCodeLayerProjection(content);
84
+ const tree = buildCodeLayerTree(projection);
85
+ for (const layerId of candidateIds) {
86
+ const node = projection.nodes.find(
87
+ (candidate) =>
88
+ candidate.id === layerId ||
89
+ candidate.dataAttributes["data-agent-native-node-id"] === layerId,
90
+ );
91
+ if (!node?.source) continue;
92
+ const html = content.slice(node.source.start, node.source.end);
93
+ const portableStyleSnapshot =
94
+ selectedElementLayerId &&
95
+ node.id === selectedElementLayerId &&
96
+ selectedElement?.portableStyleSnapshot
97
+ ? selectedElement.portableStyleSnapshot
98
+ : undefined;
99
+ snapshots.push({
100
+ html,
101
+ rootNodeId: node.dataAttributes["data-agent-native-node-id"] ?? node.id,
102
+ sourceFileId: file.id,
103
+ portableStyleSnapshot,
104
+ managedStyleSnapshot: extractDesignClipboardManagedStyles(
105
+ content,
106
+ html,
107
+ ),
108
+ node,
109
+ sourceIndex: node.source.start,
110
+ tree,
111
+ });
112
+ }
113
+ }
114
+
115
+ if (snapshots.length === 0 && activeFile && selectedElement?.selector) {
116
+ const runtimeProjectionEligible = shouldUseRuntimeLayerProjection({
117
+ screen: overviewScreens.find((screen) => screen.id === activeFile.id),
118
+ fallbackSourceType: designSourceType,
119
+ content: activeFile.content ?? "",
120
+ });
121
+ const runtimeSnapshot = runtimeProjectionEligible
122
+ ? runtimeLayerSnapshotsById[activeFile.id]
123
+ : undefined;
124
+ const content = resolveClipboardLayerSourceHtml({
125
+ runtimeProjectionEligible,
126
+ runtimeSnapshot,
127
+ liveSnapshotHtml: liveScreenSnapshotsById[activeFile.id]?.html,
128
+ storedContent: getFreshActiveContent(),
129
+ });
130
+ const projection = buildCodeLayerProjection(content);
131
+ const tree = buildCodeLayerTree(projection);
132
+ const node = resolveCodeLayerNodeFromElementInfo(
133
+ projection,
134
+ selectedElement,
135
+ );
136
+ const html = node?.source
137
+ ? content.slice(node.source.start, node.source.end)
138
+ : getElementOuterHtml(content, selectedElement.selector);
139
+ if (html && node) {
140
+ snapshots.push({
141
+ html,
142
+ rootNodeId:
143
+ node.dataAttributes["data-agent-native-node-id"] ??
144
+ selectedElement.sourceId ??
145
+ selectedElement.id,
146
+ sourceFileId: activeFile.id,
147
+ portableStyleSnapshot: selectedElement.portableStyleSnapshot,
148
+ managedStyleSnapshot: extractDesignClipboardManagedStyles(
149
+ content,
150
+ html,
151
+ ),
152
+ node,
153
+ sourceIndex: node.source?.start ?? Number.MAX_SAFE_INTEGER,
154
+ tree,
155
+ });
156
+ }
157
+ }
158
+
159
+ const selectedKeys = new Set(
160
+ snapshots.map((snapshot) => `${snapshot.sourceFileId}:${snapshot.node.id}`),
161
+ );
162
+ const topLevelSnapshots = snapshots.filter(
163
+ (snapshot) =>
164
+ !collectCodeLayerAncestors(snapshot.tree, snapshot.node.id).some(
165
+ (ancestorId) =>
166
+ selectedKeys.has(`${snapshot.sourceFileId}:${ancestorId}`),
167
+ ),
168
+ );
169
+ const fileOrder = new Map(files.map((file, index) => [file.id, index]));
170
+ return topLevelSnapshots.sort((a, b) => {
171
+ const fileDelta =
172
+ (fileOrder.get(a.sourceFileId) ?? 0) -
173
+ (fileOrder.get(b.sourceFileId) ?? 0);
174
+ if (fileDelta !== 0) return fileDelta;
175
+ return a.sourceIndex - b.sourceIndex;
176
+ });
177
+ }