@agent-native/core-corpus 0.201.0-nightly-20261005153421 → 0.201.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 (235) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/analytics/.agents/skills/security/SKILL.md +9 -2
  3. package/corpus/templates/analytics/.agents/skills/session-replay/SKILL.md +14 -4
  4. package/corpus/templates/analytics/.agents/skills/storing-data/SKILL.md +12 -7
  5. package/corpus/templates/analytics/CHANGELOG.md +6 -0
  6. package/corpus/templates/analytics/README.md +2 -2
  7. package/corpus/templates/analytics/changelog/2026-10-02-events-with-a-very-long-name-app-or-page-path-no-longer-caus.md +6 -0
  8. package/corpus/templates/analytics/package.json +1 -0
  9. package/corpus/templates/analytics/server/handlers/first-party-analytics.ts +9 -19
  10. package/corpus/templates/analytics/server/handlers/session-replay.ts +34 -47
  11. package/corpus/templates/analytics/server/lib/first-party-analytics-rollups.ts +9 -6
  12. package/corpus/templates/analytics/server/lib/first-party-analytics.ts +57 -36
  13. package/corpus/templates/analytics/server/lib/indexed-text.ts +53 -0
  14. package/corpus/templates/analytics/server/lib/request-errors.ts +73 -0
  15. package/corpus/templates/analytics/server/lib/session-event-index.ts +6 -16
  16. package/corpus/templates/analytics/server/lib/session-replay.ts +12 -2
  17. package/corpus/templates/analytics/server/plugins/auth.ts +2 -1
  18. package/corpus/templates/analytics/server/plugins/otel.ts +6 -0
  19. package/corpus/templates/assets/.agents/skills/security/SKILL.md +9 -2
  20. package/corpus/templates/assets/.agents/skills/storing-data/SKILL.md +12 -7
  21. package/corpus/templates/assets/CHANGELOG.md +7 -0
  22. package/corpus/templates/assets/README.md +2 -2
  23. package/corpus/templates/assets/package.json +1 -0
  24. package/corpus/templates/assets/server/plugins/auth.ts +2 -1
  25. package/corpus/templates/assets/server/plugins/otel.ts +6 -0
  26. package/corpus/templates/brain/.agents/skills/brain/RUNBOOK.md +2 -1
  27. package/corpus/templates/brain/.agents/skills/brain/SKILL.md +5 -4
  28. package/corpus/templates/brain/.agents/skills/security/SKILL.md +9 -2
  29. package/corpus/templates/brain/.agents/skills/storing-data/SKILL.md +12 -7
  30. package/corpus/templates/brain/CHANGELOG.md +6 -0
  31. package/corpus/templates/brain/README.md +12 -8
  32. package/corpus/templates/brain/changelog/2026-10-05-private-slack-channels-are-searchable-org-wide.md +6 -0
  33. package/corpus/templates/brain/package.json +1 -0
  34. package/corpus/templates/brain/server/lib/audiences.ts +1 -98
  35. package/corpus/templates/brain/server/lib/connectors.ts +2 -166
  36. package/corpus/templates/brain/server/plugins/otel.ts +6 -0
  37. package/corpus/templates/calendar/.agents/skills/security/SKILL.md +9 -2
  38. package/corpus/templates/calendar/.agents/skills/storing-data/SKILL.md +12 -7
  39. package/corpus/templates/calendar/CHANGELOG.md +7 -0
  40. package/corpus/templates/calendar/README.md +2 -2
  41. package/corpus/templates/calendar/package.json +1 -0
  42. package/corpus/templates/calendar/server/plugins/auth.ts +2 -1
  43. package/corpus/templates/calendar/server/plugins/otel.ts +6 -0
  44. package/corpus/templates/chat/.agents/skills/security/SKILL.md +9 -2
  45. package/corpus/templates/chat/.agents/skills/storing-data/SKILL.md +12 -7
  46. package/corpus/templates/chat/README.md +2 -2
  47. package/corpus/templates/clips/.agents/skills/ai-video-tools/SKILL.md +9 -0
  48. package/corpus/templates/clips/.agents/skills/security/SKILL.md +9 -2
  49. package/corpus/templates/clips/CHANGELOG.md +24 -0
  50. package/corpus/templates/clips/README.md +2 -2
  51. package/corpus/templates/clips/actions/lib/audio-chunk-list.ts +23 -0
  52. package/corpus/templates/clips/actions/lib/audio-only-transcription.ts +187 -292
  53. package/corpus/templates/clips/actions/lib/chunked-transcription.ts +151 -0
  54. package/corpus/templates/clips/actions/lib/recording-audio-source.ts +191 -0
  55. package/corpus/templates/clips/actions/request-transcript.ts +81 -135
  56. package/corpus/templates/clips/actions/search-recordings.ts +19 -5
  57. package/corpus/templates/clips/app/components/clips-command-menu.tsx +5 -0
  58. package/corpus/templates/clips/app/components/library/library-layout.tsx +3 -1
  59. package/corpus/templates/clips/app/components/library/search-bar.tsx +5 -0
  60. package/corpus/templates/clips/app/hooks/use-library.ts +1 -0
  61. package/corpus/templates/clips/app/lib/local-recording-upload.ts +13 -6
  62. package/corpus/templates/clips/app/lib/recording-section.ts +1 -1
  63. package/corpus/templates/clips/app/routes/_app.r.$recordingId.tsx +7 -5
  64. package/corpus/templates/clips/app/routes/record.tsx +16 -6
  65. package/corpus/templates/clips/changelog/2026-10-02-long-uploads-an-hour-or-more-now-transcribe-reliably-instead.md +6 -0
  66. package/corpus/templates/clips/changelog/2026-10-05-confirming-discard-recording-now-returns-you-to-your-library.md +6 -0
  67. package/corpus/templates/clips/changelog/2026-10-05-discarding-a-recording-now-removes-its-failed-upload-card-fr.md +6 -0
  68. package/corpus/templates/clips/changelog/2026-10-05-discarding-a-recovered-recording-no-longer-warns-that-it-s-o.md +6 -0
  69. package/corpus/templates/clips/changelog/2026-10-05-search-results-identify-recordings-in-trash-and-open-them-wi.md +6 -0
  70. package/corpus/templates/clips/package.json +1 -0
  71. package/corpus/templates/clips/server/lib/builder-cdn.ts +5 -0
  72. package/corpus/templates/clips/server/lib/builder-media-compression.ts +2 -2
  73. package/corpus/templates/clips/server/plugins/auth.ts +2 -1
  74. package/corpus/templates/clips/server/plugins/otel.ts +6 -0
  75. package/corpus/templates/clips/shared/transcript-failure.ts +5 -0
  76. package/corpus/templates/content/.agents/skills/document-editing/references/databases.md +8 -2
  77. package/corpus/templates/content/.agents/skills/security/SKILL.md +9 -2
  78. package/corpus/templates/content/.agents/skills/storing-data/SKILL.md +12 -7
  79. package/corpus/templates/content/CHANGELOG.md +41 -0
  80. package/corpus/templates/content/README.md +2 -2
  81. package/corpus/templates/content/actions/_database-navigation.ts +523 -344
  82. package/corpus/templates/content/actions/_database-row-mutation.ts +44 -10
  83. package/corpus/templates/content/actions/_database-setup-discovery.ts +9 -3
  84. package/corpus/templates/content/actions/_database-utils.ts +299 -271
  85. package/corpus/templates/content/actions/_property-utils.ts +293 -190
  86. package/corpus/templates/content/actions/_relation-values.ts +3 -1
  87. package/corpus/templates/content/actions/get-content-database.ts +1 -0
  88. package/corpus/templates/content/actions/query-content-database-items.ts +32 -5
  89. package/corpus/templates/content/actions/set-document-property.ts +17 -11
  90. package/corpus/templates/content/app/components/editor/DocumentBlockFields.tsx +21 -6
  91. package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +127 -43
  92. package/corpus/templates/content/app/components/editor/DocumentEditorSkeleton.tsx +159 -50
  93. package/corpus/templates/content/app/components/editor/DocumentToolbar.tsx +12 -28
  94. package/corpus/templates/content/app/components/editor/PageDraftRecovery.tsx +6 -1
  95. package/corpus/templates/content/app/components/editor/TableHoverControls.tsx +139 -70
  96. package/corpus/templates/content/app/components/editor/VisualEditor.tsx +16 -1
  97. package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +180 -75
  98. package/corpus/templates/content/app/components/editor/database/DatabaseViewSkeleton.tsx +76 -0
  99. package/corpus/templates/content/app/components/editor/database/navigation-state.ts +60 -2
  100. package/corpus/templates/content/app/components/editor/database/sidebar.tsx +41 -42
  101. package/corpus/templates/content/app/components/editor/document-editor-layout.ts +18 -0
  102. package/corpus/templates/content/app/components/editor/extensions/NotionExtensions.tsx +11 -3
  103. package/corpus/templates/content/app/components/editor/suggestions/draft-session.ts +15 -7
  104. package/corpus/templates/content/app/components/editor/suggestions/model.ts +13 -0
  105. package/corpus/templates/content/app/components/editor/suggestions/read-only-blocks.ts +136 -0
  106. package/corpus/templates/content/app/components/editor/table-view.ts +73 -0
  107. package/corpus/templates/content/app/components/layout/ContentStartupShell.tsx +1 -1
  108. package/corpus/templates/content/app/components/layout/Layout.tsx +5 -1
  109. package/corpus/templates/content/app/components/sidebar/DocumentSidebar.tsx +57 -16
  110. package/corpus/templates/content/app/components/sidebar/MovePageDialog.tsx +4 -4
  111. package/corpus/templates/content/app/components/sidebar/SidebarNavigationRow.tsx +34 -10
  112. package/corpus/templates/content/app/global.css +16 -8
  113. package/corpus/templates/content/app/hooks/content-action-refresh.ts +14 -13
  114. package/corpus/templates/content/app/hooks/use-content-database.ts +72 -9
  115. package/corpus/templates/content/app/hooks/use-content-personal-navigation.ts +7 -3
  116. package/corpus/templates/content/app/hooks/use-documents.ts +27 -0
  117. package/corpus/templates/content/app/i18n-data.ts +3 -2
  118. package/corpus/templates/content/app/lib/content-landing.ts +3 -31
  119. package/corpus/templates/content/app/lib/files-navigation.ts +182 -10
  120. package/corpus/templates/content/app/lib/files-root-hint.ts +20 -32
  121. package/corpus/templates/content/app/lib/page-startup-hints.ts +108 -0
  122. package/corpus/templates/content/app/lib/private-link-redirect.ts +25 -0
  123. package/corpus/templates/content/app/lib/sidebar-layout-hint.ts +34 -1
  124. package/corpus/templates/content/app/lib/startup-timing.ts +2 -0
  125. package/corpus/templates/content/app/root.tsx +3 -1
  126. package/corpus/templates/content/app/routes/_app.home.tsx +10 -19
  127. package/corpus/templates/content/app/routes/p.$id.tsx +23 -5
  128. package/corpus/templates/content/changelog/2026-10-02-a-page-link-you-cant-open-now-says-so-instead-of-opening-an.md +6 -0
  129. package/corpus/templates/content/changelog/2026-10-02-after-a-refresh-the-files-sidebar-shows-the-open-page-s-fold.md +6 -0
  130. package/corpus/templates/content/changelog/2026-10-02-large-collections-open-faster.md +6 -0
  131. package/corpus/templates/content/changelog/2026-10-02-pages-stay-inside-the-window-at-every-width-long-inline-code.md +6 -0
  132. package/corpus/templates/content/changelog/2026-10-02-pages-with-open-comments-and-collections-with-sorted-or-filt.md +6 -0
  133. package/corpus/templates/content/changelog/2026-10-02-suggest-edits-works-on-pages-written-by-agents-including-pag.md +6 -0
  134. package/corpus/templates/content/changelog/2026-10-02-while-you-suggest-edits-tables-and-other-blocks-that-suggest.md +6 -0
  135. package/corpus/templates/content/changelog/2026-10-05-collection-view-context-now-survives-quick-page-changes-when.md +6 -0
  136. package/corpus/templates/content/changelog/2026-10-05-collection-views-keep-their-saved-rows-accurate-during-refre.md +6 -0
  137. package/corpus/templates/content/changelog/2026-10-05-exact-saved-view-links-keep-collection-retry-available.md +6 -0
  138. package/corpus/templates/content/changelog/2026-10-05-expanding-a-files-folder-now-shares-one-navigation-read-acro.md +6 -0
  139. package/corpus/templates/content/changelog/2026-10-05-files-navigation-retries-temporary-failures-consistently.md +6 -0
  140. package/corpus/templates/content/changelog/2026-10-05-shared-page-owners-keep-edit-and-manage-controls-in-files-an.md +6 -0
  141. package/corpus/templates/content/changelog/2026-10-05-tagging-an-ai-in-a-comment-works-again-instead-of-failing-ri.md +6 -0
  142. package/corpus/templates/content/changelog/2026-10-05-the-home-loading-shell-keeps-the-layout-of-your-last-opened-.md +6 -0
  143. package/corpus/templates/content/changelog/2026-10-05-typing-and-a-space-or-inserting-a-toggle-or-callout-no-longe.md +6 -0
  144. package/corpus/templates/content/docs/product/capabilities/content.workspace.session-restore.md +4 -1
  145. package/corpus/templates/content/e2e/playwright.config.ts +1 -1
  146. package/corpus/templates/content/package.json +2 -1
  147. package/corpus/templates/content/scripts/seed-perf-database.mjs +600 -0
  148. package/corpus/templates/content/scripts/trace-startup.mjs +25 -3
  149. package/corpus/templates/content/server/db/index.ts +1 -1
  150. package/corpus/templates/content/server/lib/suggested-edits.ts +23 -26
  151. package/corpus/templates/content/server/plugins/auth.ts +2 -1
  152. package/corpus/templates/content/server/plugins/otel.ts +6 -0
  153. package/corpus/templates/content/shared/api.ts +17 -0
  154. package/corpus/templates/content/shared/suggestion-diff.ts +124 -38
  155. package/corpus/templates/content/shared/suggestion-formatting.ts +134 -3
  156. package/corpus/templates/crm/.agents/skills/security/SKILL.md +9 -2
  157. package/corpus/templates/crm/.agents/skills/storing-data/SKILL.md +12 -7
  158. package/corpus/templates/crm/package.json +1 -0
  159. package/corpus/templates/crm/server/plugins/otel.ts +6 -0
  160. package/corpus/templates/design/.agents/skills/security/SKILL.md +9 -2
  161. package/corpus/templates/design/.agents/skills/storing-data/SKILL.md +12 -7
  162. package/corpus/templates/design/CHANGELOG.md +41 -0
  163. package/corpus/templates/design/README.md +2 -2
  164. package/corpus/templates/design/actions/list-designs.ts +11 -4
  165. package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +110 -32
  166. package/corpus/templates/design/app/components/design/multi-screen/cross-screen-drop.ts +28 -0
  167. package/corpus/templates/design/app/components/design/multi-screen/types.ts +1 -0
  168. package/corpus/templates/design/app/components/home/DesignHomeLibrary.tsx +79 -0
  169. package/corpus/templates/design/app/components/layout/Header.tsx +2 -4
  170. package/corpus/templates/design/app/components/ui/tabs.tsx +59 -1
  171. package/corpus/templates/design/app/global.css +1 -34
  172. package/corpus/templates/design/app/i18n/en-US.ts +4 -0
  173. package/corpus/templates/design/app/lib/design-filter.ts +4 -2
  174. package/corpus/templates/design/app/pages/Index.tsx +91 -59
  175. package/corpus/templates/design/app/pages/design-editor/commands/cross-screen-element-drop.ts +58 -17
  176. package/corpus/templates/design/changelog/2026-10-01-filter-designs-by-owner.md +6 -0
  177. package/corpus/templates/design/changelog/2026-10-03-layers-dropped-into-visible-canvas-space-beyond-a-screen-now.md +6 -0
  178. package/corpus/templates/design/changelog/2026-10-03-visible-screen-overflow-no-longer-routes-drops-to-the-canvas.md +6 -0
  179. package/corpus/templates/design/changelog/2026-10-05-the-recent-tab-shows-loading-placeholders-until-your-designs.md +6 -0
  180. package/corpus/templates/design/package.json +1 -0
  181. package/corpus/templates/design/server/plugins/auth.ts +2 -1
  182. package/corpus/templates/design/server/plugins/otel.ts +6 -0
  183. package/corpus/templates/dispatch/.agents/skills/security/SKILL.md +9 -2
  184. package/corpus/templates/dispatch/.agents/skills/storing-data/SKILL.md +12 -7
  185. package/corpus/templates/dispatch/README.md +2 -2
  186. package/corpus/templates/dispatch/package.json +1 -0
  187. package/corpus/templates/dispatch/server/plugins/otel.ts +6 -0
  188. package/corpus/templates/dispatch/server/plugins/setup-dispatch.ts +2 -1
  189. package/corpus/templates/factory/.agents/skills/security/SKILL.md +9 -2
  190. package/corpus/templates/factory/.agents/skills/storing-data/SKILL.md +12 -7
  191. package/corpus/templates/factory/package.json +1 -0
  192. package/corpus/templates/factory/server/plugins/otel.ts +6 -0
  193. package/corpus/templates/forms/.agents/skills/security/SKILL.md +9 -2
  194. package/corpus/templates/forms/.agents/skills/storing-data/SKILL.md +12 -7
  195. package/corpus/templates/forms/README.md +2 -2
  196. package/corpus/templates/forms/package.json +1 -0
  197. package/corpus/templates/forms/server/plugins/auth.ts +2 -1
  198. package/corpus/templates/forms/server/plugins/otel.ts +6 -0
  199. package/corpus/templates/mail/.agents/skills/security/SKILL.md +9 -2
  200. package/corpus/templates/mail/.agents/skills/storing-data/SKILL.md +12 -7
  201. package/corpus/templates/mail/CHANGELOG.md +6 -0
  202. package/corpus/templates/mail/README.md +2 -2
  203. package/corpus/templates/mail/package.json +1 -0
  204. package/corpus/templates/mail/server/plugins/auth.ts +2 -1
  205. package/corpus/templates/mail/server/plugins/otel.ts +6 -0
  206. package/corpus/templates/plan/.agents/skills/security/SKILL.md +9 -2
  207. package/corpus/templates/plan/.agents/skills/storing-data/SKILL.md +12 -7
  208. package/corpus/templates/plan/CHANGELOG.md +1 -0
  209. package/corpus/templates/plan/README.md +2 -2
  210. package/corpus/templates/plan/package.json +1 -0
  211. package/corpus/templates/plan/server/plugins/auth.ts +2 -1
  212. package/corpus/templates/plan/server/plugins/otel.ts +6 -0
  213. package/corpus/templates/slides/.agents/skills/security/SKILL.md +9 -2
  214. package/corpus/templates/slides/.agents/skills/storing-data/SKILL.md +12 -7
  215. package/corpus/templates/slides/CHANGELOG.md +23 -0
  216. package/corpus/templates/slides/README.md +2 -2
  217. package/corpus/templates/slides/actions/list-decks.ts +11 -4
  218. package/corpus/templates/slides/app/components/deck/DeckFilterMenu.tsx +44 -36
  219. package/corpus/templates/slides/app/components/home/SlidesHomeLibrary.tsx +79 -0
  220. package/corpus/templates/slides/app/components/layout/Header.tsx +2 -15
  221. package/corpus/templates/slides/app/components/ui/tabs.tsx +59 -0
  222. package/corpus/templates/slides/app/global.css +3 -54
  223. package/corpus/templates/slides/app/i18n/en-US.ts +4 -0
  224. package/corpus/templates/slides/app/lib/deck-filter.ts +5 -2
  225. package/corpus/templates/slides/app/pages/Index.tsx +102 -88
  226. package/corpus/templates/slides/changelog/2026-10-01-filter-decks-by-owner.md +6 -0
  227. package/corpus/templates/slides/changelog/2026-10-02-hide-recent-until-decks-available.md +6 -0
  228. package/corpus/templates/slides/changelog/2026-10-05-the-recent-tab-shows-loading-placeholders-until-your-decks-a.md +6 -0
  229. package/corpus/templates/slides/package.json +1 -0
  230. package/corpus/templates/slides/server/handlers/request-auth-context.ts +8 -0
  231. package/corpus/templates/slides/server/plugins/auth.ts +2 -1
  232. package/corpus/templates/slides/server/plugins/otel.ts +6 -0
  233. package/corpus/templates/tasks/.agents/skills/security/SKILL.md +9 -2
  234. package/corpus/templates/tasks/.agents/skills/storing-data/SKILL.md +12 -7
  235. package/package.json +1 -1
@@ -77,27 +77,59 @@ type ContentDatabaseItemRow = InferSelectModel<
77
77
  typeof schema.contentDatabaseItems
78
78
  >;
79
79
  type DbClient = ReturnType<typeof getDb>;
80
+ type PropertyDefinitionRow = InferSelectModel<
81
+ typeof schema.documentPropertyDefinitions
82
+ >;
80
83
 
81
- async function sourceManagedPropertyIdsForDatabase(
84
+ /**
85
+ * A database's property definitions (in display order) and its source field
86
+ * bindings. Read once per request and pass it down; every list helper below
87
+ * would otherwise re-read both.
88
+ */
89
+ export type DatabasePropertySchema = {
90
+ definitions: PropertyDefinitionRow[];
91
+ sourceFields: Array<{
92
+ propertyId: string | null;
93
+ writeOwner: string;
94
+ readOnly: number;
95
+ sourceMetadataJson: string;
96
+ }>;
97
+ sourceManagedPropertyIds: Set<string>;
98
+ };
99
+
100
+ export async function readDatabasePropertySchema(
82
101
  db: DbClient,
83
102
  databaseId: string,
84
- ) {
85
- const fields = await db
86
- .select({
87
- propertyId: schema.contentDatabaseSourceFields.propertyId,
88
- writeOwner: schema.contentDatabaseSourceFields.writeOwner,
89
- readOnly: schema.contentDatabaseSourceFields.readOnly,
90
- })
91
- .from(schema.contentDatabaseSourceFields)
92
- .innerJoin(
93
- schema.contentDatabaseSources,
94
- eq(
95
- schema.contentDatabaseSources.id,
96
- schema.contentDatabaseSourceFields.sourceId,
97
- ),
98
- )
99
- .where(eq(schema.contentDatabaseSources.databaseId, databaseId));
100
- return contentDatabaseSourceManagedPropertyIds(fields);
103
+ ): Promise<DatabasePropertySchema> {
104
+ const [definitions, sourceFields] = await Promise.all([
105
+ db
106
+ .select()
107
+ .from(schema.documentPropertyDefinitions)
108
+ .where(eq(schema.documentPropertyDefinitions.databaseId, databaseId))
109
+ .orderBy(asc(schema.documentPropertyDefinitions.position)),
110
+ db
111
+ .select({
112
+ propertyId: schema.contentDatabaseSourceFields.propertyId,
113
+ writeOwner: schema.contentDatabaseSourceFields.writeOwner,
114
+ readOnly: schema.contentDatabaseSourceFields.readOnly,
115
+ sourceMetadataJson: schema.contentDatabaseSources.metadataJson,
116
+ })
117
+ .from(schema.contentDatabaseSourceFields)
118
+ .innerJoin(
119
+ schema.contentDatabaseSources,
120
+ eq(
121
+ schema.contentDatabaseSources.id,
122
+ schema.contentDatabaseSourceFields.sourceId,
123
+ ),
124
+ )
125
+ .where(eq(schema.contentDatabaseSources.databaseId, databaseId)),
126
+ ]);
127
+ return {
128
+ definitions,
129
+ sourceFields,
130
+ sourceManagedPropertyIds:
131
+ contentDatabaseSourceManagedPropertyIds(sourceFields),
132
+ };
101
133
  }
102
134
 
103
135
  export function nanoid(size = 12): string {
@@ -650,40 +682,38 @@ export async function listPropertiesForAllDocumentDatabases(
650
682
  export async function listPropertiesForDatabase(
651
683
  databaseId: string,
652
684
  valueDocument?: DocumentRow,
653
- options: { includeContainerDerivedValues?: boolean } = {},
685
+ options: {
686
+ includeContainerDerivedValues?: boolean;
687
+ propertySchema?: DatabasePropertySchema;
688
+ } = {},
654
689
  ) {
655
690
  const db = getDb();
656
- const definitions = await db
657
- .select()
658
- .from(schema.documentPropertyDefinitions)
659
- .where(eq(schema.documentPropertyDefinitions.databaseId, databaseId))
660
- .orderBy(asc(schema.documentPropertyDefinitions.position));
691
+ const { definitions, sourceManagedPropertyIds } =
692
+ options.propertySchema ??
693
+ (await readDatabasePropertySchema(db, databaseId));
661
694
 
662
695
  if (definitions.length === 0) return [];
663
696
  const includeContainerDerivedValues =
664
697
  options.includeContainerDerivedValues !== false;
665
- const [
666
- sourceManagedPropertyIds,
667
- values,
668
- databaseRowNumber,
669
- blockContentByPropertyId,
670
- ] = await Promise.all([
671
- sourceManagedPropertyIdsForDatabase(db, databaseId),
672
- valueDocument
673
- ? db
674
- .select()
675
- .from(schema.documentPropertyValues)
676
- .where(eq(schema.documentPropertyValues.documentId, valueDocument.id))
677
- : [],
678
- valueDocument &&
679
- includeContainerDerivedValues &&
680
- definitions.some((definition) => definition.type === "id")
681
- ? databaseRowNumberForDocument(databaseId, valueDocument.id)
682
- : undefined,
683
- valueDocument
684
- ? blockFieldContentsForDocument(valueDocument.id)
685
- : new Map<string, string>(),
686
- ]);
698
+ const [values, databaseRowNumber, blockContentByPropertyId] =
699
+ await Promise.all([
700
+ valueDocument
701
+ ? db
702
+ .select()
703
+ .from(schema.documentPropertyValues)
704
+ .where(
705
+ eq(schema.documentPropertyValues.documentId, valueDocument.id),
706
+ )
707
+ : [],
708
+ valueDocument &&
709
+ includeContainerDerivedValues &&
710
+ definitions.some((definition) => definition.type === "id")
711
+ ? databaseRowNumberForDocument(databaseId, valueDocument.id)
712
+ : undefined,
713
+ valueDocument
714
+ ? blockFieldContentsForDocument(valueDocument.id)
715
+ : new Map<string, string>(),
716
+ ]);
687
717
 
688
718
  const valueByPropertyId = new Map(
689
719
  values.map((value) => [value.propertyId, value]),
@@ -794,25 +824,15 @@ export async function listPropertiesForDatabase(
794
824
  : property,
795
825
  );
796
826
 
797
- const nextProperties = [];
798
- for (const property of evaluatedProperties) {
799
- if (
800
- property.definition.type === "rollup" &&
801
- includeContainerDerivedValues
802
- ) {
803
- nextProperties.push({
804
- ...property,
805
- value: await evaluatePropertyRollup(property, evaluatedProperties),
806
- });
807
- } else if (property.definition.type === "rollup") {
808
- nextProperties.push({ ...property, value: null });
809
- } else {
810
- nextProperties.push(property);
811
- }
827
+ if (!includeContainerDerivedValues) {
828
+ return evaluatedProperties.map((property) =>
829
+ property.definition.type === "rollup"
830
+ ? { ...property, value: null }
831
+ : property,
832
+ );
812
833
  }
813
-
814
- if (!includeContainerDerivedValues) return nextProperties;
815
- const [withTargets] = await withRelationTargets(db, [nextProperties]);
834
+ const [withRollups] = await withRollupValues([evaluatedProperties]);
835
+ const [withTargets] = await withRelationTargets(db, [withRollups]);
816
836
  return withTargets;
817
837
  }
818
838
 
@@ -844,62 +864,30 @@ function propertyValueKey(documentId: string, propertyId: string) {
844
864
  export async function listPropertiesForDatabaseDocuments(
845
865
  databaseId: string,
846
866
  valueDocuments: DocumentRow[],
867
+ options: {
868
+ /**
869
+ * Blocks field identities need each document's full body: the primary
870
+ * field is the body itself. List reads project documents without
871
+ * `content`, so asking for identities there would report every field as
872
+ * stale against an empty body (and reconcile one diff per row).
873
+ */
874
+ includeBlocksFieldIdentity: boolean;
875
+ propertySchema?: DatabasePropertySchema;
876
+ },
847
877
  ): Promise<Map<string, DocumentProperty[]>> {
848
878
  const db = getDb();
849
- const definitions = await db
850
- .select()
851
- .from(schema.documentPropertyDefinitions)
852
- .where(eq(schema.documentPropertyDefinitions.databaseId, databaseId))
853
- .orderBy(asc(schema.documentPropertyDefinitions.position));
854
879
  const result = new Map<string, DocumentProperty[]>();
855
880
  if (valueDocuments.length === 0) return result;
881
+ const { definitions, sourceManagedPropertyIds } =
882
+ options.propertySchema ??
883
+ (await readDatabasePropertySchema(db, databaseId));
856
884
  if (definitions.length === 0) {
857
885
  for (const document of valueDocuments) result.set(document.id, []);
858
886
  return result;
859
887
  }
860
- const sourceManagedPropertyIds = await sourceManagedPropertyIdsForDatabase(
861
- db,
862
- databaseId,
863
- );
864
888
 
865
889
  const documentIds = valueDocuments.map((document) => document.id);
866
890
  const propertyIds = definitions.map((definition) => definition.id);
867
- const values: Array<typeof schema.documentPropertyValues.$inferSelect> = [];
868
- for (const documentIdChunk of chunks(documentIds, 200)) {
869
- for (const propertyIdChunk of chunks(propertyIds, 200)) {
870
- values.push(
871
- ...(await db
872
- .select()
873
- .from(schema.documentPropertyValues)
874
- .where(
875
- and(
876
- inArray(
877
- schema.documentPropertyValues.documentId,
878
- documentIdChunk,
879
- ),
880
- inArray(
881
- schema.documentPropertyValues.propertyId,
882
- propertyIdChunk,
883
- ),
884
- ),
885
- )),
886
- );
887
- }
888
- }
889
- const valueByDocumentAndProperty = new Map(
890
- values.map((value) => [
891
- propertyValueKey(value.documentId, value.propertyId),
892
- value,
893
- ]),
894
- );
895
-
896
- const rowNumberByDocumentId = definitions.some(
897
- (definition) => definition.type === "id",
898
- )
899
- ? await databaseRowNumbersByDocumentId(databaseId)
900
- : new Map<string, number>();
901
-
902
- const blockContentByDocumentAndProperty = new Map<string, string>();
903
891
  const hasAdditionalBlocksFields = definitions.some((definition) => {
904
892
  const type = definition.type as DocumentPropertyType;
905
893
  return (
@@ -907,55 +895,91 @@ export async function listPropertiesForDatabaseDocuments(
907
895
  !isPrimaryBlocksField(parsePropertyOptions(definition.optionsJson))
908
896
  );
909
897
  });
910
- if (hasAdditionalBlocksFields) {
911
- for (const documentIdChunk of chunks(documentIds, 200)) {
912
- const rows = await db
913
- .select({
914
- documentId: schema.documentBlockFieldContents.documentId,
915
- propertyId: schema.documentBlockFieldContents.propertyId,
916
- content: schema.documentBlockFieldContents.content,
917
- })
918
- .from(schema.documentBlockFieldContents)
919
- .where(
920
- inArray(
921
- schema.documentBlockFieldContents.documentId,
922
- documentIdChunk,
898
+ const [values, rowNumberByDocumentId, blockContentRows] = await Promise.all([
899
+ Promise.all(
900
+ chunks(documentIds, 200).flatMap((documentIdChunk) =>
901
+ chunks(propertyIds, 200).map((propertyIdChunk) =>
902
+ db
903
+ .select()
904
+ .from(schema.documentPropertyValues)
905
+ .where(
906
+ and(
907
+ inArray(
908
+ schema.documentPropertyValues.documentId,
909
+ documentIdChunk,
910
+ ),
911
+ inArray(
912
+ schema.documentPropertyValues.propertyId,
913
+ propertyIdChunk,
914
+ ),
915
+ ),
916
+ ),
917
+ ),
918
+ ),
919
+ ).then((groups) => groups.flat()),
920
+ definitions.some((definition) => definition.type === "id")
921
+ ? databaseRowNumbersByDocumentId(databaseId)
922
+ : new Map<string, number>(),
923
+ hasAdditionalBlocksFields
924
+ ? Promise.all(
925
+ chunks(documentIds, 200).map((documentIdChunk) =>
926
+ db
927
+ .select({
928
+ documentId: schema.documentBlockFieldContents.documentId,
929
+ propertyId: schema.documentBlockFieldContents.propertyId,
930
+ content: schema.documentBlockFieldContents.content,
931
+ })
932
+ .from(schema.documentBlockFieldContents)
933
+ .where(
934
+ inArray(
935
+ schema.documentBlockFieldContents.documentId,
936
+ documentIdChunk,
937
+ ),
938
+ ),
923
939
  ),
924
- );
925
- for (const row of rows) {
926
- blockContentByDocumentAndProperty.set(
927
- propertyValueKey(row.documentId, row.propertyId),
928
- row.content ?? "",
929
- );
930
- }
931
- }
932
- }
940
+ ).then((groups) => groups.flat())
941
+ : [],
942
+ ]);
943
+ const valueByDocumentAndProperty = new Map(
944
+ values.map((value) => [
945
+ propertyValueKey(value.documentId, value.propertyId),
946
+ value,
947
+ ]),
948
+ );
949
+ const blockContentByDocumentAndProperty = new Map(
950
+ blockContentRows.map((row) => [
951
+ propertyValueKey(row.documentId, row.propertyId),
952
+ row.content ?? "",
953
+ ]),
954
+ );
933
955
 
934
- const blocksFieldIdentityById = await readBlocksFieldIdentities({
935
- db,
936
- fields: valueDocuments.flatMap((document) =>
937
- definitions.flatMap((definition) => {
938
- const type = definition.type as DocumentPropertyType;
939
- if (!isBlocksPropertyType(type)) return [];
940
- const options = parsePropertyOptions(definition.optionsJson);
941
- return [
942
- {
943
- documentId: document.id,
944
- propertyId: definition.id,
945
- markdown: resolveBlocksFieldValue({
946
- options,
947
- documentBody: document.content,
948
- blockFieldContent: blockContentByDocumentAndProperty.get(
949
- propertyValueKey(document.id, definition.id),
950
- ),
951
- }),
952
- },
953
- ];
954
- }),
955
- ),
956
- });
956
+ const blocksFieldIdentityById = options.includeBlocksFieldIdentity
957
+ ? await readBlocksFieldIdentities({
958
+ db,
959
+ fields: valueDocuments.flatMap((document) =>
960
+ definitions.flatMap((definition) => {
961
+ const type = definition.type as DocumentPropertyType;
962
+ if (!isBlocksPropertyType(type)) return [];
963
+ const options = parsePropertyOptions(definition.optionsJson);
964
+ return [
965
+ {
966
+ documentId: document.id,
967
+ propertyId: definition.id,
968
+ markdown: resolveBlocksFieldValue({
969
+ options,
970
+ documentBody: document.content,
971
+ blockFieldContent: blockContentByDocumentAndProperty.get(
972
+ propertyValueKey(document.id, definition.id),
973
+ ),
974
+ }),
975
+ },
976
+ ];
977
+ }),
978
+ ),
979
+ })
980
+ : null;
957
981
 
958
- for (const document of valueDocuments) {
982
+ const propertyLists = valueDocuments.map((document) => {
959
983
  const properties = definitions.map((definition) => {
960
984
  const propertyDefinition = serializePropertyDefinition(definition);
961
985
  const storedValue = valueByDocumentAndProperty.get(
@@ -983,7 +1007,8 @@ export async function listPropertiesForDatabaseDocuments(
983
1007
  !definition.systemRole &&
984
1008
  !isComputedPropertyType(propertyDefinition.type) &&
985
1009
  !sourceManagedPropertyIds.has(definition.id),
986
- ...(isBlocksPropertyType(propertyDefinition.type)
1010
+ ...(blocksFieldIdentityById &&
1011
+ isBlocksPropertyType(propertyDefinition.type)
987
1012
  ? {
988
1013
  blocksField: blocksFieldIdentityById.get(
989
1014
  blocksFieldId(document.id, definition.id),
@@ -998,7 +1023,7 @@ export async function listPropertiesForDatabaseDocuments(
998
1023
  .filter((property) => property.definition.type !== "formula")
999
1024
  .map((property) => [property.definition.name, property.value]),
1000
1025
  );
1001
- const evaluatedProperties = properties.map((property) =>
1026
+ return properties.map((property) =>
1002
1027
  property.definition.type === "formula"
1003
1028
  ? {
1004
1029
  ...property,
@@ -1009,58 +1034,136 @@ export async function listPropertiesForDatabaseDocuments(
1009
1034
  }
1010
1035
  : property,
1011
1036
  );
1012
- const nextProperties = [];
1013
- for (const property of evaluatedProperties) {
1014
- if (property.definition.type === "rollup") {
1015
- nextProperties.push({
1016
- ...property,
1017
- value: await evaluatePropertyRollup(property, evaluatedProperties),
1018
- });
1019
- } else {
1020
- nextProperties.push(property);
1021
- }
1022
- }
1023
- result.set(document.id, nextProperties);
1024
- }
1037
+ });
1025
1038
 
1026
- const documentIdsInOrder = [...result.keys()];
1027
- const withTargets = await withRelationTargets(db, [...result.values()]);
1028
- documentIdsInOrder.forEach((documentId, index) => {
1029
- result.set(documentId, withTargets[index]!);
1039
+ const withTargets = await withRelationTargets(
1040
+ db,
1041
+ await withRollupValues(propertyLists),
1042
+ );
1043
+ valueDocuments.forEach((document, index) => {
1044
+ result.set(document.id, withTargets[index]!);
1030
1045
  });
1031
1046
  return result;
1032
1047
  }
1033
1048
 
1034
- async function evaluatePropertyRollup(
1049
+ type RollupPlan = {
1050
+ aggregation: string;
1051
+ linkedDocumentIds: string[];
1052
+ targetProperty: DocumentProperty | null;
1053
+ };
1054
+
1055
+ function rollupPlan(
1035
1056
  property: DocumentProperty,
1036
1057
  properties: DocumentProperty[],
1037
- ): Promise<DocumentPropertyValue> {
1058
+ ): RollupPlan {
1038
1059
  const config = property.definition.options.rollup;
1039
1060
  const relationPropertyId = config?.relationPropertyId ?? null;
1040
1061
  const targetPropertyId = config?.targetPropertyId ?? null;
1041
- const aggregation = config?.aggregation ?? "count";
1042
1062
  const relationProperty = relationPropertyId
1043
1063
  ? properties.find(
1044
1064
  (candidate) => candidate.definition.id === relationPropertyId,
1045
1065
  )
1046
1066
  : null;
1047
- const linkedDocumentIds = relationValueIds(relationProperty?.value);
1067
+ return {
1068
+ aggregation: config?.aggregation ?? "count",
1069
+ linkedDocumentIds: relationValueIds(relationProperty?.value),
1070
+ targetProperty: targetPropertyId
1071
+ ? (properties.find(
1072
+ (candidate) => candidate.definition.id === targetPropertyId,
1073
+ ) ?? null)
1074
+ : null,
1075
+ };
1076
+ }
1048
1077
 
1049
- if (aggregation === "count") return linkedDocumentIds.length;
1050
- if (linkedDocumentIds.length === 0) return null;
1078
+ function rollupReadsLinkedValues(plan: RollupPlan) {
1079
+ return (
1080
+ plan.aggregation !== "count" &&
1081
+ plan.linkedDocumentIds.length > 0 &&
1082
+ plan.targetProperty !== null
1083
+ );
1084
+ }
1051
1085
 
1052
- const targetProperty = targetPropertyId
1053
- ? properties.find(
1054
- (candidate) => candidate.definition.id === targetPropertyId,
1055
- )
1056
- : null;
1057
- if (!targetProperty) return null;
1086
+ /**
1087
+ * Evaluate every rollup in `propertyLists` (one list per row) with one
1088
+ * linked-value read per rollup property across all rows, not one per row.
1089
+ */
1090
+ async function withRollupValues(
1091
+ propertyLists: DocumentProperty[][],
1092
+ ): Promise<DocumentProperty[][]> {
1093
+ if (
1094
+ !propertyLists.some((properties) =>
1095
+ properties.some((property) => property.definition.type === "rollup"),
1096
+ )
1097
+ ) {
1098
+ return propertyLists;
1099
+ }
1100
+ const requests = new Map<
1101
+ string,
1102
+ { targetProperty: DocumentProperty; documentIds: Set<string> }
1103
+ >();
1104
+ for (const properties of propertyLists) {
1105
+ for (const property of properties) {
1106
+ if (property.definition.type !== "rollup") continue;
1107
+ const plan = rollupPlan(property, properties);
1108
+ if (!rollupReadsLinkedValues(plan)) continue;
1109
+ let request = requests.get(property.definition.id);
1110
+ if (!request) {
1111
+ request = {
1112
+ targetProperty: plan.targetProperty!,
1113
+ documentIds: new Set(),
1114
+ };
1115
+ requests.set(property.definition.id, request);
1116
+ }
1117
+ for (const id of plan.linkedDocumentIds) request.documentIds.add(id);
1118
+ }
1119
+ }
1120
+ const linkedValuesByRollupId = new Map(
1121
+ await Promise.all(
1122
+ [...requests].map(async ([rollupId, request]) => {
1123
+ const valueByDocumentId = new Map<string, DocumentPropertyValue>();
1124
+ for (const ids of chunks([...request.documentIds], 500)) {
1125
+ const values = await propertyValuesForLinkedDocuments(
1126
+ ids,
1127
+ request.targetProperty,
1128
+ );
1129
+ ids.forEach((id, index) => {
1130
+ valueByDocumentId.set(id, values[index] ?? null);
1131
+ });
1132
+ }
1133
+ return [rollupId, valueByDocumentId] as const;
1134
+ }),
1135
+ ),
1136
+ );
1137
+ return propertyLists.map((properties) =>
1138
+ properties.map((property) => {
1139
+ if (property.definition.type !== "rollup") return property;
1140
+ const plan = rollupPlan(property, properties);
1141
+ const valueByDocumentId = linkedValuesByRollupId.get(
1142
+ property.definition.id,
1143
+ );
1144
+ return {
1145
+ ...property,
1146
+ value: aggregateRollup(
1147
+ plan,
1148
+ plan.linkedDocumentIds.map(
1149
+ (id) => valueByDocumentId?.get(id) ?? null,
1150
+ ),
1151
+ ),
1152
+ };
1153
+ }),
1154
+ );
1155
+ }
1058
1156
 
1059
- const values = await propertyValuesForLinkedDocuments(
1060
- linkedDocumentIds,
1061
- targetProperty,
1157
+ function aggregateRollup(
1158
+ plan: RollupPlan,
1159
+ linkedValues: DocumentPropertyValue[],
1160
+ ): DocumentPropertyValue {
1161
+ const { aggregation } = plan;
1162
+ if (aggregation === "count") return plan.linkedDocumentIds.length;
1163
+ if (!rollupReadsLinkedValues(plan)) return null;
1164
+ const filledValues = linkedValues.filter(
1165
+ (value) => !isEmptyPropertyValue(value),
1062
1166
  );
1063
- const filledValues = values.filter((value) => !isEmptyPropertyValue(value));
1064
1167
 
1065
1168
  if (aggregation === "count_values") return filledValues.length;
1066
1169
  if (aggregation === "count_unique") {
@@ -52,7 +52,9 @@ async function liveDatabase(
52
52
  }
53
53
 
54
54
  async function canReadDatabase(database: ContentDatabaseRow) {
55
- return !!(await resolveAccess("document", database.documentId));
55
+ return !!(await resolveAccess("document", database.documentId, undefined, {
56
+ skipResourceBody: true,
57
+ }));
56
58
  }
57
59
 
58
60
  /**
@@ -111,6 +111,7 @@ export default defineAction({
111
111
  offset,
112
112
  tableQuery,
113
113
  database: resolved.database,
114
+ accessRole: resolved.accessRole,
114
115
  filesMembershipDatabaseId,
115
116
  sidebarOrder,
116
117
  });
@@ -6,7 +6,11 @@ import type {
6
6
  ContentDatabaseNavigationPageResponse,
7
7
  ContentDatabaseUnavailableResponse,
8
8
  } from "../shared/api.js";
9
- import { getContentDatabaseNavigationPage } from "./_database-navigation.js";
9
+ import { readPersonalDatabaseViewOverrides } from "./_content-database-personal-view.js";
10
+ import {
11
+ getContentDatabaseNavigationPage,
12
+ MAX_NAVIGATION_EXPAND_IDS,
13
+ } from "./_database-navigation.js";
10
14
  import {
11
15
  CONTENT_DATABASE_MAX_READ_LIMIT,
12
16
  contentDatabaseTableQuerySchema,
@@ -49,6 +53,13 @@ export default defineAction({
49
53
  .min(1)
50
54
  .optional()
51
55
  .describe("Opaque cursor returned by the previous matching page"),
56
+ expand: z
57
+ .array(z.string().min(1))
58
+ .max(MAX_NAVIGATION_EXPAND_IDS)
59
+ .optional()
60
+ .describe(
61
+ "Expanded document IDs, most important first; each one this read returns comes back with its first child page in branches, and branchesTruncated is true when the cap left any out",
62
+ ),
52
63
  })
53
64
  .optional()
54
65
  .describe(
@@ -84,22 +95,38 @@ export default defineAction({
84
95
  statusCode: 400,
85
96
  });
86
97
  }
87
- const resolved = await resolveContentDatabaseRead({
88
- databaseId,
89
- documentId,
90
- });
98
+ // A person's saved order is theirs alone, so it is read while the
99
+ // database's access check runs; it is used only if that check passes.
100
+ // The check answers first: an unavailable database returns its typed
101
+ // response even when the saved order cannot be read.
102
+ const [resolved, [earlyOverrides]] = await Promise.all([
103
+ resolveContentDatabaseRead({ databaseId, documentId }),
104
+ Promise.allSettled(
105
+ navigation && userEmail && databaseId
106
+ ? [readPersonalDatabaseViewOverrides(userEmail, databaseId)]
107
+ : [],
108
+ ),
109
+ ]);
91
110
  if (!resolved.available) return resolved;
92
111
 
93
112
  if (navigation) {
94
113
  if (!userEmail) throw new Error("Not authenticated.");
114
+ if (earlyOverrides?.status === "rejected") throw earlyOverrides.reason;
95
115
  return getContentDatabaseNavigationPage({
96
116
  database: resolved.database,
97
117
  userEmail,
118
+ overrides: earlyOverrides
119
+ ? earlyOverrides.value
120
+ : await readPersonalDatabaseViewOverrides(
121
+ userEmail,
122
+ resolved.database.id,
123
+ ),
98
124
  parentId: navigation.parentId,
99
125
  sort: navigation.sort,
100
126
  viewId: navigation.viewId,
101
127
  limit: limit ?? 20,
102
128
  cursor: navigation.cursor,
129
+ expand: navigation.expand,
103
130
  });
104
131
  }
105
132