@agent-native/core 0.80.0 → 0.80.2

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 +2 -2
  2. package/corpus/core/CHANGELOG.md +28 -0
  3. package/corpus/core/docs/content/locales/ar-SA/skills-guide.mdx +5 -0
  4. package/corpus/core/docs/content/locales/ar-SA/template-design.mdx +14 -1
  5. package/corpus/core/docs/content/locales/de-DE/skills-guide.mdx +5 -0
  6. package/corpus/core/docs/content/locales/de-DE/template-design.mdx +15 -1
  7. package/corpus/core/docs/content/locales/es-ES/skills-guide.mdx +5 -0
  8. package/corpus/core/docs/content/locales/es-ES/template-design.mdx +15 -1
  9. package/corpus/core/docs/content/locales/fr-FR/skills-guide.mdx +5 -0
  10. package/corpus/core/docs/content/locales/fr-FR/template-design.mdx +15 -1
  11. package/corpus/core/docs/content/locales/hi-IN/skills-guide.mdx +5 -0
  12. package/corpus/core/docs/content/locales/hi-IN/template-design.mdx +15 -1
  13. package/corpus/core/docs/content/locales/ja-JP/skills-guide.mdx +5 -0
  14. package/corpus/core/docs/content/locales/ja-JP/template-design.mdx +16 -1
  15. package/corpus/core/docs/content/locales/ko-KR/skills-guide.mdx +5 -0
  16. package/corpus/core/docs/content/locales/ko-KR/template-design.mdx +14 -1
  17. package/corpus/core/docs/content/locales/pt-BR/skills-guide.mdx +5 -0
  18. package/corpus/core/docs/content/locales/pt-BR/template-design.mdx +14 -1
  19. package/corpus/core/docs/content/locales/zh-CN/skills-guide.mdx +5 -0
  20. package/corpus/core/docs/content/locales/zh-CN/template-design.mdx +14 -1
  21. package/corpus/core/docs/content/locales/zh-TW/skills-guide.mdx +5 -0
  22. package/corpus/core/docs/content/locales/zh-TW/template-design.mdx +14 -1
  23. package/corpus/core/docs/content/skills-guide.mdx +5 -0
  24. package/corpus/core/docs/content/template-design.mdx +14 -1
  25. package/corpus/core/package.json +1 -1
  26. package/corpus/core/src/application-state/handlers.ts +1 -1
  27. package/corpus/core/src/brand-kit/index.ts +1 -1
  28. package/corpus/core/src/cli/skills.ts +18 -0
  29. package/corpus/core/src/client/AgentPanel.tsx +1 -1
  30. package/corpus/core/src/client/MultiTabAssistantChat.tsx +1 -1
  31. package/corpus/core/src/client/extensions/portable-extension.ts +1 -1
  32. package/corpus/core/src/code-agents/transcript-normalizer.ts +1 -1
  33. package/corpus/core/src/extensions/path.ts +1 -4
  34. package/corpus/core/src/integrations/a2a-continuation-processor.ts +1 -1
  35. package/corpus/core/src/mcp/build-server.ts +2 -2
  36. package/corpus/core/src/mcp/embed-app.ts +35 -1
  37. package/corpus/core/src/org/handlers.ts +5 -5
  38. package/corpus/core/src/org/plugin.ts +2 -2
  39. package/corpus/core/src/scripts/call-agent.ts +1 -1
  40. package/corpus/core/src/scripts/db/safety.ts +1 -1
  41. package/corpus/core/src/server/auth.ts +3 -0
  42. package/corpus/core/src/server/builder-design-systems.ts +308 -0
  43. package/corpus/core/src/server/core-routes-plugin.ts +29 -3
  44. package/corpus/core/src/server/index.ts +8 -0
  45. package/corpus/core/src/styles/agent-native.css +14 -7
  46. package/corpus/core/src/usage/store.ts +2 -2
  47. package/corpus/templates/analytics/actions/dashboard-mutation-api.ts +298 -15
  48. package/corpus/templates/analytics/actions/get-sql-dashboard.ts +9 -4
  49. package/corpus/templates/analytics/actions/mutate-dashboard.ts +18 -2
  50. package/corpus/templates/analytics/actions/query-agent-native-analytics.ts +12 -2
  51. package/corpus/templates/analytics/app/hooks/use-replay-storage-status.ts +66 -0
  52. package/corpus/templates/analytics/app/i18n/zh-TW.ts +37 -0
  53. package/corpus/templates/analytics/app/i18n-data.ts +404 -0
  54. package/corpus/templates/analytics/app/pages/Settings.tsx +333 -0
  55. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/PanelEditorDialog.tsx +2 -1
  56. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/dashboard-layout.ts +121 -3
  57. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +49 -26
  58. package/corpus/templates/analytics/app/pages/sessions/SessionsPage.tsx +137 -36
  59. package/corpus/templates/analytics/changelog/2026-06-29-analytics-agents-can-now-place-new-charts-into-requested-das.md +6 -0
  60. package/corpus/templates/analytics/changelog/2026-06-29-analytics-queries-now-report-failed-source-query-errors-inst.md +6 -0
  61. package/corpus/templates/analytics/changelog/2026-06-29-charts-can-now-be-dropped-between-full-dashboard-rows-to-cre.md +6 -0
  62. package/corpus/templates/analytics/changelog/2026-06-29-dashboard-email-reports-include-the-full-dashboard-image-wit.md +6 -0
  63. package/corpus/templates/analytics/changelog/2026-06-29-main-surface-border-reaches-the-top.md +6 -0
  64. package/corpus/templates/analytics/changelog/2026-06-29-session-replay-now-records-playable-sessions-in-production-a.md +6 -0
  65. package/corpus/templates/analytics/changelog/2026-06-29-set-up-session-replay-storage-from-settings-connect-builder-.md +6 -0
  66. package/corpus/templates/analytics/scripts/cleanup-empty-session-recordings.ts +102 -0
  67. package/corpus/templates/analytics/server/lib/dashboard-report.ts +49 -26
  68. package/corpus/templates/analytics/server/lib/real-data-actions.ts +81 -20
  69. package/corpus/templates/analytics/server/lib/replay-storage.ts +79 -0
  70. package/corpus/templates/analytics/server/lib/s3-upload-provider.ts +255 -0
  71. package/corpus/templates/analytics/server/lib/session-replay.ts +29 -8
  72. package/corpus/templates/analytics/server/plugins/agent-chat.ts +11 -2
  73. package/corpus/templates/analytics/server/plugins/onboarding.ts +97 -0
  74. package/corpus/templates/clips/desktop/src/lib/recorder.ts +24 -0
  75. package/corpus/templates/clips/desktop/src/lib/transcription-capture.ts +105 -5
  76. package/corpus/templates/content/actions/set-image-alt-text.ts +1 -1
  77. package/corpus/templates/content/shared/document-export.ts +1 -1
  78. package/corpus/templates/design/.agents/skills/design-systems/SKILL.md +8 -18
  79. package/corpus/templates/design/.agents/skills/visual-edit/SKILL.md +17 -0
  80. package/corpus/templates/design/actions/add-localhost-screens.ts +32 -5
  81. package/corpus/templates/design/actions/index-design-system-with-builder.ts +259 -0
  82. package/corpus/templates/design/actions/update-file.ts +9 -2
  83. package/corpus/templates/design/app/components/design/CanvasContextMenu.tsx +33 -75
  84. package/corpus/templates/design/app/components/design/DesignCanvas.tsx +72 -9
  85. package/corpus/templates/design/app/components/design/DesignExtensionsPanel.tsx +41 -39
  86. package/corpus/templates/design/app/components/design/EditPanel.tsx +1029 -249
  87. package/corpus/templates/design/app/components/design/LayersPanel.tsx +450 -101
  88. package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +978 -154
  89. package/corpus/templates/design/app/components/design/TweaksPanel.tsx +7 -9
  90. package/corpus/templates/design/app/components/design/inspector/AlignmentMatrix.tsx +133 -66
  91. package/corpus/templates/design/app/components/design/inspector/AutoLayoutMatrix.tsx +1234 -344
  92. package/corpus/templates/design/app/components/design/inspector/ConstraintsWidget.tsx +144 -94
  93. package/corpus/templates/design/app/components/design/inspector/ExportSettingsPanel.tsx +301 -72
  94. package/corpus/templates/design/app/components/design/inspector/FigmaColorPicker.tsx +876 -286
  95. package/corpus/templates/design/app/components/design/inspector/GradientEditor.tsx +601 -0
  96. package/corpus/templates/design/app/components/design/inspector/ImageFillControls.tsx +242 -0
  97. package/corpus/templates/design/app/components/design/inspector/ScrubInput.tsx +30 -7
  98. package/corpus/templates/design/app/components/design/inspector/ShaderControls.tsx +212 -155
  99. package/corpus/templates/design/app/components/design/inspector/ShaderFillsPanel.tsx +423 -0
  100. package/corpus/templates/design/app/components/design/inspector/figma-icons.tsx +487 -107
  101. package/corpus/templates/design/app/components/design/inspector/index.ts +27 -0
  102. package/corpus/templates/design/app/global.css +29 -3
  103. package/corpus/templates/design/app/i18n/zh-TW.ts +13 -0
  104. package/corpus/templates/design/app/i18n-data.ts +239 -1
  105. package/corpus/templates/design/app/pages/DesignEditor.tsx +1023 -353
  106. package/corpus/templates/design/app/pages/DesignSystemSetup.tsx +57 -221
  107. package/corpus/templates/design/changelog/2026-06-29-canvas-selection-no-longer-flashes-broken-prototype-html-whe.md +6 -0
  108. package/corpus/templates/design/changelog/2026-06-29-command-menu-selections-are-clearer-in-dark-mode.md +6 -0
  109. package/corpus/templates/design/changelog/2026-06-29-design-mode-top-chrome-now-groups-collaborators-preview-shar.md +6 -0
  110. package/corpus/templates/design/changelog/2026-06-29-element-selection-now-stays-on-the-intended-layer-after-sele.md +6 -0
  111. package/corpus/templates/design/changelog/2026-06-29-screen-overview-full-view-controls-now-sit-above-frames-inst.md +6 -0
  112. package/corpus/templates/design/changelog/2026-06-29-screen-overview-tools-can-draw-shapes-create-screens-and-kee.md +6 -0
  113. package/corpus/templates/design/changelog/2026-06-29-the-pen-tool-now-creates-anchor-based-bezier-vectors-with-ha.md +6 -0
  114. package/corpus/templates/design/server/handlers/import-figma-system.ts +38 -85
  115. package/corpus/templates/design/server/lib/design-snapshot.ts +9 -1
  116. package/corpus/templates/design/shared/code-layer.ts +209 -9
  117. package/corpus/templates/design/shared/html-content.ts +21 -0
  118. package/corpus/templates/design/shared/pen-path.ts +207 -0
  119. package/corpus/templates/mail/app/lib/utils.ts +2 -2
  120. package/corpus/templates/slides/.agents/skills/design-systems/SKILL.md +9 -10
  121. package/corpus/templates/slides/AGENTS.md +3 -2
  122. package/corpus/templates/slides/actions/import-file.ts +26 -17
  123. package/corpus/templates/slides/actions/index-design-system-with-builder.ts +259 -0
  124. package/corpus/templates/slides/app/components/design-system/DesignSystemSetup.tsx +35 -167
  125. package/corpus/templates/slides/app/components/design-system/fig-import-response.ts +9 -13
  126. package/corpus/templates/slides/app/context/DeckContext.tsx +1 -1
  127. package/corpus/templates/slides/app/i18n/ar-SA.ts +9 -37
  128. package/corpus/templates/slides/app/i18n/de-DE.ts +10 -25
  129. package/corpus/templates/slides/app/i18n/en-US.ts +9 -26
  130. package/corpus/templates/slides/app/i18n/es-ES.ts +10 -28
  131. package/corpus/templates/slides/app/i18n/fr-FR.ts +10 -28
  132. package/corpus/templates/slides/app/i18n/hi-IN.ts +9 -26
  133. package/corpus/templates/slides/app/i18n/ja-JP.ts +10 -22
  134. package/corpus/templates/slides/app/i18n/ko-KR.ts +9 -23
  135. package/corpus/templates/slides/app/i18n/pt-BR.ts +10 -28
  136. package/corpus/templates/slides/app/i18n/zh-CN.ts +9 -22
  137. package/corpus/templates/slides/app/i18n/zh-TW.ts +9 -22
  138. package/corpus/templates/slides/server/handlers/import-figma-system.ts +44 -12
  139. package/corpus/templates/slides/server/lib/chat-attachments.ts +1 -1
  140. package/corpus/templates/videos/app/components/NewCompositionPopover.tsx +1 -1
  141. package/dist/application-state/handlers.js +1 -1
  142. package/dist/application-state/handlers.js.map +1 -1
  143. package/dist/brand-kit/index.d.ts +1 -1
  144. package/dist/brand-kit/index.js +1 -1
  145. package/dist/brand-kit/index.js.map +1 -1
  146. package/dist/cli/skills.d.ts.map +1 -1
  147. package/dist/cli/skills.js +18 -0
  148. package/dist/cli/skills.js.map +1 -1
  149. package/dist/client/AgentPanel.js +1 -1
  150. package/dist/client/AgentPanel.js.map +1 -1
  151. package/dist/client/MultiTabAssistantChat.js +1 -1
  152. package/dist/client/MultiTabAssistantChat.js.map +1 -1
  153. package/dist/client/extensions/portable-extension.d.ts +1 -1
  154. package/dist/client/extensions/portable-extension.d.ts.map +1 -1
  155. package/dist/client/extensions/portable-extension.js.map +1 -1
  156. package/dist/code-agents/transcript-normalizer.js +1 -1
  157. package/dist/code-agents/transcript-normalizer.js.map +1 -1
  158. package/dist/collab/awareness.d.ts +2 -2
  159. package/dist/collab/awareness.d.ts.map +1 -1
  160. package/dist/collab/routes.d.ts +1 -1
  161. package/dist/extensions/path.d.ts +1 -1
  162. package/dist/extensions/path.d.ts.map +1 -1
  163. package/dist/extensions/path.js.map +1 -1
  164. package/dist/file-upload/actions/upload-image.d.ts +2 -2
  165. package/dist/integrations/a2a-continuation-processor.js +1 -1
  166. package/dist/integrations/a2a-continuation-processor.js.map +1 -1
  167. package/dist/mcp/build-server.d.ts +1 -1
  168. package/dist/mcp/build-server.d.ts.map +1 -1
  169. package/dist/mcp/build-server.js +1 -1
  170. package/dist/mcp/build-server.js.map +1 -1
  171. package/dist/mcp/embed-app.d.ts.map +1 -1
  172. package/dist/mcp/embed-app.js +35 -1
  173. package/dist/mcp/embed-app.js.map +1 -1
  174. package/dist/notifications/routes.d.ts +3 -3
  175. package/dist/observability/routes.d.ts +8 -8
  176. package/dist/org/handlers.js +5 -5
  177. package/dist/org/handlers.js.map +1 -1
  178. package/dist/org/plugin.js +2 -2
  179. package/dist/org/plugin.js.map +1 -1
  180. package/dist/resources/handlers.d.ts +1 -1
  181. package/dist/scripts/call-agent.js +1 -1
  182. package/dist/scripts/call-agent.js.map +1 -1
  183. package/dist/scripts/db/safety.js +1 -1
  184. package/dist/scripts/db/safety.js.map +1 -1
  185. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  186. package/dist/server/auth.d.ts.map +1 -1
  187. package/dist/server/auth.js +3 -0
  188. package/dist/server/auth.js.map +1 -1
  189. package/dist/server/builder-design-systems.d.ts +28 -0
  190. package/dist/server/builder-design-systems.d.ts.map +1 -0
  191. package/dist/server/builder-design-systems.js +217 -0
  192. package/dist/server/builder-design-systems.js.map +1 -0
  193. package/dist/server/core-routes-plugin.d.ts +1 -0
  194. package/dist/server/core-routes-plugin.d.ts.map +1 -1
  195. package/dist/server/core-routes-plugin.js +20 -3
  196. package/dist/server/core-routes-plugin.js.map +1 -1
  197. package/dist/server/index.d.ts +1 -0
  198. package/dist/server/index.d.ts.map +1 -1
  199. package/dist/server/index.js +1 -0
  200. package/dist/server/index.js.map +1 -1
  201. package/dist/styles/agent-native.css +14 -7
  202. package/dist/usage/store.js +2 -2
  203. package/dist/usage/store.js.map +1 -1
  204. package/docs/content/locales/ar-SA/skills-guide.mdx +5 -0
  205. package/docs/content/locales/ar-SA/template-design.mdx +14 -1
  206. package/docs/content/locales/de-DE/skills-guide.mdx +5 -0
  207. package/docs/content/locales/de-DE/template-design.mdx +15 -1
  208. package/docs/content/locales/es-ES/skills-guide.mdx +5 -0
  209. package/docs/content/locales/es-ES/template-design.mdx +15 -1
  210. package/docs/content/locales/fr-FR/skills-guide.mdx +5 -0
  211. package/docs/content/locales/fr-FR/template-design.mdx +15 -1
  212. package/docs/content/locales/hi-IN/skills-guide.mdx +5 -0
  213. package/docs/content/locales/hi-IN/template-design.mdx +15 -1
  214. package/docs/content/locales/ja-JP/skills-guide.mdx +5 -0
  215. package/docs/content/locales/ja-JP/template-design.mdx +16 -1
  216. package/docs/content/locales/ko-KR/skills-guide.mdx +5 -0
  217. package/docs/content/locales/ko-KR/template-design.mdx +14 -1
  218. package/docs/content/locales/pt-BR/skills-guide.mdx +5 -0
  219. package/docs/content/locales/pt-BR/template-design.mdx +14 -1
  220. package/docs/content/locales/zh-CN/skills-guide.mdx +5 -0
  221. package/docs/content/locales/zh-CN/template-design.mdx +14 -1
  222. package/docs/content/locales/zh-TW/skills-guide.mdx +5 -0
  223. package/docs/content/locales/zh-TW/template-design.mdx +14 -1
  224. package/docs/content/skills-guide.mdx +5 -0
  225. package/docs/content/template-design.mdx +14 -1
  226. package/package.json +1 -1
  227. package/corpus/templates/design/actions/import-figma-file.ts +0 -166
  228. package/corpus/templates/slides/server/lib/fig-design-system.ts +0 -110
@@ -0,0 +1,242 @@
1
+ import { IconPhotoPlus, IconX } from "@tabler/icons-react";
2
+ import { useRef, useState } from "react";
3
+
4
+ import { Input } from "@/components/ui/input";
5
+ import {
6
+ Select,
7
+ SelectContent,
8
+ SelectItem,
9
+ SelectTrigger,
10
+ SelectValue,
11
+ } from "@/components/ui/select";
12
+ import {
13
+ Tooltip,
14
+ TooltipContent,
15
+ TooltipTrigger,
16
+ } from "@/components/ui/tooltip";
17
+ import { cn } from "@/lib/utils";
18
+
19
+ // ─── Types ───────────────────────────────────────────────────────────────────
20
+
21
+ export type ImageFitMode = "fill" | "fit" | "crop" | "tile";
22
+
23
+ export interface ImageFillValue {
24
+ url: string;
25
+ fit: ImageFitMode;
26
+ }
27
+
28
+ const FIT_MODES: Array<{ mode: ImageFitMode; label: string }> = [
29
+ { mode: "fill", label: "Fill" }, // i18n-ignore image fit mode
30
+ { mode: "fit", label: "Fit" }, // i18n-ignore image fit mode
31
+ { mode: "crop", label: "Crop" }, // i18n-ignore image fit mode
32
+ { mode: "tile", label: "Tile" }, // i18n-ignore image fit mode
33
+ ];
34
+
35
+ // ─── CSS serialization ─────────────────────────────────────────────────────────
36
+
37
+ const CHECKER_A = "#d4d4d4";
38
+ const CHECKERBOARD_IMAGE = `linear-gradient(45deg, ${CHECKER_A} 25%, transparent 25%), linear-gradient(-45deg, ${CHECKER_A} 25%, transparent 25%), linear-gradient(45deg, transparent 75%, ${CHECKER_A} 75%), linear-gradient(-45deg, transparent 75%, ${CHECKER_A} 75%)`;
39
+ const FIT_MARKER_RE =
40
+ /\/\*\s*agent-native-image-fit:(fill|fit|crop|tile)\s*\*\//i;
41
+
42
+ function imageFitMarker(fit: ImageFitMode): string {
43
+ return `/* agent-native-image-fit:${fit} */`;
44
+ }
45
+
46
+ /**
47
+ * Build the CSS `background` shorthand for an image fill.
48
+ * Maps Figma's fit semantics onto background-size / background-repeat:
49
+ * - Fill → cover, no-repeat
50
+ * - Fit → contain, no-repeat
51
+ * - Crop → cover, no-repeat (cropped to the box; identical CSS to Fill but
52
+ * kept distinct so the selection round-trips)
53
+ * - Tile → auto, repeat
54
+ */
55
+ export function imageFillToCss(value: ImageFillValue): string {
56
+ const url = value.url.trim();
57
+ if (!url) return "transparent";
58
+ const safeUrl = url.replace(/["')]/g, encodeURIComponent);
59
+ const image = `url("${safeUrl}")`;
60
+ switch (value.fit) {
61
+ case "fit":
62
+ return `${image} center / contain no-repeat ${imageFitMarker("fit")}`;
63
+ case "tile":
64
+ return `${image} top left / auto repeat ${imageFitMarker("tile")}`;
65
+ case "crop":
66
+ return `${image} center / cover no-repeat ${imageFitMarker("crop")}`;
67
+ case "fill":
68
+ default:
69
+ return `${image} center / cover no-repeat ${imageFitMarker("fill")}`;
70
+ }
71
+ }
72
+
73
+ const URL_RE = /url\((['"]?)([^'")]+)\1\)/i;
74
+
75
+ /** Extract the URL + fit mode from a CSS background value, if present. */
76
+ export function parseImageFillCss(value: string): ImageFillValue | null {
77
+ const match = value.match(URL_RE);
78
+ if (!match) return null;
79
+ const url = match[2];
80
+ const marker = value.match(FIT_MARKER_RE)?.[1] as ImageFitMode | undefined;
81
+ if (marker) return { url, fit: marker };
82
+ let fit: ImageFitMode = "fill";
83
+ if (/contain/i.test(value)) fit = "fit";
84
+ else if (/repeat(?!\s+no)/i.test(value) && !/no-repeat/i.test(value))
85
+ fit = "tile";
86
+ else if (/cover/i.test(value)) fit = "fill";
87
+ return { url, fit };
88
+ }
89
+
90
+ // ─── Component ─────────────────────────────────────────────────────────────────
91
+
92
+ export interface ImageFillControlsProps {
93
+ value: ImageFillValue;
94
+ onChange: (value: ImageFillValue) => void;
95
+ disabled?: boolean;
96
+ className?: string;
97
+ }
98
+
99
+ export function ImageFillControls({
100
+ value,
101
+ onChange,
102
+ disabled = false,
103
+ className,
104
+ }: ImageFillControlsProps) {
105
+ const fileInputRef = useRef<HTMLInputElement>(null);
106
+ const [urlDraft, setUrlDraft] = useState(value.url);
107
+
108
+ const commitUrl = () => {
109
+ onChange({ ...value, url: urlDraft.trim() });
110
+ };
111
+
112
+ const handleFilePick = (event: React.ChangeEvent<HTMLInputElement>) => {
113
+ const file = event.target.files?.[0];
114
+ if (!file) return;
115
+ const reader = new FileReader();
116
+ reader.onload = () => {
117
+ const dataUrl = typeof reader.result === "string" ? reader.result : "";
118
+ if (dataUrl) {
119
+ setUrlDraft(dataUrl);
120
+ onChange({ ...value, url: dataUrl });
121
+ }
122
+ };
123
+ reader.readAsDataURL(file);
124
+ // Allow re-selecting the same file later.
125
+ event.target.value = "";
126
+ };
127
+
128
+ return (
129
+ <div className={cn("space-y-1.5 px-3 pt-2 pb-2", className)}>
130
+ {/* ── Preview / drop target ─────────────────────────────────────────── */}
131
+ <div
132
+ className="relative h-24 w-full overflow-hidden rounded-md border border-border/60"
133
+ style={{
134
+ backgroundImage: value.url
135
+ ? `url("${value.url}")`
136
+ : CHECKERBOARD_IMAGE,
137
+ backgroundSize: value.url
138
+ ? value.fit === "fit"
139
+ ? "contain"
140
+ : value.fit === "tile"
141
+ ? "auto"
142
+ : "cover"
143
+ : "8px 8px, 8px 8px, 8px 8px, 8px 8px",
144
+ backgroundRepeat: value.fit === "tile" ? "repeat" : "no-repeat",
145
+ backgroundPosition: "center",
146
+ }}
147
+ >
148
+ {!value.url && (
149
+ <div className="absolute inset-0 flex flex-col items-center justify-center gap-1 text-muted-foreground">
150
+ <IconPhotoPlus className="size-5" />
151
+ <span className="text-[10px]">
152
+ {"Upload or paste a URL" /* i18n-ignore */}
153
+ </span>
154
+ </div>
155
+ )}
156
+ {value.url && (
157
+ <Tooltip>
158
+ <TooltipTrigger asChild>
159
+ <button
160
+ type="button"
161
+ aria-label={"Remove image" /* i18n-ignore */}
162
+ disabled={disabled}
163
+ onClick={() => {
164
+ setUrlDraft("");
165
+ onChange({ ...value, url: "" });
166
+ }}
167
+ className="absolute right-1 top-1 flex size-5 items-center justify-center rounded bg-black/50 text-white hover:bg-black/70 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
168
+ >
169
+ <IconX className="size-3" />
170
+ </button>
171
+ </TooltipTrigger>
172
+ <TooltipContent>{"Remove image" /* i18n-ignore */}</TooltipContent>
173
+ </Tooltip>
174
+ )}
175
+ </div>
176
+
177
+ {/* ── URL input + upload ────────────────────────────────────────────── */}
178
+ <div className="flex items-center gap-1">
179
+ <Input
180
+ value={urlDraft}
181
+ disabled={disabled}
182
+ placeholder={"Image URL" /* i18n-ignore */}
183
+ aria-label={"Image URL" /* i18n-ignore */}
184
+ spellCheck={false}
185
+ className="h-6 min-w-0 flex-1 rounded-md border-[var(--design-editor-control-border)] bg-[var(--design-editor-control-bg)] px-2 text-[11px]"
186
+ onChange={(event) => setUrlDraft(event.target.value)}
187
+ onBlur={commitUrl}
188
+ onKeyDown={(event) => {
189
+ if (event.key === "Enter") {
190
+ event.preventDefault();
191
+ commitUrl();
192
+ event.currentTarget.blur();
193
+ }
194
+ }}
195
+ />
196
+ <Tooltip>
197
+ <TooltipTrigger asChild>
198
+ <button
199
+ type="button"
200
+ disabled={disabled}
201
+ aria-label={"Upload image" /* i18n-ignore */}
202
+ onClick={() => fileInputRef.current?.click()}
203
+ className={cn(
204
+ "flex size-6 shrink-0 items-center justify-center rounded-md border border-[var(--design-editor-control-border)] bg-[var(--design-editor-control-bg)] text-muted-foreground hover:text-foreground",
205
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
206
+ disabled && "pointer-events-none opacity-40",
207
+ )}
208
+ >
209
+ <IconPhotoPlus className="size-3.5" />
210
+ </button>
211
+ </TooltipTrigger>
212
+ <TooltipContent>{"Upload image" /* i18n-ignore */}</TooltipContent>
213
+ </Tooltip>
214
+ <input
215
+ ref={fileInputRef}
216
+ type="file"
217
+ accept="image/*"
218
+ className="hidden"
219
+ onChange={handleFilePick}
220
+ />
221
+ </div>
222
+
223
+ {/* ── Fit mode dropdown ─────────────────────────────────────────────── */}
224
+ <Select
225
+ value={value.fit}
226
+ onValueChange={(v) => onChange({ ...value, fit: v as ImageFitMode })}
227
+ disabled={disabled}
228
+ >
229
+ <SelectTrigger className="h-6 w-full rounded-md border border-[var(--design-editor-control-border)] bg-[var(--design-editor-control-bg)] px-2 text-[11px] shadow-none focus:ring-0 focus:ring-offset-0 focus-visible:ring-2 focus-visible:ring-ring [&>svg]:size-3 [&>svg]:shrink-0">
230
+ <SelectValue />
231
+ </SelectTrigger>
232
+ <SelectContent className="text-[11px]">
233
+ {FIT_MODES.map(({ mode, label }) => (
234
+ <SelectItem key={mode} value={mode} className="text-[11px]">
235
+ {label}
236
+ </SelectItem>
237
+ ))}
238
+ </SelectContent>
239
+ </Select>
240
+ </div>
241
+ );
242
+ }
@@ -73,11 +73,13 @@ export function ScrubInput({
73
73
  );
74
74
  const [focused, setFocused] = useState(false);
75
75
  const [dragging, setDragging] = useState(false);
76
+ const inputRef = useRef<HTMLInputElement>(null);
76
77
  const skipNextBlurCommitRef = useRef(false);
77
78
  const dragRef = useRef({
78
79
  pointerId: -1,
79
80
  startX: 0,
80
81
  startValue: value,
82
+ hasDragged: false,
81
83
  });
82
84
 
83
85
  useEffect(() => {
@@ -109,7 +111,11 @@ export function ScrubInput({
109
111
  if (event.key === "ArrowUp" || event.key === "ArrowDown") {
110
112
  event.preventDefault();
111
113
  const direction = event.key === "ArrowUp" ? 1 : -1;
112
- setNextValue(value + direction * getScrubStepFromEvent(event, step), {
114
+ // getScrubStepFromEvent handles shiftKey (×10) and altKey (÷10).
115
+ // Cmd (metaKey) mirrors Shift for ×10 — Figma convention on macOS.
116
+ const baseStep = getScrubStepFromEvent(event, step);
117
+ const cmdMultiplier = event.metaKey && !event.shiftKey ? 10 : 1;
118
+ setNextValue(value + direction * baseStep * cmdMultiplier, {
113
119
  source: "keyboard",
114
120
  });
115
121
  return;
@@ -138,6 +144,7 @@ export function ScrubInput({
138
144
  pointerId: event.pointerId,
139
145
  startX: event.clientX,
140
146
  startValue: value,
147
+ hasDragged: false,
141
148
  };
142
149
  event.currentTarget.setPointerCapture(event.pointerId);
143
150
  setDragging(true);
@@ -147,6 +154,7 @@ export function ScrubInput({
147
154
  if (!dragging || dragRef.current.pointerId !== event.pointerId) return;
148
155
  const delta = event.clientX - dragRef.current.startX;
149
156
  if (delta === 0) return;
157
+ dragRef.current.hasDragged = true;
150
158
  const next =
151
159
  dragRef.current.startValue +
152
160
  delta *
@@ -160,7 +168,15 @@ export function ScrubInput({
160
168
  const endDrag = (event: PointerEvent<HTMLLabelElement>) => {
161
169
  if (dragRef.current.pointerId !== event.pointerId) return;
162
170
  event.currentTarget.releasePointerCapture(event.pointerId);
171
+ const wasDrag = dragRef.current.hasDragged;
163
172
  setDragging(false);
173
+ // If the pointer was released without dragging (a plain click), focus the
174
+ // input so the user can type immediately — mirrors Figma's label click
175
+ // behaviour (the event.preventDefault() in handlePointerDown blocks the
176
+ // native label→input focus transfer).
177
+ if (!wasDrag && !disabled) {
178
+ inputRef.current?.focus();
179
+ }
164
180
  };
165
181
 
166
182
  return (
@@ -174,9 +190,10 @@ export function ScrubInput({
174
190
  onPointerUp={endDrag}
175
191
  onPointerCancel={endDrag}
176
192
  className={cn(
177
- "flex h-6 w-20 shrink-0 cursor-ew-resize select-none items-center gap-1 text-[11px] text-muted-foreground",
178
- "hover:bg-[var(--design-editor-control-bg)] hover:rounded-sm",
179
- dragging && "text-foreground",
193
+ "flex h-6 w-20 shrink-0 cursor-ew-resize select-none items-center gap-1 rounded-sm text-[11px] text-muted-foreground transition-colors",
194
+ "hover:bg-[var(--design-editor-control-bg)] hover:text-foreground",
195
+ dragging &&
196
+ "bg-[var(--design-editor-control-bg)] text-foreground",
180
197
  disabled && "pointer-events-none cursor-not-allowed opacity-50",
181
198
  labelClassName,
182
199
  )}
@@ -185,9 +202,10 @@ export function ScrubInput({
185
202
  <span className="truncate">{label}</span>
186
203
  </Label>
187
204
  </TooltipTrigger>
188
- <TooltipContent>{`${label}. Drag to scrub, use arrows to step.`}</TooltipContent>
205
+ <TooltipContent>{`${label} drag to scrub · ↑↓ step · Shift ×10 · ⌥ fine`}</TooltipContent>
189
206
  </Tooltip>
190
207
  <Input
208
+ ref={inputRef}
191
209
  id={inputId}
192
210
  value={draft}
193
211
  disabled={disabled}
@@ -196,7 +214,7 @@ export function ScrubInput({
196
214
  aria-label={ariaLabel ?? label}
197
215
  onFocus={(event) => {
198
216
  setFocused(true);
199
- event.target.select();
217
+ event.currentTarget.select();
200
218
  }}
201
219
  onBlur={() => {
202
220
  setFocused(false);
@@ -208,7 +226,12 @@ export function ScrubInput({
208
226
  }}
209
227
  onChange={(event) => setDraft(event.target.value)}
210
228
  onKeyDown={handleKeyDown}
211
- className={cn("h-6 text-[11px] tabular-nums", inputClassName)}
229
+ className={cn(
230
+ // Compact Figma-style: h-6, 11px tabular text, ring-1 with no offset.
231
+ "h-6 text-[11px] tabular-nums",
232
+ "focus-visible:ring-1 focus-visible:ring-offset-0",
233
+ inputClassName,
234
+ )}
212
235
  />
213
236
  </div>
214
237
  );