@agent-native/core 0.81.3 → 0.83.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 (92) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +12 -0
  3. package/corpus/core/package.json +1 -1
  4. package/corpus/core/src/client/CommandMenu.tsx +34 -12
  5. package/corpus/core/src/file-upload/builder.ts +199 -37
  6. package/corpus/core/src/file-upload/index.ts +2 -0
  7. package/corpus/core/src/file-upload/types.ts +38 -0
  8. package/corpus/templates/analytics/actions/hubspot-deals.ts +64 -4
  9. package/corpus/templates/clips/actions/create-recording.ts +44 -0
  10. package/corpus/templates/clips/actions/finalize-recording.ts +198 -88
  11. package/corpus/templates/clips/actions/lib/create-recording-schema.ts +12 -0
  12. package/corpus/templates/clips/app/components/recorder/recorder-engine.ts +83 -29
  13. package/corpus/templates/clips/app/routes/record.tsx +12 -1
  14. package/corpus/templates/clips/server/lib/resumable-session.ts +39 -0
  15. package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/abort.post.ts +2 -0
  16. package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/chunk.post.ts +186 -29
  17. package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/reset-chunks.post.ts +4 -0
  18. package/corpus/templates/clips/shared/recording-core.ts +5 -0
  19. package/corpus/templates/content/.env.local.example +6 -0
  20. package/corpus/templates/content/AGENTS.md +10 -1
  21. package/corpus/templates/content/actions/_builder-cms-read-client.ts +14 -1
  22. package/corpus/templates/content/actions/_builder-cms-source-adapter.ts +43 -0
  23. package/corpus/templates/content/actions/_builder-cms-write-adapter.ts +79 -7
  24. package/corpus/templates/content/actions/_database-source-utils.ts +987 -22
  25. package/corpus/templates/content/actions/_database-utils.ts +41 -0
  26. package/corpus/templates/content/actions/_local-file-documents.ts +74 -1
  27. package/corpus/templates/content/actions/attach-content-database-source.ts +33 -8
  28. package/corpus/templates/content/actions/change-content-database-source-role.ts +14 -0
  29. package/corpus/templates/content/actions/disconnect-content-database-source.ts +3 -0
  30. package/corpus/templates/content/actions/execute-builder-source-execution.ts +71 -3
  31. package/corpus/templates/content/actions/list-content-databases.ts +41 -33
  32. package/corpus/templates/content/actions/process-builder-body-hydration.ts +47 -0
  33. package/corpus/templates/content/actions/update-document.ts +93 -0
  34. package/corpus/templates/content/app/blocks/SourceComponentBlock.tsx +277 -0
  35. package/corpus/templates/content/app/blocks/contentBlockRegistry.tsx +2 -0
  36. package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +105 -17
  37. package/corpus/templates/content/app/components/editor/VisualEditor.tsx +5 -1
  38. package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +92 -0
  39. package/corpus/templates/content/app/components/editor/database-sources/BuilderSourceReviewDialog.tsx +5 -3
  40. package/corpus/templates/content/app/components/editor/extensions/registryBlocks.ts +41 -0
  41. package/corpus/templates/content/app/components/editor/registrySlashItems.ts +4 -0
  42. package/corpus/templates/content/app/components/sidebar/DocumentSidebar.tsx +28 -33
  43. package/corpus/templates/content/app/components/sidebar/document-sidebar-sections.ts +47 -0
  44. package/corpus/templates/content/app/global.css +10 -3
  45. package/corpus/templates/content/app/hooks/use-content-database.ts +24 -0
  46. package/corpus/templates/content/app/hooks/use-documents.ts +37 -0
  47. package/corpus/templates/content/app/i18n/zh-TW.ts +24 -0
  48. package/corpus/templates/content/app/i18n-data.ts +212 -0
  49. package/corpus/templates/content/app/lib/content-command-search.ts +64 -0
  50. package/corpus/templates/content/app/root.tsx +236 -8
  51. package/corpus/templates/content/changelog/2026-06-29-builder-cms-sources-now-sync-article-bodies-through-content.md +6 -0
  52. package/corpus/templates/content/changelog/2026-06-30-builder-article-media-now-render-as-images-and-embeds-when-r.md +6 -0
  53. package/corpus/templates/content/changelog/2026-06-30-builder-source-components-now-render-as-preserved-preview-bl.md +6 -0
  54. package/corpus/templates/content/changelog/2026-06-30-cmd-k-search-now-opens-from-the-editor-and-finds-real-content.md +6 -0
  55. package/corpus/templates/content/changelog/2026-06-30-sidebar-favorites-now-keep-long-titles-tidy-and-stay-in-sync.md +6 -0
  56. package/corpus/templates/content/package.json +3 -1
  57. package/corpus/templates/content/scripts/check-native-deps.mjs +57 -0
  58. package/corpus/templates/content/scripts/dev-database.mjs +83 -0
  59. package/corpus/templates/content/scripts/seed-demo-user.mjs +107 -0
  60. package/corpus/templates/content/server/db/schema.ts +27 -0
  61. package/corpus/templates/content/server/plugins/db.ts +33 -0
  62. package/corpus/templates/content/shared/api.ts +45 -0
  63. package/corpus/templates/content/shared/builder-mdx.ts +905 -5
  64. package/corpus/templates/content/shared/nfm-registry.ts +2 -0
  65. package/corpus/templates/content/shared/source-component-block.ts +141 -0
  66. package/corpus/templates/design/app/components/design/DesignCanvas.tsx +5 -1
  67. package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +229 -131
  68. package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +103 -6
  69. package/corpus/templates/design/app/hooks/useDesignHotkeys.ts +6 -3
  70. package/corpus/templates/design/app/pages/DesignEditor.tsx +382 -72
  71. package/corpus/templates/design/changelog/2026-06-30-canvas-editing-now-keeps-undo-redo-and-cross-screen-layer-mo.md +6 -0
  72. package/dist/client/CommandMenu.d.ts +6 -2
  73. package/dist/client/CommandMenu.d.ts.map +1 -1
  74. package/dist/client/CommandMenu.js +25 -14
  75. package/dist/client/CommandMenu.js.map +1 -1
  76. package/dist/collab/awareness.d.ts +2 -2
  77. package/dist/collab/awareness.d.ts.map +1 -1
  78. package/dist/collab/routes.d.ts +1 -1
  79. package/dist/file-upload/builder.d.ts.map +1 -1
  80. package/dist/file-upload/builder.js +137 -25
  81. package/dist/file-upload/builder.js.map +1 -1
  82. package/dist/file-upload/index.d.ts +1 -1
  83. package/dist/file-upload/index.d.ts.map +1 -1
  84. package/dist/file-upload/index.js.map +1 -1
  85. package/dist/file-upload/types.d.ts +26 -0
  86. package/dist/file-upload/types.d.ts.map +1 -1
  87. package/dist/file-upload/types.js.map +1 -1
  88. package/dist/notifications/routes.d.ts +3 -3
  89. package/dist/progress/routes.d.ts +1 -1
  90. package/dist/resources/handlers.d.ts +3 -3
  91. package/dist/server/transcribe-voice.d.ts +1 -1
  92. package/package.json +1 -1
@@ -151,6 +151,7 @@ import {
151
151
  useExecuteBuilderSourceExecution,
152
152
  useMoveDatabaseItem,
153
153
  usePrepareBuilderSourceReview,
154
+ useProcessBuilderBodyHydration,
154
155
  useRefreshContentDatabaseSource,
155
156
  useSetContentDatabaseSourceWriteMode,
156
157
  useSuggestSourceJoinKey,
@@ -479,6 +480,7 @@ function DatabaseTable({
479
480
  const changeSourceRole = useChangeContentDatabaseSourceRole(document.id);
480
481
  const refreshSource = useRefreshContentDatabaseSource(document.id);
481
482
  const disconnectSource = useDisconnectContentDatabaseSource(document.id);
483
+ const processBuilderBodies = useProcessBuilderBodyHydration(document.id);
482
484
  const prepareBuilderReview = usePrepareBuilderSourceReview(document.id);
483
485
  const executeBuilderExecution = useExecuteBuilderSourceExecution(document.id);
484
486
  const setSourceWriteMode = useSetContentDatabaseSourceWriteMode(document.id);
@@ -1570,6 +1572,9 @@ function DatabaseTable({
1570
1572
  sourceId,
1571
1573
  })
1572
1574
  }
1575
+ onHydrateBuilderBodies={(sourceId) =>
1576
+ processBuilderBodies.mutate({ sourceId })
1577
+ }
1573
1578
  onDisconnectSource={(sourceId) =>
1574
1579
  disconnectSource.mutate(
1575
1580
  {
@@ -1635,6 +1640,7 @@ function DatabaseTable({
1635
1640
  attachSource.isPending ||
1636
1641
  changeSourceRole.isPending ||
1637
1642
  refreshSource.isPending ||
1643
+ processBuilderBodies.isPending ||
1638
1644
  disconnectSource.isPending ||
1639
1645
  prepareBuilderReview.isPending ||
1640
1646
  executeBuilderExecution.isPending ||
@@ -3539,6 +3545,7 @@ function DatabaseSettingsPanelSheet({
3539
3545
  onChangeSourceRole,
3540
3546
  onDisconnectSecondary,
3541
3547
  onRefreshSource,
3548
+ onHydrateBuilderBodies,
3542
3549
  onDisconnectSource,
3543
3550
  onReviewBuilderUpdate,
3544
3551
  onSetBuilderLiveWrites,
@@ -3580,6 +3587,7 @@ function DatabaseSettingsPanelSheet({
3580
3587
  ) => Promise<ContentDatabaseResponse>;
3581
3588
  onDisconnectSecondary: (sourceId: string) => void;
3582
3589
  onRefreshSource: (sourceId?: string) => void;
3590
+ onHydrateBuilderBodies: (sourceId: string) => void;
3583
3591
  onDisconnectSource: (sourceId?: string) => void;
3584
3592
  onReviewBuilderUpdate: () => void;
3585
3593
  onSetBuilderLiveWrites: (enabled: boolean) => void;
@@ -3677,6 +3685,7 @@ function DatabaseSettingsPanelSheet({
3677
3685
  setSourceNavStack([]);
3678
3686
  }}
3679
3687
  onRefreshSource={onRefreshSource}
3688
+ onHydrateBuilderBodies={onHydrateBuilderBodies}
3680
3689
  onDisconnectSource={onDisconnectSource}
3681
3690
  onReviewBuilderUpdate={onReviewBuilderUpdate}
3682
3691
  onSetBuilderLiveWrites={onSetBuilderLiveWrites}
@@ -3976,6 +3985,7 @@ function DatabaseSettingsSourcePanel({
3976
3985
  onChangeSourceRole,
3977
3986
  onDisconnectSecondary,
3978
3987
  onRefreshSource,
3988
+ onHydrateBuilderBodies,
3979
3989
  onDisconnectSource,
3980
3990
  onReviewBuilderUpdate,
3981
3991
  onSetBuilderLiveWrites,
@@ -4004,6 +4014,7 @@ function DatabaseSettingsSourcePanel({
4004
4014
  ) => Promise<ContentDatabaseResponse>;
4005
4015
  onDisconnectSecondary: (sourceId: string) => void;
4006
4016
  onRefreshSource: (sourceId?: string) => void;
4017
+ onHydrateBuilderBodies: (sourceId: string) => void;
4007
4018
  onDisconnectSource: (sourceId?: string) => void;
4008
4019
  onReviewBuilderUpdate: () => void;
4009
4020
  onSetBuilderLiveWrites: (enabled: boolean) => void;
@@ -4442,6 +4453,15 @@ function DatabaseSettingsSourcePanel({
4442
4453
  </div>
4443
4454
  ) : null}
4444
4455
 
4456
+ {isBuilderSource && source.bodyHydration ? (
4457
+ <BuilderBodyHydrationCard
4458
+ source={source}
4459
+ canEdit={canEdit}
4460
+ pending={sourceActionPending}
4461
+ onHydrate={() => onHydrateBuilderBodies(source.id)}
4462
+ />
4463
+ ) : null}
4464
+
4445
4465
  {isCodeMode ? (
4446
4466
  <>
4447
4467
  <div className="grid min-w-0 gap-2 rounded-lg border border-border bg-background p-3 text-sm">
@@ -5173,6 +5193,78 @@ function SourceRoleCard({
5173
5193
  );
5174
5194
  }
5175
5195
 
5196
+ function BuilderBodyHydrationCard({
5197
+ source,
5198
+ canEdit,
5199
+ pending,
5200
+ onHydrate,
5201
+ }: {
5202
+ source: ContentDatabaseSource;
5203
+ canEdit: boolean;
5204
+ pending: boolean;
5205
+ onHydrate: () => void;
5206
+ }) {
5207
+ const summary = source.bodyHydration;
5208
+ if (!summary || summary.total === 0) return null;
5209
+ const activeCount = summary.pending + summary.hydrating;
5210
+ const needsWork = activeCount > 0 || summary.error > 0;
5211
+ const hydratedLabel = dbText("builderBodiesHydrated", {
5212
+ hydrated: summary.hydrated,
5213
+ total: summary.total,
5214
+ });
5215
+ const detail = needsWork
5216
+ ? [
5217
+ activeCount > 0
5218
+ ? dbText("builderBodiesQueued", { count: activeCount })
5219
+ : null,
5220
+ summary.error > 0
5221
+ ? dbText("builderBodySyncFailed", { count: summary.error })
5222
+ : null,
5223
+ ]
5224
+ .filter(Boolean)
5225
+ .join(" · ")
5226
+ : dbText("builderBodiesReadyLocally");
5227
+
5228
+ return (
5229
+ <div className="grid min-w-0 gap-2 rounded-lg border border-border bg-background p-3">
5230
+ <div className="flex min-w-0 items-start justify-between gap-3">
5231
+ <div className="min-w-0">
5232
+ <div className="text-xs font-medium">{dbText("builderBodySync")}</div>
5233
+ <div className="mt-0.5 text-xs text-muted-foreground">
5234
+ {hydratedLabel}
5235
+ </div>
5236
+ </div>
5237
+ {needsWork ? (
5238
+ <Button
5239
+ type="button"
5240
+ size="sm"
5241
+ variant="outline"
5242
+ className="h-7 shrink-0 px-2 text-xs"
5243
+ disabled={!canEdit || pending}
5244
+ onClick={onHydrate}
5245
+ >
5246
+ {pending ? (
5247
+ <Spinner className="mr-1 size-3.5" />
5248
+ ) : (
5249
+ <IconRefresh className="mr-1 size-3.5" />
5250
+ )}
5251
+ {summary.error > 0 ? dbText("retry") : dbText("resume")}
5252
+ </Button>
5253
+ ) : null}
5254
+ </div>
5255
+ <div className="h-1.5 overflow-hidden rounded-full bg-muted">
5256
+ <div
5257
+ className="h-full rounded-full bg-foreground transition-[width]"
5258
+ style={{
5259
+ width: `${Math.round((summary.hydrated / summary.total) * 100)}%`,
5260
+ }}
5261
+ />
5262
+ </div>
5263
+ <div className="break-words text-xs text-muted-foreground">{detail}</div>
5264
+ </div>
5265
+ );
5266
+ }
5267
+
5176
5268
  const DETAIL_FIELD_NAME_HINTS = [
5177
5269
  "name",
5178
5270
  "title",
@@ -575,9 +575,11 @@ export function BuilderSourceReviewDialog({
575
575
  <div className="font-medium">
576
576
  {row.bodyChange.summary}
577
577
  </div>
578
- <div className="mt-1 text-muted-foreground">
579
- {t("database.builderBodyEditsNeedSaferPath")}
580
- </div>
578
+ {row.bodyChange.warnings?.length ? (
579
+ <div className="mt-1 text-muted-foreground">
580
+ {row.bodyChange.warnings.join(" ")}
581
+ </div>
582
+ ) : null}
581
583
  </div>
582
584
  ) : null}
583
585
  {showConflict ? (
@@ -1,4 +1,6 @@
1
1
  import { createRegistryBlockNode } from "@agent-native/core/client";
2
+ import { Extension } from "@tiptap/core";
3
+ import { Plugin, PluginKey } from "@tiptap/pm/state";
2
4
 
3
5
  /* -------------------------------------------------------------------------- */
4
6
  /* Content's registry-block Tiptap node — a thin wrapper over the core node. */
@@ -46,4 +48,43 @@ export const RegistryBlockNode = createRegistryBlockNode({
46
48
  mintId: createContentBlockId,
47
49
  });
48
50
 
51
+ function sourceComponentIdCounts(doc: { descendants: (fn: any) => void }) {
52
+ const counts = new Map<string, number>();
53
+ doc.descendants((node: any) => {
54
+ if (
55
+ node.type?.name === "registryBlock" &&
56
+ node.attrs?.blockType === "source-component" &&
57
+ typeof node.attrs.blockId === "string" &&
58
+ node.attrs.blockId
59
+ ) {
60
+ counts.set(node.attrs.blockId, (counts.get(node.attrs.blockId) ?? 0) + 1);
61
+ }
62
+ return true;
63
+ });
64
+ return counts;
65
+ }
66
+
67
+ export const LockedSourceComponentBlocks = Extension.create({
68
+ name: "lockedSourceComponentBlocks",
69
+
70
+ addProseMirrorPlugins() {
71
+ return [
72
+ new Plugin({
73
+ key: new PluginKey("lockedSourceComponentBlocks"),
74
+ filterTransaction(transaction, state) {
75
+ if (!transaction.docChanged) return true;
76
+ const before = sourceComponentIdCounts(state.doc);
77
+ if (before.size === 0) return true;
78
+ const after = sourceComponentIdCounts(transaction.doc);
79
+ if (after.size !== before.size) return false;
80
+ for (const [id, count] of before) {
81
+ if (after.get(id) !== count) return false;
82
+ }
83
+ return true;
84
+ },
85
+ }),
86
+ ];
87
+ },
88
+ });
89
+
49
90
  export default RegistryBlockNode;
@@ -102,6 +102,9 @@ export function buildRegistrySlashItems(
102
102
  // not a content-authoring block.
103
103
  // - `inline-database` is registered in Phase 2 for render/round-trip
104
104
  // compatibility; Phase 3 owns the `/database` insertion flow.
105
+ // - `source-component` is emitted by provider hydration to preserve
106
+ // source-native blocks; users should not author raw preservation
107
+ // markers by hand.
105
108
  // The genuinely document-friendly rich blocks (callout, decision, diagram,
106
109
  // wireframe, and the dev-doc/structured set) ARE offered.
107
110
  includeSpec: (spec) =>
@@ -110,6 +113,7 @@ export function buildRegistrySlashItems(
110
113
  "question-form",
111
114
  "visual-questions",
112
115
  "inline-database",
116
+ "source-component",
113
117
  ].includes(spec.type),
114
118
  toItem: (spec, insert) => ({
115
119
  title: spec.label,
@@ -59,6 +59,10 @@ import {
59
59
  } from "@/hooks/use-documents";
60
60
  import { cn } from "@/lib/utils";
61
61
 
62
+ import {
63
+ getDocumentSidebarSections,
64
+ isDirectLocalDocument,
65
+ } from "./document-sidebar-sections";
62
66
  import { DocumentSidebarIcon, DocumentTreeItem } from "./DocumentTreeItem";
63
67
  import { NotionButton } from "./NotionButton";
64
68
 
@@ -114,22 +118,6 @@ function collectDocumentSubtreeIds(documents: Document[], rootId: string) {
114
118
  return deletedIds;
115
119
  }
116
120
 
117
- function isDirectLocalDocument(document: Pick<Document, "id" | "source">) {
118
- return (
119
- document.source?.mode === "local-files" &&
120
- (document.id.startsWith("local-file:") ||
121
- document.id.startsWith("local-folder:"))
122
- );
123
- }
124
-
125
- function isImportedLocalSourceDocument(
126
- document: Pick<Document, "id" | "source">,
127
- ) {
128
- return (
129
- document.source?.mode === "local-files" && !isDirectLocalDocument(document)
130
- );
131
- }
132
-
133
121
  type SidebarSectionId =
134
122
  | "local-files"
135
123
  | "shared-copies"
@@ -210,24 +198,23 @@ export function DocumentSidebar({
210
198
  );
211
199
 
212
200
  const treeDocuments = filterDocumentTreeDocuments(documents);
213
- const localFileMode = documents.some(isDirectLocalDocument);
214
- const localSourceDocuments = localFileMode
215
- ? treeDocuments.filter(isDirectLocalDocument)
216
- : treeDocuments.filter(isImportedLocalSourceDocument);
217
- const databaseDocuments = localFileMode
218
- ? treeDocuments.filter((document) => !isDirectLocalDocument(document))
219
- : treeDocuments.filter(
220
- (document) => !isImportedLocalSourceDocument(document),
221
- );
201
+ const {
202
+ localFileMode,
203
+ localSourceDocuments,
204
+ databaseDocuments,
205
+ favorites,
206
+ showFavorites,
207
+ } = getDocumentSidebarSections(documents, treeDocuments);
222
208
  const localFileTree = buildDocumentTree(localSourceDocuments);
223
209
  const databaseTree = buildDocumentTree(databaseDocuments);
224
210
  const privateTree = databaseTree.filter((node) => node.visibility !== "org");
225
211
  const organizationTree = databaseTree.filter(
226
212
  (node) => node.visibility === "org",
227
213
  );
228
- const favorites = documents.filter(
229
- (d) => d.isFavorite && (localFileMode || !isImportedLocalSourceDocument(d)),
230
- );
214
+ // Match the tree rows' right-side inset so favorite titles clip inside the
215
+ // visible sidebar instead of widening the scroll surface.
216
+ const favoriteRowWidth =
217
+ width === undefined ? undefined : Math.max(224, width - 8);
231
218
  const activeDocument = activeDocumentId
232
219
  ? documents.find((doc) => doc.id === activeDocumentId)
233
220
  : null;
@@ -878,21 +865,29 @@ export function DocumentSidebar({
878
865
  ) : (
879
866
  <>
880
867
  {/* Favorites */}
881
- {!localFileMode && favorites.length > 0 && (
882
- <div className="mb-2">
883
- <div className="px-3 py-1.5 text-[10px] font-semibold uppercase tracking-wider text-muted-foreground flex items-center gap-1">
868
+ {showFavorites && (
869
+ <div className="mb-2 min-w-0">
870
+ <div className="flex min-w-0 items-center gap-1 px-3 py-1.5 text-[10px] font-semibold uppercase tracking-wider text-muted-foreground">
884
871
  <IconStar size={10} />
885
- {t("sidebar.favorites")}
872
+ <span className="min-w-0 flex-1 truncate">
873
+ {t("sidebar.favorites")}
874
+ </span>
886
875
  </div>
887
876
  {favorites.map((doc) => (
888
877
  <button
889
878
  key={doc.id}
890
879
  className={cn(
891
- "w-full flex items-center gap-2 px-4 py-[5px] text-sm text-start rounded-md",
880
+ "flex w-full min-w-0 items-center gap-2 rounded-md px-4 py-[5px] text-start text-sm",
892
881
  doc.id === activeDocumentId
893
882
  ? "bg-accent text-accent-foreground"
894
883
  : "text-muted-foreground hover:bg-accent/50 hover:text-foreground",
895
884
  )}
885
+ style={{
886
+ width:
887
+ favoriteRowWidth === undefined
888
+ ? undefined
889
+ : `${favoriteRowWidth}px`,
890
+ }}
896
891
  onClick={() => {
897
892
  navigateToDocument(doc.id);
898
893
  onNavigate?.();
@@ -0,0 +1,47 @@
1
+ import type { Document } from "@shared/api";
2
+
3
+ export function isDirectLocalDocument(
4
+ document: Pick<Document, "id" | "source">,
5
+ ) {
6
+ return (
7
+ document.source?.mode === "local-files" &&
8
+ (document.id.startsWith("local-file:") ||
9
+ document.id.startsWith("local-folder:"))
10
+ );
11
+ }
12
+
13
+ export function isImportedLocalSourceDocument(
14
+ document: Pick<Document, "id" | "source">,
15
+ ) {
16
+ return (
17
+ document.source?.mode === "local-files" && !isDirectLocalDocument(document)
18
+ );
19
+ }
20
+
21
+ export function getDocumentSidebarSections(
22
+ documents: Document[],
23
+ treeDocuments: Document[] = documents,
24
+ ) {
25
+ const localFileMode = documents.some(isDirectLocalDocument);
26
+ const localSourceDocuments = localFileMode
27
+ ? treeDocuments.filter(isDirectLocalDocument)
28
+ : treeDocuments.filter(isImportedLocalSourceDocument);
29
+ const databaseDocuments = localFileMode
30
+ ? treeDocuments.filter((document) => !isDirectLocalDocument(document))
31
+ : treeDocuments.filter(
32
+ (document) => !isImportedLocalSourceDocument(document),
33
+ );
34
+ const favorites = documents.filter(
35
+ (document) =>
36
+ document.isFavorite &&
37
+ (localFileMode || !isImportedLocalSourceDocument(document)),
38
+ );
39
+
40
+ return {
41
+ localFileMode,
42
+ localSourceDocuments,
43
+ databaseDocuments,
44
+ favorites,
45
+ showFavorites: favorites.length > 0,
46
+ };
47
+ }
@@ -2298,25 +2298,32 @@ audio.media-block__content {
2298
2298
 
2299
2299
  /* ===== Collaboration cursors ===== */
2300
2300
  .notion-editor .collaboration-cursor__selection {
2301
- background-color: hsl(210 100% 52% / 0.14) !important;
2301
+ background-color: currentColor !important;
2302
2302
  border-radius: 2px;
2303
2303
  box-decoration-break: clone;
2304
2304
  -webkit-box-decoration-break: clone;
2305
+ opacity: 0.16;
2305
2306
  }
2306
2307
 
2307
- .collaboration-cursor__caret {
2308
+ .notion-editor .collaboration-cursor__caret {
2308
2309
  border-left: 2px solid;
2309
2310
  border-color: currentColor;
2311
+ display: inline;
2310
2312
  margin-left: -1px;
2311
2313
  margin-right: -1px;
2312
2314
  position: relative;
2313
2315
  word-break: normal;
2314
2316
  pointer-events: none;
2317
+ line-height: 1;
2315
2318
  }
2316
- .collaboration-cursor__label {
2319
+ .notion-editor .collaboration-cursor__label {
2317
2320
  position: absolute;
2318
2321
  top: -1.4em;
2319
2322
  left: -1px;
2323
+ display: inline-block;
2324
+ max-width: min(180px, 50vw);
2325
+ overflow: hidden;
2326
+ text-overflow: ellipsis;
2320
2327
  font-size: 11px;
2321
2328
  font-weight: 600;
2322
2329
  line-height: 1;
@@ -22,6 +22,8 @@ import type {
22
22
  PrepareBuilderSourceExecutionRequest,
23
23
  PrepareBuilderSourceReviewRequest,
24
24
  PrepareBuilderSourceReviewResponse,
25
+ ProcessBuilderBodyHydrationRequest,
26
+ ProcessBuilderBodyHydrationResponse,
25
27
  RefreshContentDatabaseSourceRequest,
26
28
  ReviewContentDatabaseSourceChangeSetRequest,
27
29
  SetContentDatabaseSourceWriteModeRequest,
@@ -408,6 +410,28 @@ export function useRefreshContentDatabaseSource(documentId: string) {
408
410
  });
409
411
  }
410
412
 
413
+ export function useProcessBuilderBodyHydration(documentId: string) {
414
+ const queryClient = useQueryClient();
415
+ return useActionMutation<
416
+ ProcessBuilderBodyHydrationResponse,
417
+ ProcessBuilderBodyHydrationRequest
418
+ >("process-builder-body-hydration", {
419
+ onSuccess: (_data, variables) => {
420
+ queryClient.invalidateQueries({
421
+ queryKey: contentDatabaseQueryKey(documentId),
422
+ });
423
+ queryClient.invalidateQueries({
424
+ queryKey: ["action", "get-content-database-source", { documentId }],
425
+ });
426
+ if (variables?.documentId) {
427
+ queryClient.invalidateQueries({
428
+ queryKey: ["action", "get-document", { id: variables.documentId }],
429
+ });
430
+ }
431
+ },
432
+ });
433
+ }
434
+
411
435
  export function useDisconnectContentDatabaseSource(documentId: string) {
412
436
  const queryClient = useQueryClient();
413
437
  return useActionMutation<
@@ -12,6 +12,36 @@ import { toast } from "sonner";
12
12
 
13
13
  import { useRestoreContentDatabase } from "./use-content-database";
14
14
 
15
+ const LIST_DOCUMENTS_QUERY_KEY = ["action", "list-documents", undefined];
16
+
17
+ export function mergeDocumentIntoDocumentCache(
18
+ old: unknown,
19
+ document: Document,
20
+ ) {
21
+ return old && typeof old === "object" ? { ...old, ...document } : document;
22
+ }
23
+
24
+ export function mergeDocumentIntoListDocumentsCache(
25
+ old: unknown,
26
+ document: Document,
27
+ ) {
28
+ if (Array.isArray(old)) {
29
+ return old.map((item: Document) =>
30
+ item.id === document.id ? { ...item, ...document } : item,
31
+ );
32
+ }
33
+
34
+ if (!old || typeof old !== "object") return old;
35
+ const cached = old as { documents?: unknown };
36
+ if (!Array.isArray(cached.documents)) return old;
37
+
38
+ const nextDocuments = cached.documents.map((item: Document) =>
39
+ item.id === document.id ? { ...item, ...document } : item,
40
+ );
41
+
42
+ return { ...(old as object), documents: nextDocuments };
43
+ }
44
+
15
45
  export function useDocuments() {
16
46
  return useActionQuery<Document[]>("list-documents", undefined, {
17
47
  select: (data: any) => {
@@ -42,6 +72,13 @@ export function useUpdateDocument() {
42
72
  DocumentUpdateRequest & { id: string }
43
73
  >("update-document", {
44
74
  onSuccess: (data, variables) => {
75
+ queryClient.setQueryData(
76
+ ["action", "get-document", { id: variables.id }],
77
+ (old: unknown) => mergeDocumentIntoDocumentCache(old, data),
78
+ );
79
+ queryClient.setQueryData(LIST_DOCUMENTS_QUERY_KEY, (old: unknown) =>
80
+ mergeDocumentIntoListDocumentsCache(old, data),
81
+ );
45
82
  queryClient.invalidateQueries({
46
83
  queryKey: ["action", "get-document", { id: variables.id }],
47
84
  });
@@ -2,6 +2,16 @@ const messages = {
2
2
  root: {
3
3
  commandContent: "內容",
4
4
  commandSearchDocuments: "搜尋檔案",
5
+ commandSearchHeading: "搜尋",
6
+ commandSearchPlaceholder: "搜尋文件和資料庫...",
7
+ commandSearchLoading: "搜尋中...",
8
+ commandSearchError: "搜尋目前無法使用。",
9
+ commandSearchPartialError: "部分結果無法載入。",
10
+ commandSearchEmpty: "沒有相符的文件或資料庫。",
11
+ commandDocumentsHeading: "文件",
12
+ commandDatabasesHeading: "資料庫",
13
+ commandLocalFilesHeading: "本機檔案",
14
+ commandDatabaseResultDescription: "開啟資料庫頁面",
5
15
  commandAppearance: "外觀",
6
16
  toggleTheme: "切換主題",
7
17
  metaTitle:
@@ -154,6 +164,14 @@ const messages = {
154
164
  pathMissing: "引用路徑缺失",
155
165
  selfReference: "此引用指向目前文件。",
156
166
  },
167
+ sourceComponent: {
168
+ defaultTitle: "來源元件",
169
+ noPreviewRows: "沒有預覽列",
170
+ previewAvailable: "可預覽",
171
+ previewTruncated: "預覽已截斷",
172
+ previewUnavailable: "無法預覽",
173
+ needsAttention: "需要注意",
174
+ },
157
175
  media: {
158
176
  addAltTextDescription: "新增替代文字來描述該圖片。",
159
177
  addAudio: "新增音訊",
@@ -585,6 +603,11 @@ const messages = {
585
603
  builderSpace: "Builder 空間",
586
604
  builderUpdateFailed: "Builder 更新失敗",
587
605
  builderWriteModeWasNotChanged: "Builder 寫入模式未更改",
606
+ builderBodiesReadyLocally: "Builder 文章正文已在本機準備好。",
607
+ builderBodiesQueued: "{{count}} 個排隊中",
608
+ builderBodySync: "正文同步",
609
+ builderBodySyncFailed: "{{count}} 個失敗",
610
+ builderBodiesHydrated: "已同步 {{hydrated}} / {{total}} 個正文",
588
611
  closeDatabaseSettings: "關閉資料庫設定",
589
612
  closeSearch: "關閉搜尋",
590
613
  databaseRowsAndLocalPropertiesWereKeptIntact: "資料庫行和本機屬性已保留。",
@@ -754,6 +777,7 @@ const messages = {
754
777
  readOnly: "唯讀",
755
778
  removeThisSource: "移除此來源",
756
779
  renameView: "重新命名檢視",
780
+ resume: "繼續",
757
781
  retry: "Retry",
758
782
  reviewBeforeTheyReachBuilder: "在傳送到 Builder 前審核。",
759
783
  reviewDiff: "審核差異",