@agent-native/core 0.135.0 → 0.135.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +6 -0
  3. package/corpus/core/package.json +1 -1
  4. package/corpus/core/src/sharing/access.ts +44 -2
  5. package/corpus/templates/content/actions/_database-utils.ts +113 -4
  6. package/corpus/templates/content/actions/get-document.ts +84 -3
  7. package/corpus/templates/content/app/components/editor/BuilderBodySyncingNotice.tsx +9 -2
  8. package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +29 -13
  9. package/corpus/templates/content/app/components/editor/DocumentToolbar.tsx +5 -12
  10. package/corpus/templates/content/app/components/editor/body-hydration.ts +12 -6
  11. package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +2 -3
  12. package/corpus/templates/content/app/components/sidebar/DocumentSidebar.tsx +5 -12
  13. package/corpus/templates/content/app/hooks/use-content-database.ts +16 -27
  14. package/corpus/templates/content/app/hooks/use-create-page.ts +3 -6
  15. package/corpus/templates/content/app/hooks/use-document-properties.ts +9 -16
  16. package/corpus/templates/content/app/hooks/use-document-versions.ts +3 -3
  17. package/corpus/templates/content/app/hooks/use-documents.ts +85 -29
  18. package/corpus/templates/content/app/hooks/use-notion.ts +15 -13
  19. package/corpus/templates/content/app/i18n-data.ts +32 -1
  20. package/corpus/templates/content/app/lib/document-query.ts +36 -0
  21. package/corpus/templates/content/changelog/2026-08-02-pages-opened-from-a-database-now-keep-that-database-s-fields.md +6 -0
  22. package/corpus/templates/content/server/lib/document-context.ts +11 -6
  23. package/corpus/templates/content/shared/api.ts +8 -0
  24. package/dist/collab/struct-routes.d.ts +1 -1
  25. package/dist/notifications/routes.d.ts +3 -3
  26. package/dist/observability/routes.d.ts +3 -3
  27. package/dist/progress/routes.d.ts +1 -1
  28. package/dist/provider-api/actions/custom-provider-registration.d.ts +6 -6
  29. package/dist/provider-api/actions/provider-api.d.ts +4 -4
  30. package/dist/resources/handlers.d.ts +1 -1
  31. package/dist/server/transcribe-voice.d.ts +1 -1
  32. package/dist/sharing/access.d.ts.map +1 -1
  33. package/dist/sharing/access.js +32 -2
  34. package/dist/sharing/access.js.map +1 -1
  35. package/package.json +1 -1
  36. package/src/sharing/access.ts +44 -2
@@ -57,6 +57,8 @@ import type {
57
57
  import type { Query, QueryClient } from "@tanstack/react-query";
58
58
  import { useQueryClient } from "@tanstack/react-query";
59
59
 
60
+ import { documentQueryFilter } from "../lib/document-query";
61
+
60
62
  export function contentDatabaseQueryKey(documentId: string) {
61
63
  return ["action", "get-content-database", { documentId }] as const;
62
64
  }
@@ -421,9 +423,7 @@ export function clearDeletedContentDatabaseFromCache(
421
423
  documentId: string,
422
424
  ) {
423
425
  queryClient.removeQueries(contentDatabaseQueryFilter(documentId));
424
- queryClient.removeQueries({
425
- queryKey: ["action", "get-document", { id: documentId }],
426
- });
426
+ queryClient.removeQueries(documentQueryFilter(documentId));
427
427
  queryClient.invalidateQueries({
428
428
  queryKey: ["action", "get-content-database"],
429
429
  });
@@ -713,20 +713,14 @@ export function useCreateContentDatabase(documentId: string | null) {
713
713
  {
714
714
  onSuccess: (data) => {
715
715
  if (documentId) {
716
- queryClient.invalidateQueries({
717
- queryKey: ["action", "get-document", { id: documentId }],
718
- });
716
+ queryClient.invalidateQueries(documentQueryFilter(documentId));
719
717
  queryClient.invalidateQueries({
720
718
  queryKey: contentDatabaseQueryKey(documentId),
721
719
  });
722
720
  }
723
- queryClient.invalidateQueries({
724
- queryKey: [
725
- "action",
726
- "get-document",
727
- { id: data.database.documentId },
728
- ],
729
- });
721
+ queryClient.invalidateQueries(
722
+ documentQueryFilter(data.database.documentId),
723
+ );
730
724
  queryClient.invalidateQueries({
731
725
  queryKey: ["action", "list-documents"],
732
726
  });
@@ -743,17 +737,11 @@ export function useCreateInlineContentDatabase(hostDocumentId: string | null) {
743
737
  >("create-inline-content-database", {
744
738
  onSuccess: (data) => {
745
739
  if (hostDocumentId) {
746
- queryClient.invalidateQueries({
747
- queryKey: ["action", "get-document", { id: hostDocumentId }],
748
- });
740
+ queryClient.invalidateQueries(documentQueryFilter(hostDocumentId));
749
741
  }
750
- queryClient.invalidateQueries({
751
- queryKey: [
752
- "action",
753
- "get-document",
754
- { id: data.block.databaseDocumentId },
755
- ],
756
- });
742
+ queryClient.invalidateQueries(
743
+ documentQueryFilter(data.block.databaseDocumentId),
744
+ );
757
745
  queryClient.invalidateQueries({
758
746
  queryKey: contentDatabaseQueryKey(data.block.databaseDocumentId),
759
747
  });
@@ -1488,7 +1476,10 @@ export function useProcessBuilderBodyHydration(documentId: string) {
1488
1476
 
1489
1477
  export function invalidateBuilderBodyHydrationQueries(
1490
1478
  queryClient: {
1491
- invalidateQueries: (filters: { queryKey: readonly unknown[] }) => unknown;
1479
+ invalidateQueries: (filters: {
1480
+ queryKey?: readonly unknown[];
1481
+ predicate?: (query: { queryKey: readonly unknown[] }) => boolean;
1482
+ }) => unknown;
1492
1483
  },
1493
1484
  documentId: string,
1494
1485
  variables?: Pick<ProcessBuilderBodyHydrationRequest, "documentId"> | null,
@@ -1501,9 +1492,7 @@ export function invalidateBuilderBodyHydrationQueries(
1501
1492
  queryKey: ["action", "get-content-database-source", { documentId }],
1502
1493
  });
1503
1494
  if (variables?.documentId) {
1504
- queryClient.invalidateQueries({
1505
- queryKey: ["action", "get-document", { id: variables.documentId }],
1506
- });
1495
+ queryClient.invalidateQueries(documentQueryFilter(variables.documentId));
1507
1496
  }
1508
1497
  }
1509
1498
 
@@ -12,6 +12,7 @@ import {
12
12
  import { useContentSpaces } from "@/hooks/use-content-spaces";
13
13
  import { useCreateDocument } from "@/hooks/use-documents";
14
14
  import { useLocalStorage } from "@/hooks/use-local-storage";
15
+ import { documentQueryFilter } from "@/lib/document-query";
15
16
  import { markDocumentCreationPending } from "@/lib/optimistic-document";
16
17
 
17
18
  const LIST_DOCUMENTS_QUERY_KEY = [
@@ -103,9 +104,7 @@ export function useCreatePage(opts?: {
103
104
  );
104
105
  // Replace optimistic doc with real server doc + clear any 404 error
105
106
  // state from the in-flight fetch that ran before create completed.
106
- queryClient.invalidateQueries({
107
- queryKey: ["action", "get-document", { id }],
108
- });
107
+ queryClient.invalidateQueries(documentQueryFilter(id));
109
108
  queryClient.invalidateQueries({
110
109
  queryKey: ["action", "list-documents"],
111
110
  });
@@ -115,9 +114,7 @@ export function useCreatePage(opts?: {
115
114
  queryClient.invalidateQueries({
116
115
  queryKey: ["action", "list-documents"],
117
116
  });
118
- queryClient.removeQueries({
119
- queryKey: ["action", "get-document", { id }],
120
- });
117
+ queryClient.removeQueries(documentQueryFilter(id));
121
118
  if (shouldNavigate) navigate("/");
122
119
  toast.error("Failed to create page", {
123
120
  description:
@@ -22,7 +22,10 @@ import {
22
22
  contentDatabaseQueryKey,
23
23
  removeDocumentPropertyFromDatabaseResponse,
24
24
  } from "./use-content-database";
25
- import { documentPropertiesQueryKey } from "./use-documents";
25
+ import {
26
+ documentPropertiesQueryKey,
27
+ documentQueryFilter,
28
+ } from "./use-documents";
26
29
 
27
30
  type DatabaseScopedRequest = { databaseId: string };
28
31
 
@@ -91,9 +94,7 @@ export function useConfigureDocumentProperty(
91
94
  queryClient.invalidateQueries({
92
95
  queryKey: documentPropertiesQueryKey(documentId, databaseId),
93
96
  });
94
- queryClient.invalidateQueries({
95
- queryKey: ["action", "get-document", { id: documentId }],
96
- });
97
+ queryClient.invalidateQueries(documentQueryFilter(documentId));
97
98
  queryClient.invalidateQueries(
98
99
  contentDatabaseConstrainedQueryFilter(databaseDocumentId),
99
100
  );
@@ -158,9 +159,7 @@ export function useSetDocumentProperty(
158
159
  queryClient.invalidateQueries({
159
160
  queryKey: documentPropertiesQueryKey(variables.documentId, databaseId),
160
161
  });
161
- queryClient.invalidateQueries({
162
- queryKey: ["action", "get-document", { id: variables.documentId }],
163
- });
162
+ queryClient.invalidateQueries(documentQueryFilter(variables.documentId));
164
163
  queryClient.invalidateQueries(
165
164
  contentDatabaseConstrainedQueryFilter(databaseDocumentId),
166
165
  );
@@ -195,9 +194,7 @@ export function useDuplicateDocumentProperty(
195
194
  queryClient.invalidateQueries({
196
195
  queryKey: documentPropertiesQueryKey(documentId, databaseId),
197
196
  });
198
- queryClient.invalidateQueries({
199
- queryKey: ["action", "get-document", { id: documentId }],
200
- });
197
+ queryClient.invalidateQueries(documentQueryFilter(documentId));
201
198
  queryClient.invalidateQueries({
202
199
  ...contentDatabaseQueryFilter(databaseDocumentId),
203
200
  });
@@ -220,9 +217,7 @@ export function useReorderDocumentProperty(
220
217
  queryClient.invalidateQueries({
221
218
  queryKey: documentPropertiesQueryKey(documentId, databaseId),
222
219
  });
223
- queryClient.invalidateQueries({
224
- queryKey: ["action", "get-document", { id: documentId }],
225
- });
220
+ queryClient.invalidateQueries(documentQueryFilter(documentId));
226
221
  queryClient.invalidateQueries({
227
222
  queryKey: contentDatabaseQueryKey(databaseDocumentId),
228
223
  });
@@ -277,9 +272,7 @@ export function useDeleteDocumentProperty(
277
272
  queryClient.invalidateQueries({
278
273
  queryKey: documentPropertiesQueryKey(documentId, databaseId),
279
274
  });
280
- queryClient.invalidateQueries({
281
- queryKey: ["action", "get-document", { id: documentId }],
282
- });
275
+ queryClient.invalidateQueries(documentQueryFilter(documentId));
283
276
  queryClient.invalidateQueries({
284
277
  ...contentDatabaseQueryFilter(databaseDocumentId),
285
278
  });
@@ -5,6 +5,8 @@ import {
5
5
  import type { Document, DocumentVersion } from "@shared/api";
6
6
  import { useQueryClient } from "@tanstack/react-query";
7
7
 
8
+ import { documentQueryFilter } from "./use-documents";
9
+
8
10
  export function useDocumentVersions(documentId: string | null) {
9
11
  return useActionQuery<DocumentVersion[]>(
10
12
  "list-document-versions",
@@ -29,9 +31,7 @@ export function useRestoreDocumentVersion(documentId: string) {
29
31
  queryClient.invalidateQueries({
30
32
  queryKey: ["action", "list-document-versions", { documentId }],
31
33
  });
32
- queryClient.invalidateQueries({
33
- queryKey: ["action", "get-document", { id: documentId }],
34
- });
34
+ queryClient.invalidateQueries(documentQueryFilter(documentId));
35
35
  },
36
36
  },
37
37
  );
@@ -19,23 +19,50 @@ import { useQueryClient } from "@tanstack/react-query";
19
19
  import { toast } from "sonner";
20
20
 
21
21
  import type { DocumentUpdateConflictResponse } from "../../actions/update-document";
22
+ import {
23
+ documentQueryFilter,
24
+ documentQueryKey,
25
+ type DocumentQueryContext,
26
+ } from "../lib/document-query";
22
27
  import {
23
28
  removeOptimisticItemFromContentDatabase,
24
29
  useRestoreContentDatabase,
25
30
  } from "./use-content-database";
26
31
 
32
+ export {
33
+ documentQueryFilter,
34
+ documentQueryKey,
35
+ type DocumentQueryContext,
36
+ } from "../lib/document-query";
37
+
27
38
  export type { DocumentUpdateConflictResponse };
28
39
 
40
+ export type PageOwnedDocumentCachePatch = Pick<
41
+ Partial<Document>,
42
+ | "id"
43
+ | "parentId"
44
+ | "title"
45
+ | "content"
46
+ | "description"
47
+ | "icon"
48
+ | "position"
49
+ | "isFavorite"
50
+ | "hideFromSearch"
51
+ | "visibility"
52
+ | "accessRole"
53
+ | "canEdit"
54
+ | "canManage"
55
+ | "source"
56
+ | "createdAt"
57
+ | "updatedAt"
58
+ >;
59
+
29
60
  export const LIST_DOCUMENTS_QUERY_KEY = [
30
61
  "action",
31
62
  "list-documents",
32
63
  undefined,
33
64
  ] as const;
34
65
 
35
- export function documentQueryKey(documentId: string) {
36
- return ["action", "get-document", { id: documentId }] as const;
37
- }
38
-
39
66
  export function documentPropertiesQueryKey(
40
67
  documentId: string,
41
68
  databaseId: string,
@@ -74,7 +101,27 @@ export function mergeDocumentIntoDocumentCache(
74
101
  old: unknown,
75
102
  document: Document,
76
103
  ) {
77
- return old && typeof old === "object" ? { ...old, ...document } : document;
104
+ const pageOwnedPatch: PageOwnedDocumentCachePatch = {
105
+ id: document.id,
106
+ parentId: document.parentId,
107
+ title: document.title,
108
+ content: document.content,
109
+ description: document.description,
110
+ icon: document.icon,
111
+ position: document.position,
112
+ isFavorite: document.isFavorite,
113
+ hideFromSearch: document.hideFromSearch,
114
+ visibility: document.visibility,
115
+ accessRole: document.accessRole,
116
+ canEdit: document.canEdit,
117
+ canManage: document.canManage,
118
+ source: document.source,
119
+ createdAt: document.createdAt,
120
+ updatedAt: document.updatedAt,
121
+ };
122
+ return old && typeof old === "object"
123
+ ? { ...old, ...pageOwnedPatch }
124
+ : pageOwnedPatch;
78
125
  }
79
126
 
80
127
  export function mergeDocumentIntoListDocumentsCache(
@@ -157,9 +204,9 @@ function patchDocumentWithFavoriteMembershipInDatabaseCache(
157
204
  export function patchDocumentCaches(
158
205
  queryClient: Pick<QueryClient, "setQueryData" | "setQueriesData">,
159
206
  documentId: string,
160
- patch: Partial<Document>,
207
+ patch: PageOwnedDocumentCachePatch,
161
208
  ) {
162
- queryClient.setQueryData(documentQueryKey(documentId), (old: unknown) =>
209
+ queryClient.setQueriesData(documentQueryFilter(documentId), (old: unknown) =>
163
210
  old && typeof old === "object" ? { ...old, ...patch } : old,
164
211
  );
165
212
  queryClient.setQueryData(LIST_DOCUMENTS_QUERY_KEY, (old: unknown) =>
@@ -219,7 +266,7 @@ export function patchContentSpaceNameCaches(
219
266
  export function documentUpdateSuccessPatch(
220
267
  data: DocumentUpdateResponse,
221
268
  variables: DocumentUpdateRequestWithCas,
222
- ): Partial<Document> {
269
+ ): PageOwnedDocumentCachePatch {
223
270
  return {
224
271
  updatedAt: data.updatedAt,
225
272
  ...(variables.title !== undefined ? { title: data.title } : {}),
@@ -289,13 +336,28 @@ export const DOCUMENT_QUERY_FRESHNESS_OPTIONS = {
289
336
  retry: false,
290
337
  };
291
338
 
292
- export function useDocument(id: string | null) {
293
- return useActionQuery<Document>("get-document", id ? { id } : undefined, {
294
- enabled: !!id,
295
- // Doc-not-found / no-access errors are deterministic — retrying just keeps
296
- // the spinner up for ~7s before the UI can render "Not found".
297
- ...DOCUMENT_QUERY_FRESHNESS_OPTIONS,
298
- });
339
+ export function useDocument(
340
+ id: string | null,
341
+ context: DocumentQueryContext = {},
342
+ ) {
343
+ return useActionQuery<Document>(
344
+ "get-document",
345
+ id
346
+ ? {
347
+ id,
348
+ ...(context.databaseId ? { databaseId: context.databaseId } : {}),
349
+ ...(context.databaseDocumentId
350
+ ? { databaseDocumentId: context.databaseDocumentId }
351
+ : {}),
352
+ }
353
+ : undefined,
354
+ {
355
+ enabled: !!id,
356
+ // Doc-not-found / no-access errors are deterministic — retrying just keeps
357
+ // the spinner up for ~7s before the UI can render "Not found".
358
+ ...DOCUMENT_QUERY_FRESHNESS_OPTIONS,
359
+ },
360
+ );
299
361
  }
300
362
 
301
363
  export interface PreviewDocumentDraftRecord {
@@ -368,7 +430,7 @@ export function useUpdateDocument() {
368
430
  };
369
431
  if (Object.keys(optimisticPatch).length === 0) return undefined;
370
432
 
371
- const documentKey = documentQueryKey(variables.id);
433
+ const documentFilter = documentQueryFilter(variables.id);
372
434
  const databaseFilter = {
373
435
  queryKey: ["action", "get-content-database"],
374
436
  } as const;
@@ -376,14 +438,14 @@ export function useUpdateDocument() {
376
438
  queryKey: ["action", "list-content-spaces"],
377
439
  } as const;
378
440
  await Promise.all([
379
- queryClient.cancelQueries({ queryKey: documentKey }),
441
+ queryClient.cancelQueries(documentFilter),
380
442
  queryClient.cancelQueries({ queryKey: LIST_DOCUMENTS_QUERY_KEY }),
381
443
  queryClient.cancelQueries(databaseFilter),
382
444
  queryClient.cancelQueries(contentSpacesFilter),
383
445
  ]);
384
446
 
385
447
  const previous: Array<[readonly unknown[], unknown]> = [
386
- [documentKey, queryClient.getQueryData(documentKey)],
448
+ ...queryClient.getQueriesData(documentFilter),
387
449
  [
388
450
  LIST_DOCUMENTS_QUERY_KEY,
389
451
  queryClient.getQueryData(LIST_DOCUMENTS_QUERY_KEY),
@@ -423,8 +485,8 @@ export function useUpdateDocument() {
423
485
  // just-applied write.
424
486
  if (isDocumentUpdateConflict(data)) {
425
487
  const serverDocument = data.document;
426
- queryClient.setQueryData(
427
- ["action", "get-document", { id: variables.id }],
488
+ queryClient.setQueriesData(
489
+ documentQueryFilter(variables.id),
428
490
  (old: unknown) =>
429
491
  mergeDocumentIntoDocumentCache(old, serverDocument),
430
492
  );
@@ -453,9 +515,7 @@ export function useUpdateDocument() {
453
515
  queryKey: ["action", "get-content-database"],
454
516
  });
455
517
  }
456
- queryClient.invalidateQueries({
457
- queryKey: ["action", "get-document", { id: variables.id }],
458
- });
518
+ queryClient.invalidateQueries(documentQueryFilter(variables.id));
459
519
  queryClient.invalidateQueries({
460
520
  queryKey: ["action", "list-documents"],
461
521
  });
@@ -525,9 +585,7 @@ export function useDeleteDocument() {
525
585
  queryClient.invalidateQueries({
526
586
  queryKey: ["action", "list-documents"],
527
587
  });
528
- queryClient.invalidateQueries({
529
- queryKey: ["action", "get-document", { id: variables.id }],
530
- });
588
+ queryClient.invalidateQueries(documentQueryFilter(variables.id));
531
589
  queryClient.invalidateQueries({
532
590
  queryKey: ["action", "get-content-database"],
533
591
  });
@@ -606,9 +664,7 @@ export function useMoveDocument() {
606
664
  queryClient.invalidateQueries({
607
665
  queryKey: ["action", "list-documents"],
608
666
  });
609
- queryClient.invalidateQueries({
610
- queryKey: ["action", "get-document", { id: variables.id }],
611
- });
667
+ queryClient.invalidateQueries(documentQueryFilter(variables.id));
612
668
  },
613
669
  },
614
670
  );
@@ -16,6 +16,8 @@ import type {
16
16
  import { useQuery, useQueryClient } from "@tanstack/react-query";
17
17
  import { useEffect, useRef } from "react";
18
18
 
19
+ import { documentQueryFilter } from "./use-documents";
20
+
19
21
  // The server signs a `redirect` query param into the OAuth `state` and the
20
22
  // callback route sends the user back there once the connection completes. If
21
23
  // we never send it, `state.redirectPath` defaults to "/" server-side and
@@ -55,9 +57,7 @@ export function invalidateDocumentQueries(
55
57
  // path after every debounced editor save). Invalidating the bare ["action"]
56
58
  // key would refetch every mounted query app-wide (sidebar tree, comments,
57
59
  // database views, search, connection status, ...) on each cycle.
58
- queryClient.invalidateQueries({
59
- queryKey: ["action", "get-document", { id: documentId }],
60
- });
60
+ queryClient.invalidateQueries(documentQueryFilter(documentId));
61
61
  queryClient.invalidateQueries({
62
62
  queryKey: ["action", "list-documents"],
63
63
  });
@@ -155,21 +155,23 @@ export function useDocumentSyncStatus(
155
155
 
156
156
  lastObservedSyncedAtRef.current = query.data.lastSyncedAt;
157
157
 
158
- const cachedDocument = queryClient.getQueryData<Document>([
159
- "action",
160
- "get-document",
161
- { id: normalizedDocumentId },
162
- ]);
158
+ const cachedDocuments = queryClient
159
+ .getQueriesData<Document>(documentQueryFilter(normalizedDocumentId))
160
+ .map(([, document]) => document)
161
+ .filter((document): document is Document => !!document);
163
162
  const syncedLocalUpdatedAt = query.data.lastPushedLocalUpdatedAt;
164
163
 
165
164
  if (
166
- cachedDocument?.updatedAt &&
165
+ cachedDocuments.some(
166
+ (cachedDocument) =>
167
+ !!cachedDocument.updatedAt &&
168
+ !!syncedLocalUpdatedAt &&
169
+ syncedLocalUpdatedAt > cachedDocument.updatedAt,
170
+ ) &&
167
171
  syncedLocalUpdatedAt &&
168
- syncedLocalUpdatedAt > cachedDocument.updatedAt
172
+ cachedDocuments.length > 0
169
173
  ) {
170
- queryClient.invalidateQueries({
171
- queryKey: ["action", "get-document", { id: normalizedDocumentId }],
172
- });
174
+ queryClient.invalidateQueries(documentQueryFilter(normalizedDocumentId));
173
175
  queryClient.invalidateQueries({ queryKey: ["action", "list-documents"] });
174
176
  }
175
177
  }, [
@@ -3120,9 +3120,12 @@ const enUS = {
3120
3120
  liveDocumentSaveBeforeSyncFailed:
3121
3121
  "The live document could not be saved before syncing.",
3122
3122
  documentTitle: "Document title",
3123
- builderBodySyncing: "Content is still syncing from Builder",
3123
+ builderBodySyncing: "This page's content is still syncing from Builder",
3124
3124
  builderBodySyncingDescription:
3125
3125
  "Editing is paused until the Builder body finishes syncing, so the existing article content is not overwritten.",
3126
+ pageBodySyncing: "This page's content is still syncing",
3127
+ pageBodySyncingDescription:
3128
+ "Editing is paused until the page body finishes syncing, so existing content is not overwritten.",
3126
3129
  localFileSavedHistoryNotUpdated:
3127
3130
  "Local file saved, but history was not updated",
3128
3131
  reorderField: "Reorder {{name}}",
@@ -5615,6 +5618,9 @@ const editorMessagesByLocale = {
5615
5618
  builderBodySyncing: "内容仍在从 Builder 同步",
5616
5619
  builderBodySyncingDescription:
5617
5620
  "同步 Builder 正文完成前会暂停编辑,避免覆盖现有文章内容。",
5621
+ pageBodySyncing: "此页面的内容仍在同步",
5622
+ pageBodySyncingDescription:
5623
+ "在页面正文完成同步之前,编辑会暂停,以免覆盖现有内容。",
5618
5624
  creatingDatabase: "正在创建内联数据库...",
5619
5625
  databaseCreated: "内联数据库已创建",
5620
5626
  emptyBlockPlaceholder: "按“/”使用命令",
@@ -5960,6 +5966,9 @@ const editorMessagesByLocale = {
5960
5966
  builderBodySyncing: "El contenido aún se está sincronizando desde Builder",
5961
5967
  builderBodySyncingDescription:
5962
5968
  "La edición está en pausa hasta que el cuerpo de Builder termine de sincronizarse, para no sobrescribir el contenido existente del artículo.",
5969
+ pageBodySyncing: "El contenido de esta página aún se está sincronizando",
5970
+ pageBodySyncingDescription:
5971
+ "La edición está en pausa hasta que el contenido de la página termine de sincronizarse, para no sobrescribir el contenido existente.",
5963
5972
  creatingDatabase: "Creando base de datos integrada...",
5964
5973
  databaseCreated: "Base de datos integrada creada",
5965
5974
  emptyBlockPlaceholder: "Pulsa «/» para ver los comandos",
@@ -6317,6 +6326,10 @@ const editorMessagesByLocale = {
6317
6326
  "Le contenu est encore en cours de synchronisation depuis Builder",
6318
6327
  builderBodySyncingDescription:
6319
6328
  "La modification est suspendue jusqu'à la fin de la synchronisation du corps Builder, afin de ne pas écraser le contenu existant de l'article.",
6329
+ pageBodySyncing:
6330
+ "Le contenu de cette page est encore en cours de synchronisation",
6331
+ pageBodySyncingDescription:
6332
+ "La modification est suspendue jusqu'à la fin de la synchronisation du contenu de la page, afin de ne pas écraser le contenu existant.",
6320
6333
  creatingDatabase: "Création d'une base de données intégrée...",
6321
6334
  databaseCreated: "Base de données intégrée créée",
6322
6335
  emptyBlockPlaceholder: "Appuyez sur « / » pour afficher les commandes",
@@ -6676,6 +6689,9 @@ const editorMessagesByLocale = {
6676
6689
  builderBodySyncing: "Inhalte werden noch von Builder synchronisiert",
6677
6690
  builderBodySyncingDescription:
6678
6691
  "Die Bearbeitung ist pausiert, bis der Builder-Textkörper fertig synchronisiert ist, damit der bestehende Artikelinhalt nicht überschrieben wird.",
6692
+ pageBodySyncing: "Der Inhalt dieser Seite wird noch synchronisiert",
6693
+ pageBodySyncingDescription:
6694
+ "Die Bearbeitung ist pausiert, bis der Seiteninhalt fertig synchronisiert ist, damit bestehende Inhalte nicht überschrieben werden.",
6679
6695
  creatingDatabase: "Inline-Datenbank wird erstellt...",
6680
6696
  databaseCreated: "Inline-Datenbank erstellt",
6681
6697
  emptyBlockPlaceholder: "Drücke „/“ für Befehle",
@@ -7040,6 +7056,9 @@ const editorMessagesByLocale = {
7040
7056
  builderBodySyncing: "コンテンツはまだ Builder から同期中です",
7041
7057
  builderBodySyncingDescription:
7042
7058
  "既存の記事内容を上書きしないよう、Builder 本文の同期が完了するまで編集は一時停止されます。",
7059
+ pageBodySyncing: "このページのコンテンツはまだ同期中です",
7060
+ pageBodySyncingDescription:
7061
+ "既存のコンテンツを上書きしないよう、ページ本文の同期が完了するまで編集は一時停止されます。",
7043
7062
  creatingDatabase: "インラインデータベースを作成しています...",
7044
7063
  databaseCreated: "インラインデータベースが作成されました",
7045
7064
  emptyBlockPlaceholder: "「/」でコマンドを表示",
@@ -7393,6 +7412,9 @@ const editorMessagesByLocale = {
7393
7412
  builderBodySyncing: "콘텐츠가 아직 Builder에서 동기화되는 중입니다",
7394
7413
  builderBodySyncingDescription:
7395
7414
  "기존 문서 내용을 덮어쓰지 않도록 Builder 본문 동기화가 완료될 때까지 편집이 일시 중지됩니다.",
7415
+ pageBodySyncing: "이 페이지의 콘텐츠가 아직 동기화 중입니다",
7416
+ pageBodySyncingDescription:
7417
+ "기존 콘텐츠를 덮어쓰지 않도록 페이지 본문 동기화가 완료될 때까지 편집이 일시 중지됩니다.",
7396
7418
  creatingDatabase: "인라인 데이터베이스 생성 중...",
7397
7419
  databaseCreated: "인라인 데이터베이스가 생성되었습니다.",
7398
7420
  emptyBlockPlaceholder: "‘/’를 눌러 명령 사용",
@@ -7745,6 +7767,9 @@ const editorMessagesByLocale = {
7745
7767
  builderBodySyncing: "O conteúdo ainda está sincronizando do Builder",
7746
7768
  builderBodySyncingDescription:
7747
7769
  "A edição fica pausada até o corpo do Builder terminar de sincronizar, para não sobrescrever o conteúdo existente do artigo.",
7770
+ pageBodySyncing: "O conteúdo desta página ainda está sincronizando",
7771
+ pageBodySyncingDescription:
7772
+ "A edição fica pausada até o conteúdo da página terminar de sincronizar, para não sobrescrever o conteúdo existente.",
7748
7773
  creatingDatabase: "Criando banco de dados embutido...",
7749
7774
  databaseCreated: "Banco de dados embutido criado",
7750
7775
  emptyBlockPlaceholder: "Pressione “/” para comandos",
@@ -8103,6 +8128,9 @@ const editorMessagesByLocale = {
8103
8128
  builderBodySyncing: "सामग्री अभी भी Builder से सिंक हो रही है",
8104
8129
  builderBodySyncingDescription:
8105
8130
  "Builder का मुख्य भाग सिंक पूरा होने तक संपादन रोका गया है, ताकि मौजूदा लेख सामग्री अधिलेखित न हो।",
8131
+ pageBodySyncing: "इस पेज की सामग्री अभी भी सिंक हो रही है",
8132
+ pageBodySyncingDescription:
8133
+ "पेज का मुख्य भाग सिंक पूरा होने तक संपादन रोका गया है, ताकि मौजूदा सामग्री अधिलेखित न हो।",
8106
8134
  creatingDatabase: "इनलाइन डेटाबेस बनाया जा रहा है...",
8107
8135
  databaseCreated: "इनलाइन डेटाबेस बनाया गया",
8108
8136
  emptyBlockPlaceholder: "कमांड के लिए '/' दबाएं",
@@ -8450,6 +8478,9 @@ const editorMessagesByLocale = {
8450
8478
  builderBodySyncing: "لا يزال المحتوى قيد المزامنة من Builder",
8451
8479
  builderBodySyncingDescription:
8452
8480
  "يتم إيقاف التحرير مؤقتًا حتى تكتمل مزامنة نص Builder، حتى لا يتم استبدال محتوى المقالة الحالي.",
8481
+ pageBodySyncing: "لا يزال محتوى هذه الصفحة قيد المزامنة",
8482
+ pageBodySyncingDescription:
8483
+ "يتم إيقاف التحرير مؤقتًا حتى تكتمل مزامنة محتوى الصفحة، حتى لا تتم الكتابة فوق المحتوى الحالي.",
8453
8484
  creatingDatabase: "جارٍ إنشاء قاعدة بيانات مضمنة...",
8454
8485
  databaseCreated: "تم إنشاء قاعدة البيانات المضمنة",
8455
8486
  emptyBlockPlaceholder: 'اضغط على "/" للأوامر',
@@ -0,0 +1,36 @@
1
+ export interface DocumentQueryContext {
2
+ databaseId?: string | null;
3
+ databaseDocumentId?: string | null;
4
+ }
5
+
6
+ export function documentQueryKey(
7
+ documentId: string,
8
+ context: DocumentQueryContext = {},
9
+ ) {
10
+ return [
11
+ "action",
12
+ "get-document",
13
+ {
14
+ id: documentId,
15
+ ...(context.databaseId ? { databaseId: context.databaseId } : {}),
16
+ ...(context.databaseDocumentId
17
+ ? { databaseDocumentId: context.databaseDocumentId }
18
+ : {}),
19
+ },
20
+ ] as const;
21
+ }
22
+
23
+ export function documentQueryFilter(documentId: string) {
24
+ return {
25
+ queryKey: ["action", "get-document"] as const,
26
+ predicate: (query: { queryKey: readonly unknown[] }) => {
27
+ const args = query.queryKey[2];
28
+ return (
29
+ !!args &&
30
+ typeof args === "object" &&
31
+ "id" in args &&
32
+ args.id === documentId
33
+ );
34
+ },
35
+ };
36
+ }
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-08-02
4
+ ---
5
+
6
+ Pages opened from a database now keep that database’s fields and loading state, even when the page also belongs to a Builder-connected database.
@@ -14,6 +14,7 @@ export type DocumentContextPathEntry = {
14
14
  * assembles the live path without copying ancestor prose into descendants. */
15
15
  export async function getDocumentContextPath(
16
16
  document: Pick<typeof schema.documents.$inferSelect, "id" | "parentId">,
17
+ options: { databaseId?: string } = {},
17
18
  ): Promise<DocumentContextPathEntry[]> {
18
19
  const db = getDb();
19
20
  const path: DocumentContextPathEntry[] = [];
@@ -43,6 +44,15 @@ export async function getDocumentContextPath(
43
44
  parentId = parent.parentId;
44
45
  }
45
46
 
47
+ const membershipClauses = [
48
+ eq(schema.contentDatabaseItems.documentId, document.id),
49
+ isNull(schema.contentDatabases.deletedAt),
50
+ ];
51
+ if (options.databaseId) {
52
+ membershipClauses.push(
53
+ eq(schema.contentDatabaseItems.databaseId, options.databaseId),
54
+ );
55
+ }
46
56
  const [membership] = await db
47
57
  .select({ database: schema.contentDatabases })
48
58
  .from(schema.contentDatabaseItems)
@@ -50,12 +60,7 @@ export async function getDocumentContextPath(
50
60
  schema.contentDatabases,
51
61
  eq(schema.contentDatabases.id, schema.contentDatabaseItems.databaseId),
52
62
  )
53
- .where(
54
- and(
55
- eq(schema.contentDatabaseItems.documentId, document.id),
56
- isNull(schema.contentDatabases.deletedAt),
57
- ),
58
- )
63
+ .where(and(...membershipClauses))
59
64
  .orderBy(
60
65
  sql`CASE WHEN ${schema.contentDatabases.systemRole} IS NULL THEN 0 ELSE 1 END`,
61
66
  asc(schema.contentDatabases.id),