@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,710 @@
1
+ import type { CodeLayerProjection } from "@shared/code-layer";
2
+ import { buildCodeLayerProjection } from "@shared/code-layer";
3
+ import { assertDesignHtmlEditIntegrity } from "@shared/html-integrity";
4
+ import type { InteractionState } from "@shared/interaction-states";
5
+ import { isRunningAppSourceType } from "@shared/source-mode";
6
+ import type { Dispatch, RefObject, SetStateAction } from "react";
7
+ import { toast } from "sonner";
8
+ import * as Y from "yjs";
9
+
10
+ import { trace } from "@/components/design/design-trace";
11
+ import {
12
+ isShaderWriteInFlight,
13
+ waitForShaderWriteToSettle,
14
+ } from "@/components/design/inspector/GlslShaderPanel";
15
+ import type { ElementInfo } from "@/components/design/types";
16
+ import {
17
+ bridgeSourceIdForCodeLayerNode,
18
+ codeLayerPatchMessage,
19
+ codeLayerSelectorAliases,
20
+ codeLayerSelectorMatches,
21
+ elementInfoIsRuntimeOnly,
22
+ ensureGoogleFontLinkInHtml,
23
+ isClientRenderedMountShell,
24
+ preferredCodeLayerSelector,
25
+ resolveCodeLayerTargetFromBridge,
26
+ resolveCodeLayerTargetFromElementInfo,
27
+ } from "@/pages/design-editor/code-layer-state";
28
+ import type {
29
+ LiveScreenSnapshot,
30
+ PatchProofState,
31
+ ResponsiveEditScope,
32
+ } from "@/pages/design-editor/command-types";
33
+ import { clearAutoTextColorMarkerOnExplicitColorCommit } from "@/pages/design-editor/cross-screen-text-color";
34
+ import {
35
+ LOCAL_EDIT_ORIGIN,
36
+ TAB_ID,
37
+ } from "@/pages/design-editor/editor-session";
38
+ import type { PreviewContentReplaceResult } from "@/pages/design-editor/editor-state";
39
+ import {
40
+ isStandaloneHttpUrl,
41
+ previewContentReplaceNeedsRenderFallback,
42
+ shouldReplacePreviewAfterVisualStyleCommit,
43
+ } from "@/pages/design-editor/editor-state";
44
+ import type {
45
+ ContentHistoryChange,
46
+ ContentHistoryEntry,
47
+ } from "@/pages/design-editor/history";
48
+ import {
49
+ applyScopedVisualStyleEdit,
50
+ replayPendingVisualStyleRuntimePatch,
51
+ resolveVisualStyleCommitContent,
52
+ } from "@/pages/design-editor/pending-edits";
53
+ import { designSaveErrorMessage } from "@/pages/design-editor/save-failure";
54
+ import { applyInlineStylesToHtml } from "@/pages/design-editor/screen-command-utils";
55
+ import type { DesignFile } from "@/pages/design-editor/types";
56
+
57
+ export interface CommitVisualStylesArgs {
58
+ activeBreakpointUpperBoundPx: number | null;
59
+ activeBreakpointWidthStateRef: RefObject<number | undefined>;
60
+ activeCanvasSourceType: "inline" | "localhost" | "fusion";
61
+ activeCodeLayerProjection: CodeLayerProjection;
62
+ activeContent: string;
63
+ activeFile: DesignFile;
64
+ activeProjectionContent: string;
65
+ canEditDesign: boolean;
66
+ commitVisualStyles: (
67
+ selector: string,
68
+ styles: Record<string, string>,
69
+ options?: {
70
+ runtimeApplied?: boolean;
71
+ elementInfo?: ElementInfo;
72
+ originalStyles?: Record<string, string>;
73
+ },
74
+ ) => void;
75
+ isSynced: boolean;
76
+ lastDuplicateTransformRef: RefObject<{
77
+ rootNodeIds: string[];
78
+ dx: number;
79
+ dy: number;
80
+ } | null>;
81
+ lastLocalContentRef: RefObject<string | null>;
82
+ latestActiveContentRef: RefObject<string | null>;
83
+ liveScreenSnapshotsById: Record<string, LiveScreenSnapshot>;
84
+ queueFileContentSave: (
85
+ fileId: string,
86
+ content: string,
87
+ options?: { syncCollab?: boolean; immediate?: boolean },
88
+ ) => void;
89
+ recordContentHistoryEntry: (entry: ContentHistoryEntry) => void;
90
+ recordLocalContentHistoryChangeFallback: (
91
+ change: ContentHistoryChange,
92
+ ) => void;
93
+ recordLocalContentHistoryEntry: (change: ContentHistoryChange) => void;
94
+ recordPendingVisualStyleEdit: (
95
+ screenId: string,
96
+ selector: string,
97
+ styles: Record<string, string>,
98
+ elementInfo?: ElementInfo,
99
+ metadata?: {
100
+ originalStyles?: Record<string, string>;
101
+ interactionState?: InteractionState;
102
+ },
103
+ ) => void;
104
+ replacePreviewContent: (
105
+ nextContent: string,
106
+ selector?: string | null,
107
+ options?: { forceFullDocument?: boolean },
108
+ ) => PreviewContentReplaceResult;
109
+ responsiveEditScopeRef: RefObject<ResponsiveEditScope>;
110
+ selectedElement: ElementInfo | null;
111
+ setCollabContent: Dispatch<SetStateAction<string | null>>;
112
+ setCollabContentFileId: Dispatch<SetStateAction<string | null>>;
113
+ setContentRenderRevision: Dispatch<SetStateAction<number>>;
114
+ setPatchProof: Dispatch<SetStateAction<PatchProofState | null>>;
115
+ setSelectedElement: Dispatch<SetStateAction<ElementInfo | null>>;
116
+ setSelectedLayerIdsState: Dispatch<SetStateAction<string[]>>;
117
+ suppressContentHistoryRef: RefObject<boolean>;
118
+ t: (key: string, options?: Record<string, unknown>) => string;
119
+ undoManagerRef: RefObject<Y.UndoManager | null>;
120
+ updateLiveScreenSnapshotContent: (
121
+ screenId: string,
122
+ html: string,
123
+ options?: { recordHistory?: boolean },
124
+ ) => boolean;
125
+ upsertMotionKeyframesFromStyles: (
126
+ styles: Record<string, string>,
127
+ elementInfo?: ElementInfo,
128
+ selector?: string,
129
+ ) => void;
130
+ viewModeRef: RefObject<"single" | "overview">;
131
+ ydoc: Y.Doc | null;
132
+ }
133
+
134
+ export function runCommitVisualStyles(
135
+ {
136
+ activeBreakpointUpperBoundPx,
137
+ activeBreakpointWidthStateRef,
138
+ activeCanvasSourceType,
139
+ activeCodeLayerProjection,
140
+ activeContent,
141
+ activeFile,
142
+ activeProjectionContent,
143
+ canEditDesign,
144
+ commitVisualStyles,
145
+ isSynced,
146
+ lastDuplicateTransformRef,
147
+ lastLocalContentRef,
148
+ latestActiveContentRef,
149
+ liveScreenSnapshotsById,
150
+ queueFileContentSave,
151
+ recordContentHistoryEntry,
152
+ recordLocalContentHistoryChangeFallback,
153
+ recordLocalContentHistoryEntry,
154
+ recordPendingVisualStyleEdit,
155
+ replacePreviewContent,
156
+ responsiveEditScopeRef,
157
+ selectedElement,
158
+ setCollabContent,
159
+ setCollabContentFileId,
160
+ setContentRenderRevision,
161
+ setPatchProof,
162
+ setSelectedElement,
163
+ setSelectedLayerIdsState,
164
+ suppressContentHistoryRef,
165
+ t,
166
+ undoManagerRef,
167
+ updateLiveScreenSnapshotContent,
168
+ upsertMotionKeyframesFromStyles,
169
+ viewModeRef,
170
+ ydoc,
171
+ }: CommitVisualStylesArgs,
172
+ selector: string,
173
+ styles: Record<string, string>,
174
+ options: {
175
+ runtimeApplied?: boolean;
176
+ elementInfo?: ElementInfo;
177
+ /** Pre-gesture values, for the pending-edit revert stack. */
178
+ originalStyles?: Record<string, string>;
179
+ } = {},
180
+ ) {
181
+ trace("persist", "commit-styles", {
182
+ selector: typeof selector === "string" ? selector : null,
183
+ props: Object.keys(styles ?? {}),
184
+ });
185
+ if (!activeFile || !canEditDesign) return;
186
+ // Cross-pipeline write race guard (see GlslShaderPanel.tsx's module doc
187
+ // comment on withShaderWriteLock/waitForShaderWriteToSettle): a shader
188
+ // apply/remove/knob-commit for this same file goes through a completely
189
+ // separate round trip (read-source-file -> apply-source-edit) than this
190
+ // function's own commit, and both eventually rewrite the SAME Y.Doc —
191
+ // one via a server-side diff, this one via a synchronous, untracked
192
+ // full-document ydoc.transact rewrite below. Racing the two produces a
193
+ // corrupted, doubled document (verified). The common case (no shader
194
+ // write in flight for this file) stays fully synchronous — only defer
195
+ // when isShaderWriteInFlight is actually true, so this never adds a
196
+ // microtask tick to the hot path or breaks the same-tick multi-property
197
+ // composition the baseContent comment below depends on.
198
+ if (isShaderWriteInFlight(activeFile.id)) {
199
+ void waitForShaderWriteToSettle(activeFile.id).then(() => {
200
+ commitVisualStyles(selector, styles, options);
201
+ });
202
+ return;
203
+ }
204
+ const entries = Object.entries(styles).filter(
205
+ ([, value]) => value !== undefined,
206
+ );
207
+ if (entries.length === 0) return;
208
+ upsertMotionKeyframesFromStyles(styles, options.elementInfo, selector);
209
+ // §gesture-persistence — a localhost screen's source of truth is the
210
+ // running app's own files, which this client cannot write. Everything
211
+ // below patches the design's STORED html, which for such a screen is
212
+ // only the bridged route URL, so an inspector commit updated the model
213
+ // and the undo stack while the running app kept rendering the old value
214
+ // and no pending edit was ever queued for the Apply pass. Push the value
215
+ // into the live DOM and queue it, exactly like a canvas gesture
216
+ // (handleVisualStyleChange delegates here with runtimeApplied set
217
+ // because its gesture already moved the live DOM).
218
+ if (isRunningAppSourceType(activeCanvasSourceType)) {
219
+ const targetInfo = options.elementInfo ?? selectedElement ?? undefined;
220
+ // Breakpoint-scoped writes are excluded for the same reason as the
221
+ // base path below (Item 5, edit-flash): the agent persists them as a
222
+ // width-scoped class or an `@media` rule, which an inline style would
223
+ // preview wrong.
224
+ if (
225
+ !options.runtimeApplied &&
226
+ activeBreakpointUpperBoundPx == null &&
227
+ typeof (window as any).__designCanvasSendStyleForScreen === "function"
228
+ ) {
229
+ replayPendingVisualStyleRuntimePatch(
230
+ {
231
+ screenId: activeFile.id,
232
+ selector,
233
+ sourceId: targetInfo?.runtimeSourceId ?? targetInfo?.sourceId ?? null,
234
+ styles: Object.fromEntries(entries),
235
+ },
236
+ (window as any).__designCanvasSendStyleForScreen,
237
+ );
238
+ }
239
+ recordPendingVisualStyleEdit(activeFile.id, selector, styles, targetInfo, {
240
+ originalStyles: options.originalStyles,
241
+ });
242
+ return;
243
+ }
244
+ // Base every patch off the freshest known content, not the closed-over
245
+ // render value. Handlers that fire several onStyleChange calls in one
246
+ // synchronous user action (e.g. fixed-size text → width+height+whiteSpace,
247
+ // constraints center → both axes, linked padding → 4 sides) would
248
+ // otherwise each read the same pre-render `activeContent` and clobber one
249
+ // another, so only the last property survived in the saved HTML. Since we
250
+ // advance lastLocalContentRef.current to resolvedNextContent below, the
251
+ // next synchronous call reads the previous call's result and the patches
252
+ // compose. Falls back to activeContent when the ref is unset (file switch).
253
+ const activeLiveSnapshot = activeFile
254
+ ? liveScreenSnapshotsById[activeFile.id]
255
+ : undefined;
256
+ const baseContent =
257
+ activeLiveSnapshot?.html ??
258
+ latestActiveContentRef.current ??
259
+ lastLocalContentRef.current ??
260
+ activeContent;
261
+ // A localhost screen's stored content IS its route URL, so with no
262
+ // snapshot yet the chain above yields that URL string. Projecting it gives
263
+ // a 3-node document where nothing resolves: a snapshot that has not
264
+ // arrived is not an empty document.
265
+ if (isStandaloneHttpUrl(baseContent)) {
266
+ toast.error(t("designEditor.patchProof.snapshotNotLoaded"), {
267
+ duration: 4000,
268
+ });
269
+ return;
270
+ }
271
+ const [firstProperty, firstValue] = entries[0];
272
+ // PF12: reuse the already-built activeCodeLayerProjection when its
273
+ // source content is exactly the content this commit is about to patch
274
+ // (the common case — no pending live snapshot/local-edit divergence).
275
+ // Style commits fire on every slider/color-picker drag tick, so
276
+ // skipping a redundant full-document reparse here matters.
277
+ const projection =
278
+ baseContent === activeProjectionContent
279
+ ? activeCodeLayerProjection
280
+ : buildCodeLayerProjection(baseContent);
281
+ const targetInfo = options.elementInfo ?? selectedElement;
282
+ const targetResolution = targetInfo
283
+ ? resolveCodeLayerTargetFromElementInfo(projection, targetInfo)
284
+ : resolveCodeLayerTargetFromBridge(projection, selector);
285
+ const targetNode =
286
+ targetResolution.status === "resolved" ? targetResolution.node : null;
287
+ const sendStyleChange = (window as any).__designCanvasSendStyle;
288
+ // Item 5 (edit-flash): a breakpoint-scoped commit
289
+ // (activeBreakpointUpperBoundPx set) never persists as a plain inline
290
+ // style — planBreakpointStyleWrite below turns it into a width-scoped
291
+ // Tailwind class or an `@media` rule in the managed breakpoints <style>
292
+ // block. sendStyleChange only knows how to patch the live element's
293
+ // INLINE style, which unconditionally beats any `@media` rule's
294
+ // specificity. Applying it here would preview the wrong
295
+ // (inline-style-overridden) value immediately, then visibly flash to the
296
+ // correct cascaded value once the next full document patch/reload catches
297
+ // up — so skip the runtime shortcut entirely for breakpoint-scoped writes
298
+ // and fall through to the full content patch path below, which reflects
299
+ // the actual persisted class/`@media` result.
300
+ const runtimeStyleApplied =
301
+ !options.runtimeApplied &&
302
+ activeBreakpointUpperBoundPx == null &&
303
+ typeof sendStyleChange === "function";
304
+ // Shared by both terminal paths below so neither drifts into previewing a
305
+ // different element than the other.
306
+ const sendRuntimeStylePreview = (): void => {
307
+ if (!runtimeStyleApplied) return;
308
+ // A stamped id goes stale the moment React re-creates the node, so send
309
+ // the bridge-minted identities as fallbacks (see
310
+ // canonicalElementInfoForCodeLayerNode).
311
+ const selectorCandidates = targetNode
312
+ ? codeLayerSelectorAliases(targetNode)
313
+ : Array.from(
314
+ new Set(
315
+ [
316
+ selector,
317
+ targetInfo?.runtimeSelector,
318
+ targetInfo?.selector,
319
+ ].filter((candidate): candidate is string => Boolean(candidate)),
320
+ ),
321
+ );
322
+ const nodeId = targetNode
323
+ ? bridgeSourceIdForCodeLayerNode(targetNode)
324
+ : (targetInfo?.runtimeSourceId ?? targetInfo?.sourceId);
325
+ entries.forEach(([property, value]) => {
326
+ sendStyleChange(selector, property, value, {
327
+ selectorCandidates,
328
+ nodeId,
329
+ });
330
+ });
331
+ };
332
+
333
+ // U7: if this style commit repositions (left/top) the node(s) most
334
+ // recently created by Cmd+D, record the delta so the next Cmd+D on that
335
+ // same selection can replay it instead of landing back in place.
336
+ if (targetNode && lastDuplicateTransformRef.current) {
337
+ const nodeId =
338
+ targetNode.dataAttributes["data-agent-native-node-id"] ?? targetNode.id;
339
+ if (lastDuplicateTransformRef.current.rootNodeIds.includes(nodeId)) {
340
+ const nextLeft = parseFloat(styles.left ?? "");
341
+ const nextTop = parseFloat(styles.top ?? "");
342
+ const prevLeft = parseFloat(targetNode.style.left ?? "");
343
+ const prevTop = parseFloat(targetNode.style.top ?? "");
344
+ if (
345
+ Number.isFinite(nextLeft) &&
346
+ Number.isFinite(nextTop) &&
347
+ Number.isFinite(prevLeft) &&
348
+ Number.isFinite(prevTop)
349
+ ) {
350
+ lastDuplicateTransformRef.current = {
351
+ ...lastDuplicateTransformRef.current,
352
+ dx: nextLeft - prevLeft,
353
+ dy: nextTop - prevTop,
354
+ };
355
+ }
356
+ }
357
+ }
358
+ const capability =
359
+ selectedElement?.editCapabilities?.find((item) =>
360
+ item.kind.startsWith("deterministic"),
361
+ ) ?? selectedElement?.editCapabilities?.[0];
362
+ const proofId =
363
+ typeof crypto !== "undefined" && "randomUUID" in crypto
364
+ ? crypto.randomUUID()
365
+ : `${Date.now()}-${Math.random().toString(16).slice(2)}`;
366
+ if (!targetNode && elementInfoIsRuntimeOnly(targetInfo)) {
367
+ // Fail LOUD (same contract as the resolveVisualStyleCommitContent
368
+ // error branch below): patch-proof state alone is too quiet for a
369
+ // user-initiated edit that will never persist.
370
+ //
371
+ // Three facts, three remedies: ambiguous needs scoping, a mount shell
372
+ // has no app markup at ANY selector, and only an authored document that
373
+ // lost the node is actually "missing".
374
+ const resolutionFailure =
375
+ targetResolution.status === "ambiguous"
376
+ ? t("designEditor.patchProof.selectorAmbiguous", {
377
+ count: targetResolution.candidates.length,
378
+ })
379
+ : isClientRenderedMountShell(projection)
380
+ ? t("designEditor.patchProof.clientRenderedShell")
381
+ : t("designEditor.patchProof.selectorMissing");
382
+ toast.error(resolutionFailure, {
383
+ duration: 4000,
384
+ });
385
+ setPatchProof({
386
+ id: proofId,
387
+ fileId: activeFile.id,
388
+ filename: activeFile.filename,
389
+ selector,
390
+ sourceId: targetInfo?.sourceId,
391
+ property:
392
+ entries.length === 1
393
+ ? firstProperty
394
+ : entries.map(([property]) => property).join(", "),
395
+ previousValue: targetInfo?.computedStyles?.[firstProperty],
396
+ nextValue:
397
+ entries.length === 1
398
+ ? firstValue
399
+ : entries
400
+ .map(([property, value]) => `${property}: ${value}`)
401
+ .join("; "),
402
+ previousContent: baseContent,
403
+ capability: "unsupported",
404
+ confidence: 0.3,
405
+ status: "failed",
406
+ error: resolutionFailure,
407
+ createdAt: Date.now(),
408
+ });
409
+ return;
410
+ }
411
+ setPatchProof({
412
+ id: proofId,
413
+ fileId: activeFile.id,
414
+ filename: activeFile.filename,
415
+ selector,
416
+ sourceId: selectedElement?.sourceId,
417
+ property:
418
+ entries.length === 1
419
+ ? firstProperty
420
+ : entries.map(([property]) => property).join(", "),
421
+ previousValue: selectedElement?.computedStyles?.[firstProperty],
422
+ nextValue:
423
+ entries.length === 1
424
+ ? firstValue
425
+ : entries
426
+ .map(([property, value]) => `${property}: ${value}`)
427
+ .join("; "),
428
+ previousContent: baseContent,
429
+ capability: capability?.kind ?? "deterministic-style-edit",
430
+ confidence: capability?.confidence ?? 0.92,
431
+ status: "runtime",
432
+ createdAt: Date.now(),
433
+ });
434
+ sendRuntimeStylePreview();
435
+
436
+ const nextContent = applyInlineStylesToHtml(baseContent, selector, {
437
+ ...Object.fromEntries(entries),
438
+ });
439
+ // §6.4 — Breakpoint-scoped editing (Framer cascade). Reuses the
440
+ // `projection` and `targetNode` resolved above for the patch-proof
441
+ // block (same baseContent). When a non-base breakpoint frame is
442
+ // active, EVERY property routes through the single class-vs-media
443
+ // decision (planBreakpointStyleWrite):
444
+ //
445
+ // - Tailwind-utility values become width-scoped responsive classes
446
+ // (`max-[<bound>px]:text-lg`), replacing any same-stem token at the
447
+ // same bound.
448
+ // guard:allow-raw-color - prose naming CSS value kinds, not a color literal
449
+ // - Raw CSS values (exact px from drags, rgb()/calc(), …) become
450
+ // managed `@media (max-width: <bound>px)` rules in the
451
+ // `<style data-agent-native-breakpoints>` block, targeting the
452
+ // element's stable node id.
453
+ //
454
+ // Base edits (no active breakpoint, or the active frame is the widest
455
+ // context) keep the plain inline-style path and cascade down to every
456
+ // narrower breakpoint unless overridden there.
457
+ const stylePatch = entries.reduce<{
458
+ content: string;
459
+ failed: string | null;
460
+ }>(
461
+ (current, [property, value]) => {
462
+ if (current.failed) return current;
463
+ const patch = applyScopedVisualStyleEdit({
464
+ content: current.content,
465
+ target: targetNode ? { nodeId: targetNode.id } : { selector },
466
+ property,
467
+ value,
468
+ upperBoundPx: activeBreakpointUpperBoundPx,
469
+ lowerBoundPx:
470
+ responsiveEditScopeRef.current === "only"
471
+ ? activeBreakpointWidthStateRef.current
472
+ : null,
473
+ });
474
+ if (patch.result.status !== "applied") {
475
+ return {
476
+ content: current.content,
477
+ failed: codeLayerPatchMessage(
478
+ patch.result.message,
479
+ t("designEditor.patchProof.selectorMissing"),
480
+ ),
481
+ };
482
+ }
483
+ return { content: patch.content, failed: null };
484
+ },
485
+ { content: baseContent, failed: null },
486
+ );
487
+ // §6.4 — the legacy selector-based inline-style fallback (nextContent)
488
+ // is a BASE write: safe when editing the base, but while a narrower
489
+ // breakpoint is active it would clobber every viewport width with a
490
+ // value the user meant to scope. Fail loud (patch-proof error) instead
491
+ // of silently widening the edit.
492
+ const commitResolution = resolveVisualStyleCommitContent({
493
+ scopedContent: stylePatch.content,
494
+ scopedFailure: stylePatch.failed,
495
+ legacyFallbackContent: nextContent,
496
+ breakpointScoped: activeBreakpointUpperBoundPx != null,
497
+ });
498
+ if ("error" in commitResolution) {
499
+ const failureMessage = codeLayerPatchMessage(
500
+ commitResolution.error,
501
+ t("designEditor.patchProof.selectorMissing"),
502
+ );
503
+ // Fail LOUD, never silently: an unresolvable commit target (e.g. an
504
+ // Alpine template-instance element with no per-instance source node)
505
+ // used to only flip the patch-proof panel to "failed" — no toast, no
506
+ // revert, while the inspector kept displaying the new value, so users
507
+ // had no idea their edit never persisted (verified on real content:
508
+ // Gap scrub on an x-for todo-card subtask row). Same toast pattern as
509
+ // handleVisualStructureChange's move failure.
510
+ toast.error(failureMessage, { duration: 4000 });
511
+ setPatchProof((prev) =>
512
+ prev?.id === proofId
513
+ ? { ...prev, status: "failed", error: failureMessage }
514
+ : prev,
515
+ );
516
+ return;
517
+ }
518
+ const resolvedNextContentBeforeFontLink = commitResolution.content;
519
+
520
+ // T16: if this commit set fontFamily to a known Google Font not
521
+ // already loaded in this screen, inject its <link> into <head>.
522
+ const fontFamilyValue = Object.fromEntries(entries).fontFamily;
523
+ const resolvedNextContentAfterFontLink = fontFamilyValue
524
+ ? ensureGoogleFontLinkInHtml(
525
+ resolvedNextContentBeforeFontLink,
526
+ fontFamilyValue,
527
+ )
528
+ : resolvedNextContentBeforeFontLink;
529
+
530
+ // Finding 2(b): an explicit "color" commit on a node that still carries
531
+ // BOARD_TEXT_AUTO_COLOR_MARKER means the user just deliberately chose a
532
+ // color — the marker no longer describes an auto-applied default and
533
+ // must not survive to mislead a later reparent/cross-screen move (see
534
+ // isStaleAutoTextColorMarker / clearAutoTextColorMarkerOnExplicitColorCommit).
535
+ const committedNodeId =
536
+ targetNode?.dataAttributes["data-agent-native-node-id"];
537
+ const resolvedNextContent =
538
+ "color" in Object.fromEntries(entries) && committedNodeId
539
+ ? clearAutoTextColorMarkerOnExplicitColorCommit(
540
+ resolvedNextContentAfterFontLink,
541
+ committedNodeId,
542
+ )
543
+ : resolvedNextContentAfterFontLink;
544
+
545
+ try {
546
+ assertDesignHtmlEditIntegrity({
547
+ previousContent: baseContent,
548
+ nextContent: resolvedNextContent,
549
+ fileType: activeFile.fileType,
550
+ });
551
+ } catch (error) {
552
+ const message = designSaveErrorMessage(error) ?? t("common.genericError");
553
+ toast.error(message, {
554
+ id: `design-source-integrity:${activeFile.id}`,
555
+ });
556
+ setPatchProof((previous) =>
557
+ previous?.id === proofId
558
+ ? { ...previous, status: "failed", error: message }
559
+ : previous,
560
+ );
561
+ return;
562
+ }
563
+
564
+ const nextProjection = buildCodeLayerProjection(resolvedNextContent);
565
+ const resolvedNode = selectedElement
566
+ ? nextProjection.nodes.find((node) => {
567
+ const aliases = codeLayerSelectorAliases(node);
568
+ return (
569
+ (selectedElement.sourceId &&
570
+ (node.id === selectedElement.sourceId ||
571
+ node.dataAttributes["data-agent-native-node-id"] ===
572
+ selectedElement.sourceId ||
573
+ node.dataAttributes["data-code-layer-id"] ===
574
+ selectedElement.sourceId ||
575
+ node.dataAttributes["data-layer-id"] ===
576
+ selectedElement.sourceId ||
577
+ node.dataAttributes["data-builder-id"] ===
578
+ selectedElement.sourceId ||
579
+ node.dataAttributes["data-loc"] === selectedElement.sourceId ||
580
+ node.attributes.id === selectedElement.sourceId)) ||
581
+ aliases.includes(selector) ||
582
+ codeLayerSelectorMatches(node, selector)
583
+ );
584
+ })
585
+ : null;
586
+ const liveSnapshotUpdated = activeLiveSnapshot
587
+ ? updateLiveScreenSnapshotContent(activeFile.id, resolvedNextContent)
588
+ : false;
589
+ if (liveSnapshotUpdated) {
590
+ setPatchProof((prev) =>
591
+ prev?.id === proofId ? { ...prev, status: "queued" } : prev,
592
+ );
593
+ if (!runtimeStyleApplied) {
594
+ setContentRenderRevision((revision) => revision + 1);
595
+ }
596
+ } else {
597
+ const yjsHistoryAvailable = Boolean(
598
+ viewModeRef.current !== "overview" &&
599
+ ydoc &&
600
+ isSynced &&
601
+ undoManagerRef.current,
602
+ );
603
+ if (
604
+ !yjsHistoryAvailable &&
605
+ !suppressContentHistoryRef.current &&
606
+ baseContent !== resolvedNextContent
607
+ ) {
608
+ const change = {
609
+ fileId: activeFile.id,
610
+ before: baseContent,
611
+ after: resolvedNextContent,
612
+ };
613
+ if (viewModeRef.current === "overview") {
614
+ recordContentHistoryEntry(change);
615
+ } else {
616
+ recordLocalContentHistoryEntry(change);
617
+ }
618
+ } else if (
619
+ yjsHistoryAvailable &&
620
+ !suppressContentHistoryRef.current &&
621
+ baseContent !== resolvedNextContent
622
+ ) {
623
+ // BUG-UNDO-RESIZE-STACK: mirror the same before/after into the local
624
+ // fallback stack that applyLocalContentUpdate already maintains for
625
+ // every other commit path (text edits, moves, structure changes).
626
+ // The Yjs UndoManager is destroyed and recreated whenever `docId`
627
+ // changes — a view-mode switch, a zoom-triggered re-render, or a
628
+ // breakpoint switch — which silently drops its entire undo stack.
629
+ // Without this mirror, a gesture-driven style/resize commit (the
630
+ // ONLY commit path that skipped this call) became permanently
631
+ // unrecoverable the moment that happened: handleUndo's um.canUndo()
632
+ // goes false with nothing to fall back to, so Cmd+Z does nothing at
633
+ // all for a resize-drag even though every other edit kind still has
634
+ // a working fallback. Only consulted once Yjs itself has nothing
635
+ // left to undo (see handleUndo), so this never causes a double-undo.
636
+ recordLocalContentHistoryChangeFallback({
637
+ fileId: activeFile.id,
638
+ before: baseContent,
639
+ after: resolvedNextContent,
640
+ });
641
+ }
642
+
643
+ setCollabContent(resolvedNextContent);
644
+ setCollabContentFileId(activeFile.id);
645
+ setPatchProof((prev) =>
646
+ prev?.id === proofId ? { ...prev, status: "queued" } : prev,
647
+ );
648
+ // Mark as our own write so the get-design reconcile + Yjs observe don't
649
+ // treat the echo as an external edit and fight the live value.
650
+ lastLocalContentRef.current = resolvedNextContent;
651
+ latestActiveContentRef.current = resolvedNextContent;
652
+ // Write the edit into the shared Y.Doc so other open clients see it live
653
+ // through Yjs (not only via the slower update-file → applyText round-trip).
654
+ // Single-screen edits use the active-file UndoManager. Overview edits are
655
+ // tracked in the global file-content stack so all screens share one order.
656
+ if (ydoc && isSynced) {
657
+ const ytext = ydoc.getText("content");
658
+ if (ytext.toString() !== resolvedNextContent) {
659
+ if (!yjsHistoryAvailable) {
660
+ // Untracked full rewrite (overview mode with a still-live
661
+ // single-mode UndoManager, or history-suppressed replay) —
662
+ // see U1 note: clear the undo stack so a stale tracked delta
663
+ // can't be replayed against content it no longer matches.
664
+ undoManagerRef.current?.clear(true, false);
665
+ }
666
+ ydoc.transact(
667
+ () => {
668
+ ytext.delete(0, ytext.length);
669
+ ytext.insert(0, resolvedNextContent);
670
+ },
671
+ yjsHistoryAvailable ? LOCAL_EDIT_ORIGIN : TAB_ID,
672
+ );
673
+ }
674
+ }
675
+ queueFileContentSave(activeFile.id, resolvedNextContent, {
676
+ syncCollab: !(ydoc && isSynced),
677
+ });
678
+ if (
679
+ shouldReplacePreviewAfterVisualStyleCommit({
680
+ runtimeApplied: options.runtimeApplied,
681
+ runtimeStyleApplied,
682
+ }) &&
683
+ previewContentReplaceNeedsRenderFallback(
684
+ replacePreviewContent(resolvedNextContent, selector),
685
+ )
686
+ ) {
687
+ setContentRenderRevision((revision) => revision + 1);
688
+ }
689
+ }
690
+ if (resolvedNode) setSelectedLayerIdsState([resolvedNode.id]);
691
+ setSelectedElement((prev) => {
692
+ if (options.elementInfo) return options.elementInfo;
693
+ if (!prev) return prev;
694
+ const stablePatch = resolvedNode
695
+ ? {
696
+ sourceId: bridgeSourceIdForCodeLayerNode(resolvedNode),
697
+ selector: preferredCodeLayerSelector(resolvedNode),
698
+ classes: resolvedNode.classes,
699
+ }
700
+ : {};
701
+ return {
702
+ ...prev,
703
+ ...stablePatch,
704
+ computedStyles: {
705
+ ...prev.computedStyles,
706
+ ...Object.fromEntries(entries),
707
+ },
708
+ };
709
+ });
710
+ }