@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,132 @@
1
+ import { callAction } from "@agent-native/core/client/hooks";
2
+ import type { Dispatch, RefObject, SetStateAction } from "react";
3
+ import { toast } from "sonner";
4
+
5
+ import { sanitizeLocalhostSourceSnapshotHtml } from "@/components/design/design-canvas/external-preview";
6
+ import type { LocalhostWriteConsentPayload } from "@/components/design/LocalhostWriteConsentDialog";
7
+ import {
8
+ NO_LOCALHOST_CONNECTION_MESSAGE,
9
+ NO_LOCALHOST_WRITE_CONTENT_MESSAGE,
10
+ NO_LOCALHOST_WRITE_PATH_MESSAGE,
11
+ } from "@/pages/design-editor/editor-constants";
12
+ import { resolveLocalhostSourceWriteContent } from "@/pages/design-editor/editor-state";
13
+
14
+ export interface ApplyToSourceArgs {
15
+ activeLocalhostConnectionId: string;
16
+ activeLocalhostRelPath: string | undefined;
17
+ activeLocalhostSourceSnapshotHtml: string | undefined;
18
+ canEditDesign: boolean;
19
+ id: string | undefined;
20
+ latestActiveContentRef: RefObject<string | null>;
21
+ requestLocalhostWrite: (opts: {
22
+ files: string[];
23
+ onGranted: LocalhostWriteConsentPayload["onGranted"];
24
+ onCancel?: () => void;
25
+ }) => void;
26
+ setApplyToSourcePending: Dispatch<SetStateAction<boolean>>;
27
+ stripEditorOnlyAttributes: (html: string) => string;
28
+ t: (key: string, options?: Record<string, unknown>) => string;
29
+ }
30
+
31
+ export function runApplyToSource({
32
+ activeLocalhostConnectionId,
33
+ activeLocalhostRelPath,
34
+ activeLocalhostSourceSnapshotHtml,
35
+ canEditDesign,
36
+ id,
37
+ latestActiveContentRef,
38
+ requestLocalhostWrite,
39
+ setApplyToSourcePending,
40
+ stripEditorOnlyAttributes,
41
+ t,
42
+ }: ApplyToSourceArgs) {
43
+ if (!id || !canEditDesign) return;
44
+ // VE7: surface why "Apply to source" can't proceed instead of silently
45
+ // no-oping when the connection or the resolved source path is missing.
46
+ if (!activeLocalhostConnectionId) {
47
+ toast.error(NO_LOCALHOST_CONNECTION_MESSAGE);
48
+ return;
49
+ }
50
+ if (!activeLocalhostRelPath) {
51
+ toast.error(NO_LOCALHOST_WRITE_PATH_MESSAGE);
52
+ return;
53
+ }
54
+ const relPath = activeLocalhostRelPath;
55
+ const connectionId = activeLocalhostConnectionId;
56
+ // URL-backed Design files persist the route URL in `content`; HTML source
57
+ // comes from the authenticated snapshot model populated by DesignCanvas.
58
+ // Fail closed until that source exists so this button can never serialize
59
+ // a localhost URL and overwrite the user's actual file with it.
60
+ const ext = (relPath.match(/\.[^.]+$/) ?? [])[0]?.toLowerCase() ?? "";
61
+ const rawContent = resolveLocalhostSourceWriteContent({
62
+ extension: ext,
63
+ persistedContent: latestActiveContentRef.current,
64
+ liveSnapshotHtml: activeLocalhostSourceSnapshotHtml,
65
+ });
66
+ if (!rawContent) {
67
+ toast.error(NO_LOCALHOST_WRITE_CONTENT_MESSAGE);
68
+ return;
69
+ }
70
+ // Strip editor-only attributes before writing so the on-disk file stays
71
+ // clean. Only strip for HTML routes; CSS files have no DOM attributes.
72
+ const content =
73
+ ext === ".html" || ext === ".htm"
74
+ ? stripEditorOnlyAttributes(
75
+ sanitizeLocalhostSourceSnapshotHtml(rawContent),
76
+ )
77
+ : rawContent;
78
+
79
+ requestLocalhostWrite({
80
+ files: [relPath],
81
+ onGranted: () => {
82
+ void (async () => {
83
+ setApplyToSourcePending(true);
84
+ try {
85
+ // Read the file's current versionHash immediately before writing
86
+ // so write-local-file can reject a concurrent on-disk edit. Never
87
+ // downgrade to an unguarded overwrite when the read fails: the
88
+ // semantic React path is agent-driven, and this legacy whole-file
89
+ // path is intentionally limited to directly writable HTML/CSS.
90
+ const readResult = (await callAction(
91
+ "read-local-file",
92
+ {
93
+ designId: id,
94
+ connectionId,
95
+ path: relPath,
96
+ },
97
+ { method: "GET" },
98
+ )) as { versionHash?: string } | undefined;
99
+ const expectedVersionHash = readResult?.versionHash;
100
+ if (!expectedVersionHash) {
101
+ throw new Error(
102
+ `Could not verify the current version of ${relPath}; no source was written.`,
103
+ );
104
+ }
105
+
106
+ await callAction("write-local-file", {
107
+ designId: id,
108
+ connectionId,
109
+ relPath,
110
+ content,
111
+ expectedVersionHash,
112
+ });
113
+ toast.success(t("designEditor.appliedToSource", { path: relPath }));
114
+ } catch (err) {
115
+ const message = err instanceof Error ? err.message : String(err);
116
+ if (message.includes("version conflict")) {
117
+ toast.error(
118
+ t("designEditor.applyToSourceConflict", { path: relPath }),
119
+ );
120
+ } else {
121
+ toast.error(t("designEditor.applyToSourceError", { message }));
122
+ }
123
+ } finally {
124
+ setApplyToSourcePending(false);
125
+ }
126
+ })();
127
+ },
128
+ onCancel: () => {
129
+ setApplyToSourcePending(false);
130
+ },
131
+ });
132
+ }
@@ -0,0 +1,117 @@
1
+ import type { CodeLayerNode, CodeLayerTreeNode } from "@shared/code-layer";
2
+
3
+ import type { LayersPanelMoveIntent } from "@/components/design/LayersPanel";
4
+ import type { EffectiveCodeLayerState } from "@/pages/design-editor/code-layer-state";
5
+ import { collectCodeLayerAncestors } from "@/pages/design-editor/code-layer-state";
6
+ import type { DesignFile } from "@/pages/design-editor/types";
7
+
8
+ export interface CanMoveLayerArgs {
9
+ codeLayerOwnerByNodeId: Map<
10
+ string,
11
+ {
12
+ fileId: string;
13
+ node: CodeLayerNode;
14
+ tree: CodeLayerTreeNode[];
15
+ runtimeOnly: boolean;
16
+ }
17
+ >;
18
+ effectiveCodeLayerState: EffectiveCodeLayerState;
19
+ files: DesignFile[];
20
+ lockedLayerIds: Set<string>;
21
+ visualScreenFileIds: Set<string>;
22
+ }
23
+
24
+ export function runCanMoveLayer(
25
+ {
26
+ codeLayerOwnerByNodeId,
27
+ effectiveCodeLayerState,
28
+ files,
29
+ lockedLayerIds,
30
+ visualScreenFileIds,
31
+ }: CanMoveLayerArgs,
32
+ intent: LayersPanelMoveIntent,
33
+ ) {
34
+ const targetOwner = codeLayerOwnerByNodeId.get(intent.targetId);
35
+ // L19: a file/screen row is a valid drop target too — dropping a layer
36
+ // "on" a screen row (no code-layer owner, since it isn't a DOM node)
37
+ // appends it into that screen's body via moveNodeBetweenDocuments.
38
+ const targetFile = !targetOwner
39
+ ? files.find((file) => file.id === intent.targetId)
40
+ : undefined;
41
+ const draggedScreenIds = intent.draggedIds.filter((draggedId) =>
42
+ visualScreenFileIds.has(draggedId),
43
+ );
44
+ if (draggedScreenIds.length > 0) {
45
+ return Boolean(
46
+ draggedScreenIds.length === intent.draggedIds.length &&
47
+ targetFile &&
48
+ visualScreenFileIds.has(targetFile.id) &&
49
+ intent.placement !== "inside" &&
50
+ draggedScreenIds.every(
51
+ (screenId) =>
52
+ screenId !== targetFile.id && !lockedLayerIds.has(screenId),
53
+ ),
54
+ );
55
+ }
56
+ // L8: a locked or hidden row can still be used as a before/after/inside
57
+ // drop ANCHOR — locking/hiding a layer shouldn't make it impossible to
58
+ // position other layers relative to it in the panel. Only dragging
59
+ // (see per-draggedId checks below) stays gated by locked/hidden.
60
+ if (!targetOwner && !targetFile) {
61
+ return false;
62
+ }
63
+ const runtimeDraggedOwners = intent.draggedIds.map((draggedId) =>
64
+ codeLayerOwnerByNodeId.get(draggedId),
65
+ );
66
+ const hasRuntimeDraggedOwner = runtimeDraggedOwners.some(
67
+ (owner) => owner?.runtimeOnly,
68
+ );
69
+ if (targetOwner?.runtimeOnly || hasRuntimeDraggedOwner) {
70
+ if (!targetOwner?.runtimeOnly || intent.draggedIds.length !== 1) {
71
+ return false;
72
+ }
73
+ const draggedId = intent.draggedIds[0]!;
74
+ const draggedOwner = runtimeDraggedOwners[0];
75
+ return Boolean(
76
+ draggedOwner?.runtimeOnly &&
77
+ draggedId !== intent.targetId &&
78
+ draggedOwner.fileId === targetOwner.fileId &&
79
+ !effectiveCodeLayerState.lockedIds.has(draggedId) &&
80
+ !collectCodeLayerAncestors(targetOwner.tree, intent.targetId).includes(
81
+ draggedId,
82
+ ),
83
+ );
84
+ }
85
+ return intent.draggedIds.some((draggedId) => {
86
+ const draggedOwner = codeLayerOwnerByNodeId.get(draggedId);
87
+ if (
88
+ draggedId === intent.targetId ||
89
+ !draggedOwner ||
90
+ draggedOwner.runtimeOnly ||
91
+ // L8: dragging a LOCKED row is still blocked (locked means
92
+ // don't-touch-this-layer). A HIDDEN row is now draggable — hidden
93
+ // only means "not rendered in canvas", not "structurally frozen".
94
+ effectiveCodeLayerState.lockedIds.has(draggedId)
95
+ ) {
96
+ return false;
97
+ }
98
+ if (targetFile) {
99
+ // Dropping directly onto a screen row: any non-locked DOM layer
100
+ // from any file can be appended into that screen's body, EXCEPT a
101
+ // node that's already a top-level (parentless) child of that same
102
+ // screen and would end up in the same place — still allow it
103
+ // through here; handleLayerMove no-ops that case cheaply.
104
+ return true;
105
+ }
106
+ // Same-file move: also exclude ancestor drags (would orphan the node).
107
+ if (targetOwner && draggedOwner.fileId === targetOwner.fileId) {
108
+ return !collectCodeLayerAncestors(
109
+ targetOwner.tree,
110
+ intent.targetId,
111
+ ).includes(draggedId);
112
+ }
113
+ // Cross-file move: allowed as long as the dragged side isn't locked
114
+ // (already checked above). File-row ids are excluded by the owner check.
115
+ return true;
116
+ });
117
+ }
@@ -0,0 +1,212 @@
1
+ import type {
2
+ CodeLayerNode,
3
+ CodeLayerTreeNode,
4
+ MoveNodeEditIntent,
5
+ } from "@shared/code-layer";
6
+ import {
7
+ applyVisualEdit,
8
+ buildCodeLayerProjection,
9
+ buildCodeLayerTree,
10
+ } from "@shared/code-layer";
11
+ import type { Dispatch, RefObject, SetStateAction } from "react";
12
+
13
+ import type { ElementInfo } from "@/components/design/types";
14
+ import type { ClipboardContentMutationPublication } from "@/lib/clipboard-content-lineage";
15
+ import {
16
+ elementInfoFromCodeLayerNode,
17
+ findCodeLayerSiblingOrder,
18
+ } from "@/pages/design-editor/code-layer-state";
19
+ import type { DesignFile } from "@/pages/design-editor/types";
20
+
21
+ export interface ChangeSelectedZIndexArgs {
22
+ activeFile: DesignFile;
23
+ applyLocalContentUpdate: (
24
+ nextContent: string,
25
+ options?: {
26
+ refreshPreview?: boolean;
27
+ skipPreview?: boolean;
28
+ forcePreviewFullDocument?: boolean;
29
+ immediateSave?: boolean;
30
+ persist?: boolean;
31
+ recordHistory?: boolean;
32
+ historyBeforeContent?: string;
33
+ updatedAt?: string;
34
+ clipboardMutation?: ClipboardContentMutationPublication;
35
+ },
36
+ ) => void;
37
+ canEditDesign: boolean;
38
+ codeLayerOwnerByNodeIdRef: RefObject<
39
+ Map<
40
+ string,
41
+ {
42
+ fileId: string;
43
+ node: CodeLayerNode;
44
+ tree: CodeLayerTreeNode[];
45
+ runtimeOnly: boolean;
46
+ }
47
+ >
48
+ >;
49
+ commitVisualStyles: (
50
+ selector: string,
51
+ styles: Record<string, string>,
52
+ options?: {
53
+ runtimeApplied?: boolean;
54
+ elementInfo?: ElementInfo;
55
+ originalStyles?: Record<string, string>;
56
+ },
57
+ ) => void;
58
+ getFreshActiveContent: () => string;
59
+ selectedElement: ElementInfo | null;
60
+ selectedLayerIdsState: string[];
61
+ setSelectedElement: Dispatch<SetStateAction<ElementInfo | null>>;
62
+ }
63
+
64
+ export function runChangeSelectedZIndex(
65
+ {
66
+ activeFile,
67
+ applyLocalContentUpdate,
68
+ canEditDesign,
69
+ codeLayerOwnerByNodeIdRef,
70
+ commitVisualStyles,
71
+ getFreshActiveContent,
72
+ selectedElement,
73
+ selectedLayerIdsState,
74
+ setSelectedElement,
75
+ }: ChangeSelectedZIndexArgs,
76
+ mode: "forward" | "front" | "backward" | "back",
77
+ ) {
78
+ if (!canEditDesign) return;
79
+ const selector = selectedElement?.selector;
80
+ if (!selector) return;
81
+ const currentSelectedElement = selectedElement;
82
+
83
+ const zIndexFallback = () => {
84
+ const current = Number.parseInt(
85
+ currentSelectedElement.computedStyles.zIndex || "0",
86
+ 10,
87
+ );
88
+ const base = Number.isFinite(current) ? current : 0;
89
+ const next =
90
+ mode === "front"
91
+ ? 999
92
+ : mode === "back"
93
+ ? 0
94
+ : mode === "forward"
95
+ ? base + 1
96
+ : Math.max(0, base - 1);
97
+ commitVisualStyles(selector, {
98
+ position:
99
+ currentSelectedElement.computedStyles.position === "static"
100
+ ? "relative"
101
+ : currentSelectedElement.computedStyles.position || "relative",
102
+ zIndex: String(next),
103
+ });
104
+ };
105
+
106
+ const targetId =
107
+ selectedLayerIdsState.length === 1 ? selectedLayerIdsState[0] : undefined;
108
+ if (!targetId || !activeFile) {
109
+ zIndexFallback();
110
+ return;
111
+ }
112
+ const owner = codeLayerOwnerByNodeIdRef.current.get(targetId);
113
+ if (!owner || owner.fileId !== activeFile.id) {
114
+ zIndexFallback();
115
+ return;
116
+ }
117
+ const baseContent = getFreshActiveContent();
118
+ const tree = buildCodeLayerTree(buildCodeLayerProjection(baseContent));
119
+ const siblingOrder = findCodeLayerSiblingOrder(tree, targetId);
120
+ if (!siblingOrder || siblingOrder.siblingIds.length < 2) {
121
+ // Nothing to reorder against (only child, or unresolved) — z-index
122
+ // is the only lever left.
123
+ zIndexFallback();
124
+ return;
125
+ }
126
+ const { siblingIds, index, parentId } = siblingOrder;
127
+ const lastIndex = siblingIds.length - 1;
128
+
129
+ // Already at the requested end of the stack — no-op.
130
+ if (
131
+ (mode === "front" && index === lastIndex) ||
132
+ (mode === "back" && index === 0) ||
133
+ (mode === "forward" && index === lastIndex) ||
134
+ (mode === "backward" && index === 0)
135
+ ) {
136
+ return;
137
+ }
138
+
139
+ let editIntent: MoveNodeEditIntent | null = null;
140
+ if (mode === "forward") {
141
+ // Move DOM-after the next sibling (paints one step higher).
142
+ const nextSiblingId = siblingIds[index + 1];
143
+ if (nextSiblingId) {
144
+ editIntent = {
145
+ kind: "moveNode",
146
+ target: { nodeId: targetId },
147
+ anchor: { nodeId: nextSiblingId },
148
+ placement: "after",
149
+ };
150
+ }
151
+ } else if (mode === "backward") {
152
+ // Move DOM-before the previous sibling (paints one step lower).
153
+ const prevSiblingId = siblingIds[index - 1];
154
+ if (prevSiblingId) {
155
+ editIntent = {
156
+ kind: "moveNode",
157
+ target: { nodeId: targetId },
158
+ anchor: { nodeId: prevSiblingId },
159
+ placement: "before",
160
+ };
161
+ }
162
+ } else if (mode === "front") {
163
+ if (parentId) {
164
+ editIntent = {
165
+ kind: "moveNode",
166
+ target: { nodeId: targetId },
167
+ anchor: { nodeId: parentId },
168
+ placement: "inside",
169
+ };
170
+ } else {
171
+ const lastSiblingId = siblingIds[lastIndex];
172
+ if (lastSiblingId) {
173
+ editIntent = {
174
+ kind: "moveNode",
175
+ target: { nodeId: targetId },
176
+ anchor: { nodeId: lastSiblingId },
177
+ placement: "after",
178
+ };
179
+ }
180
+ }
181
+ } else {
182
+ const firstSiblingId = siblingIds[0];
183
+ if (firstSiblingId) {
184
+ editIntent = {
185
+ kind: "moveNode",
186
+ target: { nodeId: targetId },
187
+ anchor: { nodeId: firstSiblingId },
188
+ placement: "before",
189
+ };
190
+ }
191
+ }
192
+
193
+ if (!editIntent) {
194
+ zIndexFallback();
195
+ return;
196
+ }
197
+
198
+ const patch = applyVisualEdit(baseContent, editIntent);
199
+ if (patch.result.status !== "applied") {
200
+ zIndexFallback();
201
+ return;
202
+ }
203
+ applyLocalContentUpdate(patch.content, { skipPreview: true });
204
+ const movedNode = patch.projection.nodes.find(
205
+ (n) =>
206
+ n.dataAttributes["data-agent-native-node-id"] === targetId ||
207
+ n.id === targetId,
208
+ );
209
+ if (movedNode) {
210
+ setSelectedElement(elementInfoFromCodeLayerNode(movedNode));
211
+ }
212
+ }
@@ -0,0 +1,176 @@
1
+ import { buildCodeLayerProjection } from "@shared/code-layer";
2
+ import type { Dispatch, RefObject, SetStateAction } from "react";
3
+
4
+ import type { ElementInfo } from "@/components/design/types";
5
+ import type { ClipboardContentMutationPublication } from "@/lib/clipboard-content-lineage";
6
+ import type {
7
+ EffectiveCodeLayerState,
8
+ SelectedLayerTarget,
9
+ } from "@/pages/design-editor/code-layer-state";
10
+ import {
11
+ bridgeSourceIdForCodeLayerNode,
12
+ canonicalElementInfoForCodeLayerNode,
13
+ codeLayerNodeMatchesBridgeTarget,
14
+ preferredCodeLayerSelector,
15
+ } from "@/pages/design-editor/code-layer-state";
16
+ import type { ResponsiveEditScope } from "@/pages/design-editor/command-types";
17
+ import {
18
+ applyRelativeDeltaToStyleValue,
19
+ getFreshActiveFileContent,
20
+ } from "@/pages/design-editor/editor-state";
21
+ import { applyScopedVisualStyleEdit } from "@/pages/design-editor/pending-edits";
22
+ import type { DesignFile } from "@/pages/design-editor/types";
23
+
24
+ export interface CommitRelativeStyleDeltaToSelectedLayersArgs {
25
+ activeBreakpointUpperBoundPx: number | null;
26
+ activeBreakpointWidthStateRef: RefObject<number | undefined>;
27
+ activeContent: string;
28
+ activeFile: DesignFile;
29
+ applyFileContentUpdate: (
30
+ fileId: string,
31
+ nextContent: string,
32
+ options?: {
33
+ refreshPreview?: boolean;
34
+ skipPreview?: boolean;
35
+ forcePreviewFullDocument?: boolean;
36
+ persist?: boolean;
37
+ recordHistory?: boolean;
38
+ updatedAt?: string;
39
+ clipboardMutation?: ClipboardContentMutationPublication;
40
+ },
41
+ ) => void;
42
+ canEditDesign: boolean;
43
+ effectiveCodeLayerStateRef: RefObject<EffectiveCodeLayerState>;
44
+ getScreenContent: (screenId: string) => string;
45
+ lastLocalContentRef: RefObject<string | null>;
46
+ latestActiveContentRef: RefObject<string | null>;
47
+ responsiveEditScopeRef: RefObject<ResponsiveEditScope>;
48
+ selectedLayerTargetsRef: RefObject<SelectedLayerTarget[]>;
49
+ setSelectedElement: Dispatch<SetStateAction<ElementInfo | null>>;
50
+ }
51
+
52
+ export function runCommitRelativeStyleDeltaToSelectedLayers(
53
+ {
54
+ activeBreakpointUpperBoundPx,
55
+ activeBreakpointWidthStateRef,
56
+ activeContent,
57
+ activeFile,
58
+ applyFileContentUpdate,
59
+ canEditDesign,
60
+ effectiveCodeLayerStateRef,
61
+ getScreenContent,
62
+ lastLocalContentRef,
63
+ latestActiveContentRef,
64
+ responsiveEditScopeRef,
65
+ selectedLayerTargetsRef,
66
+ setSelectedElement,
67
+ }: CommitRelativeStyleDeltaToSelectedLayersArgs,
68
+ property: string,
69
+ delta: number,
70
+ ) {
71
+ if (!canEditDesign) return false;
72
+ const effectiveLayerState = effectiveCodeLayerStateRef.current;
73
+ const targets = selectedLayerTargetsRef.current.filter(
74
+ (target) =>
75
+ !effectiveLayerState.lockedIds.has(target.fileId) &&
76
+ !effectiveLayerState.hiddenIds.has(target.fileId) &&
77
+ !effectiveLayerState.lockedIds.has(target.layerId) &&
78
+ !effectiveLayerState.hiddenIds.has(target.layerId),
79
+ );
80
+ if (targets.length <= 1) return false;
81
+
82
+ const targetsByFile = new Map<string, SelectedLayerTarget[]>();
83
+ targets.forEach((target) => {
84
+ targetsByFile.set(target.fileId, [
85
+ ...(targetsByFile.get(target.fileId) ?? []),
86
+ target,
87
+ ]);
88
+ });
89
+
90
+ let appliedAny = false;
91
+ const appliedValueByLayerId = new Map<string, string>();
92
+ targetsByFile.forEach((fileTargets, fileId) => {
93
+ const baseContent =
94
+ fileId === activeFile?.id
95
+ ? getFreshActiveFileContent({
96
+ activeContent,
97
+ latestContent: latestActiveContentRef.current,
98
+ lastLocalContent: lastLocalContentRef.current,
99
+ })
100
+ : getScreenContent(fileId);
101
+ if (!baseContent) return;
102
+ let nextContent = baseContent;
103
+ let projection = buildCodeLayerProjection(nextContent);
104
+ fileTargets.forEach((target) => {
105
+ const nextValue = applyRelativeDeltaToStyleValue(
106
+ target.elementInfo.computedStyles[
107
+ property as keyof typeof target.elementInfo.computedStyles
108
+ ] as string | undefined,
109
+ delta,
110
+ );
111
+ if (nextValue === null) return;
112
+ const sourceId = bridgeSourceIdForCodeLayerNode(target.node);
113
+ const selector = preferredCodeLayerSelector(target.node);
114
+ const node =
115
+ projection.nodes.find((candidate) =>
116
+ codeLayerNodeMatchesBridgeTarget(candidate, selector, sourceId),
117
+ ) ??
118
+ projection.nodes.find((candidate) => candidate.id === target.node.id);
119
+ if (!node) return;
120
+ // §6.4 — relative-delta commits (mixed-value arrow steps) route
121
+ // through the same breakpoint scoping as absolute commits.
122
+ const patch = applyScopedVisualStyleEdit({
123
+ content: nextContent,
124
+ target: { nodeId: node.id },
125
+ property,
126
+ value: nextValue,
127
+ upperBoundPx: activeBreakpointUpperBoundPx,
128
+ lowerBoundPx:
129
+ responsiveEditScopeRef.current === "only"
130
+ ? activeBreakpointWidthStateRef.current
131
+ : null,
132
+ });
133
+ if (patch.result.status !== "applied") return;
134
+ nextContent = patch.content;
135
+ projection = patch.projection;
136
+ appliedValueByLayerId.set(target.layerId, nextValue);
137
+ });
138
+ if (nextContent === baseContent) return;
139
+ appliedAny = true;
140
+ // Same flash-free full-document routing (and same persist caveat) as
141
+ // commitStylesToSelectedLayers above.
142
+ applyFileContentUpdate(fileId, nextContent, {
143
+ forcePreviewFullDocument: fileId === activeFile?.id,
144
+ });
145
+ });
146
+
147
+ if (appliedAny) {
148
+ const primaryTarget = targets[targets.length - 1];
149
+ const primaryValue = primaryTarget
150
+ ? appliedValueByLayerId.get(primaryTarget.layerId)
151
+ : undefined;
152
+ if (primaryTarget && primaryValue !== undefined) {
153
+ setSelectedElement((previous) => {
154
+ const previousMatches =
155
+ previous &&
156
+ codeLayerNodeMatchesBridgeTarget(
157
+ primaryTarget.node,
158
+ previous.selector,
159
+ previous.sourceId ?? previous.id,
160
+ );
161
+ const base = previousMatches
162
+ ? canonicalElementInfoForCodeLayerNode(previous, primaryTarget.node)
163
+ : primaryTarget.elementInfo;
164
+ return {
165
+ ...base,
166
+ computedStyles: {
167
+ ...base.computedStyles,
168
+ [property]: primaryValue,
169
+ },
170
+ };
171
+ });
172
+ }
173
+ }
174
+
175
+ return appliedAny;
176
+ }