@agent-native/core 0.80.11 → 0.81.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 (230) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +53 -0
  3. package/corpus/core/package.json +1 -1
  4. package/corpus/core/src/cli/design-connect.ts +419 -19
  5. package/corpus/core/src/client/AgentPanel.tsx +33 -1
  6. package/corpus/core/src/client/ConnectBuilderCard.tsx +1 -0
  7. package/corpus/core/src/client/chat/index.ts +1 -0
  8. package/corpus/core/src/client/chat/run-recovery.tsx +9 -7
  9. package/corpus/core/src/client/chat-view-transition.ts +33 -2
  10. package/corpus/core/src/client/composer/PromptComposer.tsx +21 -2
  11. package/corpus/core/src/client/index.ts +1 -0
  12. package/corpus/core/src/client/org/OrgSwitcher.tsx +26 -4
  13. package/corpus/core/src/client/sharing/ShareButton.tsx +3 -3
  14. package/corpus/core/src/client/use-agent-chat-home-handoff.ts +31 -3
  15. package/corpus/core/src/db/client.ts +29 -2
  16. package/corpus/core/src/provider-api/index.ts +29 -0
  17. package/corpus/core/src/server/core-routes-plugin.ts +58 -20
  18. package/corpus/core/src/styles/agent-native.css +54 -0
  19. package/corpus/core/src/usage/store.ts +2 -2
  20. package/corpus/templates/analytics/app/components/layout/Layout.tsx +118 -10
  21. package/corpus/templates/analytics/app/components/layout/layout-route-policy.ts +7 -0
  22. package/corpus/templates/analytics/app/global.css +8 -0
  23. package/corpus/templates/analytics/app/hooks/use-navigation-state.ts +7 -1
  24. package/corpus/templates/analytics/app/lib/chat-handoff.ts +34 -0
  25. package/corpus/templates/analytics/app/pages/Ask.tsx +9 -18
  26. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +2 -2
  27. package/corpus/templates/analytics/app/pages/sessions/SessionDetailPage.tsx +109 -21
  28. package/corpus/templates/analytics/app/pages/sessions/SessionsPage.tsx +4 -6
  29. package/corpus/templates/analytics/changelog/2026-06-30-ask-opens-a-fresh-chat-unless-you-were-just-chatting-and-das.md +6 -0
  30. package/corpus/templates/analytics/changelog/2026-06-30-dashboard-email-reports-are-more-reliable-and-use-cleaner.md +6 -0
  31. package/corpus/templates/analytics/changelog/2026-06-30-dashboard-toolbar-controls-use-a-quieter-share-button-and-ve.md +6 -0
  32. package/corpus/templates/analytics/changelog/2026-06-30-session-replay-rows-show-whole-minute-durations-in-the-playl.md +6 -0
  33. package/corpus/templates/analytics/changelog/2026-06-30-session-replays-now-render-captured-styling-and-keep-the-eve.md +6 -0
  34. package/corpus/templates/analytics/server/jobs/dashboard-report.ts +16 -3
  35. package/corpus/templates/analytics/server/lib/dashboard-report.ts +31 -14
  36. package/corpus/templates/assets/actions/open-asset-picker.ts +9 -0
  37. package/corpus/templates/assets/app/global.css +15 -0
  38. package/corpus/templates/assets/app/hooks/use-navigation-state.ts +4 -0
  39. package/corpus/templates/assets/app/routes/library.tsx +90 -16
  40. package/corpus/templates/assets/changelog/2026-06-30-the-assets-picker-now-supports-a-vertical-embedded-layout-fo.md +6 -0
  41. package/corpus/templates/clips/actions/finalize-recording.ts +1 -12
  42. package/corpus/templates/clips/app/routes/r.$recordingId.tsx +10 -5
  43. package/corpus/templates/clips/app/routes/share.$shareId.tsx +6 -8
  44. package/corpus/templates/clips/changelog/2026-06-30-extension-save-recovery-avoids-false-upload-failures.md +6 -0
  45. package/corpus/templates/clips/changelog/2026-06-30-longer-chrome-extension-recordings-now-keep-saving-reliably-.md +6 -0
  46. package/corpus/templates/clips/chrome-extension/public/manifest.json +1 -1
  47. package/corpus/templates/clips/chrome-extension/src/background.ts +19 -10
  48. package/corpus/templates/clips/chrome-extension/src/content-script.ts +38 -12
  49. package/corpus/templates/clips/chrome-extension/src/finalize-recovery.ts +224 -0
  50. package/corpus/templates/clips/chrome-extension/src/offscreen.ts +119 -16
  51. package/corpus/templates/clips/chrome-extension/src/overlay.ts +16 -0
  52. package/corpus/templates/clips/server/lib/recording-upload-state.ts +48 -0
  53. package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/abort.post.ts +9 -1
  54. package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/chunk.post.ts +63 -13
  55. package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/status.get.ts +74 -0
  56. package/corpus/templates/design/.agents/skills/design-systems/SKILL.md +32 -0
  57. package/corpus/templates/design/AGENTS.md +27 -0
  58. package/corpus/templates/design/CODE_NATIVE_DESIGN_STUDIO_IMPLEMENTATION_PLAN.md +1 -1
  59. package/corpus/templates/design/DESIGN-STUDIO-PLAN.md +5 -5
  60. package/corpus/templates/design/actions/apply-component-prop-edit.ts +63 -20
  61. package/corpus/templates/design/actions/apply-motion-edit.ts +67 -69
  62. package/corpus/templates/design/actions/apply-shader-fill.ts +148 -56
  63. package/corpus/templates/design/actions/capture-design-state.ts +27 -4
  64. package/corpus/templates/design/actions/connect-localhost.ts +59 -22
  65. package/corpus/templates/design/actions/export-zip.ts +23 -8
  66. package/corpus/templates/design/actions/generate-design.ts +53 -1
  67. package/corpus/templates/design/actions/get-component-details.ts +5 -19
  68. package/corpus/templates/design/actions/get-motion-timeline.ts +135 -10
  69. package/corpus/templates/design/actions/grant-localhost-write-consent.ts +116 -0
  70. package/corpus/templates/design/actions/import-design-tokens.ts +521 -0
  71. package/corpus/templates/design/actions/index-design-tokens.ts +10 -1
  72. package/corpus/templates/design/actions/insert-design-native-asset.ts +268 -0
  73. package/corpus/templates/design/actions/insert-figma-library-asset.ts +233 -0
  74. package/corpus/templates/design/actions/list-design-extensions.ts +32 -10
  75. package/corpus/templates/design/actions/list-design-native-assets.ts +105 -0
  76. package/corpus/templates/design/actions/list-figma-library-assets.ts +292 -0
  77. package/corpus/templates/design/actions/migrate-board-objects-to-file.ts +238 -0
  78. package/corpus/templates/design/actions/navigate.ts +18 -3
  79. package/corpus/templates/design/actions/provider-api-catalog.ts +2 -2
  80. package/corpus/templates/design/actions/provider-api-docs.ts +1 -1
  81. package/corpus/templates/design/actions/provider-api-request.ts +3 -3
  82. package/corpus/templates/design/actions/revoke-localhost-write-consent.ts +48 -0
  83. package/corpus/templates/design/actions/run-design-audit.ts +53 -4
  84. package/corpus/templates/design/actions/run-design-extension-action.ts +1 -1
  85. package/corpus/templates/design/actions/view-screen.ts +1 -1
  86. package/corpus/templates/design/actions/write-local-file.ts +213 -0
  87. package/corpus/templates/design/app/components/design/DesignCanvas.tsx +230 -3308
  88. package/corpus/templates/design/app/components/design/DesignEditorSkeleton.tsx +30 -20
  89. package/corpus/templates/design/app/components/design/DesignExtensionsPanel.tsx +776 -287
  90. package/corpus/templates/design/app/components/design/EditPanel.tsx +212 -208
  91. package/corpus/templates/design/app/components/design/LayersPanel.tsx +14 -2
  92. package/corpus/templates/design/app/components/design/LocalhostWriteConsentDialog.tsx +161 -0
  93. package/corpus/templates/design/app/components/design/MotionDock.tsx +235 -266
  94. package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +490 -55
  95. package/corpus/templates/design/app/components/design/ReviewPanel.tsx +43 -62
  96. package/corpus/templates/design/app/components/design/StatesPanel.tsx +34 -61
  97. package/corpus/templates/design/app/components/design/TokensPanel.tsx +251 -0
  98. package/corpus/templates/design/app/components/design/bridge/codegen.ts +125 -0
  99. package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +4037 -0
  100. package/corpus/templates/design/app/components/design/bridge/embedded-wheel.bridge.ts +62 -0
  101. package/corpus/templates/design/app/components/design/bridge/hit-test.bridge.ts +272 -0
  102. package/corpus/templates/design/app/components/design/bridge/nav.bridge.ts +116 -0
  103. package/corpus/templates/design/app/components/design/bridge/sample.bridge.ts +24 -0
  104. package/corpus/templates/design/app/components/design/bridge/shader-fill-preview.bridge.ts +78 -0
  105. package/corpus/templates/design/app/components/design/bridge/tsconfig.json +17 -0
  106. package/corpus/templates/design/app/components/design/bridge/tweak.bridge.ts +28 -0
  107. package/corpus/templates/design/app/components/design/bridge/zoom.bridge.ts +43 -0
  108. package/corpus/templates/design/app/components/design/inspector/DesignColorPicker.tsx +45 -16
  109. package/corpus/templates/design/app/components/design/inspector/ImageFillControls.tsx +88 -9
  110. package/corpus/templates/design/app/components/editor/PromptDialog.tsx +94 -9
  111. package/corpus/templates/design/app/components/layout/Layout.tsx +19 -3
  112. package/corpus/templates/design/app/components/layout/Sidebar.tsx +1 -1
  113. package/corpus/templates/design/app/global.css +7 -0
  114. package/corpus/templates/design/app/hooks/use-navigation-state.ts +23 -0
  115. package/corpus/templates/design/app/i18n/zh-TW.ts +21 -0
  116. package/corpus/templates/design/app/i18n-data.ts +358 -0
  117. package/corpus/templates/design/app/lib/pending-generation.ts +1 -0
  118. package/corpus/templates/design/app/pages/DesignEditor.tsx +2402 -499
  119. package/corpus/templates/design/app/pages/Index.tsx +5 -1
  120. package/corpus/templates/design/changelog/2026-06-30-applying-the-one-click-fix-for-a-small-tap-target-accessibil.md +6 -0
  121. package/corpus/templates/design/changelog/2026-06-30-canvas-creation-screen-dragging-undo-redo-and-image-fill-edi.md +6 -0
  122. package/corpus/templates/design/changelog/2026-06-30-component-prop-edits-and-shader-fills-now-preserve-rapid-sam.md +6 -0
  123. package/corpus/templates/design/changelog/2026-06-30-design-can-browse-figma-library-components-and-insert-them-i.md +6 -0
  124. package/corpus/templates/design/changelog/2026-06-30-design-now-has-a-figma-style-left-rail-for-file-agent-assets.md +6 -0
  125. package/corpus/templates/design/changelog/2026-06-30-design-now-has-native-components-embedded-media-and-figma-im.md +6 -0
  126. package/corpus/templates/design/changelog/2026-06-30-design-preview-now-includes-a-publish-app-waitlist-option-fr.md +6 -0
  127. package/corpus/templates/design/changelog/2026-06-30-design-s-left-rail-now-matches-the-sidebar-chrome-and-uses-t.md +6 -0
  128. package/corpus/templates/design/changelog/2026-06-30-design-s-tools-create-menu-now-stays-compact-instead-of-rend.md +6 -0
  129. package/corpus/templates/design/changelog/2026-06-30-design-starter-prompts-now-begin-generating-immediately-and-.md +6 -0
  130. package/corpus/templates/design/changelog/2026-06-30-design-tools-now-use-a-quieter-figma-style-list-layout.md +6 -0
  131. package/corpus/templates/design/changelog/2026-06-30-drawing-rectangles-on-the-all-screens-canvas-no-longer-flash.md +6 -0
  132. package/corpus/templates/design/changelog/2026-06-30-fixed-code-native-design-studio-edits-so-component-props-mot.md +6 -0
  133. package/corpus/templates/design/changelog/2026-06-30-fixed-visual-editing-hover-overlays-and-spacing-handles-so-s.md +6 -0
  134. package/corpus/templates/design/changelog/2026-06-30-motion-edits-now-save-automatically-without-a-separate-css-a.md +6 -0
  135. package/corpus/templates/design/changelog/2026-06-30-motion-timelines-now-reopen-as-editable-tracks-after-writing.md +6 -0
  136. package/corpus/templates/design/changelog/2026-06-30-new-design-prompts-with-uploaded-screenshots-work-reliably-o.md +6 -0
  137. package/corpus/templates/design/changelog/2026-06-30-screen-previews-in-all-screens-view-now-keep-rounded-outline.md +6 -0
  138. package/corpus/templates/design/changelog/2026-06-30-selection-outlines-now-sit-flush-on-rectangles-drawn-directl.md +6 -0
  139. package/corpus/templates/design/changelog/2026-06-30-share-options-use-a-tighter-design-editor-popover-with-compa.md +6 -0
  140. package/corpus/templates/design/changelog/2026-06-30-the-collapsed-motion-panel-now-stays-tucked-under-the-left-s.md +6 -0
  141. package/corpus/templates/design/changelog/2026-06-30-the-design-inspector-now-shows-states-and-review-in-a-tighte.md +6 -0
  142. package/corpus/templates/design/changelog/2026-06-30-the-sidebar-keeps-the-organization-picker-space-stable-while.md +6 -0
  143. package/corpus/templates/design/changelog/2026-06-30-tokens-can-now-be-imported-from-pasted-notes-code-files-fold.md +6 -0
  144. package/corpus/templates/design/e2e/helpers.ts +168 -65
  145. package/corpus/templates/design/package.json +3 -1
  146. package/corpus/templates/design/server/db/schema.ts +23 -0
  147. package/corpus/templates/design/server/handlers/uploads.ts +22 -1
  148. package/corpus/templates/design/server/lib/provider-api.ts +7 -4
  149. package/corpus/templates/design/server/lib/verify-write-grant.ts +108 -0
  150. package/corpus/templates/design/server/plugins/db.ts +39 -0
  151. package/corpus/templates/design/server/register-secrets.ts +38 -0
  152. package/corpus/templates/design/shared/board-file.ts +228 -0
  153. package/corpus/templates/design/shared/board-objects.ts +288 -0
  154. package/corpus/templates/design/shared/code-layer.ts +52 -0
  155. package/corpus/templates/design/shared/motion-compiler.ts +58 -7
  156. package/corpus/templates/design/shared/motion-timeline.ts +2 -2
  157. package/corpus/templates/design/tsconfig.json +2 -1
  158. package/corpus/templates/slides/AGENTS.md +9 -0
  159. package/corpus/templates/slides/app/components/editor/EditorToolbar.tsx +4 -0
  160. package/corpus/templates/slides/app/components/editor/ExportMenu.tsx +4 -24
  161. package/corpus/templates/slides/app/lib/export-pdf-client.ts +29 -20
  162. package/corpus/templates/slides/app/lib/export-pptx-client.ts +365 -0
  163. package/corpus/templates/slides/app/pages/DeckEditor.tsx +12 -9
  164. package/corpus/templates/slides/app/vite-env.d.ts +18 -0
  165. package/corpus/templates/slides/changelog/2026-06-30-powerpoint-downloads-now-preserve-the-rendered-slide-layout-.md +6 -0
  166. package/corpus/templates/slides/changelog/2026-06-30-unavailable-decks-open-in-a-cleaner-full-height-centered-sta.md +6 -0
  167. package/corpus/templates/slides/package.json +1 -0
  168. package/corpus/templates/slides/vite.config.ts +1 -0
  169. package/dist/cli/design-connect.d.ts +25 -0
  170. package/dist/cli/design-connect.d.ts.map +1 -1
  171. package/dist/cli/design-connect.js +357 -18
  172. package/dist/cli/design-connect.js.map +1 -1
  173. package/dist/client/AgentPanel.d.ts +1 -1
  174. package/dist/client/AgentPanel.d.ts.map +1 -1
  175. package/dist/client/AgentPanel.js +18 -3
  176. package/dist/client/AgentPanel.js.map +1 -1
  177. package/dist/client/ConnectBuilderCard.d.ts.map +1 -1
  178. package/dist/client/ConnectBuilderCard.js +1 -0
  179. package/dist/client/ConnectBuilderCard.js.map +1 -1
  180. package/dist/client/chat/index.d.ts +1 -1
  181. package/dist/client/chat/index.d.ts.map +1 -1
  182. package/dist/client/chat/index.js +1 -1
  183. package/dist/client/chat/index.js.map +1 -1
  184. package/dist/client/chat/run-recovery.d.ts.map +1 -1
  185. package/dist/client/chat/run-recovery.js +5 -5
  186. package/dist/client/chat/run-recovery.js.map +1 -1
  187. package/dist/client/chat-view-transition.d.ts +6 -0
  188. package/dist/client/chat-view-transition.d.ts.map +1 -1
  189. package/dist/client/chat-view-transition.js +23 -2
  190. package/dist/client/chat-view-transition.js.map +1 -1
  191. package/dist/client/composer/PromptComposer.d.ts.map +1 -1
  192. package/dist/client/composer/PromptComposer.js +3 -2
  193. package/dist/client/composer/PromptComposer.js.map +1 -1
  194. package/dist/client/index.d.ts +1 -1
  195. package/dist/client/index.d.ts.map +1 -1
  196. package/dist/client/index.js +1 -1
  197. package/dist/client/index.js.map +1 -1
  198. package/dist/client/org/OrgSwitcher.d.ts.map +1 -1
  199. package/dist/client/org/OrgSwitcher.js +11 -4
  200. package/dist/client/org/OrgSwitcher.js.map +1 -1
  201. package/dist/client/sharing/ShareButton.js +3 -3
  202. package/dist/client/sharing/ShareButton.js.map +1 -1
  203. package/dist/client/use-agent-chat-home-handoff.d.ts +8 -2
  204. package/dist/client/use-agent-chat-home-handoff.d.ts.map +1 -1
  205. package/dist/client/use-agent-chat-home-handoff.js +20 -6
  206. package/dist/client/use-agent-chat-home-handoff.js.map +1 -1
  207. package/dist/collab/routes.d.ts +1 -1
  208. package/dist/db/client.d.ts +1 -0
  209. package/dist/db/client.d.ts.map +1 -1
  210. package/dist/db/client.js +28 -2
  211. package/dist/db/client.js.map +1 -1
  212. package/dist/file-upload/actions/upload-image.d.ts +2 -2
  213. package/dist/notifications/routes.d.ts +2 -2
  214. package/dist/observability/routes.d.ts +8 -8
  215. package/dist/progress/routes.d.ts +1 -1
  216. package/dist/provider-api/index.d.ts +5 -4
  217. package/dist/provider-api/index.d.ts.map +1 -1
  218. package/dist/provider-api/index.js +28 -0
  219. package/dist/provider-api/index.js.map +1 -1
  220. package/dist/resources/handlers.d.ts +3 -3
  221. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  222. package/dist/server/core-routes-plugin.d.ts +25 -0
  223. package/dist/server/core-routes-plugin.d.ts.map +1 -1
  224. package/dist/server/core-routes-plugin.js +43 -18
  225. package/dist/server/core-routes-plugin.js.map +1 -1
  226. package/dist/server/transcribe-voice.d.ts +1 -1
  227. package/dist/styles/agent-native.css +54 -0
  228. package/dist/usage/store.js +2 -2
  229. package/dist/usage/store.js.map +1 -1
  230. package/package.json +1 -1
@@ -139,6 +139,10 @@ export interface DesignColorPickerProps {
139
139
  onChange: (value: string) => void;
140
140
  onPaintValueChange?: (value: string) => void;
141
141
  onImageFillChange?: (value: ImageFillValue) => void;
142
+ backgroundImage?: string;
143
+ backgroundSize?: string;
144
+ backgroundRepeat?: string;
145
+ backgroundPosition?: string;
142
146
  label?: string;
143
147
  opacity?: number;
144
148
  onOpacityChange?: (opacity: number) => void;
@@ -684,6 +688,10 @@ export function DesignColorPicker({
684
688
  onChange,
685
689
  onPaintValueChange,
686
690
  onImageFillChange,
691
+ backgroundImage,
692
+ backgroundSize,
693
+ backgroundRepeat,
694
+ backgroundPosition,
687
695
  label: _label,
688
696
  opacity,
689
697
  onOpacityChange,
@@ -723,6 +731,27 @@ export function DesignColorPicker({
723
731
  )
724
732
  ? blendMode
725
733
  : "normal";
734
+ const parsedImageFill = useMemo(
735
+ () =>
736
+ backgroundImage !== undefined ||
737
+ backgroundSize !== undefined ||
738
+ backgroundRepeat !== undefined ||
739
+ backgroundPosition !== undefined
740
+ ? parseImageFillCss({
741
+ backgroundImage: backgroundImage ?? value,
742
+ backgroundSize,
743
+ backgroundRepeat,
744
+ backgroundPosition,
745
+ })
746
+ : parseImageFillCss(value),
747
+ [
748
+ backgroundImage,
749
+ backgroundPosition,
750
+ backgroundRepeat,
751
+ backgroundSize,
752
+ value,
753
+ ],
754
+ );
726
755
 
727
756
  const [mode, setMode] = useState<DesignColorMode>("hex");
728
757
  const [hexDraft, setHexDraft] = useState(() => toDisplayHex(color));
@@ -748,10 +777,9 @@ export function DesignColorPicker({
748
777
  null,
749
778
  );
750
779
  const [selectedStopId, setSelectedStopId] = useState<string>("");
751
- const [imageFill, setImageFill] = useState<ImageFillValue>({
752
- url: "",
753
- fit: "fill",
754
- });
780
+ const [imageFill, setImageFill] = useState<ImageFillValue>(
781
+ () => parsedImageFill ?? { url: "", fit: "fill" },
782
+ );
755
783
  const [shaderDescriptor, setShaderDescriptor] =
756
784
  useState<ShaderDescriptor | null>(null);
757
785
 
@@ -796,16 +824,14 @@ export function DesignColorPicker({
796
824
 
797
825
  // Keep image-fill state synced when the incoming value is an image fill.
798
826
  useEffect(() => {
799
- if (effectivePaintType !== "image") return;
800
- const parsed = parseImageFillCss(value);
801
- if (
802
- parsed &&
803
- (parsed.url !== imageFill.url || parsed.fit !== imageFill.fit)
804
- ) {
805
- setImageFill(parsed);
806
- }
827
+ if (!parsedImageFill) return;
828
+ setImageFill((current) =>
829
+ current.url === parsedImageFill.url && current.fit === parsedImageFill.fit
830
+ ? current
831
+ : parsedImageFill,
832
+ );
807
833
  // eslint-disable-next-line react-hooks/exhaustive-deps
808
- }, [value, effectivePaintType]);
834
+ }, [parsedImageFill?.url, parsedImageFill?.fit]);
809
835
 
810
836
  // Ensure a selected stop id exists whenever a gradient is active.
811
837
  useEffect(() => {
@@ -987,11 +1013,14 @@ export function DesignColorPicker({
987
1013
  return;
988
1014
  }
989
1015
  if (nextType === "image") {
990
- if (onImageFillChange && imageFill.url) {
991
- onImageFillChange(imageFill);
1016
+ const nextImageFill = parsedImageFill ?? imageFill;
1017
+ if (onImageFillChange && nextImageFill.url) {
1018
+ onImageFillChange(nextImageFill);
992
1019
  return;
993
1020
  }
994
- emitPaintValue(imageFill.url ? imageFillToCss(imageFill) : "transparent");
1021
+ emitPaintValue(
1022
+ nextImageFill.url ? imageFillToCss(nextImageFill) : "transparent",
1023
+ );
995
1024
  return;
996
1025
  }
997
1026
  if (nextType === "video") {
@@ -25,6 +25,13 @@ export interface ImageFillValue {
25
25
  fit: ImageFitMode;
26
26
  }
27
27
 
28
+ export interface ImageFillBackgroundStyles {
29
+ backgroundImage: string;
30
+ backgroundSize?: string;
31
+ backgroundRepeat?: string;
32
+ backgroundPosition?: string;
33
+ }
34
+
28
35
  const FIT_MODES: Array<{ mode: ImageFitMode; label: string }> = [
29
36
  { mode: "fill", label: "Fill" }, // i18n-ignore image fit mode
30
37
  { mode: "fit", label: "Fit" }, // i18n-ignore image fit mode
@@ -98,8 +105,8 @@ export function imageFillToBackgroundStyles(
98
105
  backgroundPosition: "center",
99
106
  };
100
107
  }
101
- const safeUrl = url.replace(/["')]/g, encodeURIComponent);
102
- const backgroundImage = `url("${safeUrl}")`;
108
+ const safeUrl = escapeForQuotedUrl(url);
109
+ const backgroundImage = `url("${safeUrl}") ${imageFitMarker(value.fit)}`;
103
110
  switch (value.fit) {
104
111
  case "fit":
105
112
  return {
@@ -139,23 +146,95 @@ export function imageFillToBackgroundStyles(
139
146
  // group 3 — unquoted: url(...no-parens-or-quotes...)
140
147
  const URL_RE = /url\(\s*(?:"([^"]*)"|'([^']*)'|([^)'"]*?))\s*\)/i;
141
148
 
142
- /** Extract the URL + fit mode from a CSS background value, if present. */
143
- export function parseImageFillCss(value: string): ImageFillValue | null {
144
- const match = value.match(URL_RE);
149
+ function normalizeCssLayer(value: string | undefined): string {
150
+ return value?.trim().toLowerCase() ?? "";
151
+ }
152
+
153
+ function firstCssLayer(value: string | undefined): string {
154
+ const trimmed = value?.trim() ?? "";
155
+ if (!trimmed) return "";
156
+ let depth = 0;
157
+ for (let index = 0; index < trimmed.length; index += 1) {
158
+ const char = trimmed[index];
159
+ if (char === "(") depth += 1;
160
+ if (char === ")") depth = Math.max(0, depth - 1);
161
+ if (char === "," && depth === 0) return trimmed.slice(0, index).trim();
162
+ }
163
+ return trimmed;
164
+ }
165
+
166
+ function isStartBackgroundPosition(position: string): boolean {
167
+ if (!position) return false;
168
+ const normalized = position
169
+ .replace(/\s+/g, " ")
170
+ .replace(/\b0(?:\.0+)?(?:px|em|rem|%)\b/g, "0")
171
+ .trim();
172
+ const tokens = normalized.split(" ").filter(Boolean);
173
+ if (tokens.length === 1) {
174
+ return tokens[0] === "0" || tokens[0] === "top" || tokens[0] === "left";
175
+ }
176
+ const horizontal = tokens[0];
177
+ const vertical = tokens[1];
178
+ return (
179
+ (horizontal === "left" || horizontal === "0") &&
180
+ (vertical === "top" || vertical === "0")
181
+ );
182
+ }
183
+
184
+ function inferFitFromBackgroundStyles(
185
+ styles: ImageFillBackgroundStyles,
186
+ ): ImageFitMode | null {
187
+ const size = normalizeCssLayer(firstCssLayer(styles.backgroundSize));
188
+ const repeat = normalizeCssLayer(firstCssLayer(styles.backgroundRepeat));
189
+ const position = normalizeCssLayer(firstCssLayer(styles.backgroundPosition));
190
+
191
+ if (size === "contain") return "fit";
192
+ if (repeat === "repeat" || repeat === "repeat-x" || repeat === "repeat-y") {
193
+ return "tile";
194
+ }
195
+ if (size === "auto" && repeat === "repeat") return "tile";
196
+ if (
197
+ isStartBackgroundPosition(position) &&
198
+ (repeat === "repeat" || size === "auto")
199
+ ) {
200
+ return "tile";
201
+ }
202
+ if (size === "cover") return "fill";
203
+ return null;
204
+ }
205
+
206
+ /** Extract the URL + fit mode from CSS background input, if present. */
207
+ export function parseImageFillCss(value: string): ImageFillValue | null;
208
+ export function parseImageFillCss(
209
+ value: ImageFillBackgroundStyles,
210
+ ): ImageFillValue | null;
211
+ export function parseImageFillCss(
212
+ value: string | ImageFillBackgroundStyles,
213
+ ): ImageFillValue | null {
214
+ const styles = typeof value === "string" ? { backgroundImage: value } : value;
215
+ const match = styles.backgroundImage.match(URL_RE);
145
216
  if (!match) return null;
146
217
  const url = (match[1] ?? match[2] ?? match[3] ?? "").trim();
147
- const marker = value.match(FIT_MARKER_RE)?.[1] as ImageFitMode | undefined;
218
+ const marker = styles.backgroundImage.match(FIT_MARKER_RE)?.[1] as
219
+ | ImageFitMode
220
+ | undefined;
148
221
  if (marker) return { url, fit: marker };
222
+ const inferredFit = inferFitFromBackgroundStyles(styles);
223
+ if (inferredFit) return { url, fit: inferredFit };
149
224
  // Heuristic fallback when no marker comment is present (e.g. CSS pasted from
150
225
  // DevTools or Figma inspect). Note: "crop" and "fill" produce identical CSS
151
226
  // (center / cover no-repeat), so external CSS without the marker comment will
152
227
  // always parse as "fill". Crop mode is only recoverable via the proprietary
153
228
  // agent-native-image-fit marker written by imageFillToCss.
154
229
  let fit: ImageFitMode = "fill";
155
- if (/contain/i.test(value)) fit = "fit";
156
- else if (/repeat(?!\s+no)/i.test(value) && !/no-repeat/i.test(value))
230
+ const backgroundImage = styles.backgroundImage;
231
+ if (/contain/i.test(backgroundImage)) fit = "fit";
232
+ else if (
233
+ /repeat(?!\s+no)/i.test(backgroundImage) &&
234
+ !/no-repeat/i.test(backgroundImage)
235
+ )
157
236
  fit = "tile";
158
- else if (/cover/i.test(value)) fit = "fill";
237
+ else if (/cover/i.test(backgroundImage)) fit = "fill";
159
238
  return { url, fit };
160
239
  }
161
240
 
@@ -47,7 +47,9 @@ export interface UploadedFile {
47
47
  }
48
48
 
49
49
  const DEFAULT_ASSETS_PICKER_URL = "https://assets.agent-native.com/picker";
50
- const MAX_CHAT_IMAGE_ATTACHMENT_BYTES = 4 * 1024 * 1024;
50
+ const RAW_CHAT_IMAGE_ATTACHMENT_BYTES = 512 * 1024;
51
+ const MAX_TOTAL_CHAT_IMAGE_DATA_URL_BYTES = 3_000_000;
52
+ const DEFAULT_MAX_CHAT_IMAGE_DATA_URL_BYTES = 1_250_000;
51
53
  const CHAT_IMAGE_ATTACHMENT_TYPES = new Set([
52
54
  "image/gif",
53
55
  "image/jpeg",
@@ -55,6 +57,11 @@ const CHAT_IMAGE_ATTACHMENT_TYPES = new Set([
55
57
  "image/png",
56
58
  "image/webp",
57
59
  ]);
60
+ const IMAGE_COMPRESSION_PASSES = [
61
+ { maxDimension: 1400, jpegQuality: 0.76 },
62
+ { maxDimension: 1024, jpegQuality: 0.7 },
63
+ { maxDimension: 768, jpegQuality: 0.65 },
64
+ ];
58
65
 
59
66
  interface PickedAssetImagePayload {
60
67
  url?: unknown;
@@ -113,14 +120,11 @@ function pickedAssetContext(payload: unknown, url: string) {
113
120
  return lines.join("\n");
114
121
  }
115
122
 
116
- function readChatImageAttachment(file: File): Promise<string | null> {
117
- if (
118
- file.size > MAX_CHAT_IMAGE_ATTACHMENT_BYTES ||
119
- !CHAT_IMAGE_ATTACHMENT_TYPES.has(file.type.toLowerCase())
120
- ) {
121
- return Promise.resolve(null);
122
- }
123
+ function dataUrlBytes(dataUrl: string): number {
124
+ return new TextEncoder().encode(dataUrl).byteLength;
125
+ }
123
126
 
127
+ function readFileDataUrl(file: File): Promise<string | null> {
124
128
  return new Promise((resolve) => {
125
129
  const reader = new FileReader();
126
130
  reader.onload = () =>
@@ -130,6 +134,77 @@ function readChatImageAttachment(file: File): Promise<string | null> {
130
134
  });
131
135
  }
132
136
 
137
+ function loadImage(url: string): Promise<HTMLImageElement> {
138
+ return new Promise((resolve, reject) => {
139
+ const img = new Image();
140
+ img.onload = () => resolve(img);
141
+ img.onerror = () => reject(new Error("Failed to decode image"));
142
+ img.src = url;
143
+ });
144
+ }
145
+
146
+ async function compressImageAttachment(
147
+ file: File,
148
+ maxDimension: number,
149
+ jpegQuality: number,
150
+ ): Promise<string | null> {
151
+ if (typeof document === "undefined" || typeof Image === "undefined") {
152
+ return null;
153
+ }
154
+
155
+ const objectUrl = URL.createObjectURL(file);
156
+ try {
157
+ const image = await loadImage(objectUrl);
158
+ const ratio = Math.min(
159
+ maxDimension / image.naturalWidth,
160
+ maxDimension / image.naturalHeight,
161
+ 1,
162
+ );
163
+ const width = Math.max(1, Math.round(image.naturalWidth * ratio));
164
+ const height = Math.max(1, Math.round(image.naturalHeight * ratio));
165
+ const canvas = document.createElement("canvas");
166
+ canvas.width = width;
167
+ canvas.height = height;
168
+ const ctx = canvas.getContext("2d");
169
+ if (!ctx) return null;
170
+ ctx.drawImage(image, 0, 0, width, height);
171
+ return canvas.toDataURL("image/jpeg", jpegQuality);
172
+ } catch {
173
+ return null;
174
+ } finally {
175
+ URL.revokeObjectURL(objectUrl);
176
+ }
177
+ }
178
+
179
+ async function readChatImageAttachment(
180
+ file: File,
181
+ maxDataUrlBytes = DEFAULT_MAX_CHAT_IMAGE_DATA_URL_BYTES,
182
+ ): Promise<string | null> {
183
+ if (!CHAT_IMAGE_ATTACHMENT_TYPES.has(file.type.toLowerCase())) return null;
184
+
185
+ if (file.size <= RAW_CHAT_IMAGE_ATTACHMENT_BYTES) {
186
+ const raw = await readFileDataUrl(file);
187
+ if (raw && dataUrlBytes(raw) <= maxDataUrlBytes) return raw;
188
+ }
189
+
190
+ let fallback: string | null = null;
191
+ for (const pass of IMAGE_COMPRESSION_PASSES) {
192
+ const compressed = await compressImageAttachment(
193
+ file,
194
+ pass.maxDimension,
195
+ pass.jpegQuality,
196
+ );
197
+ if (!compressed) continue;
198
+ fallback = compressed;
199
+ if (dataUrlBytes(compressed) <= maxDataUrlBytes) {
200
+ return compressed;
201
+ }
202
+ }
203
+ return fallback && dataUrlBytes(fallback) <= maxDataUrlBytes
204
+ ? fallback
205
+ : null;
206
+ }
207
+
133
208
  interface AssetsPickerDialogProps {
134
209
  open: boolean;
135
210
  onOpenChange: (open: boolean) => void;
@@ -355,8 +430,18 @@ export default function PromptPopover({
355
430
  );
356
431
  }
357
432
  const uploaded = (await res.json()) as UploadedFile[];
433
+ const imageFileCount =
434
+ files.filter((file) =>
435
+ CHAT_IMAGE_ATTACHMENT_TYPES.has(file.type.toLowerCase()),
436
+ ).length || 1;
437
+ const maxImageDataUrlBytes = Math.min(
438
+ DEFAULT_MAX_CHAT_IMAGE_DATA_URL_BYTES,
439
+ Math.floor(MAX_TOTAL_CHAT_IMAGE_DATA_URL_BYTES / imageFileCount),
440
+ );
358
441
  const visualAttachments = await Promise.all(
359
- files.map((file) => readChatImageAttachment(file)),
442
+ files.map((file) =>
443
+ readChatImageAttachment(file, maxImageDataUrlBytes),
444
+ ),
360
445
  );
361
446
  return uploaded.map((file, index) =>
362
447
  visualAttachments[index]
@@ -39,9 +39,9 @@ const BARE_PREFIXES = ["/present/"];
39
39
 
40
40
  /**
41
41
  * Routes where the page renders its own toolbar instead of the global Header.
42
- * The Sidebar + AgentSidebar still render. The Header is hidden so the page
43
- * can supply a richer custom toolbar (e.g. DesignEditor mode/zoom/device,
44
- * shared ExtensionViewer / ExtensionsListPage chrome).
42
+ * The Header is hidden so the page can supply richer custom chrome (e.g.
43
+ * DesignEditor mode/zoom/device, shared ExtensionViewer / ExtensionsListPage
44
+ * chrome). The editor owns its agent surface inside its Figma-style left rail.
45
45
  */
46
46
  const EDITOR_PREFIXES = ["/design/", "/extensions"];
47
47
 
@@ -117,6 +117,22 @@ export function Layout({ children }: LayoutProps) {
117
117
  );
118
118
  }
119
119
 
120
+ if (isDesignEditor) {
121
+ return (
122
+ <HeaderActionsProvider>
123
+ <MobileSidebarContext.Provider value={null}>
124
+ <div className="agent-layout-shell flex h-screen w-full overflow-hidden bg-background text-foreground">
125
+ <div className="agent-layout-main-surface design-editor-main-surface flex h-full flex-1 flex-col overflow-hidden">
126
+ <main className="agent-native-app-main flex-1 overflow-hidden">
127
+ {children}
128
+ </main>
129
+ </div>
130
+ </div>
131
+ </MobileSidebarContext.Provider>
132
+ </HeaderActionsProvider>
133
+ );
134
+ }
135
+
120
136
  return (
121
137
  <HeaderActionsProvider>
122
138
  <MobileSidebarContext.Provider
@@ -163,7 +163,7 @@ export function Sidebar() {
163
163
  </div>
164
164
 
165
165
  <div className="px-3 py-2">
166
- <OrgSwitcher />
166
+ <OrgSwitcher reserveSpace />
167
167
  </div>
168
168
 
169
169
  <div className="px-3 py-2">
@@ -220,6 +220,13 @@
220
220
  box-shadow: none;
221
221
  }
222
222
 
223
+ .agent-layout-shell .design-editor-main-surface {
224
+ border-start-start-radius: 0;
225
+ border-end-start-radius: 0;
226
+ border-top-left-radius: 0;
227
+ border-bottom-left-radius: 0;
228
+ }
229
+
223
230
  .dark .agent-layout-shell,
224
231
  .dark .agent-sidebar-shell,
225
232
  .dark.agent-layout-shell,
@@ -12,6 +12,8 @@ export interface NavigationState {
12
12
  editorView?: "single" | "overview";
13
13
  inspectorTab?: "design" | "tweaks" | "extensions";
14
14
  inspector?: "design" | "tweaks" | "extensions";
15
+ leftPanel?: "file" | "agent" | "assets" | "tools" | "tokens";
16
+ panel?: "file" | "agent" | "assets" | "tools" | "tokens";
15
17
  fileId?: string;
16
18
  screenId?: string;
17
19
  filename?: string;
@@ -44,6 +46,8 @@ export interface DesignEditorCommand {
44
46
  viewMode?: "single" | "overview";
45
47
  inspectorTab?: "design" | "tweaks" | "extensions";
46
48
  inspector?: "design" | "tweaks" | "extensions";
49
+ leftPanel?: "file" | "agent" | "assets" | "tools" | "tokens";
50
+ panel?: "file" | "agent" | "assets" | "tools" | "tokens";
47
51
  fileId?: string;
48
52
  screenId?: string;
49
53
  filename?: string;
@@ -80,6 +84,19 @@ function normalizeInspectorTab(
80
84
  : undefined;
81
85
  }
82
86
 
87
+ function normalizeLeftPanel(
88
+ value: unknown,
89
+ ): "file" | "agent" | "assets" | "tools" | "tokens" | undefined {
90
+ if (value === "extensions") return "tools";
91
+ return value === "file" ||
92
+ value === "agent" ||
93
+ value === "assets" ||
94
+ value === "tools" ||
95
+ value === "tokens"
96
+ ? value
97
+ : undefined;
98
+ }
99
+
83
100
  function normalizeDesignTool(value: unknown): string | undefined {
84
101
  return typeof value === "string" &&
85
102
  DESIGN_EDITOR_TOOLS.includes(value as (typeof DESIGN_EDITOR_TOOLS)[number])
@@ -96,6 +113,8 @@ export function editorPathFromCommand(cmd: NavigationState): string | null {
96
113
  if (editorView) params.set("view", editorView);
97
114
  const inspectorTab = normalizeInspectorTab(cmd.inspectorTab ?? cmd.inspector);
98
115
  if (inspectorTab) params.set("inspector", inspectorTab);
116
+ const leftPanel = normalizeLeftPanel(cmd.leftPanel ?? cmd.panel);
117
+ if (leftPanel) params.set("panel", leftPanel);
99
118
  const screen = cmd.fileId ?? cmd.screenId ?? cmd.filename ?? cmd.screen;
100
119
  if (screen) params.set("screen", screen);
101
120
  if (typeof cmd.zoom === "number" && Number.isFinite(cmd.zoom)) {
@@ -117,6 +136,9 @@ export function editorCommandFromNavigate(
117
136
  if (cmd.view !== "editor" || !cmd.designId) return null;
118
137
  const editorView = normalizeEditorView(cmd.editorView);
119
138
  const inspectorTab = normalizeInspectorTab(cmd.inspectorTab ?? cmd.inspector);
139
+ const leftPanel =
140
+ normalizeLeftPanel(cmd.leftPanel ?? cmd.panel) ??
141
+ normalizeLeftPanel(cmd.inspectorTab ?? cmd.inspector);
120
142
  const command: DesignEditorCommand = {
121
143
  designId: cmd.designId,
122
144
  issuedAt: Date.now(),
@@ -124,6 +146,7 @@ export function editorCommandFromNavigate(
124
146
  };
125
147
  if (editorView) command.editorView = editorView;
126
148
  if (inspectorTab) command.inspectorTab = inspectorTab;
149
+ if (leftPanel) command.leftPanel = leftPanel;
127
150
  if (cmd.fileId) command.fileId = cmd.fileId;
128
151
  if (cmd.screenId) command.screenId = cmd.screenId;
129
152
  if (cmd.filename) command.filename = cmd.filename;
@@ -240,6 +240,17 @@ const messages = {
240
240
  },
241
241
  designEditor: {
242
242
  askAgent: "詢問代理",
243
+ applyToSource: "套用至來源",
244
+ applyToSourcePath: "套用至來源({{path}})",
245
+ writingToSource: "寫入中…",
246
+ leftRail: {
247
+ file: "檔案",
248
+ agent: "代理",
249
+ assets: "資源",
250
+ tools: "工具",
251
+ tokens: "權杖",
252
+ label: "設計工作區",
253
+ },
243
254
  tokens: {
244
255
  title: "權杖",
245
256
  newToken: "新增權杖",
@@ -247,6 +258,15 @@ const messages = {
247
258
  value: "值",
248
259
  add: "新增權杖",
249
260
  refresh: "重新整理權杖",
261
+ import: "匯入權杖",
262
+ importTitle: "匯入權杖",
263
+ importHint: "先使用 design.md、CSS、主題檔案或目前設計,再手動新增權杖。",
264
+ files: "檔案",
265
+ folder: "資料夾",
266
+ current: "目前",
267
+ pastePlaceholder: "--color-accent: #2563eb;\n主要顏色: #2563eb",
268
+ importPasted: "匯入貼上的權杖",
269
+ importedCount: "已匯入 {{count}} 個權杖",
250
270
  empty: "尚無權杖",
251
271
  emptyHint: "新增設計權杖以重複使用顏色、間距等。",
252
272
  applying: "套用中…",
@@ -466,6 +486,7 @@ const messages = {
466
486
  hide: "隱藏圖層",
467
487
  show: "顯示圖層",
468
488
  rename: "重新命名圖層",
489
+ deleteBoardObject: "刪除畫布物件",
469
490
  selected: "已選取 {{count}} 個",
470
491
  },
471
492
  multiScreenCanvas: {