@agent-native/core 0.163.3 → 0.164.1

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 (202) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/analytics/app/hooks/use-dashboard-chat-context.ts +29 -0
  3. package/corpus/templates/analytics/app/pages/sessions/SessionsPage.tsx +1 -2
  4. package/corpus/templates/analytics/netlify.toml +2 -0
  5. package/corpus/templates/assets/netlify.toml +2 -0
  6. package/corpus/templates/brain/netlify.toml +2 -0
  7. package/corpus/templates/calendar/app/components/calendar/DayView.tsx +2 -1
  8. package/corpus/templates/calendar/changelog/2026-08-19-calendar-page-loads-stay-fast-after-idle-periods.md +6 -0
  9. package/corpus/templates/calendar/netlify.toml +2 -0
  10. package/corpus/templates/chat/netlify.toml +2 -0
  11. package/corpus/templates/clips/app/components/player/player-controls.tsx +3 -3
  12. package/corpus/templates/clips/app/components/player/reaction-emojis.ts +12 -0
  13. package/corpus/templates/clips/app/components/player/reactions-tray.tsx +90 -9
  14. package/corpus/templates/clips/app/components/player/scrubber.tsx +43 -10
  15. package/corpus/templates/clips/app/components/player/video-player.tsx +105 -15
  16. package/corpus/templates/clips/app/routes/_app.settings._index.tsx +1 -2
  17. package/corpus/templates/clips/app/routes/_index.tsx +10 -2
  18. package/corpus/templates/clips/app/routes/r.$recordingId.tsx +13 -8
  19. package/corpus/templates/clips/app/routes/share.$shareId.tsx +15 -8
  20. package/corpus/templates/clips/netlify.toml +2 -0
  21. package/corpus/templates/clips/server/routes/[...page].head.ts +5 -0
  22. package/corpus/templates/content/netlify.toml +2 -0
  23. package/corpus/templates/design/.agents/skills/design-editor-architecture/SKILL.md +103 -0
  24. package/corpus/templates/design/.agents/skills/design-generation/SKILL.md +2 -1
  25. package/corpus/templates/design/AGENTS.md +3 -1
  26. package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +3 -2
  27. package/corpus/templates/design/app/components/design/editor/AgentNativeMenuMark.tsx +25 -0
  28. package/corpus/templates/design/app/components/design/editor/DesignBottomToolbar.tsx +411 -0
  29. package/corpus/templates/design/app/components/design/editor/DesignCollaborators.tsx +153 -0
  30. package/corpus/templates/design/app/components/design/editor/DesignWorkspaceRail.tsx +231 -0
  31. package/corpus/templates/design/app/components/design/editor/MakeRealDialog.tsx +223 -0
  32. package/corpus/templates/design/app/components/design/editor/PendingScreenDeletionDialog.tsx +58 -0
  33. package/corpus/templates/design/app/components/design/editor/PendingVisualStyleWarningDialog.tsx +56 -0
  34. package/corpus/templates/design/app/components/design/editor/ReadOnlyEditorPanel.tsx +23 -0
  35. package/corpus/templates/design/app/components/design/editor/SaveTemplateDialog.tsx +161 -0
  36. package/corpus/templates/design/app/components/design/editor/toolbar-controls.tsx +185 -0
  37. package/corpus/templates/design/app/pages/DesignEditor.tsx +20913 -0
  38. package/corpus/templates/design/app/pages/design-editor/command-types.ts +120 -0
  39. package/corpus/templates/design/app/pages/design-editor/commands/README.md +193 -0
  40. package/corpus/templates/design/app/pages/design-editor/commands/add-auto-layout.ts +416 -0
  41. package/corpus/templates/design/app/pages/design-editor/commands/add-screen.ts +112 -0
  42. package/corpus/templates/design/app/pages/design-editor/commands/align-selection.ts +183 -0
  43. package/corpus/templates/design/app/pages/design-editor/commands/apply-design-editor-command.ts +243 -0
  44. package/corpus/templates/design/app/pages/design-editor/commands/apply-file-content-update.ts +236 -0
  45. package/corpus/templates/design/app/pages/design-editor/commands/apply-local-content-update.ts +304 -0
  46. package/corpus/templates/design/app/pages/design-editor/commands/apply-pending-visual-styles-with-agent.ts +376 -0
  47. package/corpus/templates/design/app/pages/design-editor/commands/apply-to-source.ts +132 -0
  48. package/corpus/templates/design/app/pages/design-editor/commands/can-move-layer.ts +117 -0
  49. package/corpus/templates/design/app/pages/design-editor/commands/change-selected-z-index.ts +212 -0
  50. package/corpus/templates/design/app/pages/design-editor/commands/commit-relative-style-delta-to-selected-layers.ts +176 -0
  51. package/corpus/templates/design/app/pages/design-editor/commands/commit-styles-to-selected-layers.ts +175 -0
  52. package/corpus/templates/design/app/pages/design-editor/commands/commit-visual-styles.ts +710 -0
  53. package/corpus/templates/design/app/pages/design-editor/commands/confirm-make-real.ts +99 -0
  54. package/corpus/templates/design/app/pages/design-editor/commands/copy-as-figma-svg.ts +95 -0
  55. package/corpus/templates/design/app/pages/design-editor/commands/copy-selection.ts +156 -0
  56. package/corpus/templates/design/app/pages/design-editor/commands/create-primitive.ts +291 -0
  57. package/corpus/templates/design/app/pages/design-editor/commands/create-screen-frame.ts +131 -0
  58. package/corpus/templates/design/app/pages/design-editor/commands/cross-screen-element-drop.ts +577 -0
  59. package/corpus/templates/design/app/pages/design-editor/commands/delete-files.ts +364 -0
  60. package/corpus/templates/design/app/pages/design-editor/commands/delete-selection.ts +521 -0
  61. package/corpus/templates/design/app/pages/design-editor/commands/detach-instance-menu-action.ts +88 -0
  62. package/corpus/templates/design/app/pages/design-editor/commands/distribute-selection.ts +114 -0
  63. package/corpus/templates/design/app/pages/design-editor/commands/download-all-screens-pdf.ts +116 -0
  64. package/corpus/templates/design/app/pages/design-editor/commands/download-pdf.ts +103 -0
  65. package/corpus/templates/design/app/pages/design-editor/commands/download-svg.ts +159 -0
  66. package/corpus/templates/design/app/pages/design-editor/commands/duplicate-screen.ts +201 -0
  67. package/corpus/templates/design/app/pages/design-editor/commands/duplicate-selection.ts +279 -0
  68. package/corpus/templates/design/app/pages/design-editor/commands/editor-paste.ts +81 -0
  69. package/corpus/templates/design/app/pages/design-editor/commands/enter-hotkey.ts +155 -0
  70. package/corpus/templates/design/app/pages/design-editor/commands/enter-single-screen.ts +168 -0
  71. package/corpus/templates/design/app/pages/design-editor/commands/escape-hotkey.ts +212 -0
  72. package/corpus/templates/design/app/pages/design-editor/commands/frame-selection.ts +119 -0
  73. package/corpus/templates/design/app/pages/design-editor/commands/geometry-commit.ts +169 -0
  74. package/corpus/templates/design/app/pages/design-editor/commands/get-selected-layer-snapshots.ts +177 -0
  75. package/corpus/templates/design/app/pages/design-editor/commands/group-selection.ts +127 -0
  76. package/corpus/templates/design/app/pages/design-editor/commands/iframe-context-menu.ts +121 -0
  77. package/corpus/templates/design/app/pages/design-editor/commands/import-figma-clipboard-into-design.ts +119 -0
  78. package/corpus/templates/design/app/pages/design-editor/commands/layer-marquee-selection-change.ts +163 -0
  79. package/corpus/templates/design/app/pages/design-editor/commands/layer-move-to-screen.ts +331 -0
  80. package/corpus/templates/design/app/pages/design-editor/commands/layer-move.ts +517 -0
  81. package/corpus/templates/design/app/pages/design-editor/commands/layer-rename.ts +247 -0
  82. package/corpus/templates/design/app/pages/design-editor/commands/layer-selection-change.ts +161 -0
  83. package/corpus/templates/design/app/pages/design-editor/commands/mode-change.ts +122 -0
  84. package/corpus/templates/design/app/pages/design-editor/commands/nudge-selection.ts +213 -0
  85. package/corpus/templates/design/app/pages/design-editor/commands/overview-primitive-reparent.ts +318 -0
  86. package/corpus/templates/design/app/pages/design-editor/commands/paste-copied-screens.ts +114 -0
  87. package/corpus/templates/design/app/pages/design-editor/commands/paste-over-selection.ts +70 -0
  88. package/corpus/templates/design/app/pages/design-editor/commands/paste-selection.ts +436 -0
  89. package/corpus/templates/design/app/pages/design-editor/commands/paste-to-replace.ts +135 -0
  90. package/corpus/templates/design/app/pages/design-editor/commands/pasted-image-files.ts +209 -0
  91. package/corpus/templates/design/app/pages/design-editor/commands/persist-frame-geometry-save.ts +113 -0
  92. package/corpus/templates/design/app/pages/design-editor/commands/persist-tweak-save.ts +160 -0
  93. package/corpus/templates/design/app/pages/design-editor/commands/primitive-created.ts +168 -0
  94. package/corpus/templates/design/app/pages/design-editor/commands/record-pending-live-layer-state-edit.ts +149 -0
  95. package/corpus/templates/design/app/pages/design-editor/commands/record-pending-live-structure-edit.ts +200 -0
  96. package/corpus/templates/design/app/pages/design-editor/commands/record-pending-live-text-edit.ts +152 -0
  97. package/corpus/templates/design/app/pages/design-editor/commands/record-pending-visual-style-edit.ts +247 -0
  98. package/corpus/templates/design/app/pages/design-editor/commands/redo.ts +1086 -0
  99. package/corpus/templates/design/app/pages/design-editor/commands/render-png-blob.ts +200 -0
  100. package/corpus/templates/design/app/pages/design-editor/commands/save-file-content.ts +207 -0
  101. package/corpus/templates/design/app/pages/design-editor/commands/screen-element-select.ts +262 -0
  102. package/corpus/templates/design/app/pages/design-editor/commands/screen-text-content-change.ts +216 -0
  103. package/corpus/templates/design/app/pages/design-editor/commands/screen-visual-duplicate-change.ts +108 -0
  104. package/corpus/templates/design/app/pages/design-editor/commands/screen-visual-structure-change.ts +280 -0
  105. package/corpus/templates/design/app/pages/design-editor/commands/screen-visual-style-change.ts +151 -0
  106. package/corpus/templates/design/app/pages/design-editor/commands/send-overview-annotations.ts +88 -0
  107. package/corpus/templates/design/app/pages/design-editor/commands/send-runtime-layer-move-semantic-handoff.ts +122 -0
  108. package/corpus/templates/design/app/pages/design-editor/commands/send-runtime-layer-semantic-handoff.ts +143 -0
  109. package/corpus/templates/design/app/pages/design-editor/commands/send-runtime-layer-state-semantic-handoff.ts +115 -0
  110. package/corpus/templates/design/app/pages/design-editor/commands/start-retry-generation.ts +152 -0
  111. package/corpus/templates/design/app/pages/design-editor/commands/start-sidebar-resize.ts +99 -0
  112. package/corpus/templates/design/app/pages/design-editor/commands/style-change.ts +152 -0
  113. package/corpus/templates/design/app/pages/design-editor/commands/styles-change.ts +155 -0
  114. package/corpus/templates/design/app/pages/design-editor/commands/suggest-auto-layout.ts +125 -0
  115. package/corpus/templates/design/app/pages/design-editor/commands/text-content-change.ts +201 -0
  116. package/corpus/templates/design/app/pages/design-editor/commands/tidy-up.ts +123 -0
  117. package/corpus/templates/design/app/pages/design-editor/commands/toggle-layer-hidden.ts +167 -0
  118. package/corpus/templates/design/app/pages/design-editor/commands/toggle-layer-locked.ts +175 -0
  119. package/corpus/templates/design/app/pages/design-editor/commands/toggle-motion-keyframe.ts +99 -0
  120. package/corpus/templates/design/app/pages/design-editor/commands/tweak-prompt-submit.ts +84 -0
  121. package/corpus/templates/design/app/pages/design-editor/commands/undo.ts +906 -0
  122. package/corpus/templates/design/app/pages/design-editor/commands/ungroup-selection.ts +170 -0
  123. package/corpus/templates/design/app/pages/design-editor/commands/visual-duplicate-change.ts +119 -0
  124. package/corpus/templates/design/app/pages/design-editor/commands/visual-structure-change.ts +274 -0
  125. package/corpus/templates/design/app/pages/design-editor/commands/write-frame-geometry-snapshot.ts +99 -0
  126. package/corpus/templates/design/app/pages/design-editor/derive/design-breakpoints.ts +56 -0
  127. package/corpus/templates/design/app/pages/design-editor/derive/overview-screens.ts +146 -0
  128. package/corpus/templates/design/app/pages/design-editor/domains/use-tweaks.ts +326 -0
  129. package/corpus/templates/design/app/pages/design-editor/editor-constants.ts +54 -0
  130. package/corpus/templates/design/app/pages/design-editor/editor-helpers.ts +183 -0
  131. package/corpus/templates/design/app/pages/design-editor/effects/adopt-db-file-content.ts +270 -0
  132. package/corpus/templates/design/app/pages/design-editor/effects/mirror-selection-to-agent-chat.ts +134 -0
  133. package/corpus/templates/design/app/pages/design-editor/effects/motion-autosave.ts +335 -0
  134. package/corpus/templates/design/app/pages/design-editor/effects/observe-collab-text.ts +183 -0
  135. package/corpus/templates/design/app/pages/design-editor/effects/publish-agent-selection-context.ts +252 -0
  136. package/corpus/templates/design/app/pages/design-editor/effects/resume-pending-generation.ts +178 -0
  137. package/corpus/templates/design/app/pages/design-editor/effects/seed-collab-content.ts +157 -0
  138. package/corpus/templates/design/app/pages/design-editor/preview-html.ts +19 -0
  139. package/corpus/templates/design/netlify.toml +2 -0
  140. package/corpus/templates/design/shared/board-file.ts +2 -1
  141. package/corpus/templates/dispatch/netlify.toml +2 -0
  142. package/corpus/templates/forms/netlify.toml +2 -0
  143. package/corpus/templates/mail/netlify.toml +2 -0
  144. package/corpus/templates/plan/netlify.toml +2 -0
  145. package/corpus/templates/slides/netlify.toml +2 -0
  146. package/dist/agent/engine/builder-engine.d.ts +6 -4
  147. package/dist/agent/engine/builder-engine.js +91 -26
  148. package/dist/agent/engine/registry.js +16 -0
  149. package/dist/cli/code-agent-agent-tools.d.ts +2 -0
  150. package/dist/cli/code-agent-agent-tools.js +188 -0
  151. package/dist/cli/code-agent-collaboration.d.ts +34 -0
  152. package/dist/cli/code-agent-collaboration.js +104 -0
  153. package/dist/cli/code-agent-executor.js +6 -0
  154. package/dist/cli/code-agent-schedules.d.ts +54 -0
  155. package/dist/cli/code-agent-schedules.js +198 -0
  156. package/dist/client/AgentPanel.js +1 -1
  157. package/dist/client/ConnectBuilderCard.js +5 -2
  158. package/dist/client/EnvironmentBadge.js +6 -5
  159. package/dist/client/chat/run-recovery.js +3 -2
  160. package/dist/client/chat/tool-call-display.js +2 -3
  161. package/dist/client/components/CodeRequiredDialog.js +4 -2
  162. package/dist/client/i18n.js +5 -4
  163. package/dist/client/settings/SettingsPanel.js +1 -1
  164. package/dist/client/settings/VoiceTranscriptionSection.js +1 -1
  165. package/dist/client/settings/useBuilderStatus.d.ts +6 -1
  166. package/dist/client/settings/useBuilderStatus.js +22 -33
  167. package/dist/client/transcription/BuilderTranscriptionCta.js +1 -1
  168. package/dist/code-agents/index.d.ts +2 -0
  169. package/dist/code-agents/index.js +2 -0
  170. package/dist/collab/routes.d.ts +1 -1
  171. package/dist/localization/shared.d.ts +2 -0
  172. package/dist/localization/shared.js +5 -0
  173. package/dist/mcp-client/oauth-client.d.ts +1 -0
  174. package/dist/mcp-client/oauth-client.js +50 -1
  175. package/dist/observability/routes.d.ts +1 -1
  176. package/dist/provider-api/actions/provider-api.d.ts +2 -2
  177. package/dist/resources/handlers.d.ts +1 -1
  178. package/dist/server/auth-marketing-locales.d.ts +7 -0
  179. package/dist/server/auth-marketing-locales.js +1322 -0
  180. package/dist/server/auth-marketing.d.ts +1 -0
  181. package/dist/server/auth-marketing.js +35 -6
  182. package/dist/server/auth.js +4 -17
  183. package/dist/server/builder-browser.d.ts +22 -0
  184. package/dist/server/builder-browser.js +77 -1
  185. package/dist/server/builder-oauth.d.ts +50 -0
  186. package/dist/server/builder-oauth.js +263 -0
  187. package/dist/server/core-routes-plugin.d.ts +11 -0
  188. package/dist/server/core-routes-plugin.js +291 -415
  189. package/dist/server/credential-provider.d.ts +1 -1
  190. package/dist/server/credential-provider.js +1 -1
  191. package/dist/server/csrf.js +2 -2
  192. package/dist/server/onboarding-html.js +52 -120
  193. package/dist/server/realtime-token.d.ts +1 -1
  194. package/dist/server/transcribe-voice.d.ts +1 -1
  195. package/dist/settings/store.d.ts +9 -0
  196. package/dist/settings/store.js +19 -0
  197. package/dist/templates/chat/netlify.toml +2 -0
  198. package/dist/vite/client.js +35 -37
  199. package/docs/content/deployment.mdx +2 -0
  200. package/docs/content/multi-app-workspace.mdx +1 -1
  201. package/package.json +2 -2
  202. package/src/templates/chat/netlify.toml +2 -0
@@ -1274,7 +1274,7 @@ export default function ShareRoute() {
1274
1274
  onReact={(emoji) => {
1275
1275
  if (!session) {
1276
1276
  requireSignIn("react");
1277
- return;
1277
+ return false;
1278
1278
  }
1279
1279
  tracking.reportReaction(emoji);
1280
1280
  const liveCt = isLoomEmbedBacked
@@ -1287,7 +1287,7 @@ export default function ShareRoute() {
1287
1287
  liveCt < 1e7
1288
1288
  ? Math.floor(liveCt * 1000)
1289
1289
  : currentMs;
1290
- fetch(
1290
+ return fetch(
1291
1291
  agentNativePath("/_agent-native/actions/react-to-recording"),
1292
1292
  {
1293
1293
  method: "POST",
@@ -1303,7 +1303,11 @@ export default function ShareRoute() {
1303
1303
  if (!res.ok) throw new Error(`react failed: ${res.status}`);
1304
1304
  return dataQ.refetch();
1305
1305
  })
1306
- .catch((err) => console.warn("[clips] react failed", err));
1306
+ .then(() => true)
1307
+ .catch((err) => {
1308
+ console.warn("[clips] react failed", err);
1309
+ return false;
1310
+ });
1307
1311
  }}
1308
1312
  className="h-full w-full rounded-none sm:rounded-xl"
1309
1313
  />
@@ -1371,10 +1375,11 @@ export default function ShareRoute() {
1371
1375
  ) : null}
1372
1376
  {recording.enableReactions ? (
1373
1377
  <ReactionsTray
1378
+ reactions={reactions}
1374
1379
  onReact={(emoji) => {
1375
1380
  if (!session) {
1376
1381
  requireSignIn("react");
1377
- return;
1382
+ return false;
1378
1383
  }
1379
1384
  tracking.reportReaction(emoji);
1380
1385
  const liveCt = isLoomEmbedBacked
@@ -1387,7 +1392,7 @@ export default function ShareRoute() {
1387
1392
  liveCt < 1e7
1388
1393
  ? Math.floor(liveCt * 1000)
1389
1394
  : currentMs;
1390
- fetch(
1395
+ return fetch(
1391
1396
  agentNativePath(
1392
1397
  "/_agent-native/actions/react-to-recording",
1393
1398
  ),
@@ -1406,9 +1411,11 @@ export default function ShareRoute() {
1406
1411
  throw new Error(`react failed: ${res.status}`);
1407
1412
  return dataQ.refetch();
1408
1413
  })
1409
- .catch((err) =>
1410
- console.warn("[clips] react failed", err),
1411
- );
1414
+ .then(() => true)
1415
+ .catch((err) => {
1416
+ console.warn("[clips] react failed", err);
1417
+ return false;
1418
+ });
1412
1419
  }}
1413
1420
  />
1414
1421
  ) : null}
@@ -16,6 +16,8 @@ AGENT_CHAT_DURABLE_BACKGROUND = "true"
16
16
 
17
17
  [context.production.environment]
18
18
  AGENT_NATIVE_HOSTED_HARNESS = "true"
19
+ AGENT_NATIVE_ENABLE_KEEP_WARM = "1"
20
+ AGENT_NATIVE_DISABLE_KEEP_WARM_BACKGROUND = "1"
19
21
 
20
22
  # Netlify Functions run on linux/x64 (AWS Lambda). This tells the deploy build
21
23
  # to bundle the matching ffmpeg-static binary into the server bundle so frame
@@ -16,6 +16,11 @@ export default defineEventHandler((event) => {
16
16
  return new Response(null, {
17
17
  status: 302,
18
18
  headers: {
19
+ // Keep this redirect on the same public SSR-cache path as the shell.
20
+ // Without a content type the deploy adapter cannot apply the canonical
21
+ // cache policy, so every visit to `/` reaches the origin before the
22
+ // already-cached `/library` shell can load.
23
+ "content-type": "text/html; charset=utf-8",
19
24
  location: "/library",
20
25
  "Permissions-Policy": MEDIA_CAPTURE_PERMISSIONS_POLICY,
21
26
  },
@@ -16,6 +16,8 @@ AGENT_CHAT_DURABLE_BACKGROUND = "true"
16
16
 
17
17
  [context.production.environment]
18
18
  AGENT_NATIVE_HOSTED_HARNESS = "true"
19
+ AGENT_NATIVE_ENABLE_KEEP_WARM = "1"
20
+ AGENT_NATIVE_DISABLE_KEEP_WARM_BACKGROUND = "1"
19
21
 
20
22
  # A2A delegation calls run a full agent loop (LLM + tool calls + DB queries).
21
23
  # The framework does not impose a default agent-run timeout; if a template sets
@@ -0,0 +1,103 @@
1
+ ---
2
+ name: design-editor-architecture
3
+ description: >-
4
+ Where design editor behavior lives and how to navigate DesignEditor.tsx. Use
5
+ before changing any editor behavior — undo/redo, paste, delete, duplicate,
6
+ style commit, layer move/rename, export, structure change — or before opening
7
+ `app/pages/DesignEditor.tsx`.
8
+ scope: dev
9
+ metadata:
10
+ internal: true
11
+ ---
12
+
13
+ # Design Editor Architecture
14
+
15
+ ## The routing rule
16
+
17
+ Editor **behavior** lives in `app/pages/design-editor/`, not in
18
+ `app/pages/DesignEditor.tsx`.
19
+
20
+ `DesignEditor.tsx` is ~20,900 lines and holds only three things: state
21
+ declarations, the `useCallback` wrappers that gather arguments, and the JSX.
22
+ Every wrapper delegates to a `run<Name>()` command module.
23
+
24
+ **To change what an editor action does, edit the command module.** Opening
25
+ `DesignEditor.tsx` to change behavior is almost always the wrong move — it will
26
+ exhaust your context before you find the code.
27
+
28
+ | Directory | Holds |
29
+ | --- | --- |
30
+ | `design-editor/commands/` | 86 one-per-action modules, each exporting `run<Name>(args, …)`. Start at `commands/README.md` |
31
+ | `design-editor/effects/` | Subscription and autosave loops (collab text, motion autosave, agent selection mirroring) |
32
+ | `design-editor/derive/` | Pure derivations (`overview-screens.ts`, `design-breakpoints.ts`) |
33
+ | `design-editor/domains/` | Whole-domain hooks owning state + refs + effects + handlers together (`use-tweaks.ts`) |
34
+ | `design-editor/*.ts` | Shared helpers: `history.ts`, `selection-state.ts`, `pending-edits.ts`, `editor-state.ts`, `editor-helpers.ts`, … |
35
+
36
+ ## Common task → file
37
+
38
+ | Task | File |
39
+ | --- | --- |
40
+ | Undo / redo | `commands/undo.ts`, `commands/redo.ts` |
41
+ | Paste | `commands/editor-paste.ts` routes; `commands/paste-selection.ts` and `commands/paste-over-selection.ts` do layers |
42
+ | Copy | `commands/copy-selection.ts` |
43
+ | Delete | `commands/delete-selection.ts` (layers), `commands/delete-files.ts` (screens) |
44
+ | Duplicate | `commands/duplicate-selection.ts`, `commands/duplicate-screen.ts` |
45
+ | Style commit | `commands/commit-visual-styles.ts` |
46
+ | Inspector style edit | `commands/style-change.ts` (one property), `commands/styles-change.ts` (many) |
47
+ | Structure change | `commands/visual-structure-change.ts`, `commands/screen-visual-structure-change.ts` |
48
+ | Layer move / rename | `commands/layer-move.ts`, `commands/layer-move-to-screen.ts`, `commands/layer-rename.ts` |
49
+ | Lock / hide a layer | `commands/toggle-layer-locked.ts`, `commands/toggle-layer-hidden.ts` |
50
+ | Export | `commands/render-png-blob.ts`, `download-pdf.ts`, `download-svg.ts`, `copy-as-figma-svg.ts` |
51
+ | Save / persistence | `commands/save-file-content.ts`, `commands/apply-file-content-update.ts` |
52
+ | Breakpoints | `responsive-breakpoints` skill; `derive/design-breakpoints.ts` |
53
+
54
+ A `screen-` prefix means the command is addressed by an explicit `screenId`
55
+ (overview canvas or board). The unprefixed twin acts on the focused screen.
56
+
57
+ Before adding a `domains/` hook, count what it would expose. Past roughly 16
58
+ returned values the hook stops hiding anything and just relocates the wiring —
59
+ measured surfaces for share/export (20), generation (18), and motion (48) are
60
+ all why those still live inline. Also check no input it needs is declared after
61
+ the point where its own outputs are first consumed; responsive-interact fails
62
+ that test and cannot be extracted without changing when values are read.
63
+
64
+ ## Navigating DesignEditor.tsx when you must
65
+
66
+ The file carries ~82 section banners. This prints a table of contents:
67
+
68
+ ```bash
69
+ grep -n "──" app/pages/DesignEditor.tsx
70
+ ```
71
+
72
+ Read one region with an offset and a limit instead of opening the file. Every
73
+ section is under ~800 lines. Banners use `// ── Name ──` in the component body
74
+ and `{/* ── Render: name ── */}` inside the JSX.
75
+
76
+ When you add or move a region, add a banner for it.
77
+
78
+ ## Hard constraints
79
+
80
+ **`DesignEditor.tsx` must keep exactly one runtime export — the default — and
81
+ zero runtime named exports.** Type-only exports are fine. Both routes
82
+ (`app/routes/design.$id.tsx`, `app/routes/visual-edit.$id.tsx`) import that
83
+ default. A named export breaks React Fast Refresh for the whole editor.
84
+
85
+ **The render-callback trio's dependency arrays are load-bearing.**
86
+ `DesignEditor.routeRefreshBoundary.test.ts` parses the file with the TypeScript
87
+ compiler and enforces:
88
+
89
+ - `renderScreenContent` deps are exactly `[renderEditableScreenContent]`
90
+ - `renderBreakpointContent` deps are exactly `[renderEditableScreenContent]`
91
+ - `renderEditableScreenContent` deps include `activeBreakpointWidthState`,
92
+ `motionDefaultEase`, `motionDurationMs`, `inScreenGradientEditTarget`,
93
+ `handleInScreenGradientEditChange`, and `statePreviewTarget`
94
+
95
+ That last one exists because cached overview canvases must invalidate on
96
+ preview-only state changes. Dropping a name from it renders a stale canvas.
97
+
98
+ **Many specs read source as text.** ~20 specs `readFileSync` either
99
+ `DesignEditor.tsx` or a command module and slice it with `indexOf` markers, then
100
+ assert on the source string. Moving code breaks them with a confusing failure.
101
+ Re-point the path and the marker in the same commit that moves the code. Prefer
102
+ asserting against the command module (`commandSource("undo.ts")`) over the
103
+ editor file.
@@ -907,7 +907,8 @@ read `references/code-layers.md`. For the VS Code-style source workbench
907
907
  reserved for editor-internal state (selection chrome, editor-chrome scale
908
908
  compensation, clipboard/surface tokens) — the editor strips any property
909
909
  under those prefixes before persisting a cross-screen style capture
910
- (`isEditorInternalCssVar` in `app/pages/DesignEditor.tsx`), so a design that
910
+ (`isEditorInternalCssVar` in `app/pages/design-editor/portable-style.ts`),
911
+ so a design that
911
912
  defines its own theming under that namespace would silently lose those
912
913
  values on a cross-screen move. Use `--color-*`, `--font-*`, or another
913
914
  design-owned prefix for tokens/tweaks instead.
@@ -99,4 +99,6 @@ Read the relevant skill before deeper work in that area.
99
99
  ## Source Changes
100
100
 
101
101
  Before building common workspace or agent UI, read `agent-native-toolkit`; read
102
- `customizing-agent-native` before adapting shared UI.
102
+ `customizing-agent-native` before adapting shared UI. Editor behavior lives in
103
+ `app/pages/design-editor/commands/*.ts`, not `DesignEditor.tsx` — read
104
+ `design-editor-architecture` before changing it.
@@ -1768,14 +1768,15 @@ declare var __SELECTED_LAYER_DRAG_PRIORITY__: boolean;
1768
1768
  // Editor-internal CSS custom-property prefixes — selection chrome colors,
1769
1769
  // editor-chrome scale compensation, framework clipboard/surface tokens.
1770
1770
  // These have no meaning outside this editor session and must never leak
1771
- // into persisted user HTML/exports. DesignEditor.tsx's
1771
+ // into persisted user HTML/exports. design-editor/portable-style.ts's
1772
1772
  // applyPortableStyleSnapshotToHtml (isEditorInternalCssVar /
1773
1773
  // EDITOR_INTERNAL_CSS_VAR_PREFIXES) already filters them back out on the
1774
1774
  // apply side; filtering here too at COLLECTION time is pure bloat
1775
1775
  // reduction (skips carrying them across the postMessage boundary at all)
1776
1776
  // and changes no observable behavior on the apply side.
1777
1777
  //
1778
- // keep in sync with DesignEditor.tsx's EDITOR_INTERNAL_CSS_VAR_PREFIXES
1778
+ // keep in sync with design-editor/portable-style.ts's
1779
+ // EDITOR_INTERNAL_CSS_VAR_PREFIXES
1779
1780
  var EDITOR_INTERNAL_CSS_VAR_PREFIXES = [
1780
1781
  "--design-editor-",
1781
1782
  "--agent-native-editor-chrome-",
@@ -0,0 +1,25 @@
1
+ export function AgentNativeMenuMark({ className }: { className?: string }) {
2
+ return (
3
+ <svg
4
+ viewBox="-5 -5 145 88"
5
+ fill="none"
6
+ aria-hidden="true"
7
+ className={className}
8
+ >
9
+ <path
10
+ d="M29.0771 77.8838H2.5L18.9 48.9L43.2 6.6C44 5.2 45.9 5.2 46.7 6.6L69.1 44.2C69.9 45.5 69 46.7305 67.5 46.7305H48.3C47.6 46.7305 46.9 47.1 46.6 47.7L30.8 76.9C30.45 77.5 29.8 77.8838 29.0771 77.8838Z"
11
+ stroke="currentColor"
12
+ strokeWidth="10.5"
13
+ strokeLinecap="round"
14
+ strokeLinejoin="round"
15
+ />
16
+ <path
17
+ d="M105.927 0H132.5C134 0 134.9 1.6 134.15 2.9L91.5 76.9C91.15 77.5 90.5 77.8853 89.8 77.8853H63.8C62.3 77.8853 61.4 76.3 62.15 75L104.2 1C104.55 0.38 105.2 0 105.927 0Z"
18
+ stroke="currentColor"
19
+ strokeWidth="10.5"
20
+ strokeLinecap="round"
21
+ strokeLinejoin="round"
22
+ />
23
+ </svg>
24
+ );
25
+ }
@@ -0,0 +1,411 @@
1
+ import { useT } from "@agent-native/core/client/i18n";
2
+ import {
3
+ IconArrowUpRight,
4
+ IconBrush,
5
+ IconCircle,
6
+ IconDevices,
7
+ IconFrame,
8
+ IconHandClick,
9
+ IconHandStop,
10
+ IconLine,
11
+ IconMessage,
12
+ IconPhotoVideo,
13
+ IconPointer,
14
+ IconScale,
15
+ IconScribble,
16
+ IconSquare,
17
+ IconStar,
18
+ IconTransformPoint,
19
+ IconTriangle,
20
+ } from "@tabler/icons-react";
21
+ import type { ReactNode } from "react";
22
+
23
+ import type { DesignToolbarOption } from "@/components/design/editor/toolbar-controls";
24
+ import {
25
+ DesignModeTab,
26
+ DesignPenToolIcon,
27
+ DesignToolbarTool,
28
+ } from "@/components/design/editor/toolbar-controls";
29
+ import { IconText } from "@/components/design/inspector/design-icons";
30
+ import {
31
+ MOVE_GROUP_TOOL_PRESENTATIONS,
32
+ getMoveGroupToolPresentation,
33
+ } from "@/pages/design-editor/tool-state";
34
+ import type {
35
+ DesignTool,
36
+ EditorMode,
37
+ ShapeTool,
38
+ } from "@/pages/design-editor/types";
39
+
40
+ export function DesignBottomToolbar({
41
+ mode,
42
+ pinMode,
43
+ drawMode,
44
+ activeTool,
45
+ isOverview,
46
+ hasActiveFile,
47
+ onMove,
48
+ onFrame,
49
+ frameToolDraws,
50
+ onFrameToolDrawsChange,
51
+ onShape,
52
+ onText,
53
+ onPen,
54
+ onHand,
55
+ onDraw,
56
+ onScale,
57
+ onCommentPin,
58
+ onModeChange,
59
+ shortcutsPanelOpen,
60
+ }: {
61
+ mode: EditorMode;
62
+ pinMode: boolean;
63
+ drawMode: boolean;
64
+ activeTool: DesignTool;
65
+ isOverview: boolean;
66
+ hasActiveFile: boolean;
67
+ onMove: () => void;
68
+ onFrame: () => void;
69
+ frameToolDraws: "screen" | "frame";
70
+ onFrameToolDrawsChange: (value: "screen" | "frame") => void;
71
+ onShape: (tool: ShapeTool) => void;
72
+ onText: () => void;
73
+ onPen: () => void;
74
+ onHand: () => void;
75
+ onDraw: () => void;
76
+ onScale: () => void;
77
+ onCommentPin: () => void;
78
+ onModeChange: (mode: EditorMode) => void;
79
+ shortcutsPanelOpen: boolean;
80
+ }) {
81
+ const t = useT();
82
+ const shapeTools = new Set<DesignTool>([
83
+ "rect",
84
+ "line",
85
+ "arrow",
86
+ "ellipse",
87
+ "polygon",
88
+ "star",
89
+ ]);
90
+ const activeShape = shapeTools.has(activeTool)
91
+ ? (activeTool as ShapeTool)
92
+ : "rect";
93
+ const shapeIcon = (tool: ShapeTool, className: string) => {
94
+ switch (tool) {
95
+ case "line":
96
+ return <IconLine className={className} />;
97
+ case "arrow":
98
+ return <IconArrowUpRight className={className} />;
99
+ case "ellipse":
100
+ return <IconCircle className={className} />;
101
+ case "polygon":
102
+ return <IconTriangle className={className} />;
103
+ case "star":
104
+ return <IconStar className={className} />;
105
+ case "rect":
106
+ default:
107
+ return <IconSquare className={className} />;
108
+ }
109
+ };
110
+ const shapeOptions: DesignToolbarOption[] = [
111
+ {
112
+ key: "rect",
113
+ label: t("designEditor.tools.rect"),
114
+ icon: shapeIcon("rect", "size-4"),
115
+ shortcut: "R",
116
+ active: activeTool === "rect",
117
+ onSelect: () => onShape("rect"),
118
+ },
119
+ {
120
+ key: "line",
121
+ label: t("designEditor.tools.line"),
122
+ icon: shapeIcon("line", "size-4"),
123
+ shortcut: "L",
124
+ active: activeTool === "line",
125
+ onSelect: () => onShape("line"),
126
+ },
127
+ {
128
+ key: "arrow",
129
+ label: t("designEditor.tools.arrow"),
130
+ icon: shapeIcon("arrow", "size-4"),
131
+ shortcut: "⇧L",
132
+ active: activeTool === "arrow",
133
+ onSelect: () => onShape("arrow"),
134
+ },
135
+ {
136
+ key: "ellipse",
137
+ label: t("designEditor.tools.ellipse"),
138
+ icon: shapeIcon("ellipse", "size-4"),
139
+ shortcut: "O",
140
+ active: activeTool === "ellipse",
141
+ onSelect: () => onShape("ellipse"),
142
+ },
143
+ {
144
+ key: "polygon",
145
+ label: t("designEditor.tools.polygon"),
146
+ icon: shapeIcon("polygon", "size-4"),
147
+ active: activeTool === "polygon",
148
+ onSelect: () => onShape("polygon"),
149
+ },
150
+ {
151
+ key: "star",
152
+ label: t("designEditor.tools.star"),
153
+ icon: shapeIcon("star", "size-4"),
154
+ active: activeTool === "star",
155
+ onSelect: () => onShape("star"),
156
+ },
157
+ {
158
+ key: "image-video",
159
+ label: t("designEditor.tools.imageVideo"),
160
+ icon: <IconPhotoVideo className="size-4" />,
161
+ disabled: true,
162
+ onSelect: () => {},
163
+ },
164
+ ];
165
+ const activeShapeOption =
166
+ shapeOptions.find((option) => option.key === activeShape) ??
167
+ shapeOptions[0]!;
168
+ const activeMoveGroupTool = getMoveGroupToolPresentation(activeTool);
169
+ const handleActiveMoveGroupTool =
170
+ activeMoveGroupTool.tool === "hand"
171
+ ? onHand
172
+ : activeMoveGroupTool.tool === "scale"
173
+ ? onScale
174
+ : onMove;
175
+ const tools: Array<{
176
+ key: string;
177
+ active: boolean;
178
+ label: string;
179
+ icon: ReactNode;
180
+ onClick: () => void;
181
+ options: DesignToolbarOption[];
182
+ }> = [
183
+ {
184
+ key: "move",
185
+ // Parent button is active whenever any of the move-group sub-tools is
186
+ // selected so the toolbar visually reflects hand and scale modes too.
187
+ active:
188
+ (activeTool === "move" && mode === "edit") ||
189
+ activeTool === "hand" ||
190
+ activeTool === "scale",
191
+ // The parent button represents the active move-group sub-tool. Expose
192
+ // that same identity to assistive technology and the tooltip instead of
193
+ // announcing every H/K activation as the Move tool.
194
+ label: t(activeMoveGroupTool.labelKey),
195
+ // Mirror the active sub-tool icon so the parent button is always
196
+ // informative about the currently selected move-group tool.
197
+ icon:
198
+ activeTool === "hand" ? (
199
+ <IconHandStop className="size-[18px]" />
200
+ ) : activeTool === "scale" ? (
201
+ <IconScale className="size-[18px]" />
202
+ ) : (
203
+ <IconPointer className="size-[18px]" />
204
+ ),
205
+ // Keep the primary action aligned with the icon/label it presents. A
206
+ // Hand or Scale button should remain Hand or Scale when clicked rather
207
+ // than silently switching back to Move.
208
+ onClick: handleActiveMoveGroupTool,
209
+ options: [
210
+ {
211
+ key: "move",
212
+ label: t("designEditor.tools.move"),
213
+ icon: <IconPointer className="size-4" />,
214
+ shortcut: MOVE_GROUP_TOOL_PRESENTATIONS.move.shortcut,
215
+ active: activeTool === "move" && mode === "edit",
216
+ onSelect: onMove,
217
+ },
218
+ {
219
+ key: "hand",
220
+ label: t("designEditor.tools.hand"),
221
+ icon: <IconHandStop className="size-4" />,
222
+ shortcut: MOVE_GROUP_TOOL_PRESENTATIONS.hand.shortcut,
223
+ active: activeTool === "hand",
224
+ onSelect: onHand,
225
+ },
226
+ {
227
+ key: "scale",
228
+ label: t("designEditor.tools.scale"),
229
+ icon: <IconScale className="size-4" />,
230
+ shortcut: MOVE_GROUP_TOOL_PRESENTATIONS.scale.shortcut,
231
+ active: activeTool === "scale",
232
+ onSelect: onScale,
233
+ },
234
+ ],
235
+ },
236
+ {
237
+ key: "frame",
238
+ active: activeTool === "frame",
239
+ label:
240
+ frameToolDraws === "screen"
241
+ ? t("designEditor.tools.screen")
242
+ : t("designEditor.tools.frame"),
243
+ icon:
244
+ frameToolDraws === "screen" ? (
245
+ <IconDevices className="size-[18px]" />
246
+ ) : (
247
+ <IconFrame className="size-[18px]" />
248
+ ),
249
+ onClick: onFrame,
250
+ options: [
251
+ {
252
+ key: "frame",
253
+ label: t("designEditor.tools.frame"),
254
+ icon: <IconFrame className="size-4" />,
255
+ shortcut: "F",
256
+ active: activeTool === "frame" && frameToolDraws === "frame",
257
+ onSelect: () => {
258
+ onFrameToolDrawsChange("frame");
259
+ onFrame();
260
+ },
261
+ },
262
+ {
263
+ key: "screen",
264
+ label: t("designEditor.tools.screen"),
265
+ icon: <IconDevices className="size-4" />,
266
+ active: activeTool === "frame" && frameToolDraws === "screen",
267
+ onSelect: () => {
268
+ onFrameToolDrawsChange("screen");
269
+ onFrame();
270
+ },
271
+ },
272
+ ],
273
+ },
274
+ {
275
+ key: "shape",
276
+ active: shapeTools.has(activeTool),
277
+ label: activeShapeOption.label,
278
+ icon: shapeIcon(activeShape, "size-[18px]"),
279
+ onClick: () => onShape(activeShape),
280
+ options: shapeOptions,
281
+ },
282
+ {
283
+ key: "pen",
284
+ active: activeTool === "pen",
285
+ label: t("designEditor.tools.pen"),
286
+ icon: <DesignPenToolIcon className="size-[18px]" />,
287
+ onClick: onPen,
288
+ options: [
289
+ {
290
+ key: "pen",
291
+ label: t("designEditor.tools.pen"),
292
+ icon: <DesignPenToolIcon className="size-4" />,
293
+ shortcut: "P",
294
+ active: activeTool === "pen",
295
+ onSelect: onPen,
296
+ },
297
+ {
298
+ key: "draw",
299
+ label: t("designEditor.modes.draw"),
300
+ icon: <IconBrush className="size-4" />,
301
+ shortcut: "Y",
302
+ active: activeTool === "draw" && mode === "annotate" && drawMode,
303
+ disabled: !hasActiveFile,
304
+ onSelect: onDraw,
305
+ },
306
+ ],
307
+ },
308
+ {
309
+ key: "text",
310
+ active: activeTool === "text",
311
+ label: t("designEditor.tools.text"),
312
+ icon: <IconText className="size-[18px]" />,
313
+ onClick: onText,
314
+ options: [
315
+ {
316
+ key: "text",
317
+ label: t("designEditor.tools.text"),
318
+ icon: <IconText className="size-4" />,
319
+ shortcut: "T",
320
+ active: activeTool === "text",
321
+ onSelect: onText,
322
+ },
323
+ ],
324
+ },
325
+ {
326
+ key: "comment",
327
+ active: activeTool === "comment" && mode === "annotate" && pinMode,
328
+ label: t("designEditor.pinComment"),
329
+ icon: <IconMessage className="size-[18px]" />,
330
+ onClick: onCommentPin,
331
+ options: [
332
+ {
333
+ key: "comment",
334
+ label: t("designEditor.pinComment"),
335
+ icon: <IconMessage className="size-4" />,
336
+ shortcut: "C",
337
+ active: activeTool === "comment" && mode === "annotate" && pinMode,
338
+ disabled: !hasActiveFile || isOverview,
339
+ onSelect: onCommentPin,
340
+ },
341
+ ],
342
+ },
343
+ ];
344
+
345
+ const modes: Array<{
346
+ key: EditorMode;
347
+ active: boolean;
348
+ label: string;
349
+ icon: ReactNode;
350
+ onClick: () => void;
351
+ }> = [
352
+ {
353
+ key: "annotate",
354
+ active: mode === "annotate",
355
+ label: t("designEditor.modes.annotate"),
356
+ icon: <IconScribble className="size-[18px]" />,
357
+ onClick: () => onModeChange("annotate"),
358
+ },
359
+ {
360
+ key: "edit",
361
+ active: mode === "edit",
362
+ label: t("designEditor.modes.edit"),
363
+ icon: <IconTransformPoint className="size-[18px]" />,
364
+ onClick: () => onModeChange("edit"),
365
+ },
366
+ {
367
+ key: "interact",
368
+ active: mode === "interact",
369
+ label: t("designEditor.modes.interact"),
370
+ icon: <IconHandClick className="size-[18px]" />,
371
+ onClick: () => onModeChange("interact"),
372
+ },
373
+ ];
374
+ return (
375
+ <div
376
+ data-design-bottom-toolbar
377
+ /* guard:allow-raw-color — fixed dark editor chrome, intentionally theme-independent */
378
+ className="fixed left-1/2 z-[70] flex max-w-[calc(100%-1rem)] -translate-x-1/2 items-center gap-1.5 overflow-x-auto rounded-xl border border-white/10 bg-[#2c2c2c]/95 p-1.5 text-neutral-100 shadow-[0_22px_55px_-24px_rgba(0,0,0,0.9),0_0_0_1px_rgba(0,0,0,0.25)] backdrop-blur transition-[bottom] duration-150 motion-reduce:transition-none md:max-w-[calc(100%-2rem)] md:overflow-visible"
379
+ style={{ bottom: shortcutsPanelOpen ? 257 : 16 }}
380
+ >
381
+ <div className="flex min-w-0 items-center gap-0.5">
382
+ {tools.map((tool) => (
383
+ <DesignToolbarTool
384
+ key={tool.key}
385
+ active={tool.active}
386
+ label={tool.label}
387
+ icon={tool.icon}
388
+ options={tool.options}
389
+ onPrimary={tool.onClick}
390
+ />
391
+ ))}
392
+ </div>
393
+
394
+ {/* guard:allow-raw-color — fixed dark editor chrome, intentionally theme-independent */}
395
+ <div className="h-9 w-px shrink-0 bg-white/15" />
396
+
397
+ {/* guard:allow-raw-color — fixed dark editor chrome, intentionally theme-independent */}
398
+ <div className="flex shrink-0 items-center gap-0.5 rounded-md bg-white/10 p-0.5">
399
+ {modes.map((item) => (
400
+ <DesignModeTab
401
+ key={item.key}
402
+ active={item.active}
403
+ label={item.label}
404
+ icon={item.icon}
405
+ onClick={item.onClick}
406
+ />
407
+ ))}
408
+ </div>
409
+ </div>
410
+ );
411
+ }