@agent-native/core 0.134.0 → 0.135.0

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 (493) hide show
  1. package/corpus/README.md +3 -3
  2. package/corpus/core/CHANGELOG.md +113 -0
  3. package/corpus/core/docs/content/template-content-developers.mdx +1 -1
  4. package/corpus/core/package.json +3 -2
  5. package/corpus/core/src/automations/service.ts +41 -4
  6. package/corpus/core/src/cli/mcp-config-writers.ts +27 -21
  7. package/corpus/core/src/client/agent-page/AgentJobsTab.tsx +187 -85
  8. package/corpus/core/src/client/agent-page/AutomationDetailsDialog.tsx +208 -0
  9. package/corpus/core/src/client/agent-page/AutomationScheduleDialog.tsx +180 -0
  10. package/corpus/core/src/client/agent-page/TimezoneSelect.tsx +241 -0
  11. package/corpus/core/src/client/agent-page/use-jobs.ts +50 -10
  12. package/corpus/core/src/client/org/OrgSwitcher.tsx +26 -0
  13. package/corpus/core/src/client/settings/AccountSettingsCard.tsx +35 -31
  14. package/corpus/core/src/client/settings/SchedulingTimezoneField.tsx +71 -0
  15. package/corpus/core/src/client/settings/SettingsRow.tsx +113 -0
  16. package/corpus/core/src/client/settings/agent-settings-search.ts +2 -1
  17. package/corpus/core/src/client/settings/index.ts +6 -0
  18. package/corpus/core/src/integrations/identity.ts +2 -12
  19. package/corpus/core/src/jobs/actions/list-automation-runs.ts +33 -0
  20. package/corpus/core/src/jobs/actions/list-recurring-jobs.ts +27 -6
  21. package/corpus/core/src/jobs/actions/manage-recurring-job.ts +41 -9
  22. package/corpus/core/src/jobs/background-automation-runner.ts +88 -0
  23. package/corpus/core/src/jobs/cron.ts +52 -6
  24. package/corpus/core/src/jobs/frontmatter.ts +20 -0
  25. package/corpus/core/src/jobs/run-history.ts +242 -0
  26. package/corpus/core/src/jobs/scheduler.ts +73 -18
  27. package/corpus/core/src/jobs/tools.ts +54 -6
  28. package/corpus/core/src/localization/actions/get-localization-preference.ts +2 -2
  29. package/corpus/core/src/localization/actions/set-localization-preference.ts +49 -11
  30. package/corpus/core/src/localization/default-messages.ts +27 -0
  31. package/corpus/core/src/localization/shared.ts +39 -6
  32. package/corpus/core/src/localization/user-timezone.ts +36 -0
  33. package/corpus/core/src/org/app-roles-handlers.ts +1 -9
  34. package/corpus/core/src/org/index.ts +2 -0
  35. package/corpus/core/src/org/membership.ts +18 -0
  36. package/corpus/core/src/review/actions/create-review-comment.ts +4 -1
  37. package/corpus/core/src/review/actions/reply-review-comment.ts +4 -1
  38. package/corpus/core/src/review/index.ts +5 -0
  39. package/corpus/core/src/review/notifications.ts +163 -0
  40. package/corpus/core/src/review/types.ts +7 -0
  41. package/corpus/core/src/server/action-discovery.ts +4 -0
  42. package/corpus/core/src/server/activity-notifications.ts +174 -0
  43. package/corpus/core/src/server/app-name.ts +31 -0
  44. package/corpus/core/src/server/better-auth-instance.ts +4 -4
  45. package/corpus/core/src/server/email-templates.ts +70 -14
  46. package/corpus/core/src/server/email.ts +66 -3
  47. package/corpus/core/src/server/index.ts +10 -0
  48. package/corpus/core/src/sharing/index.ts +5 -0
  49. package/corpus/core/src/sharing/recipients.ts +67 -0
  50. package/corpus/core/src/triggers/actions/list-automations.ts +27 -8
  51. package/corpus/core/src/triggers/actions/manage-automation.ts +17 -6
  52. package/corpus/core/src/triggers/actions.ts +16 -4
  53. package/corpus/core/src/triggers/dispatcher.ts +44 -30
  54. package/corpus/core/src/triggers/routes.ts +19 -6
  55. package/corpus/core/src/vitest-config.ts +68 -0
  56. package/corpus/core/vitest.config.ts +5 -0
  57. package/corpus/templates/analytics/app/pages/Settings.tsx +37 -53
  58. package/corpus/templates/analytics/changelog/2026-08-01-new-error-alerts-now-reach-you-by-email-and-a-scheduled-dash.md +6 -0
  59. package/corpus/templates/analytics/server/jobs/dashboard-report.ts +43 -0
  60. package/corpus/templates/analytics/server/lib/error-capture.ts +5 -1
  61. package/corpus/templates/analytics/vitest.config.ts +16 -12
  62. package/corpus/templates/assets/actions/refresh-generation-run.ts +3 -0
  63. package/corpus/templates/assets/app/components/ui/switch.tsx +1 -0
  64. package/corpus/templates/assets/app/hooks/use-assets-prefs.ts +55 -0
  65. package/corpus/templates/assets/app/i18n-data.ts +30 -0
  66. package/corpus/templates/assets/app/routes/settings.tsx +58 -55
  67. package/corpus/templates/assets/changelog/2026-08-01-get-an-email-when-a-generation-you-started-finishes-or-fails.md +6 -0
  68. package/corpus/templates/assets/server/lib/generation-run-notifications.ts +89 -0
  69. package/corpus/templates/assets/server/lib/video-runs.ts +8 -1
  70. package/corpus/templates/assets/server/routes/_agent-native/assets/user-prefs.get.ts +17 -0
  71. package/corpus/templates/assets/server/routes/_agent-native/assets/user-prefs.put.ts +36 -0
  72. package/corpus/templates/assets/shared/assets-user-prefs.ts +10 -0
  73. package/corpus/templates/assets/vitest.config.ts +16 -12
  74. package/corpus/templates/brain/app/routes/settings.tsx +15 -15
  75. package/corpus/templates/brain/vitest.config.ts +16 -12
  76. package/corpus/templates/calendar/app/pages/Settings.tsx +32 -40
  77. package/corpus/templates/calendar/changelog/2026-08-01-language-and-appearance-now-sit-together-in-one-preferences-.md +6 -0
  78. package/corpus/templates/calendar/vitest.config.ts +16 -12
  79. package/corpus/templates/chat/app/routes/settings.tsx +14 -22
  80. package/corpus/templates/chat/vitest.config.ts +9 -0
  81. package/corpus/templates/clips/.agents/skills/video-editing/SKILL.md +31 -0
  82. package/corpus/templates/clips/AGENTS.md +1 -0
  83. package/corpus/templates/clips/actions/add-comment.ts +12 -1
  84. package/corpus/templates/clips/actions/create-meeting.ts +2 -2
  85. package/corpus/templates/clips/actions/create-recording.ts +2 -2
  86. package/corpus/templates/clips/actions/finalize-recording.ts +42 -6
  87. package/corpus/templates/clips/actions/generate-filmstrip.ts +154 -0
  88. package/corpus/templates/clips/actions/get-recording-player-data.ts +6 -0
  89. package/corpus/templates/clips/actions/import-loom-recording.ts +2 -2
  90. package/corpus/templates/clips/actions/lib/ensure-recording-filmstrip.ts +258 -0
  91. package/corpus/templates/clips/actions/lib/ensure-seekable-video.ts +11 -3
  92. package/corpus/templates/clips/actions/react-to-comment.ts +13 -0
  93. package/corpus/templates/clips/actions/react-to-recording.ts +10 -1
  94. package/corpus/templates/clips/actions/reply-to-comment.ts +12 -1
  95. package/corpus/templates/clips/actions/stitch-recordings.ts +2 -2
  96. package/corpus/templates/clips/app/components/editor/editor-layout.tsx +152 -23
  97. package/corpus/templates/clips/app/components/editor/waveform.tsx +237 -75
  98. package/corpus/templates/clips/app/components/recorder/recorder-engine.ts +23 -1
  99. package/corpus/templates/clips/app/components/settings/ai-setup-section.tsx +441 -0
  100. package/corpus/templates/clips/app/components/settings/slack-section.tsx +329 -0
  101. package/corpus/templates/clips/app/components/settings/types.ts +14 -0
  102. package/corpus/templates/clips/app/components/settings/video-storage-section.tsx +443 -0
  103. package/corpus/templates/clips/app/hooks/use-secret-status.ts +69 -0
  104. package/corpus/templates/clips/app/i18n/en-US.ts +9 -1
  105. package/corpus/templates/clips/app/lib/video-filmstrip.ts +262 -0
  106. package/corpus/templates/clips/app/routes/_app.settings._index.tsx +165 -1351
  107. package/corpus/templates/clips/changelog/2026-07-27-saved-clips-now-resume-from-the-last-uploaded-byte-after-an-.md +6 -0
  108. package/corpus/templates/clips/changelog/2026-07-29-meeting-notes-no-longer-transcribe-the-other-side-twice-when.md +6 -0
  109. package/corpus/templates/clips/changelog/2026-07-31-editor-timeline-now-shows-video-frames-behind-the-waveform-g.md +6 -0
  110. package/corpus/templates/clips/changelog/2026-07-31-email-notifications-now-go-out-when-someone-comments-on-or-r.md +6 -0
  111. package/corpus/templates/clips/changelog/2026-07-31-you-can-now-set-a-default-visibility-for-new-recordings-in-s.md +6 -0
  112. package/corpus/templates/clips/changelog/2026-08-01-email-notifications-for-comments-and-reactions-now-actually-.md +6 -0
  113. package/corpus/templates/clips/changelog/2026-08-03-the-desktop-app-now-opens-directly-beneath-its-tray-icon-on-.md +6 -0
  114. package/corpus/templates/clips/chrome-extension/package.json +1 -0
  115. package/corpus/templates/clips/chrome-extension/vitest.config.ts +9 -0
  116. package/corpus/templates/clips/desktop/package.json +1 -0
  117. package/corpus/templates/clips/desktop/src/app.tsx +17 -1
  118. package/corpus/templates/clips/desktop/src/hooks/useMeetingTranscription.ts +37 -48
  119. package/corpus/templates/clips/desktop/src/lib/recorder.ts +331 -36
  120. package/corpus/templates/clips/desktop/src/lib/transcription-capture.ts +7 -5
  121. package/corpus/templates/clips/desktop/src/lib/transcription-engine.ts +182 -106
  122. package/corpus/templates/clips/desktop/src/lib/upload-recovery.ts +129 -0
  123. package/corpus/templates/clips/desktop/src/overlays/live-transcript.tsx +43 -14
  124. package/corpus/templates/clips/desktop/src/overlays/recording-pill.tsx +3 -3
  125. package/corpus/templates/clips/desktop/src-tauri/src/echo_guard.rs +443 -0
  126. package/corpus/templates/clips/desktop/src-tauri/src/lib.rs +1 -0
  127. package/corpus/templates/clips/desktop/src-tauri/src/native_screen.rs +815 -15
  128. package/corpus/templates/clips/desktop/src-tauri/src/tray.rs +61 -11
  129. package/corpus/templates/clips/desktop/src-tauri/src/whisper_speech.rs +125 -16
  130. package/corpus/templates/clips/desktop/vitest.config.ts +9 -0
  131. package/corpus/templates/clips/server/db/schema.ts +17 -0
  132. package/corpus/templates/clips/server/jobs/media-verification.ts +4 -0
  133. package/corpus/templates/clips/server/lib/activity-notifications.ts +185 -0
  134. package/corpus/templates/clips/server/lib/recording-media-cleanup.ts +2 -0
  135. package/corpus/templates/clips/server/lib/recording-upload-state.ts +46 -9
  136. package/corpus/templates/clips/server/lib/recordings.ts +24 -0
  137. package/corpus/templates/clips/server/lib/resumable-session.ts +10 -4
  138. package/corpus/templates/clips/server/lib/transactional-email-templates.ts +109 -0
  139. package/corpus/templates/clips/server/lib/upload-interruption.ts +8 -0
  140. package/corpus/templates/clips/server/lib/upload-lease.ts +50 -4
  141. package/corpus/templates/clips/server/lib/video-filmstrip-sprite.ts +207 -0
  142. package/corpus/templates/clips/server/lib/video-remux.ts +13 -4
  143. package/corpus/templates/clips/server/plugins/db.ts +16 -0
  144. package/corpus/templates/clips/server/routes/api/_agent-native-background/post-finalize-worker.post.ts +4 -0
  145. package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/abort.post.ts +123 -36
  146. package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/chunk.post.ts +159 -29
  147. package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/interrupt.post.ts +197 -0
  148. package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/reset-chunks.post.ts +193 -39
  149. package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/resume.get.ts +191 -12
  150. package/corpus/templates/clips/shared/clips-ai-prefs.ts +6 -0
  151. package/corpus/templates/clips/shared/feature-flags.ts +12 -0
  152. package/corpus/templates/clips/shared/recording-core.ts +4 -0
  153. package/corpus/templates/clips/vitest.config.ts +23 -19
  154. package/corpus/templates/content/.agents/skills/document-editing/references/databases.md +6 -4
  155. package/corpus/templates/content/actions/_database-membership-lock.ts +25 -0
  156. package/corpus/templates/content/actions/_database-row-batch.ts +6 -2
  157. package/corpus/templates/content/actions/_database-source-utils.ts +289 -129
  158. package/corpus/templates/content/actions/_database-utils.ts +12 -3
  159. package/corpus/templates/content/actions/add-comment.ts +15 -1
  160. package/corpus/templates/content/actions/add-content-database-source-field-property.ts +39 -21
  161. package/corpus/templates/content/actions/attach-content-database-source.ts +4 -1
  162. package/corpus/templates/content/actions/change-content-database-source-role.ts +3 -5
  163. package/corpus/templates/content/actions/duplicate-document-property.ts +59 -46
  164. package/corpus/templates/content/actions/execute-builder-source-execution.ts +77 -71
  165. package/corpus/templates/content/actions/list-documents.ts +1 -0
  166. package/corpus/templates/content/actions/materialize-builder-required-fields.ts +7 -0
  167. package/corpus/templates/content/actions/remove-database-items.ts +183 -0
  168. package/corpus/templates/content/actions/set-document-property.ts +57 -39
  169. package/corpus/templates/content/actions/sync-local-folder-source.ts +7 -0
  170. package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +5 -2
  171. package/corpus/templates/content/app/components/editor/DocumentToolbar.tsx +50 -1
  172. package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +471 -329
  173. package/corpus/templates/content/app/components/editor/database/row-access.ts +45 -0
  174. package/corpus/templates/content/app/components/editor/database/shared.tsx +141 -38
  175. package/corpus/templates/content/app/components/ui/switch.tsx +1 -0
  176. package/corpus/templates/content/app/hooks/use-content-database.ts +2 -2
  177. package/corpus/templates/content/app/hooks/use-content-prefs.ts +55 -0
  178. package/corpus/templates/content/app/i18n-data.ts +170 -0
  179. package/corpus/templates/content/app/routes/_app.settings.tsx +47 -18
  180. package/corpus/templates/content/changelog/2026-07-30-breadcrumb-menus-now-hide-internal-system-pages.md +6 -0
  181. package/corpus/templates/content/changelog/2026-07-30-database-rows-can-now-be-removed-without-deleting-their-page.md +6 -0
  182. package/corpus/templates/content/changelog/2026-08-01-get-an-email-when-someone-comments-on-replies-in-or-mentions.md +6 -0
  183. package/corpus/templates/content/parity/eval-scenarios.ts +2 -2
  184. package/corpus/templates/content/parity/matrix.md +1 -1
  185. package/corpus/templates/content/parity/matrix.ts +4 -5
  186. package/corpus/templates/content/server/lib/comment-notifications.ts +146 -0
  187. package/corpus/templates/content/server/routes/_agent-native/content/user-prefs.get.ts +17 -0
  188. package/corpus/templates/content/server/routes/_agent-native/content/user-prefs.put.ts +36 -0
  189. package/corpus/templates/content/shared/api.ts +4 -0
  190. package/corpus/templates/content/shared/content-user-prefs.ts +11 -0
  191. package/corpus/templates/content/vitest.config.ts +20 -16
  192. package/corpus/templates/crm/app/routes/settings.tsx +14 -22
  193. package/corpus/templates/crm/vitest.config.ts +8 -4
  194. package/corpus/templates/design/.generated/bridge/editor-chrome.generated.ts +26 -11
  195. package/corpus/templates/design/actions/take-design-screenshot.ts +7 -0
  196. package/corpus/templates/design/app/components/design/DesignCanvas.tsx +11 -3
  197. package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +4 -1
  198. package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +54 -17
  199. package/corpus/templates/design/app/components/design/design-canvas/local-runtime.ts +126 -0
  200. package/corpus/templates/design/app/components/templates/TemplatePreview.tsx +2 -0
  201. package/corpus/templates/design/app/pages/Index.tsx +3 -1
  202. package/corpus/templates/design/app/pages/Present.tsx +2 -1
  203. package/corpus/templates/design/app/routes/settings.tsx +14 -22
  204. package/corpus/templates/design/changelog/2026-07-30-a-broken-inline-script-in-a-generated-screen-is-now-caught-o.md +6 -0
  205. package/corpus/templates/design/changelog/2026-07-30-design-html-is-now-validated-with-a-spec-grade-html-parser-i.md +6 -0
  206. package/corpus/templates/design/changelog/2026-07-30-designs-now-load-their-tailwind-and-alpine-runtimes-from-the.md +6 -0
  207. package/corpus/templates/design/changelog/2026-07-30-designs-with-a-broken-alpine-expression-are-now-caught-on-sa.md +6 -0
  208. package/corpus/templates/design/changelog/2026-07-30-editing-a-design-no-longer-strips-its-styling-the-canvas-kep.md +6 -0
  209. package/corpus/templates/design/changelog/2026-08-01-review-comments-replies-and-mentions-now-send-an-email-to-th.md +6 -0
  210. package/corpus/templates/design/package.json +4 -0
  211. package/corpus/templates/design/shared/html-integrity.ts +802 -556
  212. package/corpus/templates/design/vitest.config.ts +18 -12
  213. package/corpus/templates/dispatch/app/routes/settings.tsx +26 -40
  214. package/corpus/templates/dispatch/vitest.config.ts +11 -7
  215. package/corpus/templates/factory/app/routes/settings.tsx +14 -22
  216. package/corpus/templates/factory/vitest.config.ts +9 -0
  217. package/corpus/templates/forms/app/routes/_app.settings.tsx +14 -22
  218. package/corpus/templates/forms/vitest.config.ts +27 -23
  219. package/corpus/templates/macros/app/routes/settings.tsx +14 -22
  220. package/corpus/templates/macros/vitest.config.ts +16 -12
  221. package/corpus/templates/mail/app/pages/SettingsPage.tsx +14 -16
  222. package/corpus/templates/mail/vitest.config.ts +16 -12
  223. package/corpus/templates/plan/app/routes/settings.tsx +30 -44
  224. package/corpus/templates/plan/vitest.config.ts +24 -20
  225. package/corpus/templates/slides/actions/add-slide-comment.ts +12 -1
  226. package/corpus/templates/slides/app/components/ui/switch.tsx +1 -0
  227. package/corpus/templates/slides/app/hooks/use-slides-prefs.ts +55 -0
  228. package/corpus/templates/slides/app/i18n/en-US.ts +4 -0
  229. package/corpus/templates/slides/app/routes/settings.tsx +45 -22
  230. package/corpus/templates/slides/changelog/2026-08-01-get-an-email-when-someone-comments-on-or-replies-in-your-dec.md +6 -0
  231. package/corpus/templates/slides/server/lib/comment-notifications.ts +182 -0
  232. package/corpus/templates/slides/server/routes/_agent-native/slides/user-prefs.get.ts +16 -0
  233. package/corpus/templates/slides/server/routes/_agent-native/slides/user-prefs.put.ts +36 -0
  234. package/corpus/templates/slides/shared/slides-user-prefs.ts +11 -0
  235. package/corpus/templates/slides/vitest.config.ts +17 -13
  236. package/corpus/templates/tasks/app/components/layout/Sidebar.tsx +2 -0
  237. package/corpus/templates/tasks/app/routes/settings.tsx +14 -22
  238. package/corpus/templates/tasks/vitest.config.ts +17 -13
  239. package/corpus/toolkit/CHANGELOG.md +9 -0
  240. package/corpus/toolkit/package.json +1 -1
  241. package/corpus/toolkit/vitest.config.ts +3 -0
  242. package/dist/automations/service.d.ts +2 -0
  243. package/dist/automations/service.d.ts.map +1 -1
  244. package/dist/automations/service.js +29 -4
  245. package/dist/automations/service.js.map +1 -1
  246. package/dist/cli/mcp-config-writers.d.ts.map +1 -1
  247. package/dist/cli/mcp-config-writers.js +24 -23
  248. package/dist/cli/mcp-config-writers.js.map +1 -1
  249. package/dist/client/agent-page/AgentJobsTab.d.ts.map +1 -1
  250. package/dist/client/agent-page/AgentJobsTab.js +86 -29
  251. package/dist/client/agent-page/AgentJobsTab.js.map +1 -1
  252. package/dist/client/agent-page/AutomationDetailsDialog.d.ts +21 -0
  253. package/dist/client/agent-page/AutomationDetailsDialog.d.ts.map +1 -0
  254. package/dist/client/agent-page/AutomationDetailsDialog.js +33 -0
  255. package/dist/client/agent-page/AutomationDetailsDialog.js.map +1 -0
  256. package/dist/client/agent-page/AutomationScheduleDialog.d.ts +15 -0
  257. package/dist/client/agent-page/AutomationScheduleDialog.d.ts.map +1 -0
  258. package/dist/client/agent-page/AutomationScheduleDialog.js +46 -0
  259. package/dist/client/agent-page/AutomationScheduleDialog.js.map +1 -0
  260. package/dist/client/agent-page/TimezoneSelect.d.ts +17 -0
  261. package/dist/client/agent-page/TimezoneSelect.d.ts.map +1 -0
  262. package/dist/client/agent-page/TimezoneSelect.js +107 -0
  263. package/dist/client/agent-page/TimezoneSelect.js.map +1 -0
  264. package/dist/client/agent-page/use-jobs.d.ts +20 -7
  265. package/dist/client/agent-page/use-jobs.d.ts.map +1 -1
  266. package/dist/client/agent-page/use-jobs.js +24 -4
  267. package/dist/client/agent-page/use-jobs.js.map +1 -1
  268. package/dist/client/org/OrgSwitcher.d.ts +8 -1
  269. package/dist/client/org/OrgSwitcher.d.ts.map +1 -1
  270. package/dist/client/org/OrgSwitcher.js +8 -2
  271. package/dist/client/org/OrgSwitcher.js.map +1 -1
  272. package/dist/client/settings/AccountSettingsCard.d.ts.map +1 -1
  273. package/dist/client/settings/AccountSettingsCard.js +10 -9
  274. package/dist/client/settings/AccountSettingsCard.js.map +1 -1
  275. package/dist/client/settings/SchedulingTimezoneField.d.ts +10 -0
  276. package/dist/client/settings/SchedulingTimezoneField.d.ts.map +1 -0
  277. package/dist/client/settings/SchedulingTimezoneField.js +30 -0
  278. package/dist/client/settings/SchedulingTimezoneField.js.map +1 -0
  279. package/dist/client/settings/SettingsRow.d.ts +36 -0
  280. package/dist/client/settings/SettingsRow.d.ts.map +1 -0
  281. package/dist/client/settings/SettingsRow.js +15 -0
  282. package/dist/client/settings/SettingsRow.js.map +1 -0
  283. package/dist/client/settings/agent-settings-search.d.ts.map +1 -1
  284. package/dist/client/settings/agent-settings-search.js +1 -1
  285. package/dist/client/settings/agent-settings-search.js.map +1 -1
  286. package/dist/client/settings/index.d.ts +1 -0
  287. package/dist/client/settings/index.d.ts.map +1 -1
  288. package/dist/client/settings/index.js +1 -0
  289. package/dist/client/settings/index.js.map +1 -1
  290. package/dist/collab/struct-routes.d.ts +1 -1
  291. package/dist/file-upload/actions/upload-image.d.ts +1 -1
  292. package/dist/integrations/identity.d.ts.map +1 -1
  293. package/dist/integrations/identity.js +2 -11
  294. package/dist/integrations/identity.js.map +1 -1
  295. package/dist/jobs/actions/list-automation-runs.d.ts +8 -0
  296. package/dist/jobs/actions/list-automation-runs.d.ts.map +1 -0
  297. package/dist/jobs/actions/list-automation-runs.js +29 -0
  298. package/dist/jobs/actions/list-automation-runs.js.map +1 -0
  299. package/dist/jobs/actions/list-recurring-jobs.d.ts +2 -0
  300. package/dist/jobs/actions/list-recurring-jobs.d.ts.map +1 -1
  301. package/dist/jobs/actions/list-recurring-jobs.js +20 -7
  302. package/dist/jobs/actions/list-recurring-jobs.js.map +1 -1
  303. package/dist/jobs/actions/manage-recurring-job.d.ts +2 -0
  304. package/dist/jobs/actions/manage-recurring-job.js +30 -9
  305. package/dist/jobs/actions/manage-recurring-job.js.map +1 -1
  306. package/dist/jobs/background-automation-runner.d.ts.map +1 -1
  307. package/dist/jobs/background-automation-runner.js +60 -0
  308. package/dist/jobs/background-automation-runner.js.map +1 -1
  309. package/dist/jobs/cron.d.ts +16 -2
  310. package/dist/jobs/cron.d.ts.map +1 -1
  311. package/dist/jobs/cron.js +43 -6
  312. package/dist/jobs/cron.js.map +1 -1
  313. package/dist/jobs/frontmatter.d.ts +12 -0
  314. package/dist/jobs/frontmatter.d.ts.map +1 -1
  315. package/dist/jobs/frontmatter.js +8 -0
  316. package/dist/jobs/frontmatter.js.map +1 -1
  317. package/dist/jobs/run-history.d.ts +56 -0
  318. package/dist/jobs/run-history.d.ts.map +1 -0
  319. package/dist/jobs/run-history.js +169 -0
  320. package/dist/jobs/run-history.js.map +1 -0
  321. package/dist/jobs/scheduler.d.ts.map +1 -1
  322. package/dist/jobs/scheduler.js +51 -18
  323. package/dist/jobs/scheduler.js.map +1 -1
  324. package/dist/jobs/tools.d.ts.map +1 -1
  325. package/dist/jobs/tools.js +37 -6
  326. package/dist/jobs/tools.js.map +1 -1
  327. package/dist/localization/actions/get-localization-preference.d.ts +1 -2
  328. package/dist/localization/actions/get-localization-preference.d.ts.map +1 -1
  329. package/dist/localization/actions/get-localization-preference.js.map +1 -1
  330. package/dist/localization/actions/set-localization-preference.d.ts +3 -3
  331. package/dist/localization/actions/set-localization-preference.d.ts.map +1 -1
  332. package/dist/localization/actions/set-localization-preference.js +33 -8
  333. package/dist/localization/actions/set-localization-preference.js.map +1 -1
  334. package/dist/localization/default-messages.d.ts +25 -0
  335. package/dist/localization/default-messages.d.ts.map +1 -1
  336. package/dist/localization/default-messages.js +25 -0
  337. package/dist/localization/default-messages.js.map +1 -1
  338. package/dist/localization/shared.d.ts +16 -1
  339. package/dist/localization/shared.d.ts.map +1 -1
  340. package/dist/localization/shared.js +22 -4
  341. package/dist/localization/shared.js.map +1 -1
  342. package/dist/localization/user-timezone.d.ts +11 -0
  343. package/dist/localization/user-timezone.d.ts.map +1 -0
  344. package/dist/localization/user-timezone.js +31 -0
  345. package/dist/localization/user-timezone.js.map +1 -0
  346. package/dist/notifications/routes.d.ts +2 -2
  347. package/dist/observability/routes.d.ts +1 -1
  348. package/dist/org/app-roles-handlers.d.ts.map +1 -1
  349. package/dist/org/app-roles-handlers.js +1 -8
  350. package/dist/org/app-roles-handlers.js.map +1 -1
  351. package/dist/org/index.d.ts +1 -0
  352. package/dist/org/index.d.ts.map +1 -1
  353. package/dist/org/index.js +1 -0
  354. package/dist/org/index.js.map +1 -1
  355. package/dist/org/membership.d.ts +6 -0
  356. package/dist/org/membership.d.ts.map +1 -0
  357. package/dist/org/membership.js +16 -0
  358. package/dist/org/membership.js.map +1 -0
  359. package/dist/provider-api/actions/custom-provider-registration.d.ts +10 -10
  360. package/dist/provider-api/actions/provider-api.d.ts +8 -8
  361. package/dist/provider-api/corpus-jobs.d.ts +2 -2
  362. package/dist/resources/handlers.d.ts +1 -1
  363. package/dist/review/actions/create-review-comment.d.ts +32 -1
  364. package/dist/review/actions/create-review-comment.d.ts.map +1 -1
  365. package/dist/review/actions/create-review-comment.js +3 -1
  366. package/dist/review/actions/create-review-comment.js.map +1 -1
  367. package/dist/review/actions/reply-review-comment.d.ts +32 -1
  368. package/dist/review/actions/reply-review-comment.d.ts.map +1 -1
  369. package/dist/review/actions/reply-review-comment.js +3 -1
  370. package/dist/review/actions/reply-review-comment.js.map +1 -1
  371. package/dist/review/index.d.ts +1 -0
  372. package/dist/review/index.d.ts.map +1 -1
  373. package/dist/review/index.js +1 -0
  374. package/dist/review/index.js.map +1 -1
  375. package/dist/review/notifications.d.ts +24 -0
  376. package/dist/review/notifications.d.ts.map +1 -0
  377. package/dist/review/notifications.js +123 -0
  378. package/dist/review/notifications.js.map +1 -0
  379. package/dist/review/types.d.ts +5 -0
  380. package/dist/review/types.d.ts.map +1 -1
  381. package/dist/review/types.js.map +1 -1
  382. package/dist/server/action-discovery.d.ts.map +1 -1
  383. package/dist/server/action-discovery.js +4 -0
  384. package/dist/server/action-discovery.js.map +1 -1
  385. package/dist/server/activity-notifications.d.ts +61 -0
  386. package/dist/server/activity-notifications.d.ts.map +1 -0
  387. package/dist/server/activity-notifications.js +103 -0
  388. package/dist/server/activity-notifications.js.map +1 -0
  389. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  390. package/dist/server/app-name.d.ts +18 -0
  391. package/dist/server/app-name.d.ts.map +1 -1
  392. package/dist/server/app-name.js +31 -0
  393. package/dist/server/app-name.js.map +1 -1
  394. package/dist/server/better-auth-instance.js +4 -4
  395. package/dist/server/better-auth-instance.js.map +1 -1
  396. package/dist/server/email-templates.d.ts +12 -0
  397. package/dist/server/email-templates.d.ts.map +1 -1
  398. package/dist/server/email-templates.js +57 -14
  399. package/dist/server/email-templates.js.map +1 -1
  400. package/dist/server/email.d.ts +12 -0
  401. package/dist/server/email.d.ts.map +1 -1
  402. package/dist/server/email.js +52 -5
  403. package/dist/server/email.js.map +1 -1
  404. package/dist/server/index.d.ts +1 -0
  405. package/dist/server/index.d.ts.map +1 -1
  406. package/dist/server/index.js +1 -0
  407. package/dist/server/index.js.map +1 -1
  408. package/dist/sharing/index.d.ts +1 -0
  409. package/dist/sharing/index.d.ts.map +1 -1
  410. package/dist/sharing/index.js +1 -0
  411. package/dist/sharing/index.js.map +1 -1
  412. package/dist/sharing/recipients.d.ts +31 -0
  413. package/dist/sharing/recipients.d.ts.map +1 -0
  414. package/dist/sharing/recipients.js +40 -0
  415. package/dist/sharing/recipients.js.map +1 -0
  416. package/dist/templates/chat/app/routes/settings.tsx +14 -22
  417. package/dist/templates/chat/vitest.config.ts +9 -0
  418. package/dist/triggers/actions/list-automations.d.ts +2 -0
  419. package/dist/triggers/actions/list-automations.d.ts.map +1 -1
  420. package/dist/triggers/actions/list-automations.js +19 -8
  421. package/dist/triggers/actions/list-automations.js.map +1 -1
  422. package/dist/triggers/actions/manage-automation.d.ts +6 -0
  423. package/dist/triggers/actions/manage-automation.js +12 -6
  424. package/dist/triggers/actions/manage-automation.js.map +1 -1
  425. package/dist/triggers/actions.d.ts.map +1 -1
  426. package/dist/triggers/actions.js +15 -4
  427. package/dist/triggers/actions.js.map +1 -1
  428. package/dist/triggers/dispatcher.d.ts.map +1 -1
  429. package/dist/triggers/dispatcher.js +19 -17
  430. package/dist/triggers/dispatcher.js.map +1 -1
  431. package/dist/triggers/routes.d.ts.map +1 -1
  432. package/dist/triggers/routes.js +6 -6
  433. package/dist/triggers/routes.js.map +1 -1
  434. package/dist/vitest-config.d.ts +10 -0
  435. package/dist/vitest-config.d.ts.map +1 -0
  436. package/dist/vitest-config.js +55 -0
  437. package/dist/vitest-config.js.map +1 -0
  438. package/docs/content/template-content-developers.mdx +1 -1
  439. package/package.json +5 -4
  440. package/src/automations/service.ts +41 -4
  441. package/src/cli/mcp-config-writers.ts +27 -21
  442. package/src/client/agent-page/AgentJobsTab.tsx +187 -85
  443. package/src/client/agent-page/AutomationDetailsDialog.tsx +208 -0
  444. package/src/client/agent-page/AutomationScheduleDialog.tsx +180 -0
  445. package/src/client/agent-page/TimezoneSelect.tsx +241 -0
  446. package/src/client/agent-page/use-jobs.ts +50 -10
  447. package/src/client/org/OrgSwitcher.tsx +26 -0
  448. package/src/client/settings/AccountSettingsCard.tsx +35 -31
  449. package/src/client/settings/SchedulingTimezoneField.tsx +71 -0
  450. package/src/client/settings/SettingsRow.tsx +113 -0
  451. package/src/client/settings/agent-settings-search.ts +2 -1
  452. package/src/client/settings/index.ts +6 -0
  453. package/src/integrations/identity.ts +2 -12
  454. package/src/jobs/actions/list-automation-runs.ts +33 -0
  455. package/src/jobs/actions/list-recurring-jobs.ts +27 -6
  456. package/src/jobs/actions/manage-recurring-job.ts +41 -9
  457. package/src/jobs/background-automation-runner.ts +88 -0
  458. package/src/jobs/cron.ts +52 -6
  459. package/src/jobs/frontmatter.ts +20 -0
  460. package/src/jobs/run-history.ts +242 -0
  461. package/src/jobs/scheduler.ts +73 -18
  462. package/src/jobs/tools.ts +54 -6
  463. package/src/localization/actions/get-localization-preference.ts +2 -2
  464. package/src/localization/actions/set-localization-preference.ts +49 -11
  465. package/src/localization/default-messages.ts +27 -0
  466. package/src/localization/shared.ts +39 -6
  467. package/src/localization/user-timezone.ts +36 -0
  468. package/src/org/app-roles-handlers.ts +1 -9
  469. package/src/org/index.ts +2 -0
  470. package/src/org/membership.ts +18 -0
  471. package/src/review/actions/create-review-comment.ts +4 -1
  472. package/src/review/actions/reply-review-comment.ts +4 -1
  473. package/src/review/index.ts +5 -0
  474. package/src/review/notifications.ts +163 -0
  475. package/src/review/types.ts +7 -0
  476. package/src/server/action-discovery.ts +4 -0
  477. package/src/server/activity-notifications.ts +174 -0
  478. package/src/server/app-name.ts +31 -0
  479. package/src/server/better-auth-instance.ts +4 -4
  480. package/src/server/email-templates.ts +70 -14
  481. package/src/server/email.ts +66 -3
  482. package/src/server/index.ts +10 -0
  483. package/src/sharing/index.ts +5 -0
  484. package/src/sharing/recipients.ts +67 -0
  485. package/src/templates/chat/app/routes/settings.tsx +14 -22
  486. package/src/templates/chat/vitest.config.ts +9 -0
  487. package/src/triggers/actions/list-automations.ts +27 -8
  488. package/src/triggers/actions/manage-automation.ts +17 -6
  489. package/src/triggers/actions.ts +16 -4
  490. package/src/triggers/dispatcher.ts +44 -30
  491. package/src/triggers/routes.ts +19 -6
  492. package/src/vitest-config.ts +68 -0
  493. package/corpus/templates/content/actions/delete-database-items.ts +0 -89
@@ -1,11 +1,16 @@
1
1
  import { useEffect, useMemo, useRef } from "react";
2
2
 
3
3
  import { cn } from "@/lib/utils";
4
+ import type { FilmstripFrame, FilmstripSprite } from "@/lib/video-filmstrip";
4
5
  import type { WaveformPeaks } from "@/lib/waveform-peaks";
5
6
 
6
7
  export interface WaveformProps {
7
8
  /** Peaks computed via `computePeaks()`. */
8
9
  peaks: WaveformPeaks | null;
10
+ /** Server-generated filmstrip sprite. Preferred over `frames` when present. */
11
+ sprite?: FilmstripSprite | null;
12
+ /** Browser-extracted frame thumbnails — the fallback when there's no sprite. */
13
+ frames?: FilmstripFrame[];
9
14
  /** Width in px of the viewport (the scroll container). */
10
15
  width: number;
11
16
  /** Height in px. */
@@ -49,15 +54,19 @@ const getBrandColorAlpha = (alpha: number) => {
49
54
  return v ? `hsl(${v} / ${alpha})` : `rgba(15, 23, 42, ${alpha})`;
50
55
  };
51
56
 
52
- const getWaveColor = () => getBrandColorAlpha(0.55);
53
- const getWaveBg = () => getBrandColorAlpha(0.12);
54
- const EXCLUDED_FILL = "rgba(15, 23, 42, 0.72)";
55
- const EXCLUDED_STROKE = "rgba(148, 163, 184, 0.45)";
57
+ const getWaveColor = () => getBrandColorAlpha(0.85);
58
+ const getWaveBg = () => getBrandColorAlpha(0.08);
59
+ const EXCLUDED_FILL = "rgba(15, 23, 42, 0.65)";
60
+ const EXCLUDED_STROKE = "rgba(148, 163, 184, 0.4)";
61
+ const EMPTY_FRAMES: FilmstripFrame[] = [];
56
62
  const VISUAL_TARGET_PEAK = 0.78;
57
63
  const VISUAL_MAX_GAIN = 24;
58
64
  const VISUAL_GAIN_PERCENTILE = 0.95;
59
65
  const VISUAL_SILENCE_FLOOR = 0.001;
60
66
 
67
+ const MAX_BASE_TRACK_WIDTH = 8192;
68
+ const MAX_CANVAS_PIXELS_WIDTH = 4096;
69
+
61
70
  function clampSample(value: number): number {
62
71
  return Math.max(-1, Math.min(1, value));
63
72
  }
@@ -80,9 +89,28 @@ function computeVisualGain(samples: number[]): number {
80
89
  return Math.min(VISUAL_MAX_GAIN, VISUAL_TARGET_PEAK / reference);
81
90
  }
82
91
 
92
+ function drawPillBar(
93
+ ctx: CanvasRenderingContext2D,
94
+ x: number,
95
+ y: number,
96
+ width: number,
97
+ height: number,
98
+ ) {
99
+ const radius = Math.min(width / 2, height / 2);
100
+ ctx.beginPath();
101
+ if (typeof ctx.roundRect === "function") {
102
+ ctx.roundRect(x, y, width, height, radius);
103
+ } else {
104
+ ctx.rect(x, y, width, height);
105
+ }
106
+ ctx.fill();
107
+ }
108
+
83
109
  /** Canvas-rendered waveform. Supports up to 50x zoom with horizontal scroll. */
84
110
  export function Waveform({
85
111
  peaks,
112
+ sprite,
113
+ frames = EMPTY_FRAMES,
86
114
  width,
87
115
  height = 120,
88
116
  zoom = 1,
@@ -100,8 +128,51 @@ export function Waveform({
100
128
  const canvasRef = useRef<HTMLCanvasElement | null>(null);
101
129
  const scrollRef = useRef<HTMLDivElement | null>(null);
102
130
 
103
- // The total drawable width (scrolls horizontally). zoom=1 fits exactly.
104
- const totalWidth = Math.max(width, Math.floor(width * Math.max(1, zoom)));
131
+ const baseTrackWidth = useMemo(() => {
132
+ if (durationMs <= 0 || width <= 0) return width;
133
+ const durationSec = durationMs / 1000;
134
+ // Scale timeline track width proportionally with duration so long videos don't squish (bounded to safe max allocation)
135
+ const durationScaledPx = Math.min(
136
+ MAX_BASE_TRACK_WIDTH,
137
+ Math.round(durationSec * 14),
138
+ );
139
+ return Math.max(width, durationScaledPx);
140
+ }, [durationMs, width]);
141
+
142
+ // The total drawable width (scrolls horizontally). Scales with duration and zoom.
143
+ const totalWidth = Math.max(
144
+ baseTrackWidth,
145
+ Math.floor(baseTrackWidth * Math.max(1, zoom)),
146
+ );
147
+
148
+ // A sprite has a fixed frame count, but the track needs however many cells
149
+ // fit at the video's aspect — otherwise cells go portrait and each thumbnail
150
+ // shows a narrow centre slice. Pick the cell count from the geometry, then
151
+ // map each cell to the sprite frame nearest its midpoint.
152
+ const spriteCells = useMemo(() => {
153
+ if (!sprite?.url || sprite.frameCount <= 0 || sprite.columns <= 0)
154
+ return [];
155
+ const aspect =
156
+ sprite.frameHeight > 0 ? sprite.frameWidth / sprite.frameHeight : 16 / 9;
157
+ const cellWidth = Math.max(24, height * aspect);
158
+ const count = Math.max(
159
+ 1,
160
+ Math.min(sprite.frameCount, Math.round(totalWidth / cellWidth)),
161
+ );
162
+ return Array.from({ length: count }, (_, i) => {
163
+ const frame = Math.min(
164
+ sprite.frameCount - 1,
165
+ Math.floor(((i + 0.5) * sprite.frameCount) / count),
166
+ );
167
+ return {
168
+ key: `${i}-${frame}`,
169
+ column: frame % sprite.columns,
170
+ row: Math.floor(frame / sprite.columns),
171
+ };
172
+ });
173
+ }, [sprite, totalWidth, height]);
174
+
175
+ const hasImagery = spriteCells.length > 0 || frames.length > 0;
105
176
 
106
177
  useEffect(() => {
107
178
  const el = scrollRef.current;
@@ -112,12 +183,15 @@ export function Waveform({
112
183
  }
113
184
  }, [scrollLeft, totalWidth, width]);
114
185
 
115
- // Re-draw whenever peaks, size, or excluded ranges change.
186
+ // Re-draw whenever peaks, imagery, size, or excluded ranges change.
116
187
  useEffect(() => {
117
188
  const canvas = canvasRef.current;
118
189
  if (!canvas) return;
119
190
  const dpr = window.devicePixelRatio || 1;
120
- canvas.width = Math.floor(totalWidth * dpr);
191
+ canvas.width = Math.min(
192
+ MAX_CANVAS_PIXELS_WIDTH,
193
+ Math.floor(totalWidth * dpr),
194
+ );
121
195
  canvas.height = Math.floor(height * dpr);
122
196
  canvas.style.width = `${totalWidth}px`;
123
197
  canvas.style.height = `${height}px`;
@@ -126,74 +200,75 @@ export function Waveform({
126
200
  if (!ctx) return;
127
201
  ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
128
202
 
129
- // Background
130
- ctx.fillStyle = getWaveBg();
131
- ctx.fillRect(0, 0, totalWidth, height);
132
-
133
203
  const hasPeaks = Boolean(
134
204
  peaks?.bucketCount &&
135
205
  peaks.peaks.some((value) => Math.abs(value) > 0.0001),
136
206
  );
137
207
 
138
- if (peaks && hasPeaks) {
139
- // Map each x pixel to a bucket range. Use a visual-only auto-gain so
140
- // quiet microphone recordings still read clearly in the trim track.
141
- const mid = height / 2;
142
- const visualGain = computeVisualGain(peaks.peaks);
143
- ctx.strokeStyle = getWaveColor();
144
- ctx.lineWidth = 1;
145
- ctx.beginPath();
146
- const bucketsPerPx = peaks.bucketCount / totalWidth;
147
- for (let x = 0; x < totalWidth; x++) {
148
- const startBucket = Math.floor(x * bucketsPerPx);
149
- const endBucket = Math.max(
150
- startBucket + 1,
151
- Math.floor((x + 1) * bucketsPerPx),
152
- );
153
- let min = 0;
154
- let max = 0;
155
- for (let b = startBucket; b < endBucket && b < peaks.bucketCount; b++) {
156
- const lo = peaks.peaks[b * 2];
157
- const hi = peaks.peaks[b * 2 + 1];
158
- if (lo < min) min = lo;
159
- if (hi > max) max = hi;
160
- }
161
- let topY = mid + clampSample(min * visualGain) * mid * 0.95;
162
- let botY = mid + clampSample(max * visualGain) * mid * 0.95;
163
- if (
164
- Math.max(Math.abs(min), Math.abs(max)) > VISUAL_SILENCE_FLOOR &&
165
- botY - topY < 2
166
- ) {
167
- const centerY = (topY + botY) / 2;
168
- topY = centerY - 1;
169
- botY = centerY + 1;
208
+ // Canvas background & audio visualization
209
+ ctx.clearRect(0, 0, totalWidth, height);
210
+ if (!hasImagery) {
211
+ ctx.fillStyle = getWaveBg();
212
+ ctx.fillRect(0, 0, totalWidth, height);
213
+
214
+ const barWidth = 3;
215
+ const barGap = 1.5;
216
+ const step = barWidth + barGap;
217
+ const barCount = Math.floor(totalWidth / step);
218
+ const maxWaveHeight = Math.min(height * 0.5, 52);
219
+ const minBarHeight = 3;
220
+ const midY = height / 2;
221
+
222
+ if (peaks && hasPeaks) {
223
+ const visualGain = computeVisualGain(peaks.peaks);
224
+ const bucketsPerBar = peaks.bucketCount / barCount;
225
+
226
+ for (let i = 0; i < barCount; i++) {
227
+ const x = i * step;
228
+ const startBucket = Math.floor(i * bucketsPerBar);
229
+ const endBucket = Math.max(
230
+ startBucket + 1,
231
+ Math.floor((i + 1) * bucketsPerBar),
232
+ );
233
+
234
+ let maxAmp = 0;
235
+ for (
236
+ let b = startBucket;
237
+ b < endBucket && b < peaks.bucketCount;
238
+ b++
239
+ ) {
240
+ const lo = Math.abs(peaks.peaks[b * 2] ?? 0);
241
+ const hi = Math.abs(peaks.peaks[b * 2 + 1] ?? 0);
242
+ if (lo > maxAmp) maxAmp = lo;
243
+ if (hi > maxAmp) maxAmp = hi;
244
+ }
245
+
246
+ const scaledAmp = clampSample(maxAmp * visualGain);
247
+ const barHeight = Math.max(minBarHeight, scaledAmp * maxWaveHeight);
248
+ const topY = midY - barHeight / 2;
249
+
250
+ ctx.fillStyle =
251
+ maxAmp > VISUAL_SILENCE_FLOOR
252
+ ? getWaveColor()
253
+ : getBrandColorAlpha(0.2);
254
+
255
+ drawPillBar(ctx, x, topY, barWidth, barHeight);
170
256
  }
171
- ctx.moveTo(x + 0.5, topY);
172
- ctx.lineTo(x + 0.5, botY);
173
- }
174
- ctx.stroke();
175
- } else {
176
- const mid = height / 2;
177
- ctx.strokeStyle = getBrandColorAlpha(0.2);
178
- ctx.lineWidth = 1;
179
- ctx.setLineDash([3, 6]);
180
- ctx.beginPath();
181
- ctx.moveTo(0, mid);
182
- ctx.lineTo(totalWidth, mid);
183
- ctx.stroke();
184
- ctx.setLineDash([]);
185
-
186
- if (activityRanges.length) {
187
- const blockHeight = Math.max(18, Math.min(32, height * 0.32));
188
- const top = mid - blockHeight / 2;
189
- ctx.fillStyle = getBrandColorAlpha(0.24);
190
- for (const range of activityRanges) {
191
- const xStart =
192
- (Math.max(0, range.startMs) / Math.max(durationMs, 1)) * totalWidth;
193
- const xEnd =
194
- (Math.min(durationMs, range.endMs) / Math.max(durationMs, 1)) *
195
- totalWidth;
196
- ctx.fillRect(xStart, top, Math.max(2, xEnd - xStart), blockHeight);
257
+ } else {
258
+ // Idle state without imagery
259
+ for (let i = 0; i < barCount; i++) {
260
+ const x = i * step;
261
+ const barMs = (i / Math.max(1, barCount)) * durationMs;
262
+ const inActivity = activityRanges.some(
263
+ (r) => barMs >= r.startMs && barMs <= r.endMs,
264
+ );
265
+
266
+ const barHeight = inActivity ? 12 : minBarHeight;
267
+ const topY = midY - barHeight / 2;
268
+
269
+ ctx.fillStyle = inActivity ? getWaveColor() : getBrandColorAlpha(0.2);
270
+
271
+ drawPillBar(ctx, x, topY, barWidth, barHeight);
197
272
  }
198
273
  }
199
274
  }
@@ -248,6 +323,7 @@ export function Waveform({
248
323
  }
249
324
  }, [
250
325
  peaks,
326
+ hasImagery,
251
327
  totalWidth,
252
328
  height,
253
329
  excludedRanges,
@@ -257,15 +333,56 @@ export function Waveform({
257
333
  activityRanges,
258
334
  ]);
259
335
 
260
- const handleClick = (e: React.MouseEvent<HTMLDivElement>) => {
336
+ const scrubRef = useRef<{ pointerId: number; startX: number } | null>(null);
337
+
338
+ const seekToEvent = (
339
+ e: React.PointerEvent<HTMLDivElement> | React.MouseEvent<HTMLDivElement>, // i18n-ignore
340
+ ) => {
261
341
  if (!onSeek) return;
262
- const rect = e.currentTarget.getBoundingClientRect();
263
- const scroll = scrollRef.current?.scrollLeft ?? 0;
342
+ const el = scrollRef.current;
343
+ if (!el) return;
344
+ const rect = el.getBoundingClientRect();
345
+ const scroll = el.scrollLeft;
264
346
  const x = e.clientX - rect.left + scroll;
265
347
  const ms = Math.max(0, Math.min(durationMs, (x / totalWidth) * durationMs));
266
348
  onSeek(ms);
267
349
  };
268
350
 
351
+ const handlePointerDown = (e: React.PointerEvent<HTMLDivElement>) => {
352
+ if (!onSeek || e.button !== 0) return;
353
+ const el = scrollRef.current;
354
+ if (!el) return;
355
+ // A pointerdown on this element's own horizontal scrollbar still targets
356
+ // the element. Capturing there would turn the drag that pans a zoomed
357
+ // track into a scrub, leaving no way to reach the rest of the timeline.
358
+ if (e.clientY - el.getBoundingClientRect().top >= el.clientHeight) return;
359
+ scrubRef.current = { pointerId: e.pointerId, startX: e.clientX };
360
+ // Touch keeps the browser's native pan; a tap still seeks on pointerup.
361
+ if (e.pointerType === "touch") return;
362
+ el.setPointerCapture(e.pointerId);
363
+ seekToEvent(e);
364
+ };
365
+
366
+ const handlePointerMove = (e: React.PointerEvent<HTMLDivElement>) => {
367
+ const scrub = scrubRef.current;
368
+ if (!scrub || scrub.pointerId !== e.pointerId) return;
369
+ if (e.pointerType === "touch") return;
370
+ seekToEvent(e);
371
+ };
372
+
373
+ const handlePointerUp = (e: React.PointerEvent<HTMLDivElement>) => {
374
+ const scrub = scrubRef.current;
375
+ if (!scrub || scrub.pointerId !== e.pointerId) return;
376
+ scrubRef.current = null;
377
+ const el = scrollRef.current;
378
+ if (el?.hasPointerCapture(e.pointerId)) {
379
+ el.releasePointerCapture(e.pointerId);
380
+ }
381
+ if (e.pointerType === "touch" && Math.abs(e.clientX - scrub.startX) < 8) {
382
+ seekToEvent(e);
383
+ }
384
+ };
385
+
269
386
  const handleScroll = () => {
270
387
  const el = scrollRef.current;
271
388
  if (!el) return;
@@ -287,10 +404,55 @@ export function Waveform({
287
404
  )}
288
405
  style={{ width, height }}
289
406
  onScroll={handleScroll}
290
- onClick={handleClick}
407
+ onPointerDown={handlePointerDown}
408
+ onPointerMove={handlePointerMove}
409
+ onPointerUp={handlePointerUp}
410
+ onPointerCancel={handlePointerUp}
291
411
  >
292
412
  <div className="relative" style={{ width: totalWidth, height }}>
293
- <canvas ref={canvasRef} />
413
+ {spriteCells.length > 0 && sprite ? (
414
+ <div className="absolute inset-0 flex overflow-hidden pointer-events-none">
415
+ {spriteCells.map((cell) => (
416
+ <div
417
+ key={cell.key}
418
+ className="h-full animate-in fade-in duration-500"
419
+ style={{
420
+ width: `${100 / spriteCells.length}%`,
421
+ backgroundImage: `url(${sprite.url})`,
422
+ // Percentage sizing maps one grid cell onto one element box,
423
+ // so percentage positioning addresses cells exactly.
424
+ backgroundSize: `${sprite.columns * 100}% ${sprite.rows * 100}%`,
425
+ backgroundPosition: `${
426
+ sprite.columns > 1
427
+ ? (cell.column * 100) / (sprite.columns - 1)
428
+ : 0
429
+ }% ${
430
+ sprite.rows > 1 ? (cell.row * 100) / (sprite.rows - 1) : 0
431
+ }%`,
432
+ backgroundRepeat: "no-repeat",
433
+ }}
434
+ />
435
+ ))}
436
+ </div>
437
+ ) : (
438
+ frames.length > 0 && (
439
+ <div className="absolute inset-0 flex overflow-hidden pointer-events-none">
440
+ {frames.map((frame) => (
441
+ <img
442
+ key={frame.timeMs}
443
+ src={frame.dataUrl}
444
+ alt=""
445
+ className="h-full object-cover animate-in fade-in duration-500"
446
+ style={{ width: `${100 / frames.length}%` }}
447
+ />
448
+ ))}
449
+ </div>
450
+ )
451
+ )}
452
+ <canvas
453
+ ref={canvasRef}
454
+ className="absolute inset-0 pointer-events-none"
455
+ />
294
456
  <div
295
457
  className="absolute top-0 h-full w-[2px] pointer-events-none"
296
458
  style={{
@@ -540,6 +540,7 @@ export class RecorderEngine {
540
540
  */
541
541
  private uploadAbort: AbortController | null = null;
542
542
  private uploadMode: UploadMode = "buffered";
543
+ private uploadGenerationId: string | null = null;
543
544
  /**
544
545
  * Streaming-path buffer. MediaRecorder blobs accumulate here until at least
545
546
  * STREAM_CHUNK_BYTES is available, then a 256 KiB-aligned slice is PUT to API
@@ -1102,6 +1103,7 @@ export class RecorderEngine {
1102
1103
  this.opts.uploadUrl = target.uploadUrl;
1103
1104
  this.opts.abortUrl = target.abortUrl;
1104
1105
  this.opts.uploadMode = target.uploadMode ?? "buffered";
1106
+ this.uploadGenerationId = null;
1105
1107
  }
1106
1108
 
1107
1109
  // -------------------------------------------------------------------------
@@ -1178,6 +1180,7 @@ export class RecorderEngine {
1178
1180
  this.lastFinalizeMeta = null;
1179
1181
  this.uploadAbort = new AbortController();
1180
1182
  this.uploadMode = this.opts.uploadMode ?? "buffered";
1183
+ this.uploadGenerationId = null;
1181
1184
  this.pendingStreamBlobs = [];
1182
1185
  this.pendingStreamBytes = 0;
1183
1186
  this.cameraDisconnectNotified = false;
@@ -1540,6 +1543,10 @@ export class RecorderEngine {
1540
1543
  compression,
1541
1544
  requestStreaming: this.uploadMode === "streaming",
1542
1545
  mimeType: uploadMimeType,
1546
+ useGenerationFence: true,
1547
+ ...(this.uploadGenerationId
1548
+ ? { uploadGenerationId: this.uploadGenerationId }
1549
+ : {}),
1543
1550
  }),
1544
1551
  signal,
1545
1552
  });
@@ -1566,6 +1573,7 @@ export class RecorderEngine {
1566
1573
  }
1567
1574
  const reset = (await resetRes.json().catch(() => null)) as {
1568
1575
  uploadMode?: unknown;
1576
+ uploadGenerationId?: unknown;
1569
1577
  } | null;
1570
1578
  if (reset?.uploadMode !== "streaming" && reset?.uploadMode !== "buffered") {
1571
1579
  throw new Error(
@@ -1574,6 +1582,11 @@ export class RecorderEngine {
1574
1582
  }
1575
1583
  const uploadMode = reset.uploadMode;
1576
1584
  this.uploadMode = uploadMode;
1585
+ this.uploadGenerationId =
1586
+ typeof reset.uploadGenerationId === "string" &&
1587
+ reset.uploadGenerationId.length > 0
1588
+ ? reset.uploadGenerationId
1589
+ : null;
1577
1590
  return uploadMode;
1578
1591
  }
1579
1592
 
@@ -1752,8 +1765,10 @@ export class RecorderEngine {
1752
1765
  this.uploadAbort = null;
1753
1766
  }
1754
1767
  this.cleanupTracks();
1768
+ const uploadGenerationId = this.uploadGenerationId;
1755
1769
  this.chunkIndex = 0;
1756
1770
  this.uploadFailure = null;
1771
+ this.uploadGenerationId = null;
1757
1772
  this.startedAtMs = null;
1758
1773
  this.pausedAccumMs = 0;
1759
1774
  this.pausedStartedMs = null;
@@ -1764,7 +1779,13 @@ export class RecorderEngine {
1764
1779
 
1765
1780
  if (this.opts.abortUrl) {
1766
1781
  try {
1767
- await fetch(this.opts.abortUrl, { method: "POST" });
1782
+ await fetch(this.opts.abortUrl, {
1783
+ method: "POST",
1784
+ headers: { "Content-Type": "application/json" },
1785
+ body: JSON.stringify({
1786
+ ...(uploadGenerationId ? { uploadGenerationId } : {}),
1787
+ }),
1788
+ });
1768
1789
  } catch {
1769
1790
  // ignore — best effort
1770
1791
  }
@@ -2136,6 +2157,7 @@ export class RecorderEngine {
2136
2157
  height: extra.height,
2137
2158
  hasAudio: extra.hasAudio,
2138
2159
  hasCamera: extra.hasCamera,
2160
+ uploadGenerationId: this.uploadGenerationId ?? undefined,
2139
2161
  });
2140
2162
 
2141
2163
  const body = await blob.arrayBuffer();