@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
@@ -8,16 +8,18 @@ export type VideoStorageStatus =
8
8
  | "missing"
9
9
  | "unavailable";
10
10
 
11
- /**
12
- * The local mode a recording actually uses. Recording never waits on
13
- * storage: until storage reads as connected, a cloud recording is written to
14
- * Movies/Clips first and uploads once storage connects.
15
- */
11
+ export function shouldPollVideoStorageStatus(
12
+ authenticated: boolean,
13
+ status: VideoStorageStatus,
14
+ ): boolean {
15
+ return authenticated && status !== "configured";
16
+ }
17
+
18
+ /** Storage status never enables local recording when the configured mode is off. */
16
19
  export function effectiveLocalRecordingMode(
17
20
  mode: LocalRecordingMode,
18
- storageStatus: VideoStorageStatus,
19
21
  ): LocalRecordingMode {
20
- return mode === "off" && storageStatus !== "configured" ? "composed" : mode;
22
+ return mode;
21
23
  }
22
24
 
23
25
  /** A recording saved to disk because storage was not connected yet. */
@@ -32,31 +34,6 @@ export interface RecordFirstFile extends LocalExportedFile {
32
34
  stagedRecordingId?: string;
33
35
  }
34
36
 
35
- /**
36
- * What one file of a capture holds. A separate desktop file carries the
37
- * screen and the take's audio but no camera; a separate camera file holds
38
- * only camera video.
39
- */
40
- export function recordFirstMediaFlags(
41
- role: string,
42
- take: { hasAudio: boolean; hasCamera: boolean },
43
- ): { hasAudio: boolean; hasCamera: boolean } {
44
- if (role === "desktop") return { hasAudio: take.hasAudio, hasCamera: false };
45
- if (role === "camera") return { hasAudio: false, hasCamera: true };
46
- return take;
47
- }
48
-
49
- /**
50
- * The files a stopped record-first capture leaves to upload: its composed
51
- * file, or every file it wrote when there is none.
52
- */
53
- export function recordFirstFilesToQueue<T extends { role: string }>(
54
- files: readonly T[],
55
- ): T[] {
56
- const composed = files.find((file) => file.role === "composed");
57
- return composed ? [composed] : [...files];
58
- }
59
-
60
37
  /**
61
38
  * Per server and account, so a file only ever uploads to who recorded it. A
62
39
  * file saved while signed out (an expired session) goes under "unclaimed"
@@ -1,5 +1,33 @@
1
1
  import type { PendingBrowserRecordingUpload } from "./recorder";
2
2
 
3
+ export type RecordFirstFileUploadError = {
4
+ message: string;
5
+ missing: boolean;
6
+ };
7
+
8
+ export type LocalRecoveryFile = {
9
+ path: string;
10
+ fileName: string;
11
+ savedAt: string;
12
+ error?: string;
13
+ missing?: boolean;
14
+ };
15
+
16
+ export function mapRecordFirstFilesForRecovery(
17
+ files: readonly Pick<LocalRecoveryFile, "path" | "fileName" | "savedAt">[],
18
+ errors: Record<string, RecordFirstFileUploadError>,
19
+ ): LocalRecoveryFile[] {
20
+ return files.map((file) => {
21
+ const failure = errors[file.path];
22
+ return {
23
+ path: file.path,
24
+ fileName: file.fileName,
25
+ savedAt: file.savedAt,
26
+ ...(failure ? { error: failure.message, missing: failure.missing } : {}),
27
+ };
28
+ });
29
+ }
30
+
3
31
  export interface PendingNativeUpload {
4
32
  kind: "native";
5
33
  recordingId: string;
@@ -37,8 +65,9 @@ export function recordingRecoveryKey(upload: PendingDesktopUpload): string {
37
65
  export function shouldShowRecordingRecoveryBanner(
38
66
  view: string,
39
67
  authenticated: boolean,
68
+ hasLocalFiles = false,
40
69
  ): boolean {
41
- return authenticated && view === "recorder";
70
+ return view === "recorder" && (authenticated || hasLocalFiles);
42
71
  }
43
72
 
44
73
  export function classifyRecordingRecoveryError(
@@ -21,6 +21,7 @@ import {
21
21
  REDACTION_HOLD_MESSAGE,
22
22
  } from "./pending-redactions.js";
23
23
  import {
24
+ applyAgentJsonHeaders,
24
25
  CLIPS_AGENT_ACCESS_PARAM,
25
26
  describeAgentAccessFailure,
26
27
  loadPublicAgentAccess,
@@ -37,8 +38,10 @@ export function sendFrameJson<T>(
37
38
  headers: Record<string, string> = {},
38
39
  ): T {
39
40
  setResponseStatus(event, status);
40
- setResponseHeader(event, "Content-Type", "application/json; charset=utf-8");
41
- setResponseHeader(event, "X-Content-Type-Options", "nosniff");
41
+ // These paths end in .jpg, so an error without its own Cache-Control gets
42
+ // the CDN's 4-hour image default, and a client could keep a "still
43
+ // processing" answer after the clip is ready.
44
+ applyAgentJsonHeaders(event);
42
45
  for (const [name, value] of Object.entries(headers)) {
43
46
  setResponseHeader(event, name, value);
44
47
  }
@@ -27,6 +27,8 @@ export interface RecordingUploadTicket extends Record<string, unknown> {
27
27
  tokenHash: string;
28
28
  reservedAt: number;
29
29
  expiresAt: number;
30
+ /** The creator's auth user id, which a hosted-storage reservation is held under. */
31
+ authUserId?: string;
30
32
  }
31
33
 
32
34
  interface RecordingUploadTickets extends Record<string, unknown> {
@@ -37,6 +39,7 @@ export interface RecordingUploadTicketOwner {
37
39
  recordingId: string;
38
40
  ownerEmail: string;
39
41
  orgId: string;
42
+ authUserId?: string;
40
43
  }
41
44
 
42
45
  function tokenHash(token: string): string {
@@ -180,6 +183,7 @@ async function compareAndSwapTickets(
180
183
  export async function createRecordingUploadTicket(
181
184
  ownerEmail: string,
182
185
  recordingId: string,
186
+ authUserId?: string,
183
187
  ): Promise<RecordingUploadTicket & { token: string }> {
184
188
  const token = `${TOKEN_PREFIX}${encodeOwner(ownerEmail)}.${crypto.randomBytes(32).toString("base64url")}`;
185
189
  const reservedAt = Date.now();
@@ -188,6 +192,7 @@ export async function createRecordingUploadTicket(
188
192
  tokenHash: tokenHash(token),
189
193
  reservedAt,
190
194
  expiresAt: reservedAt + RECORDING_UPLOAD_TICKET_TTL_MS,
195
+ ...(authUserId ? { authUserId } : {}),
191
196
  };
192
197
 
193
198
  for (let attempt = 0; attempt < MAX_CAS_ATTEMPTS; attempt += 1) {
@@ -296,5 +301,8 @@ export async function requireRecordingUploadTicketOwner(
296
301
  recordingId,
297
302
  ownerEmail: verified.ownerEmail,
298
303
  orgId: recording.organizationId,
304
+ ...(typeof verified.ticket.authUserId === "string"
305
+ ? { authUserId: verified.ticket.authUserId }
306
+ : {}),
299
307
  };
300
308
  }
@@ -510,6 +510,7 @@ export async function handleRecordingChunk(
510
510
  recordingId?: string;
511
511
  ownerEmail?: string;
512
512
  orgId?: string;
513
+ authUserId?: string;
513
514
  },
514
515
  ) {
515
516
  const recordingId =
@@ -579,6 +580,7 @@ export async function handleRecordingChunk(
579
580
  if (override?.ownerEmail) {
580
581
  ownerEmail = override.ownerEmail;
581
582
  orgId = override.orgId;
583
+ authUserId = override.authUserId;
582
584
  } else {
583
585
  try {
584
586
  const context = await getEventOwnerContext(event);
@@ -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.
@@ -5,6 +5,16 @@ time from the command menu (Cmd+K → "What's new").
5
5
 
6
6
  ## 2026-10-19
7
7
 
8
+ ### Improved
9
+
10
+ - Database views can filter one property more than once, number filters add ≥ and ≤, and filter chips name their operator, like "Status: is not done".
11
+
12
+ ### Fixed
13
+
14
+ - Edits made from a block's menu or a table's row and column menus, such as deleting a table, adding a row or turning off the header row, are now saved instead of coming back after a reload
15
+ - Starting a comment puts the cursor in the comment box, ready to type.
16
+ - Table row and column options now appear for the row or column under the pointer, Escape selects the cell before leaving the editor, wide tables keep their add-column control on screen on phones, and a tap near a table's edge on a touch screen edits the cell there instead of adding a row or column
17
+
8
18
  ### Changed
9
19
 
10
20
  - Apps no longer keep server functions and databases warm by default; set `AGENT_NATIVE_ENABLE_KEEP_WARM=1` to opt in.
@@ -32,6 +42,8 @@ time from the command menu (Cmd+K → "What's new").
32
42
 
33
43
  ### Fixed
34
44
 
45
+ - Opening a page in a new tab while another tab is still saving no longer sends a second save or files a copy to History.
46
+ - Typing steadily in one tab no longer makes another open tab show "This page changed elsewhere" after its edits saved.
35
47
  - Reopening a page after someone else renamed it no longer adds a copy of your edits to Version History
36
48
 
37
49
  ## 2026-10-12
@@ -4,7 +4,10 @@ import { and, eq, isNull } from "drizzle-orm";
4
4
  import { z } from "zod";
5
5
 
6
6
  import { getDb, schema } from "../server/db/index.js";
7
- import { CONTENT_DATABASE_PERSONAL_VIEW_OVERRIDES_VERSION } from "../shared/api.js";
7
+ import {
8
+ CONTENT_DATABASE_FILTER_OPERATORS,
9
+ CONTENT_DATABASE_PERSONAL_VIEW_OVERRIDES_VERSION,
10
+ } from "../shared/api.js";
8
11
  import { resolveContentSpaceAccess } from "./_content-space-access.js";
9
12
  import { filesParentPropertyId } from "./_files-system-properties.js";
10
13
 
@@ -23,20 +26,7 @@ export const sortSchema = z.object({
23
26
  export const filterSchema = z.object({
24
27
  key: z.string(),
25
28
  label: z.string(),
26
- operator: z.enum([
27
- "contains",
28
- "equals",
29
- "does_not_equal",
30
- "greater_than",
31
- "less_than",
32
- "before",
33
- "after",
34
- "between",
35
- "is_checked",
36
- "is_unchecked",
37
- "is_empty",
38
- "is_not_empty",
39
- ]),
29
+ operator: z.enum(CONTENT_DATABASE_FILTER_OPERATORS),
40
30
  value: z.string(),
41
31
  filterGroupId: z.string().optional(),
42
32
  parentFilterGroupId: z.string().optional(),
@@ -29,6 +29,7 @@ import type {
29
29
  ContentDatabaseNavigationSort,
30
30
  ContentDatabasePersonalViewOverrides,
31
31
  } from "../shared/api.js";
32
+ import { contentDatabaseFilterOperatorNeedsValue } from "../shared/database-query.js";
32
33
  import { favoritesSystemIds } from "./_content-favorites.js";
33
34
  import { softDeletedDatabaseDocumentExclusions } from "./_document-discovery-query.js";
34
35
  import { parseDatabaseViewConfig } from "./_property-utils.js";
@@ -114,9 +115,8 @@ function selectedFilterValues(value: string) {
114
115
 
115
116
  function activeNavigationFilter(filter: ContentDatabaseFilter) {
116
117
  return (
117
- ["is_empty", "is_not_empty", "is_checked", "is_unchecked"].includes(
118
- filter.operator,
119
- ) || selectedFilterValues(filter.value).length > 0
118
+ !contentDatabaseFilterOperatorNeedsValue(filter.operator) ||
119
+ selectedFilterValues(filter.value).length > 0
120
120
  );
121
121
  }
122
122
 
@@ -32,14 +32,15 @@ import {
32
32
  parseDocumentFavorite,
33
33
  parseDocumentHideFromSearch,
34
34
  } from "../server/lib/documents.js";
35
- import type {
36
- ContentDatabaseBodyHydration,
37
- ContentDatabaseItem,
38
- ContentDatabaseMembership,
39
- ContentDatabaseResponse,
40
- ContentDatabaseTableQuery,
41
- ContentSidebarViewOrder,
42
- DocumentProperty,
35
+ import {
36
+ CONTENT_DATABASE_FILTER_OPERATORS,
37
+ type ContentDatabaseBodyHydration,
38
+ type ContentDatabaseItem,
39
+ type ContentDatabaseMembership,
40
+ type ContentDatabaseResponse,
41
+ type ContentDatabaseTableQuery,
42
+ type ContentSidebarViewOrder,
43
+ type DocumentProperty,
43
44
  } from "../shared/api.js";
44
45
  import {
45
46
  applyContentDatabaseTableQuery,
@@ -147,20 +148,7 @@ export const contentDatabaseTableQuerySchema = z
147
148
  z.object({
148
149
  key: z.string(),
149
150
  label: z.string(),
150
- operator: z.enum([
151
- "contains",
152
- "equals",
153
- "does_not_equal",
154
- "greater_than",
155
- "less_than",
156
- "before",
157
- "after",
158
- "between",
159
- "is_checked",
160
- "is_unchecked",
161
- "is_empty",
162
- "is_not_empty",
163
- ]),
151
+ operator: z.enum(CONTENT_DATABASE_FILTER_OPERATORS),
164
152
  value: z.string(),
165
153
  filterGroupId: z.string().optional(),
166
154
  parentFilterGroupId: z.string().optional(),
@@ -5,12 +5,13 @@ import { z } from "zod";
5
5
 
6
6
  import { getDb, schema } from "../server/db/index.js";
7
7
  import { syncPrivateViewIconReferences } from "../server/lib/private-icon-references.js";
8
- import type {
9
- ContentDatabaseFilter,
10
- ContentDatabaseFilterOperator,
11
- ContentDatabaseView,
12
- ContentDatabaseViewConfig,
8
+ import {
9
+ CONTENT_DATABASE_FILTER_OPERATORS,
10
+ type ContentDatabaseFilter,
11
+ type ContentDatabaseView,
12
+ type ContentDatabaseViewConfig,
13
13
  } from "../shared/api.js";
14
+ import { contentDatabaseFilterOperatorsForType } from "../shared/database-query.js";
14
15
  import type { DocumentPropertyType } from "../shared/properties.js";
15
16
  import {
16
17
  runDatabaseSetupMutation,
@@ -31,20 +32,7 @@ const filterSchema = z
31
32
  .object({
32
33
  key: z.string().trim().min(1).max(200),
33
34
  label: z.string().max(500),
34
- operator: z.enum([
35
- "contains",
36
- "equals",
37
- "does_not_equal",
38
- "greater_than",
39
- "less_than",
40
- "before",
41
- "after",
42
- "between",
43
- "is_checked",
44
- "is_unchecked",
45
- "is_empty",
46
- "is_not_empty",
47
- ]),
35
+ operator: z.enum(CONTENT_DATABASE_FILTER_OPERATORS),
48
36
  value: z.string(),
49
37
  filterGroupId: z.string().max(200).optional(),
50
38
  parentFilterGroupId: z.string().max(200).optional(),
@@ -147,59 +135,6 @@ export type UpdateContentDatabaseViewAgentInput = z.infer<
147
135
  typeof updateContentDatabaseViewAgentSchema
148
136
  >;
149
137
 
150
- const operatorsByType: Partial<
151
- Record<DocumentPropertyType, readonly ContentDatabaseFilterOperator[]>
152
- > = {
153
- checkbox: ["is_checked", "is_unchecked"],
154
- select: ["contains", "does_not_equal", "is_empty", "is_not_empty"],
155
- status: ["contains", "does_not_equal", "is_empty", "is_not_empty"],
156
- multi_select: ["contains", "does_not_equal", "is_empty", "is_not_empty"],
157
- person: ["contains", "does_not_equal", "is_empty", "is_not_empty"],
158
- number: [
159
- "equals",
160
- "does_not_equal",
161
- "greater_than",
162
- "less_than",
163
- "is_empty",
164
- "is_not_empty",
165
- ],
166
- date: [
167
- "equals",
168
- "does_not_equal",
169
- "before",
170
- "after",
171
- "between",
172
- "is_empty",
173
- "is_not_empty",
174
- ],
175
- created_time: [
176
- "equals",
177
- "does_not_equal",
178
- "before",
179
- "after",
180
- "between",
181
- "is_empty",
182
- "is_not_empty",
183
- ],
184
- last_edited_time: [
185
- "equals",
186
- "does_not_equal",
187
- "before",
188
- "after",
189
- "between",
190
- "is_empty",
191
- "is_not_empty",
192
- ],
193
- };
194
-
195
- const defaultOperators: readonly ContentDatabaseFilterOperator[] = [
196
- "contains",
197
- "equals",
198
- "does_not_equal",
199
- "is_empty",
200
- "is_not_empty",
201
- ];
202
-
203
138
  function uniqueReferences(values: readonly string[], field: string) {
204
139
  if (new Set(values).size !== values.length) {
205
140
  setupError(
@@ -228,9 +163,10 @@ function validateTablePatch(
228
163
  values: readonly string[] | undefined,
229
164
  allowed: ReadonlySet<string>,
230
165
  field: string,
166
+ { unique = true }: { unique?: boolean } = {},
231
167
  ) => {
232
168
  if (!values) return;
233
- uniqueReferences(values, field);
169
+ if (unique) uniqueReferences(values, field);
234
170
  const invalid = values.find((value) => !allowed.has(value));
235
171
  if (invalid) {
236
172
  setupError(
@@ -248,10 +184,13 @@ function validateTablePatch(
248
184
  columnIds,
249
185
  "sorts",
250
186
  );
187
+ // A view may filter one property more than once ("Rank is not empty" and
188
+ // "Rank ≤ 25"), so filter keys only need to exist.
251
189
  assertReferences(
252
190
  patch.filters?.map((filter) => filter.key),
253
191
  columnIds,
254
192
  "filters",
193
+ { unique: false },
255
194
  );
256
195
  assertReferences(
257
196
  patch.columnWidths ? Object.keys(patch.columnWidths) : undefined,
@@ -277,8 +216,9 @@ function validateTablePatch(
277
216
  }
278
217
  for (const filter of patch.filters ?? []) {
279
218
  const type = filter.key === "name" ? "text" : typeById.get(filter.key)!;
280
- const allowed = operatorsByType[type] ?? defaultOperators;
281
- if (!allowed.includes(filter.operator)) {
219
+ if (
220
+ !contentDatabaseFilterOperatorsForType(type).includes(filter.operator)
221
+ ) {
282
222
  setupError(
283
223
  "INVALID_VIEW_REFERENCE",
284
224
  `Filter operator \"${filter.operator}\" is not valid for property \"${filter.key}\".`,
@@ -589,5 +529,5 @@ export function filterIsCompatible(
589
529
  filter: ContentDatabaseFilter,
590
530
  type: DocumentPropertyType,
591
531
  ) {
592
- return (operatorsByType[type] ?? defaultOperators).includes(filter.operator);
532
+ return contentDatabaseFilterOperatorsForType(type).includes(filter.operator);
593
533
  }