@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
@@ -45,6 +45,7 @@ import {
45
45
  getCredentialContext,
46
46
  } from "@agent-native/core/server/request-context";
47
47
  import { getSetting, getUserSetting } from "@agent-native/core/settings";
48
+ import { assertAccess } from "@agent-native/core/sharing";
48
49
  import { transcribeWithBuilder } from "@agent-native/core/transcription/builder";
49
50
  import { and, eq } from "drizzle-orm";
50
51
  import { z } from "zod";
@@ -110,6 +111,7 @@ type TranscriptionProvider = {
110
111
  type RecordingMediaRow = {
111
112
  videoUrl: string | null;
112
113
  videoFormat?: "webm" | "mp4" | null;
114
+ videoSizeBytes?: number | null;
113
115
  hasAudio?: boolean | null;
114
116
  sourceAppName?: string | null;
115
117
  sourceWindowTitle?: string | null;
@@ -127,6 +129,11 @@ const BUILDER_TRANSCRIPTION_MIN_TIMEOUT_MS = 45_000;
127
129
  const BUILDER_TRANSCRIPTION_MAX_TIMEOUT_MS = 65_000;
128
130
  const BUILDER_TRANSCRIPTION_BASE_TIMEOUT_MS = 30_000;
129
131
  const BUILDER_TRANSCRIPTION_PER_MINUTE_MS = 3_000;
132
+ const MEDIA_FETCH_MIN_TIMEOUT_MS = 45_000;
133
+ const MEDIA_FETCH_MAX_TIMEOUT_MS = 120_000;
134
+ const MEDIA_FETCH_BASE_TIMEOUT_MS = 30_000;
135
+ const MEDIA_FETCH_PER_50MB_MS = 10_000;
136
+ const ESTIMATED_VIDEO_BYTES_PER_MINUTE = 5 * 1024 * 1024;
130
137
 
131
138
  function clampTimeoutMs(value: number): number {
132
139
  return Math.max(
@@ -154,6 +161,38 @@ export function builderTranscriptionTimeoutMs(
154
161
  );
155
162
  }
156
163
 
164
+ export function recordingMediaFetchTimeoutMs(
165
+ videoSizeBytes: number | null | undefined,
166
+ durationMs: number | null | undefined,
167
+ ): number {
168
+ const override = Number(
169
+ process.env.CLIPS_TRANSCRIPTION_MEDIA_FETCH_TIMEOUT_MS,
170
+ );
171
+ if (Number.isFinite(override) && override > 0) {
172
+ return Math.max(
173
+ MEDIA_FETCH_MIN_TIMEOUT_MS,
174
+ Math.min(MEDIA_FETCH_MAX_TIMEOUT_MS, Math.floor(override)),
175
+ );
176
+ }
177
+
178
+ const estimatedBytes =
179
+ videoSizeBytes && Number.isFinite(videoSizeBytes) && videoSizeBytes > 0
180
+ ? videoSizeBytes
181
+ : durationMs && Number.isFinite(durationMs) && durationMs > 0
182
+ ? Math.ceil(durationMs / 60_000) * ESTIMATED_VIDEO_BYTES_PER_MINUTE
183
+ : 0;
184
+ if (!estimatedBytes) return MEDIA_FETCH_MIN_TIMEOUT_MS;
185
+
186
+ const fiftyMbUnits = Math.ceil(estimatedBytes / (50 * 1024 * 1024));
187
+ return Math.max(
188
+ MEDIA_FETCH_MIN_TIMEOUT_MS,
189
+ Math.min(
190
+ MEDIA_FETCH_MAX_TIMEOUT_MS,
191
+ MEDIA_FETCH_BASE_TIMEOUT_MS + fiftyMbUnits * MEDIA_FETCH_PER_50MB_MS,
192
+ ),
193
+ );
194
+ }
195
+
157
196
  // Bounded automatic retry for transient failures (ffmpeg timeout, transient
158
197
  // provider network/5xx errors) — NOT for permanent failures like "no audio
159
198
  // track" or a missing/rejected API key. Mirrors the fire-and-forget
@@ -301,10 +340,12 @@ async function loadRecordingMediaBlob({
301
340
  recordingId,
302
341
  videoUrl,
303
342
  fallbackMimeType,
343
+ timeoutMs,
304
344
  }: {
305
345
  recordingId: string;
306
346
  videoUrl: string;
307
347
  fallbackMimeType: string;
348
+ timeoutMs: number;
308
349
  }): Promise<{ blob: Blob; sourceMimeType: string }> {
309
350
  const isLocalBlob =
310
351
  videoUrl.startsWith("/api/video/") ||
@@ -334,10 +375,10 @@ async function loadRecordingMediaBlob({
334
375
  resolvedVideoUrl = `${origin}${resolvedVideoUrl}`;
335
376
  }
336
377
  const vidRes = isAppRelativeUrl
337
- ? await fetch(resolvedVideoUrl, { signal: AbortSignal.timeout(30_000) })
378
+ ? await fetch(resolvedVideoUrl, { signal: AbortSignal.timeout(timeoutMs) })
338
379
  : await ssrfSafeFetch(
339
380
  resolvedVideoUrl,
340
- { signal: AbortSignal.timeout(30_000) },
381
+ { signal: AbortSignal.timeout(timeoutMs) },
341
382
  { maxRedirects: 3 },
342
383
  );
343
384
  if (!vidRes.ok) {
@@ -899,7 +940,7 @@ async function pickProvider(
899
940
 
900
941
  const requestTranscriptAction = defineAction({
901
942
  description:
902
- "Ensure a recording has a transcript. Preserves native Web Speech/macOS Speech transcripts first, then uses configured backup transcription only when needed.",
943
+ "Ensure a recording has a transcript, or explicitly regenerate it from the recording media. Preserves native Web Speech/macOS Speech transcripts unless regenerate is true, then uses Builder.io managed transcription or the configured Groq fallback.",
903
944
  schema: z.object({
904
945
  recordingId: z.string().describe("Recording ID"),
905
946
  force: z
@@ -908,6 +949,12 @@ const requestTranscriptAction = defineAction({
908
949
  .describe(
909
950
  "Bypass the recent pending guard for explicit retries or the finalize-recording background worker.",
910
951
  ),
952
+ regenerate: z
953
+ .boolean()
954
+ .optional()
955
+ .describe(
956
+ "Generate a fresh transcript from the recording media even when a ready transcript already exists. The existing ready transcript is kept if regeneration fails.",
957
+ ),
911
958
  retryAttempt: z
912
959
  .number()
913
960
  .int()
@@ -918,6 +965,8 @@ const requestTranscriptAction = defineAction({
918
965
  ),
919
966
  }),
920
967
  run: async (args) => {
968
+ await assertAccess("recording", args.recordingId, "editor");
969
+
921
970
  const db = getDb();
922
971
  const ownerEmail = getCurrentOwnerEmail();
923
972
  const now = new Date().toISOString();
@@ -944,6 +993,10 @@ const requestTranscriptAction = defineAction({
944
993
  recordingId: args.recordingId,
945
994
  videoUrl,
946
995
  fallbackMimeType,
996
+ timeoutMs: recordingMediaFetchTimeoutMs(
997
+ rec.videoSizeBytes,
998
+ rec.durationMs,
999
+ ),
947
1000
  });
948
1001
  return prepareAudioOnlyTranscriptionMedia({
949
1002
  blob: media.blob,
@@ -980,6 +1033,11 @@ const requestTranscriptAction = defineAction({
980
1033
  // manual retry can still top the budget back up for one more bounded
981
1034
  // automatic pass if it fails transiently again.
982
1035
  const currentRetryCount = existingNativeTranscript?.retryCount ?? 0;
1036
+ const regeneratingReadyTranscript = Boolean(
1037
+ args.regenerate &&
1038
+ existingNativeTranscript?.status === "ready" &&
1039
+ existingNativeTranscript.fullText?.trim(),
1040
+ );
983
1041
  if (args.retryAttempt !== undefined) {
984
1042
  console.log(
985
1043
  `[clips] auto-retry transcription attempt ${args.retryAttempt} for ${args.recordingId}`,
@@ -987,6 +1045,7 @@ const requestTranscriptAction = defineAction({
987
1045
  }
988
1046
 
989
1047
  if (
1048
+ !args.regenerate &&
990
1049
  existingNativeTranscript?.status === "ready" &&
991
1050
  existingNativeTranscript.fullText?.trim()
992
1051
  ) {
@@ -1032,19 +1091,22 @@ const requestTranscriptAction = defineAction({
1032
1091
  // is set at the deployment level. Use the per-user-aware resolver so
1033
1092
  // a sidebar OAuth connection actually wires through to transcription.
1034
1093
  if (await resolveHasBuilderPrivateKey()) {
1035
- await upsertTranscriptRow(db, {
1036
- recordingId: args.recordingId,
1037
- ownerEmail,
1038
- status: "pending",
1039
- failureReason: null,
1040
- now,
1041
- });
1042
- await writeAppState("refresh-signal", { ts: Date.now() });
1094
+ if (!regeneratingReadyTranscript) {
1095
+ await upsertTranscriptRow(db, {
1096
+ recordingId: args.recordingId,
1097
+ ownerEmail,
1098
+ status: "pending",
1099
+ failureReason: null,
1100
+ now,
1101
+ });
1102
+ await writeAppState("refresh-signal", { ts: Date.now() });
1103
+ }
1043
1104
 
1044
1105
  const [rec] = await db
1045
1106
  .select({
1046
1107
  videoUrl: schema.recordings.videoUrl,
1047
1108
  videoFormat: schema.recordings.videoFormat,
1109
+ videoSizeBytes: schema.recordings.videoSizeBytes,
1048
1110
  hasAudio: schema.recordings.hasAudio,
1049
1111
  sourceAppName: schema.recordings.sourceAppName,
1050
1112
  sourceWindowTitle: schema.recordings.sourceWindowTitle,
@@ -1121,13 +1183,15 @@ const requestTranscriptAction = defineAction({
1121
1183
  );
1122
1184
  const fullText = normalizedTranscript.fullText;
1123
1185
 
1124
- const preserved = await preserveReadyTranscriptIfAvailable({
1125
- db,
1126
- recordingId: args.recordingId,
1127
- ownerEmail,
1128
- allowLikelyLanguageMismatch: false,
1129
- });
1130
- if (preserved) return preserved;
1186
+ if (!regeneratingReadyTranscript) {
1187
+ const preserved = await preserveReadyTranscriptIfAvailable({
1188
+ db,
1189
+ recordingId: args.recordingId,
1190
+ ownerEmail,
1191
+ allowLikelyLanguageMismatch: false,
1192
+ });
1193
+ if (preserved) return preserved;
1194
+ }
1131
1195
 
1132
1196
  if (!fullText) {
1133
1197
  return failEmptyProviderTranscript({
@@ -1251,15 +1315,17 @@ const requestTranscriptAction = defineAction({
1251
1315
  }
1252
1316
 
1253
1317
  // Upsert a pending row so the UI can show "Transcribing…".
1254
- await upsertTranscriptRow(db, {
1255
- recordingId: args.recordingId,
1256
- ownerEmail,
1257
- status: "pending",
1258
- failureReason: null,
1259
- now,
1260
- });
1318
+ if (!regeneratingReadyTranscript) {
1319
+ await upsertTranscriptRow(db, {
1320
+ recordingId: args.recordingId,
1321
+ ownerEmail,
1322
+ status: "pending",
1323
+ failureReason: null,
1324
+ now,
1325
+ });
1261
1326
 
1262
- await writeAppState("refresh-signal", { ts: Date.now() });
1327
+ await writeAppState("refresh-signal", { ts: Date.now() });
1328
+ }
1263
1329
 
1264
1330
  // Load the recording's media URL and prepare audio-only bytes. We never
1265
1331
  // send video frames to a transcription provider; screen-only recordings
@@ -1269,6 +1335,7 @@ const requestTranscriptAction = defineAction({
1269
1335
  .select({
1270
1336
  videoUrl: schema.recordings.videoUrl,
1271
1337
  videoFormat: schema.recordings.videoFormat,
1338
+ videoSizeBytes: schema.recordings.videoSizeBytes,
1272
1339
  hasAudio: schema.recordings.hasAudio,
1273
1340
  sourceAppName: schema.recordings.sourceAppName,
1274
1341
  sourceWindowTitle: schema.recordings.sourceWindowTitle,
@@ -1369,13 +1436,15 @@ const requestTranscriptAction = defineAction({
1369
1436
  );
1370
1437
  const fullText = normalizedTranscript.fullText;
1371
1438
 
1372
- const preserved = await preserveReadyTranscriptIfAvailable({
1373
- db,
1374
- recordingId: args.recordingId,
1375
- ownerEmail,
1376
- allowLikelyLanguageMismatch: false,
1377
- });
1378
- if (preserved) return preserved;
1439
+ if (!regeneratingReadyTranscript) {
1440
+ const preserved = await preserveReadyTranscriptIfAvailable({
1441
+ db,
1442
+ recordingId: args.recordingId,
1443
+ ownerEmail,
1444
+ allowLikelyLanguageMismatch: false,
1445
+ });
1446
+ if (preserved) return preserved;
1447
+ }
1379
1448
 
1380
1449
  if (!fullText) {
1381
1450
  return failEmptyProviderTranscript({
@@ -253,6 +253,7 @@ export function LibraryGrid({
253
253
  <ShareRecordingDialog
254
254
  recordingId={sharingRec.id}
255
255
  recordingTitle={sharingRec.title}
256
+ initialVisibility={sharingRec.visibility}
256
257
  open={!!sharingRec}
257
258
  onOpenChange={(open) => {
258
259
  if (!open) setSharingRec(null);
@@ -62,6 +62,8 @@ function absoluteAppUrl(path: string): string {
62
62
  export interface ShareRecordingPopoverProps {
63
63
  recordingId: string;
64
64
  recordingTitle?: string;
65
+ initialVisibility?: Visibility | null;
66
+ initialRole?: "owner" | "admin" | "editor" | "viewer";
65
67
  videoUrl?: string | null;
66
68
  animatedThumbnailUrl?: string | null;
67
69
  isLoomRecording?: boolean;
@@ -90,6 +92,8 @@ type ShareRecordingDialogProps = Omit<
90
92
  export function ShareRecordingPopover({
91
93
  recordingId,
92
94
  recordingTitle,
95
+ initialVisibility,
96
+ initialRole,
93
97
  videoUrl,
94
98
  animatedThumbnailUrl,
95
99
  isLoomRecording = false,
@@ -108,6 +112,8 @@ export function ShareRecordingPopover({
108
112
  <ShareRecordingContent
109
113
  recordingId={recordingId}
110
114
  recordingTitle={recordingTitle}
115
+ initialVisibility={initialVisibility}
116
+ initialRole={initialRole}
111
117
  videoUrl={videoUrl}
112
118
  animatedThumbnailUrl={animatedThumbnailUrl}
113
119
  isLoomRecording={isLoomRecording}
@@ -125,6 +131,8 @@ export function ShareRecordingPopover({
125
131
  export function ShareRecordingDialog({
126
132
  recordingId,
127
133
  recordingTitle,
134
+ initialVisibility,
135
+ initialRole,
128
136
  videoUrl,
129
137
  animatedThumbnailUrl,
130
138
  isLoomRecording = false,
@@ -143,6 +151,8 @@ export function ShareRecordingDialog({
143
151
  <ShareRecordingContent
144
152
  recordingId={recordingId}
145
153
  recordingTitle={recordingTitle}
154
+ initialVisibility={initialVisibility}
155
+ initialRole={initialRole}
146
156
  videoUrl={videoUrl}
147
157
  animatedThumbnailUrl={animatedThumbnailUrl}
148
158
  isLoomRecording={isLoomRecording}
@@ -156,6 +166,8 @@ export function ShareRecordingDialog({
156
166
  function ShareRecordingContent({
157
167
  recordingId,
158
168
  recordingTitle,
169
+ initialVisibility,
170
+ initialRole,
159
171
  videoUrl,
160
172
  animatedThumbnailUrl,
161
173
  isLoomRecording = false,
@@ -163,6 +175,8 @@ function ShareRecordingContent({
163
175
  }: {
164
176
  recordingId: string;
165
177
  recordingTitle?: string;
178
+ initialVisibility?: Visibility | null;
179
+ initialRole?: "owner" | "admin" | "editor" | "viewer";
166
180
  videoUrl?: string | null;
167
181
  animatedThumbnailUrl?: string | null;
168
182
  isLoomRecording?: boolean;
@@ -175,7 +189,12 @@ function ShareRecordingContent({
175
189
  });
176
190
 
177
191
  const data = sharesQuery.data;
178
- const canManage = data?.role === "owner" || data?.role === "admin";
192
+ const role = data?.role ?? initialRole;
193
+ const canManage = role === "owner" || role === "admin";
194
+ const visibility =
195
+ (data?.visibility as Visibility | null | undefined) ??
196
+ initialVisibility ??
197
+ null;
179
198
 
180
199
  // Attribution `via` must be a stable non-PII id, never an email. The only
181
200
  // owner id available client-side is the *current* session's userId, which is
@@ -226,6 +245,7 @@ function ShareRecordingContent({
226
245
  recordingId={recordingId}
227
246
  shareUrl={shareUrl}
228
247
  sharesQuery={sharesQuery}
248
+ visibility={visibility}
229
249
  canManage={canManage}
230
250
  videoUrl={videoUrl}
231
251
  animatedThumbnailUrl={animatedThumbnailUrl}
@@ -247,6 +267,7 @@ function ShareRecordingContent({
247
267
  <ClipsEmbedConfigurator
248
268
  recordingId={recordingId}
249
269
  sharesQuery={sharesQuery}
270
+ visibility={visibility}
250
271
  canManage={canManage}
251
272
  ownerViaId={ownerViaId}
252
273
  />
@@ -264,6 +285,7 @@ function LinkTab({
264
285
  recordingId,
265
286
  shareUrl,
266
287
  sharesQuery,
288
+ visibility,
267
289
  canManage,
268
290
  videoUrl,
269
291
  animatedThumbnailUrl,
@@ -272,6 +294,7 @@ function LinkTab({
272
294
  recordingId: string;
273
295
  shareUrl: string;
274
296
  sharesQuery: SharesQuery;
297
+ visibility: Visibility | null;
275
298
  canManage: boolean;
276
299
  videoUrl?: string | null;
277
300
  animatedThumbnailUrl?: string | null;
@@ -283,11 +306,9 @@ function LinkTab({
283
306
  recordingId,
284
307
  sharesQuery,
285
308
  );
286
- const data = sharesQuery.data;
287
- const visibility: Visibility =
288
- (data?.visibility as Visibility | null) ?? "private";
289
309
  const isPublic = visibility === "public";
290
- const sharesLoaded = data !== undefined;
310
+ const sharesLoaded = visibility !== null;
311
+ const visibilityPending = isPending || sharesQuery.isLoading;
291
312
  const isLoomRecording = isLoomRecordingProp || isLoomEmbedUrl(videoUrl);
292
313
  const createAgentLink = useActionMutation(
293
314
  "create-recording-agent-link" as any,
@@ -355,25 +376,34 @@ function LinkTab({
355
376
 
356
377
  return (
357
378
  <div className="space-y-4">
358
- <GeneralAccessSelect
359
- visibility={visibility}
360
- canManage={canManage}
361
- isPending={isPending}
362
- onChange={(next) => setResourceVisibility(next)}
363
- publicDescription={t("shareDialog.publicDescription")}
364
- />
379
+ {visibility ? (
380
+ <GeneralAccessSelect
381
+ visibility={visibility}
382
+ canManage={canManage}
383
+ isPending={visibilityPending}
384
+ onChange={(next) => setResourceVisibility(next)}
385
+ publicDescription={t("shareDialog.publicDescription")}
386
+ />
387
+ ) : (
388
+ <div className="space-y-2" aria-hidden>
389
+ <div className="h-3 w-24 animate-pulse rounded bg-muted" />
390
+ <div className="h-12 w-full animate-pulse rounded bg-muted" />
391
+ </div>
392
+ )}
365
393
 
366
394
  <CopyField
367
395
  label={t("shareDialog.shareLink")}
368
396
  value={shareUrl}
369
- disabled={isPending || (!isPublic && canManage)}
397
+ disabled={
398
+ visibilityPending || !sharesLoaded || (!isPublic && canManage)
399
+ }
370
400
  />
371
401
 
372
402
  {/* Public links unfurl into a playable video in Slack; surface that here
373
403
  (and a connect link) instead of leaving it buried in Settings. */}
374
404
  {isPublic ? <SlackShareHint canManage={canManage} /> : null}
375
405
 
376
- {!isPublic ? (
406
+ {sharesLoaded && !isPublic ? (
377
407
  <Collapsible open={agentShareOpen} onOpenChange={setAgentShareOpen}>
378
408
  <CollapsibleTrigger asChild>
379
409
  <Button
@@ -431,7 +461,7 @@ function LinkTab({
431
461
  </Collapsible>
432
462
  ) : null}
433
463
 
434
- {!isPublic && canManage ? (
464
+ {sharesLoaded && !isPublic && canManage ? (
435
465
  <MakePublicCard
436
466
  isPending={isPending}
437
467
  onMakePublic={() =>
@@ -485,11 +515,13 @@ function LinkTab({
485
515
  function ClipsEmbedConfigurator({
486
516
  recordingId,
487
517
  sharesQuery,
518
+ visibility,
488
519
  canManage,
489
520
  ownerViaId,
490
521
  }: {
491
522
  recordingId: string;
492
523
  sharesQuery: SharesQuery;
524
+ visibility: Visibility | null;
493
525
  canManage: boolean;
494
526
  ownerViaId?: string;
495
527
  }) {
@@ -500,11 +532,10 @@ function ClipsEmbedConfigurator({
500
532
  const [width, setWidth] = useState(640);
501
533
  const [height, setHeight] = useState(360);
502
534
 
503
- const data = sharesQuery.data;
504
- const visibility: Visibility =
505
- (data?.visibility as Visibility | null) ?? "private";
506
535
  const isPublic = visibility === "public";
507
- const visibilityLabel = t(`shareUi.visibility.${visibility}.label`);
536
+ const visibilityLabel = visibility
537
+ ? t(`shareUi.visibility.${visibility}.label`)
538
+ : "";
508
539
  const { setResourceVisibility, isPending } = useResourceVisibilityMutation(
509
540
  "recording",
510
541
  recordingId,
@@ -529,6 +560,15 @@ function ClipsEmbedConfigurator({
529
560
  ? `<div style="position:relative;padding-bottom:56.25%;height:0;background:#000;overflow:hidden"><iframe src="${src}" title="${t("shareDialog.embedIframeTitle")}" frameborder="0" scrolling="no" allowfullscreen allow="autoplay; fullscreen; picture-in-picture" style="position:absolute;inset:0;width:100%;height:100%;border:0;background:#000;overflow:hidden"></iframe></div>`
530
561
  : `<iframe src="${src}" title="${t("shareDialog.embedIframeTitle")}" width="${width}" height="${height}" frameborder="0" scrolling="no" allowfullscreen allow="autoplay; fullscreen; picture-in-picture" style="display:block;max-width:100%;border:0;background:#000;overflow:hidden"></iframe>`;
531
562
 
563
+ if (!visibility) {
564
+ return (
565
+ <div className="space-y-2" aria-hidden>
566
+ <div className="h-16 w-full animate-pulse rounded bg-muted" />
567
+ <div className="h-24 w-full animate-pulse rounded bg-muted" />
568
+ </div>
569
+ );
570
+ }
571
+
532
572
  return (
533
573
  <div className="space-y-3">
534
574
  {!isPublic ? (
@@ -19,6 +19,7 @@ import {
19
19
  IconBolt,
20
20
  IconChevronDown,
21
21
  IconChevronUp,
22
+ IconRefresh,
22
23
  } from "@tabler/icons-react";
23
24
  import { useCallback, useEffect, useMemo, useRef, useState } from "react";
24
25
 
@@ -56,6 +57,9 @@ export interface TranscriptPanelProps {
56
57
  recordingTitle?: string;
57
58
  /** Called when the user asks us to retry transcription after fixing an error. */
58
59
  onRetry?: () => void;
60
+ /** Called when the user asks for a fresh transcript from the recording media. */
61
+ onRegenerate?: () => void;
62
+ isRegenerating?: boolean;
59
63
  }
60
64
 
61
65
  export function TranscriptPanel(props: TranscriptPanelProps) {
@@ -71,6 +75,8 @@ export function TranscriptPanel(props: TranscriptPanelProps) {
71
75
  cleanup,
72
76
  recordingTitle,
73
77
  onRetry,
78
+ onRegenerate,
79
+ isRegenerating = false,
74
80
  } = props;
75
81
  const [query, setQuery] = useState("");
76
82
  const [copied, setCopied] = useState(false);
@@ -170,8 +176,18 @@ export function TranscriptPanel(props: TranscriptPanelProps) {
170
176
  </p>
171
177
  </div>
172
178
  {onRetry ? (
173
- <Button size="sm" variant="outline" onClick={onRetry}>
174
- {t("transcriptPanel.retry")}
179
+ <Button
180
+ size="sm"
181
+ variant="outline"
182
+ onClick={onRetry}
183
+ disabled={isRegenerating}
184
+ >
185
+ {isRegenerating ? (
186
+ <IconLoader2 className="size-4 animate-spin" />
187
+ ) : null}
188
+ {isRegenerating
189
+ ? t("transcriptPanel.transcribing")
190
+ : t("transcriptPanel.regenerate")}
175
191
  </Button>
176
192
  ) : null}
177
193
  </div>
@@ -188,8 +204,18 @@ export function TranscriptPanel(props: TranscriptPanelProps) {
188
204
  </div>
189
205
  <div className="flex items-center gap-2">
190
206
  {onRetry ? (
191
- <Button size="sm" variant="outline" onClick={onRetry}>
192
- {t("transcriptPanel.retry")}
207
+ <Button
208
+ size="sm"
209
+ variant="outline"
210
+ onClick={onRetry}
211
+ disabled={isRegenerating}
212
+ >
213
+ {isRegenerating ? (
214
+ <IconLoader2 className="size-4 animate-spin" />
215
+ ) : null}
216
+ {isRegenerating
217
+ ? t("transcriptPanel.transcribing")
218
+ : t("transcriptPanel.regenerate")}
193
219
  </Button>
194
220
  ) : null}
195
221
  </div>
@@ -229,6 +255,24 @@ export function TranscriptPanel(props: TranscriptPanelProps) {
229
255
  </TooltipTrigger>
230
256
  <TooltipContent>{t("transcriptPanel.downloadSrt")}</TooltipContent>
231
257
  </Tooltip>
258
+ {onRegenerate ? (
259
+ <Tooltip>
260
+ <TooltipTrigger asChild>
261
+ <Button
262
+ variant="ghost"
263
+ size="icon"
264
+ onClick={onRegenerate}
265
+ disabled={isRegenerating}
266
+ aria-label={t("transcriptPanel.regenerate")}
267
+ >
268
+ <IconRefresh
269
+ className={cn("h-4 w-4", isRegenerating && "animate-spin")}
270
+ />
271
+ </Button>
272
+ </TooltipTrigger>
273
+ <TooltipContent>{t("transcriptPanel.regenerate")}</TooltipContent>
274
+ </Tooltip>
275
+ ) : null}
232
276
  </div>
233
277
 
234
278
  {cleanup?.status === "running" ? (
@@ -261,14 +305,24 @@ export function TranscriptPanel(props: TranscriptPanelProps) {
261
305
  const isActive = displaySegments[activeIndex] === seg;
262
306
  return (
263
307
  <li key={seg.startMs}>
264
- <button
265
- onClick={() => onSeek(seg.startMs)}
308
+ <div
309
+ role="button"
310
+ tabIndex={0}
311
+ onClick={(event) => {
312
+ if (hasSelectionWithin(event.currentTarget)) return;
313
+ onSeek(seg.startMs);
314
+ }}
315
+ onKeyDown={(event) => {
316
+ if (event.key !== "Enter" && event.key !== " ") return;
317
+ event.preventDefault();
318
+ onSeek(seg.startMs);
319
+ }}
266
320
  className={cn(
267
- "w-full text-left px-3 py-2 flex gap-3 items-start hover:bg-accent/50",
321
+ "flex w-full cursor-pointer items-start gap-3 px-3 py-2 text-left hover:bg-accent/50",
268
322
  isActive && "bg-accent",
269
323
  )}
270
324
  >
271
- <span className="text-[11px] text-muted-foreground font-mono tabular-nums pt-0.5 shrink-0">
325
+ <span className="shrink-0 pt-0.5 font-mono text-[11px] tabular-nums text-muted-foreground">
272
326
  {msToClock(seg.startMs)}
273
327
  </span>
274
328
  <span
@@ -280,7 +334,7 @@ export function TranscriptPanel(props: TranscriptPanelProps) {
280
334
  __html: highlight(seg.text, query),
281
335
  }}
282
336
  />
283
- </button>
337
+ </div>
284
338
  </li>
285
339
  );
286
340
  })}
@@ -291,6 +345,18 @@ export function TranscriptPanel(props: TranscriptPanelProps) {
291
345
  );
292
346
  }
293
347
 
348
+ function hasSelectionWithin(element: HTMLElement): boolean {
349
+ const selection = window.getSelection();
350
+ if (!selection || selection.isCollapsed || !selection.toString().trim()) {
351
+ return false;
352
+ }
353
+ const { anchorNode, focusNode } = selection;
354
+ return Boolean(
355
+ (anchorNode && element.contains(anchorNode)) ||
356
+ (focusNode && element.contains(focusNode)),
357
+ );
358
+ }
359
+
294
360
  function highlight(text: string, q: string): string {
295
361
  const escaped = text.replace(/[&<>]/g, (c) =>
296
362
  c === "&" ? "&amp;" : c === "<" ? "&lt;" : "&gt;",
@@ -563,16 +563,21 @@ export const VideoPlayer = forwardRef<VideoPlayerHandle, VideoPlayerProps>(
563
563
  requestPlay();
564
564
  }, [isPlaying, pauseVideo, requestPlay]);
565
565
 
566
- const activateVideoSurface = useCallback(() => {
567
- const v = videoRef.current;
568
- if (!v) return;
569
- if (!v.paused || isPlaying) {
570
- pauseVideo();
571
- } else {
572
- requestPlay();
573
- }
574
- bumpControls();
575
- }, [bumpControls, isPlaying, pauseVideo, requestPlay]);
566
+ const activateVideoSurface = useCallback(
567
+ (input: "mouse" | "touch") => {
568
+ // Match native mobile players: touching the video reveals the controls
569
+ // without unexpectedly pausing or resuming it. Embeds that explicitly
570
+ // hide their chrome keep surface-tap playback so they remain usable.
571
+ if (input === "touch" && !hideChrome) {
572
+ bumpControls();
573
+ return;
574
+ }
575
+
576
+ togglePlayback();
577
+ bumpControls();
578
+ },
579
+ [bumpControls, hideChrome, togglePlayback],
580
+ );
576
581
 
577
582
  const handlePlayerPointerDown = useCallback(
578
583
  (e: React.PointerEvent<HTMLDivElement>) => {
@@ -610,7 +615,7 @@ export const VideoPlayer = forwardRef<VideoPlayerHandle, VideoPlayerProps>(
610
615
 
611
616
  e.preventDefault();
612
617
  suppressNextClickRef.current = true;
613
- activateVideoSurface();
618
+ activateVideoSurface("touch");
614
619
  },
615
620
  [activateVideoSurface, isLoomEmbed],
616
621
  );
@@ -1115,7 +1120,7 @@ export const VideoPlayer = forwardRef<VideoPlayerHandle, VideoPlayerProps>(
1115
1120
  // keep their own behavior.
1116
1121
  if (isPlayerUiTarget(e.target)) return;
1117
1122
  if (isLoomEmbed) return;
1118
- activateVideoSurface();
1123
+ activateVideoSurface("mouse");
1119
1124
  }}
1120
1125
  >
1121
1126
  {isLoomEmbed && loomIframeSrc ? (