@agent-native/core 0.92.9 → 0.92.11

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 (345) 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 +181 -15
  5. package/corpus/core/src/agent/run-manager.ts +31 -4
  6. package/corpus/core/src/agent/run-store.ts +80 -0
  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.ts +165 -1
  12. package/corpus/core/src/client/chat/index.ts +9 -1
  13. package/corpus/core/src/client/extensions/EmbeddedExtension.tsx +38 -2
  14. package/corpus/core/src/client/index.ts +5 -0
  15. package/corpus/core/src/client/rich-markdown-editor/useCollabReconcile.ts +18 -7
  16. package/corpus/core/src/collab/awareness-store.ts +23 -12
  17. package/corpus/core/src/collab/client.ts +16 -5
  18. package/corpus/core/src/collab/index.ts +4 -0
  19. package/corpus/core/src/collab/recent-edits.ts +65 -2
  20. package/corpus/core/src/deploy/build.ts +133 -5
  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 +48 -4
  33. package/corpus/templates/clips/app/components/player/video-player.tsx +17 -12
  34. package/corpus/templates/clips/app/i18n/ar-SA.ts +1 -0
  35. package/corpus/templates/clips/app/i18n/de-DE.ts +1 -0
  36. package/corpus/templates/clips/app/i18n/en-US.ts +1 -0
  37. package/corpus/templates/clips/app/i18n/es-ES.ts +1 -0
  38. package/corpus/templates/clips/app/i18n/fr-FR.ts +1 -0
  39. package/corpus/templates/clips/app/i18n/hi-IN.ts +1 -0
  40. package/corpus/templates/clips/app/i18n/ja-JP.ts +1 -0
  41. package/corpus/templates/clips/app/i18n/ko-KR.ts +1 -0
  42. package/corpus/templates/clips/app/i18n/pt-BR.ts +1 -0
  43. package/corpus/templates/clips/app/i18n/zh-CN.ts +1 -0
  44. package/corpus/templates/clips/app/i18n/zh-TW.ts +1 -0
  45. package/corpus/templates/clips/app/routes/r.$recordingId.tsx +70 -37
  46. package/corpus/templates/clips/app/routes/record.tsx +17 -4
  47. package/corpus/templates/clips/app/routes/share.$shareId.tsx +2 -0
  48. package/corpus/templates/clips/changelog/2026-07-09-desktop-popover-sits-closer-to-the-menu-bar.md +6 -0
  49. package/corpus/templates/clips/changelog/2026-07-09-desktop-recording-optimization-progress-stays-visible-until-the-clip-opens.md +6 -0
  50. package/corpus/templates/clips/changelog/2026-07-09-fixed-large-video-file-uploads-failing-before-their-resumabl.md +6 -0
  51. package/corpus/templates/clips/changelog/2026-07-09-fixed-new-video-uploads-defaulting-to-private.md +6 -0
  52. package/corpus/templates/clips/changelog/2026-07-09-long-recordings-now-get-enough-time-to-download-their-media-.md +6 -0
  53. package/corpus/templates/clips/changelog/2026-07-09-meeting-notes-no-longer-lowers-your-microphone-volume-for-ot.md +6 -0
  54. package/corpus/templates/clips/changelog/2026-07-09-on-mobile-tapping-a-playing-clip-reveals-playback-controls-w.md +6 -0
  55. package/corpus/templates/clips/changelog/2026-07-09-regenerate-any-clip-transcript-from-its-saved-recording-or-r.md +6 -0
  56. package/corpus/templates/clips/changelog/2026-07-09-slack-previews-now-show-the-clip-length-alongside-its-descri.md +6 -0
  57. package/corpus/templates/clips/desktop/src/app.tsx +12 -2
  58. package/corpus/templates/clips/desktop/src/hooks/useMeetingTranscription.ts +8 -1
  59. package/corpus/templates/clips/desktop/src/lib/recorder.ts +8 -5
  60. package/corpus/templates/clips/desktop/src/lib/transcription-engine.ts +5 -5
  61. package/corpus/templates/clips/desktop/src/lib/voice-dictation.ts +3 -0
  62. package/corpus/templates/clips/desktop/src/styles.css +6 -3
  63. package/corpus/templates/clips/desktop/src-tauri/src/clips/mod.rs +29 -11
  64. package/corpus/templates/clips/desktop/src-tauri/src/native_screen.rs +1 -1
  65. package/corpus/templates/clips/desktop/src-tauri/src/system_audio.rs +103 -21
  66. package/corpus/templates/clips/desktop/src-tauri/src/whisper_speech.rs +117 -39
  67. package/corpus/templates/clips/server/lib/slack-unfurls.ts +20 -1
  68. package/corpus/templates/clips/server/lib/streaming-upload-mode.ts +10 -4
  69. package/corpus/templates/clips/shared/recording-core.ts +12 -0
  70. package/corpus/templates/content/AGENTS.md +7 -6
  71. package/corpus/templates/content/actions/_document-flush.ts +132 -17
  72. package/corpus/templates/content/actions/_notion-action-utils.ts +15 -0
  73. package/corpus/templates/content/actions/create-and-link-notion-page.ts +2 -0
  74. package/corpus/templates/content/actions/link-notion-page.ts +2 -0
  75. package/corpus/templates/content/actions/pull-document.ts +5 -7
  76. package/corpus/templates/content/actions/pull-notion-page.ts +2 -0
  77. package/corpus/templates/content/actions/push-notion-page.ts +10 -0
  78. package/corpus/templates/content/actions/resolve-notion-sync-conflict.ts +2 -0
  79. package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +88 -23
  80. package/corpus/templates/content/app/components/editor/VisualEditor.tsx +32 -2
  81. package/corpus/templates/content/app/hooks/use-notion.ts +6 -6
  82. package/corpus/templates/content/app/i18n/zh-TW.ts +1 -0
  83. package/corpus/templates/content/app/i18n-data.ts +18 -0
  84. package/corpus/templates/content/changelog/2026-07-09-notion-conflict-choices-and-version-restores-preserve-docume.md +6 -0
  85. package/corpus/templates/content/server/lib/notion-sync.ts +80 -64
  86. package/corpus/templates/design/.agents/skills/design-generation/SKILL.md +29 -2
  87. package/corpus/templates/design/.agents/skills/design-systems/SKILL.md +86 -0
  88. package/corpus/templates/design/.agents/skills/export-handoff/SKILL.md +57 -0
  89. package/corpus/templates/design/.agents/skills/visual-edit/SKILL.md +11 -0
  90. package/corpus/templates/design/AGENTS.md +45 -10
  91. package/corpus/templates/design/actions/add-localhost-screens.ts +66 -13
  92. package/corpus/templates/design/actions/apply-component-prop-edit.ts +45 -36
  93. package/corpus/templates/design/actions/apply-motion-edit.ts +25 -31
  94. package/corpus/templates/design/actions/apply-shader-fill.ts +29 -42
  95. package/corpus/templates/design/actions/detach-component-instance.ts +337 -0
  96. package/corpus/templates/design/actions/export-design-as-figma-svg.ts +219 -0
  97. package/corpus/templates/design/actions/generate-design.ts +79 -12
  98. package/corpus/templates/design/actions/generate-screens.ts +57 -12
  99. package/corpus/templates/design/actions/get-figma-styles.ts +115 -0
  100. package/corpus/templates/design/actions/go-to-main-component.ts +254 -0
  101. package/corpus/templates/design/actions/grant-localhost-write-consent.ts +4 -3
  102. package/corpus/templates/design/actions/import-design-source.ts +4 -31
  103. package/corpus/templates/design/actions/import-figma-clipboard.ts +134 -0
  104. package/corpus/templates/design/actions/import-figma-frame.ts +95 -0
  105. package/corpus/templates/design/actions/index-design-system-with-builder.ts +16 -1
  106. package/corpus/templates/design/actions/list-design-components.ts +104 -0
  107. package/corpus/templates/design/actions/list-figma-library-assets.ts +1 -24
  108. package/corpus/templates/design/actions/open-component-source.ts +2 -4
  109. package/corpus/templates/design/actions/present-design-variants.ts +412 -16
  110. package/corpus/templates/design/actions/preview-source-edit.ts +14 -1
  111. package/corpus/templates/design/actions/run-design-audit.ts +25 -3
  112. package/corpus/templates/design/actions/show-design-questions.ts +10 -0
  113. package/corpus/templates/design/actions/swap-component-instance.ts +544 -0
  114. package/corpus/templates/design/actions/take-design-screenshot.ts +45 -60
  115. package/corpus/templates/design/actions/write-local-file.ts +25 -1
  116. package/corpus/templates/design/app/components/design/CanvasContextMenu.tsx +152 -0
  117. package/corpus/templates/design/app/components/design/DesignCanvas.tsx +1077 -58
  118. package/corpus/templates/design/app/components/design/DesignEditorSkeleton.tsx +5 -5
  119. package/corpus/templates/design/app/components/design/DesignExtensionsPanel.tsx +73 -21
  120. package/corpus/templates/design/app/components/design/EditPanel.tsx +16 -11
  121. package/corpus/templates/design/app/components/design/KeyboardShortcutsPanel.tsx +364 -0
  122. package/corpus/templates/design/app/components/design/LayersPanel.tsx +180 -53
  123. package/corpus/templates/design/app/components/design/LocalhostWriteConsentDialog.tsx +0 -2
  124. package/corpus/templates/design/app/components/design/MotionDock.tsx +144 -21
  125. package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +1152 -167
  126. package/corpus/templates/design/app/components/design/QuestionFlow.tsx +27 -5
  127. package/corpus/templates/design/app/components/design/ReviewPanel.tsx +23 -2
  128. package/corpus/templates/design/app/components/design/TokensPanel.tsx +63 -49
  129. package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +1293 -54
  130. package/corpus/templates/design/app/components/design/bridge/embedded-wheel.bridge.ts +7 -0
  131. package/corpus/templates/design/app/components/design/bridge/hit-test.bridge.ts +6 -1
  132. package/corpus/templates/design/app/components/design/bridge/motion-preview.bridge.ts +23 -6
  133. package/corpus/templates/design/app/components/design/bridge/sample.bridge.ts +7 -0
  134. package/corpus/templates/design/app/components/design/code-workbench/CodeWorkbench.tsx +5 -0
  135. package/corpus/templates/design/app/components/design/code-workbench/editor/MonacoHost.tsx +14 -2
  136. package/corpus/templates/design/app/components/design/code-workbench/explorer/FileTree.tsx +37 -2
  137. package/corpus/templates/design/app/components/design/code-workbench/format/format-on-open-guard.ts +25 -0
  138. package/corpus/templates/design/app/components/design/code-workbench/format/format-on-open.ts +19 -1
  139. package/corpus/templates/design/app/components/design/code-workbench/model-registry.ts +43 -5
  140. package/corpus/templates/design/app/components/design/code-workbench/search/SearchView.tsx +27 -2
  141. package/corpus/templates/design/app/components/design/code-workbench/search/search-engine.ts +10 -1
  142. package/corpus/templates/design/app/components/design/code-workbench/store.tsx +19 -2
  143. package/corpus/templates/design/app/components/design/design-canvas/annotation-snapshot.ts +265 -0
  144. package/corpus/templates/design/app/components/design/design-canvas/annotation-submit.ts +16 -5
  145. package/corpus/templates/design/app/components/design/design-canvas/coordinate-transforms.ts +16 -2
  146. package/corpus/templates/design/app/components/design/design-canvas/embedded-frame.ts +7 -2
  147. package/corpus/templates/design/app/components/design/design-canvas/iframe-events.ts +3 -1
  148. package/corpus/templates/design/app/components/design/edit-panel/code-inspect-helpers.tsx +78 -8
  149. package/corpus/templates/design/app/components/design/edit-panel/component-section.tsx +451 -73
  150. package/corpus/templates/design/app/components/design/edit-panel/document-colors.ts +12 -4
  151. package/corpus/templates/design/app/components/design/edit-panel/effects-properties.tsx +398 -272
  152. package/corpus/templates/design/app/components/design/edit-panel/element-classification.ts +41 -7
  153. package/corpus/templates/design/app/components/design/edit-panel/element-identity.ts +16 -3
  154. package/corpus/templates/design/app/components/design/edit-panel/field-primitives.tsx +24 -4
  155. package/corpus/templates/design/app/components/design/edit-panel/fill-gradient-helpers.ts +222 -4
  156. package/corpus/templates/design/app/components/design/edit-panel/fill-properties.tsx +171 -78
  157. package/corpus/templates/design/app/components/design/edit-panel/inspector-controls.tsx +45 -5
  158. package/corpus/templates/design/app/components/design/edit-panel/layout-properties.tsx +299 -28
  159. package/corpus/templates/design/app/components/design/edit-panel/panel-primitives.tsx +120 -150
  160. package/corpus/templates/design/app/components/design/edit-panel/position-helpers.ts +53 -1
  161. package/corpus/templates/design/app/components/design/edit-panel/position-layout-properties.tsx +284 -117
  162. package/corpus/templates/design/app/components/design/edit-panel/selection-helpers.ts +81 -0
  163. package/corpus/templates/design/app/components/design/edit-panel/stroke-properties.tsx +19 -14
  164. package/corpus/templates/design/app/components/design/edit-panel/style-options.ts +7 -0
  165. package/corpus/templates/design/app/components/design/edit-panel/typography-helpers.ts +155 -0
  166. package/corpus/templates/design/app/components/design/edit-panel/typography-properties.tsx +268 -46
  167. package/corpus/templates/design/app/components/design/inspector/AutoLayoutMatrix.tsx +350 -31
  168. package/corpus/templates/design/app/components/design/inspector/ConstraintsWidget.tsx +13 -4
  169. package/corpus/templates/design/app/components/design/inspector/DesignColorPicker.tsx +233 -23
  170. package/corpus/templates/design/app/components/design/inspector/GradientEditor.tsx +72 -2
  171. package/corpus/templates/design/app/components/design/inspector/ScrubInput.tsx +51 -11
  172. package/corpus/templates/design/app/components/design/inspector/ShaderFillsPanel.tsx +84 -11
  173. package/corpus/templates/design/app/components/design/inspector/index.ts +3 -0
  174. package/corpus/templates/design/app/components/design/keyboard-shortcuts.ts +552 -0
  175. package/corpus/templates/design/app/components/design/multi-screen/board-surface-html.ts +157 -0
  176. package/corpus/templates/design/app/components/design/multi-screen/frame-geometry.ts +53 -0
  177. package/corpus/templates/design/app/components/design/multi-screen/overview-layout.ts +184 -0
  178. package/corpus/templates/design/app/components/design/multi-screen/primitive-drop-target.ts +403 -26
  179. package/corpus/templates/design/app/components/design/multi-screen/types.ts +20 -1
  180. package/corpus/templates/design/app/components/design/types.ts +33 -0
  181. package/corpus/templates/design/app/components/editor/PromptDialog.tsx +59 -7
  182. package/corpus/templates/design/app/components/visual-editor/CanvasCommentPins.tsx +119 -17
  183. package/corpus/templates/design/app/components/visual-editor/DrawOverlay.tsx +275 -63
  184. package/corpus/templates/design/app/hooks/use-question-flow.ts +14 -3
  185. package/corpus/templates/design/app/hooks/useDesignHotkeys.ts +67 -10
  186. package/corpus/templates/design/app/i18n/zh-TW.ts +91 -1
  187. package/corpus/templates/design/app/i18n-data.ts +1217 -10
  188. package/corpus/templates/design/app/i18n-keyboard-shortcuts.ts +577 -0
  189. package/corpus/templates/design/app/lib/agent-chat.ts +23 -0
  190. package/corpus/templates/design/app/lib/design-import.ts +24 -0
  191. package/corpus/templates/design/app/lib/figma-clipboard.ts +157 -0
  192. package/corpus/templates/design/app/lib/figma-svg-copy.ts +221 -0
  193. package/corpus/templates/design/app/pages/design-editor/code-layer-state.ts +143 -30
  194. package/corpus/templates/design/app/pages/design-editor/collab-sync.ts +15 -0
  195. package/corpus/templates/design/app/pages/design-editor/editor-session.ts +33 -0
  196. package/corpus/templates/design/app/pages/design-editor/editor-state.ts +38 -0
  197. package/corpus/templates/design/app/pages/design-editor/overview-annotation-context.ts +157 -0
  198. package/corpus/templates/design/app/pages/design-editor/overview-camera.ts +62 -6
  199. package/corpus/templates/design/app/pages/design-editor/pending-edits.ts +245 -0
  200. package/corpus/templates/design/app/pages/design-editor/react-semantic-handoff.ts +586 -0
  201. package/corpus/templates/design/app/pages/design-editor/screen-auto-layout.ts +169 -0
  202. package/corpus/templates/design/app/pages/design-editor/selection-state.ts +201 -1
  203. package/corpus/templates/design/changelog/2026-07-09-added-underline-strikethrough-and-text-case-uppercase-lowerc.md +6 -0
  204. package/corpus/templates/design/changelog/2026-07-09-annotate-now-draws-across-the-all-screens-canvas-without-swi.md +6 -0
  205. package/corpus/templates/design/changelog/2026-07-09-auto-layout-children-can-now-be-dragged-into-freeform-screen.md +6 -0
  206. package/corpus/templates/design/changelog/2026-07-09-board-shapes-now-stay-visible-and-nest-correctly-across-the-.md +6 -0
  207. package/corpus/templates/design/changelog/2026-07-09-component-source-navigation-now-opens-reliably-from-the-insp.md +6 -0
  208. package/corpus/templates/design/changelog/2026-07-09-cross-screen-local-react-layer-moves-now-hand-off-exact-sour.md +6 -0
  209. package/corpus/templates/design/changelog/2026-07-09-cross-screen-moves-undo-and-redo-now-keep-both-screens-stabl.md +6 -0
  210. package/corpus/templates/design/changelog/2026-07-09-detach-a-component-instance-into-plain-editable-eleme.md +6 -0
  211. package/corpus/templates/design/changelog/2026-07-09-dragging-layers-into-auto-layout-now-respects-ignore-auto-la.md +6 -0
  212. package/corpus/templates/design/changelog/2026-07-09-drawing-annotations-sent-to-the-agent-now-include-a-rendered.md +6 -0
  213. package/corpus/templates/design/changelog/2026-07-09-editor-sidebars-now-use-figma-matched-default-widths-for-a-r.md +6 -0
  214. package/corpus/templates/design/changelog/2026-07-09-empty-frames-and-rectangles-now-expose-full-auto-layout-cont.md +6 -0
  215. package/corpus/templates/design/changelog/2026-07-09-export-any-design-screen-as-a-real-vector-svg-for-figma-with.md +6 -0
  216. package/corpus/templates/design/changelog/2026-07-09-grid-auto-layout-now-uses-true-responsive-rows-and-columns-w.md +6 -0
  217. package/corpus/templates/design/changelog/2026-07-09-hidden-and-locked-screens-now-behave-consistently-between-la.md +6 -0
  218. package/corpus/templates/design/changelog/2026-07-09-image-fills-now-preserve-valid-urls-and-layered-fit-settings.md +6 -0
  219. package/corpus/templates/design/changelog/2026-07-09-import-figma-frames-as-pixel-accurate-design-screens-via-a-s.md +6 -0
  220. package/corpus/templates/design/changelog/2026-07-09-inspect-code-now-hides-internal-styling-metadata-and-wraps-l.md +6 -0
  221. package/corpus/templates/design/changelog/2026-07-09-inspector-blur-and-shadow-effects-now-preserve-other-effects.md +6 -0
  222. package/corpus/templates/design/changelog/2026-07-09-jump-to-a-components-other-instances-from-the-inspecto.md +6 -0
  223. package/corpus/templates/design/changelog/2026-07-09-keyboard-shortcuts-are-now-discoverable-in-a-figma-style-bot.md +6 -0
  224. package/corpus/templates/design/changelog/2026-07-09-layer-selection-now-keeps-cmd-or-ctrl-toggles-and-shift-rang.md +6 -0
  225. package/corpus/templates/design/changelog/2026-07-09-layers-dropped-into-auto-layout-now-participate-correctly-in.md +6 -0
  226. package/corpus/templates/design/changelog/2026-07-09-local-live-edit-screens-now-enter-and-leave-full-view-withou.md +6 -0
  227. package/corpus/templates/design/changelog/2026-07-09-local-preview-urls-now-live-in-the-inspector-instead-of-taki.md +6 -0
  228. package/corpus/templates/design/changelog/2026-07-09-local-react-layers-can-now-be-hidden-and-locked-from-layers-.md +6 -0
  229. package/corpus/templates/design/changelog/2026-07-09-local-react-layers-now-keep-exact-source-provenance-and-supp.md +6 -0
  230. package/corpus/templates/design/changelog/2026-07-09-local-react-visual-editing-now-loads-reliably-and-shows-live.md +6 -0
  231. package/corpus/templates/design/changelog/2026-07-09-paste-from-figma-cmd-v-now-imports-exact-editable-nodes-when.md +6 -0
  232. package/corpus/templates/design/changelog/2026-07-09-pressing-escape-after-selecting-a-screen-now-reveals-page-pr.md +6 -0
  233. package/corpus/templates/design/changelog/2026-07-09-right-click-overlapping-or-nested-objects-to-choose-the-exac.md +6 -0
  234. package/corpus/templates/design/changelog/2026-07-09-scale-edge-stretch-and-center-constraints-now-preserve-layer.md +6 -0
  235. package/corpus/templates/design/changelog/2026-07-09-screen-rows-can-now-be-reordered-in-layers-with-matching-can.md +6 -0
  236. package/corpus/templates/design/changelog/2026-07-09-screen-selection-stays-responsive-in-designs-with-hundreds-o.md +6 -0
  237. package/corpus/templates/design/changelog/2026-07-09-shift-a-now-enables-auto-layout-on-a-selected-screen-with-fl.md +6 -0
  238. package/corpus/templates/design/changelog/2026-07-09-swap-a-component-instance-for-another-from-the-libra.md +6 -0
  239. package/corpus/templates/design/changelog/2026-07-10-drawing-annotations-now-stay-available-to-retry-when-the.md +6 -0
  240. package/corpus/templates/design/e2e/global-setup.ts +3 -6
  241. package/corpus/templates/design/e2e/helpers.ts +27 -6
  242. package/corpus/templates/design/server/lib/design-to-figma-svg.ts +1412 -0
  243. package/corpus/templates/design/server/lib/figma-clipboard-match.ts +196 -0
  244. package/corpus/templates/design/server/lib/figma-node-import.ts +311 -0
  245. package/corpus/templates/design/server/lib/figma-node-to-html.ts +1334 -0
  246. package/corpus/templates/design/server/lib/figma-paste-fallback.ts +64 -0
  247. package/corpus/templates/design/server/lib/playwright-runtime.ts +78 -0
  248. package/corpus/templates/design/server/plugins/db.ts +41 -0
  249. package/corpus/templates/design/server/source-workspace.ts +64 -0
  250. package/corpus/templates/design/shared/board-file.ts +13 -2
  251. package/corpus/templates/design/shared/canvas-math.ts +231 -33
  252. package/corpus/templates/design/shared/code-layer.ts +122 -8
  253. package/corpus/templates/design/shared/component-library.ts +161 -0
  254. package/corpus/templates/design/shared/db-conflict.ts +27 -0
  255. package/corpus/templates/design/shared/design-source-capabilities.ts +2 -1
  256. package/corpus/templates/design/shared/figma-url.ts +149 -0
  257. package/corpus/templates/design/shared/pen-path.ts +16 -7
  258. package/corpus/templates/design/shared/source-mode.ts +1 -0
  259. package/corpus/templates/plan/app/components/plan/CanvasArea.tsx +7 -19
  260. package/corpus/templates/plan/changelog/2026-07-09-canvas-panning-now-keeps-zoom-fixed-even-at-the-edges.md +6 -0
  261. package/corpus/templates/slides/actions/index-design-system-with-builder.ts +16 -1
  262. package/corpus/templates/slides/app/context/DeckContext.tsx +311 -70
  263. package/corpus/templates/slides/changelog/2026-07-09-fixed-the-slide-rail-going-permanently-stale-during-a-long-a.md +6 -0
  264. package/dist/agent/production-agent.d.ts +79 -6
  265. package/dist/agent/production-agent.d.ts.map +1 -1
  266. package/dist/agent/production-agent.js +161 -14
  267. package/dist/agent/production-agent.js.map +1 -1
  268. package/dist/agent/run-manager.d.ts.map +1 -1
  269. package/dist/agent/run-manager.js +29 -5
  270. package/dist/agent/run-manager.js.map +1 -1
  271. package/dist/agent/run-store.d.ts +46 -0
  272. package/dist/agent/run-store.d.ts.map +1 -1
  273. package/dist/agent/run-store.js +61 -0
  274. package/dist/agent/run-store.js.map +1 -1
  275. package/dist/cli/design-connect.d.ts +6 -0
  276. package/dist/cli/design-connect.d.ts.map +1 -1
  277. package/dist/cli/design-connect.js +336 -139
  278. package/dist/cli/design-connect.js.map +1 -1
  279. package/dist/cli/skills.d.ts.map +1 -1
  280. package/dist/cli/skills.js +14 -0
  281. package/dist/cli/skills.js.map +1 -1
  282. package/dist/client/AssistantChat.d.ts +2 -0
  283. package/dist/client/AssistantChat.d.ts.map +1 -1
  284. package/dist/client/AssistantChat.js +19 -3
  285. package/dist/client/AssistantChat.js.map +1 -1
  286. package/dist/client/MultiTabAssistantChat.d.ts.map +1 -1
  287. package/dist/client/MultiTabAssistantChat.js +38 -18
  288. package/dist/client/MultiTabAssistantChat.js.map +1 -1
  289. package/dist/client/agent-chat.d.ts +60 -0
  290. package/dist/client/agent-chat.d.ts.map +1 -1
  291. package/dist/client/agent-chat.js +122 -1
  292. package/dist/client/agent-chat.js.map +1 -1
  293. package/dist/client/chat/index.d.ts +1 -1
  294. package/dist/client/chat/index.d.ts.map +1 -1
  295. package/dist/client/chat/index.js +1 -1
  296. package/dist/client/chat/index.js.map +1 -1
  297. package/dist/client/extensions/EmbeddedExtension.d.ts +12 -0
  298. package/dist/client/extensions/EmbeddedExtension.d.ts.map +1 -1
  299. package/dist/client/extensions/EmbeddedExtension.js +36 -2
  300. package/dist/client/extensions/EmbeddedExtension.js.map +1 -1
  301. package/dist/client/index.d.ts +1 -1
  302. package/dist/client/index.d.ts.map +1 -1
  303. package/dist/client/index.js +1 -1
  304. package/dist/client/index.js.map +1 -1
  305. package/dist/client/rich-markdown-editor/useCollabReconcile.d.ts.map +1 -1
  306. package/dist/client/rich-markdown-editor/useCollabReconcile.js +18 -6
  307. package/dist/client/rich-markdown-editor/useCollabReconcile.js.map +1 -1
  308. package/dist/collab/awareness-store.d.ts +5 -0
  309. package/dist/collab/awareness-store.d.ts.map +1 -1
  310. package/dist/collab/awareness-store.js +19 -12
  311. package/dist/collab/awareness-store.js.map +1 -1
  312. package/dist/collab/awareness.d.ts +2 -2
  313. package/dist/collab/awareness.d.ts.map +1 -1
  314. package/dist/collab/client.d.ts.map +1 -1
  315. package/dist/collab/client.js +13 -5
  316. package/dist/collab/client.js.map +1 -1
  317. package/dist/collab/index.d.ts +1 -0
  318. package/dist/collab/index.d.ts.map +1 -1
  319. package/dist/collab/index.js +1 -0
  320. package/dist/collab/index.js.map +1 -1
  321. package/dist/collab/recent-edits.d.ts +3 -1
  322. package/dist/collab/recent-edits.d.ts.map +1 -1
  323. package/dist/collab/recent-edits.js +57 -2
  324. package/dist/collab/recent-edits.js.map +1 -1
  325. package/dist/collab/routes.d.ts +1 -1
  326. package/dist/deploy/build.d.ts +24 -0
  327. package/dist/deploy/build.d.ts.map +1 -1
  328. package/dist/deploy/build.js +102 -5
  329. package/dist/deploy/build.js.map +1 -1
  330. package/dist/index.browser.d.ts +1 -1
  331. package/dist/index.browser.d.ts.map +1 -1
  332. package/dist/index.browser.js +1 -1
  333. package/dist/index.browser.js.map +1 -1
  334. package/dist/notifications/routes.d.ts +1 -1
  335. package/dist/observability/routes.d.ts +5 -5
  336. package/dist/progress/routes.d.ts +1 -1
  337. package/dist/server/agent-chat-plugin.d.ts.map +1 -1
  338. package/dist/server/agent-chat-plugin.js +87 -10
  339. package/dist/server/agent-chat-plugin.js.map +1 -1
  340. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  341. package/dist/server/builder-design-systems.d.ts +15 -0
  342. package/dist/server/builder-design-systems.d.ts.map +1 -1
  343. package/dist/server/builder-design-systems.js +9 -4
  344. package/dist/server/builder-design-systems.js.map +1 -1
  345. package/package.json +1 -1
@@ -25,8 +25,8 @@
25
25
  //! | `audio_transcription_reset_timeline`| Rebase transcript timestamps to now. |
26
26
  //! | `audio_transcription_stop` | Stop the capture. |
27
27
  //!
28
- //! `start_raw_system_capture` (in the `macos` submodule) is the capture entry
29
- //! point the Whisper engine calls directly.
28
+ //! `start_raw_system_capture` and `start_raw_meeting_capture` (in the `macos`
29
+ //! submodule) are the capture entry points the Whisper engine calls directly.
30
30
  //!
31
31
  //! ## Events
32
32
  //! - `voice:audio-level` `{ level, source: "system" }` — waveform meter.
@@ -124,7 +124,10 @@ pub async fn audio_transcription_start(
124
124
  mic_device_id,
125
125
  mic_device_label,
126
126
  capture_system.unwrap_or(true),
127
- voice_processing.unwrap_or(true),
127
+ // Fail safe for meeting/recording callers: an omitted flag must not
128
+ // create a second VoiceProcessingIO stack beside a live call app.
129
+ // Short dictation sessions opt in explicitly from the renderer.
130
+ voice_processing.unwrap_or(false),
128
131
  owner,
129
132
  )
130
133
  .await
@@ -246,6 +249,8 @@ pub(crate) mod macos {
246
249
  app: AppHandle,
247
250
  cancelled: Arc<AtomicBool>,
248
251
  level_tick: Arc<AtomicU32>,
252
+ output_type: SCStreamOutputType,
253
+ source: &'static str,
249
254
  }
250
255
 
251
256
  // SAFETY: `Retained<SFSpeech*>` and `Retained<AVAudioFormat>` wrap
@@ -261,7 +266,7 @@ pub(crate) mod macos {
261
266
  sample_buffer: CMSampleBuffer,
262
267
  of_type: SCStreamOutputType,
263
268
  ) {
264
- if of_type != SCStreamOutputType::Audio {
269
+ if of_type != self.output_type {
265
270
  return;
266
271
  }
267
272
  if self.cancelled.load(Ordering::SeqCst) {
@@ -287,7 +292,7 @@ pub(crate) mod macos {
287
292
  "voice:audio-level",
288
293
  AudioLevelPayload {
289
294
  level,
290
- source: "system",
295
+ source: self.source,
291
296
  },
292
297
  );
293
298
  }
@@ -295,17 +300,18 @@ pub(crate) mod macos {
295
300
  }
296
301
  }
297
302
 
298
- /// Handle for a running raw system capture. `stop()` ends the SCK stream.
299
- pub(crate) struct RawSystemCapture {
303
+ /// Handle for a running raw SCK audio capture. A meeting capture can carry
304
+ /// both system and microphone output handlers on this one stream.
305
+ pub(crate) struct RawSckAudioCapture {
300
306
  stream: SCStream,
301
307
  cancelled: Arc<AtomicBool>,
302
308
  }
303
309
 
304
310
  // SAFETY: `SCStream` is `Send` (the crate marks it so); the atomic is
305
311
  // trivially `Send`. We only move the handle through ownership.
306
- unsafe impl Send for RawSystemCapture {}
312
+ unsafe impl Send for RawSckAudioCapture {}
307
313
 
308
- impl RawSystemCapture {
314
+ impl RawSckAudioCapture {
309
315
  pub(crate) fn stop(self) {
310
316
  self.cancelled.store(true, Ordering::SeqCst);
311
317
  let _ = self.stream.stop_capture();
@@ -317,7 +323,50 @@ pub(crate) mod macos {
317
323
  pub(crate) fn start_raw_system_capture(
318
324
  app: AppHandle,
319
325
  on_samples: Arc<dyn Fn(&[f32]) + Send + Sync>,
320
- ) -> Result<RawSystemCapture, String> {
326
+ ) -> Result<RawSckAudioCapture, String> {
327
+ start_raw_sck_audio_capture(app, true, None, None, Some(on_samples), None)
328
+ }
329
+
330
+ /// Whether this macOS version supports ScreenCaptureKit's independent
331
+ /// microphone output (`SCStreamOutputType::Microphone`).
332
+ pub(crate) fn supports_sck_microphone_capture() -> bool {
333
+ let version = NSProcessInfo::processInfo().operatingSystemVersion();
334
+ version.majorVersion >= 15
335
+ }
336
+
337
+ /// Start one ScreenCaptureKit stream with independent system-audio and
338
+ /// microphone callbacks. This avoids opening a second AVAudioEngine /
339
+ /// VoiceProcessingIO input while Zoom, Meet, or Teams owns the live-call
340
+ /// microphone uplink.
341
+ pub(crate) fn start_raw_meeting_capture(
342
+ app: AppHandle,
343
+ mic_device_id: Option<String>,
344
+ mic_device_label: Option<String>,
345
+ capture_system: bool,
346
+ on_mic_samples: Arc<dyn Fn(&[f32]) + Send + Sync>,
347
+ on_system_samples: Option<Arc<dyn Fn(&[f32]) + Send + Sync>>,
348
+ ) -> Result<RawSckAudioCapture, String> {
349
+ if !supports_sck_microphone_capture() {
350
+ return Err("ScreenCaptureKit microphone capture requires macOS 15 or later.".into());
351
+ }
352
+ start_raw_sck_audio_capture(
353
+ app,
354
+ capture_system,
355
+ mic_device_id,
356
+ mic_device_label,
357
+ on_system_samples,
358
+ Some(on_mic_samples),
359
+ )
360
+ }
361
+
362
+ fn start_raw_sck_audio_capture(
363
+ app: AppHandle,
364
+ capture_system: bool,
365
+ mic_device_id: Option<String>,
366
+ mic_device_label: Option<String>,
367
+ on_system_samples: Option<Arc<dyn Fn(&[f32]) + Send + Sync>>,
368
+ on_mic_samples: Option<Arc<dyn Fn(&[f32]) + Send + Sync>>,
369
+ ) -> Result<RawSckAudioCapture, String> {
321
370
  let granted = unsafe { CGPreflightScreenCaptureAccess() };
322
371
  if !granted {
323
372
  let granted_now = unsafe { CGRequestScreenCaptureAccess() };
@@ -340,13 +389,30 @@ pub(crate) mod macos {
340
389
  .with_excluding_windows(&[])
341
390
  .build();
342
391
 
343
- let config = SCStreamConfiguration::new()
344
- .with_captures_audio(true)
392
+ let capture_microphone = on_mic_samples.is_some();
393
+ let selected_mic = if capture_microphone {
394
+ crate::native_screen::resolve_microphone_capture_device(
395
+ mic_device_id.as_deref(),
396
+ mic_device_label.as_deref(),
397
+ )?
398
+ } else {
399
+ None
400
+ };
401
+ let mut config = SCStreamConfiguration::new()
402
+ .with_captures_audio(capture_system)
403
+ .with_captures_microphone(capture_microphone)
345
404
  .with_excludes_current_process_audio(true)
346
405
  .with_sample_rate(48000)
347
406
  .with_channel_count(2)
348
407
  .with_width(2)
349
408
  .with_height(2);
409
+ if let Some(device) = selected_mic.as_ref() {
410
+ config.set_microphone_capture_device_id(&device.id);
411
+ eprintln!(
412
+ "[whisper] ScreenCaptureKit meeting microphone pinned to {} ({})",
413
+ device.name, device.id
414
+ );
415
+ }
350
416
 
351
417
  // Mono float32 @ 48 kHz destination format for the mono-mix.
352
418
  let speech_format = unsafe {
@@ -357,21 +423,37 @@ pub(crate) mod macos {
357
423
 
358
424
  let cancelled = Arc::new(AtomicBool::new(false));
359
425
  let mut stream = SCStream::new(&filter, &config);
360
- let forwarder = RawAudioForwarder {
361
- on_samples,
362
- speech_format,
363
- app: app.clone(),
364
- cancelled: cancelled.clone(),
365
- level_tick: Arc::new(AtomicU32::new(0)),
366
- };
367
- stream.add_output_handler(forwarder, SCStreamOutputType::Audio);
426
+ if let Some(on_samples) = on_system_samples {
427
+ let forwarder = RawAudioForwarder {
428
+ on_samples,
429
+ speech_format: speech_format.clone(),
430
+ app: app.clone(),
431
+ cancelled: cancelled.clone(),
432
+ level_tick: Arc::new(AtomicU32::new(0)),
433
+ output_type: SCStreamOutputType::Audio,
434
+ source: "system",
435
+ };
436
+ stream.add_output_handler(forwarder, SCStreamOutputType::Audio);
437
+ }
438
+ if let Some(on_samples) = on_mic_samples {
439
+ let forwarder = RawAudioForwarder {
440
+ on_samples,
441
+ speech_format,
442
+ app: app.clone(),
443
+ cancelled: cancelled.clone(),
444
+ level_tick: Arc::new(AtomicU32::new(0)),
445
+ output_type: SCStreamOutputType::Microphone,
446
+ source: "mic",
447
+ };
448
+ stream.add_output_handler(forwarder, SCStreamOutputType::Microphone);
449
+ }
368
450
 
369
451
  if let Err(e) = stream.start_capture() {
370
452
  cancelled.store(true, Ordering::SeqCst);
371
453
  return Err(format!("SCStream start_capture failed: {e:?}"));
372
454
  }
373
455
 
374
- Ok(RawSystemCapture { stream, cancelled })
456
+ Ok(RawSckAudioCapture { stream, cancelled })
375
457
  }
376
458
 
377
459
  #[derive(Serialize, Clone)]
@@ -10,7 +10,9 @@
10
10
  //! Capture is reused from the existing modules:
11
11
  //! - mic → `native_speech::macos::start_raw_mic_capture` (AVAudioEngine +
12
12
  //! optional VoiceProcessingIO AEC, other-audio ducking off)
13
- //! - system`system_audio::macos::start_raw_system_capture` (ScreenCaptureKit)
13
+ //! - meetings on macOS 15+ one ScreenCaptureKit stream with independent
14
+ //! microphone + system-audio outputs
15
+ //! - legacy system audio → `system_audio::macos::start_raw_system_capture`
14
16
  //!
15
17
  use tauri::AppHandle;
16
18
 
@@ -108,7 +110,10 @@ mod macos {
108
110
  use whisper_rs::{FullParams, SamplingStrategy, WhisperContext, WhisperContextParameters};
109
111
 
110
112
  use crate::native_speech::macos::{start_raw_mic_capture, RawMicCapture};
111
- use crate::system_audio::macos::{start_raw_system_capture, RawSystemCapture};
113
+ use crate::system_audio::macos::{
114
+ start_raw_meeting_capture, start_raw_system_capture, supports_sck_microphone_capture,
115
+ RawSckAudioCapture,
116
+ };
112
117
  use crate::whisper_model::{ensure_model, model_file};
113
118
 
114
119
  /// One transcript segment with real timestamps from whisper, already
@@ -571,11 +576,20 @@ mod macos {
571
576
  }
572
577
  }
573
578
 
579
+ fn should_use_combined_sck_capture(
580
+ owner: SessionOwner,
581
+ microphone_capture_supported: bool,
582
+ ) -> bool {
583
+ owner == SessionOwner::Meeting && microphone_capture_supported
584
+ }
585
+
574
586
  struct Session {
575
- mic_cap: RawMicCapture,
587
+ // macOS 15+ meetings use a combined SCK capture, so there is no
588
+ // competing AVAudioEngine / VoiceProcessingIO mic input to stop.
589
+ mic_cap: Option<RawMicCapture>,
576
590
  // System capture is optional — skipped when the user turns system
577
591
  // audio off, so neither the recording nor the transcript include it.
578
- sys_cap: Option<RawSystemCapture>,
592
+ sys_cap: Option<RawSckAudioCapture>,
579
593
  mic: Arc<WhisperStream>,
580
594
  sys: Option<Arc<WhisperStream>>,
581
595
  /// Who started this session — see `SessionOwner`.
@@ -644,8 +658,10 @@ mod macos {
644
658
  let _ = language;
645
659
  let lang: Option<String> = None;
646
660
 
647
- // Mic stream + capture. The real hardware rate is read back from the
648
- // capture handle and pushed into the stream (default 48 kHz until then).
661
+ // Create both Whisper streams first. On macOS 15+ meetings, one
662
+ // ScreenCaptureKit stream feeds both callbacks without opening a
663
+ // competing VoiceProcessingIO mic input. Older macOS versions (and a
664
+ // failed SCK start) keep the existing split-capture fallback.
649
665
  let session_start = Instant::now();
650
666
  let mic_stream = WhisperStream::new(
651
667
  app.clone(),
@@ -655,50 +671,92 @@ mod macos {
655
671
  ctx.clone(),
656
672
  session_start,
657
673
  );
658
- let mic_for_cb = mic_stream.clone();
659
- let reuse_voice_processing_engine = owner == SessionOwner::Dictation && !capture_system;
660
- let mic_cap = start_raw_mic_capture(
661
- app.clone(),
662
- mic_device_id,
663
- mic_device_label,
664
- voice_processing,
665
- reuse_voice_processing_engine,
666
- Arc::new(move |s: &[f32]| mic_for_cb.push(s)),
667
- )
668
- .map_err(|e| {
669
- mic_stream.stop();
670
- format!("mic capture failed: {e}")
671
- })?;
672
- mic_stream.set_src_rate(mic_cap.sample_rate());
673
-
674
- // System stream + capture (SCK delivers 48 kHz). Skipped entirely when
675
- // system audio is off.
676
- let (sys_stream, sys_cap) = if capture_system {
677
- let sys_stream = WhisperStream::new(
674
+ let sys_stream = capture_system.then(|| {
675
+ WhisperStream::new(
678
676
  app.clone(),
679
677
  "system",
680
678
  48000.0,
681
679
  lang.clone(),
682
680
  ctx.clone(),
683
681
  session_start,
684
- );
685
- let sys_for_cb = sys_stream.clone();
686
- let sys_cap = match start_raw_system_capture(
682
+ )
683
+ });
684
+ let mic_for_cb = mic_stream.clone();
685
+ let mic_callback: Arc<dyn Fn(&[f32]) + Send + Sync> =
686
+ Arc::new(move |samples: &[f32]| mic_for_cb.push(samples));
687
+ let system_callback: Option<Arc<dyn Fn(&[f32]) + Send + Sync>> =
688
+ sys_stream.as_ref().map(|stream| {
689
+ let stream = stream.clone();
690
+ Arc::new(move |samples: &[f32]| stream.push(samples))
691
+ as Arc<dyn Fn(&[f32]) + Send + Sync>
692
+ });
693
+
694
+ let combined_cap = if should_use_combined_sck_capture(
695
+ owner,
696
+ supports_sck_microphone_capture(),
697
+ ) {
698
+ match start_raw_meeting_capture(
687
699
  app.clone(),
688
- Arc::new(move |s: &[f32]| sys_for_cb.push(s)),
700
+ mic_device_id.clone(),
701
+ mic_device_label.clone(),
702
+ capture_system,
703
+ mic_callback.clone(),
704
+ system_callback.clone(),
689
705
  ) {
690
- Ok(cap) => cap,
706
+ Ok(cap) => {
707
+ eprintln!("[whisper] using combined ScreenCaptureKit mic + system capture");
708
+ Some(cap)
709
+ }
691
710
  Err(e) => {
692
- // Roll back the mic side so we don't leave a half-open meeting.
711
+ eprintln!(
712
+ "[whisper] combined ScreenCaptureKit meeting capture failed: {e}; falling back to split capture"
713
+ );
714
+ None
715
+ }
716
+ }
717
+ } else {
718
+ None
719
+ };
720
+
721
+ let (mic_cap, sys_cap) = if let Some(combined_cap) = combined_cap {
722
+ // Both SCK outputs are configured at 48 kHz.
723
+ mic_stream.set_src_rate(48000.0);
724
+ (None, Some(combined_cap))
725
+ } else {
726
+ let reuse_voice_processing_engine = owner == SessionOwner::Dictation && !capture_system;
727
+ let mic_cap = start_raw_mic_capture(
728
+ app.clone(),
729
+ mic_device_id,
730
+ mic_device_label,
731
+ voice_processing,
732
+ reuse_voice_processing_engine,
733
+ mic_callback,
734
+ )
735
+ .map_err(|e| {
736
+ mic_stream.stop();
737
+ if let Some(sys_stream) = &sys_stream {
693
738
  sys_stream.stop();
694
- mic_stream.stop();
695
- mic_cap.stop();
696
- return Err(format!("system capture failed: {e}"));
697
739
  }
740
+ format!("mic capture failed: {e}")
741
+ })?;
742
+ mic_stream.set_src_rate(mic_cap.sample_rate());
743
+
744
+ let sys_cap = if let Some(system_callback) = system_callback {
745
+ match start_raw_system_capture(app.clone(), system_callback) {
746
+ Ok(cap) => Some(cap),
747
+ Err(e) => {
748
+ if let Some(sys_stream) = &sys_stream {
749
+ sys_stream.stop();
750
+ }
751
+ mic_stream.stop();
752
+ mic_cap.stop();
753
+ return Err(format!("system capture failed: {e}"));
754
+ }
755
+ }
756
+ } else {
757
+ None
698
758
  };
699
- (Some(sys_stream), Some(sys_cap))
700
- } else {
701
- (None, None)
759
+ (Some(mic_cap), sys_cap)
702
760
  };
703
761
 
704
762
  let mut slot = session_slot().lock().map_err(|e| e.to_string())?;
@@ -750,7 +808,9 @@ mod macos {
750
808
  sys.stop();
751
809
  }
752
810
  // Stop captures so no more samples arrive while workers flush.
753
- session.mic_cap.stop();
811
+ if let Some(mic_cap) = session.mic_cap {
812
+ mic_cap.stop();
813
+ }
754
814
  if let Some(sys_cap) = session.sys_cap {
755
815
  sys_cap.stop();
756
816
  }
@@ -769,4 +829,22 @@ mod macos {
769
829
  }
770
830
  eprintln!("[whisper] meeting transcription stopped");
771
831
  }
832
+
833
+ #[cfg(test)]
834
+ mod tests {
835
+ use super::{should_use_combined_sck_capture, SessionOwner};
836
+
837
+ #[test]
838
+ fn combined_sck_capture_is_only_selected_for_supported_meetings() {
839
+ assert!(should_use_combined_sck_capture(SessionOwner::Meeting, true));
840
+ assert!(!should_use_combined_sck_capture(
841
+ SessionOwner::Meeting,
842
+ false
843
+ ));
844
+ assert!(!should_use_combined_sck_capture(
845
+ SessionOwner::Dictation,
846
+ true
847
+ ));
848
+ }
849
+ }
772
850
  }
@@ -28,6 +28,7 @@ type SlackUnfurlRecording = {
28
28
  id: string;
29
29
  title: string;
30
30
  description: string;
31
+ durationMs: number;
31
32
  thumbnailUrl: string | null;
32
33
  animatedThumbnailUrl: string | null;
33
34
  visibility: string | null;
@@ -205,6 +206,19 @@ function isExpired(value: string | null): boolean {
205
206
  return Number.isFinite(expires) && expires < Date.now();
206
207
  }
207
208
 
209
+ function formatSlackDuration(durationMs: number): string | null {
210
+ if (!Number.isFinite(durationMs) || durationMs <= 0) return null;
211
+ const totalSeconds = Math.floor(durationMs / 1000);
212
+ const hours = Math.floor(totalSeconds / 3600);
213
+ const minutes = Math.floor((totalSeconds % 3600) / 60);
214
+ const seconds = totalSeconds % 60;
215
+ return hours > 0
216
+ ? `${hours}:${minutes.toString().padStart(2, "0")}:${seconds
217
+ .toString()
218
+ .padStart(2, "0")}`
219
+ : `${minutes}:${seconds.toString().padStart(2, "0")}`;
220
+ }
221
+
208
222
  export function isSlackPlayableRecording(
209
223
  recording: SlackUnfurlRecording,
210
224
  ): boolean {
@@ -236,7 +250,11 @@ export function buildSlackVideoBlock(options: {
236
250
  origin,
237
251
  ).toString();
238
252
  const title = displayRecordingTitle(recording.title);
239
- const description = clipsShareDescription(recording);
253
+ const shareDescription = clipsShareDescription(recording);
254
+ const duration = formatSlackDuration(recording.durationMs);
255
+ const description = duration
256
+ ? `${duration} · ${shareDescription}`
257
+ : shareDescription;
240
258
  const thumbnailUrl = absoluteUrl(
241
259
  recording.thumbnailUrl || recording.animatedThumbnailUrl,
242
260
  origin,
@@ -265,6 +283,7 @@ export async function loadSlackVideoBlockForUrl(
265
283
  id: schema.recordings.id,
266
284
  title: schema.recordings.title,
267
285
  description: schema.recordings.description,
286
+ durationMs: schema.recordings.durationMs,
268
287
  thumbnailUrl: schema.recordings.thumbnailUrl,
269
288
  animatedThumbnailUrl: schema.recordings.animatedThumbnailUrl,
270
289
  visibility: schema.recordings.visibility,
@@ -1,8 +1,8 @@
1
1
  import { enabledFlag } from "./env-flags.js";
2
2
 
3
- // Streaming resumable uploads are deployment opt-in while the provider/finalize
4
- // path hardens. Set CLIPS_ENABLE_STREAMING_UPLOAD=1 to allow recorder requests;
5
- // CLIPS_DISABLE_STREAMING_UPLOAD=1 still forces the buffered fallback.
3
+ // Local/dev deployments can opt into resumable uploads while retaining their
4
+ // SQL scratch fallback. Hosted deployments have no safe buffered fallback, so
5
+ // requested video uploads use resumable storage unless explicitly disabled.
6
6
  export function isStreamingUploadDisabled(): boolean {
7
7
  return enabledFlag(process.env.CLIPS_DISABLE_STREAMING_UPLOAD);
8
8
  }
@@ -10,9 +10,15 @@ export function isStreamingUploadDisabled(): boolean {
10
10
  export function shouldEnableStreamingUpload(args: {
11
11
  client?: string | null;
12
12
  mimeType?: string | null;
13
+ bufferedFallbackAvailable?: boolean;
13
14
  }): boolean {
14
15
  if (isStreamingUploadDisabled()) return false;
15
- if (!enabledFlag(process.env.CLIPS_ENABLE_STREAMING_UPLOAD)) return false;
16
+ if (
17
+ args.bufferedFallbackAvailable !== false &&
18
+ !enabledFlag(process.env.CLIPS_ENABLE_STREAMING_UPLOAD)
19
+ ) {
20
+ return false;
21
+ }
16
22
 
17
23
  const mimeType = (args.mimeType ?? "").split(";")[0]?.trim().toLowerCase();
18
24
  return !mimeType || mimeType.startsWith("video/");
@@ -51,6 +51,18 @@ export function pickMimeType(): string {
51
51
  * - `"buffered"` — full blob assembled after stop() and uploaded in slices */
52
52
  export type UploadMode = "streaming" | "buffered";
53
53
 
54
+ /**
55
+ * Resumable providers advance a single byte offset, so their chunks must be
56
+ * sent in strict index order. Buffered uploads can retain bounded parallelism.
57
+ */
58
+ export function chunkUploadParallelism(
59
+ uploadMode: UploadMode | undefined,
60
+ bufferedParallelism: number,
61
+ ): number {
62
+ if (uploadMode === "streaming") return 1;
63
+ return Math.max(1, Math.floor(bufferedParallelism));
64
+ }
65
+
54
66
  /** Query params understood by the chunk-upload route
55
67
  * (`/api/uploads/:id/chunk`). This is the on-the-wire contract — the route in
56
68
  * `server/routes/api/uploads/[recordingId]/chunk.post.ts` reads exactly these. */
@@ -158,13 +158,14 @@ to SQL. `pull-document` closes that gap with a flush handshake — if a live Yjs
158
158
  collab session exists for the document it writes a one-shot `flush-request-<id>`
159
159
  application-state key (scoped to the browser session, just like `navigate`); the
160
160
  open editor sees that key, serializes its current document to markdown through
161
- its own existing serializer, calls `update-document`, and deletes the key;
162
- `pull-document` waits for the key to clear and then returns the now-fresh row.
163
- When no editor is open the SQL column is authoritative and the handshake is
161
+ its own existing serializer, calls `update-document`, and writes an explicit
162
+ request-id-matched success/error acknowledgement. `pull-document` waits for that
163
+ acknowledgement and fails closed if the open editor cannot save; when no active
164
+ human editor is present, the SQL column is authoritative and the handshake is
164
165
  skipped. It is GET + read-only + public-agent exposed (`requiresAuth: true`),
165
- returns `{ id, title, content, format, deepLink }`, and surfaces an
166
- "Open document" deep link for external agents. Use `--format text` for a
167
- plain-text strip of the markdown.
166
+ returns `{ id, title, content, format, deepLink }`, and surfaces an "Open
167
+ document" deep link for external agents. Use `--format text` for a plain-text
168
+ strip of the markdown.
168
169
 
169
170
  ### Local Source Files
170
171