@agent-native/core 0.90.11 → 0.91.1

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 (322) hide show
  1. package/corpus/README.md +2 -2
  2. package/corpus/core/CHANGELOG.md +34 -0
  3. package/corpus/core/docs/content/toolkit-settings.mdx +56 -3
  4. package/corpus/core/package.json +2 -1
  5. package/corpus/core/src/agent/run-ownership.ts +29 -1
  6. package/corpus/core/src/changelog/parse.ts +78 -0
  7. package/corpus/core/src/chat-threads/schema.ts +43 -0
  8. package/corpus/core/src/chat-threads/store.ts +144 -12
  9. package/corpus/core/src/cli/design-connect.ts +53 -9
  10. package/corpus/core/src/cli/workspace-dev.ts +25 -5
  11. package/corpus/core/src/client/AgentPanel.tsx +79 -14
  12. package/corpus/core/src/client/AssistantChat.tsx +359 -425
  13. package/corpus/core/src/client/analytics-session.ts +70 -0
  14. package/corpus/core/src/client/analytics.ts +175 -49
  15. package/corpus/core/src/client/chat/message-components.tsx +22 -2
  16. package/corpus/core/src/client/components/ui/message-scroller.tsx +131 -0
  17. package/corpus/core/src/client/conversation/AgentConversation.tsx +63 -41
  18. package/corpus/core/src/client/error-capture.ts +616 -0
  19. package/corpus/core/src/client/index.ts +12 -0
  20. package/corpus/core/src/client/session-replay.ts +57 -7
  21. package/corpus/core/src/client/settings/SettingsPanel.tsx +123 -1
  22. package/corpus/core/src/client/settings/SettingsTabsPage.tsx +292 -45
  23. package/corpus/core/src/client/settings/index.ts +1 -0
  24. package/corpus/core/src/client/sharing/ShareButton.tsx +14 -11
  25. package/corpus/core/src/client/sharing/ShareDialog.tsx +13 -10
  26. package/corpus/core/src/mcp/builtin-tools.ts +29 -2
  27. package/corpus/core/src/server/agent-chat-plugin.ts +174 -79
  28. package/corpus/core/src/server/onboarding-html.ts +28 -16
  29. package/corpus/core/src/sharing/actions/list-resource-shares.ts +45 -1
  30. package/corpus/core/src/sharing/actions/share-resource.ts +9 -0
  31. package/corpus/core/src/templates/workspace-core/.agents/skills/agent-native-toolkit/SKILL.md +4 -2
  32. package/corpus/core/src/templates/workspace-core/.agents/skills/changelog/SKILL.md +9 -6
  33. package/corpus/core/src/vite/client.ts +217 -35
  34. package/corpus/templates/analytics/.agents/skills/adhoc-analysis/SKILL.md +8 -7
  35. package/corpus/templates/analytics/.agents/skills/analysis-workspace/SKILL.md +14 -10
  36. package/corpus/templates/analytics/.agents/skills/bigquery/SKILL.md +15 -15
  37. package/corpus/templates/analytics/.agents/skills/cross-source-analysis/SKILL.md +1 -0
  38. package/corpus/templates/analytics/.agents/skills/dashboard-management/SKILL.md +38 -22
  39. package/corpus/templates/analytics/.agents/skills/data-programs/SKILL.md +114 -61
  40. package/corpus/templates/analytics/.agents/skills/data-querying/SKILL.md +1 -0
  41. package/corpus/templates/analytics/.agents/skills/gong/SKILL.md +7 -7
  42. package/corpus/templates/analytics/.agents/skills/hubspot/SKILL.md +1 -0
  43. package/corpus/templates/analytics/.agents/skills/prometheus/SKILL.md +1 -1
  44. package/corpus/templates/analytics/.builder/skills/bigquery/SKILL.md +17 -17
  45. package/corpus/templates/analytics/.builder/skills/gcloud/SKILL.md +5 -5
  46. package/corpus/templates/analytics/.builder/skills/github/SKILL.md +7 -7
  47. package/corpus/templates/analytics/.builder/skills/gong/SKILL.md +12 -12
  48. package/corpus/templates/analytics/.builder/skills/grafana/SKILL.md +7 -7
  49. package/corpus/templates/analytics/.builder/skills/sentry/SKILL.md +7 -7
  50. package/corpus/templates/analytics/.builder/skills/slack/SKILL.md +6 -6
  51. package/corpus/templates/analytics/.builder/skills/stripe/SKILL.md +7 -7
  52. package/corpus/templates/analytics/AGENTS.md +47 -2
  53. package/corpus/templates/analytics/actions/add-status-page-monitor.ts +36 -0
  54. package/corpus/templates/analytics/actions/capture-test-error.ts +33 -0
  55. package/corpus/templates/analytics/actions/delete-monitor.ts +24 -0
  56. package/corpus/templates/analytics/actions/delete-status-page.ts +23 -0
  57. package/corpus/templates/analytics/actions/get-error-issue.ts +36 -0
  58. package/corpus/templates/analytics/actions/get-monitor-stats.ts +55 -0
  59. package/corpus/templates/analytics/actions/get-monitor.ts +27 -0
  60. package/corpus/templates/analytics/actions/get-public-status-page.ts +41 -0
  61. package/corpus/templates/analytics/actions/get-status-page.ts +29 -0
  62. package/corpus/templates/analytics/actions/list-error-issues.ts +40 -0
  63. package/corpus/templates/analytics/actions/list-monitors.ts +21 -0
  64. package/corpus/templates/analytics/actions/list-status-pages.ts +21 -0
  65. package/corpus/templates/analytics/actions/match-error-issues.ts +51 -0
  66. package/corpus/templates/analytics/actions/navigate.ts +58 -4
  67. package/corpus/templates/analytics/actions/remove-status-page-monitor.ts +22 -0
  68. package/corpus/templates/analytics/actions/reorder-status-page-monitors.ts +25 -0
  69. package/corpus/templates/analytics/actions/resolve-error-issue.ts +38 -0
  70. package/corpus/templates/analytics/actions/run-monitor-check.ts +23 -0
  71. package/corpus/templates/analytics/actions/save-analysis.ts +1 -0
  72. package/corpus/templates/analytics/actions/save-monitor.ts +145 -0
  73. package/corpus/templates/analytics/actions/save-status-page.ts +47 -0
  74. package/corpus/templates/analytics/actions/view-screen.ts +212 -0
  75. package/corpus/templates/analytics/app/components/layout/Header.tsx +1 -0
  76. package/corpus/templates/analytics/app/components/layout/Layout.tsx +10 -1
  77. package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +134 -39
  78. package/corpus/templates/analytics/app/components/monitoring/PublicStatusView.tsx +351 -0
  79. package/corpus/templates/analytics/app/components/monitoring/ResponseTimeChart.tsx +174 -0
  80. package/corpus/templates/analytics/app/components/monitoring/UptimeStatCards.tsx +85 -0
  81. package/corpus/templates/analytics/app/components/monitoring/UptimeTimelineBars.tsx +106 -0
  82. package/corpus/templates/analytics/app/components/monitoring/chart-utils.ts +101 -0
  83. package/corpus/templates/analytics/app/components/monitoring/index.ts +28 -0
  84. package/corpus/templates/analytics/app/components/monitoring/types.ts +42 -0
  85. package/corpus/templates/analytics/app/hooks/use-navigation-state.ts +34 -0
  86. package/corpus/templates/analytics/app/i18n/zh-TW.ts +6 -0
  87. package/corpus/templates/analytics/app/i18n-data.ts +33 -0
  88. package/corpus/templates/analytics/app/pages/DataSources.tsx +28 -0
  89. package/corpus/templates/analytics/app/pages/Settings.tsx +77 -9
  90. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/SqlChartCard.tsx +3 -3
  91. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/dashboard-layout.ts +7 -0
  92. package/corpus/templates/analytics/app/pages/monitoring/ErrorsPanel.tsx +205 -0
  93. package/corpus/templates/analytics/app/pages/monitoring/MonitoringPage.tsx +100 -0
  94. package/corpus/templates/analytics/app/pages/monitoring/UptimePanel.tsx +475 -0
  95. package/corpus/templates/analytics/app/pages/monitoring/errors/IssueDetail.tsx +372 -0
  96. package/corpus/templates/analytics/app/pages/monitoring/errors/IssueList.tsx +360 -0
  97. package/corpus/templates/analytics/app/pages/monitoring/errors/Sparkline.tsx +55 -0
  98. package/corpus/templates/analytics/app/pages/monitoring/errors/i18n.ts +106 -0
  99. package/corpus/templates/analytics/app/pages/monitoring/errors/types.ts +75 -0
  100. package/corpus/templates/analytics/app/pages/monitoring/errors/utils.ts +132 -0
  101. package/corpus/templates/analytics/app/pages/monitoring/uptime/MonitorDetail.tsx +535 -0
  102. package/corpus/templates/analytics/app/pages/monitoring/uptime/MonitorFormPage.tsx +1026 -0
  103. package/corpus/templates/analytics/app/pages/monitoring/uptime/MonitorList.tsx +311 -0
  104. package/corpus/templates/analytics/app/pages/monitoring/uptime/i18n.ts +235 -0
  105. package/corpus/templates/analytics/app/pages/monitoring/uptime/status-pages/StatusPageEditor.tsx +661 -0
  106. package/corpus/templates/analytics/app/pages/monitoring/uptime/status-pages/StatusPagesView.tsx +355 -0
  107. package/corpus/templates/analytics/app/pages/monitoring/uptime/status-pages/i18n.ts +106 -0
  108. package/corpus/templates/analytics/app/pages/monitoring/uptime/status-pages/slug.ts +24 -0
  109. package/corpus/templates/analytics/app/pages/monitoring/uptime/status-pages/types.ts +20 -0
  110. package/corpus/templates/analytics/app/pages/monitoring/uptime/status-summary.ts +87 -0
  111. package/corpus/templates/analytics/app/pages/monitoring/uptime/types.ts +161 -0
  112. package/corpus/templates/analytics/app/pages/monitoring/uptime/utils.ts +210 -0
  113. package/corpus/templates/analytics/app/pages/sessions/SessionDetailPage.tsx +76 -1
  114. package/corpus/templates/analytics/app/pages/sessions/SessionDevToolsPanel.tsx +74 -9
  115. package/corpus/templates/analytics/app/pages/sessions/SessionsPage.tsx +197 -20
  116. package/corpus/templates/analytics/app/root.tsx +33 -1
  117. package/corpus/templates/analytics/app/routes/monitoring._index.tsx +10 -0
  118. package/corpus/templates/analytics/app/routes/status.$slug.tsx +118 -0
  119. package/corpus/templates/analytics/changelog/2026-07-08-add-uptime-monitors-that-ping-your-urls-and-alert-you-when-t.md +6 -0
  120. package/corpus/templates/analytics/changelog/2026-07-08-analytics-chats-keep-large-data-dictionaries-compact-so-prov.md +6 -0
  121. package/corpus/templates/analytics/changelog/2026-07-08-create-public-status-pages-that-share-the-live-health-of-cho.md +6 -0
  122. package/corpus/templates/analytics/changelog/2026-07-08-dashboard-extension-panels-use-clearer-open-copy.md +6 -0
  123. package/corpus/templates/analytics/changelog/2026-07-08-error-capture.md +6 -0
  124. package/corpus/templates/analytics/changelog/2026-07-08-fixed-single-panel-dashboard-drags-so-they-no-longer-rewrite.md +6 -0
  125. package/corpus/templates/analytics/changelog/2026-07-08-jump-straight-from-an-error-in-a-session-recording-to-its-fu.md +6 -0
  126. package/corpus/templates/analytics/changelog/2026-07-08-monitor-detail-and-list-now-show-colorful-uptime-timelines-r.md +6 -0
  127. package/corpus/templates/analytics/changelog/2026-07-08-pinned-ask-chats-now-show-a-pin-icon-in-the-sidebar.md +6 -0
  128. package/corpus/templates/analytics/changelog/2026-07-08-publish-a-public-uptime-status-page-with-colorful-uptime-tim.md +6 -0
  129. package/corpus/templates/analytics/changelog/2026-07-08-sessions-now-hide-visitor-emails-in-demo-mode-and-show-clean.md +6 -0
  130. package/corpus/templates/analytics/changelog/2026-07-08-settings-are-cleaner-searchable-and-alerts-have-their-own-tab.md +5 -0
  131. package/corpus/templates/analytics/changelog/2026-07-08-sidebar-sections-stay-collapsed-until-opened.md +5 -0
  132. package/corpus/templates/analytics/changelog/2026-07-08-the-uptime-monitor-list-now-has-a-current-status-overview-wi.md +6 -0
  133. package/corpus/templates/analytics/docs/error-capture.md +172 -0
  134. package/corpus/templates/analytics/docs/uptime-monitoring.md +150 -0
  135. package/corpus/templates/analytics/server/db/schema-errors.ts +150 -0
  136. package/corpus/templates/analytics/server/db/schema-monitoring.ts +177 -0
  137. package/corpus/templates/analytics/server/db/schema.ts +6 -0
  138. package/corpus/templates/analytics/server/jobs/uptime-monitors.ts +123 -0
  139. package/corpus/templates/analytics/server/lib/agent-chat-plan-mode.ts +41 -0
  140. package/corpus/templates/analytics/server/lib/data-dictionary-context.ts +55 -12
  141. package/corpus/templates/analytics/server/lib/error-capture.ts +1331 -0
  142. package/corpus/templates/analytics/server/lib/first-party-analytics.ts +47 -1
  143. package/corpus/templates/analytics/server/lib/monitor-stats.ts +494 -0
  144. package/corpus/templates/analytics/server/lib/status-pages.ts +798 -0
  145. package/corpus/templates/analytics/server/lib/uptime-monitors.ts +1665 -0
  146. package/corpus/templates/analytics/server/plugins/agent-chat.ts +7 -37
  147. package/corpus/templates/analytics/server/plugins/auth.ts +11 -1
  148. package/corpus/templates/analytics/server/plugins/db.ts +408 -0
  149. package/corpus/templates/analytics/server/plugins/uptime-monitor-jobs.ts +63 -0
  150. package/corpus/templates/assets/app/routes/_index.tsx +1 -1
  151. package/corpus/templates/assets/app/routes/settings.tsx +22 -1
  152. package/corpus/templates/assets/changelog/2026-07-08-recent-drafts-keep-comfortable-side-padding.md +6 -0
  153. package/corpus/templates/assets/changelog/2026-07-08-settings-are-cleaner-and-searchable.md +5 -0
  154. package/corpus/templates/brain/app/routes/settings.tsx +43 -5
  155. package/corpus/templates/brain/changelog/2026-07-08-settings-are-cleaner-and-searchable.md +5 -0
  156. package/corpus/templates/calendar/app/pages/Settings.tsx +44 -6
  157. package/corpus/templates/calendar/changelog/2026-07-08-settings-are-cleaner-and-searchable.md +5 -0
  158. package/corpus/templates/chat/app/routes/settings.tsx +16 -1
  159. package/corpus/templates/chat/changelog/2026-07-08-settings-are-cleaner-and-searchable.md +5 -0
  160. package/corpus/templates/clips/actions/lib/audio-only-transcription.ts +79 -37
  161. package/corpus/templates/clips/app/components/player/player-controls.tsx +35 -0
  162. package/corpus/templates/clips/app/components/player/video-player.tsx +162 -18
  163. package/corpus/templates/clips/app/routes/_app.settings._index.tsx +63 -6
  164. package/corpus/templates/clips/app/routes/record.tsx +18 -1
  165. package/corpus/templates/clips/changelog/2026-07-07-shared-clips-start-playback-reliably-from-the-first-play-click.md +6 -0
  166. package/corpus/templates/clips/changelog/2026-07-08-chrome-extension-camera-and-microphone-menus-now-show-which-.md +6 -0
  167. package/corpus/templates/clips/changelog/2026-07-08-chrome-extension-copies-new-clip-links-after-saving.md +6 -0
  168. package/corpus/templates/clips/changelog/2026-07-08-chrome-extension-recordings-now-continue-after-granting-came.md +6 -0
  169. package/corpus/templates/clips/changelog/2026-07-08-mobile-shared-clips-now-have-loom-style-tap-and-skip-controls.md +6 -0
  170. package/corpus/templates/clips/changelog/2026-07-08-recordings-and-uploads-copy-links-after-saving.md +6 -0
  171. package/corpus/templates/clips/changelog/2026-07-08-settings-are-cleaner-searchable-and-collapse-s3-with-builder.md +5 -0
  172. package/corpus/templates/clips/changelog/2026-07-08-transcripts-are-more-reliable-for-longer-desktop-recordings-.md +6 -0
  173. package/corpus/templates/clips/chrome-extension/public/manifest.json +1 -0
  174. package/corpus/templates/clips/chrome-extension/src/background.ts +162 -13
  175. package/corpus/templates/clips/chrome-extension/src/content-script.ts +14 -0
  176. package/corpus/templates/clips/chrome-extension/src/offscreen.ts +36 -0
  177. package/corpus/templates/clips/chrome-extension/src/overlay.ts +99 -25
  178. package/corpus/templates/clips/chrome-extension/src/permission.ts +63 -14
  179. package/corpus/templates/clips/chrome-extension/src/popup.ts +123 -23
  180. package/corpus/templates/content/actions/_builder-cms-read-client.ts +47 -0
  181. package/corpus/templates/content/actions/_database-source-utils.ts +33 -4
  182. package/corpus/templates/content/actions/add-content-database-source-field-property.ts +214 -29
  183. package/corpus/templates/content/actions/attach-content-database-source.ts +3 -0
  184. package/corpus/templates/content/actions/change-content-database-source-role.ts +1 -0
  185. package/corpus/templates/content/app/components/editor/DocumentProperties.tsx +42 -32
  186. package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +352 -81
  187. package/corpus/templates/content/app/components/editor/database/settings.tsx +49 -24
  188. package/corpus/templates/content/app/routes/_app.settings.tsx +19 -1
  189. package/corpus/templates/content/changelog/2026-07-07-builder-source-tag-fields-now-import-as-multi-select.md +6 -0
  190. package/corpus/templates/content/changelog/2026-07-08-settings-are-cleaner-and-searchable.md +5 -0
  191. package/corpus/templates/content/shared/api.ts +3 -0
  192. package/corpus/templates/content/shared/properties.ts +42 -0
  193. package/corpus/templates/design/AGENTS.md +9 -0
  194. package/corpus/templates/design/actions/generate-design.ts +3 -0
  195. package/corpus/templates/design/actions/get-design-system.ts +147 -1
  196. package/corpus/templates/design/actions/request-localhost-write-consent.ts +141 -0
  197. package/corpus/templates/design/app/components/design/DesignCanvas.tsx +13 -19
  198. package/corpus/templates/design/app/routes/settings.tsx +16 -1
  199. package/corpus/templates/design/changelog/2026-07-08-design-generation-now-follows-hydrated-builder-design-system.md +6 -0
  200. package/corpus/templates/design/changelog/2026-07-08-settings-are-cleaner-and-searchable.md +5 -0
  201. package/corpus/templates/design/server/lib/verify-write-grant.ts +4 -2
  202. package/corpus/templates/dispatch/app/routes/settings.tsx +23 -2
  203. package/corpus/templates/dispatch/changelog/2026-07-08-settings-are-cleaner-and-searchable.md +5 -0
  204. package/corpus/templates/forms/app/routes/_app.settings.tsx +16 -1
  205. package/corpus/templates/forms/changelog/2026-07-08-settings-are-cleaner-and-searchable.md +5 -0
  206. package/corpus/templates/macros/app/routes/settings.tsx +16 -1
  207. package/corpus/templates/macros/changelog/2026-07-08-settings-are-cleaner-and-searchable.md +5 -0
  208. package/corpus/templates/mail/app/components/settings/GmailFiltersSection.tsx +1 -1
  209. package/corpus/templates/mail/app/components/settings/SnippetsSection.tsx +1 -1
  210. package/corpus/templates/mail/app/pages/SettingsPage.tsx +123 -105
  211. package/corpus/templates/mail/changelog/2026-07-08-settings-redesigned-with-a-consistent-searchable-layout.md +5 -0
  212. package/corpus/templates/plan/app/routes/settings.tsx +23 -2
  213. package/corpus/templates/plan/changelog/2026-07-08-settings-are-cleaner-and-searchable.md +5 -0
  214. package/corpus/templates/slides/actions/add-slide.ts +1 -0
  215. package/corpus/templates/slides/actions/get-design-system.ts +147 -1
  216. package/corpus/templates/slides/app/pages/Index.tsx +46 -1
  217. package/corpus/templates/slides/app/routes/settings.tsx +16 -1
  218. package/corpus/templates/slides/changelog/2026-07-08-deck-generation-now-follows-hydrated-builder-design-system-g.md +6 -0
  219. package/corpus/templates/slides/changelog/2026-07-08-settings-are-cleaner-and-searchable.md +5 -0
  220. package/dist/agent/run-ownership.d.ts +6 -0
  221. package/dist/agent/run-ownership.d.ts.map +1 -1
  222. package/dist/agent/run-ownership.js +11 -1
  223. package/dist/agent/run-ownership.js.map +1 -1
  224. package/dist/changelog/parse.d.ts +7 -0
  225. package/dist/changelog/parse.d.ts.map +1 -1
  226. package/dist/changelog/parse.js +64 -0
  227. package/dist/changelog/parse.js.map +1 -1
  228. package/dist/chat-threads/schema.d.ts +445 -0
  229. package/dist/chat-threads/schema.d.ts.map +1 -0
  230. package/dist/chat-threads/schema.js +39 -0
  231. package/dist/chat-threads/schema.js.map +1 -0
  232. package/dist/chat-threads/store.d.ts +12 -0
  233. package/dist/chat-threads/store.d.ts.map +1 -1
  234. package/dist/chat-threads/store.js +99 -10
  235. package/dist/chat-threads/store.js.map +1 -1
  236. package/dist/cli/design-connect.d.ts.map +1 -1
  237. package/dist/cli/design-connect.js +40 -10
  238. package/dist/cli/design-connect.js.map +1 -1
  239. package/dist/cli/workspace-dev.d.ts.map +1 -1
  240. package/dist/cli/workspace-dev.js +20 -5
  241. package/dist/cli/workspace-dev.js.map +1 -1
  242. package/dist/client/AgentPanel.d.ts.map +1 -1
  243. package/dist/client/AgentPanel.js +35 -7
  244. package/dist/client/AgentPanel.js.map +1 -1
  245. package/dist/client/AssistantChat.d.ts.map +1 -1
  246. package/dist/client/AssistantChat.js +105 -183
  247. package/dist/client/AssistantChat.js.map +1 -1
  248. package/dist/client/analytics-session.d.ts +3 -0
  249. package/dist/client/analytics-session.d.ts.map +1 -0
  250. package/dist/client/analytics-session.js +66 -0
  251. package/dist/client/analytics-session.js.map +1 -0
  252. package/dist/client/analytics.d.ts +32 -0
  253. package/dist/client/analytics.d.ts.map +1 -1
  254. package/dist/client/analytics.js +116 -46
  255. package/dist/client/analytics.js.map +1 -1
  256. package/dist/client/chat/message-components.d.ts +2 -1
  257. package/dist/client/chat/message-components.d.ts.map +1 -1
  258. package/dist/client/chat/message-components.js +16 -6
  259. package/dist/client/chat/message-components.js.map +1 -1
  260. package/dist/client/components/ui/message-scroller.d.ts +16 -0
  261. package/dist/client/components/ui/message-scroller.d.ts.map +1 -0
  262. package/dist/client/components/ui/message-scroller.js +25 -0
  263. package/dist/client/components/ui/message-scroller.js.map +1 -0
  264. package/dist/client/conversation/AgentConversation.d.ts +1 -1
  265. package/dist/client/conversation/AgentConversation.d.ts.map +1 -1
  266. package/dist/client/conversation/AgentConversation.js +3 -8
  267. package/dist/client/conversation/AgentConversation.js.map +1 -1
  268. package/dist/client/error-capture.d.ts +99 -0
  269. package/dist/client/error-capture.d.ts.map +1 -0
  270. package/dist/client/error-capture.js +437 -0
  271. package/dist/client/error-capture.js.map +1 -0
  272. package/dist/client/index.d.ts +2 -2
  273. package/dist/client/index.d.ts.map +1 -1
  274. package/dist/client/index.js +3 -1
  275. package/dist/client/index.js.map +1 -1
  276. package/dist/client/session-replay.d.ts +22 -0
  277. package/dist/client/session-replay.d.ts.map +1 -1
  278. package/dist/client/session-replay.js +45 -4
  279. package/dist/client/session-replay.js.map +1 -1
  280. package/dist/client/settings/SettingsPanel.d.ts.map +1 -1
  281. package/dist/client/settings/SettingsPanel.js +100 -5
  282. package/dist/client/settings/SettingsPanel.js.map +1 -1
  283. package/dist/client/settings/SettingsTabsPage.d.ts +49 -1
  284. package/dist/client/settings/SettingsTabsPage.d.ts.map +1 -1
  285. package/dist/client/settings/SettingsTabsPage.js +131 -19
  286. package/dist/client/settings/SettingsTabsPage.js.map +1 -1
  287. package/dist/client/settings/index.d.ts +1 -1
  288. package/dist/client/settings/index.d.ts.map +1 -1
  289. package/dist/client/settings/index.js.map +1 -1
  290. package/dist/client/sharing/ShareButton.d.ts.map +1 -1
  291. package/dist/client/sharing/ShareButton.js +13 -8
  292. package/dist/client/sharing/ShareButton.js.map +1 -1
  293. package/dist/client/sharing/ShareDialog.d.ts.map +1 -1
  294. package/dist/client/sharing/ShareDialog.js +12 -7
  295. package/dist/client/sharing/ShareDialog.js.map +1 -1
  296. package/dist/collab/routes.d.ts +1 -1
  297. package/dist/file-upload/actions/upload-image.d.ts +1 -1
  298. package/dist/mcp/builtin-tools.d.ts.map +1 -1
  299. package/dist/mcp/builtin-tools.js +21 -2
  300. package/dist/mcp/builtin-tools.js.map +1 -1
  301. package/dist/observability/routes.d.ts +1 -1
  302. package/dist/org/schema.d.ts +15 -15
  303. package/dist/provider-api/corpus-jobs.d.ts +2 -2
  304. package/dist/server/agent-chat-plugin.d.ts.map +1 -1
  305. package/dist/server/agent-chat-plugin.js +109 -73
  306. package/dist/server/agent-chat-plugin.js.map +1 -1
  307. package/dist/server/onboarding-html.d.ts.map +1 -1
  308. package/dist/server/onboarding-html.js +28 -16
  309. package/dist/server/onboarding-html.js.map +1 -1
  310. package/dist/sharing/actions/list-resource-shares.js +30 -1
  311. package/dist/sharing/actions/list-resource-shares.js.map +1 -1
  312. package/dist/sharing/actions/share-resource.js +6 -0
  313. package/dist/sharing/actions/share-resource.js.map +1 -1
  314. package/dist/templates/workspace-core/.agents/skills/agent-native-toolkit/SKILL.md +4 -2
  315. package/dist/templates/workspace-core/.agents/skills/changelog/SKILL.md +9 -6
  316. package/dist/vite/client.d.ts.map +1 -1
  317. package/dist/vite/client.js +192 -30
  318. package/dist/vite/client.js.map +1 -1
  319. package/docs/content/toolkit-settings.mdx +56 -3
  320. package/package.json +2 -1
  321. package/src/templates/workspace-core/.agents/skills/agent-native-toolkit/SKILL.md +4 -2
  322. package/src/templates/workspace-core/.agents/skills/changelog/SKILL.md +9 -6
@@ -6,6 +6,7 @@ import {
6
6
  useActionMutation,
7
7
  useActionQuery,
8
8
  useT,
9
+ type SettingsSearchEntry,
9
10
  } from "@agent-native/core/client";
10
11
  import { TeamPage } from "@agent-native/core/client/org";
11
12
  import {
@@ -120,6 +121,42 @@ export default function SettingsRoute() {
120
121
  setSettings((current) => ({ ...current, [key]: value }));
121
122
  }
122
123
 
124
+ const generalSearchEntries = useMemo<SettingsSearchEntry[]>(
125
+ () => [
126
+ {
127
+ id: "brain-identity",
128
+ label: t("settings.identityTitle"),
129
+ keywords: "identity company name assistant name",
130
+ hash: "identity",
131
+ },
132
+ {
133
+ id: "brain-behavior",
134
+ label: t("settings.assistantBehaviorTitle"),
135
+ keywords: "assistant behavior tone source policy instructions",
136
+ hash: "assistant-behavior",
137
+ },
138
+ {
139
+ id: "brain-publishing",
140
+ label: t("settings.publishingReviewTitle"),
141
+ keywords: "publishing review publish tier approval connector poll",
142
+ hash: "publishing-review",
143
+ },
144
+ {
145
+ id: "brain-safety",
146
+ label: t("settings.safetyEvidenceTitle"),
147
+ keywords: "safety evidence sanitize redact citations sources",
148
+ hash: "safety-evidence",
149
+ },
150
+ {
151
+ id: "brain-language",
152
+ label: t("settings.languageTitle"),
153
+ keywords: "language locale translation i18n",
154
+ hash: "language",
155
+ },
156
+ ],
157
+ [t],
158
+ );
159
+
123
160
  return (
124
161
  <div className="min-h-full bg-background">
125
162
  <PageHeader
@@ -146,10 +183,11 @@ export default function SettingsRoute() {
146
183
  <SettingsTabsPage
147
184
  teamLabel={t("team.title")}
148
185
  extraTabs={agentSettingsTabs}
186
+ generalSearchEntries={generalSearchEntries}
149
187
  general={
150
188
  <div className="brain-settings-general-grid grid gap-5">
151
189
  <main className="grid gap-5">
152
- <Card>
190
+ <Card id="identity" className="scroll-mt-4">
153
191
  <CardHeader>
154
192
  <CardTitle className="flex items-center gap-2 text-base">
155
193
  <IconUsersGroup className="size-4 text-primary" />
@@ -177,7 +215,7 @@ export default function SettingsRoute() {
177
215
  </CardContent>
178
216
  </Card>
179
217
 
180
- <Card>
218
+ <Card id="assistant-behavior" className="scroll-mt-4">
181
219
  <CardHeader>
182
220
  <CardTitle className="flex items-center gap-2 text-base">
183
221
  <IconMessageCircle className="size-4 text-primary" />
@@ -235,7 +273,7 @@ export default function SettingsRoute() {
235
273
  </CardContent>
236
274
  </Card>
237
275
 
238
- <Card>
276
+ <Card id="publishing-review" className="scroll-mt-4">
239
277
  <CardHeader>
240
278
  <CardTitle className="flex items-center gap-2 text-base">
241
279
  <IconAdjustments className="size-4 text-primary" />
@@ -321,7 +359,7 @@ export default function SettingsRoute() {
321
359
  </CardContent>
322
360
  </Card>
323
361
 
324
- <Card>
362
+ <Card id="safety-evidence" className="scroll-mt-4">
325
363
  <CardHeader>
326
364
  <CardTitle className="flex items-center gap-2 text-base">
327
365
  <IconShieldCheck className="size-4 text-primary" />
@@ -406,7 +444,7 @@ export default function SettingsRoute() {
406
444
  </main>
407
445
 
408
446
  <aside className="grid content-start gap-5">
409
- <Card>
447
+ <Card id="language" className="scroll-mt-4">
410
448
  <CardHeader>
411
449
  <CardTitle className="flex items-center gap-2 text-base">
412
450
  <IconAdjustments className="size-4 text-primary" />
@@ -0,0 +1,5 @@
1
+ ---
2
+ type: improved
3
+ ---
4
+
5
+ Settings are cleaner and searchable, with a consistent navigation that jumps straight to any setting.
@@ -6,6 +6,7 @@ import {
6
6
  SettingsTabsPage,
7
7
  useAgentSettingsTabs,
8
8
  type AppearancePresetId,
9
+ type SettingsSearchEntry,
9
10
  useT,
10
11
  } from "@agent-native/core/client";
11
12
  import { TeamPage } from "@agent-native/core/client/org";
@@ -17,7 +18,7 @@ import {
17
18
  IconCircleCheck,
18
19
  IconCircleX,
19
20
  } from "@tabler/icons-react";
20
- import { useState, useEffect } from "react";
21
+ import { useState, useEffect, useMemo } from "react";
21
22
  import { Link } from "react-router";
22
23
  import { toast } from "sonner";
23
24
 
@@ -157,18 +158,55 @@ export default function Settings() {
157
158
  });
158
159
  }
159
160
 
161
+ const generalSearchEntries = useMemo<SettingsSearchEntry[]>(
162
+ () => [
163
+ {
164
+ id: "calendar-language",
165
+ label: t("settings.languageTitle"),
166
+ keywords: "language locale translation i18n",
167
+ hash: "language",
168
+ },
169
+ {
170
+ id: "calendar-google",
171
+ label: t("settings.googleCalendar"),
172
+ keywords: "google calendar connect oauth sync account",
173
+ hash: "google-calendar",
174
+ },
175
+ {
176
+ id: "calendar-zoom",
177
+ label: "Zoom",
178
+ keywords: "zoom meeting video conferencing connect",
179
+ hash: "zoom",
180
+ },
181
+ {
182
+ id: "calendar-general",
183
+ label: t("settings.general"),
184
+ keywords: "timezone booking duration defaults general",
185
+ hash: "general-settings",
186
+ },
187
+ {
188
+ id: "calendar-appearance",
189
+ label: t("settings.appearance"),
190
+ keywords: "appearance theme color mode dark light",
191
+ hash: "appearance",
192
+ },
193
+ ],
194
+ [t],
195
+ );
196
+
160
197
  return (
161
198
  <SettingsTabsPage
162
199
  generalLabel={t("settings.general")}
163
200
  teamLabel={t("navigation.team")}
164
201
  extraTabs={agentSettingsTabs}
202
+ generalSearchEntries={generalSearchEntries}
165
203
  general={
166
204
  <div className="mx-auto max-w-2xl space-y-6 pb-12">
167
205
  <p className="text-sm text-muted-foreground">
168
206
  {t("settings.description")}
169
207
  </p>
170
208
 
171
- <Card>
209
+ <Card id="language" className="scroll-mt-16">
172
210
  <CardHeader>
173
211
  <CardTitle className="text-lg">
174
212
  {t("settings.languageTitle")}
@@ -184,7 +222,7 @@ export default function Settings() {
184
222
  </Card>
185
223
 
186
224
  {/* Google Calendar Connection */}
187
- <Card>
225
+ <Card id="google-calendar" className="scroll-mt-16">
188
226
  <CardHeader>
189
227
  <CardTitle className="text-lg">
190
228
  {t("settings.googleCalendar")}
@@ -250,7 +288,7 @@ export default function Settings() {
250
288
  </CardContent>
251
289
  </Card>
252
290
 
253
- <Card>
291
+ <Card id="zoom" className="scroll-mt-16">
254
292
  <CardHeader>
255
293
  <CardTitle className="text-lg">Zoom</CardTitle>
256
294
  <CardDescription>{t("settings.zoomDescription")}</CardDescription>
@@ -340,7 +378,7 @@ export default function Settings() {
340
378
  <Separator />
341
379
 
342
380
  {/* General Settings */}
343
- <Card>
381
+ <Card id="general-settings" className="scroll-mt-16">
344
382
  <CardHeader>
345
383
  <CardTitle className="text-lg">{t("settings.general")}</CardTitle>
346
384
  <CardDescription>
@@ -421,7 +459,7 @@ export default function Settings() {
421
459
  </Card>
422
460
 
423
461
  {/* Appearance */}
424
- <Card>
462
+ <Card id="appearance" className="scroll-mt-16">
425
463
  <CardHeader>
426
464
  <CardTitle className="text-lg">
427
465
  {t("settings.appearance")}
@@ -0,0 +1,5 @@
1
+ ---
2
+ type: improved
3
+ ---
4
+
5
+ Settings are cleaner and searchable, with a consistent navigation that jumps straight to any setting.
@@ -4,9 +4,11 @@ import {
4
4
  SettingsTabsPage,
5
5
  useAgentSettingsTabs,
6
6
  useT,
7
+ type SettingsSearchEntry,
7
8
  } from "@agent-native/core/client";
8
9
  import { TeamPage } from "@agent-native/core/client/org";
9
10
  import { useSetPageTitle } from "@agent-native/toolkit/app-shell";
11
+ import { useMemo } from "react";
10
12
 
11
13
  import {
12
14
  Card,
@@ -29,17 +31,30 @@ export default function SettingsRoute() {
29
31
  const agentSettingsTabs = useAgentSettingsTabs();
30
32
  useSetPageTitle(t("settings.title"));
31
33
 
34
+ const generalSearchEntries = useMemo<SettingsSearchEntry[]>(
35
+ () => [
36
+ {
37
+ id: "chat-language",
38
+ label: t("settings.languageTitle"),
39
+ keywords: "language locale translation i18n",
40
+ hash: "language",
41
+ },
42
+ ],
43
+ [t],
44
+ );
45
+
32
46
  return (
33
47
  <SettingsTabsPage
34
48
  teamLabel={t("navigation.team")}
35
49
  extraTabs={agentSettingsTabs}
50
+ generalSearchEntries={generalSearchEntries}
36
51
  general={
37
52
  <div className="mx-auto w-full max-w-3xl space-y-6">
38
53
  <p className="text-sm leading-6 text-muted-foreground">
39
54
  {t("settings.description")}
40
55
  </p>
41
56
 
42
- <Card>
57
+ <Card id="language" className="scroll-mt-16">
43
58
  <CardHeader>
44
59
  <CardTitle className="text-base">
45
60
  {t("settings.languageTitle")}
@@ -0,0 +1,5 @@
1
+ ---
2
+ type: improved
3
+ ---
4
+
5
+ Settings are cleaner and searchable, with a consistent navigation that jumps straight to any setting.
@@ -5,7 +5,10 @@ import { createRequire } from "node:module";
5
5
  import { tmpdir } from "node:os";
6
6
  import { join } from "node:path";
7
7
 
8
- const AUDIO_EXTRACTION_TIMEOUT_MS = 30_000;
8
+ const AUDIO_EXTRACTION_MIN_TIMEOUT_MS = 30_000;
9
+ const AUDIO_EXTRACTION_MAX_TIMEOUT_MS = 90_000;
10
+ const AUDIO_EXTRACTION_BASE_TIMEOUT_MS = 25_000;
11
+ const AUDIO_EXTRACTION_PER_50MB_MS = 10_000;
9
12
  const SILENCE_MAX_VOLUME_DB = -60;
10
13
  const STDERR_LIMIT = 16 * 1024;
11
14
  const requireFromThisFile = createRequire(import.meta.url);
@@ -115,6 +118,36 @@ export function audioExtensionForMimeType(
115
118
  }
116
119
  }
117
120
 
121
+ function clampAudioExtractionTimeoutMs(value: number): number {
122
+ return Math.max(
123
+ AUDIO_EXTRACTION_MIN_TIMEOUT_MS,
124
+ Math.min(AUDIO_EXTRACTION_MAX_TIMEOUT_MS, Math.floor(value)),
125
+ );
126
+ }
127
+
128
+ export function audioExtractionTimeoutMs(
129
+ mediaByteLength: number | null | undefined,
130
+ ): number {
131
+ const override = Number(process.env.CLIPS_AUDIO_EXTRACTION_TIMEOUT_MS);
132
+ if (Number.isFinite(override) && override > 0) {
133
+ return clampAudioExtractionTimeoutMs(override);
134
+ }
135
+
136
+ if (
137
+ typeof mediaByteLength !== "number" ||
138
+ !Number.isFinite(mediaByteLength) ||
139
+ mediaByteLength <= 0
140
+ ) {
141
+ return AUDIO_EXTRACTION_MIN_TIMEOUT_MS;
142
+ }
143
+
144
+ const fiftyMbUnits = Math.ceil(mediaByteLength / (50 * 1024 * 1024));
145
+ return clampAudioExtractionTimeoutMs(
146
+ AUDIO_EXTRACTION_BASE_TIMEOUT_MS +
147
+ fiftyMbUnits * AUDIO_EXTRACTION_PER_50MB_MS,
148
+ );
149
+ }
150
+
118
151
  function mediaExtensionForMimeType(mimeType: string): string {
119
152
  switch (baseMimeType(mimeType)) {
120
153
  case "video/mp4":
@@ -249,7 +282,7 @@ function mapFfmpegError(err: unknown): AudioOnlyExtractionError {
249
282
  );
250
283
  }
251
284
 
252
- async function runFfmpeg(args: string[]): Promise<void> {
285
+ async function runFfmpeg(args: string[], timeoutMs: number): Promise<void> {
253
286
  await new Promise<void>((resolve, reject) => {
254
287
  const child = spawn(ffmpegCommand(), args, {
255
288
  stdio: ["ignore", "ignore", "pipe"],
@@ -258,7 +291,7 @@ async function runFfmpeg(args: string[]): Promise<void> {
258
291
  const timeout = setTimeout(() => {
259
292
  child.kill("SIGKILL");
260
293
  reject(new FfmpegRunError("ffmpeg timed out", stderr));
261
- }, AUDIO_EXTRACTION_TIMEOUT_MS);
294
+ }, timeoutMs);
262
295
 
263
296
  child.stderr?.on("data", (chunk: Buffer) => {
264
297
  stderr = (stderr + chunk.toString("utf8")).slice(-STDERR_LIMIT);
@@ -278,7 +311,10 @@ async function runFfmpeg(args: string[]): Promise<void> {
278
311
  });
279
312
  }
280
313
 
281
- async function runFfmpegForStderr(args: string[]): Promise<string> {
314
+ async function runFfmpegForStderr(
315
+ args: string[],
316
+ timeoutMs: number,
317
+ ): Promise<string> {
282
318
  return await new Promise<string>((resolve, reject) => {
283
319
  const child = spawn(ffmpegCommand(), args, {
284
320
  stdio: ["ignore", "ignore", "pipe"],
@@ -287,7 +323,7 @@ async function runFfmpegForStderr(args: string[]): Promise<string> {
287
323
  const timeout = setTimeout(() => {
288
324
  child.kill("SIGKILL");
289
325
  reject(new FfmpegRunError("ffmpeg timed out", stderr));
290
- }, AUDIO_EXTRACTION_TIMEOUT_MS);
326
+ }, timeoutMs);
291
327
 
292
328
  child.stderr?.on("data", (chunk: Buffer) => {
293
329
  stderr = (stderr + chunk.toString("utf8")).slice(-STDERR_LIMIT);
@@ -331,21 +367,25 @@ export async function analyzeAudioSignal({
331
367
 
332
368
  const dir = await mkdtemp(join(tmpdir(), "clips-transcription-"));
333
369
  const inputPath = join(dir, `input.${audioExtensionForMimeType(mimeType)}`);
370
+ const timeoutMs = audioExtractionTimeoutMs(audioBytes.byteLength);
334
371
 
335
372
  try {
336
373
  await writeFile(inputPath, audioBytes);
337
- const stderr = await runFfmpegForStderr([
338
- "-hide_banner",
339
- "-nostdin",
340
- "-i",
341
- inputPath,
342
- "-vn",
343
- "-af",
344
- "volumedetect",
345
- "-f",
346
- "null",
347
- "-",
348
- ]).catch((err) => {
374
+ const stderr = await runFfmpegForStderr(
375
+ [
376
+ "-hide_banner",
377
+ "-nostdin",
378
+ "-i",
379
+ inputPath,
380
+ "-vn",
381
+ "-af",
382
+ "volumedetect",
383
+ "-f",
384
+ "null",
385
+ "-",
386
+ ],
387
+ timeoutMs,
388
+ ).catch((err) => {
349
389
  throw mapFfmpegError(err);
350
390
  });
351
391
 
@@ -361,6 +401,10 @@ export async function analyzeAudioSignal({
361
401
  export async function assertAudioHasAudibleSignal(
362
402
  media: AudioOnlyTranscriptionMedia,
363
403
  ): Promise<void> {
404
+ if (media.source === "raw-media-fallback") {
405
+ return;
406
+ }
407
+
364
408
  let signal: { meanVolumeDb: number | null; maxVolumeDb: number | null };
365
409
  try {
366
410
  signal = await analyzeAudioSignal(media);
@@ -402,17 +446,15 @@ export async function extractAudioOnlyWithFfmpeg({
402
446
  const output = outputForSourceMimeType(mimeType);
403
447
  const outputPath = join(dir, `audio.${output.extension}`);
404
448
  const baseArgs = ["-hide_banner", "-loglevel", "error", "-nostdin", "-y"];
449
+ const timeoutMs = audioExtractionTimeoutMs(mediaBytes.byteLength);
405
450
 
406
451
  try {
407
452
  await writeFile(inputPath, mediaBytes);
408
453
  try {
409
- await runFfmpeg([
410
- ...baseArgs,
411
- "-i",
412
- inputPath,
413
- ...output.copyArgs,
414
- outputPath,
415
- ]);
454
+ await runFfmpeg(
455
+ [...baseArgs, "-i", inputPath, ...output.copyArgs, outputPath],
456
+ timeoutMs,
457
+ );
416
458
  } catch (copyErr) {
417
459
  if (
418
460
  copyErr instanceof FfmpegRunError &&
@@ -420,13 +462,10 @@ export async function extractAudioOnlyWithFfmpeg({
420
462
  ) {
421
463
  throw copyErr;
422
464
  }
423
- await runFfmpeg([
424
- ...baseArgs,
425
- "-i",
426
- inputPath,
427
- ...output.transcodeArgs,
428
- outputPath,
429
- ]).catch((transcodeErr) => {
465
+ await runFfmpeg(
466
+ [...baseArgs, "-i", inputPath, ...output.transcodeArgs, outputPath],
467
+ timeoutMs,
468
+ ).catch((transcodeErr) => {
430
469
  throw mapFfmpegError(transcodeErr);
431
470
  });
432
471
  }
@@ -491,13 +530,16 @@ export async function prepareAudioOnlyTranscriptionMedia({
491
530
  recordingId,
492
531
  });
493
532
  } catch (err) {
494
- // No ffmpeg to strip the audio track. Rather than fail outright, hand the
495
- // original media to the transcription provider Gemini/Builder accepts
496
- // video containers directly. (Whisper-style providers may reject it, in
497
- // which case the normal provider error path takes over.)
498
- if (isFfmpegUnavailableError(err)) {
533
+ // If ffmpeg is unavailable or too slow for this clip, hand the original
534
+ // media to the transcription provider. Gemini/Builder accepts video
535
+ // containers directly; Whisper-style providers may reject them, in which
536
+ // case the normal provider error path takes over.
537
+ if (
538
+ isFfmpegUnavailableError(err) ||
539
+ (err instanceof AudioOnlyExtractionError && err.code === "TIMEOUT")
540
+ ) {
499
541
  console.warn(
500
- "[clips] ffmpeg unavailable; sending original media to the transcription provider without audio extraction.",
542
+ "[clips] ffmpeg could not prepare audio-only media; sending original media to the transcription provider.",
501
543
  );
502
544
  return {
503
545
  audioBytes: mediaBytes,
@@ -2,6 +2,7 @@ import { useT } from "@agent-native/core/client";
2
2
  import {
3
3
  IconPlayerPlay,
4
4
  IconPlayerPause,
5
+ IconPlayerSkipForward,
5
6
  IconVolume,
6
7
  IconVolumeOff,
7
8
  IconMaximize,
@@ -35,6 +36,7 @@ import { cn } from "@/lib/utils";
35
36
  import { Scrubber, msToClock } from "./scrubber";
36
37
 
37
38
  export const SPEED_OPTIONS = PLAYBACK_SPEED_OPTIONS;
39
+ export const PLAYER_SEEK_STEP_MS = 15_000;
38
40
 
39
41
  export interface PlayerControlsProps {
40
42
  isPlaying: boolean;
@@ -54,6 +56,7 @@ export interface PlayerControlsProps {
54
56
  excludedRanges?: { startMs: number; endMs: number }[];
55
57
  onPlayPause: () => void;
56
58
  onSeek: (ms: number) => void;
59
+ onSeekRelative: (deltaMs: number) => void;
57
60
  onVolumeChange: (volume: number) => void;
58
61
  onToggleMute: () => void;
59
62
  onSpeedChange: (rate: number) => void;
@@ -84,6 +87,7 @@ export function PlayerControls(props: PlayerControlsProps) {
84
87
  excludedRanges,
85
88
  onPlayPause,
86
89
  onSeek,
90
+ onSeekRelative,
87
91
  onVolumeChange,
88
92
  onToggleMute,
89
93
  onSpeedChange,
@@ -116,9 +120,18 @@ export function PlayerControls(props: PlayerControlsProps) {
116
120
  />
117
121
 
118
122
  <div className="flex min-w-0 items-center gap-1.5 text-white">
123
+ <IconBtn
124
+ onClick={() => onSeekRelative(-PLAYER_SEEK_STEP_MS)}
125
+ tooltip="Back 15 seconds"
126
+ ariaLabel="Back 15 seconds"
127
+ >
128
+ <SkipIcon direction="back" />
129
+ </IconBtn>
130
+
119
131
  <IconBtn
120
132
  onClick={onPlayPause}
121
133
  tooltip={isPlaying ? "Pause (K)" : "Play (K)"}
134
+ ariaLabel={isPlaying ? "Pause" : "Play"}
122
135
  >
123
136
  {isPlaying ? (
124
137
  <IconPlayerPause className="h-5 w-5" />
@@ -127,6 +140,14 @@ export function PlayerControls(props: PlayerControlsProps) {
127
140
  )}
128
141
  </IconBtn>
129
142
 
143
+ <IconBtn
144
+ onClick={() => onSeekRelative(PLAYER_SEEK_STEP_MS)}
145
+ tooltip="Forward 15 seconds"
146
+ ariaLabel="Forward 15 seconds"
147
+ >
148
+ <SkipIcon direction="forward" />
149
+ </IconBtn>
150
+
130
151
  <div
131
152
  className="relative flex shrink-0 items-center"
132
153
  onMouseEnter={() => setVolumePopoverOpen(true)}
@@ -264,11 +285,13 @@ function IconBtn({
264
285
  children,
265
286
  onClick,
266
287
  tooltip,
288
+ ariaLabel,
267
289
  active,
268
290
  }: {
269
291
  children: React.ReactNode;
270
292
  onClick?: () => void;
271
293
  tooltip?: string;
294
+ ariaLabel?: string;
272
295
  active?: boolean;
273
296
  }) {
274
297
  return (
@@ -276,6 +299,7 @@ function IconBtn({
276
299
  <TooltipTrigger asChild>
277
300
  <button
278
301
  onClick={onClick}
302
+ aria-label={ariaLabel ?? tooltip}
279
303
  className={cn(
280
304
  "flex h-8 w-8 shrink-0 items-center justify-center rounded-md",
281
305
  active ? "bg-white/20 text-white" : "text-white hover:bg-white/10",
@@ -288,3 +312,14 @@ function IconBtn({
288
312
  </Tooltip>
289
313
  );
290
314
  }
315
+
316
+ function SkipIcon({ direction }: { direction: "back" | "forward" }) {
317
+ return (
318
+ <span className="relative flex h-5 w-5 items-center justify-center">
319
+ <IconPlayerSkipForward
320
+ className={cn("h-5 w-5", direction === "back" && "rotate-180")}
321
+ />
322
+ <span className="absolute text-[7px] font-bold leading-none">15</span>
323
+ </span>
324
+ );
325
+ }