@agent-native/core 0.90.1 → 0.90.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 (295) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +14 -0
  3. package/corpus/core/package.json +1 -1
  4. package/corpus/core/src/client/AgentPanel.tsx +1 -1
  5. package/corpus/core/src/client/rich-markdown-editor/useCollabReconcile.ts +11 -6
  6. package/corpus/core/src/client/use-action.ts +39 -2
  7. package/corpus/core/src/client/use-db-sync.ts +44 -18
  8. package/corpus/core/src/db/create-get-db.ts +32 -2
  9. package/corpus/core/src/server/framework-request-handler.ts +52 -4
  10. package/corpus/templates/assets/.agents/skills/asset-generation/SKILL.md +5 -0
  11. package/corpus/templates/assets/.agents/skills/logo-composite/SKILL.md +34 -6
  12. package/corpus/templates/assets/AGENTS.md +23 -4
  13. package/corpus/templates/assets/actions/_generation-preset-settings.ts +48 -0
  14. package/corpus/templates/assets/actions/_helpers.ts +8 -1
  15. package/corpus/templates/assets/actions/_preset-skeleton-validation.ts +64 -0
  16. package/corpus/templates/assets/actions/create-generation-preset.ts +11 -3
  17. package/corpus/templates/assets/actions/generate-image-batch.ts +7 -2
  18. package/corpus/templates/assets/actions/generate-image.ts +342 -25
  19. package/corpus/templates/assets/actions/get-library.ts +4 -3
  20. package/corpus/templates/assets/actions/navigate.ts +2 -1
  21. package/corpus/templates/assets/actions/refresh-generation-run.ts +5 -1
  22. package/corpus/templates/assets/actions/update-asset.ts +1 -0
  23. package/corpus/templates/assets/actions/update-generation-preset.ts +8 -1
  24. package/corpus/templates/assets/actions/view-screen.ts +8 -1
  25. package/corpus/templates/assets/app/components/generation/GenerationResults.tsx +9 -1
  26. package/corpus/templates/assets/app/hooks/use-image-model-menu.ts +1 -0
  27. package/corpus/templates/assets/app/hooks/use-navigation-state.ts +11 -0
  28. package/corpus/templates/assets/app/i18n/zh-TW.ts +64 -0
  29. package/corpus/templates/assets/app/i18n-data.ts +650 -0
  30. package/corpus/templates/assets/app/routes/asset.$id.tsx +2 -1
  31. package/corpus/templates/assets/app/routes/brand-kits.$id.tsx +44 -33
  32. package/corpus/templates/assets/app/routes/brand-kits.$id_.presets.$presetId.tsx +1474 -0
  33. package/corpus/templates/assets/app/routes/library.tsx +43 -1
  34. package/corpus/templates/assets/server/lib/generation.ts +366 -44
  35. package/corpus/templates/assets/server/lib/image-processing.ts +252 -0
  36. package/corpus/templates/assets/server/lib/preset-skeleton.ts +137 -0
  37. package/corpus/templates/assets/shared/api.ts +47 -0
  38. package/corpus/templates/clips/app/components/library/library-layout.tsx +7 -2
  39. package/corpus/templates/clips/app/components/library/sort-menu.tsx +1 -1
  40. package/corpus/templates/clips/app/components/recorder/recorder-engine.ts +114 -33
  41. package/corpus/templates/clips/app/routes/_app.library._index.tsx +27 -1
  42. package/corpus/templates/clips/app/routes/record.tsx +157 -68
  43. package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/abort.post.ts +5 -5
  44. package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/chunk.post.ts +20 -3
  45. package/corpus/templates/clips/shared/finalize-recovery.ts +23 -3
  46. package/corpus/templates/content/AGENTS.md +52 -52
  47. package/corpus/templates/content/actions/_builder-cms-read-client.ts +188 -8
  48. package/corpus/templates/content/actions/_builder-docs-client.ts +41 -3
  49. package/corpus/templates/content/actions/_database-source-utils.ts +998 -160
  50. package/corpus/templates/content/actions/_database-utils.ts +44 -4
  51. package/corpus/templates/content/actions/_notion-action-utils.ts +6 -2
  52. package/corpus/templates/content/actions/add-content-database-source-field-property.ts +23 -1
  53. package/corpus/templates/content/actions/attach-content-database-source.ts +85 -12
  54. package/corpus/templates/content/actions/change-content-database-source-role.ts +9 -1
  55. package/corpus/templates/content/actions/delete-content-database.ts +1 -0
  56. package/corpus/templates/content/actions/get-document.ts +11 -2
  57. package/corpus/templates/content/actions/list-content-databases.ts +57 -9
  58. package/corpus/templates/content/actions/prepare-builder-source-review.ts +23 -6
  59. package/corpus/templates/content/actions/refresh-content-database-source.ts +26 -3
  60. package/corpus/templates/content/actions/restore-content-database.ts +1 -0
  61. package/corpus/templates/content/actions/update-document.ts +43 -0
  62. package/corpus/templates/content/app/components/editor/BuilderBodySyncingNotice.tsx +21 -0
  63. package/corpus/templates/content/app/components/editor/DocumentDatabase.tsx +0 -1
  64. package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +70 -28
  65. package/corpus/templates/content/app/components/editor/DocumentProperties.tsx +69 -17
  66. package/corpus/templates/content/app/components/editor/VisualEditor.tsx +4 -4
  67. package/corpus/templates/content/app/components/editor/body-hydration.ts +112 -0
  68. package/corpus/templates/content/app/components/editor/builder-body-hydration-pump.ts +52 -0
  69. package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +9076 -2876
  70. package/corpus/templates/content/app/components/editor/database-sources/BuilderSourceReviewDialog.tsx +41 -1
  71. package/corpus/templates/content/app/components/editor/extensions/ImageBlock.tsx +36 -11
  72. package/corpus/templates/content/app/components/editor/previewDocumentSaveController.ts +21 -1
  73. package/corpus/templates/content/app/components/sidebar/DocumentSidebar.tsx +11 -1
  74. package/corpus/templates/content/app/components/sidebar/DocumentTreeItem.tsx +11 -8
  75. package/corpus/templates/content/app/global.css +41 -0
  76. package/corpus/templates/content/app/hooks/use-content-database.ts +346 -42
  77. package/corpus/templates/content/app/hooks/use-db-sync.ts +9 -0
  78. package/corpus/templates/content/app/hooks/use-documents.ts +47 -0
  79. package/corpus/templates/content/app/hooks/use-notion.ts +12 -10
  80. package/corpus/templates/content/app/i18n/zh-TW.ts +35 -0
  81. package/corpus/templates/content/app/i18n-data.ts +355 -0
  82. package/corpus/templates/content/app/root.tsx +22 -0
  83. package/corpus/templates/content/changelog/2026-07-01-builder-database-sources-keep-loading-rows-in-the-background.md +6 -0
  84. package/corpus/templates/content/changelog/2026-07-01-builder-source-attach-starts-with-usable-rows.md +6 -0
  85. package/corpus/templates/content/changelog/2026-07-01-builder-source-attachment-now-stays-connected-after-large.md +6 -0
  86. package/corpus/templates/content/changelog/2026-07-01-builder-source-refreshes-now-show-progress-for-large-docs-blog.md +6 -0
  87. package/corpus/templates/content/changelog/2026-07-01-database-pages-now-delete-to-trash-without-route-errors.md +6 -0
  88. package/corpus/templates/content/changelog/2026-07-02-builder-source-refreshes-recover-from-stalled-background-row.md +6 -0
  89. package/corpus/templates/content/changelog/2026-07-02-builder-sourced-rows-open-with-sync-protection.md +6 -0
  90. package/corpus/templates/content/changelog/2026-07-02-database-rows-and-pages-open-faster-from-recently-loaded-dat.md +6 -0
  91. package/corpus/templates/content/changelog/2026-07-02-preview-panels-close-reliably-narrow-sidebar-row-actions-sta.md +6 -0
  92. package/corpus/templates/content/changelog/2026-07-06-builder-sync-progress-no-longer-shows-continue.md +6 -0
  93. package/corpus/templates/content/shared/api.ts +13 -0
  94. package/corpus/templates/content/shared/builder-mdx.ts +16 -3
  95. package/corpus/templates/design/.agents/skills/design-generation/SKILL.md +23 -13
  96. package/corpus/templates/design/.agents/skills/responsive-breakpoints/SKILL.md +98 -0
  97. package/corpus/templates/design/.agents/skills/shader-fills/SKILL.md +162 -0
  98. package/corpus/templates/design/AGENTS.md +9 -2
  99. package/corpus/templates/design/actions/add-breakpoint.ts +6 -3
  100. package/corpus/templates/design/actions/apply-a11y-fix.ts +57 -37
  101. package/corpus/templates/design/actions/apply-component-prop-edit.ts +41 -20
  102. package/corpus/templates/design/actions/apply-motion-edit.ts +251 -38
  103. package/corpus/templates/design/actions/apply-shader-fill.ts +90 -66
  104. package/corpus/templates/design/actions/apply-visual-edit.ts +282 -55
  105. package/corpus/templates/design/actions/create-component.ts +29 -60
  106. package/corpus/templates/design/actions/create-file.ts +9 -2
  107. package/corpus/templates/design/actions/duplicate-design.ts +31 -2
  108. package/corpus/templates/design/actions/edit-design.ts +36 -38
  109. package/corpus/templates/design/actions/generate-design-title.ts +87 -0
  110. package/corpus/templates/design/actions/generate-design.ts +53 -18
  111. package/corpus/templates/design/actions/generate-screens.ts +108 -4
  112. package/corpus/templates/design/actions/get-motion-timeline.ts +35 -7
  113. package/corpus/templates/design/actions/insert-asset.ts +34 -29
  114. package/corpus/templates/design/actions/insert-design-native-asset.ts +129 -32
  115. package/corpus/templates/design/actions/insert-figma-library-asset.ts +35 -30
  116. package/corpus/templates/design/actions/present-design-variants.ts +7 -2
  117. package/corpus/templates/design/actions/remove-motion-timeline.ts +31 -36
  118. package/corpus/templates/design/actions/update-file.ts +190 -75
  119. package/corpus/templates/design/app/components/design/BreakpointBar.tsx +403 -0
  120. package/corpus/templates/design/app/components/design/CanvasContextMenu.tsx +469 -222
  121. package/corpus/templates/design/app/components/design/DesignCanvas.tsx +708 -53
  122. package/corpus/templates/design/app/components/design/DesignExtensionsPanel.tsx +83 -3
  123. package/corpus/templates/design/app/components/design/EditPanel.tsx +1091 -6948
  124. package/corpus/templates/design/app/components/design/LayersPanel.tsx +419 -85
  125. package/corpus/templates/design/app/components/design/MotionDock.tsx +1124 -103
  126. package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +2610 -1398
  127. package/corpus/templates/design/app/components/design/ReadOnlyDesignBanner.tsx +22 -0
  128. package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +2182 -235
  129. package/corpus/templates/design/app/components/design/bridge/motion-preview.bridge.ts +187 -11
  130. package/corpus/templates/design/app/components/design/bridge/shader-fill-preview.bridge.ts +376 -1
  131. package/corpus/templates/design/app/components/design/bridge/shader-runtime.bridge.ts +746 -0
  132. package/corpus/templates/design/app/components/design/canvas-primitive-style.ts +17 -0
  133. package/corpus/templates/design/app/components/design/code-workbench/SideBar.tsx +1 -1
  134. package/corpus/templates/design/app/components/design/code-workbench/explorer/FileTree.tsx +1 -3
  135. package/corpus/templates/design/app/components/design/code-workbench/quickinput/QuickInput.tsx +1 -1
  136. package/corpus/templates/design/app/components/design/edit-panel/appearance-properties.tsx +518 -0
  137. package/corpus/templates/design/app/components/design/edit-panel/code-inspect-helpers.tsx +562 -0
  138. package/corpus/templates/design/app/components/design/edit-panel/component-section.tsx +763 -0
  139. package/corpus/templates/design/app/components/design/edit-panel/document-colors.ts +94 -0
  140. package/corpus/templates/design/app/components/design/edit-panel/effects-properties.tsx +780 -0
  141. package/corpus/templates/design/app/components/design/edit-panel/element-classification.ts +446 -0
  142. package/corpus/templates/design/app/components/design/edit-panel/element-identity.ts +79 -0
  143. package/corpus/templates/design/app/components/design/edit-panel/field-primitives.tsx +315 -0
  144. package/corpus/templates/design/app/components/design/edit-panel/fill-gradient-helpers.ts +344 -0
  145. package/corpus/templates/design/app/components/design/edit-panel/fill-properties.tsx +594 -0
  146. package/corpus/templates/design/app/components/design/edit-panel/frame-presets-panel.tsx +119 -0
  147. package/corpus/templates/design/app/components/design/edit-panel/inspector-controls.tsx +248 -0
  148. package/corpus/templates/design/app/components/design/edit-panel/interaction-state-helpers.ts +45 -0
  149. package/corpus/templates/design/app/components/design/edit-panel/layout-properties.tsx +693 -0
  150. package/corpus/templates/design/app/components/design/edit-panel/panel-primitives.tsx +724 -0
  151. package/corpus/templates/design/app/components/design/edit-panel/position-helpers.ts +207 -0
  152. package/corpus/templates/design/app/components/design/edit-panel/position-layout-properties.tsx +628 -0
  153. package/corpus/templates/design/app/components/design/edit-panel/selection-helpers.ts +91 -0
  154. package/corpus/templates/design/app/components/design/edit-panel/stroke-properties.tsx +649 -0
  155. package/corpus/templates/design/app/components/design/edit-panel/style-change-types.ts +118 -0
  156. package/corpus/templates/design/app/components/design/edit-panel/style-options.ts +88 -0
  157. package/corpus/templates/design/app/components/design/edit-panel/transform-helpers.ts +100 -0
  158. package/corpus/templates/design/app/components/design/edit-panel/typography-helpers.ts +117 -0
  159. package/corpus/templates/design/app/components/design/edit-panel/typography-properties.tsx +501 -0
  160. package/corpus/templates/design/app/components/design/inspector/AutoLayoutMatrix.tsx +91 -40
  161. package/corpus/templates/design/app/components/design/inspector/BreakpointOverrideIndicator.tsx +82 -0
  162. package/corpus/templates/design/app/components/design/inspector/DesignColorPicker.tsx +189 -87
  163. package/corpus/templates/design/app/components/design/inspector/GlslShaderPanel.tsx +1097 -0
  164. package/corpus/templates/design/app/components/design/inspector/GradientEditor.tsx +44 -0
  165. package/corpus/templates/design/app/components/design/inspector/InteractionStatePanel.tsx +258 -0
  166. package/corpus/templates/design/app/components/design/inspector/MotionKeyframeDiamond.tsx +144 -0
  167. package/corpus/templates/design/app/components/design/inspector/ScrubInput.tsx +99 -9
  168. package/corpus/templates/design/app/components/design/inspector/frame-size-presets.ts +116 -0
  169. package/corpus/templates/design/app/components/design/inspector/index.ts +26 -0
  170. package/corpus/templates/design/app/components/design/inspector/scrub-input-utils.ts +29 -0
  171. package/corpus/templates/design/app/components/design/multi-screen/alt-hover-measurement.ts +171 -0
  172. package/corpus/templates/design/app/components/design/multi-screen/board-surface-html.ts +220 -0
  173. package/corpus/templates/design/app/components/design/multi-screen/chrome-transitions.ts +35 -0
  174. package/corpus/templates/design/app/components/design/multi-screen/cross-screen-drop.ts +134 -0
  175. package/corpus/templates/design/app/components/design/multi-screen/culling.ts +185 -0
  176. package/corpus/templates/design/app/components/design/multi-screen/gradient-overlay-geometry.ts +120 -0
  177. package/corpus/templates/design/app/components/design/multi-screen/types.ts +892 -0
  178. package/corpus/templates/design/app/components/design/multi-screen/vector-edit-geometry.ts +21 -0
  179. package/corpus/templates/design/app/components/design/types.ts +15 -0
  180. package/corpus/templates/design/app/components/editor/PromptDialog.tsx +109 -5
  181. package/corpus/templates/design/app/global.css +8 -8
  182. package/corpus/templates/design/app/hooks/useDesignHotkeys.ts +259 -2
  183. package/corpus/templates/design/app/i18n/zh-TW.ts +163 -9
  184. package/corpus/templates/design/app/i18n-breakpoints.ts +205 -0
  185. package/corpus/templates/design/app/i18n-data.ts +1664 -152
  186. package/corpus/templates/design/app/i18n-motion.ts +563 -0
  187. package/corpus/templates/design/app/lib/design-import.ts +73 -0
  188. package/corpus/templates/design/app/pages/Index.tsx +41 -22
  189. package/corpus/templates/design/changelog/2026-06-29-design-inspector-controls-now-match-figmas-softer-dark-chrome.md +1 -1
  190. package/corpus/templates/design/changelog/2026-06-30-design-now-has-a-figma-style-left-rail-for-file-agent-assets.md +1 -1
  191. package/corpus/templates/design/changelog/2026-06-30-design-tools-now-use-a-quieter-figma-style-list-layout.md +1 -1
  192. package/corpus/templates/design/changelog/2026-06-30-multi-select-outlines-now-stay-consistent-across-all-screens.md +1 -1
  193. package/corpus/templates/design/changelog/2026-07-03-canvas-interactions-now-match-figma-rotation-aware-resizing-.md +1 -1
  194. package/corpus/templates/design/changelog/2026-07-03-layers-panel-matches-figma-top-layer-on-top-drag-with-auto-s.md +1 -1
  195. package/corpus/templates/design/changelog/2026-07-05-designs-get-ai-generated-names-from-your-prompt.md +6 -0
  196. package/corpus/templates/design/changelog/2026-07-05-local-code-screens-keep-their-connection-when-duplicated-and.md +6 -0
  197. package/corpus/templates/design/changelog/2026-07-05-press-a-to-draw-screens-on-the-canvas-inside-a-screen-it-dr.md +6 -0
  198. package/corpus/templates/design/changelog/2026-07-06-3d-rotation-and-perspective-controls-on-every-element.md +6 -0
  199. package/corpus/templates/design/changelog/2026-07-06-ai-generated-and-duplicated-screens-now-get-their-editor-sel.md +6 -0
  200. package/corpus/templates/design/changelog/2026-07-06-applying-shaders-and-fills-in-quick-succession-can-no-longer.md +6 -0
  201. package/corpus/templates/design/changelog/2026-07-06-breakpoint-frames-in-the-overview-now-support-click-to-selec.md +6 -0
  202. package/corpus/templates/design/changelog/2026-07-06-breakpoint-segments-in-the-inspector-now-show-a-device-icon-.md +6 -0
  203. package/corpus/templates/design/changelog/2026-07-06-breakpoint-targeting-now-lives-in-one-compact-control-in-the.md +6 -0
  204. package/corpus/templates/design/changelog/2026-07-06-canvas-edits-can-no-longer-be-overwritten-by-stale-collabora.md +6 -0
  205. package/corpus/templates/design/changelog/2026-07-06-canvas-edits-inside-screens-save-reliably-again.md +6 -0
  206. package/corpus/templates/design/changelog/2026-07-06-cleaner-inspector-removed-the-ask-ai-box-from-the-design-tab.md +6 -0
  207. package/corpus/templates/design/changelog/2026-07-06-clicking-empty-canvas-space-now-deselects-an-element-inside-.md +6 -0
  208. package/corpus/templates/design/changelog/2026-07-06-dragging-an-element-onto-a-rectangle-now-nests-it-inside-wit.md +6 -0
  209. package/corpus/templates/design/changelog/2026-07-06-dragging-screens-and-objects-on-the-canvas-is-now-much-smoot.md +6 -0
  210. package/corpus/templates/design/changelog/2026-07-06-drawing-a-shape-or-text-on-the-canvas-no-longer-flashes-away.md +6 -0
  211. package/corpus/templates/design/changelog/2026-07-06-dropping-an-element-between-two-others-inside-a-screen-now-s.md +6 -0
  212. package/corpus/templates/design/changelog/2026-07-06-duplicating-a-screen-now-places-the-copy-exactly-where-you-d.md +6 -0
  213. package/corpus/templates/design/changelog/2026-07-06-edit-gradients-directly-on-elements-inside-screens-scale-str.md +6 -0
  214. package/corpus/templates/design/changelog/2026-07-06-edits-from-collaborators-and-the-agent-now-update-the-canvas.md +6 -0
  215. package/corpus/templates/design/changelog/2026-07-06-fixed-a-bug-where-most-style-position-and-text-edits-briefly.md +6 -0
  216. package/corpus/templates/design/changelog/2026-07-06-fixed-a-confusing-raw-error-message-when-a-file-upload-fails.md +6 -0
  217. package/corpus/templates/design/changelog/2026-07-06-fixed-a-rare-case-where-an-edit-could-be-silently-overwritte.md +6 -0
  218. package/corpus/templates/design/changelog/2026-07-06-fixed-inserted-assets-sometimes-disappearing-reappearing-or-.md +6 -0
  219. package/corpus/templates/design/changelog/2026-07-06-fixed-inspector-value-scrubbing-producing-erratic-numbers-an.md +6 -0
  220. package/corpus/templates/design/changelog/2026-07-06-fixed-padding-gap-and-min-max-size-scrubbing-not-updating-th.md +6 -0
  221. package/corpus/templates/design/changelog/2026-07-06-fixed-text-stroke-color-turning-black-and-glyphs-disappearin.md +6 -0
  222. package/corpus/templates/design/changelog/2026-07-06-fixed-the-collapsed-effects-and-stroke-inspector-sections-sh.md +6 -0
  223. package/corpus/templates/design/changelog/2026-07-06-fixed-the-new-design-dialog-closing-unexpectedly-when-using-.md +6 -0
  224. package/corpus/templates/design/changelog/2026-07-06-fixed-the-typography-section-missing-when-selecting-text-nes.md +6 -0
  225. package/corpus/templates/design/changelog/2026-07-06-full-view-no-longer-flashes-back-to-the-overview-on-entry-no.md +6 -0
  226. package/corpus/templates/design/changelog/2026-07-06-keyframe-animation-timeline-per-property-keyframes-easing-cu.md +6 -0
  227. package/corpus/templates/design/changelog/2026-07-06-moving-restyling-and-scrubbing-values-on-elements-in-ai-gene.md +6 -0
  228. package/corpus/templates/design/changelog/2026-07-06-padding-and-corner-radius-value-scrubbing-now-snaps-to-whole.md +6 -0
  229. package/corpus/templates/design/changelog/2026-07-06-padding-handles-now-only-resize-on-the-handle-line-itself-sh.md +6 -0
  230. package/corpus/templates/design/changelog/2026-07-06-preview-and-edit-hover-focus-and-pressed-states-with-real-cs.md +6 -0
  231. package/corpus/templates/design/changelog/2026-07-06-promoting-an-element-to-a-component-now-works-correctly-for-.md +6 -0
  232. package/corpus/templates/design/changelog/2026-07-06-reduced-url-history-churn-and-selection-overlay-flicker-whil.md +6 -0
  233. package/corpus/templates/design/changelog/2026-07-06-removed-the-decorative-dot-before-screen-names-in-the-overvi.md +6 -0
  234. package/corpus/templates/design/changelog/2026-07-06-responsive-breakpoints-edit-at-any-width-with-cascading-over.md +6 -0
  235. package/corpus/templates/design/changelog/2026-07-06-screen-labels-no-longer-turn-blue-when-you-hover-inside-the-.md +6 -0
  236. package/corpus/templates/design/changelog/2026-07-06-scrolling-a-screen-with-an-element-selected-stays-smooth-on-.md +6 -0
  237. package/corpus/templates/design/changelog/2026-07-06-selecting-multiple-elements-and-dragging-now-moves-the-whole.md +6 -0
  238. package/corpus/templates/design/changelog/2026-07-06-selection-borders-handles-and-value-readouts-now-stay-the-sa.md +6 -0
  239. package/corpus/templates/design/changelog/2026-07-06-selection-handles-and-editing-chrome-now-stay-a-constant-siz.md +6 -0
  240. package/corpus/templates/design/changelog/2026-07-06-shader-fills-and-effects-ai-authored-or-preset-glsl-shaders-.md +6 -0
  241. package/corpus/templates/design/changelog/2026-07-06-text-drawn-on-the-canvas-now-defaults-to-white-and-inter-in-.md +6 -0
  242. package/corpus/templates/design/changelog/2026-07-06-text-dropped-into-a-shape-now-stays-readable-instead-of-disa.md +6 -0
  243. package/corpus/templates/design/changelog/2026-07-06-the-add-tweak-controls-prompt-no-longer-jumps-to-the-corner-.md +6 -0
  244. package/corpus/templates/design/changelog/2026-07-06-the-padding-value-readout-now-stays-visible-while-you-hover-.md +6 -0
  245. package/corpus/templates/design/changelog/2026-07-06-the-selected-element-attachment-now-clears-from-the-chat-com.md +6 -0
  246. package/corpus/templates/design/changelog/2026-07-06-the-zoom-menu-is-more-compact-with-tight-rows-and-right-alig.md +6 -0
  247. package/corpus/templates/design/changelog/2026-07-06-undo-and-redo-no-longer-flash-the-screen-changes-now-apply-i.md +6 -0
  248. package/corpus/templates/design/changelog/2026-07-06-zoom-presets-like-zoom-to-50-now-stay-on-the-focused-screen-.md +6 -0
  249. package/corpus/templates/design/server/lib/import-design-files.ts +11 -2
  250. package/corpus/templates/design/server/source-workspace.ts +139 -59
  251. package/corpus/templates/design/shared/board-file.ts +11 -2
  252. package/corpus/templates/design/shared/breakpoint-media.ts +543 -0
  253. package/corpus/templates/design/shared/canvas-math.ts +354 -35
  254. package/corpus/templates/design/shared/code-layer.ts +277 -8
  255. package/corpus/templates/design/shared/design-state.ts +11 -4
  256. package/corpus/templates/design/shared/interaction-states.ts +548 -0
  257. package/corpus/templates/design/shared/motion-compiler.ts +186 -23
  258. package/corpus/templates/design/shared/motion-easing.ts +294 -0
  259. package/corpus/templates/design/shared/motion-timeline.ts +400 -34
  260. package/corpus/templates/design/shared/prompt-title.ts +95 -0
  261. package/corpus/templates/design/shared/responsive-classes.ts +448 -10
  262. package/corpus/templates/design/shared/screen-annotation.ts +46 -0
  263. package/corpus/templates/design/shared/shader-fills.ts +1009 -0
  264. package/corpus/templates/design/shared/shader-presets.ts +852 -4
  265. package/corpus/templates/slides/app/components/editor/EditorToolbar.tsx +15 -2
  266. package/corpus/templates/slides/app/components/editor/ImportButton.tsx +10 -1
  267. package/corpus/templates/slides/app/lib/upload-response.ts +71 -0
  268. package/dist/client/AgentPanel.js +1 -1
  269. package/dist/client/AgentPanel.js.map +1 -1
  270. package/dist/client/rich-markdown-editor/useCollabReconcile.d.ts.map +1 -1
  271. package/dist/client/rich-markdown-editor/useCollabReconcile.js +11 -6
  272. package/dist/client/rich-markdown-editor/useCollabReconcile.js.map +1 -1
  273. package/dist/client/use-action.d.ts +3 -0
  274. package/dist/client/use-action.d.ts.map +1 -1
  275. package/dist/client/use-action.js +34 -3
  276. package/dist/client/use-action.js.map +1 -1
  277. package/dist/client/use-db-sync.d.ts +4 -0
  278. package/dist/client/use-db-sync.d.ts.map +1 -1
  279. package/dist/client/use-db-sync.js +37 -18
  280. package/dist/client/use-db-sync.js.map +1 -1
  281. package/dist/collab/routes.d.ts +1 -1
  282. package/dist/collab/struct-routes.d.ts +1 -1
  283. package/dist/db/create-get-db.d.ts +25 -0
  284. package/dist/db/create-get-db.d.ts.map +1 -1
  285. package/dist/db/create-get-db.js +25 -2
  286. package/dist/db/create-get-db.js.map +1 -1
  287. package/dist/observability/routes.d.ts +3 -3
  288. package/dist/secrets/routes.d.ts +9 -9
  289. package/dist/server/framework-request-handler.d.ts.map +1 -1
  290. package/dist/server/framework-request-handler.js +37 -1
  291. package/dist/server/framework-request-handler.js.map +1 -1
  292. package/package.json +1 -1
  293. package/corpus/templates/design/app/components/design/inspector/InspectorAiActions.tsx +0 -153
  294. package/corpus/templates/design/app/hooks/useAgentEditRequest.ts +0 -133
  295. package/corpus/templates/design/app/pages/DesignEditor.tsx +0 -22924
@@ -0,0 +1,649 @@
1
+ import { useT } from "@agent-native/core/client";
2
+ import {
3
+ parseCssColor,
4
+ rgbaToCss,
5
+ withColorOpacity,
6
+ } from "@shared/color-utils";
7
+ import {
8
+ IconBorderStyle,
9
+ IconEye,
10
+ IconEyeOff,
11
+ IconMinus,
12
+ IconPlus,
13
+ } from "@tabler/icons-react";
14
+
15
+ import {
16
+ Select,
17
+ SelectContent,
18
+ SelectItem,
19
+ SelectTrigger,
20
+ SelectValue,
21
+ } from "@/components/ui/select";
22
+
23
+ import { ScrubInput } from "../inspector";
24
+ import type { DesignPaintType } from "../inspector/DesignColorPicker";
25
+ import type { ElementInfo } from "../types";
26
+ import { isTextElement } from "./element-classification";
27
+ import { commitStylePatch, FieldTrailer } from "./field-primitives";
28
+ import { SectionIconButton } from "./inspector-controls";
29
+ import { ColorInput, PanelSection } from "./panel-primitives";
30
+ import {
31
+ cssColorOrFallback,
32
+ cssLengthNumber,
33
+ outlineOffsetForPosition,
34
+ readStrokeOutlinePosition,
35
+ readTextStrokeStyle,
36
+ resolveTextStrokeColor,
37
+ roundToOneDecimal,
38
+ strokeHiddenByColor,
39
+ strokeIsVisible,
40
+ textStrokeIsVisible,
41
+ } from "./position-helpers";
42
+ import { isMixedValue } from "./selection-helpers";
43
+ import type {
44
+ BreakpointOverrideFieldContext,
45
+ MotionKeyframeFieldContext,
46
+ StyleChangeHandler,
47
+ StylesChangeHandler,
48
+ } from "./style-change-types";
49
+ import { STROKE_POSITION_OPTIONS } from "./style-options";
50
+
51
+ /**
52
+ * Paint types allowed for CSS properties with no clean gradient/image
53
+ * equivalent — currently strokes (`border`/`outline`), which are plain CSS
54
+ * colors with no `border-image`/layered-background trickery clean enough to
55
+ * support here. Passed as `supportedPaintTypes` so the picker never shows a
56
+ * tab that would silently discard its write.
57
+ */
58
+ const SOLID_ONLY_PAINT_TYPES: DesignPaintType[] = ["solid"];
59
+
60
+ type StrokeLayerKind = "border" | "outline";
61
+ type StrokePosition = "inside" | "outside" | "center";
62
+
63
+ function StrokeLayerControl({
64
+ kind,
65
+ visible,
66
+ color,
67
+ width,
68
+ styleValue,
69
+ outlineOffset,
70
+ onStyleChange,
71
+ onStylesChange,
72
+ onRemove,
73
+ element,
74
+ motionKeyframeContext,
75
+ breakpointOverrideContext,
76
+ }: {
77
+ kind: StrokeLayerKind;
78
+ visible: boolean;
79
+ color: string;
80
+ width: string;
81
+ styleValue: string;
82
+ /** Only meaningful when `kind === "outline"` — distinguishes outside vs
83
+ * center (see readStrokeOutlinePosition). Ignored for `kind === "border"`. */
84
+ outlineOffset?: string;
85
+ onStyleChange: StyleChangeHandler;
86
+ onStylesChange?: StylesChangeHandler;
87
+ onRemove: () => void;
88
+ /**
89
+ * Optional — only needed for the keyframe diamond / breakpoint override
90
+ * indicator. The motion catalog only tracks `border-color`/`border-width`
91
+ * (not `outline-color`/`outline-width`), so both affordances only ever
92
+ * render for `kind === "border"` regardless of whether these are passed.
93
+ */
94
+ element?: ElementInfo;
95
+ motionKeyframeContext?: MotionKeyframeFieldContext;
96
+ breakpointOverrideContext?: BreakpointOverrideFieldContext;
97
+ }) {
98
+ const t = useT();
99
+ const strokePositionOptions = STROKE_POSITION_OPTIONS.map((option) => ({
100
+ value: option.value,
101
+ label: t(`editPanel.labels.${option.key}`),
102
+ }));
103
+ const prefix = kind === "border" ? "border" : "outline";
104
+ const position: StrokePosition =
105
+ kind === "border"
106
+ ? "inside"
107
+ : readStrokeOutlinePosition(width, outlineOffset);
108
+
109
+ const movePosition = (next: string) => {
110
+ if (next === position) return;
111
+ const nextPosition = next as StrokePosition;
112
+ if (kind === "outline" && nextPosition !== "inside") {
113
+ // Outline → outline (outside ⇄ center): no property-family change,
114
+ // just re-point outline-offset. Single commit, no remove/re-add.
115
+ onStyleChange(
116
+ "outlineOffset",
117
+ outlineOffsetForPosition(nextPosition, width),
118
+ );
119
+ return;
120
+ }
121
+ const nextPrefix = nextPosition === "inside" ? "border" : "outline";
122
+ const patch: Record<string, string> = {
123
+ [`${nextPrefix}Color`]: color,
124
+ [`${nextPrefix}Width`]: width || "1px",
125
+ // Preserve the original border-style so a hidden stroke (style:none,
126
+ // kept visible as a row because width>0) stays hidden when its
127
+ // position moves. Only default to solid when there's no style at all.
128
+ [`${nextPrefix}Style`]: styleValue || "solid",
129
+ };
130
+ if (nextPrefix === "outline") {
131
+ patch.outlineOffset = outlineOffsetForPosition(
132
+ nextPosition === "center" ? "center" : "outside",
133
+ width || "1px",
134
+ );
135
+ }
136
+ // Clear the property family we're moving away from in the SAME commit
137
+ // (rather than a separate onRemove() call afterwards) so the position
138
+ // switch lands as one history step instead of two.
139
+ if (kind === "border") {
140
+ patch.borderWidth = "0px";
141
+ patch.borderStyle = "none";
142
+ } else {
143
+ patch.outlineWidth = "0px";
144
+ patch.outlineStyle = "none";
145
+ }
146
+ if (onStylesChange) {
147
+ onStylesChange(patch);
148
+ } else {
149
+ Object.entries(patch).forEach(([property, value]) =>
150
+ onStyleChange(property, value),
151
+ );
152
+ }
153
+ };
154
+
155
+ return (
156
+ <div className="space-y-1.5">
157
+ {/* design stroke row: [swatch+hex trigger (flex-1)] [eye] [remove] */}
158
+ <div className="group flex items-center gap-1.5">
159
+ <div className="min-w-0 flex-1">
160
+ <ColorInput
161
+ label=""
162
+ value={cssColorOrFallback(color, "#000000")}
163
+ onChange={(value, meta) =>
164
+ onStyleChange(`${prefix}Color`, value, meta)
165
+ }
166
+ supportedPaintTypes={SOLID_ONLY_PAINT_TYPES}
167
+ />
168
+ </div>
169
+ <SectionIconButton
170
+ label={
171
+ visible
172
+ ? t("editPanel.labels.hideLayer")
173
+ : t("editPanel.labels.showLayer")
174
+ }
175
+ onClick={() => {
176
+ // Hide/show by zeroing the stroke color's alpha (preserving its
177
+ // RGB channels — same durable, comment-free technique as the
178
+ // fill visibility toggle) instead of forcing borderStyle to
179
+ // "none"/"solid". Writing "none" would lose a dashed/dotted
180
+ // style permanently, since there is no round-trippable "unset"
181
+ // for that keyword once it's overwritten.
182
+ const parsed = parseCssColor(color);
183
+ if (visible) {
184
+ onStyleChange(
185
+ `${prefix}Color`,
186
+ parsed ? rgbaToCss(withColorOpacity(parsed, 0)) : "transparent",
187
+ );
188
+ return;
189
+ }
190
+ const restoredColor = parsed
191
+ ? rgbaToCss(withColorOpacity(parsed, 100))
192
+ : "#000000";
193
+ onStyleChange(`${prefix}Color`, restoredColor);
194
+ if (styleValue === "none") onStyleChange(`${prefix}Style`, "solid");
195
+ onStyleChange(
196
+ `${prefix}Width`,
197
+ width === "0px" ? "1px" : width || "1px",
198
+ );
199
+ }}
200
+ >
201
+ {visible ? (
202
+ <IconEye className="size-3.5" />
203
+ ) : (
204
+ <IconEyeOff className="size-3.5" />
205
+ )}
206
+ </SectionIconButton>
207
+ <SectionIconButton
208
+ label={t("editPanel.labels.removeLayer")}
209
+ onClick={onRemove}
210
+ >
211
+ <IconMinus className="size-3.5" />
212
+ </SectionIconButton>
213
+ {kind === "border" && element ? (
214
+ <FieldTrailer
215
+ element={element}
216
+ motionCssProperty="border-color"
217
+ motionKeyframeContext={motionKeyframeContext}
218
+ breakpointOverrideContext={breakpointOverrideContext}
219
+ hoverRevealClassName="opacity-0 group-hover:opacity-100"
220
+ />
221
+ ) : null}
222
+ </div>
223
+ {/* design stroke geometry: position + weight side by side */}
224
+ <div className="grid grid-cols-2 gap-1.5">
225
+ <Select value={position} onValueChange={movePosition}>
226
+ <SelectTrigger className="h-6 rounded-md border-[var(--design-editor-control-border)] bg-[var(--design-editor-control-bg)] px-1.5 !text-[11px] shadow-none focus:ring-1 focus:ring-[var(--design-editor-accent-color)]">
227
+ <SelectValue />
228
+ </SelectTrigger>
229
+ <SelectContent>
230
+ {strokePositionOptions.map((option) => (
231
+ <SelectItem
232
+ key={option.value}
233
+ value={option.value}
234
+ className="!text-[11px]"
235
+ >
236
+ {option.label}
237
+ </SelectItem>
238
+ ))}
239
+ </SelectContent>
240
+ </Select>
241
+ <div className="group/field relative min-w-0">
242
+ <ScrubInput
243
+ label={t("editPanel.labels.weight")}
244
+ ariaLabel={t("editPanel.labels.weight")}
245
+ icon={IconBorderStyle}
246
+ value={cssLengthNumber(width)}
247
+ onChange={(value, meta) => {
248
+ const nextWidth = `${Math.max(0, roundToOneDecimal(value))}px`;
249
+ // A centered outline's offset is derived from its own width
250
+ // (-width/2) — re-derive it in the same commit so the stroke
251
+ // stays centered as its weight changes, instead of drifting
252
+ // toward "outside" as a stale offset.
253
+ if (kind === "outline" && position === "center") {
254
+ const patch = {
255
+ outlineWidth: nextWidth,
256
+ outlineOffset: outlineOffsetForPosition("center", nextWidth),
257
+ };
258
+ if (onStylesChange) onStylesChange(patch, meta);
259
+ else
260
+ Object.entries(patch).forEach(([p, v]) =>
261
+ onStyleChange(p, v, meta),
262
+ );
263
+ return;
264
+ }
265
+ onStyleChange(`${prefix}Width`, nextWidth, meta);
266
+ }}
267
+ unit="px"
268
+ min={0}
269
+ precision={1}
270
+ className="gap-0"
271
+ labelClassName="h-6 w-6 justify-center gap-0 rounded-l-md rounded-r-none border border-r-0 border-[var(--design-editor-control-border)] bg-[var(--design-editor-control-bg)] !text-[11px] [&>span]:hidden"
272
+ inputClassName="h-6 rounded-l-none rounded-r-md border-[var(--design-editor-control-border)] bg-[var(--design-editor-control-bg)] shadow-none focus-visible:ring-1 focus-visible:ring-[var(--design-editor-accent-color)]"
273
+ />
274
+ {kind === "border" && element ? (
275
+ <FieldTrailer
276
+ element={element}
277
+ motionCssProperty="border-width"
278
+ motionKeyframeContext={motionKeyframeContext}
279
+ breakpointOverrideContext={breakpointOverrideContext}
280
+ className="absolute -top-3.5 right-0"
281
+ hoverRevealClassName="opacity-0 group-hover/field:opacity-100"
282
+ />
283
+ ) : null}
284
+ </div>
285
+ </div>
286
+ </div>
287
+ );
288
+ }
289
+
290
+ export function StrokeProperties({
291
+ element,
292
+ onStyleChange,
293
+ onStylesChange,
294
+ motionKeyframeContext,
295
+ breakpointOverrideContext,
296
+ }: {
297
+ element: ElementInfo;
298
+ onStyleChange: StyleChangeHandler;
299
+ onStylesChange?: StylesChangeHandler;
300
+ motionKeyframeContext?: MotionKeyframeFieldContext;
301
+ breakpointOverrideContext?: BreakpointOverrideFieldContext;
302
+ }) {
303
+ const t = useT();
304
+ const styles = element.computedStyles;
305
+ // R94 fix — Figma semantics: a text node's "Stroke" is the glyph outline
306
+ // (-webkit-text-stroke), never a box border. Route text nodes to their own
307
+ // control entirely so the border/outline logic below (and its `styles.color`
308
+ // fallback, which used to leak the removed fill color into the stroke) never
309
+ // runs for text at all.
310
+ if (isTextElement(element)) {
311
+ return (
312
+ <TextStrokeProperties
313
+ element={element}
314
+ onStyleChange={onStyleChange}
315
+ onStylesChange={onStylesChange}
316
+ />
317
+ );
318
+ }
319
+ // Visible requires: real width, style not "none" (legacy hide path), and
320
+ // color not zero-alpha (current hide path — see strokeHiddenByColor).
321
+ const borderVisible =
322
+ strokeIsVisible(styles.borderWidth, styles.borderStyle) &&
323
+ !strokeHiddenByColor(styles.borderColor);
324
+ const outlineVisible =
325
+ strokeIsVisible(styles.outlineWidth, styles.outlineStyle) &&
326
+ !strokeHiddenByColor(styles.outlineColor);
327
+ const strokeIsMixed = [
328
+ styles.borderWidth,
329
+ styles.borderStyle,
330
+ styles.borderColor,
331
+ styles.outlineWidth,
332
+ styles.outlineStyle,
333
+ styles.outlineColor,
334
+ styles.outlineOffset,
335
+ ].some(isMixedValue);
336
+ // Render the row whenever a stroke has been configured (non-zero width),
337
+ // even when its style is "none" (hidden). This mirrors Figma's behavior where
338
+ // hidden stroke rows remain present so the user can re-show them via the eye icon.
339
+ const borderExists = cssLengthNumber(styles.borderWidth) > 0;
340
+ const outlineExists = cssLengthNumber(styles.outlineWidth) > 0;
341
+ // Same empty-wrapper hazard as EffectsProperties: border and outline are
342
+ // separate top-level sibling conditionals, so when neither exists (and the
343
+ // mixed-value hint isn't showing either) JSX would still hand PanelSection
344
+ // a truthy array of `null`s as `children`, rendering an empty spacer div
345
+ // under the header instead of staying collapsed like Fill's empty state.
346
+ const hasStrokeContent = strokeIsMixed || borderExists || outlineExists;
347
+
348
+ return (
349
+ <PanelSection
350
+ title={t("editPanel.sections.stroke")}
351
+ actions={
352
+ <SectionIconButton
353
+ label={t("editPanel.labels.addLayer")}
354
+ onClick={() => {
355
+ if (strokeIsMixed) {
356
+ commitStylePatch(
357
+ {
358
+ borderWidth: "1px",
359
+ borderStyle: "solid",
360
+ borderColor: "#000000",
361
+ outlineWidth: "0px",
362
+ outlineStyle: "none",
363
+ },
364
+ onStyleChange,
365
+ onStylesChange,
366
+ );
367
+ return;
368
+ }
369
+ if (!borderVisible) {
370
+ // Restore full alpha before falling back to cssColorOrFallback
371
+ // — a border previously hidden via the eye toggle (zero-alpha,
372
+ // real RGB preserved) is not "transparent" by that helper's
373
+ // narrow literal check, so without this an "Add" click here
374
+ // could silently re-add an invisible border.
375
+ const existingBorderColor = styles.borderColor || styles.color;
376
+ const existingParsed = parseCssColor(existingBorderColor || "");
377
+ const borderColor = cssColorOrFallback(
378
+ existingParsed
379
+ ? rgbaToCss(withColorOpacity(existingParsed, 100))
380
+ : existingBorderColor,
381
+ "#000000",
382
+ );
383
+ commitStylePatch(
384
+ {
385
+ borderWidth: "1px",
386
+ borderStyle: "solid",
387
+ borderColor,
388
+ },
389
+ onStyleChange,
390
+ onStylesChange,
391
+ );
392
+ return;
393
+ }
394
+ if (outlineVisible) {
395
+ const outlineWidth = `${
396
+ Math.max(1, cssLengthNumber(styles.outlineWidth, 1)) + 1
397
+ }px`;
398
+ const outlineStyle =
399
+ styles.outlineStyle === "none"
400
+ ? "solid"
401
+ : styles.outlineStyle || "solid";
402
+ const outlineColor = cssColorOrFallback(
403
+ styles.outlineColor || styles.borderColor,
404
+ "#000000",
405
+ );
406
+ commitStylePatch(
407
+ {
408
+ outlineWidth,
409
+ outlineStyle,
410
+ outlineColor,
411
+ outlineOffset: styles.outlineOffset || "0px",
412
+ },
413
+ onStyleChange,
414
+ onStylesChange,
415
+ );
416
+ return;
417
+ }
418
+ commitStylePatch(
419
+ {
420
+ outlineWidth: "1px",
421
+ outlineStyle: "solid",
422
+ outlineColor: cssColorOrFallback(styles.borderColor, "#000000"),
423
+ outlineOffset: "0px",
424
+ },
425
+ onStyleChange,
426
+ onStylesChange,
427
+ );
428
+ }}
429
+ >
430
+ <IconPlus className="size-3.5" />
431
+ </SectionIconButton>
432
+ }
433
+ >
434
+ {hasStrokeContent ? (
435
+ <>
436
+ {strokeIsMixed ? (
437
+ <p className="px-1.5 py-2 !text-[11px] text-muted-foreground">
438
+ {
439
+ "Click + to replace mixed content" /* i18n-ignore figma mixed stroke hint */
440
+ }
441
+ </p>
442
+ ) : borderExists ? (
443
+ <StrokeLayerControl
444
+ kind="border"
445
+ visible={borderVisible}
446
+ color={styles.borderColor || "#000000"}
447
+ width={styles.borderWidth || "0px"}
448
+ styleValue={styles.borderStyle || "none"}
449
+ onStyleChange={onStyleChange}
450
+ onStylesChange={onStylesChange}
451
+ onRemove={() => {
452
+ if (onStylesChange) {
453
+ onStylesChange({ borderWidth: "0px", borderStyle: "none" });
454
+ } else {
455
+ onStyleChange("borderWidth", "0px");
456
+ }
457
+ }}
458
+ element={element}
459
+ motionKeyframeContext={motionKeyframeContext}
460
+ breakpointOverrideContext={breakpointOverrideContext}
461
+ />
462
+ ) : null}
463
+ {outlineExists ? (
464
+ <StrokeLayerControl
465
+ kind="outline"
466
+ visible={outlineVisible}
467
+ color={styles.outlineColor || styles.borderColor || "#000000"}
468
+ width={styles.outlineWidth || "0px"}
469
+ styleValue={styles.outlineStyle || "solid"}
470
+ outlineOffset={styles.outlineOffset || "0px"}
471
+ onStyleChange={onStyleChange}
472
+ onStylesChange={onStylesChange}
473
+ onRemove={() => {
474
+ if (onStylesChange) {
475
+ onStylesChange({ outlineWidth: "0px", outlineStyle: "none" });
476
+ } else {
477
+ onStyleChange("outlineWidth", "0px");
478
+ }
479
+ }}
480
+ element={element}
481
+ motionKeyframeContext={motionKeyframeContext}
482
+ breakpointOverrideContext={breakpointOverrideContext}
483
+ />
484
+ ) : null}
485
+ </>
486
+ ) : null}
487
+ </PanelSection>
488
+ );
489
+ }
490
+
491
+ /**
492
+ * R94 fix — text "Stroke" section: a real glyph outline via
493
+ * `-webkit-text-stroke-width` / `-webkit-text-stroke-color`, independent of
494
+ * fill (`color`). Removing the fill (FillProperties zeroing `color`'s alpha)
495
+ * must never hide the glyphs when a stroke is set, and must never coerce the
496
+ * stroke to black by reading `styles.color` — both bugs the box-border-based
497
+ * StrokeProperties path had for text. `-webkit-text-stroke` paints centered
498
+ * on the glyph edge (CSS has no outside/center/inside position control for
499
+ * it, unlike border/outline), so there is no position selector here.
500
+ */
501
+ function TextStrokeProperties({
502
+ element,
503
+ onStyleChange,
504
+ onStylesChange,
505
+ }: {
506
+ element: ElementInfo;
507
+ onStyleChange: StyleChangeHandler;
508
+ onStylesChange?: StylesChangeHandler;
509
+ }) {
510
+ const t = useT();
511
+ const styles = element.computedStyles;
512
+ // R94 fix — read through readTextStrokeStyle rather than the longhand
513
+ // keys directly: right after a reload/reselect the panel's computedStyles
514
+ // may only carry the browser-serialized `-webkit-text-stroke` shorthand
515
+ // (see readTextStrokeStyle's doc comment), not the two longhands a live
516
+ // DOM selection reports. Reading the longhands directly here would make
517
+ // the section falsely show "no stroke" for a stroke that is persisted and
518
+ // rendering.
519
+ const { width, color } = readTextStrokeStyle(styles);
520
+ const isMixed = [
521
+ styles.webkitTextStrokeWidth,
522
+ styles.webkitTextStrokeColor,
523
+ styles["-webkit-text-stroke"],
524
+ styles.WebkitTextStroke,
525
+ ].some(isMixedValue);
526
+ const strokeExists = cssLengthNumber(width) > 0;
527
+ const visible = textStrokeIsVisible(width, color);
528
+
529
+ return (
530
+ <PanelSection
531
+ title={t("editPanel.sections.stroke")}
532
+ actions={
533
+ <SectionIconButton
534
+ label={t("editPanel.labels.addLayer")}
535
+ onClick={() => {
536
+ commitStylePatch(
537
+ {
538
+ webkitTextStrokeWidth: "1px",
539
+ webkitTextStrokeColor: resolveTextStrokeColor(color),
540
+ },
541
+ onStyleChange,
542
+ onStylesChange,
543
+ );
544
+ }}
545
+ >
546
+ <IconPlus className="size-3.5" />
547
+ </SectionIconButton>
548
+ }
549
+ >
550
+ {isMixed ? (
551
+ <p className="px-1.5 py-2 !text-[11px] text-muted-foreground">
552
+ {
553
+ "Click + to replace mixed content" /* i18n-ignore figma mixed stroke hint */
554
+ }
555
+ </p>
556
+ ) : strokeExists ? (
557
+ <div className="space-y-1.5">
558
+ <div className="group flex items-center gap-1.5">
559
+ <div className="min-w-0 flex-1">
560
+ <ColorInput
561
+ label=""
562
+ value={resolveTextStrokeColor(color)}
563
+ onChange={(value, meta) =>
564
+ onStyleChange("-webkit-text-stroke-color", value, meta)
565
+ }
566
+ supportedPaintTypes={SOLID_ONLY_PAINT_TYPES}
567
+ />
568
+ </div>
569
+ <SectionIconButton
570
+ label={
571
+ visible
572
+ ? t("editPanel.labels.hideLayer")
573
+ : t("editPanel.labels.showLayer")
574
+ }
575
+ onClick={() => {
576
+ // Same durable, comment-free hide technique as border/outline
577
+ // and fill: zero the stroke color's alpha (preserving its RGB
578
+ // channels) instead of zeroing width, so re-showing restores
579
+ // the exact same color rather than defaulting back to black.
580
+ const parsed = parseCssColor(color);
581
+ if (visible) {
582
+ onStyleChange(
583
+ "-webkit-text-stroke-color",
584
+ parsed
585
+ ? rgbaToCss(withColorOpacity(parsed, 0))
586
+ : "transparent",
587
+ );
588
+ return;
589
+ }
590
+ const restoredColor = parsed
591
+ ? rgbaToCss(withColorOpacity(parsed, 100))
592
+ : "#000000";
593
+ commitStylePatch(
594
+ {
595
+ "-webkit-text-stroke-color": restoredColor,
596
+ "-webkit-text-stroke-width":
597
+ width === "0px" ? "1px" : width,
598
+ },
599
+ onStyleChange,
600
+ onStylesChange,
601
+ );
602
+ }}
603
+ >
604
+ {visible ? (
605
+ <IconEye className="size-3.5" />
606
+ ) : (
607
+ <IconEyeOff className="size-3.5" />
608
+ )}
609
+ </SectionIconButton>
610
+ <SectionIconButton
611
+ label={t("editPanel.labels.removeLayer")}
612
+ onClick={() => {
613
+ commitStylePatch(
614
+ {
615
+ "-webkit-text-stroke-width": "0px",
616
+ "-webkit-text-stroke-color": "transparent",
617
+ },
618
+ onStyleChange,
619
+ onStylesChange,
620
+ );
621
+ }}
622
+ >
623
+ <IconMinus className="size-3.5" />
624
+ </SectionIconButton>
625
+ </div>
626
+ <div className="grid grid-cols-2 gap-1.5">
627
+ <span aria-hidden="true" />
628
+ <ScrubInput
629
+ label={t("editPanel.labels.weight")}
630
+ ariaLabel={t("editPanel.labels.weight")}
631
+ icon={IconBorderStyle}
632
+ value={cssLengthNumber(width)}
633
+ onChange={(value, meta) => {
634
+ const nextWidth = `${Math.max(0, roundToOneDecimal(value))}px`;
635
+ onStyleChange("-webkit-text-stroke-width", nextWidth, meta);
636
+ }}
637
+ unit="px"
638
+ min={0}
639
+ precision={1}
640
+ className="gap-0"
641
+ labelClassName="h-6 w-6 justify-center gap-0 rounded-l-md rounded-r-none border border-r-0 border-[var(--design-editor-control-border)] bg-[var(--design-editor-control-bg)] !text-[11px] [&>span]:hidden"
642
+ inputClassName="h-6 rounded-l-none rounded-r-md border-[var(--design-editor-control-border)] bg-[var(--design-editor-control-bg)] shadow-none focus-visible:ring-1 focus-visible:ring-[var(--design-editor-accent-color)]"
643
+ />
644
+ </div>
645
+ </div>
646
+ ) : null}
647
+ </PanelSection>
648
+ );
649
+ }