@agent-native/core 0.90.3 → 0.90.5

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 (256) hide show
  1. package/bin/agent-native.js +25 -3
  2. package/corpus/README.md +1 -1
  3. package/corpus/core/CHANGELOG.md +17 -0
  4. package/corpus/core/bin/agent-native.js +25 -3
  5. package/corpus/core/package.json +1 -1
  6. package/corpus/core/src/agent/durable-background.ts +18 -18
  7. package/corpus/core/src/agent/engine/ai-sdk-engine.ts +8 -2
  8. package/corpus/core/src/agent/engine/anthropic-engine.ts +11 -1
  9. package/corpus/core/src/agent/engine/index.ts +1 -0
  10. package/corpus/core/src/agent/engine/registry.ts +63 -2
  11. package/corpus/core/src/agent/production-agent.ts +22 -11
  12. package/corpus/core/src/agent/run-manager.ts +17 -6
  13. package/corpus/core/src/agent/run-store.ts +16 -11
  14. package/corpus/core/src/cli/create.ts +35 -0
  15. package/corpus/core/src/cli/design-connect.ts +263 -1
  16. package/corpus/core/src/cli/index.ts +84 -7
  17. package/corpus/core/src/cli/workspacify.ts +34 -0
  18. package/corpus/core/src/client/KeepTabOpenNotice.tsx +20 -15
  19. package/corpus/core/src/client/RunStuckBanner.tsx +14 -13
  20. package/corpus/core/src/client/agent-chat-adapter.ts +28 -17
  21. package/corpus/core/src/client/history/VersionHistoryPanel.tsx +77 -47
  22. package/corpus/core/src/client/history/index.ts +3 -0
  23. package/corpus/core/src/client/history/use-history.ts +28 -0
  24. package/corpus/core/src/client/index.ts +8 -0
  25. package/corpus/core/src/client/review/index.ts +5 -0
  26. package/corpus/core/src/client/review/use-review.ts +40 -2
  27. package/corpus/core/src/client/use-run-stuck-detection.ts +8 -5
  28. package/corpus/core/src/history/actions/create-resource-version.ts +11 -8
  29. package/corpus/core/src/history/index.ts +0 -1
  30. package/corpus/core/src/history/registry.ts +24 -12
  31. package/corpus/core/src/history/store.ts +84 -53
  32. package/corpus/core/src/review/actions/consume-review-feedback.ts +15 -5
  33. package/corpus/core/src/review/actions/delete-review-comment.ts +15 -3
  34. package/corpus/core/src/review/actions/resolve-review-thread.ts +12 -5
  35. package/corpus/core/src/review/index.ts +0 -5
  36. package/corpus/core/src/review/registry.ts +24 -12
  37. package/corpus/core/src/review/store.ts +10 -7
  38. package/corpus/core/src/server/agent-chat-plugin.ts +6 -0
  39. package/corpus/core/src/server/credential-provider.ts +13 -3
  40. package/corpus/templates/assets/actions/_preset-skeleton-validation.ts +11 -11
  41. package/corpus/templates/assets/actions/generate-image.ts +64 -28
  42. package/corpus/templates/assets/app/components/generation/GenerationResults.tsx +1 -1
  43. package/corpus/templates/assets/app/routes/brand-kits.$id_.presets.$presetId.tsx +91 -74
  44. package/corpus/templates/assets/server/lib/generation.ts +29 -4
  45. package/corpus/templates/assets/server/lib/image-processing.ts +131 -0
  46. package/corpus/templates/calendar/.agents/skills/event-management/SKILL.md +24 -2
  47. package/corpus/templates/calendar/AGENTS.md +3 -0
  48. package/corpus/templates/calendar/actions/create-event.ts +8 -34
  49. package/corpus/templates/calendar/actions/event-action-helpers.ts +52 -0
  50. package/corpus/templates/calendar/actions/manage-event-draft.ts +7 -34
  51. package/corpus/templates/calendar/actions/update-event.ts +10 -23
  52. package/corpus/templates/calendar/app/components/calendar/AttendeeAutocomplete.tsx +47 -0
  53. package/corpus/templates/calendar/app/components/calendar/CreateEventDialog.tsx +23 -0
  54. package/corpus/templates/calendar/app/components/calendar/EventAttendeesSection.tsx +59 -2
  55. package/corpus/templates/calendar/app/components/calendar/EventDetailPanel.tsx +40 -1
  56. package/corpus/templates/calendar/app/components/calendar/EventDetailPopover.tsx +34 -1
  57. package/corpus/templates/calendar/app/hooks/use-events.ts +6 -0
  58. package/corpus/templates/calendar/app/i18n/zh-TW.ts +4 -0
  59. package/corpus/templates/calendar/app/i18n-data.ts +40 -0
  60. package/corpus/templates/calendar/changelog/2026-07-07-you-can-mark-event-guests-as-optional-while-inviting-them-or.md +6 -0
  61. package/corpus/templates/calendar/server/handlers/events.ts +1 -0
  62. package/corpus/templates/calendar/server/lib/google-calendar.ts +4 -0
  63. package/corpus/templates/calendar/server/plugins/agent-chat.ts +1 -1
  64. package/corpus/templates/calendar/shared/api.ts +2 -0
  65. package/corpus/templates/clips/.agents/skills/ai-video-tools/SKILL.md +29 -3
  66. package/corpus/templates/clips/actions/generate-workflow.ts +19 -8
  67. package/corpus/templates/clips/actions/get-clips-ai-prefs.ts +36 -0
  68. package/corpus/templates/clips/actions/lib/clips-ai-prefs.ts +28 -0
  69. package/corpus/templates/clips/actions/regenerate-chapters.ts +21 -7
  70. package/corpus/templates/clips/actions/regenerate-summary.ts +26 -8
  71. package/corpus/templates/clips/actions/regenerate-title.ts +44 -7
  72. package/corpus/templates/clips/actions/update-clips-ai-prefs.ts +50 -0
  73. package/corpus/templates/clips/app/components/meetings/meeting-card.tsx +7 -2
  74. package/corpus/templates/clips/app/hooks/use-auto-title.ts +25 -10
  75. package/corpus/templates/clips/app/i18n/ar-SA.ts +28 -6
  76. package/corpus/templates/clips/app/i18n/de-DE.ts +28 -6
  77. package/corpus/templates/clips/app/i18n/en-US.ts +29 -6
  78. package/corpus/templates/clips/app/i18n/es-ES.ts +29 -6
  79. package/corpus/templates/clips/app/i18n/fr-FR.ts +30 -6
  80. package/corpus/templates/clips/app/i18n/hi-IN.ts +26 -6
  81. package/corpus/templates/clips/app/i18n/ja-JP.ts +27 -6
  82. package/corpus/templates/clips/app/i18n/ko-KR.ts +28 -6
  83. package/corpus/templates/clips/app/i18n/pt-BR.ts +28 -6
  84. package/corpus/templates/clips/app/i18n/zh-CN.ts +26 -6
  85. package/corpus/templates/clips/app/i18n/zh-TW.ts +25 -6
  86. package/corpus/templates/clips/app/routes/_app.meetings._index.tsx +73 -0
  87. package/corpus/templates/clips/app/routes/_app.settings._index.tsx +1 -0
  88. package/corpus/templates/clips/app/routes/r.$recordingId.tsx +70 -2
  89. package/corpus/templates/clips/changelog/2026-07-07-ai-tools-can-include-the-full-video-not-just-the-transcript-.md +6 -0
  90. package/corpus/templates/clips/changelog/2026-07-07-clips-desktop-now-surfaces-meetings-and-dictate-directly-in-.md +6 -0
  91. package/corpus/templates/clips/changelog/2026-07-07-meeting-notes-now-explain-exactly-how-to-start-granola-style.md +6 -0
  92. package/corpus/templates/clips/changelog/2026-07-07-meeting-start-alerts-now-include-separate-start-notes-and-jo.md +6 -0
  93. package/corpus/templates/clips/changelog/2026-07-07-recordings-start-more-predictably-and-newly-saved-clips-refr.md +6 -0
  94. package/corpus/templates/clips/chrome-extension/public/manifest.json +1 -1
  95. package/corpus/templates/clips/desktop/src/app.tsx +435 -105
  96. package/corpus/templates/clips/desktop/src/lib/recorder.ts +27 -21
  97. package/corpus/templates/clips/desktop/src/overlays/meeting-notification.tsx +23 -22
  98. package/corpus/templates/clips/desktop/src/styles.css +167 -2
  99. package/corpus/templates/clips/desktop/src-tauri/src/native_screen.rs +1 -1
  100. package/corpus/templates/clips/desktop/src-tauri/src/notifications.rs +3 -7
  101. package/corpus/templates/clips/desktop/src-tauri/src/tray_meetings.rs +5 -4
  102. package/corpus/templates/clips/server/routes/_agent-native/clips/user-prefs.put.ts +16 -8
  103. package/corpus/templates/clips/shared/clips-ai-prefs.ts +81 -0
  104. package/corpus/templates/content/AGENTS.md +1 -0
  105. package/corpus/templates/content/actions/_database-source-utils.ts +73 -8
  106. package/corpus/templates/content/actions/_database-utils.ts +2 -1
  107. package/corpus/templates/content/actions/prepare-builder-source-review.ts +20 -1
  108. package/corpus/templates/content/actions/stage-builder-source-bulk-update.ts +663 -0
  109. package/corpus/templates/content/app/components/editor/DocumentProperties.tsx +33 -7
  110. package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +53 -4
  111. package/corpus/templates/content/app/components/editor/database/settings.tsx +24 -0
  112. package/corpus/templates/content/app/hooks/use-content-database.ts +70 -1
  113. package/corpus/templates/content/app/hooks/use-document-properties.ts +63 -5
  114. package/corpus/templates/content/app/i18n/zh-TW.ts +3 -0
  115. package/corpus/templates/content/app/i18n-data.ts +31 -0
  116. package/corpus/templates/content/changelog/2026-07-01-builder-source-rows-can-be-staged-in-bulk-with-a-reviewable.md +6 -0
  117. package/corpus/templates/content/changelog/2026-07-07-builder-backed-database-fields-now-edit-like-normal-content-fields.md +6 -0
  118. package/corpus/templates/content/parity/matrix.md +24 -24
  119. package/corpus/templates/content/parity/matrix.ts +2 -0
  120. package/corpus/templates/content/shared/api.ts +59 -4
  121. package/corpus/templates/design/.agents/skills/visual-edit/SKILL.md +16 -1
  122. package/corpus/templates/design/actions/add-localhost-screens.ts +1 -0
  123. package/corpus/templates/design/actions/connect-localhost.ts +26 -3
  124. package/corpus/templates/design/actions/open-visual-edit.ts +4 -37
  125. package/corpus/templates/design/app/components/design/DesignCanvas.tsx +362 -12
  126. package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +89 -14
  127. package/corpus/templates/design/app/components/design/bridge-security.ts +7 -1
  128. package/corpus/templates/design/app/components/design/multi-screen/types.ts +1 -1
  129. package/corpus/templates/design/app/i18n/zh-TW.ts +3 -0
  130. package/corpus/templates/design/app/i18n-data.ts +41 -0
  131. package/corpus/templates/design/changelog/2026-07-07-local-visual-edit-keeps-bridge-access-when-opening-previousl.md +6 -0
  132. package/corpus/templates/design/changelog/2026-07-07-local-visual-edit-now-keeps-the-live-app-connection-authenti.md +6 -0
  133. package/corpus/templates/design/changelog/2026-07-07-local-visual-edit-uses-live-app-iframes-for-click-to-edit-pr.md +6 -0
  134. package/dist/agent/durable-background.d.ts +12 -12
  135. package/dist/agent/durable-background.js +18 -18
  136. package/dist/agent/durable-background.js.map +1 -1
  137. package/dist/agent/engine/ai-sdk-engine.d.ts.map +1 -1
  138. package/dist/agent/engine/ai-sdk-engine.js +8 -2
  139. package/dist/agent/engine/ai-sdk-engine.js.map +1 -1
  140. package/dist/agent/engine/anthropic-engine.d.ts.map +1 -1
  141. package/dist/agent/engine/anthropic-engine.js +11 -1
  142. package/dist/agent/engine/anthropic-engine.js.map +1 -1
  143. package/dist/agent/engine/index.d.ts +1 -1
  144. package/dist/agent/engine/index.d.ts.map +1 -1
  145. package/dist/agent/engine/index.js +1 -1
  146. package/dist/agent/engine/index.js.map +1 -1
  147. package/dist/agent/engine/registry.d.ts +14 -0
  148. package/dist/agent/engine/registry.d.ts.map +1 -1
  149. package/dist/agent/engine/registry.js +56 -2
  150. package/dist/agent/engine/registry.js.map +1 -1
  151. package/dist/agent/production-agent.d.ts +2 -2
  152. package/dist/agent/production-agent.d.ts.map +1 -1
  153. package/dist/agent/production-agent.js +18 -11
  154. package/dist/agent/production-agent.js.map +1 -1
  155. package/dist/agent/run-manager.d.ts +7 -1
  156. package/dist/agent/run-manager.d.ts.map +1 -1
  157. package/dist/agent/run-manager.js +6 -1
  158. package/dist/agent/run-manager.js.map +1 -1
  159. package/dist/agent/run-store.d.ts +1 -1
  160. package/dist/agent/run-store.d.ts.map +1 -1
  161. package/dist/agent/run-store.js +15 -10
  162. package/dist/agent/run-store.js.map +1 -1
  163. package/dist/cli/create.d.ts.map +1 -1
  164. package/dist/cli/create.js +31 -0
  165. package/dist/cli/create.js.map +1 -1
  166. package/dist/cli/design-connect.d.ts.map +1 -1
  167. package/dist/cli/design-connect.js +197 -1
  168. package/dist/cli/design-connect.js.map +1 -1
  169. package/dist/cli/index.js +76 -9
  170. package/dist/cli/index.js.map +1 -1
  171. package/dist/cli/workspacify.d.ts.map +1 -1
  172. package/dist/cli/workspacify.js +30 -0
  173. package/dist/cli/workspacify.js.map +1 -1
  174. package/dist/client/KeepTabOpenNotice.d.ts +9 -10
  175. package/dist/client/KeepTabOpenNotice.d.ts.map +1 -1
  176. package/dist/client/KeepTabOpenNotice.js +8 -4
  177. package/dist/client/KeepTabOpenNotice.js.map +1 -1
  178. package/dist/client/RunStuckBanner.d.ts.map +1 -1
  179. package/dist/client/RunStuckBanner.js +14 -13
  180. package/dist/client/RunStuckBanner.js.map +1 -1
  181. package/dist/client/agent-chat-adapter.d.ts.map +1 -1
  182. package/dist/client/agent-chat-adapter.js +25 -17
  183. package/dist/client/agent-chat-adapter.js.map +1 -1
  184. package/dist/client/history/VersionHistoryPanel.d.ts +1 -1
  185. package/dist/client/history/VersionHistoryPanel.d.ts.map +1 -1
  186. package/dist/client/history/VersionHistoryPanel.js +12 -5
  187. package/dist/client/history/VersionHistoryPanel.js.map +1 -1
  188. package/dist/client/history/index.d.ts +1 -1
  189. package/dist/client/history/index.d.ts.map +1 -1
  190. package/dist/client/history/index.js +1 -1
  191. package/dist/client/history/index.js.map +1 -1
  192. package/dist/client/history/use-history.d.ts +12 -0
  193. package/dist/client/history/use-history.d.ts.map +1 -1
  194. package/dist/client/history/use-history.js +6 -0
  195. package/dist/client/history/use-history.js.map +1 -1
  196. package/dist/client/index.d.ts +2 -2
  197. package/dist/client/index.d.ts.map +1 -1
  198. package/dist/client/index.js +2 -2
  199. package/dist/client/index.js.map +1 -1
  200. package/dist/client/review/index.d.ts +1 -1
  201. package/dist/client/review/index.d.ts.map +1 -1
  202. package/dist/client/review/index.js +1 -1
  203. package/dist/client/review/index.js.map +1 -1
  204. package/dist/client/review/use-review.d.ts +23 -0
  205. package/dist/client/review/use-review.d.ts.map +1 -1
  206. package/dist/client/review/use-review.js +8 -0
  207. package/dist/client/review/use-review.js.map +1 -1
  208. package/dist/client/use-run-stuck-detection.d.ts +1 -1
  209. package/dist/client/use-run-stuck-detection.d.ts.map +1 -1
  210. package/dist/client/use-run-stuck-detection.js +6 -4
  211. package/dist/client/use-run-stuck-detection.js.map +1 -1
  212. package/dist/collab/struct-routes.d.ts +1 -1
  213. package/dist/file-upload/actions/upload-image.d.ts +1 -1
  214. package/dist/history/actions/create-resource-version.js +7 -4
  215. package/dist/history/actions/create-resource-version.js.map +1 -1
  216. package/dist/history/index.d.ts +1 -1
  217. package/dist/history/index.d.ts.map +1 -1
  218. package/dist/history/index.js +1 -1
  219. package/dist/history/index.js.map +1 -1
  220. package/dist/history/registry.d.ts.map +1 -1
  221. package/dist/history/registry.js +14 -12
  222. package/dist/history/registry.js.map +1 -1
  223. package/dist/history/store.d.ts.map +1 -1
  224. package/dist/history/store.js +74 -49
  225. package/dist/history/store.js.map +1 -1
  226. package/dist/notifications/routes.d.ts +1 -1
  227. package/dist/resources/handlers.d.ts +3 -3
  228. package/dist/review/actions/consume-review-feedback.d.ts +1 -0
  229. package/dist/review/actions/consume-review-feedback.js +8 -2
  230. package/dist/review/actions/consume-review-feedback.js.map +1 -1
  231. package/dist/review/actions/delete-review-comment.d.ts +2 -1
  232. package/dist/review/actions/delete-review-comment.js +11 -3
  233. package/dist/review/actions/delete-review-comment.js.map +1 -1
  234. package/dist/review/actions/resolve-review-thread.d.ts +2 -1
  235. package/dist/review/actions/resolve-review-thread.js +5 -2
  236. package/dist/review/actions/resolve-review-thread.js.map +1 -1
  237. package/dist/review/index.d.ts +1 -1
  238. package/dist/review/index.d.ts.map +1 -1
  239. package/dist/review/index.js +1 -1
  240. package/dist/review/index.js.map +1 -1
  241. package/dist/review/registry.d.ts.map +1 -1
  242. package/dist/review/registry.js +14 -12
  243. package/dist/review/registry.js.map +1 -1
  244. package/dist/review/store.d.ts +3 -3
  245. package/dist/review/store.d.ts.map +1 -1
  246. package/dist/review/store.js +7 -4
  247. package/dist/review/store.js.map +1 -1
  248. package/dist/server/agent-chat-plugin.d.ts.map +1 -1
  249. package/dist/server/agent-chat-plugin.js +5 -0
  250. package/dist/server/agent-chat-plugin.js.map +1 -1
  251. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  252. package/dist/server/credential-provider.d.ts +2 -0
  253. package/dist/server/credential-provider.d.ts.map +1 -1
  254. package/dist/server/credential-provider.js +13 -3
  255. package/dist/server/credential-provider.js.map +1 -1
  256. package/package.json +1 -1
@@ -1,9 +1,6 @@
1
- import crypto from "node:crypto";
2
-
3
1
  import { defineAction, embedApp } from "@agent-native/core";
4
2
  import { writeAppState } from "@agent-native/core/application-state";
5
3
  import { buildDeepLink } from "@agent-native/core/server";
6
- import { getRequestUserEmail } from "@agent-native/core/server/request-context";
7
4
  import { eq } from "drizzle-orm";
8
5
  import { z } from "zod";
9
6
 
@@ -89,28 +86,6 @@ function isLoopbackUrl(value: string): boolean {
89
86
  );
90
87
  }
91
88
 
92
- /**
93
- * Derive a stable per-user connection id for a devServerUrl + rootPath pair.
94
- *
95
- * The owner email is embedded in the hash so two users with an identical
96
- * devServerUrl + rootPath (common with devcontainers/codespaces images) derive
97
- * DIFFERENT ids and never collide on the shared primary key. Connections
98
- * created before user scoping keep their old ids; those users simply
99
- * reconnect fresh under the new per-user id.
100
- */
101
- function stableConnectionId(
102
- devServerUrl: string,
103
- rootPath: string | undefined,
104
- ownerEmail: string,
105
- ) {
106
- const hash = crypto
107
- .createHash("sha256")
108
- .update(`${ownerEmail}\n${devServerUrl}\n${rootPath ?? ""}`)
109
- .digest("base64url")
110
- .slice(0, 16);
111
- return `localhost_${hash}`;
112
- }
113
-
114
89
  function designOverviewDeepLink(designId: string): string {
115
90
  return buildDeepLink({
116
91
  app: "design",
@@ -254,19 +229,11 @@ export default defineAction({
254
229
  }) ?? [],
255
230
  generatedAt: new Date().toISOString(),
256
231
  };
257
- let connectionId = args.connectionId;
258
- if (!connectionId) {
259
- const ownerEmail = getRequestUserEmail();
260
- if (!ownerEmail) throw new Error("no authenticated user");
261
- connectionId = stableConnectionId(
262
- devServerUrl,
263
- args.rootPath,
264
- ownerEmail,
265
- );
266
- }
267
-
268
232
  const connection = await connectLocalhostAction.run({
269
- id: connectionId,
233
+ // Let connect-localhost be the single source of truth for stable
234
+ // per-user/per-org id derivation. Duplicating it here can create a second
235
+ // tokenless row after the CLI self-registers the bridge token.
236
+ id: args.connectionId,
270
237
  name: args.name,
271
238
  devServerUrl,
272
239
  bridgeUrl: args.bridgeUrl,
@@ -351,6 +351,7 @@ interface DesignCanvasProps {
351
351
  * For `"inline"` and `"localhost"` sources this prop is ignored.
352
352
  */
353
353
  fusionUrl?: string;
354
+ bridgeToken?: string;
354
355
  zoom: number;
355
356
  onZoomChange?: (zoom: number) => void;
356
357
  deviceFrame: DeviceFrameType;
@@ -371,6 +372,28 @@ interface DesignCanvasProps {
371
372
  */
372
373
  runtimeReplacementContent?: string;
373
374
  runtimeReplacementKey?: string;
375
+ styleRevertRequest?: {
376
+ requestId: number;
377
+ patches: Array<{
378
+ selector: string;
379
+ sourceId?: string | null;
380
+ styles: Record<string, string>;
381
+ }>;
382
+ } | null;
383
+ styleBaselineResetRequest?: number | null;
384
+ textRevertRequest?: {
385
+ requestId: number;
386
+ patches: Array<{
387
+ selector: string;
388
+ sourceId?: string | null;
389
+ value: string;
390
+ html?: string;
391
+ }>;
392
+ } | null;
393
+ structureAckRequest?: {
394
+ requestId: number;
395
+ acks: Array<{ requestId: string; applied: boolean }>;
396
+ } | null;
374
397
  embeddedFrameBackground?: string;
375
398
  transparentBackground?: boolean;
376
399
  editorChromeScaleX?: number;
@@ -390,12 +413,17 @@ interface DesignCanvasProps {
390
413
  selector: string,
391
414
  styles: Record<string, string>,
392
415
  info?: ElementInfo,
416
+ metadata?: { originalStyles?: Record<string, string> },
393
417
  ) => void;
394
418
  onTextContentChange?: (
395
419
  selector: string,
396
420
  value: string,
397
421
  info?: ElementInfo,
398
- details?: { html?: string },
422
+ details?: {
423
+ html?: string;
424
+ originalValue?: string;
425
+ originalHtml?: string;
426
+ },
399
427
  ) => void;
400
428
  onTextEditingStateChange?: (state: {
401
429
  active: boolean;
@@ -420,7 +448,7 @@ interface DesignCanvasProps {
420
448
  sourceRect?: { x: number; y: number; width: number; height: number };
421
449
  anchorRect?: { x: number; y: number; width: number; height: number };
422
450
  },
423
- ) => boolean | void;
451
+ ) => boolean | "pending" | void;
424
452
  onVisualDuplicateChange?: (
425
453
  selector: string,
426
454
  cloneHtml: string,
@@ -837,6 +865,87 @@ function snapshotEndpointUrl(bridgeUrl: string, previewUrl: string): string {
837
865
  return endpoint.toString();
838
866
  }
839
867
 
868
+ export function liveEditEndpointUrl(
869
+ bridgeUrl: string,
870
+ previewUrl: string,
871
+ options?: { includeEditorBridge?: boolean },
872
+ ): string {
873
+ const endpoint = new URL("/live-edit", bridgeUrl);
874
+ endpoint.searchParams.set("url", previewUrl);
875
+ if (options?.includeEditorBridge === false) {
876
+ endpoint.searchParams.set("bridge", "0");
877
+ }
878
+ return endpoint.toString();
879
+ }
880
+
881
+ function originFromUrl(value: string | undefined): string | null {
882
+ if (!value) return null;
883
+ try {
884
+ return new URL(value).origin;
885
+ } catch {
886
+ return null;
887
+ }
888
+ }
889
+
890
+ function buildEditorChromeBridgeScript(args: {
891
+ readOnly: boolean;
892
+ editMode: boolean;
893
+ editorChromeScaleX: number;
894
+ editorChromeScaleY: number;
895
+ screenId: string;
896
+ boardSurface: boolean;
897
+ }) {
898
+ return (
899
+ createEditorBridgeThemeScript(readEditorBridgeThemeVars()) +
900
+ EDITOR_CHROME_BRIDGE_SCRIPT.replace(
901
+ "__READ_ONLY__",
902
+ args.readOnly ? "true" : "false",
903
+ )
904
+ .replace("__TEXT_EDITING_ENABLED__", args.editMode ? "true" : "false")
905
+ .replace("__EDITOR_CHROME_SCALE_X__", String(args.editorChromeScaleX))
906
+ .replace("__EDITOR_CHROME_SCALE_Y__", String(args.editorChromeScaleY))
907
+ .replace("__DESIGN_CANVAS_SCREEN_ID__", JSON.stringify(args.screenId))
908
+ .replace(
909
+ "__DESIGN_CANVAS_BOARD_SURFACE__",
910
+ args.boardSurface ? "true" : "false",
911
+ )
912
+ );
913
+ }
914
+
915
+ function contentHash(value: string): string {
916
+ let hash = 0;
917
+ for (let index = 0; index < value.length; index += 1) {
918
+ hash = (hash * 31 + value.charCodeAt(index)) | 0;
919
+ }
920
+ return `${value.length}:${hash >>> 0}`;
921
+ }
922
+
923
+ function isPlainRecord(value: unknown): value is Record<string, unknown> {
924
+ return Boolean(value && typeof value === "object" && !Array.isArray(value));
925
+ }
926
+
927
+ function isFiniteRect(value: unknown): value is ElementInfo["boundingRect"] {
928
+ if (!isPlainRecord(value)) return false;
929
+ return (
930
+ Number.isFinite(value.x) &&
931
+ Number.isFinite(value.y) &&
932
+ Number.isFinite(value.width) &&
933
+ Number.isFinite(value.height)
934
+ );
935
+ }
936
+
937
+ export function isElementInfoPayload(value: unknown): value is ElementInfo {
938
+ if (!isPlainRecord(value)) return false;
939
+ return (
940
+ typeof value.tagName === "string" &&
941
+ Array.isArray(value.classes) &&
942
+ isPlainRecord(value.computedStyles) &&
943
+ isFiniteRect(value.boundingRect) &&
944
+ typeof value.isFlexChild === "boolean" &&
945
+ typeof value.isFlexContainer === "boolean"
946
+ );
947
+ }
948
+
840
949
  /**
841
950
  * Exponential backoff delay (ms) for the localhost bridge snapshot auto-retry
842
951
  * loop, keyed by the zero-based retry attempt number (0 = first retry after
@@ -945,6 +1054,7 @@ export function DesignCanvas({
945
1054
  externalSnapshotHtml,
946
1055
  onExternalContentSnapshot,
947
1056
  fusionUrl,
1057
+ bridgeToken,
948
1058
  zoom,
949
1059
  onZoomChange,
950
1060
  deviceFrame,
@@ -952,6 +1062,10 @@ export function DesignCanvas({
952
1062
  boardSurface = false,
953
1063
  runtimeReplacementContent,
954
1064
  runtimeReplacementKey,
1065
+ styleRevertRequest,
1066
+ styleBaselineResetRequest,
1067
+ textRevertRequest,
1068
+ structureAckRequest,
955
1069
  embeddedFrameBackground,
956
1070
  transparentBackground = false,
957
1071
  editorChromeScaleX = 1,
@@ -1082,6 +1196,8 @@ export function DesignCanvas({
1082
1196
  status: "loading" | "error";
1083
1197
  message?: string;
1084
1198
  } | null>(null);
1199
+ const [registeredLiveEditBridgeKey, setRegisteredLiveEditBridgeKey] =
1200
+ useState<string | null>(null);
1085
1201
  const [externalSnapshotRetryNonce, setExternalSnapshotRetryNonce] =
1086
1202
  useState(0);
1087
1203
  // Exponential backoff for the auto-retry loop below: 1.5s → 3s → 6s →
@@ -1116,20 +1232,83 @@ export function DesignCanvas({
1116
1232
  (fetchedExternalSnapshot?.url === rawExternalPreviewUrl
1117
1233
  ? fetchedExternalSnapshot.html
1118
1234
  : undefined);
1119
- const requiresEditableExternalSnapshot =
1235
+ const editorChromeBridgeForCurrentState = useMemo(
1236
+ () =>
1237
+ buildEditorChromeBridgeScript({
1238
+ readOnly,
1239
+ editMode,
1240
+ editorChromeScaleX: effectiveEditorChromeScaleX,
1241
+ editorChromeScaleY: effectiveEditorChromeScaleY,
1242
+ screenId: screenId ?? contentKey ?? "",
1243
+ boardSurface,
1244
+ }),
1245
+ [
1246
+ boardSurface,
1247
+ contentKey,
1248
+ editMode,
1249
+ effectiveEditorChromeScaleX,
1250
+ effectiveEditorChromeScaleY,
1251
+ readOnly,
1252
+ screenId,
1253
+ ],
1254
+ );
1255
+ const embeddedWheelBridgeForCurrentState = useMemo(
1256
+ () =>
1257
+ EMBEDDED_WHEEL_BRIDGE_SCRIPT.replace(
1258
+ "__EMBEDDED_WHEEL_FORWARDING_ENABLED__",
1259
+ isEmbeddedFrame ? "true" : "false",
1260
+ ),
1261
+ [isEmbeddedFrame],
1262
+ );
1263
+ const liveEditBridgeScript = useMemo(
1264
+ () =>
1265
+ MOTION_PREVIEW_BRIDGE_SCRIPT +
1266
+ SHADER_FILL_PREVIEW_BRIDGE_SCRIPT +
1267
+ TWEAK_BRIDGE_SCRIPT +
1268
+ ZOOM_BRIDGE_SCRIPT +
1269
+ LIGHTWEIGHT_HIT_TEST_BRIDGE_SCRIPT +
1270
+ embeddedWheelBridgeForCurrentState +
1271
+ editorChromeBridgeForCurrentState,
1272
+ [editorChromeBridgeForCurrentState, embeddedWheelBridgeForCurrentState],
1273
+ );
1274
+ const liveEditBridgeKey = useMemo(
1275
+ () => contentHash(liveEditBridgeScript),
1276
+ [liveEditBridgeScript],
1277
+ );
1278
+ const hasLiveEditExternalFrame =
1279
+ sourceType === "localhost" && Boolean(bridgeUrl && rawExternalPreviewUrl);
1280
+ const usesLiveEditEditorBridge =
1120
1281
  sourceType === "localhost" &&
1121
1282
  Boolean(bridgeUrl && rawExternalPreviewUrl) &&
1122
1283
  !interactMode &&
1123
1284
  !readOnly;
1285
+ const liveEditBridgeRegistered =
1286
+ usesLiveEditEditorBridge &&
1287
+ registeredLiveEditBridgeKey === liveEditBridgeKey;
1288
+ const requiresEditableExternalSnapshot = false;
1289
+ const liveEditExternalPreviewUrl =
1290
+ hasLiveEditExternalFrame &&
1291
+ bridgeUrl &&
1292
+ rawExternalPreviewUrl &&
1293
+ interactMode
1294
+ ? liveEditEndpointUrl(bridgeUrl, rawExternalPreviewUrl, {
1295
+ includeEditorBridge: false,
1296
+ })
1297
+ : liveEditBridgeRegistered && bridgeUrl && rawExternalPreviewUrl
1298
+ ? liveEditEndpointUrl(bridgeUrl, rawExternalPreviewUrl)
1299
+ : null;
1124
1300
  const iframeRenderContent =
1125
1301
  activeExternalSnapshotHtml ??
1126
1302
  (requiresEditableExternalSnapshot ? "" : renderedContent);
1127
1303
  const externalPreviewUrl =
1128
- activeExternalSnapshotHtml || requiresEditableExternalSnapshot
1304
+ liveEditExternalPreviewUrl ??
1305
+ (activeExternalSnapshotHtml || requiresEditableExternalSnapshot
1129
1306
  ? null
1130
- : rawExternalPreviewUrl;
1307
+ : rawExternalPreviewUrl);
1131
1308
  const waitingForEditableExternalSnapshot =
1132
1309
  requiresEditableExternalSnapshot && !activeExternalSnapshotHtml;
1310
+ const waitingForLiveEditBridge =
1311
+ usesLiveEditEditorBridge && !liveEditBridgeRegistered;
1133
1312
  zoomRef.current = zoom;
1134
1313
  runtimeReplacementContentRef.current = runtimeReplacementContent;
1135
1314
  runtimeReplacementKeyRef.current = runtimeReplacementKey;
@@ -1138,9 +1317,61 @@ export function DesignCanvas({
1138
1317
  onExternalContentSnapshotRef.current = onExternalContentSnapshot;
1139
1318
  }, [onExternalContentSnapshot]);
1140
1319
 
1320
+ useEffect(() => {
1321
+ if (!usesLiveEditEditorBridge || !bridgeUrl) {
1322
+ setRegisteredLiveEditBridgeKey(null);
1323
+ return;
1324
+ }
1325
+ let cancelled = false;
1326
+ setRegisteredLiveEditBridgeKey((current) =>
1327
+ current === liveEditBridgeKey ? current : null,
1328
+ );
1329
+ void (async () => {
1330
+ try {
1331
+ const endpoint = new URL("/live-edit-bridge", bridgeUrl).toString();
1332
+ const response = await fetch(endpoint, {
1333
+ method: "POST",
1334
+ headers: {
1335
+ "content-type": "application/json",
1336
+ ...(bridgeToken ? { "x-bridge-token": bridgeToken } : {}),
1337
+ },
1338
+ body: JSON.stringify({ script: liveEditBridgeScript }),
1339
+ });
1340
+ if (!response.ok) {
1341
+ throw new Error(`Bridge registration failed (${response.status})`);
1342
+ }
1343
+ if (!cancelled) {
1344
+ setRegisteredLiveEditBridgeKey(liveEditBridgeKey);
1345
+ }
1346
+ } catch (error) {
1347
+ if (!cancelled) {
1348
+ console.warn(
1349
+ "[DesignCanvas] live edit bridge registration failed",
1350
+ error,
1351
+ );
1352
+ setRegisteredLiveEditBridgeKey(null);
1353
+ }
1354
+ }
1355
+ })();
1356
+ return () => {
1357
+ cancelled = true;
1358
+ };
1359
+ }, [
1360
+ bridgeUrl,
1361
+ bridgeToken,
1362
+ liveEditBridgeKey,
1363
+ liveEditBridgeScript,
1364
+ usesLiveEditEditorBridge,
1365
+ ]);
1366
+
1141
1367
  useEffect(() => {
1142
1368
  const previewUrl = rawExternalPreviewUrl;
1143
- if (sourceType !== "localhost" || !bridgeUrl || !previewUrl) {
1369
+ if (
1370
+ !requiresEditableExternalSnapshot ||
1371
+ sourceType !== "localhost" ||
1372
+ !bridgeUrl ||
1373
+ !previewUrl
1374
+ ) {
1144
1375
  snapshotRetryAttemptRef.current = 0;
1145
1376
  setFetchedExternalSnapshot((current) =>
1146
1377
  current?.url === previewUrl ? current : null,
@@ -1468,6 +1699,12 @@ export function DesignCanvas({
1468
1699
  origin: e.origin,
1469
1700
  iframeWindow,
1470
1701
  parentOrigin: window.location.origin,
1702
+ allowedOrigins:
1703
+ sourceType === "localhost" && bridgeUrl
1704
+ ? [originFromUrl(bridgeUrl)].filter(
1705
+ (origin): origin is string => Boolean(origin),
1706
+ )
1707
+ : [],
1471
1708
  });
1472
1709
  if (!trusted) {
1473
1710
  return;
@@ -1509,8 +1746,19 @@ export function DesignCanvas({
1509
1746
  e.data.styles && typeof e.data.styles === "object"
1510
1747
  ? (e.data.styles as Record<string, string>)
1511
1748
  : {};
1749
+ const originalStyles =
1750
+ e.data.originalStyles && typeof e.data.originalStyles === "object"
1751
+ ? (e.data.originalStyles as Record<string, string>)
1752
+ : undefined;
1512
1753
  if (selector && Object.keys(styles).length > 0) {
1513
- onVisualStyleChange?.(selector, styles, e.data.payload);
1754
+ onVisualStyleChange?.(
1755
+ selector,
1756
+ styles,
1757
+ isElementInfoPayload(e.data.payload) ? e.data.payload : undefined,
1758
+ {
1759
+ originalStyles,
1760
+ },
1761
+ );
1514
1762
  }
1515
1763
  return;
1516
1764
  }
@@ -1528,8 +1776,20 @@ export function DesignCanvas({
1528
1776
  const value = String(e.data.value ?? "");
1529
1777
  const html =
1530
1778
  typeof e.data.html === "string" ? String(e.data.html) : undefined;
1779
+ const originalValue =
1780
+ typeof e.data.originalValue === "string"
1781
+ ? String(e.data.originalValue)
1782
+ : undefined;
1783
+ const originalHtml =
1784
+ typeof e.data.originalHtml === "string"
1785
+ ? String(e.data.originalHtml)
1786
+ : undefined;
1531
1787
  if (selector) {
1532
- onTextContentChange?.(selector, value, e.data.payload, { html });
1788
+ onTextContentChange?.(selector, value, e.data.payload, {
1789
+ html,
1790
+ originalValue,
1791
+ originalHtml,
1792
+ });
1533
1793
  }
1534
1794
  return;
1535
1795
  }
@@ -1599,7 +1859,7 @@ export function DesignCanvas({
1599
1859
  anchorRect,
1600
1860
  },
1601
1861
  );
1602
- if (requestId) {
1862
+ if (requestId && applied !== "pending") {
1603
1863
  iframeRef.current?.contentWindow?.postMessage(
1604
1864
  {
1605
1865
  type: "visual-structure-ack",
@@ -1828,6 +2088,7 @@ export function DesignCanvas({
1828
2088
  onComponentSourceJump,
1829
2089
  isEmbeddedFrame,
1830
2090
  sourceType,
2091
+ bridgeUrl,
1831
2092
  fusionUrl,
1832
2093
  flushPendingOneShotMessages,
1833
2094
  ]);
@@ -2159,6 +2420,89 @@ export function DesignCanvas({
2159
2420
  [postOneShotBridgeMessage],
2160
2421
  );
2161
2422
 
2423
+ const lastStyleRevertRequestIdRef = useRef<number | null>(null);
2424
+ useEffect(() => {
2425
+ if (!styleRevertRequest) return;
2426
+ if (lastStyleRevertRequestIdRef.current === styleRevertRequest.requestId) {
2427
+ return;
2428
+ }
2429
+ lastStyleRevertRequestIdRef.current = styleRevertRequest.requestId;
2430
+ for (const patch of styleRevertRequest.patches) {
2431
+ const selectorCandidates = [
2432
+ patch.selector,
2433
+ patch.sourceId
2434
+ ? `[data-agent-native-node-id="${String(patch.sourceId).replace(/"/g, '\\"')}"]`
2435
+ : "",
2436
+ ].filter(Boolean);
2437
+ for (const [property, value] of Object.entries(patch.styles)) {
2438
+ postOneShotBridgeMessage({
2439
+ type: "style-change",
2440
+ selector: patch.selector,
2441
+ property,
2442
+ value,
2443
+ selectorCandidates,
2444
+ nodeId: patch.sourceId ?? "",
2445
+ });
2446
+ }
2447
+ }
2448
+ }, [postOneShotBridgeMessage, styleRevertRequest]);
2449
+
2450
+ const lastStyleBaselineResetRequestRef = useRef<number | null>(null);
2451
+ useEffect(() => {
2452
+ if (styleBaselineResetRequest == null) return;
2453
+ if (
2454
+ lastStyleBaselineResetRequestRef.current === styleBaselineResetRequest
2455
+ ) {
2456
+ return;
2457
+ }
2458
+ lastStyleBaselineResetRequestRef.current = styleBaselineResetRequest;
2459
+ postOneShotBridgeMessage({
2460
+ type: "agent-native:reset-live-visual-edit-baselines",
2461
+ });
2462
+ }, [postOneShotBridgeMessage, styleBaselineResetRequest]);
2463
+
2464
+ const lastTextRevertRequestIdRef = useRef<number | null>(null);
2465
+ useEffect(() => {
2466
+ if (!textRevertRequest) return;
2467
+ if (lastTextRevertRequestIdRef.current === textRevertRequest.requestId) {
2468
+ return;
2469
+ }
2470
+ lastTextRevertRequestIdRef.current = textRevertRequest.requestId;
2471
+ for (const patch of textRevertRequest.patches) {
2472
+ const selectorCandidates = [
2473
+ patch.selector,
2474
+ patch.sourceId
2475
+ ? `[data-agent-native-node-id="${String(patch.sourceId).replace(/"/g, '\\"')}"]`
2476
+ : "",
2477
+ ].filter(Boolean);
2478
+ postOneShotBridgeMessage({
2479
+ type: "set-text-content",
2480
+ selector: patch.selector,
2481
+ selectorCandidates,
2482
+ value: patch.value,
2483
+ html: patch.html,
2484
+ });
2485
+ }
2486
+ }, [postOneShotBridgeMessage, textRevertRequest]);
2487
+
2488
+ const lastStructureAckRequestIdRef = useRef<number | null>(null);
2489
+ useEffect(() => {
2490
+ if (!structureAckRequest) return;
2491
+ if (
2492
+ lastStructureAckRequestIdRef.current === structureAckRequest.requestId
2493
+ ) {
2494
+ return;
2495
+ }
2496
+ lastStructureAckRequestIdRef.current = structureAckRequest.requestId;
2497
+ for (const ack of structureAckRequest.acks) {
2498
+ postOneShotBridgeMessage({
2499
+ type: "visual-structure-ack",
2500
+ requestId: ack.requestId,
2501
+ applied: ack.applied,
2502
+ });
2503
+ }
2504
+ }, [postOneShotBridgeMessage, structureAckRequest]);
2505
+
2162
2506
  /**
2163
2507
  * Send a motion-preview scrub tick to the iframe. `t` is the normalised
2164
2508
  * playhead position in [0, 1]. Tracks must have been loaded first via the
@@ -2675,9 +3019,15 @@ export function DesignCanvas({
2675
3019
  </div>
2676
3020
  </div>
2677
3021
  ) : null}
2678
- {waitingForEditableExternalSnapshot ? (
2679
- <div className="pointer-events-none absolute inset-0 z-10 flex items-center justify-center bg-background/85 px-4 text-center text-sm text-muted-foreground">
2680
- {externalSnapshotState?.status === "error" ? (
3022
+ {waitingForEditableExternalSnapshot || waitingForLiveEditBridge ? (
3023
+ <div className="pointer-events-auto absolute inset-0 z-10 flex items-center justify-center bg-background/85 px-4 text-center text-sm text-muted-foreground">
3024
+ {waitingForLiveEditBridge ? (
3025
+ <div className="max-w-[28rem] rounded-md border bg-card px-4 py-3 shadow-sm">
3026
+ {
3027
+ "Preparing live editor..." /* i18n-ignore transient localhost live-edit bridge loading state */
3028
+ }
3029
+ </div>
3030
+ ) : externalSnapshotState?.status === "error" ? (
2681
3031
  // A real offline dev server previously looked identical to the
2682
3032
  // ordinary "still loading" state and retried forever on a fixed
2683
3033
  // 1.5s timer — see getSnapshotRetryDelayMs for the backoff that