@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,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
+ }
@@ -0,0 +1,153 @@
1
+ import type { CollabUser } from "@agent-native/core/client/collab";
2
+ import { emailToColor, emailToName } from "@agent-native/core/client/collab";
3
+ import { useAvatarUrl } from "@agent-native/core/client/hooks";
4
+ import { IconCheck, IconChevronDown } from "@tabler/icons-react";
5
+
6
+ import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
7
+ import {
8
+ DropdownMenu,
9
+ DropdownMenuContent,
10
+ DropdownMenuItem,
11
+ DropdownMenuLabel,
12
+ DropdownMenuTrigger,
13
+ } from "@/components/ui/dropdown-menu";
14
+ import { cn } from "@/lib/utils";
15
+
16
+ export interface DesignCollaborator {
17
+ user: CollabUser;
18
+ image?: string;
19
+ isCurrent?: boolean;
20
+ }
21
+
22
+ function userInitial(nameOrEmail: string): string {
23
+ const trimmed = nameOrEmail.trim();
24
+ if (!trimmed) return "?";
25
+ return trimmed.charAt(0).toUpperCase();
26
+ }
27
+
28
+ function userColor(user: CollabUser): string {
29
+ return user.color || emailToColor(user.email);
30
+ }
31
+
32
+ export function DesignCollaboratorAvatar({
33
+ collaborator,
34
+ className,
35
+ }: {
36
+ collaborator: DesignCollaborator;
37
+ className?: string;
38
+ }) {
39
+ const label = collaborator.user.name || emailToName(collaborator.user.email);
40
+ const storedAvatarUrl = useAvatarUrl(collaborator.user.email);
41
+ const avatarUrl = storedAvatarUrl ?? collaborator.image;
42
+
43
+ return (
44
+ <Avatar
45
+ className={cn(
46
+ "size-7 border-2 border-[var(--design-editor-panel-bg)] shadow-sm",
47
+ className,
48
+ )}
49
+ >
50
+ {avatarUrl ? <AvatarImage src={avatarUrl} alt={label} /> : null}
51
+ <AvatarFallback
52
+ /* guard:allow-raw-color — avatar initials sit on a generated user color, not a themed surface */
53
+ className="text-[10px] font-semibold text-white"
54
+ style={{ backgroundColor: userColor(collaborator.user) }}
55
+ >
56
+ {userInitial(label || collaborator.user.email)}
57
+ </AvatarFallback>
58
+ </Avatar>
59
+ );
60
+ }
61
+
62
+ export function DesignCollaboratorsMenu({
63
+ collaborators,
64
+ followingEmail,
65
+ label,
66
+ onAvatarClick,
67
+ }: {
68
+ collaborators: DesignCollaborator[];
69
+ followingEmail?: string | null;
70
+ label: string;
71
+ onAvatarClick?: (user: CollabUser | null) => void;
72
+ }) {
73
+ if (collaborators.length === 0) return null;
74
+
75
+ const visibleCollaborators = collaborators.slice(0, 3);
76
+ const hasMultipleCollaborators = collaborators.length > 1;
77
+ const followingLower = followingEmail?.trim().toLowerCase() ?? null;
78
+
79
+ return (
80
+ <DropdownMenu>
81
+ <DropdownMenuTrigger asChild>
82
+ <button
83
+ type="button"
84
+ className="flex h-8 min-w-0 cursor-pointer items-center rounded-md pr-1 text-muted-foreground transition-colors hover:bg-accent/50 hover:text-foreground"
85
+ aria-label={label}
86
+ >
87
+ <span className="flex items-center">
88
+ {visibleCollaborators.map((collaborator, index) => (
89
+ <DesignCollaboratorAvatar
90
+ key={`${collaborator.user.email}:${index}`}
91
+ collaborator={collaborator}
92
+ className={index === 0 ? undefined : "-ml-2"}
93
+ />
94
+ ))}
95
+ </span>
96
+ {hasMultipleCollaborators ? (
97
+ <IconChevronDown className="ml-0.5 size-3 opacity-70" />
98
+ ) : null}
99
+ </button>
100
+ </DropdownMenuTrigger>
101
+ <DropdownMenuContent align="start" className="w-64">
102
+ <DropdownMenuLabel className="text-xs text-muted-foreground">
103
+ {label}
104
+ </DropdownMenuLabel>
105
+ {collaborators.map((collaborator) => {
106
+ const user = collaborator.user;
107
+ const email = user.email.trim().toLowerCase();
108
+ const isFollowing =
109
+ followingLower != null && email === followingLower;
110
+ const name = user.name || emailToName(user.email);
111
+
112
+ return (
113
+ <DropdownMenuItem
114
+ key={user.email}
115
+ onSelect={(event) => {
116
+ if (collaborator.isCurrent) {
117
+ event.preventDefault();
118
+ return;
119
+ }
120
+ onAvatarClick?.(user);
121
+ }}
122
+ className={cn(
123
+ "gap-2",
124
+ collaborator.isCurrent && "cursor-default",
125
+ )}
126
+ >
127
+ <DesignCollaboratorAvatar collaborator={collaborator} />
128
+ <span className="min-w-0 flex-1">
129
+ <span className="block truncate text-sm font-medium">
130
+ {name}
131
+ </span>
132
+ <span className="block truncate text-xs text-muted-foreground">
133
+ {user.email}
134
+ </span>
135
+ </span>
136
+ {collaborator.isCurrent ? (
137
+ <span className="text-xs text-muted-foreground">
138
+ {"You" /* i18n-ignore collaborator row */}
139
+ </span>
140
+ ) : isFollowing ? (
141
+ <IconCheck className="size-3.5 text-[var(--design-editor-accent-color)]" />
142
+ ) : (
143
+ <span className="text-xs text-muted-foreground">
144
+ {"Follow" /* i18n-ignore collaborator row */}
145
+ </span>
146
+ )}
147
+ </DropdownMenuItem>
148
+ );
149
+ })}
150
+ </DropdownMenuContent>
151
+ </DropdownMenu>
152
+ );
153
+ }