@agent-native/core 0.92.10 → 0.92.12

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 (354) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +29 -0
  3. package/corpus/core/package.json +1 -1
  4. package/corpus/core/src/agent/production-agent.ts +214 -17
  5. package/corpus/core/src/agent/run-manager.ts +72 -14
  6. package/corpus/core/src/agent/run-store.ts +152 -27
  7. package/corpus/core/src/cli/design-connect.ts +433 -151
  8. package/corpus/core/src/cli/skills.ts +14 -0
  9. package/corpus/core/src/client/AssistantChat.tsx +25 -0
  10. package/corpus/core/src/client/MultiTabAssistantChat.tsx +40 -6
  11. package/corpus/core/src/client/agent-chat-adapter.ts +56 -12
  12. package/corpus/core/src/client/agent-chat.ts +165 -1
  13. package/corpus/core/src/client/chat/index.ts +9 -1
  14. package/corpus/core/src/client/extensions/EmbeddedExtension.tsx +38 -2
  15. package/corpus/core/src/client/index.ts +5 -0
  16. package/corpus/core/src/client/rich-markdown-editor/useCollabReconcile.ts +18 -7
  17. package/corpus/core/src/collab/awareness-store.ts +23 -12
  18. package/corpus/core/src/collab/client.ts +16 -5
  19. package/corpus/core/src/collab/index.ts +4 -0
  20. package/corpus/core/src/collab/recent-edits.ts +65 -2
  21. package/corpus/core/src/index.browser.ts +1 -0
  22. package/corpus/core/src/server/agent-chat-plugin.ts +98 -11
  23. package/corpus/core/src/server/builder-design-systems.ts +30 -4
  24. package/corpus/templates/clips/.agents/skills/ai-video-tools/SKILL.md +1 -1
  25. package/corpus/templates/clips/.agents/skills/video-sharing/SKILL.md +12 -6
  26. package/corpus/templates/clips/AGENTS.md +4 -0
  27. package/corpus/templates/clips/actions/create-recording.ts +2 -0
  28. package/corpus/templates/clips/actions/lib/create-recording-schema.ts +5 -3
  29. package/corpus/templates/clips/actions/request-transcript.ts +102 -33
  30. package/corpus/templates/clips/app/components/library/library-grid.tsx +1 -0
  31. package/corpus/templates/clips/app/components/player/share-dialog.tsx +59 -19
  32. package/corpus/templates/clips/app/components/player/transcript-panel.tsx +75 -9
  33. package/corpus/templates/clips/app/components/player/video-player.tsx +17 -12
  34. package/corpus/templates/clips/app/hooks/use-player-shortcuts.ts +1 -0
  35. package/corpus/templates/clips/app/i18n/ar-SA.ts +1 -0
  36. package/corpus/templates/clips/app/i18n/de-DE.ts +1 -0
  37. package/corpus/templates/clips/app/i18n/en-US.ts +1 -0
  38. package/corpus/templates/clips/app/i18n/es-ES.ts +1 -0
  39. package/corpus/templates/clips/app/i18n/fr-FR.ts +1 -0
  40. package/corpus/templates/clips/app/i18n/hi-IN.ts +1 -0
  41. package/corpus/templates/clips/app/i18n/ja-JP.ts +1 -0
  42. package/corpus/templates/clips/app/i18n/ko-KR.ts +1 -0
  43. package/corpus/templates/clips/app/i18n/pt-BR.ts +1 -0
  44. package/corpus/templates/clips/app/i18n/zh-CN.ts +1 -0
  45. package/corpus/templates/clips/app/i18n/zh-TW.ts +1 -0
  46. package/corpus/templates/clips/app/routes/r.$recordingId.tsx +70 -37
  47. package/corpus/templates/clips/app/routes/record.tsx +17 -4
  48. package/corpus/templates/clips/app/routes/share.$shareId.tsx +2 -0
  49. package/corpus/templates/clips/changelog/2026-07-09-desktop-popover-sits-closer-to-the-menu-bar.md +6 -0
  50. package/corpus/templates/clips/changelog/2026-07-09-desktop-recording-optimization-progress-stays-visible-until-the-clip-opens.md +6 -0
  51. package/corpus/templates/clips/changelog/2026-07-09-fixed-large-video-file-uploads-failing-before-their-resumabl.md +6 -0
  52. package/corpus/templates/clips/changelog/2026-07-09-fixed-new-video-uploads-defaulting-to-private.md +6 -0
  53. package/corpus/templates/clips/changelog/2026-07-09-long-recordings-now-get-enough-time-to-download-their-media-.md +6 -0
  54. package/corpus/templates/clips/changelog/2026-07-09-meeting-notes-no-longer-lowers-your-microphone-volume-for-ot.md +6 -0
  55. package/corpus/templates/clips/changelog/2026-07-09-on-mobile-tapping-a-playing-clip-reveals-playback-controls-w.md +6 -0
  56. package/corpus/templates/clips/changelog/2026-07-09-regenerate-any-clip-transcript-from-its-saved-recording-or-r.md +6 -0
  57. package/corpus/templates/clips/changelog/2026-07-09-slack-previews-now-show-the-clip-length-alongside-its-descri.md +6 -0
  58. package/corpus/templates/clips/changelog/2026-07-10-fixed-copying-agent-responses-and-transcript-text-from-recor.md +6 -0
  59. package/corpus/templates/clips/desktop/src/app.tsx +12 -2
  60. package/corpus/templates/clips/desktop/src/hooks/useMeetingTranscription.ts +8 -1
  61. package/corpus/templates/clips/desktop/src/lib/recorder.ts +8 -5
  62. package/corpus/templates/clips/desktop/src/lib/transcription-engine.ts +5 -5
  63. package/corpus/templates/clips/desktop/src/lib/voice-dictation.ts +3 -0
  64. package/corpus/templates/clips/desktop/src/styles.css +6 -3
  65. package/corpus/templates/clips/desktop/src-tauri/src/clips/mod.rs +29 -11
  66. package/corpus/templates/clips/desktop/src-tauri/src/native_screen.rs +1 -1
  67. package/corpus/templates/clips/desktop/src-tauri/src/system_audio.rs +103 -21
  68. package/corpus/templates/clips/desktop/src-tauri/src/whisper_speech.rs +117 -39
  69. package/corpus/templates/clips/server/lib/slack-unfurls.ts +20 -1
  70. package/corpus/templates/clips/server/lib/streaming-upload-mode.ts +10 -4
  71. package/corpus/templates/clips/shared/recording-core.ts +12 -0
  72. package/corpus/templates/content/AGENTS.md +7 -6
  73. package/corpus/templates/content/actions/_builder-cms-read-client.ts +82 -7
  74. package/corpus/templates/content/actions/_database-source-utils.ts +149 -30
  75. package/corpus/templates/content/actions/_document-flush.ts +132 -17
  76. package/corpus/templates/content/actions/_notion-action-utils.ts +15 -0
  77. package/corpus/templates/content/actions/add-content-database-source-field-property.ts +4 -7
  78. package/corpus/templates/content/actions/attach-content-database-source.ts +41 -17
  79. package/corpus/templates/content/actions/change-content-database-source-role.ts +46 -8
  80. package/corpus/templates/content/actions/create-and-link-notion-page.ts +2 -0
  81. package/corpus/templates/content/actions/link-notion-page.ts +2 -0
  82. package/corpus/templates/content/actions/pull-document.ts +5 -7
  83. package/corpus/templates/content/actions/pull-notion-page.ts +2 -0
  84. package/corpus/templates/content/actions/push-notion-page.ts +10 -0
  85. package/corpus/templates/content/actions/resolve-notion-sync-conflict.ts +2 -0
  86. package/corpus/templates/content/actions/suggest-source-join-key.ts +8 -1
  87. package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +88 -23
  88. package/corpus/templates/content/app/components/editor/VisualEditor.tsx +32 -2
  89. package/corpus/templates/content/app/hooks/use-notion.ts +6 -6
  90. package/corpus/templates/content/app/i18n/zh-TW.ts +1 -0
  91. package/corpus/templates/content/app/i18n-data.ts +18 -0
  92. package/corpus/templates/content/changelog/2026-07-09-builder-source-refreshes-now-keep-mapped-fields-such-as-topi.md +6 -0
  93. package/corpus/templates/content/changelog/2026-07-09-notion-conflict-choices-and-version-restores-preserve-docume.md +6 -0
  94. package/corpus/templates/content/server/lib/notion-sync.ts +80 -64
  95. package/corpus/templates/content/shared/api.ts +1 -0
  96. package/corpus/templates/design/.agents/skills/design-generation/SKILL.md +29 -2
  97. package/corpus/templates/design/.agents/skills/design-systems/SKILL.md +86 -0
  98. package/corpus/templates/design/.agents/skills/export-handoff/SKILL.md +57 -0
  99. package/corpus/templates/design/.agents/skills/visual-edit/SKILL.md +11 -0
  100. package/corpus/templates/design/AGENTS.md +45 -10
  101. package/corpus/templates/design/actions/add-localhost-screens.ts +66 -13
  102. package/corpus/templates/design/actions/apply-component-prop-edit.ts +45 -36
  103. package/corpus/templates/design/actions/apply-motion-edit.ts +25 -31
  104. package/corpus/templates/design/actions/apply-shader-fill.ts +29 -42
  105. package/corpus/templates/design/actions/detach-component-instance.ts +337 -0
  106. package/corpus/templates/design/actions/export-design-as-figma-svg.ts +219 -0
  107. package/corpus/templates/design/actions/generate-design.ts +79 -12
  108. package/corpus/templates/design/actions/generate-screens.ts +57 -12
  109. package/corpus/templates/design/actions/get-figma-styles.ts +115 -0
  110. package/corpus/templates/design/actions/go-to-main-component.ts +254 -0
  111. package/corpus/templates/design/actions/grant-localhost-write-consent.ts +4 -3
  112. package/corpus/templates/design/actions/import-design-source.ts +4 -31
  113. package/corpus/templates/design/actions/import-figma-clipboard.ts +134 -0
  114. package/corpus/templates/design/actions/import-figma-frame.ts +95 -0
  115. package/corpus/templates/design/actions/index-design-system-with-builder.ts +16 -1
  116. package/corpus/templates/design/actions/list-design-components.ts +104 -0
  117. package/corpus/templates/design/actions/list-figma-library-assets.ts +1 -24
  118. package/corpus/templates/design/actions/open-component-source.ts +2 -4
  119. package/corpus/templates/design/actions/present-design-variants.ts +412 -16
  120. package/corpus/templates/design/actions/preview-source-edit.ts +14 -1
  121. package/corpus/templates/design/actions/run-design-audit.ts +25 -3
  122. package/corpus/templates/design/actions/show-design-questions.ts +10 -0
  123. package/corpus/templates/design/actions/swap-component-instance.ts +544 -0
  124. package/corpus/templates/design/actions/take-design-screenshot.ts +45 -60
  125. package/corpus/templates/design/actions/write-local-file.ts +25 -1
  126. package/corpus/templates/design/app/components/design/CanvasContextMenu.tsx +152 -0
  127. package/corpus/templates/design/app/components/design/DesignCanvas.tsx +1077 -58
  128. package/corpus/templates/design/app/components/design/DesignEditorSkeleton.tsx +5 -5
  129. package/corpus/templates/design/app/components/design/DesignExtensionsPanel.tsx +73 -21
  130. package/corpus/templates/design/app/components/design/EditPanel.tsx +16 -11
  131. package/corpus/templates/design/app/components/design/KeyboardShortcutsPanel.tsx +364 -0
  132. package/corpus/templates/design/app/components/design/LayersPanel.tsx +180 -53
  133. package/corpus/templates/design/app/components/design/LocalhostWriteConsentDialog.tsx +0 -2
  134. package/corpus/templates/design/app/components/design/MotionDock.tsx +144 -21
  135. package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +1152 -167
  136. package/corpus/templates/design/app/components/design/QuestionFlow.tsx +27 -5
  137. package/corpus/templates/design/app/components/design/ReviewPanel.tsx +23 -2
  138. package/corpus/templates/design/app/components/design/TokensPanel.tsx +63 -49
  139. package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +1293 -54
  140. package/corpus/templates/design/app/components/design/bridge/embedded-wheel.bridge.ts +7 -0
  141. package/corpus/templates/design/app/components/design/bridge/hit-test.bridge.ts +6 -1
  142. package/corpus/templates/design/app/components/design/bridge/motion-preview.bridge.ts +23 -6
  143. package/corpus/templates/design/app/components/design/bridge/sample.bridge.ts +7 -0
  144. package/corpus/templates/design/app/components/design/code-workbench/CodeWorkbench.tsx +5 -0
  145. package/corpus/templates/design/app/components/design/code-workbench/editor/MonacoHost.tsx +14 -2
  146. package/corpus/templates/design/app/components/design/code-workbench/explorer/FileTree.tsx +37 -2
  147. package/corpus/templates/design/app/components/design/code-workbench/format/format-on-open-guard.ts +25 -0
  148. package/corpus/templates/design/app/components/design/code-workbench/format/format-on-open.ts +19 -1
  149. package/corpus/templates/design/app/components/design/code-workbench/model-registry.ts +43 -5
  150. package/corpus/templates/design/app/components/design/code-workbench/search/SearchView.tsx +27 -2
  151. package/corpus/templates/design/app/components/design/code-workbench/search/search-engine.ts +10 -1
  152. package/corpus/templates/design/app/components/design/code-workbench/store.tsx +19 -2
  153. package/corpus/templates/design/app/components/design/design-canvas/annotation-snapshot.ts +265 -0
  154. package/corpus/templates/design/app/components/design/design-canvas/annotation-submit.ts +16 -5
  155. package/corpus/templates/design/app/components/design/design-canvas/coordinate-transforms.ts +16 -2
  156. package/corpus/templates/design/app/components/design/design-canvas/embedded-frame.ts +7 -2
  157. package/corpus/templates/design/app/components/design/design-canvas/iframe-events.ts +3 -1
  158. package/corpus/templates/design/app/components/design/edit-panel/code-inspect-helpers.tsx +78 -8
  159. package/corpus/templates/design/app/components/design/edit-panel/component-section.tsx +451 -73
  160. package/corpus/templates/design/app/components/design/edit-panel/document-colors.ts +12 -4
  161. package/corpus/templates/design/app/components/design/edit-panel/effects-properties.tsx +398 -272
  162. package/corpus/templates/design/app/components/design/edit-panel/element-classification.ts +41 -7
  163. package/corpus/templates/design/app/components/design/edit-panel/element-identity.ts +16 -3
  164. package/corpus/templates/design/app/components/design/edit-panel/field-primitives.tsx +24 -4
  165. package/corpus/templates/design/app/components/design/edit-panel/fill-gradient-helpers.ts +222 -4
  166. package/corpus/templates/design/app/components/design/edit-panel/fill-properties.tsx +171 -78
  167. package/corpus/templates/design/app/components/design/edit-panel/inspector-controls.tsx +45 -5
  168. package/corpus/templates/design/app/components/design/edit-panel/layout-properties.tsx +299 -28
  169. package/corpus/templates/design/app/components/design/edit-panel/panel-primitives.tsx +120 -150
  170. package/corpus/templates/design/app/components/design/edit-panel/position-helpers.ts +53 -1
  171. package/corpus/templates/design/app/components/design/edit-panel/position-layout-properties.tsx +284 -117
  172. package/corpus/templates/design/app/components/design/edit-panel/selection-helpers.ts +81 -0
  173. package/corpus/templates/design/app/components/design/edit-panel/stroke-properties.tsx +19 -14
  174. package/corpus/templates/design/app/components/design/edit-panel/style-options.ts +7 -0
  175. package/corpus/templates/design/app/components/design/edit-panel/typography-helpers.ts +155 -0
  176. package/corpus/templates/design/app/components/design/edit-panel/typography-properties.tsx +268 -46
  177. package/corpus/templates/design/app/components/design/inspector/AutoLayoutMatrix.tsx +350 -31
  178. package/corpus/templates/design/app/components/design/inspector/ConstraintsWidget.tsx +13 -4
  179. package/corpus/templates/design/app/components/design/inspector/DesignColorPicker.tsx +233 -23
  180. package/corpus/templates/design/app/components/design/inspector/GradientEditor.tsx +72 -2
  181. package/corpus/templates/design/app/components/design/inspector/ScrubInput.tsx +51 -11
  182. package/corpus/templates/design/app/components/design/inspector/ShaderFillsPanel.tsx +84 -11
  183. package/corpus/templates/design/app/components/design/inspector/index.ts +3 -0
  184. package/corpus/templates/design/app/components/design/keyboard-shortcuts.ts +552 -0
  185. package/corpus/templates/design/app/components/design/multi-screen/board-surface-html.ts +157 -0
  186. package/corpus/templates/design/app/components/design/multi-screen/frame-geometry.ts +53 -0
  187. package/corpus/templates/design/app/components/design/multi-screen/overview-layout.ts +184 -0
  188. package/corpus/templates/design/app/components/design/multi-screen/primitive-drop-target.ts +403 -26
  189. package/corpus/templates/design/app/components/design/multi-screen/types.ts +20 -1
  190. package/corpus/templates/design/app/components/design/types.ts +33 -0
  191. package/corpus/templates/design/app/components/editor/PromptDialog.tsx +59 -7
  192. package/corpus/templates/design/app/components/visual-editor/CanvasCommentPins.tsx +119 -17
  193. package/corpus/templates/design/app/components/visual-editor/DrawOverlay.tsx +275 -63
  194. package/corpus/templates/design/app/hooks/use-question-flow.ts +14 -3
  195. package/corpus/templates/design/app/hooks/useDesignHotkeys.ts +67 -10
  196. package/corpus/templates/design/app/i18n/zh-TW.ts +91 -1
  197. package/corpus/templates/design/app/i18n-data.ts +1217 -10
  198. package/corpus/templates/design/app/i18n-keyboard-shortcuts.ts +577 -0
  199. package/corpus/templates/design/app/lib/agent-chat.ts +23 -0
  200. package/corpus/templates/design/app/lib/design-import.ts +24 -0
  201. package/corpus/templates/design/app/lib/figma-clipboard.ts +157 -0
  202. package/corpus/templates/design/app/lib/figma-svg-copy.ts +221 -0
  203. package/corpus/templates/design/app/pages/design-editor/code-layer-state.ts +143 -30
  204. package/corpus/templates/design/app/pages/design-editor/collab-sync.ts +15 -0
  205. package/corpus/templates/design/app/pages/design-editor/editor-session.ts +33 -0
  206. package/corpus/templates/design/app/pages/design-editor/editor-state.ts +38 -0
  207. package/corpus/templates/design/app/pages/design-editor/overview-annotation-context.ts +157 -0
  208. package/corpus/templates/design/app/pages/design-editor/overview-camera.ts +62 -6
  209. package/corpus/templates/design/app/pages/design-editor/pending-edits.ts +245 -0
  210. package/corpus/templates/design/app/pages/design-editor/react-semantic-handoff.ts +586 -0
  211. package/corpus/templates/design/app/pages/design-editor/screen-auto-layout.ts +169 -0
  212. package/corpus/templates/design/app/pages/design-editor/selection-state.ts +201 -1
  213. package/corpus/templates/design/changelog/2026-07-09-added-underline-strikethrough-and-text-case-uppercase-lowerc.md +6 -0
  214. package/corpus/templates/design/changelog/2026-07-09-annotate-now-draws-across-the-all-screens-canvas-without-swi.md +6 -0
  215. package/corpus/templates/design/changelog/2026-07-09-auto-layout-children-can-now-be-dragged-into-freeform-screen.md +6 -0
  216. package/corpus/templates/design/changelog/2026-07-09-board-shapes-now-stay-visible-and-nest-correctly-across-the-.md +6 -0
  217. package/corpus/templates/design/changelog/2026-07-09-component-source-navigation-now-opens-reliably-from-the-insp.md +6 -0
  218. package/corpus/templates/design/changelog/2026-07-09-cross-screen-local-react-layer-moves-now-hand-off-exact-sour.md +6 -0
  219. package/corpus/templates/design/changelog/2026-07-09-cross-screen-moves-undo-and-redo-now-keep-both-screens-stabl.md +6 -0
  220. package/corpus/templates/design/changelog/2026-07-09-detach-a-component-instance-into-plain-editable-eleme.md +6 -0
  221. package/corpus/templates/design/changelog/2026-07-09-dragging-layers-into-auto-layout-now-respects-ignore-auto-la.md +6 -0
  222. package/corpus/templates/design/changelog/2026-07-09-drawing-annotations-sent-to-the-agent-now-include-a-rendered.md +6 -0
  223. package/corpus/templates/design/changelog/2026-07-09-editor-sidebars-now-use-figma-matched-default-widths-for-a-r.md +6 -0
  224. package/corpus/templates/design/changelog/2026-07-09-empty-frames-and-rectangles-now-expose-full-auto-layout-cont.md +6 -0
  225. package/corpus/templates/design/changelog/2026-07-09-export-any-design-screen-as-a-real-vector-svg-for-figma-with.md +6 -0
  226. package/corpus/templates/design/changelog/2026-07-09-grid-auto-layout-now-uses-true-responsive-rows-and-columns-w.md +6 -0
  227. package/corpus/templates/design/changelog/2026-07-09-hidden-and-locked-screens-now-behave-consistently-between-la.md +6 -0
  228. package/corpus/templates/design/changelog/2026-07-09-image-fills-now-preserve-valid-urls-and-layered-fit-settings.md +6 -0
  229. package/corpus/templates/design/changelog/2026-07-09-import-figma-frames-as-pixel-accurate-design-screens-via-a-s.md +6 -0
  230. package/corpus/templates/design/changelog/2026-07-09-inspect-code-now-hides-internal-styling-metadata-and-wraps-l.md +6 -0
  231. package/corpus/templates/design/changelog/2026-07-09-inspector-blur-and-shadow-effects-now-preserve-other-effects.md +6 -0
  232. package/corpus/templates/design/changelog/2026-07-09-jump-to-a-components-other-instances-from-the-inspecto.md +6 -0
  233. package/corpus/templates/design/changelog/2026-07-09-keyboard-shortcuts-are-now-discoverable-in-a-figma-style-bot.md +6 -0
  234. package/corpus/templates/design/changelog/2026-07-09-layer-selection-now-keeps-cmd-or-ctrl-toggles-and-shift-rang.md +6 -0
  235. package/corpus/templates/design/changelog/2026-07-09-layers-dropped-into-auto-layout-now-participate-correctly-in.md +6 -0
  236. package/corpus/templates/design/changelog/2026-07-09-local-live-edit-screens-now-enter-and-leave-full-view-withou.md +6 -0
  237. package/corpus/templates/design/changelog/2026-07-09-local-preview-urls-now-live-in-the-inspector-instead-of-taki.md +6 -0
  238. package/corpus/templates/design/changelog/2026-07-09-local-react-layers-can-now-be-hidden-and-locked-from-layers-.md +6 -0
  239. package/corpus/templates/design/changelog/2026-07-09-local-react-layers-now-keep-exact-source-provenance-and-supp.md +6 -0
  240. package/corpus/templates/design/changelog/2026-07-09-local-react-visual-editing-now-loads-reliably-and-shows-live.md +6 -0
  241. package/corpus/templates/design/changelog/2026-07-09-paste-from-figma-cmd-v-now-imports-exact-editable-nodes-when.md +6 -0
  242. package/corpus/templates/design/changelog/2026-07-09-pressing-escape-after-selecting-a-screen-now-reveals-page-pr.md +6 -0
  243. package/corpus/templates/design/changelog/2026-07-09-right-click-overlapping-or-nested-objects-to-choose-the-exac.md +6 -0
  244. package/corpus/templates/design/changelog/2026-07-09-scale-edge-stretch-and-center-constraints-now-preserve-layer.md +6 -0
  245. package/corpus/templates/design/changelog/2026-07-09-screen-rows-can-now-be-reordered-in-layers-with-matching-can.md +6 -0
  246. package/corpus/templates/design/changelog/2026-07-09-screen-selection-stays-responsive-in-designs-with-hundreds-o.md +6 -0
  247. package/corpus/templates/design/changelog/2026-07-09-shift-a-now-enables-auto-layout-on-a-selected-screen-with-fl.md +6 -0
  248. package/corpus/templates/design/changelog/2026-07-09-swap-a-component-instance-for-another-from-the-libra.md +6 -0
  249. package/corpus/templates/design/changelog/2026-07-10-drawing-annotations-now-stay-available-to-retry-when-the.md +6 -0
  250. package/corpus/templates/design/e2e/global-setup.ts +3 -6
  251. package/corpus/templates/design/e2e/helpers.ts +27 -6
  252. package/corpus/templates/design/server/lib/design-to-figma-svg.ts +1412 -0
  253. package/corpus/templates/design/server/lib/figma-clipboard-match.ts +196 -0
  254. package/corpus/templates/design/server/lib/figma-node-import.ts +311 -0
  255. package/corpus/templates/design/server/lib/figma-node-to-html.ts +1334 -0
  256. package/corpus/templates/design/server/lib/figma-paste-fallback.ts +64 -0
  257. package/corpus/templates/design/server/lib/playwright-runtime.ts +78 -0
  258. package/corpus/templates/design/server/plugins/db.ts +41 -0
  259. package/corpus/templates/design/server/source-workspace.ts +64 -0
  260. package/corpus/templates/design/shared/board-file.ts +13 -2
  261. package/corpus/templates/design/shared/canvas-math.ts +231 -33
  262. package/corpus/templates/design/shared/code-layer.ts +122 -8
  263. package/corpus/templates/design/shared/component-library.ts +161 -0
  264. package/corpus/templates/design/shared/db-conflict.ts +27 -0
  265. package/corpus/templates/design/shared/design-source-capabilities.ts +2 -1
  266. package/corpus/templates/design/shared/figma-url.ts +149 -0
  267. package/corpus/templates/design/shared/pen-path.ts +16 -7
  268. package/corpus/templates/design/shared/source-mode.ts +1 -0
  269. package/corpus/templates/plan/app/components/plan/CanvasArea.tsx +7 -19
  270. package/corpus/templates/plan/changelog/2026-07-09-canvas-panning-now-keeps-zoom-fixed-even-at-the-edges.md +6 -0
  271. package/corpus/templates/slides/actions/index-design-system-with-builder.ts +16 -1
  272. package/corpus/templates/slides/app/context/DeckContext.tsx +311 -70
  273. package/corpus/templates/slides/changelog/2026-07-09-fixed-the-slide-rail-going-permanently-stale-during-a-long-a.md +6 -0
  274. package/dist/agent/production-agent.d.ts +82 -7
  275. package/dist/agent/production-agent.d.ts.map +1 -1
  276. package/dist/agent/production-agent.js +180 -17
  277. package/dist/agent/production-agent.js.map +1 -1
  278. package/dist/agent/run-manager.d.ts.map +1 -1
  279. package/dist/agent/run-manager.js +63 -14
  280. package/dist/agent/run-manager.js.map +1 -1
  281. package/dist/agent/run-store.d.ts +46 -0
  282. package/dist/agent/run-store.d.ts.map +1 -1
  283. package/dist/agent/run-store.js +129 -22
  284. package/dist/agent/run-store.js.map +1 -1
  285. package/dist/cli/design-connect.d.ts +6 -0
  286. package/dist/cli/design-connect.d.ts.map +1 -1
  287. package/dist/cli/design-connect.js +336 -139
  288. package/dist/cli/design-connect.js.map +1 -1
  289. package/dist/cli/skills.d.ts.map +1 -1
  290. package/dist/cli/skills.js +14 -0
  291. package/dist/cli/skills.js.map +1 -1
  292. package/dist/client/AssistantChat.d.ts +2 -0
  293. package/dist/client/AssistantChat.d.ts.map +1 -1
  294. package/dist/client/AssistantChat.js +19 -3
  295. package/dist/client/AssistantChat.js.map +1 -1
  296. package/dist/client/MultiTabAssistantChat.d.ts.map +1 -1
  297. package/dist/client/MultiTabAssistantChat.js +38 -18
  298. package/dist/client/MultiTabAssistantChat.js.map +1 -1
  299. package/dist/client/agent-chat-adapter.d.ts.map +1 -1
  300. package/dist/client/agent-chat-adapter.js +48 -12
  301. package/dist/client/agent-chat-adapter.js.map +1 -1
  302. package/dist/client/agent-chat.d.ts +60 -0
  303. package/dist/client/agent-chat.d.ts.map +1 -1
  304. package/dist/client/agent-chat.js +122 -1
  305. package/dist/client/agent-chat.js.map +1 -1
  306. package/dist/client/chat/index.d.ts +1 -1
  307. package/dist/client/chat/index.d.ts.map +1 -1
  308. package/dist/client/chat/index.js +1 -1
  309. package/dist/client/chat/index.js.map +1 -1
  310. package/dist/client/extensions/EmbeddedExtension.d.ts +12 -0
  311. package/dist/client/extensions/EmbeddedExtension.d.ts.map +1 -1
  312. package/dist/client/extensions/EmbeddedExtension.js +36 -2
  313. package/dist/client/extensions/EmbeddedExtension.js.map +1 -1
  314. package/dist/client/index.d.ts +1 -1
  315. package/dist/client/index.d.ts.map +1 -1
  316. package/dist/client/index.js +1 -1
  317. package/dist/client/index.js.map +1 -1
  318. package/dist/client/rich-markdown-editor/useCollabReconcile.d.ts.map +1 -1
  319. package/dist/client/rich-markdown-editor/useCollabReconcile.js +18 -6
  320. package/dist/client/rich-markdown-editor/useCollabReconcile.js.map +1 -1
  321. package/dist/collab/awareness-store.d.ts +5 -0
  322. package/dist/collab/awareness-store.d.ts.map +1 -1
  323. package/dist/collab/awareness-store.js +19 -12
  324. package/dist/collab/awareness-store.js.map +1 -1
  325. package/dist/collab/awareness.d.ts +2 -2
  326. package/dist/collab/awareness.d.ts.map +1 -1
  327. package/dist/collab/client.d.ts.map +1 -1
  328. package/dist/collab/client.js +13 -5
  329. package/dist/collab/client.js.map +1 -1
  330. package/dist/collab/index.d.ts +1 -0
  331. package/dist/collab/index.d.ts.map +1 -1
  332. package/dist/collab/index.js +1 -0
  333. package/dist/collab/index.js.map +1 -1
  334. package/dist/collab/recent-edits.d.ts +3 -1
  335. package/dist/collab/recent-edits.d.ts.map +1 -1
  336. package/dist/collab/recent-edits.js +57 -2
  337. package/dist/collab/recent-edits.js.map +1 -1
  338. package/dist/collab/struct-routes.d.ts +1 -1
  339. package/dist/index.browser.d.ts +1 -1
  340. package/dist/index.browser.d.ts.map +1 -1
  341. package/dist/index.browser.js +1 -1
  342. package/dist/index.browser.js.map +1 -1
  343. package/dist/observability/routes.d.ts +5 -5
  344. package/dist/progress/routes.d.ts +1 -1
  345. package/dist/resources/handlers.d.ts +3 -3
  346. package/dist/server/agent-chat-plugin.d.ts.map +1 -1
  347. package/dist/server/agent-chat-plugin.js +87 -10
  348. package/dist/server/agent-chat-plugin.js.map +1 -1
  349. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  350. package/dist/server/builder-design-systems.d.ts +15 -0
  351. package/dist/server/builder-design-systems.d.ts.map +1 -1
  352. package/dist/server/builder-design-systems.js +9 -4
  353. package/dist/server/builder-design-systems.js.map +1 -1
  354. package/package.json +1 -1
@@ -1,3 +1,5 @@
1
+ import { isMixedValue, MIXED_VALUE } from "./selection-helpers";
2
+
1
3
  export const FONT_FAMILY_OPTIONS = [
2
4
  { value: "inherit", key: "inherit" },
3
5
  { value: "sans-serif", key: "sansSerif" },
@@ -21,8 +23,38 @@ export const FONT_WEIGHT_OPTIONS = [
21
23
  { value: "900", key: "black" },
22
24
  ] as const;
23
25
 
26
+ /**
27
+ * True when `value` matches one of the nine standard FONT_WEIGHT_OPTIONS
28
+ * notches. Variable-font weights (e.g. "550") or a keyword the browser
29
+ * didn't normalize are real but "unknown" — callers should inject a
30
+ * synthesized option for these instead of silently rendering a Select whose
31
+ * value matches no item (blank dropdown, current weight still applied).
32
+ */
33
+ export function isKnownFontWeight(value: string): boolean {
34
+ return FONT_WEIGHT_OPTIONS.some((option) => option.value === value);
35
+ }
36
+
24
37
  export type TextResizeMode = "auto-width" | "auto-height" | "fixed";
25
38
 
39
+ /**
40
+ * Fallback dimension used when converting a text box from an auto (width or
41
+ * height) resize mode to "fixed". When the box already has a real authored
42
+ * size (not auto), that size is preserved verbatim. Otherwise this must use
43
+ * the element's actual current on-screen size (`boundingSizePx`, from
44
+ * `boundingRect`) rather than an arbitrary constant — converting auto-width
45
+ * text that currently renders at, say, 340px wide to "fixed" must keep it at
46
+ * ~340px, not silently snap it to a hardcoded default and visibly resize it.
47
+ */
48
+ export function resolveFixedResizeDimension(
49
+ authoredValue: string | undefined,
50
+ isAuto: boolean,
51
+ boundingSizePx: number,
52
+ ): string {
53
+ if (authoredValue && !isAuto) return authoredValue;
54
+ const size = Number.isFinite(boundingSizePx) ? Math.round(boundingSizePx) : 0;
55
+ return `${Math.max(1, size)}px`;
56
+ }
57
+
26
58
  function cleanFontFamilyName(value: string): string {
27
59
  const trimmed = value.trim();
28
60
  if (
@@ -115,3 +147,126 @@ export function resolveFontFamilySelectValue(
115
147
  );
116
148
  return firstFamilyOption?.value ?? raw;
117
149
  }
150
+
151
+ /**
152
+ * Mixed-selection-safe wrapper around resolveFontFamilySelectValue.
153
+ *
154
+ * A multi-selection spanning different font families injects the MIXED_VALUE
155
+ * sentinel string ("Mixed") into computedStyles.fontFamily (see
156
+ * mixedElementFromSelection/sameOrMixed in selection-helpers.ts). Feeding
157
+ * that sentinel straight into resolveFontFamilySelectValue happened to
158
+ * resolve back to the literal string "Mixed" (no option's normalized stack
159
+ * or first-family matches, so the raw fallback wins) — but only by
160
+ * coincidence, since MIXED_VALUE itself is "Mixed". Callers must not rely on
161
+ * that coincidence: without an explicit mixed check the caller has no signal
162
+ * to render the value as a disabled placeholder, so "Mixed" ends up as a
163
+ * normal, clickable SelectItem the user could select and commit as a literal
164
+ * (nonsensical) `font-family: Mixed` style. This wrapper makes the mixed
165
+ * state explicit so callers can branch on it the same way they already do
166
+ * for fontWeight/fontSize/lineHeight/letterSpacing.
167
+ */
168
+ export function resolveFontFamilyFieldValue(
169
+ computedFontFamily: string | undefined,
170
+ ): string {
171
+ if (isMixedValue(computedFontFamily)) return MIXED_VALUE;
172
+ return resolveFontFamilySelectValue(computedFontFamily);
173
+ }
174
+
175
+ /**
176
+ * PERSISTENCE GOTCHA — commit text-decoration toggles through "text-decoration"
177
+ * (the shorthand), never "text-decoration-line" (the longhand).
178
+ *
179
+ * The persisted-source style patcher (`applyStyleEdit`/`normalizeStyleProperty`
180
+ * in `shared/code-layer.ts`) only writes properties on its `VisualStyleProperty`
181
+ * allow-list. That list has "text-decoration" but does NOT have
182
+ * "text-decoration-line" — so an `onStyleChange("textDecorationLine", ...)`
183
+ * call would normalize to the unlisted kebab name, miss the allow-list, and
184
+ * return "unsupported": the live iframe preview (which patches the DOM
185
+ * directly via `element.style.setProperty`, no allow-list) would still
186
+ * visually flip on the toggle tick, but the change would never reach the
187
+ * saved HTML source and would revert on the next load/reparse — a
188
+ * works-in-preview, doesn't-persist bug. The shorthand happily accepts a bare
189
+ * line-keyword list ("underline", "underline line-through", "none") as its
190
+ * value, which is valid CSS and *is* on the allow-list, so every helper below
191
+ * reads/writes through "text-decoration" (property name "textDecoration" from
192
+ * call sites) even though the bridge separately exposes the clean longhand
193
+ * `textDecorationLine` computed value for reading current state.
194
+ */
195
+ export type TextDecorationLineToken = "underline" | "line-through" | "overline";
196
+
197
+ const TEXT_DECORATION_LINE_TOKENS: readonly TextDecorationLineToken[] = [
198
+ "underline",
199
+ "line-through",
200
+ "overline",
201
+ ];
202
+
203
+ /**
204
+ * Parses a text-decoration-line-ish CSS value ("none", "underline",
205
+ * "underline line-through", or even the full shorthand computed string like
206
+ * "underline solid rgb(0, 0, 0)") into the set of line tokens present. Works
207
+ * on either the clean longhand or the composite shorthand since it just
208
+ * looks for each known keyword as a whole word.
209
+ */
210
+ export function parseTextDecorationLineTokens(
211
+ value: string | undefined,
212
+ ): Set<TextDecorationLineToken> {
213
+ const tokens = new Set<TextDecorationLineToken>();
214
+ if (!value) return tokens;
215
+ for (const token of TEXT_DECORATION_LINE_TOKENS) {
216
+ if (new RegExp(`(?:^|\\s)${token}(?:\\s|$)`).test(value)) {
217
+ tokens.add(token);
218
+ }
219
+ }
220
+ return tokens;
221
+ }
222
+
223
+ /**
224
+ * True when `line` is active in `value`. A mixed-selection sentinel (see
225
+ * `isMixedValue`) always reads as inactive — same convention every other
226
+ * mixed-aware field in this panel uses (fontFamily/fontWeight/fontSize/...):
227
+ * an indeterminate state renders as "off", not as a guess at one element's
228
+ * value.
229
+ */
230
+ export function isTextDecorationLineActive(
231
+ value: string | undefined,
232
+ line: TextDecorationLineToken,
233
+ ): boolean {
234
+ if (isMixedValue(value)) return false;
235
+ return parseTextDecorationLineTokens(value).has(line);
236
+ }
237
+
238
+ /**
239
+ * Returns the "text-decoration" value to commit after toggling `line` on/off
240
+ * against the element's current decoration-line state. A mixed selection is
241
+ * treated as "no lines active yet" so the first click always turns the
242
+ * toggled line ON uniformly across every selected element — matching how
243
+ * every other Select-driven field here (fontFamily, fontWeight, ...)
244
+ * overwrites a mixed selection with one explicit value instead of trying to
245
+ * merge each element's own prior state.
246
+ */
247
+ export function nextTextDecorationLineValue(
248
+ currentValue: string | undefined,
249
+ line: TextDecorationLineToken,
250
+ ): string {
251
+ const current = isMixedValue(currentValue)
252
+ ? new Set<TextDecorationLineToken>()
253
+ : parseTextDecorationLineTokens(currentValue);
254
+ if (current.has(line)) current.delete(line);
255
+ else current.add(line);
256
+ return current.size === 0 ? "none" : Array.from(current).join(" ");
257
+ }
258
+
259
+ /**
260
+ * text-transform options (Figma's "Case" control). Unlike font-family/weight,
261
+ * "text-transform" is already on the persisted-source `VisualStyleProperty`
262
+ * allow-list under its own name, so callers can commit it directly with
263
+ * `onStyleChange("textTransform", value)` — no shorthand workaround needed.
264
+ */
265
+ export const TEXT_CASE_OPTIONS = [
266
+ { value: "none", key: "none" },
267
+ { value: "uppercase", key: "uppercase" },
268
+ { value: "lowercase", key: "lowercase" },
269
+ { value: "capitalize", key: "capitalize" },
270
+ ] as const;
271
+
272
+ export type TextCaseValue = (typeof TEXT_CASE_OPTIONS)[number]["value"];
@@ -10,9 +10,15 @@ import {
10
10
  IconLayoutAlignMiddle,
11
11
  IconLayoutAlignTop,
12
12
  IconLetterCase,
13
+ IconLetterCaseLower,
14
+ IconLetterCaseToggle,
15
+ IconLetterCaseUpper,
13
16
  IconLetterSpacing,
14
17
  IconLineHeight,
15
18
  IconSquare,
19
+ IconStrikethrough,
20
+ IconTextSize,
21
+ IconUnderline,
16
22
  } from "@tabler/icons-react";
17
23
  import { useState } from "react";
18
24
 
@@ -45,12 +51,22 @@ import { PanelSection } from "./panel-primitives";
45
51
  import { roundToOneDecimal } from "./position-helpers";
46
52
  import { isMixedValue, MIXED_VALUE } from "./selection-helpers";
47
53
  import type { StyleChangeHandler } from "./style-change-types";
48
- import { parseNumericValue, resolveLineHeight } from "./style-options";
54
+ import {
55
+ optionValue,
56
+ parseNumericValue,
57
+ resolveLineHeight,
58
+ } from "./style-options";
49
59
  import {
50
60
  displayFontFamilyName,
51
61
  FONT_FAMILY_OPTIONS,
52
62
  FONT_WEIGHT_OPTIONS,
53
- resolveFontFamilySelectValue,
63
+ isKnownFontWeight,
64
+ isTextDecorationLineActive,
65
+ nextTextDecorationLineValue,
66
+ resolveFixedResizeDimension,
67
+ resolveFontFamilyFieldValue,
68
+ TEXT_CASE_OPTIONS,
69
+ type TextDecorationLineToken,
54
70
  type TextResizeMode,
55
71
  } from "./typography-helpers";
56
72
 
@@ -90,14 +106,76 @@ function TextResizeControls({
90
106
  );
91
107
  }
92
108
 
109
+ type TypographyDetailsTab = "basics" | "details";
110
+
111
+ /**
112
+ * The tab bar's "Basics"/"Details" buttons are a real tab list (not the
113
+ * static, non-interactive spans this replaced) — see the module-level note
114
+ * near `TypographyDetailsPopover` for why the third "Variable" tab was
115
+ * dropped instead of wired up.
116
+ */
117
+ function TypographyDetailsTabButton({
118
+ label,
119
+ active,
120
+ onClick,
121
+ }: {
122
+ label: string;
123
+ active: boolean;
124
+ onClick: () => void;
125
+ }) {
126
+ return (
127
+ <button
128
+ type="button"
129
+ role="tab"
130
+ aria-selected={active}
131
+ onClick={onClick}
132
+ className={cn(
133
+ "cursor-pointer rounded px-2.5 py-1 !text-[11px] font-medium text-muted-foreground",
134
+ active &&
135
+ "bg-[var(--design-editor-panel-raised-bg)] font-semibold text-foreground",
136
+ )}
137
+ >
138
+ {label}
139
+ </button>
140
+ );
141
+ }
142
+
143
+ /**
144
+ * Text decoration (underline/strikethrough) and case (none/uppercase/
145
+ * lowercase/capitalize) live here, in the popover's "Details" tab, rather
146
+ * than as a 5th always-visible row in the compact panel above — matching
147
+ * Figma, which tucks these into the same type-details flyout instead of the
148
+ * always-on compact type row. Deliberately NOT duplicating line-height /
149
+ * letter-spacing here even though Figma's flyout also shows them: this
150
+ * panel's compact row (see TypographyProperties below) already exposes both
151
+ * as always-visible, directly-editable fields, so a second live-editable
152
+ * copy of the exact same property here would be redundant clutter and an
153
+ * easy source of two-inputs-fighting-the-same-value bugs, not a feature.
154
+ */
93
155
  function TypographyDetailsPopover({
94
156
  resizeMode,
95
157
  onResizeModeChange,
158
+ underlineActive,
159
+ strikethroughActive,
160
+ onToggleUnderline,
161
+ onToggleStrikethrough,
162
+ textCase,
163
+ textCaseIsMixed,
164
+ onTextCaseChange,
96
165
  }: {
97
166
  resizeMode: TextResizeMode;
98
167
  onResizeModeChange: (mode: TextResizeMode) => void;
168
+ underlineActive: boolean;
169
+ strikethroughActive: boolean;
170
+ onToggleUnderline: () => void;
171
+ onToggleStrikethrough: () => void;
172
+ textCase: string;
173
+ textCaseIsMixed: boolean;
174
+ onTextCaseChange: (value: string) => void;
99
175
  }) {
176
+ const t = useT();
100
177
  const [open, setOpen] = useState(false);
178
+ const [activeTab, setActiveTab] = useState<TypographyDetailsTab>("basics");
101
179
 
102
180
  return (
103
181
  <Popover open={open} onOpenChange={setOpen}>
@@ -131,32 +209,99 @@ function TypographyDetailsPopover({
131
209
  className="z-[100010] w-[360px] rounded-xl border-[var(--design-editor-control-border)] bg-[var(--design-editor-panel-bg)] p-0 text-foreground shadow-2xl"
132
210
  >
133
211
  <div className="flex items-center gap-1 border-b border-[var(--design-editor-control-border)] p-2.5">
134
- <div className="flex rounded-md bg-[var(--design-editor-control-bg)] p-0.5">
135
- <span className="rounded bg-[var(--design-editor-panel-raised-bg)] px-2.5 py-1 !text-[11px] font-semibold text-foreground">
136
- {"Basics" /* i18n-ignore design typography details tab */}
137
- </span>
138
- <span className="px-2.5 py-1 !text-[11px] font-medium text-muted-foreground">
139
- {"Details" /* i18n-ignore design typography details tab */}
140
- </span>
141
- <span className="px-2.5 py-1 !text-[11px] font-medium text-muted-foreground">
142
- {"Variable" /* i18n-ignore design typography details tab */}
143
- </span>
212
+ <div
213
+ role="tablist"
214
+ className="flex rounded-md bg-[var(--design-editor-control-bg)] p-0.5"
215
+ >
216
+ <TypographyDetailsTabButton
217
+ label={t("editPanel.typographyDetails.basicsTab")}
218
+ active={activeTab === "basics"}
219
+ onClick={() => setActiveTab("basics")}
220
+ />
221
+ <TypographyDetailsTabButton
222
+ label={t("editPanel.typographyDetails.detailsTab")}
223
+ active={activeTab === "details"}
224
+ onClick={() => setActiveTab("details")}
225
+ />
144
226
  </div>
145
227
  </div>
146
- <div className="space-y-3 p-4 !text-[11px]">
147
- <div className="flex h-20 items-center justify-center rounded-md bg-[var(--design-editor-control-bg)] text-[18px] text-muted-foreground/80">
148
- {"Preview" /* i18n-ignore design typography details preview */}
228
+ {activeTab === "basics" ? (
229
+ <div className="space-y-3 p-4 !text-[11px]">
230
+ <div className="flex h-20 items-center justify-center rounded-md bg-[var(--design-editor-control-bg)] text-[18px] text-muted-foreground/80">
231
+ {"Preview" /* i18n-ignore design typography details preview */}
232
+ </div>
233
+ <div className="flex items-center justify-between gap-3">
234
+ <span className="!text-[11px] font-medium text-muted-foreground">
235
+ {"Text box" /* i18n-ignore design typography details label */}
236
+ </span>
237
+ <TextResizeControls
238
+ resizeMode={resizeMode}
239
+ onResizeModeChange={onResizeModeChange}
240
+ />
241
+ </div>
149
242
  </div>
150
- <div className="flex items-center justify-between gap-3">
151
- <span className="!text-[11px] font-medium text-muted-foreground">
152
- {"Text box" /* i18n-ignore design typography details label */}
153
- </span>
154
- <TextResizeControls
155
- resizeMode={resizeMode}
156
- onResizeModeChange={onResizeModeChange}
157
- />
243
+ ) : (
244
+ <div className="space-y-3 p-4 !text-[11px]">
245
+ <div className="flex items-center justify-between gap-3">
246
+ <span className="!text-[11px] font-medium text-muted-foreground">
247
+ {t("editPanel.typographyDetails.decorationLabel")}
248
+ </span>
249
+ <InspectorSegment>
250
+ <InspectorIconButton
251
+ label={t("editPanel.textDecorations.underline")}
252
+ shortcut="⌘U"
253
+ active={underlineActive}
254
+ onClick={onToggleUnderline}
255
+ >
256
+ <IconUnderline className="size-3.5" />
257
+ </InspectorIconButton>
258
+ <InspectorIconButton
259
+ label={t("editPanel.textDecorations.strikethrough")}
260
+ shortcut="⌘⇧X"
261
+ active={strikethroughActive}
262
+ onClick={onToggleStrikethrough}
263
+ >
264
+ <IconStrikethrough className="size-3.5" />
265
+ </InspectorIconButton>
266
+ </InspectorSegment>
267
+ </div>
268
+ <div className="flex items-center justify-between gap-3">
269
+ <span className="!text-[11px] font-medium text-muted-foreground">
270
+ {t("editPanel.typographyDetails.caseLabel")}
271
+ </span>
272
+ <InspectorSegment>
273
+ <InspectorIconButton
274
+ label={t("editPanel.textCases.none")}
275
+ active={!textCaseIsMixed && textCase === "none"}
276
+ onClick={() => onTextCaseChange("none")}
277
+ >
278
+ <IconLetterCase className="size-3.5" />
279
+ </InspectorIconButton>
280
+ <InspectorIconButton
281
+ label={t("editPanel.textCases.uppercase")}
282
+ active={!textCaseIsMixed && textCase === "uppercase"}
283
+ onClick={() => onTextCaseChange("uppercase")}
284
+ >
285
+ <IconLetterCaseUpper className="size-3.5" />
286
+ </InspectorIconButton>
287
+ <InspectorIconButton
288
+ label={t("editPanel.textCases.lowercase")}
289
+ active={!textCaseIsMixed && textCase === "lowercase"}
290
+ onClick={() => onTextCaseChange("lowercase")}
291
+ >
292
+ <IconLetterCaseLower className="size-3.5" />
293
+ </InspectorIconButton>
294
+ <InspectorIconButton
295
+ label={t("editPanel.textCases.capitalize")}
296
+ active={!textCaseIsMixed && textCase === "capitalize"}
297
+ onClick={() => onTextCaseChange("capitalize")}
298
+ >
299
+ <IconLetterCaseToggle className="size-3.5" />
300
+ </InspectorIconButton>
301
+ </InspectorSegment>
302
+ </div>
158
303
  </div>
159
- </div>
304
+ )}
160
305
  </PopoverContent>
161
306
  </Popover>
162
307
  );
@@ -176,24 +321,6 @@ export function TypographyProperties({
176
321
  value: option.value,
177
322
  label: t(`editPanel.fontFamilies.${option.key}`),
178
323
  }));
179
- const fontFamily = resolveFontFamilySelectValue(styles.fontFamily);
180
- const fontFamilyOptions = FONT_FAMILY_OPTIONS.some(
181
- (option) => option.value === fontFamily,
182
- )
183
- ? baseFontFamilyOptions
184
- : [
185
- {
186
- value: fontFamily,
187
- label: displayFontFamilyName(styles.fontFamily || fontFamily),
188
- },
189
- ...baseFontFamilyOptions,
190
- ];
191
- const fontWeightOptions = FONT_WEIGHT_OPTIONS.map((option) => ({
192
- value: option.value,
193
- label: t(`editPanel.fontWeights.${option.key}`),
194
- }));
195
- const textAlign = styles.textAlign || "left";
196
-
197
324
  // Mixed-selection guards: a multi-selection with differing values injects
198
325
  // the MIXED_VALUE sentinel string into these computedStyles fields (see
199
326
  // mixedElementFromSelection/sameOrMixed). Parsing that sentinel with
@@ -202,10 +329,75 @@ export function TypographyProperties({
202
329
  // fabricated 0 (size), 1.2 (line-height), or blank (tracking) rather than
203
330
  // the Mixed state ScrubInput already knows how to render — same pattern as
204
331
  // the rotation field above.
332
+ const fontFamilyIsMixed = isMixedValue(styles.fontFamily);
205
333
  const fontWeightIsMixed = isMixedValue(styles.fontWeight);
206
334
  const fontSizeIsMixed = isMixedValue(styles.fontSize);
207
335
  const lineHeightIsMixed = isMixedValue(styles.lineHeight);
208
336
  const letterSpacingIsMixed = isMixedValue(styles.letterSpacing);
337
+ const textTransformIsMixed = isMixedValue(styles.textTransform);
338
+
339
+ // Text decoration (underline/strikethrough) reads through the bridge's
340
+ // clean `textDecorationLine` computed longhand (never the composite
341
+ // `textDecoration` shorthand string, which also carries style/color) but
342
+ // WRITES commit through the "textDecoration" property name — see
343
+ // nextTextDecorationLineValue's doc comment in typography-helpers.ts for
344
+ // why the longhand isn't on the persisted-source style allow-list.
345
+ const underlineActive = isTextDecorationLineActive(
346
+ styles.textDecorationLine,
347
+ "underline",
348
+ );
349
+ const strikethroughActive = isTextDecorationLineActive(
350
+ styles.textDecorationLine,
351
+ "line-through",
352
+ );
353
+ const toggleTextDecorationLine = (line: TextDecorationLineToken) => {
354
+ onStyleChange(
355
+ "textDecoration",
356
+ nextTextDecorationLineValue(styles.textDecorationLine, line),
357
+ );
358
+ };
359
+ // Mixed-selection guard mirrors fontWeight/fontFamily above: an
360
+ // indeterminate case across the selection renders with none of the four
361
+ // options highlighted rather than guessing one element's value.
362
+ const textCase = textTransformIsMixed
363
+ ? "none"
364
+ : optionValue(TEXT_CASE_OPTIONS, styles.textTransform, "none");
365
+ const setTextCase = (value: string) => onStyleChange("textTransform", value);
366
+
367
+ // resolveFontFamilyFieldValue returns the MIXED_VALUE sentinel unchanged
368
+ // when the selection differs so the Select below can render it as an
369
+ // explicit disabled placeholder (matching fontWeight's pattern just below)
370
+ // instead of a normal, clickable option that could commit the literal
371
+ // string "Mixed" as a font-family value.
372
+ const fontFamily = resolveFontFamilyFieldValue(styles.fontFamily);
373
+ const fontFamilyOptions = fontFamilyIsMixed
374
+ ? baseFontFamilyOptions
375
+ : FONT_FAMILY_OPTIONS.some((option) => option.value === fontFamily)
376
+ ? baseFontFamilyOptions
377
+ : [
378
+ {
379
+ value: fontFamily,
380
+ label: displayFontFamilyName(styles.fontFamily || fontFamily),
381
+ },
382
+ ...baseFontFamilyOptions,
383
+ ];
384
+ const baseFontWeightOptions = FONT_WEIGHT_OPTIONS.map((option) => ({
385
+ value: option.value,
386
+ label: t(`editPanel.fontWeights.${option.key}`),
387
+ }));
388
+ // Non-mixed but not one of the nine standard notches (e.g. a variable-font
389
+ // weight like "550") needs the same synthesized-option treatment as an
390
+ // unknown font family — otherwise the Select's value matches no item and
391
+ // renders blank even though the real weight is still applied.
392
+ const currentFontWeight = styles.fontWeight || "400";
393
+ const fontWeightOptions =
394
+ fontWeightIsMixed || isKnownFontWeight(currentFontWeight)
395
+ ? baseFontWeightOptions
396
+ : [
397
+ { value: currentFontWeight, label: currentFontWeight },
398
+ ...baseFontWeightOptions,
399
+ ];
400
+ const textAlign = styles.textAlign || "left";
209
401
 
210
402
  // M1 · Text resizing mode (auto-width / auto-height / fixed). the design
211
403
  // editor's text nodes always expose this segment. Read authored
@@ -240,8 +432,20 @@ export function TypographyProperties({
240
432
  : !heightIsAuto && !widthIsAuto
241
433
  ? "fixed"
242
434
  : "auto-height";
243
- const currentWidth = styles.width && !widthIsAuto ? styles.width : "200px";
244
- const currentHeight = styles.height && !heightIsAuto ? styles.height : "48px";
435
+ // Fall back to the element's actual current on-screen size (not an
436
+ // arbitrary constant) when there's no real authored size yet — converting
437
+ // auto-width/auto-height text to "fixed" must preserve its current
438
+ // rendered size instead of visibly snapping it to a hardcoded default.
439
+ const currentWidth = resolveFixedResizeDimension(
440
+ styles.width,
441
+ widthIsAuto,
442
+ element.boundingRect.width,
443
+ );
444
+ const currentHeight = resolveFixedResizeDimension(
445
+ styles.height,
446
+ heightIsAuto,
447
+ element.boundingRect.height,
448
+ );
245
449
  const setResizeMode = (mode: TextResizeMode) => {
246
450
  if (mode === "auto-width") {
247
451
  onStyleChange("width", "max-content");
@@ -318,6 +522,15 @@ export function TypographyProperties({
318
522
  <SelectValue />
319
523
  </SelectTrigger>
320
524
  <SelectContent>
525
+ {fontFamilyIsMixed ? (
526
+ <SelectItem
527
+ value={MIXED_VALUE}
528
+ disabled
529
+ className="!text-[11px] text-muted-foreground"
530
+ >
531
+ {MIXED_VALUE}
532
+ </SelectItem>
533
+ ) : null}
321
534
  {fontFamilyOptions.map((opt) => (
322
535
  <SelectItem
323
536
  key={opt.value}
@@ -334,7 +547,7 @@ export function TypographyProperties({
334
547
  {/* Row 2: weight + size side by side */}
335
548
  <div className="grid grid-cols-2 gap-1.5">
336
549
  <Select
337
- value={fontWeightIsMixed ? MIXED_VALUE : styles.fontWeight || "400"}
550
+ value={fontWeightIsMixed ? MIXED_VALUE : currentFontWeight}
338
551
  onValueChange={(v) => onStyleChange("fontWeight", v)}
339
552
  >
340
553
  <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)]">
@@ -364,7 +577,7 @@ export function TypographyProperties({
364
577
  <ScrubInput
365
578
  label={t("editPanel.labels.size")}
366
579
  ariaLabel={t("editPanel.labels.size")}
367
- icon={IconLetterCase}
580
+ icon={IconTextSize}
368
581
  value={
369
582
  fontSizeIsMixed
370
583
  ? 0
@@ -493,6 +706,15 @@ export function TypographyProperties({
493
706
  <TypographyDetailsPopover
494
707
  resizeMode={resizeMode}
495
708
  onResizeModeChange={setResizeMode}
709
+ underlineActive={underlineActive}
710
+ strikethroughActive={strikethroughActive}
711
+ onToggleUnderline={() => toggleTextDecorationLine("underline")}
712
+ onToggleStrikethrough={() =>
713
+ toggleTextDecorationLine("line-through")
714
+ }
715
+ textCase={textCase}
716
+ textCaseIsMixed={textTransformIsMixed}
717
+ onTextCaseChange={setTextCase}
496
718
  />
497
719
  </div>
498
720
  </div>