@agent-native/core 0.101.7 → 0.101.10

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 (153) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +22 -0
  3. package/corpus/core/docs/content/deployment.mdx +9 -7
  4. package/corpus/core/docs/content/locales/ar-SA/deployment.mdx +9 -7
  5. package/corpus/core/docs/content/locales/de-DE/deployment.mdx +9 -7
  6. package/corpus/core/docs/content/locales/es-ES/deployment.mdx +9 -7
  7. package/corpus/core/docs/content/locales/fr-FR/deployment.mdx +9 -7
  8. package/corpus/core/docs/content/locales/hi-IN/deployment.mdx +9 -7
  9. package/corpus/core/docs/content/locales/ja-JP/deployment.mdx +9 -7
  10. package/corpus/core/docs/content/locales/ko-KR/deployment.mdx +9 -7
  11. package/corpus/core/docs/content/locales/pt-BR/deployment.mdx +9 -7
  12. package/corpus/core/docs/content/locales/zh-CN/deployment.mdx +9 -7
  13. package/corpus/core/docs/content/locales/zh-TW/deployment.mdx +9 -7
  14. package/corpus/core/package.json +1 -1
  15. package/corpus/core/src/client/review/ReviewCommentComposer.tsx +9 -1
  16. package/corpus/core/src/client/review/ReviewThreadPanel.tsx +17 -2
  17. package/corpus/core/src/client/settings/SettingsPanel.tsx +41 -21
  18. package/corpus/core/src/client/settings/useBuilderStatus.ts +28 -9
  19. package/corpus/core/src/client/setup-connections/BuilderConnectCard.tsx +2 -7
  20. package/corpus/core/src/server/auth.ts +25 -1
  21. package/corpus/core/src/server/builder-browser.ts +353 -1
  22. package/corpus/core/src/server/core-routes-plugin.ts +601 -223
  23. package/corpus/templates/calendar/app/components/calendar/DayView.tsx +80 -1
  24. package/corpus/templates/calendar/app/components/calendar/EventCard.tsx +28 -3
  25. package/corpus/templates/calendar/app/components/calendar/EventDetailPanel.tsx +5 -1
  26. package/corpus/templates/calendar/app/components/calendar/EventDetailPopover.tsx +30 -3
  27. package/corpus/templates/calendar/app/components/calendar/OutOfOfficeEvent.tsx +196 -0
  28. package/corpus/templates/calendar/app/components/calendar/WeekView.tsx +115 -1
  29. package/corpus/templates/calendar/app/lib/out-of-office.ts +45 -0
  30. package/corpus/templates/calendar/changelog/2026-07-14-out-of-office-blocks-now-sit-behind-meetings-and-event-stack.md +6 -0
  31. package/corpus/templates/content/AGENTS.md +3 -2
  32. package/corpus/templates/content/actions/_builder-cms-intent-lookup.ts +87 -0
  33. package/corpus/templates/content/actions/_builder-cms-read-client.ts +173 -25
  34. package/corpus/templates/content/actions/_builder-cms-source-adapter.ts +1 -0
  35. package/corpus/templates/content/actions/_builder-cms-write-adapter.ts +427 -18
  36. package/corpus/templates/content/actions/_builder-cms-write-client.ts +17 -73
  37. package/corpus/templates/content/actions/_builder-cms-write-settings.ts +27 -7
  38. package/corpus/templates/content/actions/_builder-source-execution-claim.ts +124 -0
  39. package/corpus/templates/content/actions/_builder-source-execution-preservation.ts +21 -0
  40. package/corpus/templates/content/actions/_builder-source-timings.ts +57 -0
  41. package/corpus/templates/content/actions/_database-source-utils.ts +1041 -154
  42. package/corpus/templates/content/actions/_database-utils.ts +89 -12
  43. package/corpus/templates/content/actions/_preview-document-draft.ts +31 -0
  44. package/corpus/templates/content/actions/cancel-prepared-builder-source-update.ts +309 -0
  45. package/corpus/templates/content/actions/execute-builder-source-batch.ts +120 -49
  46. package/corpus/templates/content/actions/execute-builder-source-execution.ts +608 -264
  47. package/corpus/templates/content/actions/get-preview-document-draft.ts +26 -0
  48. package/corpus/templates/content/actions/materialize-builder-required-fields.ts +550 -0
  49. package/corpus/templates/content/actions/prepare-builder-source-execution.ts +112 -65
  50. package/corpus/templates/content/actions/prepare-builder-source-review.ts +463 -170
  51. package/corpus/templates/content/actions/preview-builder-source-review.ts +99 -0
  52. package/corpus/templates/content/actions/set-content-database-source-write-mode.ts +2 -2
  53. package/corpus/templates/content/actions/update-preview-document-draft.ts +153 -0
  54. package/corpus/templates/content/app/components/editor/DocumentProperties.tsx +27 -15
  55. package/corpus/templates/content/app/components/editor/VisualEditor.tsx +57 -5
  56. package/corpus/templates/content/app/components/editor/body-hydration.ts +27 -0
  57. package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +2353 -576
  58. package/corpus/templates/content/app/components/editor/database/settings.tsx +13 -10
  59. package/corpus/templates/content/app/components/editor/database-sources/BuilderSourceReviewDialog.tsx +358 -75
  60. package/corpus/templates/content/app/components/editor/previewDocumentSaveController.ts +124 -29
  61. package/corpus/templates/content/app/components/editor/previewDocumentSaveRegistry.ts +2 -0
  62. package/corpus/templates/content/app/global.css +18 -1
  63. package/corpus/templates/content/app/hooks/use-content-database.ts +68 -0
  64. package/corpus/templates/content/app/hooks/use-documents.ts +53 -4
  65. package/corpus/templates/content/app/i18n/zh-TW.ts +25 -0
  66. package/corpus/templates/content/app/i18n-data.ts +287 -0
  67. package/corpus/templates/content/changelog/2026-07-10-builder-backed-preview-edits-are-preserved-when-article-bodi.md +6 -0
  68. package/corpus/templates/content/changelog/2026-07-10-builder-cms-sources-can-now-be-enabled-for-guarded-staged-or.md +6 -0
  69. package/corpus/templates/content/changelog/2026-07-13-builder-connection-setup-is-now-available-directly-from-conn.md +6 -0
  70. package/corpus/templates/content/changelog/2026-07-13-builder-reviews-now-hide-already-applied-writes-detect-nativ.md +6 -0
  71. package/corpus/templates/content/changelog/2026-07-13-required-builder-publishing-fields-can-now-be-added-directly.md +6 -0
  72. package/corpus/templates/content/changelog/2026-07-13-you-can-now-cancel-a-prepared-builder-update-safely-before-i.md +6 -0
  73. package/corpus/templates/content/changelog/2026-07-14-builder-account-connection-now-remains-available-when-branch.md +6 -0
  74. package/corpus/templates/content/changelog/2026-07-14-builder-backed-articles-now-preserve-fifth-and-sixth-level-h.md +6 -0
  75. package/corpus/templates/content/changelog/2026-07-14-builder-sources-now-review-and-publish-rich-article-updates-.md +6 -0
  76. package/corpus/templates/content/changelog/2026-07-14-builder-writes-now-stop-retries-when-reconciliation-is-needed.md +6 -0
  77. package/corpus/templates/content/changelog/2026-07-14-file-and-media-links-now-validate-before-saving.md +6 -0
  78. package/corpus/templates/content/parity/matrix.md +27 -25
  79. package/corpus/templates/content/parity/matrix.ts +50 -2
  80. package/corpus/templates/content/server/db/schema.ts +47 -0
  81. package/corpus/templates/content/server/plugins/auth.ts +1 -0
  82. package/corpus/templates/content/server/plugins/db.ts +53 -0
  83. package/corpus/templates/content/shared/api.ts +70 -1
  84. package/corpus/templates/content/shared/builder-mdx.ts +705 -15
  85. package/corpus/templates/content/shared/nfm.ts +3 -3
  86. package/corpus/templates/design/app/components/design/ReadOnlyDesignBanner.tsx +52 -7
  87. package/corpus/templates/design/app/components/design/ReviewCommentsPanel.tsx +36 -4
  88. package/corpus/templates/design/app/components/visual-editor/ReviewCanvasPins.tsx +4 -1
  89. package/corpus/templates/design/app/i18n/ar-SA.ts +1 -0
  90. package/corpus/templates/design/app/i18n/de-DE.ts +1 -0
  91. package/corpus/templates/design/app/i18n/en-US.ts +1 -0
  92. package/corpus/templates/design/app/i18n/es-ES.ts +1 -0
  93. package/corpus/templates/design/app/i18n/fr-FR.ts +1 -0
  94. package/corpus/templates/design/app/i18n/hi-IN.ts +1 -0
  95. package/corpus/templates/design/app/i18n/ja-JP.ts +1 -0
  96. package/corpus/templates/design/app/i18n/ko-KR.ts +1 -0
  97. package/corpus/templates/design/app/i18n/pt-BR.ts +1 -0
  98. package/corpus/templates/design/app/i18n/zh-CN.ts +1 -0
  99. package/corpus/templates/design/app/i18n/zh-TW.ts +1 -0
  100. package/corpus/templates/design/app/pages/design-editor/selection-state.ts +8 -0
  101. package/corpus/templates/design/app/pages/design-editor/tool-state.ts +11 -0
  102. package/corpus/templates/design/changelog/2026-07-14-reviewers-can-now-pin-comments-from-the-canvas-toolbar-targe.md +6 -0
  103. package/corpus/templates/design/changelog/2026-07-15-viewer-comment-controls-now-sit-inside-the-read-only-notice-.md +6 -0
  104. package/corpus/templates/design/shared/review-anchor.ts +48 -0
  105. package/corpus/templates/plan/server/plan-mdx.ts +53 -7
  106. package/dist/client/review/ReviewCommentComposer.d.ts +2 -1
  107. package/dist/client/review/ReviewCommentComposer.d.ts.map +1 -1
  108. package/dist/client/review/ReviewCommentComposer.js +3 -3
  109. package/dist/client/review/ReviewCommentComposer.js.map +1 -1
  110. package/dist/client/review/ReviewThreadPanel.d.ts +9 -1
  111. package/dist/client/review/ReviewThreadPanel.d.ts.map +1 -1
  112. package/dist/client/review/ReviewThreadPanel.js +6 -4
  113. package/dist/client/review/ReviewThreadPanel.js.map +1 -1
  114. package/dist/client/settings/SettingsPanel.d.ts +3 -0
  115. package/dist/client/settings/SettingsPanel.d.ts.map +1 -1
  116. package/dist/client/settings/SettingsPanel.js +11 -4
  117. package/dist/client/settings/SettingsPanel.js.map +1 -1
  118. package/dist/client/settings/useBuilderStatus.d.ts +8 -4
  119. package/dist/client/settings/useBuilderStatus.d.ts.map +1 -1
  120. package/dist/client/settings/useBuilderStatus.js +19 -6
  121. package/dist/client/settings/useBuilderStatus.js.map +1 -1
  122. package/dist/client/setup-connections/BuilderConnectCard.d.ts.map +1 -1
  123. package/dist/client/setup-connections/BuilderConnectCard.js +2 -5
  124. package/dist/client/setup-connections/BuilderConnectCard.js.map +1 -1
  125. package/dist/collab/awareness.d.ts +2 -2
  126. package/dist/collab/awareness.d.ts.map +1 -1
  127. package/dist/observability/routes.d.ts +5 -5
  128. package/dist/resources/handlers.d.ts +1 -1
  129. package/dist/secrets/routes.d.ts +9 -9
  130. package/dist/server/auth.d.ts +1 -0
  131. package/dist/server/auth.d.ts.map +1 -1
  132. package/dist/server/auth.js +19 -2
  133. package/dist/server/auth.js.map +1 -1
  134. package/dist/server/builder-browser.d.ts +78 -0
  135. package/dist/server/builder-browser.d.ts.map +1 -1
  136. package/dist/server/builder-browser.js +246 -1
  137. package/dist/server/builder-browser.js.map +1 -1
  138. package/dist/server/core-routes-plugin.d.ts +41 -0
  139. package/dist/server/core-routes-plugin.d.ts.map +1 -1
  140. package/dist/server/core-routes-plugin.js +260 -14
  141. package/dist/server/core-routes-plugin.js.map +1 -1
  142. package/docs/content/deployment.mdx +9 -7
  143. package/docs/content/locales/ar-SA/deployment.mdx +9 -7
  144. package/docs/content/locales/de-DE/deployment.mdx +9 -7
  145. package/docs/content/locales/es-ES/deployment.mdx +9 -7
  146. package/docs/content/locales/fr-FR/deployment.mdx +9 -7
  147. package/docs/content/locales/hi-IN/deployment.mdx +9 -7
  148. package/docs/content/locales/ja-JP/deployment.mdx +9 -7
  149. package/docs/content/locales/ko-KR/deployment.mdx +9 -7
  150. package/docs/content/locales/pt-BR/deployment.mdx +9 -7
  151. package/docs/content/locales/zh-CN/deployment.mdx +9 -7
  152. package/docs/content/locales/zh-TW/deployment.mdx +9 -7
  153. package/package.json +2 -2
@@ -61,12 +61,39 @@ export interface PreviewDocumentPayload {
61
61
  loadedContentWasEmpty?: boolean;
62
62
  }
63
63
 
64
- export interface PreviewDocumentSaveSkipped {
65
- skipped: true;
64
+ export interface PreviewDocumentSaveDeferred {
65
+ outcome: "deferred";
66
+ reason: "hydration" | "conflict";
67
+ conflictSnapshot?: PreviewDocumentDraftSnapshot;
66
68
  }
67
69
 
68
- export function skippedPreviewDocumentSave(): PreviewDocumentSaveSkipped {
69
- return { skipped: true };
70
+ export interface PreviewDocumentSaveAdapter {
71
+ save: (
72
+ documentId: string,
73
+ payload: PreviewDocumentPayload,
74
+ baseline?: PreviewDocumentPayload,
75
+ ) => Promise<unknown>;
76
+ onSaved?: (payload: PreviewDocumentPayload) => void;
77
+ onError?: (error: unknown) => void;
78
+ onDraftConflict?: (snapshot: PreviewDocumentDraftSnapshot) => void;
79
+ }
80
+
81
+ export interface PreviewDocumentDraftSnapshot {
82
+ lastSaved: PreviewDocumentPayload;
83
+ pending: PreviewDocumentPayload;
84
+ deferredReason: PreviewDocumentSaveDeferred["reason"] | null;
85
+ }
86
+
87
+ /**
88
+ * The save could not run yet, but the payload is still user-owned and must stay
89
+ * dirty until a later flush can persist it. This is intentionally different
90
+ * from success: callers must never advance or reset the confirmed baseline.
91
+ */
92
+ export function deferredPreviewDocumentSave(
93
+ reason: PreviewDocumentSaveDeferred["reason"] = "hydration",
94
+ conflictSnapshot?: PreviewDocumentDraftSnapshot,
95
+ ): PreviewDocumentSaveDeferred {
96
+ return { outcome: "deferred", reason, conflictSnapshot };
70
97
  }
71
98
 
72
99
  export interface PreviewDocumentSaveController {
@@ -89,6 +116,19 @@ export interface PreviewDocumentSaveController {
89
116
  cancel(): void;
90
117
  /** Adopt `payload` as the confirmed-saved baseline (no save scheduled). */
91
118
  mark(payload: PreviewDocumentPayload): void;
119
+ /**
120
+ * Adopt a fresher server baseline while retaining the user's pending title
121
+ * and content. Used only after an explicit "keep local draft" choice.
122
+ */
123
+ rebasePending(payload: PreviewDocumentPayload): void;
124
+ /** Replace callbacks captured by an older preview mount. */
125
+ replaceSaveAdapter(adapter: PreviewDocumentSaveAdapter): void;
126
+ /** Serializable dirty state for bounded browser draft storage. */
127
+ draftSnapshot(): PreviewDocumentDraftSnapshot;
128
+ /** Restore a previously persisted dirty draft into a fresh controller. */
129
+ restoreDraft(snapshot: PreviewDocumentDraftSnapshot): void;
130
+ /** Notify whichever preview mount currently owns this controller. */
131
+ notifyDraftConflict(snapshot: PreviewDocumentDraftSnapshot): void;
92
132
  /** The payload last CONFIRMED persisted. */
93
133
  readonly lastSaved: PreviewDocumentPayload;
94
134
  /** The latest payload the user has typed (may differ from lastSaved). */
@@ -97,6 +137,8 @@ export interface PreviewDocumentSaveController {
97
137
  readonly hasPendingTimer: boolean;
98
138
  /** Whether a save() call is currently outstanding (in flight). */
99
139
  readonly isSaving: boolean;
140
+ /** Why the latest attempted save remains dirty, if it was deferred. */
141
+ readonly deferredReason: PreviewDocumentSaveDeferred["reason"] | null;
100
142
  /**
101
143
  * Whether this controller has confirmed at least one local save since creation.
102
144
  * Until the server query echoes that payload, clean local state is newer than
@@ -109,25 +151,21 @@ function payloadsEqual(a: PreviewDocumentPayload, b: PreviewDocumentPayload) {
109
151
  return a.title === b.title && a.content === b.content;
110
152
  }
111
153
 
112
- export function createPreviewDocumentSaveController(args: {
113
- /**
114
- * The document id this controller persists to, fixed for its entire life. A
115
- * controller NEVER changes which document it targets switching rows acquires
116
- * a different controller (see previewDocumentSaveRegistry).
117
- */
118
- documentId: string;
119
- initial: PreviewDocumentPayload;
120
- /** Persist `payload` to this controller's document. */
121
- save: (
122
- documentId: string,
123
- payload: PreviewDocumentPayload,
124
- ) => Promise<unknown>;
125
- onSaved?: (payload: PreviewDocumentPayload) => void;
126
- onError?: (error: unknown) => void;
127
- debounceMs?: number;
128
- setTimeoutFn?: typeof setTimeout;
129
- clearTimeoutFn?: typeof clearTimeout;
130
- }): PreviewDocumentSaveController {
154
+ export function createPreviewDocumentSaveController(
155
+ args: PreviewDocumentSaveAdapter & {
156
+ /**
157
+ * The document id this controller persists to, fixed for its entire life. A
158
+ * controller NEVER changes which document it targets — switching rows acquires
159
+ * a different controller (see previewDocumentSaveRegistry).
160
+ */
161
+ documentId: string;
162
+ initial: PreviewDocumentPayload;
163
+ /** Persist `payload` to this controller's document. */
164
+ debounceMs?: number;
165
+ setTimeoutFn?: typeof setTimeout;
166
+ clearTimeoutFn?: typeof clearTimeout;
167
+ },
168
+ ): PreviewDocumentSaveController {
131
169
  const documentId = args.documentId;
132
170
  const debounceMs = args.debounceMs ?? 450;
133
171
  const setTimeoutFn = args.setTimeoutFn ?? setTimeout;
@@ -137,6 +175,13 @@ export function createPreviewDocumentSaveController(args: {
137
175
  let pending: PreviewDocumentPayload = { ...args.initial };
138
176
  let timer: ReturnType<typeof setTimeout> | null = null;
139
177
  let hasSavedLocally = false;
178
+ let deferredReason: PreviewDocumentSaveDeferred["reason"] | null = null;
179
+ let saveAdapter: PreviewDocumentSaveAdapter = {
180
+ save: args.save,
181
+ onSaved: args.onSaved,
182
+ onError: args.onError,
183
+ onDraftConflict: args.onDraftConflict,
184
+ };
140
185
 
141
186
  // The single in-flight save, or null when idle. A debounced edit made while
142
187
  // this is set does NOT start a new save; it updates `pending` and a trailing
@@ -161,29 +206,42 @@ export function createPreviewDocumentSaveController(args: {
161
206
  if (payloadsEqual(pending, lastSaved)) return; // nothing dirty.
162
207
 
163
208
  const attempted = { ...pending };
164
- const promise = Promise.resolve(args.save(documentId, attempted))
209
+ const promise = Promise.resolve(
210
+ saveAdapter.save(documentId, attempted, { ...lastSaved }),
211
+ )
165
212
  .then((result) => {
166
213
  if (
167
214
  result &&
168
215
  typeof result === "object" &&
169
- "skipped" in result &&
170
- result.skipped === true
216
+ "outcome" in result &&
217
+ result.outcome === "deferred" &&
218
+ "reason" in result &&
219
+ (result.reason === "hydration" || result.reason === "conflict")
171
220
  ) {
172
- pending = { ...lastSaved };
221
+ // Hydration can begin after a keystroke but before the debounce fires.
222
+ // Keep the attempted payload dirty so the registry can retain it across
223
+ // close/reopen and a later flush can retry it. A deferred save is not a
224
+ // successful save, and user-authored content is never disposable.
225
+ deferredReason = result.reason;
173
226
  inFlight = null;
227
+ const deferredResult = result as PreviewDocumentSaveDeferred;
228
+ if (deferredResult.conflictSnapshot) {
229
+ saveAdapter.onDraftConflict?.(deferredResult.conflictSnapshot);
230
+ }
174
231
  return;
175
232
  }
176
233
  lastSaved = attempted;
177
234
  hasSavedLocally = true;
235
+ deferredReason = null;
178
236
  inFlight = null;
179
- args.onSaved?.(attempted);
237
+ saveAdapter.onSaved?.(attempted);
180
238
  // A trailing edit may have landed while this save was in flight. Issue
181
239
  // exactly one more for the LATEST payload. Bounded: stops once quiescent.
182
240
  kick();
183
241
  })
184
242
  .catch((error) => {
185
243
  inFlight = null;
186
- args.onError?.(error);
244
+ saveAdapter.onError?.(error);
187
245
  });
188
246
  inFlight = promise;
189
247
  }
@@ -201,10 +259,12 @@ export function createPreviewDocumentSaveController(args: {
201
259
  documentId,
202
260
  changeTitle(title: string) {
203
261
  pending = { ...pending, title };
262
+ deferredReason = null;
204
263
  schedule();
205
264
  },
206
265
  changeContent(content: string) {
207
266
  pending = { ...pending, content };
267
+ deferredReason = null;
208
268
  schedule();
209
269
  },
210
270
  flush() {
@@ -233,6 +293,38 @@ export function createPreviewDocumentSaveController(args: {
233
293
  lastSaved = { ...payload };
234
294
  pending = { ...payload };
235
295
  hasSavedLocally = false;
296
+ deferredReason = null;
297
+ },
298
+ rebasePending(payload: PreviewDocumentPayload) {
299
+ clearTimer();
300
+ lastSaved = { ...payload };
301
+ pending = {
302
+ ...pending,
303
+ loadedUpdatedAt: payload.loadedUpdatedAt,
304
+ loadedContentWasEmpty: payload.loadedContentWasEmpty,
305
+ };
306
+ hasSavedLocally = false;
307
+ deferredReason = null;
308
+ },
309
+ replaceSaveAdapter(adapter: PreviewDocumentSaveAdapter) {
310
+ saveAdapter = adapter;
311
+ },
312
+ draftSnapshot() {
313
+ return {
314
+ lastSaved: { ...lastSaved },
315
+ pending: { ...pending },
316
+ deferredReason,
317
+ };
318
+ },
319
+ restoreDraft(snapshot: PreviewDocumentDraftSnapshot) {
320
+ clearTimer();
321
+ lastSaved = { ...snapshot.lastSaved };
322
+ pending = { ...snapshot.pending };
323
+ deferredReason = snapshot.deferredReason;
324
+ hasSavedLocally = false;
325
+ },
326
+ notifyDraftConflict(snapshot: PreviewDocumentDraftSnapshot) {
327
+ saveAdapter.onDraftConflict?.(snapshot);
236
328
  },
237
329
  get lastSaved() {
238
330
  return { ...lastSaved };
@@ -246,6 +338,9 @@ export function createPreviewDocumentSaveController(args: {
246
338
  get isSaving() {
247
339
  return inFlight !== null;
248
340
  },
341
+ get deferredReason() {
342
+ return deferredReason;
343
+ },
249
344
  get hasSavedLocally() {
250
345
  return hasSavedLocally;
251
346
  },
@@ -67,12 +67,14 @@ function controllerIsDirty(controller: PreviewDocumentSaveController): boolean {
67
67
  export function acquirePreviewDocumentSaveController(
68
68
  documentId: string,
69
69
  factory: () => PreviewDocumentSaveController,
70
+ refreshAdapter?: (controller: PreviewDocumentSaveController) => void,
70
71
  ): PreviewDocumentSaveController {
71
72
  let entry = registry.get(documentId);
72
73
  if (!entry) {
73
74
  entry = { controller: factory(), refCount: 0, evicting: false };
74
75
  registry.set(documentId, entry);
75
76
  }
77
+ refreshAdapter?.(entry.controller);
76
78
  entry.refCount += 1;
77
79
  // A reopen before a pending eviction settled: keep the instance alive.
78
80
  entry.evicting = false;
@@ -364,6 +364,21 @@
364
364
  line-height: 1.45;
365
365
  }
366
366
 
367
+ .notion-editor h5 {
368
+ font-size: 1em;
369
+ font-weight: 600;
370
+ margin: 0.8em 0 0.15em;
371
+ line-height: 1.5;
372
+ }
373
+
374
+ .notion-editor h6 {
375
+ font-size: 0.95em;
376
+ font-weight: 600;
377
+ margin: 0.75em 0 0.15em;
378
+ line-height: 1.5;
379
+ color: hsl(var(--muted-foreground));
380
+ }
381
+
367
382
  /* Paragraphs */
368
383
  .notion-editor p {
369
384
  margin: 0.5em 0;
@@ -408,7 +423,9 @@
408
423
  .notion-editor h1.is-empty::before,
409
424
  .notion-editor h2.is-empty::before,
410
425
  .notion-editor h3.is-empty::before,
411
- .notion-editor h4.is-empty::before {
426
+ .notion-editor h4.is-empty::before,
427
+ .notion-editor h5.is-empty::before,
428
+ .notion-editor h6.is-empty::before {
412
429
  content: attr(data-placeholder);
413
430
  float: left;
414
431
  color: hsl(var(--muted-foreground));
@@ -4,6 +4,8 @@ import type {
4
4
  AddDatabaseItemRequest,
5
5
  AttachContentDatabaseSourceRequest,
6
6
  BuilderCmsModelsResponse,
7
+ CancelPreparedBuilderSourceUpdateRequest,
8
+ CancelPreparedBuilderSourceUpdateResponse,
7
9
  ChangeContentDatabaseSourceRoleRequest,
8
10
  ContentDatabaseResponse,
9
11
  ContentDatabasePersonalViewResponse,
@@ -25,6 +27,7 @@ import type {
25
27
  ExecuteBuilderSourceExecutionRequest,
26
28
  MoveDatabaseItemRequest,
27
29
  PrepareBuilderSourceExecutionRequest,
30
+ PreviewBuilderSourceReviewResponse,
28
31
  PrepareBuilderSourceReviewRequest,
29
32
  PrepareBuilderSourceReviewResponse,
30
33
  ProcessBuilderBodyHydrationRequest,
@@ -815,6 +818,27 @@ export function useAddContentDatabaseSourceFieldProperty(documentId: string) {
815
818
  });
816
819
  }
817
820
 
821
+ export function useMaterializeBuilderRequiredFields(documentId: string) {
822
+ const queryClient = useQueryClient();
823
+ return useActionMutation<
824
+ ContentDatabaseResponse,
825
+ { documentId: string; sourceId: string }
826
+ >("materialize-builder-required-fields", {
827
+ onSuccess: (data) => {
828
+ writeContentDatabaseResponseToCache(queryClient, documentId, data);
829
+ queryClient.invalidateQueries({
830
+ queryKey: contentDatabaseQueryKey(documentId),
831
+ });
832
+ queryClient.invalidateQueries({
833
+ queryKey: ["action", "get-content-database-source", { documentId }],
834
+ });
835
+ queryClient.invalidateQueries({
836
+ queryKey: ["action", "list-document-properties", { documentId }],
837
+ });
838
+ },
839
+ });
840
+ }
841
+
818
842
  export function useBuilderCmsModels(enabled: boolean) {
819
843
  return useActionQuery<BuilderCmsModelsResponse>(
820
844
  "list-builder-cms-models",
@@ -1014,6 +1038,23 @@ export function usePrepareBuilderSourceExecution(documentId: string) {
1014
1038
  });
1015
1039
  }
1016
1040
 
1041
+ export function useCancelPreparedBuilderSourceUpdate(documentId: string) {
1042
+ const queryClient = useQueryClient();
1043
+ return useActionMutation<
1044
+ CancelPreparedBuilderSourceUpdateResponse,
1045
+ CancelPreparedBuilderSourceUpdateRequest
1046
+ >("cancel-prepared-builder-source-update", {
1047
+ onSuccess: () => {
1048
+ queryClient.invalidateQueries({
1049
+ queryKey: contentDatabaseQueryKey(documentId),
1050
+ });
1051
+ queryClient.invalidateQueries({
1052
+ queryKey: ["action", "get-content-database-source", { documentId }],
1053
+ });
1054
+ },
1055
+ });
1056
+ }
1057
+
1017
1058
  export function useValidateBuilderSourceExecution(documentId: string) {
1018
1059
  const queryClient = useQueryClient();
1019
1060
  return useActionMutation<
@@ -1099,6 +1140,33 @@ export function usePrepareBuilderSourceReview(documentId: string) {
1099
1140
  });
1100
1141
  }
1101
1142
 
1143
+ export function usePreviewBuilderSourceReview(args: {
1144
+ documentId: string;
1145
+ sourceId: string | null;
1146
+ scope: "selected" | "all";
1147
+ documentIds?: string[];
1148
+ enabled: boolean;
1149
+ }) {
1150
+ return useActionQuery<PreviewBuilderSourceReviewResponse>(
1151
+ "preview-builder-source-review",
1152
+ args.enabled && args.sourceId
1153
+ ? {
1154
+ documentId: args.documentId,
1155
+ sourceId: args.sourceId,
1156
+ scope: args.scope,
1157
+ documentIds: args.documentIds,
1158
+ }
1159
+ : undefined,
1160
+ {
1161
+ enabled: args.enabled && !!args.sourceId,
1162
+ retry: false,
1163
+ staleTime: 0,
1164
+ gcTime: 0,
1165
+ refetchOnWindowFocus: false,
1166
+ },
1167
+ );
1168
+ }
1169
+
1102
1170
  export function useStageBuilderSourceBulkUpdate(documentId: string) {
1103
1171
  const queryClient = useQueryClient();
1104
1172
  return useActionMutation<
@@ -92,13 +92,15 @@ export function seedDatabaseItemDocumentCaches(
92
92
  // Seed only cold caches. Overwriting an existing entry would bump its
93
93
  // freshness with possibly older table-snapshot data (a background database
94
94
  // refetch can lag a just-saved document edit) and suppress the correcting
95
- // refetch for the whole staleTime window. Rows whose Builder body has not
96
- // hydrated yet, or whose source-backed list snapshot intentionally omits the
97
- // body, are never seeded: their empty table-snapshot `content` would render as
98
- // an authoritative empty document.
95
+ // refetch for the whole staleTime window. Source-backed rows are never seeded:
96
+ // list snapshots are not authoritative enough to unlock the body editor, even
97
+ // when they happen to contain non-empty content. The dedicated get-document
98
+ // response owns that decision and prevents an edit from racing hydration.
99
99
  if (
100
100
  !databaseItemBodyHydrationIsPending(item) &&
101
101
  !sourceBackedEmptyBody &&
102
+ !item.bodyHydration &&
103
+ !item.document.databaseMembership?.sourceId &&
102
104
  queryClient.getQueryData(documentQueryKey(item.document.id)) === undefined
103
105
  ) {
104
106
  queryClient.setQueryData<Document>(documentQueryKey(item.document.id), {
@@ -139,6 +141,53 @@ export function useDocument(id: string | null) {
139
141
  });
140
142
  }
141
143
 
144
+ export interface PreviewDocumentDraftRecord {
145
+ documentId: string;
146
+ title: string;
147
+ content: string;
148
+ baseDocumentUpdatedAt: string | null;
149
+ loadedContentWasEmpty: number;
150
+ deferredReason: string | null;
151
+ version: number;
152
+ updatedAt: string;
153
+ }
154
+
155
+ export function usePreviewDocumentDraft(documentId: string | null) {
156
+ return useActionQuery<{ draft: PreviewDocumentDraftRecord | null }>(
157
+ "get-preview-document-draft",
158
+ documentId ? { documentId } : undefined,
159
+ { enabled: !!documentId, retry: false },
160
+ );
161
+ }
162
+
163
+ export function useUpdatePreviewDocumentDraft() {
164
+ return useActionMutation<
165
+ {
166
+ status: "saved" | "deleted" | "conflict";
167
+ draft: PreviewDocumentDraftRecord | null;
168
+ },
169
+ | {
170
+ operation: "upsert";
171
+ documentId: string;
172
+ expectedVersion: number | null;
173
+ draft: {
174
+ title: string;
175
+ content: string;
176
+ baseDocumentUpdatedAt: string | null;
177
+ loadedContentWasEmpty: boolean;
178
+ deferredReason: "hydration" | "conflict" | null;
179
+ };
180
+ }
181
+ | {
182
+ operation: "delete";
183
+ documentId: string;
184
+ expectedVersion: number;
185
+ expectedTitle: string;
186
+ expectedContent: string;
187
+ }
188
+ >("update-preview-document-draft");
189
+ }
190
+
142
191
  export function useCreateDocument() {
143
192
  return useActionMutation<Document, DocumentCreateRequest>("create-document");
144
193
  }
@@ -578,6 +578,13 @@ const messages = {
578
578
  addDetailsToExistingItems: "為現有項目新增詳細資料",
579
579
  addedFieldsFromSource: "已從此來源新增 {{count}} 個欄位。",
580
580
  addedOneFieldFromSource: "已從此來源新增 1 個欄位。",
581
+ addRequiredFields: "新增必填欄位",
582
+ publishingFieldsAdded: "已新增發布欄位",
583
+ requiredBuilderFieldsReady:
584
+ "{{count}} 個 Builder 必填欄位已可在 Content 中編輯。",
585
+ requiredPublishingFields: "必填發布欄位",
586
+ requiredPublishingFieldsDescription:
587
+ "新增此集合發布前 Builder 所需的中繼資料:{{fields}}。",
581
588
  addingDetails: "正在新增詳細資料",
582
589
  addingDetailsMatchedOn: "正在新增以 {{field}} 比對的詳細資料。",
583
590
  addingItems: "正在新增項目",
@@ -628,6 +635,7 @@ const messages = {
628
635
  noPendingLocalBuilderChanges: "尚無待處理的本機 Builder 變更。",
629
636
  needsAttention: "需要處理",
630
637
  failedYouCanRetry: "失敗 — 您可以重試",
638
+ reconciliationRequired: "需要核對 — 請勿重試",
631
639
  needsAFreshReview: "需要重新審查",
632
640
  working: "處理中…",
633
641
  ready: "就緒",
@@ -653,6 +661,11 @@ const messages = {
653
661
  builderBodySyncing: "內容仍在從 Builder 同步",
654
662
  builderBodySyncingDescription:
655
663
  "同步 Builder 正文完成前會暫停編輯,避免覆寫既有文章內容。",
664
+ builderDraftConflictTitle: "你編輯期間收到了新的 Builder 內容",
665
+ builderDraftConflictDescription:
666
+ "你的本機草稿已保留。你可以保留草稿以在本機取代新同步的正文,或重新載入 Builder 正文。",
667
+ keepLocalDraft: "保留本機草稿",
668
+ reloadBuilderBody: "重新載入 Builder 正文",
656
669
  builderBodiesHydrated: "已同步 {{hydrated}} / {{total}} 個正文",
657
670
  builderReviewShowingRows:
658
671
  "此次審查顯示 {{total}} 個 Builder 列中的 {{shown}} 個。",
@@ -870,6 +883,8 @@ const messages = {
870
883
  retry: "Retry",
871
884
  reviewBeforeTheyReachBuilder: "在傳送到 Builder 前審核。",
872
885
  reviewDiff: "審核差異",
886
+ reviewDiffDescription: "在任何內容傳送到 Builder 前載入完整的內容差異。",
887
+ loadingCompleteBuilderDiff: "正在載入完整的 Builder 差異…",
873
888
  sampleMatches: "範例匹配",
874
889
  showAll: "顯示全部",
875
890
  sortAscending: "升序排序",
@@ -917,6 +932,7 @@ const messages = {
917
932
  builderModel: "Builder 模型",
918
933
  builderPreparingGate: "正在準備 Builder gate,並透過受保護路徑送出。",
919
934
  builderPushFinished: "Builder 推送已完成。",
935
+ builderPushAlreadyRunning: "Builder 推送已在執行中。未送出第二次寫入。",
920
936
  builderPushedReconciled: "已送出至 Builder 並完成本機對帳。",
921
937
  builderPushWillSend: "送出會透過受保護的 Builder 路徑寫入 autosave。",
922
938
  builderWritesDisabledCheckOnly: "Builder 寫入已停用。送出只會檢查更新。",
@@ -926,6 +942,15 @@ const messages = {
926
942
  checksOnly: "僅檢查",
927
943
  close: "關閉",
928
944
  closeBuilderUpdateReview: "關閉 Builder 更新審查",
945
+ cancelPreparedUpdate: "取消已準備的更新",
946
+ cancelPreparedUpdateQuestion: "要取消這個已準備的更新嗎?",
947
+ cancelPreparedUpdateDescription:
948
+ "這會在送出前停止本機執行並保留稽核記錄。不會向 Builder 傳送任何內容。",
949
+ keepPreparedUpdate: "保留已準備的更新",
950
+ cancellingPreparedUpdate: "正在取消…",
951
+ preparedUpdateCancelled: "已取消準備好的 Builder 更新",
952
+ preparedUpdateAlreadyCancelled: "準備好的 Builder 更新已被取消",
953
+ cancelPreparedUpdateFailed: "未能取消準備好的 Builder 更新",
929
954
  disabled: "已停用",
930
955
  draft: "草稿",
931
956
  enabled: "已啟用",