@agent-native/core 0.100.2 → 0.101.3

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 (388) hide show
  1. package/corpus/README.md +2 -2
  2. package/corpus/core/CHANGELOG.md +83 -0
  3. package/corpus/core/docs/content/locales/ar-SA/pr-visual-recap.mdx +48 -30
  4. package/corpus/core/docs/content/locales/ar-SA/template-plan.mdx +7 -0
  5. package/corpus/core/docs/content/locales/de-DE/pr-visual-recap.mdx +48 -30
  6. package/corpus/core/docs/content/locales/de-DE/template-plan.mdx +7 -0
  7. package/corpus/core/docs/content/locales/es-ES/pr-visual-recap.mdx +48 -30
  8. package/corpus/core/docs/content/locales/es-ES/template-plan.mdx +14 -0
  9. package/corpus/core/docs/content/locales/fr-FR/pr-visual-recap.mdx +48 -30
  10. package/corpus/core/docs/content/locales/fr-FR/template-plan.mdx +7 -0
  11. package/corpus/core/docs/content/locales/hi-IN/pr-visual-recap.mdx +48 -30
  12. package/corpus/core/docs/content/locales/hi-IN/template-plan.mdx +7 -0
  13. package/corpus/core/docs/content/locales/ja-JP/pr-visual-recap.mdx +48 -30
  14. package/corpus/core/docs/content/locales/ja-JP/template-plan.mdx +7 -0
  15. package/corpus/core/docs/content/locales/ko-KR/pr-visual-recap.mdx +48 -30
  16. package/corpus/core/docs/content/locales/ko-KR/template-plan.mdx +7 -0
  17. package/corpus/core/docs/content/locales/pt-BR/pr-visual-recap.mdx +48 -30
  18. package/corpus/core/docs/content/locales/pt-BR/template-plan.mdx +14 -0
  19. package/corpus/core/docs/content/locales/zh-CN/pr-visual-recap.mdx +48 -30
  20. package/corpus/core/docs/content/locales/zh-CN/template-plan.mdx +7 -0
  21. package/corpus/core/docs/content/locales/zh-TW/pr-visual-recap.mdx +48 -30
  22. package/corpus/core/docs/content/locales/zh-TW/template-plan.mdx +7 -0
  23. package/corpus/core/docs/content/pr-visual-recap.mdx +50 -32
  24. package/corpus/core/docs/content/template-plan.mdx +14 -0
  25. package/corpus/core/docs/content/toolkit-comments-review.mdx +16 -2
  26. package/corpus/core/package.json +4 -1
  27. package/corpus/core/src/a2a/index.ts +2 -1
  28. package/corpus/core/src/a2a/server.ts +34 -9
  29. package/corpus/core/src/agent/production-agent.ts +56 -10
  30. package/corpus/core/src/cli/create.ts +47 -19
  31. package/corpus/core/src/cli/pr-visual-recap-workflow.ts +2 -3
  32. package/corpus/core/src/cli/recap.ts +3 -4932
  33. package/corpus/core/src/cli/skills-content/visual-plan-skill.ts +6 -4
  34. package/corpus/core/src/cli/skills-content/visual-recap-skill.ts +7 -1
  35. package/corpus/core/src/client/AgentPanel.tsx +19 -17
  36. package/corpus/core/src/client/analytics.ts +8 -1
  37. package/corpus/core/src/client/chat/markdown-renderer.tsx +54 -8
  38. package/corpus/core/src/client/chat/message-components.tsx +8 -2
  39. package/corpus/core/src/client/clipboard.ts +28 -1
  40. package/corpus/core/src/client/guided-questions.tsx +4 -0
  41. package/corpus/core/src/client/i18n.tsx +2 -1
  42. package/corpus/core/src/client/index.ts +4 -0
  43. package/corpus/core/src/client/review/ReviewCommentComposer.tsx +108 -0
  44. package/corpus/core/src/client/review/ReviewThreadPanel.tsx +482 -150
  45. package/corpus/core/src/client/review/index.ts +8 -0
  46. package/corpus/core/src/client/review/use-review.ts +35 -1
  47. package/corpus/core/src/client/use-agent-chat-context.ts +3 -2
  48. package/corpus/core/src/credentials/index.ts +41 -8
  49. package/corpus/core/src/extensions/fetch-tool.ts +33 -3
  50. package/corpus/core/src/notifications/channels.ts +20 -6
  51. package/corpus/core/src/review/actions/create-review-comment.ts +2 -1
  52. package/corpus/core/src/review/actions/get-review-feedback.ts +6 -11
  53. package/corpus/core/src/review/actions/list-review-comments.ts +38 -4
  54. package/corpus/core/src/review/actions/reply-review-comment.ts +33 -22
  55. package/corpus/core/src/review/actions/resolve-review-thread.ts +29 -2
  56. package/corpus/core/src/review/actions/send-review-thread-to-agent.ts +62 -0
  57. package/corpus/core/src/review/identity.ts +101 -0
  58. package/corpus/core/src/review/index.ts +14 -0
  59. package/corpus/core/src/review/store.ts +315 -8
  60. package/corpus/core/src/review/types.ts +4 -0
  61. package/corpus/core/src/secrets/crypto.ts +26 -10
  62. package/corpus/core/src/secrets/storage.ts +16 -3
  63. package/corpus/core/src/server/action-discovery.ts +4 -0
  64. package/corpus/core/src/server/action-routes.ts +125 -4
  65. package/corpus/core/src/server/agent-chat/plugin-options.ts +16 -0
  66. package/corpus/core/src/server/agent-chat-plugin.ts +61 -7
  67. package/corpus/core/src/server/derived-secret.ts +5 -1
  68. package/corpus/core/src/server/index.ts +8 -0
  69. package/corpus/core/src/vite/action-types-plugin.ts +4 -0
  70. package/corpus/templates/analytics/changelog/2026-07-14-daily-dashboard-email-captures-authenticate-with-a-sessi.md +6 -0
  71. package/corpus/templates/analytics/changelog/2026-07-14-long-running-cross-app-analyses-now-continue-reliably-in-the.md +6 -0
  72. package/corpus/templates/analytics/server/lib/dashboard-report.ts +156 -3
  73. package/corpus/templates/analytics/server/plugins/agent-chat.ts +8 -4
  74. package/corpus/templates/assets/app/components/generation/GenerationResults.tsx +402 -142
  75. package/corpus/templates/assets/app/i18n/zh-TW.ts +1 -0
  76. package/corpus/templates/assets/app/i18n-data.ts +1 -0
  77. package/corpus/templates/assets/app/routes/library.tsx +187 -48
  78. package/corpus/templates/assets/changelog/2026-07-13-merged-the-candidates-panel-into-the-library-as-a-drafts-tab.md +6 -0
  79. package/corpus/templates/chat/app/lib/agent-page.tsx +40 -0
  80. package/corpus/templates/chat/app/routes/agent.tsx +5 -3
  81. package/corpus/templates/chat/changelog/2026-07-14-fixed-chat-template-startup-with-older-core-versions.md +6 -0
  82. package/corpus/templates/clips/.agents/skills/ai-video-tools/SKILL.md +5 -0
  83. package/corpus/templates/clips/AGENTS.md +4 -0
  84. package/corpus/templates/clips/actions/get-feature-flags.ts +30 -0
  85. package/corpus/templates/clips/actions/lib/transcript-preview.ts +48 -0
  86. package/corpus/templates/clips/actions/request-transcript.ts +41 -1
  87. package/corpus/templates/clips/actions/view-screen.ts +5 -9
  88. package/corpus/templates/clips/app/components/library/library-layout.tsx +1 -1
  89. package/corpus/templates/clips/app/components/player/share-dialog.tsx +44 -81
  90. package/corpus/templates/clips/app/components/player/video-player.tsx +39 -1
  91. package/corpus/templates/clips/app/hooks/use-desktop-promo.ts +6 -12
  92. package/corpus/templates/clips/app/hooks/use-mse-video-source.ts +146 -0
  93. package/corpus/templates/clips/app/i18n/ar-SA.ts +0 -8
  94. package/corpus/templates/clips/app/i18n/de-DE.ts +0 -9
  95. package/corpus/templates/clips/app/i18n/en-US.ts +0 -8
  96. package/corpus/templates/clips/app/i18n/es-ES.ts +0 -9
  97. package/corpus/templates/clips/app/i18n/fr-FR.ts +0 -9
  98. package/corpus/templates/clips/app/i18n/hi-IN.ts +0 -8
  99. package/corpus/templates/clips/app/i18n/ja-JP.ts +0 -9
  100. package/corpus/templates/clips/app/i18n/ko-KR.ts +0 -9
  101. package/corpus/templates/clips/app/i18n/pt-BR.ts +0 -9
  102. package/corpus/templates/clips/app/i18n/zh-CN.ts +0 -7
  103. package/corpus/templates/clips/app/i18n/zh-TW.ts +0 -7
  104. package/corpus/templates/clips/app/lib/capture-install-options.ts +21 -0
  105. package/corpus/templates/clips/app/lib/fmp4.ts +271 -0
  106. package/corpus/templates/clips/app/lib/mse-video-loader.ts +585 -0
  107. package/corpus/templates/clips/app/routes/_app.dictate.tsx +0 -4
  108. package/corpus/templates/clips/app/routes/_app.meetings.$meetingId.tsx +5 -13
  109. package/corpus/templates/clips/app/routes/_app.meetings._index.tsx +2 -37
  110. package/corpus/templates/clips/app/routes/download.tsx +3 -2
  111. package/corpus/templates/clips/changelog/2026-07-10-shared-clips-now-start-playing-instantly-instead-of-showing-.md +6 -0
  112. package/corpus/templates/clips/changelog/2026-07-14-clips-now-distinguishes-transcript-previews-from-incomplete-.md +6 -0
  113. package/corpus/templates/clips/changelog/2026-07-14-desktop-app-prompts-stay-hidden-after-you-download-the-insta.md +6 -0
  114. package/corpus/templates/clips/changelog/2026-07-14-share-dialogs-show-separate-links-for-people-and-agents.md +6 -0
  115. package/corpus/templates/clips/desktop/src/lib/recorder.ts +5 -0
  116. package/corpus/templates/clips/desktop/src/overlays/recording-pill.tsx +10 -0
  117. package/corpus/templates/clips/desktop/src-tauri/src/lib.rs +4 -0
  118. package/corpus/templates/clips/desktop/src-tauri/src/native_screen/custom_capture.rs +2518 -0
  119. package/corpus/templates/clips/desktop/src-tauri/src/native_screen/live_upload.rs +353 -0
  120. package/corpus/templates/clips/desktop/src-tauri/src/native_screen.rs +558 -67
  121. package/corpus/templates/clips/desktop/src-tauri/src/recording_indicator.rs +11 -1
  122. package/corpus/templates/clips/desktop/src-tauri/src/remote_flags.rs +188 -0
  123. package/corpus/templates/clips/desktop/src-tauri/src/whisper_speech.rs +1 -1
  124. package/corpus/templates/clips/server/lib/post-finalize-dispatch.ts +1 -0
  125. package/corpus/templates/clips/server/plugins/agent-chat.ts +5 -0
  126. package/corpus/templates/clips/server/routes/api/_agent-native-background/post-finalize-worker.post.ts +5 -1
  127. package/corpus/templates/clips/shared/feature-flags.ts +41 -0
  128. package/corpus/templates/design/.agents/skills/design-generation/SKILL.md +3 -0
  129. package/corpus/templates/design/.agents/skills/design-review-feedback/SKILL.md +42 -0
  130. package/corpus/templates/design/AGENTS.md +6 -0
  131. package/corpus/templates/design/README.md +1 -0
  132. package/corpus/templates/design/actions/navigate.ts +4 -4
  133. package/corpus/templates/design/actions/view-screen.ts +116 -2
  134. package/corpus/templates/design/agent-native.app-skill.json +5 -0
  135. package/corpus/templates/design/app/components/design/DesignCanvas.tsx +52 -59
  136. package/corpus/templates/design/app/components/design/DesignExtensionsPanel.tsx +13 -9
  137. package/corpus/templates/design/app/components/design/EditPanel.tsx +79 -11
  138. package/corpus/templates/design/app/components/design/LayersPanel.tsx +78 -65
  139. package/corpus/templates/design/app/components/design/ReviewCommentsPanel.tsx +161 -0
  140. package/corpus/templates/design/app/components/design/ReviewStatusControl.tsx +96 -0
  141. package/corpus/templates/design/app/components/design/bridge/hit-test.bridge.ts +166 -0
  142. package/corpus/templates/design/app/components/layout/Layout.tsx +1 -0
  143. package/corpus/templates/design/app/components/visual-editor/ReviewCanvasPins.tsx +1077 -0
  144. package/corpus/templates/design/app/components/visual-editor/index.ts +1 -0
  145. package/corpus/templates/design/app/components/visual-editor/review-canvas-state.ts +42 -0
  146. package/corpus/templates/design/app/hooks/use-design-systems.ts +2 -2
  147. package/corpus/templates/design/app/hooks/use-navigation-state.ts +9 -6
  148. package/corpus/templates/design/app/hooks/use-question-flow.ts +7 -1
  149. package/corpus/templates/design/app/i18n/ar-SA.ts +56 -1
  150. package/corpus/templates/design/app/i18n/de-DE.ts +57 -1
  151. package/corpus/templates/design/app/i18n/en-US.ts +56 -1
  152. package/corpus/templates/design/app/i18n/es-ES.ts +56 -1
  153. package/corpus/templates/design/app/i18n/fr-FR.ts +57 -1
  154. package/corpus/templates/design/app/i18n/hi-IN.ts +56 -1
  155. package/corpus/templates/design/app/i18n/ja-JP.ts +56 -1
  156. package/corpus/templates/design/app/i18n/ko-KR.ts +56 -1
  157. package/corpus/templates/design/app/i18n/pt-BR.ts +57 -1
  158. package/corpus/templates/design/app/i18n/zh-CN.ts +56 -1
  159. package/corpus/templates/design/app/i18n/zh-TW.ts +51 -0
  160. package/corpus/templates/design/app/pages/Present.tsx +172 -14
  161. package/corpus/templates/design/app/pages/design-editor/overview-camera.ts +23 -0
  162. package/corpus/templates/design/app/pages/design-editor/tool-state.ts +10 -0
  163. package/corpus/templates/design/app/pages/present-review-state.ts +18 -0
  164. package/corpus/templates/design/app/public-routes.ts +8 -0
  165. package/corpus/templates/design/app/root.tsx +7 -6
  166. package/corpus/templates/design/changelog/2026-07-13-dragged-design-assets-now-appear-at-the-visible-drop-locatio.md +6 -0
  167. package/corpus/templates/design/changelog/2026-07-13-reviewers-can-pin-comments-on-shared-designs-and-apply-verif.md +6 -0
  168. package/corpus/templates/design/server/plugins/agent-chat.ts +10 -0
  169. package/corpus/templates/design/server/plugins/auth.ts +7 -5
  170. package/corpus/templates/design/server/plugins/review.ts +9 -0
  171. package/corpus/templates/design/shared/review-anchor.ts +83 -0
  172. package/corpus/templates/design/shared/review-summary.ts +25 -0
  173. package/corpus/templates/dispatch/changelog/2026-07-14-dispatch-now-distinguishes-connected-agents-from-locally-mou.md +6 -0
  174. package/corpus/templates/forms/.agents/skills/form-publishing/SKILL.md +2 -0
  175. package/corpus/templates/forms/AGENTS.md +4 -0
  176. package/corpus/templates/forms/actions/create-form.ts +2 -1
  177. package/corpus/templates/forms/actions/update-form.ts +5 -2
  178. package/corpus/templates/forms/app/i18n/ar-SA.ts +3 -0
  179. package/corpus/templates/forms/app/i18n/de-DE.ts +3 -0
  180. package/corpus/templates/forms/app/i18n/en-US.ts +3 -0
  181. package/corpus/templates/forms/app/i18n/es-ES.ts +3 -0
  182. package/corpus/templates/forms/app/i18n/fr-FR.ts +3 -0
  183. package/corpus/templates/forms/app/i18n/hi-IN.ts +3 -0
  184. package/corpus/templates/forms/app/i18n/ja-JP.ts +3 -0
  185. package/corpus/templates/forms/app/i18n/ko-KR.ts +3 -0
  186. package/corpus/templates/forms/app/i18n/pt-BR.ts +3 -0
  187. package/corpus/templates/forms/app/i18n/zh-CN.ts +3 -0
  188. package/corpus/templates/forms/app/i18n/zh-TW.ts +3 -0
  189. package/corpus/templates/forms/app/pages/FormBuilderPage.tsx +19 -0
  190. package/corpus/templates/forms/changelog/2026-07-14-fixed-the-ask-forms-loading-input-appearing-above-the-welcom.md +6 -0
  191. package/corpus/templates/forms/changelog/2026-07-14-form-social-previews-now-show-the-form-title-description-and.md +6 -0
  192. package/corpus/templates/forms/changelog/2026-07-14-forms-can-email-the-form-owner-when-new-responses-arrive.md +6 -0
  193. package/corpus/templates/forms/server/handlers/submissions.ts +17 -0
  194. package/corpus/templates/forms/server/lib/form-og-image.ts +64 -6
  195. package/corpus/templates/forms/server/lib/public-form-ssr.ts +6 -2
  196. package/corpus/templates/forms/server/lib/response-email.ts +65 -0
  197. package/corpus/templates/forms/server/plugins/agent-chat.ts +1 -0
  198. package/corpus/templates/forms/server/routes/api/forms/og/[...slug]/og.png.get.ts +184 -0
  199. package/corpus/templates/forms/shared/types.ts +2 -0
  200. package/corpus/templates/plan/.agents/skills/visual-plan/SKILL.md +6 -4
  201. package/corpus/templates/plan/.agents/skills/visual-recap/SKILL.md +7 -1
  202. package/dist/a2a/index.d.ts +2 -1
  203. package/dist/a2a/index.d.ts.map +1 -1
  204. package/dist/a2a/index.js +1 -1
  205. package/dist/a2a/index.js.map +1 -1
  206. package/dist/a2a/server.d.ts +25 -0
  207. package/dist/a2a/server.d.ts.map +1 -1
  208. package/dist/a2a/server.js +30 -6
  209. package/dist/a2a/server.js.map +1 -1
  210. package/dist/agent/production-agent.d.ts +2 -0
  211. package/dist/agent/production-agent.d.ts.map +1 -1
  212. package/dist/agent/production-agent.js +37 -12
  213. package/dist/agent/production-agent.js.map +1 -1
  214. package/dist/cli/create.d.ts +5 -3
  215. package/dist/cli/create.d.ts.map +1 -1
  216. package/dist/cli/create.js +44 -19
  217. package/dist/cli/create.js.map +1 -1
  218. package/dist/cli/pr-visual-recap-workflow.d.ts +2 -2
  219. package/dist/cli/pr-visual-recap-workflow.d.ts.map +1 -1
  220. package/dist/cli/pr-visual-recap-workflow.js +2 -2
  221. package/dist/cli/pr-visual-recap-workflow.js.map +1 -1
  222. package/dist/cli/recap.d.ts +3 -562
  223. package/dist/cli/recap.d.ts.map +1 -1
  224. package/dist/cli/recap.js +3 -3875
  225. package/dist/cli/recap.js.map +1 -1
  226. package/dist/cli/skills-content/visual-plan-skill.d.ts +1 -1
  227. package/dist/cli/skills-content/visual-plan-skill.d.ts.map +1 -1
  228. package/dist/cli/skills-content/visual-plan-skill.js +6 -4
  229. package/dist/cli/skills-content/visual-plan-skill.js.map +1 -1
  230. package/dist/cli/skills-content/visual-recap-skill.d.ts +1 -1
  231. package/dist/cli/skills-content/visual-recap-skill.d.ts.map +1 -1
  232. package/dist/cli/skills-content/visual-recap-skill.js +7 -1
  233. package/dist/cli/skills-content/visual-recap-skill.js.map +1 -1
  234. package/dist/client/AgentPanel.d.ts.map +1 -1
  235. package/dist/client/AgentPanel.js +3 -3
  236. package/dist/client/AgentPanel.js.map +1 -1
  237. package/dist/client/analytics.d.ts +5 -0
  238. package/dist/client/analytics.d.ts.map +1 -1
  239. package/dist/client/analytics.js +3 -1
  240. package/dist/client/analytics.js.map +1 -1
  241. package/dist/client/chat/markdown-renderer.d.ts +1 -0
  242. package/dist/client/chat/markdown-renderer.d.ts.map +1 -1
  243. package/dist/client/chat/markdown-renderer.js +36 -1
  244. package/dist/client/chat/markdown-renderer.js.map +1 -1
  245. package/dist/client/chat/message-components.d.ts.map +1 -1
  246. package/dist/client/chat/message-components.js +5 -2
  247. package/dist/client/chat/message-components.js.map +1 -1
  248. package/dist/client/clipboard.d.ts +3 -1
  249. package/dist/client/clipboard.d.ts.map +1 -1
  250. package/dist/client/clipboard.js +24 -1
  251. package/dist/client/clipboard.js.map +1 -1
  252. package/dist/client/guided-questions.d.ts +3 -1
  253. package/dist/client/guided-questions.d.ts.map +1 -1
  254. package/dist/client/guided-questions.js +2 -1
  255. package/dist/client/guided-questions.js.map +1 -1
  256. package/dist/client/i18n.d.ts.map +1 -1
  257. package/dist/client/i18n.js +3 -1
  258. package/dist/client/i18n.js.map +1 -1
  259. package/dist/client/index.d.ts +1 -1
  260. package/dist/client/index.d.ts.map +1 -1
  261. package/dist/client/index.js +1 -1
  262. package/dist/client/index.js.map +1 -1
  263. package/dist/client/review/ReviewCommentComposer.d.ts +18 -0
  264. package/dist/client/review/ReviewCommentComposer.d.ts.map +1 -0
  265. package/dist/client/review/ReviewCommentComposer.js +30 -0
  266. package/dist/client/review/ReviewCommentComposer.js.map +1 -0
  267. package/dist/client/review/ReviewThreadPanel.d.ts +28 -1
  268. package/dist/client/review/ReviewThreadPanel.d.ts.map +1 -1
  269. package/dist/client/review/ReviewThreadPanel.js +151 -39
  270. package/dist/client/review/ReviewThreadPanel.js.map +1 -1
  271. package/dist/client/review/index.d.ts +3 -2
  272. package/dist/client/review/index.d.ts.map +1 -1
  273. package/dist/client/review/index.js +2 -1
  274. package/dist/client/review/index.js.map +1 -1
  275. package/dist/client/review/use-review.d.ts +23 -0
  276. package/dist/client/review/use-review.d.ts.map +1 -1
  277. package/dist/client/review/use-review.js +3 -0
  278. package/dist/client/review/use-review.js.map +1 -1
  279. package/dist/client/use-agent-chat-context.d.ts +1 -1
  280. package/dist/client/use-agent-chat-context.d.ts.map +1 -1
  281. package/dist/client/use-agent-chat-context.js +4 -2
  282. package/dist/client/use-agent-chat-context.js.map +1 -1
  283. package/dist/collab/awareness.d.ts +2 -2
  284. package/dist/collab/awareness.d.ts.map +1 -1
  285. package/dist/collab/struct-routes.d.ts +1 -1
  286. package/dist/credentials/index.d.ts +12 -7
  287. package/dist/credentials/index.d.ts.map +1 -1
  288. package/dist/credentials/index.js +33 -8
  289. package/dist/credentials/index.js.map +1 -1
  290. package/dist/extensions/fetch-tool.d.ts +13 -1
  291. package/dist/extensions/fetch-tool.d.ts.map +1 -1
  292. package/dist/extensions/fetch-tool.js +9 -2
  293. package/dist/extensions/fetch-tool.js.map +1 -1
  294. package/dist/notifications/channels.js +20 -4
  295. package/dist/notifications/channels.js.map +1 -1
  296. package/dist/observability/routes.d.ts +5 -5
  297. package/dist/progress/routes.d.ts +1 -1
  298. package/dist/provider-api/corpus-jobs.d.ts +2 -2
  299. package/dist/resources/handlers.d.ts +3 -3
  300. package/dist/review/actions/create-review-comment.js +2 -1
  301. package/dist/review/actions/create-review-comment.js.map +1 -1
  302. package/dist/review/actions/get-review-feedback.js +6 -11
  303. package/dist/review/actions/get-review-feedback.js.map +1 -1
  304. package/dist/review/actions/list-review-comments.d.ts +1 -0
  305. package/dist/review/actions/list-review-comments.js +25 -4
  306. package/dist/review/actions/list-review-comments.js.map +1 -1
  307. package/dist/review/actions/reply-review-comment.js +12 -5
  308. package/dist/review/actions/reply-review-comment.js.map +1 -1
  309. package/dist/review/actions/resolve-review-thread.d.ts +3 -0
  310. package/dist/review/actions/resolve-review-thread.js +20 -3
  311. package/dist/review/actions/resolve-review-thread.js.map +1 -1
  312. package/dist/review/actions/send-review-thread-to-agent.d.ts +17 -0
  313. package/dist/review/actions/send-review-thread-to-agent.d.ts.map +1 -0
  314. package/dist/review/actions/send-review-thread-to-agent.js +49 -0
  315. package/dist/review/actions/send-review-thread-to-agent.js.map +1 -0
  316. package/dist/review/identity.d.ts +6 -0
  317. package/dist/review/identity.d.ts.map +1 -0
  318. package/dist/review/identity.js +68 -0
  319. package/dist/review/identity.js.map +1 -0
  320. package/dist/review/index.d.ts +3 -1
  321. package/dist/review/index.d.ts.map +1 -1
  322. package/dist/review/index.js +2 -1
  323. package/dist/review/index.js.map +1 -1
  324. package/dist/review/store.d.ts +36 -0
  325. package/dist/review/store.d.ts.map +1 -1
  326. package/dist/review/store.js +223 -8
  327. package/dist/review/store.js.map +1 -1
  328. package/dist/review/types.d.ts +4 -0
  329. package/dist/review/types.d.ts.map +1 -1
  330. package/dist/review/types.js.map +1 -1
  331. package/dist/secrets/crypto.d.ts +15 -7
  332. package/dist/secrets/crypto.d.ts.map +1 -1
  333. package/dist/secrets/crypto.js +23 -10
  334. package/dist/secrets/crypto.js.map +1 -1
  335. package/dist/secrets/routes.d.ts +9 -9
  336. package/dist/secrets/storage.d.ts.map +1 -1
  337. package/dist/secrets/storage.js +16 -3
  338. package/dist/secrets/storage.js.map +1 -1
  339. package/dist/server/action-discovery.d.ts.map +1 -1
  340. package/dist/server/action-discovery.js +4 -0
  341. package/dist/server/action-discovery.js.map +1 -1
  342. package/dist/server/action-routes.d.ts +45 -0
  343. package/dist/server/action-routes.d.ts.map +1 -1
  344. package/dist/server/action-routes.js +71 -5
  345. package/dist/server/action-routes.js.map +1 -1
  346. package/dist/server/agent-chat/plugin-options.d.ts +16 -0
  347. package/dist/server/agent-chat/plugin-options.d.ts.map +1 -1
  348. package/dist/server/agent-chat/plugin-options.js.map +1 -1
  349. package/dist/server/agent-chat-plugin.d.ts +20 -0
  350. package/dist/server/agent-chat-plugin.d.ts.map +1 -1
  351. package/dist/server/agent-chat-plugin.js +39 -4
  352. package/dist/server/agent-chat-plugin.js.map +1 -1
  353. package/dist/server/derived-secret.d.ts +1 -1
  354. package/dist/server/derived-secret.d.ts.map +1 -1
  355. package/dist/server/derived-secret.js.map +1 -1
  356. package/dist/server/index.d.ts +3 -1
  357. package/dist/server/index.d.ts.map +1 -1
  358. package/dist/server/index.js +2 -0
  359. package/dist/server/index.js.map +1 -1
  360. package/dist/vite/action-types-plugin.d.ts.map +1 -1
  361. package/dist/vite/action-types-plugin.js +4 -0
  362. package/dist/vite/action-types-plugin.js.map +1 -1
  363. package/docs/content/locales/ar-SA/pr-visual-recap.mdx +48 -30
  364. package/docs/content/locales/ar-SA/template-plan.mdx +7 -0
  365. package/docs/content/locales/de-DE/pr-visual-recap.mdx +48 -30
  366. package/docs/content/locales/de-DE/template-plan.mdx +7 -0
  367. package/docs/content/locales/es-ES/pr-visual-recap.mdx +48 -30
  368. package/docs/content/locales/es-ES/template-plan.mdx +14 -0
  369. package/docs/content/locales/fr-FR/pr-visual-recap.mdx +48 -30
  370. package/docs/content/locales/fr-FR/template-plan.mdx +7 -0
  371. package/docs/content/locales/hi-IN/pr-visual-recap.mdx +48 -30
  372. package/docs/content/locales/hi-IN/template-plan.mdx +7 -0
  373. package/docs/content/locales/ja-JP/pr-visual-recap.mdx +48 -30
  374. package/docs/content/locales/ja-JP/template-plan.mdx +7 -0
  375. package/docs/content/locales/ko-KR/pr-visual-recap.mdx +48 -30
  376. package/docs/content/locales/ko-KR/template-plan.mdx +7 -0
  377. package/docs/content/locales/pt-BR/pr-visual-recap.mdx +48 -30
  378. package/docs/content/locales/pt-BR/template-plan.mdx +14 -0
  379. package/docs/content/locales/zh-CN/pr-visual-recap.mdx +48 -30
  380. package/docs/content/locales/zh-CN/template-plan.mdx +7 -0
  381. package/docs/content/locales/zh-TW/pr-visual-recap.mdx +48 -30
  382. package/docs/content/locales/zh-TW/template-plan.mdx +7 -0
  383. package/docs/content/pr-visual-recap.mdx +50 -32
  384. package/docs/content/template-plan.mdx +14 -0
  385. package/docs/content/toolkit-comments-review.mdx +16 -2
  386. package/package.json +4 -1
  387. package/corpus/templates/clips/app/components/sharing/slack-share-hint.tsx +0 -68
  388. package/corpus/templates/forms/server/routes/api/forms/og/[slug]/og.png.get.ts +0 -45
@@ -0,0 +1,585 @@
1
+ /**
2
+ * Media Source Extensions loader for raw fragmented-MP4 recordings.
3
+ *
4
+ * Why this exists: the desktop custom recording pipeline live-streams captures
5
+ * as fragmented MP4 with no up-front duration (`mvhd duration=0`, no `mehd`).
6
+ * Chrome's progressive `<video src>` pipeline therefore scans the entire file
7
+ * over the network before it can fire `loadedmetadata`, so CDN-served clips
8
+ * spin forever. The bytes are valid and cannot be rewritten at rest (they were
9
+ * committed to an append-only resumable upload), so instead we feed them to a
10
+ * `MediaSource` ourselves and set the duration from the DB.
11
+ *
12
+ * The loader owns a `MediaSource` + one `SourceBuffer`, streams the asset with
13
+ * sequential HTTP range requests, keeps a buffer-ahead window relative to
14
+ * `currentTime`, realigns to fragment boundaries on seek, and evicts played
15
+ * ranges under memory pressure. Any unrecoverable failure calls `onFatal` so
16
+ * the caller can drop back to the plain `<video src>` path.
17
+ *
18
+ * The player component only ever sees a normal `HTMLVideoElement`; this class
19
+ * drives it entirely through `video.src = objectUrl` + range fetches.
20
+ */
21
+
22
+ import { findMoofOffset, parseInitSegment } from "./fmp4";
23
+
24
+ const CHUNK_SIZE = 2 * 1024 * 1024; // 2MB sequential range reads
25
+ const INIT_PROBE_SIZE = 512 * 1024; // enough to always contain ftyp+moov
26
+ const SEEK_PROBE_SIZE = 4 * 1024 * 1024; // window scanned for a moof after seek
27
+ const BUFFER_AHEAD_SECONDS = 30; // download target ahead of currentTime
28
+ const BUFFER_BEHIND_SECONDS = 10; // played media kept before evicting on quota
29
+ const SEEK_BACKOFF_BYTES = 512 * 1024; // land a little early so we don't overshoot
30
+
31
+ export interface MseVideoLoaderOptions {
32
+ /** Asset URL. Range requests go straight here (external/proxied media). */
33
+ url: string;
34
+ /** Authoritative duration from the DB, in milliseconds. */
35
+ durationMs: number;
36
+ /** The video element this loader drives. */
37
+ video: HTMLVideoElement;
38
+ /** Called once on any unrecoverable failure so the caller can fall back. */
39
+ onFatal: (err: unknown) => void;
40
+ }
41
+
42
+ export function isMediaSourceSupported(): boolean {
43
+ return (
44
+ typeof window !== "undefined" &&
45
+ typeof window.MediaSource !== "undefined" &&
46
+ typeof window.MediaSource.isTypeSupported === "function"
47
+ );
48
+ }
49
+
50
+ function parseTotalFromContentRange(header: string | null): number | null {
51
+ if (!header) return null;
52
+ const match = header.match(/\/(\d+)\s*$/);
53
+ if (!match) return null;
54
+ const total = Number.parseInt(match[1], 10);
55
+ return Number.isFinite(total) ? total : null;
56
+ }
57
+
58
+ function isAbortError(err: unknown): boolean {
59
+ return err instanceof DOMException && err.name === "AbortError";
60
+ }
61
+
62
+ export class MseVideoLoader {
63
+ readonly objectUrl: string;
64
+
65
+ private readonly opts: MseVideoLoaderOptions;
66
+ private readonly video: HTMLVideoElement;
67
+ private readonly mediaSource: MediaSource;
68
+ private sourceBuffer: SourceBuffer | null = null;
69
+
70
+ private totalBytes = 0;
71
+ /**
72
+ * Whether we know the asset's real length. Cross-origin responses hide the
73
+ * `Content-Range` header (it is not CORS-safelisted), so when playing a raw
74
+ * CDN asset we cannot read the total and instead detect the end via short
75
+ * reads. Same-origin proxied media exposes it and we use it directly.
76
+ */
77
+ private totalKnown = false;
78
+ private eofReached = false;
79
+ private initLength = 0;
80
+ private nextOffset = 0;
81
+ private initAppended = false;
82
+ private needsRealign = false;
83
+ /** Duration (seconds) waiting to be written once the source buffer is idle. */
84
+ private pendingDurationSec: number | null = null;
85
+ /**
86
+ * Byte offset the current realign probe started from (the seek estimate,
87
+ * then walked backward by `backUpRealignProbe`). Used to recover when a seek
88
+ * estimate overshoots past the target fragment's `moof` into its `mdat`.
89
+ */
90
+ private realignProbeStart = 0;
91
+ /** Media bytes appended so far (excludes the init segment). */
92
+ private mediaBytesAppended = 0;
93
+
94
+ private destroyed = false;
95
+ private pumping = false;
96
+ private restart = false;
97
+ private currentFetch: AbortController | null = null;
98
+
99
+ constructor(opts: MseVideoLoaderOptions) {
100
+ this.opts = opts;
101
+ this.video = opts.video;
102
+ this.mediaSource = new MediaSource();
103
+ this.objectUrl = URL.createObjectURL(this.mediaSource);
104
+ // Not `once`: after `endOfStream()` a seek into an evicted/unbuffered range
105
+ // transitions the source back to "open" and fires `sourceopen` again, which
106
+ // we use to resume fetching (see `onSourceOpen`).
107
+ this.mediaSource.addEventListener("sourceopen", this.onSourceOpen);
108
+ this.video.addEventListener("seeking", this.onSeeking);
109
+ this.video.addEventListener("timeupdate", this.onTimeUpdate);
110
+ }
111
+
112
+ destroy(): void {
113
+ if (this.destroyed) return;
114
+ this.destroyed = true;
115
+ this.currentFetch?.abort();
116
+ this.video.removeEventListener("seeking", this.onSeeking);
117
+ this.video.removeEventListener("timeupdate", this.onTimeUpdate);
118
+ this.mediaSource.removeEventListener("sourceopen", this.onSourceOpen);
119
+ this.sourceBuffer?.removeEventListener(
120
+ "updateend",
121
+ this.onSourceBufferIdle,
122
+ );
123
+ try {
124
+ if (this.sourceBuffer && this.mediaSource.readyState === "open") {
125
+ this.mediaSource.removeSourceBuffer(this.sourceBuffer);
126
+ }
127
+ } catch {
128
+ // Removing a buffer mid-update throws; the object URL revoke below is
129
+ // what actually tears the pipeline down.
130
+ }
131
+ try {
132
+ URL.revokeObjectURL(this.objectUrl);
133
+ } catch {
134
+ // ignore
135
+ }
136
+ }
137
+
138
+ /**
139
+ * Update the authoritative duration after construction. Recording metadata
140
+ * polling can deliver a later/larger value while the same asset is playing;
141
+ * apply it to the live `MediaSource` (and the seek-estimation math) instead
142
+ * of forcing a loader rebuild, which would revoke the object URL and restart
143
+ * playback from byte zero.
144
+ */
145
+ setDuration(durationMs: number): void {
146
+ if (this.destroyed) return;
147
+ if (!Number.isFinite(durationMs) || durationMs <= 0) return;
148
+ if (durationMs === this.opts.durationMs) return;
149
+ this.opts.durationMs = durationMs;
150
+ // Queue the timeline update and apply it as soon as the source buffer is
151
+ // idle. Writing `mediaSource.duration` throws while an append is in flight,
152
+ // so a value that lands mid-append must be retried on `updateend` — never
153
+ // dropped, or the timeline stays permanently shorter than the recording.
154
+ this.pendingDurationSec = durationMs / 1000;
155
+ this.flushPendingDuration();
156
+ }
157
+
158
+ /** Apply a queued duration once the source is open and no append is running. */
159
+ private flushPendingDuration(): void {
160
+ if (this.destroyed || this.pendingDurationSec === null) return;
161
+ if (this.mediaSource.readyState !== "open") return;
162
+ if (this.sourceBuffer?.updating) return;
163
+ try {
164
+ this.mediaSource.duration = this.pendingDurationSec;
165
+ this.pendingDurationSec = null;
166
+ } catch {
167
+ // e.g. the new duration is below the highest buffered PTS; keep it
168
+ // queued and retry on the next `updateend`.
169
+ }
170
+ }
171
+
172
+ private onSourceBufferIdle = (): void => {
173
+ if (this.destroyed) return;
174
+ this.flushPendingDuration();
175
+ };
176
+
177
+ private fail(err: unknown): void {
178
+ if (this.destroyed) return;
179
+ this.opts.onFatal(err);
180
+ }
181
+
182
+ private onSourceOpen = async (): Promise<void> => {
183
+ if (this.destroyed) return;
184
+ // Reopened after `endOfStream()`: a seek into an evicted/unbuffered range
185
+ // transitions the "ended" source back to "open". The pipeline is already
186
+ // built, so just clear the EOF latch and resume fetching for the seek
187
+ // (`onSeeking` has set `needsRealign`/`nextOffset`) instead of re-running
188
+ // init (which would try to add a second source buffer).
189
+ if (this.initAppended) {
190
+ this.eofReached = false;
191
+ this.schedulePump();
192
+ return;
193
+ }
194
+ try {
195
+ const durationSec = this.opts.durationMs / 1000;
196
+ if (Number.isFinite(durationSec) && durationSec > 0) {
197
+ // The whole point: the timeline length comes from us, never from
198
+ // scanning the file.
199
+ this.mediaSource.duration = durationSec;
200
+ }
201
+
202
+ const first = await this.fetchRange(0, INIT_PROBE_SIZE - 1);
203
+ if (this.destroyed) return;
204
+
205
+ const parsed = parseInitSegment(first.bytes);
206
+ if (!parsed) throw new Error("Could not parse fMP4 init segment");
207
+ this.initLength = parsed.initLength;
208
+
209
+ const mime = `video/mp4; codecs="${parsed.codecs}"`;
210
+ if (!window.MediaSource.isTypeSupported(mime)) {
211
+ throw new Error(`Unsupported MSE type: ${mime}`);
212
+ }
213
+
214
+ const sb = this.mediaSource.addSourceBuffer(mime);
215
+ // Flush any duration update that arrived (or was deferred) while an
216
+ // append was running.
217
+ sb.addEventListener("updateend", this.onSourceBufferIdle);
218
+ // "segments" mode honors each fragment's baseMediaDecodeTime, which is
219
+ // what lets us append a later fragment after a seek without any manual
220
+ // timestampOffset bookkeeping.
221
+ sb.mode = "segments";
222
+ this.sourceBuffer = sb;
223
+
224
+ await this.appendBuffer(first.bytes.subarray(0, this.initLength));
225
+ this.initAppended = true;
226
+
227
+ // The 512KB probe usually also contains the first media fragments — append
228
+ // whatever came after the init segment so playback can start immediately.
229
+ const fetchedEnd = first.bytes.byteLength;
230
+ if (fetchedEnd > this.initLength) {
231
+ const media = first.bytes.subarray(this.initLength);
232
+ await this.appendWithQuota(media);
233
+ this.mediaBytesAppended += media.byteLength;
234
+ }
235
+ this.nextOffset = fetchedEnd;
236
+ if (first.eof) this.eofReached = true;
237
+
238
+ this.schedulePump();
239
+ } catch (err) {
240
+ this.fail(err);
241
+ }
242
+ };
243
+
244
+ private onTimeUpdate = (): void => {
245
+ if (this.destroyed) return;
246
+ // Re-pump when the buffer-ahead window has drained below target.
247
+ this.schedulePump();
248
+ };
249
+
250
+ private onSeeking = (): void => {
251
+ if (this.destroyed || !this.initAppended) return;
252
+ const target = this.video.currentTime;
253
+ if (this.isBuffered(target)) return; // already have this position
254
+
255
+ // Abort any in-flight sequential fetch so we can jump.
256
+ this.currentFetch?.abort();
257
+ this.eofReached = false;
258
+
259
+ this.nextOffset = Math.max(
260
+ this.initLength,
261
+ this.estimateByteOffset(target),
262
+ );
263
+ this.realignProbeStart = this.nextOffset;
264
+ this.needsRealign = true;
265
+ this.schedulePump();
266
+ };
267
+
268
+ /**
269
+ * Recover a seek whose estimate overshot past the target fragment's `moof`
270
+ * into its `mdat`. That can only happen inside the FINAL fragment (every
271
+ * earlier one is followed by another `moof`), so a forward scan reaches EOF
272
+ * with no boundary. Back the probe window up by one `SEEK_PROBE_SIZE`
273
+ * (floored at the init segment) and re-scan, instead of treating EOF as a
274
+ * completed seek and stalling. Returns false when there is nothing earlier
275
+ * left to probe.
276
+ */
277
+ private backUpRealignProbe(): boolean {
278
+ if (this.realignProbeStart <= this.initLength) return false;
279
+ this.realignProbeStart = Math.max(
280
+ this.initLength,
281
+ this.realignProbeStart - SEEK_PROBE_SIZE,
282
+ );
283
+ this.nextOffset = this.realignProbeStart;
284
+ this.eofReached = false;
285
+ return true;
286
+ }
287
+
288
+ private schedulePump(): void {
289
+ if (this.pumping) {
290
+ this.restart = true;
291
+ return;
292
+ }
293
+ void this.runPump();
294
+ }
295
+
296
+ private async runPump(): Promise<void> {
297
+ if (this.pumping) return;
298
+ this.pumping = true;
299
+ try {
300
+ while (
301
+ !this.destroyed &&
302
+ this.sourceBuffer &&
303
+ this.mediaSource.readyState === "open"
304
+ ) {
305
+ if (
306
+ this.eofReached ||
307
+ (this.totalKnown && this.nextOffset >= this.totalBytes)
308
+ ) {
309
+ // A realign that reached EOF without finding its boundary overshot
310
+ // into the final fragment — back up and keep looking before ending.
311
+ if (this.needsRealign && this.backUpRealignProbe()) {
312
+ continue;
313
+ }
314
+ this.needsRealign = false;
315
+ this.tryEndOfStream();
316
+ break;
317
+ }
318
+ // Stop downloading once we're comfortably ahead — unless we still need
319
+ // to realign to a fragment boundary after a seek.
320
+ if (
321
+ !this.needsRealign &&
322
+ this.bufferedAhead() >= BUFFER_AHEAD_SECONDS
323
+ ) {
324
+ break;
325
+ }
326
+
327
+ const chunkStart = this.nextOffset;
328
+ const windowSize = this.needsRealign ? SEEK_PROBE_SIZE : CHUNK_SIZE;
329
+ const chunkEnd = this.totalKnown
330
+ ? Math.min(chunkStart + windowSize, this.totalBytes) - 1
331
+ : chunkStart + windowSize - 1;
332
+
333
+ let res: { bytes: Uint8Array; eof: boolean };
334
+ try {
335
+ res = await this.fetchRange(chunkStart, chunkEnd);
336
+ } catch (err) {
337
+ if (isAbortError(err)) break; // superseded by a seek
338
+ throw err;
339
+ }
340
+ if (this.destroyed) break;
341
+ if (res.bytes.byteLength === 0) {
342
+ if (this.needsRealign && this.backUpRealignProbe()) {
343
+ continue;
344
+ }
345
+ this.eofReached = true;
346
+ this.tryEndOfStream();
347
+ break;
348
+ }
349
+
350
+ let bytes = res.bytes;
351
+ if (this.needsRealign) {
352
+ const moof = findMoofOffset(bytes);
353
+ if (moof < 0) {
354
+ if (res.eof) {
355
+ // Reached EOF with no boundary — the estimate overshot into the
356
+ // final fragment's mdat. Back up and re-probe rather than
357
+ // stalling the seek; give up only once nothing earlier remains.
358
+ if (this.backUpRealignProbe()) {
359
+ continue;
360
+ }
361
+ this.needsRealign = false;
362
+ this.eofReached = true;
363
+ this.tryEndOfStream();
364
+ break;
365
+ }
366
+ // No fragment boundary in this window — advance and keep scanning.
367
+ this.nextOffset = chunkStart + bytes.byteLength;
368
+ continue;
369
+ }
370
+ bytes = bytes.subarray(moof);
371
+ this.nextOffset = chunkStart + moof;
372
+ this.needsRealign = false;
373
+ // Reset the segment parser so it drops any partial fragment left over
374
+ // from the aborted sequential append and treats these bytes as a
375
+ // fresh media segment. Without this, appending a fragment from a new
376
+ // byte position fails with CHUNK_DEMUXER_ERROR_APPEND_FAILED.
377
+ this.abortParser();
378
+ }
379
+
380
+ await this.appendWithQuota(bytes);
381
+ this.mediaBytesAppended += bytes.byteLength;
382
+ this.nextOffset = chunkStart + res.bytes.byteLength;
383
+ if (res.eof) this.eofReached = true;
384
+ }
385
+ } catch (err) {
386
+ this.fail(err);
387
+ } finally {
388
+ this.pumping = false;
389
+ if (this.restart && !this.destroyed) {
390
+ this.restart = false;
391
+ this.schedulePump();
392
+ }
393
+ }
394
+ }
395
+
396
+ private abortParser(): void {
397
+ const sb = this.sourceBuffer;
398
+ if (!sb || this.mediaSource.readyState !== "open" || sb.updating) return;
399
+ try {
400
+ sb.abort();
401
+ } catch {
402
+ // abort() is best-effort; a failure here just means the next append may
403
+ // still be treated as a continuation.
404
+ }
405
+ }
406
+
407
+ private tryEndOfStream(): void {
408
+ if (this.mediaSource.readyState !== "open") return;
409
+ if (this.sourceBuffer?.updating) return;
410
+ try {
411
+ this.mediaSource.endOfStream();
412
+ } catch {
413
+ // Duration is already correct from the DB; endOfStream is best-effort.
414
+ }
415
+ }
416
+
417
+ private bufferedAhead(): number {
418
+ const buffered = this.sourceBuffer?.buffered;
419
+ if (!buffered || buffered.length === 0) return 0;
420
+ const t = this.video.currentTime;
421
+ for (let i = 0; i < buffered.length; i++) {
422
+ const start = buffered.start(i);
423
+ const end = buffered.end(i);
424
+ if (t >= start - 0.25 && t <= end) return end - t;
425
+ }
426
+ return 0;
427
+ }
428
+
429
+ /** Highest buffered presentation time — how far into the media we've decoded. */
430
+ private bufferedSeconds(): number {
431
+ const buffered = this.sourceBuffer?.buffered;
432
+ if (!buffered || buffered.length === 0) return 0;
433
+ return buffered.end(buffered.length - 1);
434
+ }
435
+
436
+ /**
437
+ * Estimate the byte offset for a seek target. Uses the known total when we
438
+ * have it, otherwise the observed average bitrate (bytes of media appended per
439
+ * second buffered). Lands `SEEK_BACKOFF_BYTES` early so we don't overshoot the
440
+ * target fragment; the caller realigns forward to the next `moof`.
441
+ */
442
+ private estimateByteOffset(targetSec: number): number {
443
+ const durationSec = this.opts.durationMs / 1000;
444
+ if (this.totalKnown && durationSec > 0) {
445
+ const frac = Math.min(Math.max(targetSec / durationSec, 0), 1);
446
+ return Math.floor(this.totalBytes * frac) - SEEK_BACKOFF_BYTES;
447
+ }
448
+ const bufferedSec = this.bufferedSeconds();
449
+ if (bufferedSec > 0 && this.mediaBytesAppended > 0) {
450
+ const bytesPerSec = this.mediaBytesAppended / bufferedSec;
451
+ return (
452
+ Math.floor(this.initLength + targetSec * bytesPerSec) -
453
+ SEEK_BACKOFF_BYTES
454
+ );
455
+ }
456
+ return this.initLength;
457
+ }
458
+
459
+ private isBuffered(time: number): boolean {
460
+ const buffered = this.sourceBuffer?.buffered;
461
+ if (!buffered) return false;
462
+ for (let i = 0; i < buffered.length; i++) {
463
+ if (time >= buffered.start(i) - 0.25 && time <= buffered.end(i)) {
464
+ return true;
465
+ }
466
+ }
467
+ return false;
468
+ }
469
+
470
+ private appendBuffer(bytes: Uint8Array): Promise<void> {
471
+ return new Promise((resolve, reject) => {
472
+ const sb = this.sourceBuffer;
473
+ if (!sb) {
474
+ reject(new Error("No source buffer"));
475
+ return;
476
+ }
477
+ const onEnd = () => {
478
+ cleanup();
479
+ resolve();
480
+ };
481
+ const onErr = () => {
482
+ cleanup();
483
+ reject(new Error("SourceBuffer append error"));
484
+ };
485
+ const cleanup = () => {
486
+ sb.removeEventListener("updateend", onEnd);
487
+ sb.removeEventListener("error", onErr);
488
+ };
489
+ sb.addEventListener("updateend", onEnd);
490
+ sb.addEventListener("error", onErr);
491
+ try {
492
+ sb.appendBuffer(bytes as BufferSource);
493
+ } catch (err) {
494
+ cleanup();
495
+ reject(err);
496
+ }
497
+ });
498
+ }
499
+
500
+ private async appendWithQuota(bytes: Uint8Array): Promise<void> {
501
+ for (let attempt = 0; attempt < 6; attempt++) {
502
+ try {
503
+ await this.appendBuffer(bytes);
504
+ return;
505
+ } catch (err) {
506
+ if (err instanceof DOMException && err.name === "QuotaExceededError") {
507
+ const freed = await this.evictBehind();
508
+ if (!freed) throw err;
509
+ continue;
510
+ }
511
+ throw err;
512
+ }
513
+ }
514
+ throw new Error("SourceBuffer quota could not be reclaimed");
515
+ }
516
+
517
+ private async evictBehind(): Promise<boolean> {
518
+ const sb = this.sourceBuffer;
519
+ if (!sb || sb.buffered.length === 0) return false;
520
+ const removeEnd = this.video.currentTime - BUFFER_BEHIND_SECONDS;
521
+ const start = sb.buffered.start(0);
522
+ if (removeEnd <= start) return false;
523
+ await this.remove(start, removeEnd);
524
+ return true;
525
+ }
526
+
527
+ private remove(start: number, end: number): Promise<void> {
528
+ return new Promise((resolve) => {
529
+ const sb = this.sourceBuffer;
530
+ if (!sb) {
531
+ resolve();
532
+ return;
533
+ }
534
+ const onEnd = () => {
535
+ sb.removeEventListener("updateend", onEnd);
536
+ resolve();
537
+ };
538
+ sb.addEventListener("updateend", onEnd);
539
+ try {
540
+ sb.remove(start, end);
541
+ } catch {
542
+ sb.removeEventListener("updateend", onEnd);
543
+ resolve();
544
+ }
545
+ });
546
+ }
547
+
548
+ private async fetchRange(
549
+ start: number,
550
+ end: number,
551
+ ): Promise<{ bytes: Uint8Array; eof: boolean }> {
552
+ const controller = new AbortController();
553
+ this.currentFetch = controller;
554
+ const res = await fetch(this.opts.url, {
555
+ headers: { Range: `bytes=${start}-${end}` },
556
+ signal: controller.signal,
557
+ });
558
+ // 416 means we asked past the end — treat as a clean end-of-stream.
559
+ if (res.status === 416) return { bytes: new Uint8Array(0), eof: true };
560
+ if (!res.ok) {
561
+ throw new Error(`Range request failed: ${res.status}`);
562
+ }
563
+ const buffer = await res.arrayBuffer();
564
+ const bytes = new Uint8Array(buffer);
565
+
566
+ // Learn the real total when the header is readable (same-origin proxied
567
+ // media). Cross-origin CDN responses hide Content-Range, so we fall back to
568
+ // short-read detection below.
569
+ const total = parseTotalFromContentRange(res.headers.get("content-range"));
570
+ if (total != null && total > 0) {
571
+ this.totalBytes = total;
572
+ this.totalKnown = true;
573
+ }
574
+
575
+ const requested = end - start + 1;
576
+ // A 200 (server ignored Range) or a short 206 both mean this response ran to
577
+ // the end of the asset.
578
+ const eof =
579
+ res.status === 200 ||
580
+ bytes.byteLength < requested ||
581
+ (this.totalKnown && start + bytes.byteLength >= this.totalBytes);
582
+
583
+ return { bytes, eof };
584
+ }
585
+ }
@@ -679,10 +679,6 @@ function DownloadDesktopAppCard() {
679
679
  })}
680
680
  </p>
681
681
  </div>
682
- <CaptureInstallButton size="sm" className="gap-1.5">
683
- <IconDownload className="h-3.5 w-3.5" />
684
- {t("dictateRoute.downloadDesktopApp")}
685
- </CaptureInstallButton>
686
682
  </div>
687
683
  );
688
684
  }
@@ -797,19 +797,11 @@ export default function MeetingDetailRoute() {
797
797
  </PageHeader>
798
798
 
799
799
  {showDesktopRecordHint && (
800
- <div className="mb-4 flex flex-wrap items-center gap-3 rounded-md border border-border bg-accent/20 px-3 py-2.5">
801
- <IconDeviceDesktop className="h-4 w-4 shrink-0 text-muted-foreground" />
802
- <span className="text-sm">{t("meetingDetail.desktopHint")}</span>
803
- {!isDesktopApp && (
804
- <CaptureInstallButton
805
- size="sm"
806
- variant="secondary"
807
- className="ml-auto h-8 gap-1.5 cursor-pointer"
808
- >
809
- <IconExternalLink className="h-3.5 w-3.5" />
810
- {t("meetingDetail.getDesktopApp")}
811
- </CaptureInstallButton>
812
- )}
800
+ <div className="mb-4 flex items-start gap-3 rounded-md border border-border bg-accent/20 px-3 py-2.5">
801
+ <IconDeviceDesktop className="mt-0.5 h-4 w-4 shrink-0 text-muted-foreground" />
802
+ <span className="min-w-0 flex-1 text-sm">
803
+ {t("meetingDetail.desktopHint")}
804
+ </span>
813
805
  </div>
814
806
  )}
815
807