@agent-native/core-corpus 0.210.2 → 0.211.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 (192) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/analytics/.agents/skills/actions/references/action-fields.md +5 -3
  3. package/corpus/templates/analytics/.agents/skills/dashboard-management/SKILL.md +41 -2
  4. package/corpus/templates/analytics/.agents/skills/data-querying/SKILL.md +4 -2
  5. package/corpus/templates/analytics/CHANGELOG.md +18 -0
  6. package/corpus/templates/analytics/actions/build-data-index.ts +11 -2
  7. package/corpus/templates/analytics/actions/configure-content-collection.ts +29 -4
  8. package/corpus/templates/analytics/actions/configure-first-party-events-model.ts +9 -7
  9. package/corpus/templates/analytics/actions/create-analytics-public-key.ts +4 -0
  10. package/corpus/templates/analytics/actions/estimate-content-signups-cost.ts +72 -0
  11. package/corpus/templates/analytics/actions/estimate-query-cost.ts +16 -134
  12. package/corpus/templates/analytics/actions/list-sigma-workbooks.ts +123 -42
  13. package/corpus/templates/analytics/actions/migrate-sigma-workbook.ts +20 -17
  14. package/corpus/templates/analytics/actions/revoke-analytics-public-key.ts +4 -0
  15. package/corpus/templates/analytics/app/components/dashboard/SigmaMigrationDialog.tsx +246 -680
  16. package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +1 -1
  17. package/corpus/templates/analytics/app/i18n/en-US.ts +54 -55
  18. package/corpus/templates/analytics/app/pages/DashboardOverview.tsx +0 -1
  19. package/corpus/templates/analytics/app/pages/DataSources.tsx +56 -0
  20. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/filter-vars.ts +4 -3
  21. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +26 -8
  22. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/interpolate.ts +3 -1
  23. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/panel-cost-notice.ts +172 -0
  24. package/corpus/templates/analytics/app/pages/data-sources/SearchConsoleDetail.tsx +441 -0
  25. package/corpus/templates/analytics/changelog/2026-10-15-a-content-dashboard-lists-every-blog-article-with-its-author.md +6 -0
  26. package/corpus/templates/analytics/changelog/2026-10-15-data-sources-has-a-google-search-console-card-that-shows-the.md +6 -0
  27. package/corpus/templates/analytics/changelog/2026-10-16-the-content-dashboard-can-show-signups-per-blog-article.md +6 -0
  28. package/corpus/templates/analytics/changelog/2026-10-19-creating-or-revoking-a-public-analytics-key-now-asks-you-to-.md +6 -0
  29. package/corpus/templates/analytics/changelog/2026-10-19-search-sigma-workbooks-by-name-and-open-ones-a-teammate-alre.md +6 -0
  30. package/corpus/templates/analytics/changelog/2026-10-20-blog-signup-attribution-excludes-signups-before-the-first-vi.md +6 -0
  31. package/corpus/templates/analytics/changelog/2026-10-20-collapsed-analytics-navigation-starts-at-the-top.md +6 -0
  32. package/corpus/templates/analytics/changelog/2026-10-20-content-keeps-successful-coverage-dates-and-missing-day-gaps.md +6 -0
  33. package/corpus/templates/analytics/changelog/2026-10-20-private-github-repositories-can-be-indexed-with-your-connect.md +6 -0
  34. package/corpus/templates/analytics/changelog/2026-10-20-search-console-preserves-backfill-and-paused-collection.md +6 -0
  35. package/corpus/templates/analytics/changelog/2026-10-20-search-console-refreshes-historical-coverage-when-properties.md +6 -0
  36. package/corpus/templates/analytics/changelog/2026-10-20-search-console-setup-keeps-saved-properties-readable-and-sup.md +6 -0
  37. package/corpus/templates/analytics/seeds/dashboards/content-view.json +182 -0
  38. package/corpus/templates/analytics/server/db/schema.ts +1 -0
  39. package/corpus/templates/analytics/server/lib/bi-migration/external-dashboards-store.ts +56 -18
  40. package/corpus/templates/analytics/server/lib/bi-migration/unfinished-import.ts +64 -0
  41. package/corpus/templates/analytics/server/lib/builtin-dashboard-pass.ts +17 -2
  42. package/corpus/templates/analytics/server/lib/content-metrics/collect.ts +2 -0
  43. package/corpus/templates/analytics/server/lib/content-metrics/config.ts +92 -3
  44. package/corpus/templates/analytics/server/lib/content-metrics/legacy-content-view.json +172 -0
  45. package/corpus/templates/analytics/server/lib/content-metrics/migration.ts +2 -0
  46. package/corpus/templates/analytics/server/lib/content-metrics/steps/daily-metrics.ts +8 -3
  47. package/corpus/templates/analytics/server/lib/content-metrics/steps/search-console.ts +16 -3
  48. package/corpus/templates/analytics/server/lib/content-metrics/steps/signups.ts +240 -0
  49. package/corpus/templates/analytics/server/lib/content-metrics/store.ts +5 -0
  50. package/corpus/templates/analytics/server/lib/dashboard-catalog.ts +21 -0
  51. package/corpus/templates/analytics/server/lib/dashboard-panel-verification.ts +300 -5
  52. package/corpus/templates/analytics/server/lib/dashboard-seeds.ts +2 -0
  53. package/corpus/templates/analytics/server/lib/dashboards-store.ts +28 -0
  54. package/corpus/templates/analytics/server/lib/first-party-analytics-backend.ts +128 -24
  55. package/corpus/templates/analytics/server/lib/google-analytics.ts +56 -16
  56. package/corpus/templates/analytics/server/lib/index-build-github.ts +5 -0
  57. package/corpus/templates/analytics/server/lib/provider-credentials.ts +47 -2
  58. package/corpus/templates/analytics/server/lib/query-cost-estimate.ts +184 -5
  59. package/corpus/templates/analytics/server/plugins/db.ts +9 -1
  60. package/corpus/templates/analytics/shared/builtin-dashboard-ids.ts +2 -0
  61. package/corpus/templates/analytics/shared/search-console-property.ts +78 -0
  62. package/corpus/templates/assets/.agents/skills/actions/references/action-fields.md +5 -3
  63. package/corpus/templates/brain/.agents/skills/actions/references/action-fields.md +5 -3
  64. package/corpus/templates/calendar/.agents/skills/actions/references/action-fields.md +5 -3
  65. package/corpus/templates/calendar/actions/delete-event.ts +1 -3
  66. package/corpus/templates/calendar/actions/delete-events.ts +1 -2
  67. package/corpus/templates/calendar/actions/event-action-helpers.ts +0 -4
  68. package/corpus/templates/calendar/actions/update-event.ts +2 -6
  69. package/corpus/templates/chat/.agents/skills/actions/references/action-fields.md +5 -3
  70. package/corpus/templates/clips/.agents/skills/actions/references/action-fields.md +5 -3
  71. package/corpus/templates/clips/CHANGELOG.md +11 -24
  72. package/corpus/templates/clips/actions/create-recording-upload.ts +5 -1
  73. package/corpus/templates/clips/app/components/player/chapter-list.tsx +7 -6
  74. package/corpus/templates/clips/app/components/player/comment-composer.tsx +5 -1
  75. package/corpus/templates/clips/app/components/player/player-controls.tsx +1 -0
  76. package/corpus/templates/clips/app/components/player/scrubber.tsx +4 -2
  77. package/corpus/templates/clips/app/i18n/en-US.ts +3 -3
  78. package/corpus/templates/clips/app/lib/extension-auth.ts +70 -0
  79. package/corpus/templates/clips/app/root.tsx +22 -10
  80. package/corpus/templates/clips/app/routes/_app.notifications.tsx +3 -0
  81. package/corpus/templates/clips/app/routes/import.tsx +115 -110
  82. package/corpus/templates/clips/changelog/2026-10-16-agent-uploads-can-use-free-clips-storage.md +6 -0
  83. package/corpus/templates/clips/changelog/2026-10-19-agents-see-a-clip-s-frames-as-soon-as-it-finishes-processing.md +6 -0
  84. package/corpus/templates/clips/changelog/2026-10-19-chapter-list-times-now-match-the-player-s-clock-and-each-cha.md +6 -0
  85. package/corpus/templates/clips/changelog/2026-10-19-editing-a-comment-puts-the-cursor-after-its-text-and-reply-o.md +6 -0
  86. package/corpus/templates/clips/changelog/2026-10-19-youtube-import-followups.md +6 -0
  87. package/corpus/templates/clips/changelog/2026-10-20-locally-saved-desktop-recordings-now-appear-in-the-recovery-.md +6 -0
  88. package/corpus/templates/clips/desktop/src/app.tsx +29 -186
  89. package/corpus/templates/clips/desktop/src/components/RecordingRecovery.tsx +171 -16
  90. package/corpus/templates/clips/desktop/src/lib/record-first.ts +9 -32
  91. package/corpus/templates/clips/desktop/src/lib/recording-recovery.ts +30 -1
  92. package/corpus/templates/clips/server/lib/agent-frame-request.ts +5 -2
  93. package/corpus/templates/clips/server/lib/recording-upload-ticket.ts +8 -0
  94. package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/chunk.post.ts +2 -0
  95. package/corpus/templates/content/.agents/skills/actions/references/action-fields.md +5 -3
  96. package/corpus/templates/content/CHANGELOG.md +12 -0
  97. package/corpus/templates/content/actions/_content-database-personal-view.ts +5 -15
  98. package/corpus/templates/content/actions/_database-navigation.ts +3 -3
  99. package/corpus/templates/content/actions/_database-utils.ts +10 -22
  100. package/corpus/templates/content/actions/_database-view-setup.ts +16 -76
  101. package/corpus/templates/content/actions/create-comment-ai-suggestion.ts +93 -44
  102. package/corpus/templates/content/actions/export-document.ts +2 -14
  103. package/corpus/templates/content/actions/suggest-document-edit.ts +5 -7
  104. package/corpus/templates/content/actions/update-content-database-view.ts +2 -14
  105. package/corpus/templates/content/app/components/editor/CommentsSidebar.tsx +86 -30
  106. package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +64 -28
  107. package/corpus/templates/content/app/components/editor/DocumentProperties.tsx +6 -46
  108. package/corpus/templates/content/app/components/editor/PageDraftRecovery.tsx +72 -7
  109. package/corpus/templates/content/app/components/editor/TableHoverControls.tsx +292 -103
  110. package/corpus/templates/content/app/components/editor/ThreadSuggestion.tsx +147 -40
  111. package/corpus/templates/content/app/components/editor/VisualEditor.tsx +108 -11
  112. package/corpus/templates/content/app/components/editor/database/ContentTable.tsx +2 -1
  113. package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +150 -166
  114. package/corpus/templates/content/app/components/editor/database/database-cell-comments.tsx +515 -0
  115. package/corpus/templates/content/app/components/editor/database/database-item-comments.tsx +5 -43
  116. package/corpus/templates/content/app/components/editor/database/filter-sort.ts +8 -783
  117. package/corpus/templates/content/app/components/editor/live-editor-session.ts +5 -1
  118. package/corpus/templates/content/app/components/editor/page-draft-journal.ts +61 -1
  119. package/corpus/templates/content/app/components/editor/property-comments.tsx +35 -4
  120. package/corpus/templates/content/app/components/editor/property-date-format.ts +45 -0
  121. package/corpus/templates/content/app/components/editor/thread-suggestions.ts +30 -0
  122. package/corpus/templates/content/app/components/editor/useDocumentReconcileRecovery.ts +23 -4
  123. package/corpus/templates/content/app/components/sidebar/DocumentSidebar.tsx +36 -10
  124. package/corpus/templates/content/app/global.css +23 -0
  125. package/corpus/templates/content/app/i18n-data.ts +1 -0
  126. package/corpus/templates/content/changelog/2026-10-16-opening-a-page-in-a-new-tab-while-another-tab-is-still-savin.md +6 -0
  127. package/corpus/templates/content/changelog/2026-10-16-typing-steadily-in-one-tab-no-longer-makes-another-open-tab-.md +6 -0
  128. package/corpus/templates/content/changelog/2026-10-19-database-views-can-filter-the-same-property-more-than-once-n.md +6 -0
  129. package/corpus/templates/content/changelog/2026-10-19-edits-made-from-a-block-s-menu-or-a-table-s-row-and-column-m.md +6 -0
  130. package/corpus/templates/content/changelog/2026-10-19-starting-a-comment-puts-the-cursor-in-the-comment-box-ready-.md +6 -0
  131. package/corpus/templates/content/changelog/2026-10-19-table-row-and-column-options-now-appear-for-the-row-or-colum.md +6 -0
  132. package/corpus/templates/content/docs/product/capabilities/content.comment.page-owned.md +5 -1
  133. package/corpus/templates/content/docs/product/capabilities/content.revision.suggestions.md +3 -1
  134. package/corpus/templates/content/e2e/helpers.ts +10 -0
  135. package/corpus/templates/content/shared/api.ts +18 -12
  136. package/corpus/templates/content/shared/database-query.ts +107 -13
  137. package/corpus/templates/content/shared/suggestion-diff.ts +35 -0
  138. package/corpus/templates/crm/.agents/skills/actions/references/action-fields.md +5 -3
  139. package/corpus/templates/design/.agents/skills/actions/references/action-fields.md +5 -3
  140. package/corpus/templates/design/CHANGELOG.md +2 -0
  141. package/corpus/templates/design/actions/delete-file.ts +1 -4
  142. package/corpus/templates/design/app/components/design/LayersPanel.tsx +10 -1
  143. package/corpus/templates/design/app/components/visual-editor/ReviewCanvasPins.tsx +24 -4
  144. package/corpus/templates/design/app/pages/design-editor/commands/mode-change.ts +14 -9
  145. package/corpus/templates/design/app/pages/design-editor/domains/use-editor-modes.ts +23 -2
  146. package/corpus/templates/design/app/pages/design-editor/tool-state.ts +29 -0
  147. package/corpus/templates/design/changelog/2026-10-19-reply-and-edit-on-a-comment-in-the-sidebar-or-on-a-canvas-pi.md +6 -0
  148. package/corpus/templates/design/changelog/2026-10-20-comments-return-to-the-mode-they-came-from-and-layers-search.md +6 -0
  149. package/corpus/templates/design/e2e/global-setup.ts +185 -85
  150. package/corpus/templates/dispatch/.agents/skills/actions/references/action-fields.md +5 -3
  151. package/corpus/templates/dispatch/CHANGELOG.md +4 -0
  152. package/corpus/templates/dispatch/actions/apply-workspace-connection-setup.ts +4 -0
  153. package/corpus/templates/dispatch/actions/delete-workspace-connection.ts +4 -0
  154. package/corpus/templates/dispatch/actions/set-workspace-connection-grant.ts +4 -0
  155. package/corpus/templates/dispatch/actions/upsert-workspace-connection.ts +4 -0
  156. package/corpus/templates/dispatch/changelog/2026-10-19-vault-secrets-grants-link-tokens-app-access-and-workspace-co.md +6 -0
  157. package/corpus/templates/factory/.agents/skills/actions/references/action-fields.md +5 -3
  158. package/corpus/templates/factory/.agents/skills/review-latest-feedback/SKILL.md +105 -33
  159. package/corpus/templates/factory/.agents/skills/review-latest-feedback/references/channel-inventory.md +128 -74
  160. package/corpus/templates/factory/.agents/skills/review-latest-feedback/references/slack-reaction-replies.md +39 -11
  161. package/corpus/templates/forms/.agents/skills/actions/references/action-fields.md +5 -3
  162. package/corpus/templates/mail/.agents/skills/actions/references/action-fields.md +5 -3
  163. package/corpus/templates/mail/.agents/skills/contacts-and-crm/SKILL.md +22 -17
  164. package/corpus/templates/mail/AGENTS.md +1 -1
  165. package/corpus/templates/mail/CHANGELOG.md +14 -0
  166. package/corpus/templates/mail/actions/provider-api-request.ts +3 -3
  167. package/corpus/templates/mail/app/components/email/IntegrationsSidebar.tsx +211 -54
  168. package/corpus/templates/mail/app/hooks/use-inbox-threads.ts +26 -2
  169. package/corpus/templates/mail/app/i18n/en-US.ts +5 -1
  170. package/corpus/templates/mail/app/lib/gong-connection.ts +27 -0
  171. package/corpus/templates/mail/app/lib/thread-cache.ts +110 -9
  172. package/corpus/templates/mail/app/root.tsx +10 -2
  173. package/corpus/templates/mail/changelog/2026-10-20-connected-gong-settings-now-open-the-shared-integration-mana.md +6 -0
  174. package/corpus/templates/mail/changelog/2026-10-20-gong-connection-details-now-match-the-call-data-available-in.md +6 -0
  175. package/corpus/templates/mail/changelog/2026-10-20-improve-gong-setup-with-a-click-to-connect-flow-and-permissi.md +6 -0
  176. package/corpus/templates/mail/changelog/2026-10-20-mail-lets-you-retry-when-it-cannot-check-your-gong-access.md +6 -0
  177. package/corpus/templates/mail/changelog/2026-10-20-mail-thread-cache-stays-isolated-when-mailbox-scope-changes.md +6 -0
  178. package/corpus/templates/mail/changelog/2026-10-20-the-inbox-and-recently-opened-threads-now-open-instantly-fro.md +6 -0
  179. package/corpus/templates/mail/server/handlers/emails.ts +9 -0
  180. package/corpus/templates/plan/.agents/skills/actions/references/action-fields.md +5 -3
  181. package/corpus/templates/plan/CHANGELOG.md +4 -0
  182. package/corpus/templates/plan/app/pages/PlansPage.tsx +173 -33
  183. package/corpus/templates/plan/changelog/2026-10-19-opening-a-comment-thread-puts-the-cursor-in-the-reply-box-an.md +6 -0
  184. package/corpus/templates/slides/.agents/skills/actions/references/action-fields.md +5 -3
  185. package/corpus/templates/slides/app/components/editor/EditorToolbar.tsx +6 -42
  186. package/corpus/templates/slides/app/components/editor/FloatingToolbar.tsx +3 -7
  187. package/corpus/templates/slides/app/components/editor/SlideEditor.tsx +5 -14
  188. package/corpus/templates/slides/app/i18n/en-US.ts +0 -2
  189. package/corpus/templates/slides/app/pages/DeckEditor.tsx +27 -33
  190. package/corpus/templates/slides/scripts/edit-fidelity/run.ts +1 -3
  191. package/corpus/templates/tasks/.agents/skills/actions/references/action-fields.md +5 -3
  192. package/package.json +1 -1
@@ -95,9 +95,9 @@ MCP clients see no `outputSchema` by default. Set `mcpOutputSchema: true` (requi
95
95
 
96
96
  `authorize` decides whether the caller may run the action at all. It wraps
97
97
  `run`, so it holds at **all six dispatch sites** — agent tool, HTTP, frontend
98
- hook, MCP, A2A, CLI — unlike `needsApproval`, which is honoured only in the
99
- agent loop. Use `authorize` for "not everyone may do this"; use `needsApproval`
100
- to ask a human to bless one call a permitted caller is already allowed to make.
98
+ hook, MCP, A2A, CLI. Use `authorize` for "not everyone may do this"; use
99
+ `needsApproval` to ask a human to bless one call a permitted caller is already
100
+ allowed to make.
101
101
 
102
102
  ```ts
103
103
  import { coachAccess } from "../lib/access.js"; // defineAppRoles(...)
@@ -140,4 +140,6 @@ export default defineAction({
140
140
 
141
141
  When the gate is truthy and the call isn't yet approved, the loop emits an `approval_required` event and **stops the turn — `run()` never executes**. A predicate gates conditionally (e.g. only external recipients) and **fails closed**: a throw is treated as "approval required". It receives its own copy of the prepared snapshot of the validated arguments, defaults applied, and the approval binds to the snapshot's hash, so an approved call runs only with the arguments that were approved (a Date binds by its time). The human approves via the chat UI's Approve affordance, which re-issues the turn with the call's `approvalKey`, and only then does the action run.
142
142
 
143
+ Approval is given in the app, so HTTP never grants it. The HTTP action route and the generated Workers entry run a gated call only for the proven app UI (a frontend call from the signed-in user, same origin, holding the short-lived UI capability; `isProvenUiRequest`, the proof `uiOnly` uses) or the app's MCP directory widget. Every other HTTP caller (bearer or API key, MCP OAuth token, org service token, A2A, extension iframe, mobile app) gets `409 approval_required`, and a service identity can never approve. The shared client presents the capability with every write from a signed-in session (minted once per user, reused for nine minutes, never for reads or public pages), so a gated button works on its first request. A call without proof gets `403 ui_capability_required`; the client then re-mints and retries once, which is only the fallback for an expired or rejected capability. Every surface evaluates the gate through `preparedCallNeedsApproval`, so a throwing predicate asks there too. A caller that must run unattended needs a different action, never a bypass of the gate.
144
+
143
145
  **Keep approvals rare** — the default is off and almost every action should leave it off. The canonical example is Mail's `send-email` (`needsApproval: true`). See the `security` skill and the Human Approval doc.
@@ -95,9 +95,9 @@ MCP clients see no `outputSchema` by default. Set `mcpOutputSchema: true` (requi
95
95
 
96
96
  `authorize` decides whether the caller may run the action at all. It wraps
97
97
  `run`, so it holds at **all six dispatch sites** — agent tool, HTTP, frontend
98
- hook, MCP, A2A, CLI — unlike `needsApproval`, which is honoured only in the
99
- agent loop. Use `authorize` for "not everyone may do this"; use `needsApproval`
100
- to ask a human to bless one call a permitted caller is already allowed to make.
98
+ hook, MCP, A2A, CLI. Use `authorize` for "not everyone may do this"; use
99
+ `needsApproval` to ask a human to bless one call a permitted caller is already
100
+ allowed to make.
101
101
 
102
102
  ```ts
103
103
  import { coachAccess } from "../lib/access.js"; // defineAppRoles(...)
@@ -140,4 +140,6 @@ export default defineAction({
140
140
 
141
141
  When the gate is truthy and the call isn't yet approved, the loop emits an `approval_required` event and **stops the turn — `run()` never executes**. A predicate gates conditionally (e.g. only external recipients) and **fails closed**: a throw is treated as "approval required". It receives its own copy of the prepared snapshot of the validated arguments, defaults applied, and the approval binds to the snapshot's hash, so an approved call runs only with the arguments that were approved (a Date binds by its time). The human approves via the chat UI's Approve affordance, which re-issues the turn with the call's `approvalKey`, and only then does the action run.
142
142
 
143
+ Approval is given in the app, so HTTP never grants it. The HTTP action route and the generated Workers entry run a gated call only for the proven app UI (a frontend call from the signed-in user, same origin, holding the short-lived UI capability; `isProvenUiRequest`, the proof `uiOnly` uses) or the app's MCP directory widget. Every other HTTP caller (bearer or API key, MCP OAuth token, org service token, A2A, extension iframe, mobile app) gets `409 approval_required`, and a service identity can never approve. The shared client presents the capability with every write from a signed-in session (minted once per user, reused for nine minutes, never for reads or public pages), so a gated button works on its first request. A call without proof gets `403 ui_capability_required`; the client then re-mints and retries once, which is only the fallback for an expired or rejected capability. Every surface evaluates the gate through `preparedCallNeedsApproval`, so a throwing predicate asks there too. A caller that must run unattended needs a different action, never a bypass of the gate.
144
+
143
145
  **Keep approvals rare** — the default is off and almost every action should leave it off. The canonical example is Mail's `send-email` (`needsApproval: true`). See the `security` skill and the Human Approval doc.
@@ -3,10 +3,20 @@
3
3
  All notable user-facing changes to Clips are documented here. Open it any time
4
4
  from the command menu (Cmd+K → "What's new") or from Settings.
5
5
 
6
+ ## 2026-10-20
7
+
8
+ ### Fixed
9
+
10
+ - Locally saved desktop recordings now appear in the recovery list with upload and storage setup actions.
11
+
6
12
  ## 2026-10-19
7
13
 
8
14
  ### Fixed
9
15
 
16
+ - Agents see a clip's frames as soon as it finishes processing instead of a cached "still processing" answer.
17
+ - Chapter list times now match the player's clock, and each chapter shows a focus ring when you tab to it.
18
+ - Editing a comment puts the cursor after its text, and Reply on another notification moves the cursor to its reply box.
19
+ - Video imports use clearer labels, YouTube links work without connected storage, and embedded players stay clear of timeline hover previews.
10
20
  - Screen Memory can transcribe desktop recordings without a separate FFmpeg install.
11
21
 
12
22
  ### Changed
@@ -57,6 +67,7 @@ from the command menu (Cmd+K → "What's new") or from Settings.
57
67
 
58
68
  ### Fixed
59
69
 
70
+ - Recordings uploaded by a connected agent can now use free Clips storage, like recordings made in the app.
60
71
  - An agent upload too large for free hosted storage is now refused before it starts, with the size limit that applies.
61
72
  - Agents connected to Clips over MCP can now import a transcript file, instead of getting an unknown-tool error.
62
73
  - Clips keeps meeting transcripts recoverable while a save or notes finalization is still in progress.
@@ -1616,28 +1627,4 @@ from the command menu (Cmd+K → "What's new") or from Settings.
1616
1627
  - Fixed the Mac menu bar popover so it opens reliably after launch at login.
1617
1628
  - Localized Clips Settings changelog and extension sidebar labels in every supported interface language.
1618
1629
 
1619
- ## 2026-06-24
1620
-
1621
- ### Added
1622
-
1623
- - Added a language picker and localized app chrome for supported languages.
1624
- - Clips now includes a language picker with Simplified Chinese support for core navigation, empty states, and settings.
1625
-
1626
- ### Improved
1627
-
1628
- - Chrome extension setup now shows a clear all-done confirmation when permissions are ready.
1629
- - Settings now link directly to Agent settings for model, API key, automation, and voice preferences.
1630
- - Settings now puts What's new in a compact sidebar, prioritizes Builder.io setup, and lets you manage S3 storage and AI provider keys in place.
1631
- - Shared clips now expose their full console log stream (all levels, redacted) to connected agents, not just warnings and errors
1632
- - Shared clips now expose their full fetch/XHR network request stream (method, sanitized URL, status, duration) to connected agents, not just failed requests
1633
-
1634
- ### Fixed
1635
-
1636
- - Archive and spaces navigation is more resilient when sidebar count loading fails.
1637
- - Inline Slack playback controls no longer shift when adjusting volume, and compact clip times stay on one line.
1638
-
1639
- ### Changed
1640
-
1641
- - The Chrome extension option now only appears on supported Clips hosts unless a custom deployment explicitly enables it.
1642
-
1643
1630
  For the full list of updates, see the [changelog folder](./changelog/).
@@ -82,7 +82,11 @@ export default defineAction({
82
82
  // The ticket comes first: if it is refused or fails to save, no recording
83
83
  // is left stranded in "uploading" with nothing able to finish it.
84
84
  const recordingId = nanoid();
85
- const ticket = await createRecordingUploadTicket(ownerEmail, recordingId);
85
+ const ticket = await createRecordingUploadTicket(
86
+ ownerEmail,
87
+ recordingId,
88
+ getRequestContext()?.authUserId,
89
+ );
86
90
  let created: Awaited<ReturnType<typeof createRecording.run>>;
87
91
  try {
88
92
  created = await createRecording.run(
@@ -45,7 +45,6 @@ import {
45
45
  editedMarkerMs,
46
46
  editedToOriginal,
47
47
  effectiveDuration,
48
- formatMs,
49
48
  cutRangesOf,
50
49
  isExcluded,
51
50
  parseEdits,
@@ -54,6 +53,8 @@ import {
54
53
  } from "@/lib/timestamp-mapping";
55
54
  import { cn } from "@/lib/utils";
56
55
 
56
+ import { msToClock } from "./scrubber";
57
+
57
58
  /**
58
59
  * Parse "m:ss" or "h:mm:ss" into milliseconds. A bare number is refused, so
59
60
  * a numbered list ("1 Introduction") or a title starting with a number
@@ -300,11 +301,11 @@ function ChapterRows({
300
301
  <button
301
302
  type="button"
302
303
  onClick={() => onSeek(chapter.startMs)}
303
- className="group text-start"
304
+ className="group rounded-sm text-start focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
304
305
  >
305
- <span className="me-2 font-mono text-primary group-hover:underline">
306
- {formatMs(editedMs)}
307
- </span>
306
+ <span className="me-1 font-mono text-primary group-hover:underline">
307
+ {msToClock(editedMs)}
308
+ </span>{" "}
308
309
  <span className="break-words text-foreground">{chapter.title}</span>
309
310
  </button>
310
311
  </li>
@@ -432,7 +433,7 @@ function EditableChapterList({
432
433
  const fresh = visible
433
434
  .map(
434
435
  ({ chapter, editedMs }) =>
435
- `${formatMs(editedMs)} ${oneLine(chapter.title)}`,
436
+ `${msToClock(editedMs)} ${oneLine(chapter.title)}`,
436
437
  )
437
438
  .join("\n");
438
439
  setDraft(failedSave?.draft ?? fresh);
@@ -112,7 +112,11 @@ export const CommentComposer = forwardRef<
112
112
  };
113
113
 
114
114
  useEffect(() => {
115
- if (autoFocus) innerRef.current?.focus();
115
+ const textarea = innerRef.current;
116
+ if (!autoFocus || !textarea) return;
117
+ textarea.focus();
118
+ // Editing opens with the comment's text; keep typing after it.
119
+ textarea.setSelectionRange(textarea.value.length, textarea.value.length);
116
120
  }, [autoFocus]);
117
121
 
118
122
  useAutoResizeCommentTextarea(innerRef, value, rows);
@@ -157,6 +157,7 @@ export function PlayerControls(props: PlayerControlsProps) {
157
157
  }
158
158
  >
159
159
  <Scrubber
160
+ hoverPreviews={!docked}
160
161
  currentMs={currentMs}
161
162
  durationMs={durationMs}
162
163
  onSeek={onSeek}
@@ -17,6 +17,7 @@ import {
17
17
  } from "./scrubber-position";
18
18
 
19
19
  export interface ScrubberProps {
20
+ hoverPreviews?: boolean;
20
21
  currentMs: number;
21
22
  durationMs: number;
22
23
  onSeek: (ms: number) => void;
@@ -33,6 +34,7 @@ const MARKER_GAP_PX = 2;
33
34
 
34
35
  export function Scrubber(props: ScrubberProps) {
35
36
  const {
37
+ hoverPreviews = true,
36
38
  currentMs,
37
39
  durationMs,
38
40
  onSeek,
@@ -235,7 +237,7 @@ export function Scrubber(props: ScrubberProps) {
235
237
  }}
236
238
  >
237
239
  {/* Hover bubble */}
238
- {hoverMs !== null && !tooltip ? (
240
+ {hoverPreviews && hoverMs !== null && !tooltip ? (
239
241
  <div
240
242
  className="absolute -top-8 -translate-x-1/2 rounded bg-black/90 px-2 py-1 text-[11px] text-white pointer-events-none"
241
243
  style={{ left: hoverX }}
@@ -245,7 +247,7 @@ export function Scrubber(props: ScrubberProps) {
245
247
  ) : null}
246
248
 
247
249
  {/* Tooltip (comment / chapter) */}
248
- {tooltip ? (
250
+ {hoverPreviews && tooltip ? (
249
251
  <div
250
252
  data-player-comment-hover
251
253
  className={cn(
@@ -1810,7 +1810,7 @@ const messages = {
1810
1810
  unmuteMicrophone: "Unmute",
1811
1811
  uploadVideo: "Upload video",
1812
1812
  takeScreenshot: "Take screenshot",
1813
- importLoom: "Import Loom",
1813
+ importLoom: "Import video",
1814
1814
  importing: "Importing...",
1815
1815
  import: "Import",
1816
1816
  recordNew: "Record new",
@@ -2057,8 +2057,8 @@ const messages = {
2057
2057
  "This recording is still processing, so Clips kept your copy here. Wait for it, or upload it again.",
2058
2058
  },
2059
2059
  importRoute: {
2060
- pageTitle: "Import Loom — Clips",
2061
- title: "Import Loom",
2060
+ pageTitle: "Import video — Clips",
2061
+ title: "Import video",
2062
2062
  urlPlaceholder: "https://www.loom.com/share/...",
2063
2063
  cta: "Import",
2064
2064
  helperText:
@@ -1,3 +1,7 @@
1
+ export type ClipsExtensionSession = { email: string; token: string };
2
+
3
+ const SESSION_RETRY_DELAYS_MS = [250, 750] as const;
4
+
1
5
  export function buildClipsExtensionBaseUrl(
2
6
  origin: string,
3
7
  appRootPath: string,
@@ -7,3 +11,69 @@ export function buildClipsExtensionBaseUrl(
7
11
  url.hash = "";
8
12
  return url.toString().replace(/\/+$/, "");
9
13
  }
14
+
15
+ export async function fetchClipsExtensionSession(
16
+ fetchSession: () => Promise<Response>,
17
+ shouldContinue: () => boolean,
18
+ pause: (ms: number) => Promise<void> = (ms) =>
19
+ new Promise((resolve) => setTimeout(resolve, ms)),
20
+ ): Promise<ClipsExtensionSession | null> {
21
+ let lastError: unknown;
22
+ for (let attempt = 0; ; attempt += 1) {
23
+ if (!shouldContinue()) return null;
24
+
25
+ let response: Response | undefined;
26
+ try {
27
+ response = await fetchSession();
28
+ } catch (error) {
29
+ lastError = error;
30
+ }
31
+ if (!shouldContinue()) return null;
32
+
33
+ if (response?.ok) {
34
+ const session: unknown = await response.json();
35
+ if (
36
+ session &&
37
+ typeof session === "object" &&
38
+ "error" in session &&
39
+ session.error === "Not authenticated"
40
+ ) {
41
+ return null;
42
+ }
43
+ if (
44
+ !session ||
45
+ typeof session !== "object" ||
46
+ !("email" in session) ||
47
+ typeof session.email !== "string" ||
48
+ !session.email ||
49
+ !("token" in session) ||
50
+ typeof session.token !== "string" ||
51
+ !session.token
52
+ ) {
53
+ throw new Error(
54
+ "The Clips session endpoint returned an invalid session.",
55
+ );
56
+ }
57
+ return { email: session.email, token: session.token };
58
+ }
59
+ if (response && response.status < 500) {
60
+ if (response.status === 401 || response.status === 403) return null;
61
+ throw new Error(
62
+ `The Clips session endpoint failed with HTTP ${response.status}.`,
63
+ );
64
+ }
65
+ if (response) {
66
+ lastError = new Error(
67
+ `The Clips session endpoint failed with HTTP ${response.status}.`,
68
+ );
69
+ }
70
+
71
+ if (!shouldContinue()) return null;
72
+ if (attempt >= SESSION_RETRY_DELAYS_MS.length) {
73
+ throw (
74
+ lastError ?? new Error("The Clips session endpoint is unavailable.")
75
+ );
76
+ }
77
+ await pause(SESSION_RETRY_DELAYS_MS[attempt]);
78
+ }
79
+ }
@@ -31,6 +31,7 @@ import {
31
31
  useRouteLoaderData,
32
32
  } from "react-router";
33
33
  import type { LinksFunction, LoaderFunctionArgs } from "react-router";
34
+ import { toast } from "sonner";
34
35
 
35
36
  import { BugReportDialog } from "@/components/bug-report/bug-report-dialog";
36
37
  import { ClipsCommandMenu } from "@/components/clips-command-menu";
@@ -47,7 +48,11 @@ import {
47
48
  import { Toaster } from "@/components/ui/sonner";
48
49
  import { AppToolkitProvider } from "@/components/ui/toolkit-provider";
49
50
  import { useNavigationState } from "@/hooks/use-navigation-state";
50
- import { buildClipsExtensionBaseUrl } from "@/lib/extension-auth";
51
+ import {
52
+ buildClipsExtensionBaseUrl,
53
+ fetchClipsExtensionSession,
54
+ type ClipsExtensionSession,
55
+ } from "@/lib/extension-auth";
51
56
  import {
52
57
  isLegacyRecordingPath,
53
58
  isRecordingSharePath,
@@ -238,17 +243,24 @@ function ClipsExtensionAuthBridge() {
238
243
  }
239
244
  ).chrome?.runtime;
240
245
 
241
- const response = await fetch(appPath("/_agent-native/auth/session"), {
242
- credentials: "include",
243
- cache: "no-store",
244
- });
245
- const session = (await response.json().catch(() => null)) as {
246
- email?: string;
247
- token?: string;
248
- } | null;
249
- if (cancelled || !response.ok || !session?.email || !session.token) {
246
+ let session: ClipsExtensionSession | null;
247
+ try {
248
+ session = await fetchClipsExtensionSession(
249
+ () =>
250
+ fetch(appPath("/_agent-native/auth/session"), {
251
+ credentials: "include",
252
+ cache: "no-store",
253
+ }),
254
+ () => !cancelled,
255
+ );
256
+ } catch (error) {
257
+ if (!cancelled) {
258
+ console.error("Could not complete Clips extension sign-in.", error);
259
+ toast.error(t("recordingPage.tryAgainMoment"));
260
+ }
250
261
  return;
251
262
  }
263
+ if (cancelled || !session) return;
252
264
 
253
265
  const message = {
254
266
  source: "clips-auth-bridge",
@@ -193,6 +193,9 @@ export default function NotificationsRoute() {
193
193
  </div>
194
194
  <div className="flex items-end gap-2">
195
195
  <CommentComposer
196
+ // A reply to another notification gets its own composer, so
197
+ // the cursor moves to it.
198
+ key={replyFor.id}
196
199
  value={replyText}
197
200
  onChange={setReplyText}
198
201
  onMentionAdd={(mention) =>
@@ -129,6 +129,9 @@ export default function ImportRoute() {
129
129
  const timeoutsRef = useRef<ReturnType<typeof setTimeout>[]>([]);
130
130
 
131
131
  const busy = phase !== "form";
132
+ const requiresStorage = !isYouTubeUrl(loomUrl);
133
+ const storageBlocked =
134
+ requiresStorage && (storageQuery.isError || storageConfigured !== true);
132
135
 
133
136
  const importStages = useMemo(
134
137
  () => [
@@ -173,7 +176,7 @@ export default function ImportRoute() {
173
176
  async (event: FormEvent<HTMLFormElement>) => {
174
177
  event.preventDefault();
175
178
  const url = loomUrl.trim();
176
- if (!url || busy) return;
179
+ if (!url || busy || storageBlocked) return;
177
180
  const failureTitle = isYouTubeUrl(url)
178
181
  ? t("recordRoute.couldNotImportVideo")
179
182
  : t("recordRoute.couldNotImportLoom");
@@ -264,6 +267,7 @@ export default function ImportRoute() {
264
267
  loomUrl,
265
268
  navigate,
266
269
  spaceIdFromUrl,
270
+ storageBlocked,
267
271
  t,
268
272
  ],
269
273
  );
@@ -304,120 +308,121 @@ export default function ImportRoute() {
304
308
  </p>
305
309
 
306
310
  <div className="mx-auto w-full max-w-lg">
307
- {storageQuery.isError ? (
308
- <StorageStatusRetry onRetry={() => void storageQuery.refetch()} />
309
- ) : storageConfigured === null ? (
310
- <ImportPanelSkeleton />
311
- ) : storageConfigured ? (
312
- <div
313
- className={cn(
314
- "overflow-hidden rounded-2xl border border-border bg-card shadow-lg transition-opacity duration-300",
315
- phase === "leaving" && "opacity-0",
316
- )}
317
- >
318
- {busy ? (
319
- <div className="h-1 w-full overflow-hidden bg-muted">
320
- <div
321
- className="h-full bg-primary transition-[width] duration-200 ease-out"
322
- style={{ width: `${Math.min(100, progress)}%` }}
323
- />
311
+ <div
312
+ className={cn(
313
+ "overflow-hidden rounded-2xl border border-border bg-card shadow-lg transition-opacity duration-300",
314
+ phase === "leaving" && "opacity-0",
315
+ )}
316
+ >
317
+ {busy ? (
318
+ <div className="h-1 w-full overflow-hidden bg-muted">
319
+ <div
320
+ className="h-full bg-primary transition-[width] duration-200 ease-out"
321
+ style={{ width: `${Math.min(100, progress)}%` }}
322
+ />
323
+ </div>
324
+ ) : null}
325
+ <div className="p-6">
326
+ {phase === "importing" ? (
327
+ <div className="flex flex-col items-center gap-2 py-8 text-center">
328
+ <AgentMark className="h-6 w-6 animate-pulse text-primary" />
329
+ <p
330
+ key={stageIndex}
331
+ className="text-base font-semibold text-foreground"
332
+ style={{ animation: "clips-benefit-in 260ms ease-out" }}
333
+ >
334
+ {importStages[stageIndex]}
335
+ </p>
336
+ <p className="max-w-xs text-xs text-muted-foreground">
337
+ {t("importRoute.importingSubtitle")}
338
+ </p>
324
339
  </div>
325
- ) : null}
326
- <div className="p-6">
327
- {phase === "importing" ? (
328
- <div className="flex flex-col items-center gap-2 py-8 text-center">
329
- <AgentMark className="h-6 w-6 animate-pulse text-primary" />
330
- <p
331
- key={stageIndex}
332
- className="text-base font-semibold text-foreground"
333
- style={{ animation: "clips-benefit-in 260ms ease-out" }}
334
- >
335
- {importStages[stageIndex]}
340
+ ) : phase === "done" || phase === "leaving" ? (
341
+ <div className="flex flex-col items-center gap-4 py-6 text-center">
342
+ <div className="flex items-center gap-2 text-primary">
343
+ <AgentMark className="h-6 w-6" />
344
+ <p className="text-base font-semibold text-foreground">
345
+ {t("importRoute.doneHeading")}
336
346
  </p>
337
- <p className="max-w-xs text-xs text-muted-foreground">
338
- {t("importRoute.importingSubtitle")}
339
- </p>
340
- </div>
341
- ) : phase === "done" || phase === "leaving" ? (
342
- <div className="flex flex-col items-center gap-4 py-6 text-center">
343
- <div className="flex items-center gap-2 text-primary">
344
- <AgentMark className="h-6 w-6" />
345
- <p className="text-base font-semibold text-foreground">
346
- {t("importRoute.doneHeading")}
347
- </p>
348
- </div>
349
- <ul className="flex flex-col items-start gap-2 text-start">
350
- {benefits.map((benefit, index) => (
351
- <li
352
- key={benefit}
353
- className="flex items-center gap-2 text-sm font-medium text-foreground"
354
- style={{
355
- animation: "clips-benefit-in 220ms ease-out both",
356
- animationDelay: `${index * 110}ms`,
357
- }}
358
- >
359
- <IconCheck className="h-4 w-4 shrink-0 text-primary" />
360
- {benefit}
361
- </li>
362
- ))}
363
- </ul>
364
347
  </div>
365
- ) : (
366
- <form onSubmit={handleSubmit} className="flex flex-col gap-3">
367
- <Input
368
- autoFocus
369
- value={loomUrl}
370
- onChange={(event) => {
371
- setLoomUrl(event.target.value);
372
- setLoomError(null);
373
- }}
374
- placeholder={t("importRoute.urlPlaceholder")}
375
- className="h-12 text-base"
376
- inputMode="url"
377
- />
378
- <Button
379
- type="submit"
380
- className="h-12 w-full gap-2"
381
- disabled={!loomUrl.trim()}
382
- >
383
- <IconLink className="h-4 w-4" />
384
- {t("importRoute.cta")}
385
- </Button>
386
- {loomError ? (
387
- <p className="text-xs leading-relaxed text-destructive">
388
- {loomError}
389
- </p>
390
- ) : null}
391
- </form>
392
- )}
393
- </div>
394
-
395
- {!busy ? (
396
- <div className="flex items-center justify-center gap-4 border-t border-border px-6 py-4">
397
- <button
398
- type="button"
399
- onClick={() => openUploadPicker(recordHref)}
400
- className="inline-flex items-center gap-1.5 text-xs font-medium text-muted-foreground hover:text-foreground"
401
- >
402
- <IconUpload className="h-3.5 w-3.5" />
403
- {t("preRecord.uploadVideo")}
404
- </button>
405
- <Link
406
- to={recordHref}
407
- className="text-xs font-medium text-muted-foreground hover:text-foreground"
408
- >
409
- {t("preRecord.recordNew")}
410
- </Link>
348
+ <ul className="flex flex-col items-start gap-2 text-start">
349
+ {benefits.map((benefit, index) => (
350
+ <li
351
+ key={benefit}
352
+ className="flex items-center gap-2 text-sm font-medium text-foreground"
353
+ style={{
354
+ animation: "clips-benefit-in 220ms ease-out both",
355
+ animationDelay: `${index * 110}ms`,
356
+ }}
357
+ >
358
+ <IconCheck className="h-4 w-4 shrink-0 text-primary" />
359
+ {benefit}
360
+ </li>
361
+ ))}
362
+ </ul>
411
363
  </div>
412
- ) : null}
364
+ ) : (
365
+ <form onSubmit={handleSubmit} className="flex flex-col gap-3">
366
+ <Input
367
+ autoFocus
368
+ value={loomUrl}
369
+ onChange={(event) => {
370
+ setLoomUrl(event.target.value);
371
+ setLoomError(null);
372
+ }}
373
+ placeholder={t("importRoute.urlPlaceholder")}
374
+ className="h-12 text-base"
375
+ inputMode="url"
376
+ />
377
+ <Button
378
+ type="submit"
379
+ className="h-12 w-full gap-2"
380
+ disabled={!loomUrl.trim() || storageBlocked}
381
+ >
382
+ <IconLink className="h-4 w-4" />
383
+ {t("importRoute.cta")}
384
+ </Button>
385
+ {loomError ? (
386
+ <p className="text-xs leading-relaxed text-destructive">
387
+ {loomError}
388
+ </p>
389
+ ) : null}
390
+ </form>
391
+ )}
413
392
  </div>
414
- ) : (
415
- <StorageSetupCard
416
- onConfigured={markStorageConfigured}
417
- connectSource="clips_import_storage_setup_card"
418
- connectFlow="import"
419
- />
420
- )}
393
+
394
+ {!busy ? (
395
+ <div className="flex items-center justify-center gap-4 border-t border-border px-6 py-4">
396
+ <button
397
+ type="button"
398
+ onClick={() => openUploadPicker(recordHref)}
399
+ className="inline-flex items-center gap-1.5 text-xs font-medium text-muted-foreground hover:text-foreground"
400
+ >
401
+ <IconUpload className="h-3.5 w-3.5" />
402
+ {t("preRecord.uploadVideo")}
403
+ </button>
404
+ <Link
405
+ to={recordHref}
406
+ className="text-xs font-medium text-muted-foreground hover:text-foreground"
407
+ >
408
+ {t("preRecord.recordNew")}
409
+ </Link>
410
+ </div>
411
+ ) : null}
412
+ </div>
413
+ {loomUrl.trim() && requiresStorage ? (
414
+ storageQuery.isError ? (
415
+ <StorageStatusRetry onRetry={() => void storageQuery.refetch()} />
416
+ ) : storageConfigured === null ? (
417
+ <ImportPanelSkeleton />
418
+ ) : !storageConfigured ? (
419
+ <StorageSetupCard
420
+ onConfigured={markStorageConfigured}
421
+ connectSource="clips_import_storage_setup_card"
422
+ connectFlow="import"
423
+ />
424
+ ) : null
425
+ ) : null}
421
426
  </div>
422
427
  </div>
423
428
  {uploadInput}
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-10-16
4
+ ---
5
+
6
+ Recordings uploaded by a connected agent can now use free Clips storage, like recordings made in the app.
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-10-19
4
+ ---
5
+
6
+ Agents see a clip's frames as soon as it finishes processing instead of a cached "still processing" answer.