@agent-native/core 0.101.6 → 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 (237) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +28 -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/a2a/artifact-response.ts +255 -12
  16. package/corpus/core/src/agent/thread-data-builder.ts +84 -11
  17. package/corpus/core/src/client/review/ReviewCommentComposer.tsx +9 -1
  18. package/corpus/core/src/client/review/ReviewThreadPanel.tsx +17 -2
  19. package/corpus/core/src/client/settings/SettingsPanel.tsx +41 -21
  20. package/corpus/core/src/client/settings/useBuilderStatus.ts +28 -9
  21. package/corpus/core/src/client/setup-connections/BuilderConnectCard.tsx +2 -7
  22. package/corpus/core/src/integrations/a2a-continuation-processor.ts +99 -7
  23. package/corpus/core/src/integrations/adapters/discord.ts +3 -1
  24. package/corpus/core/src/integrations/adapters/email.ts +4 -1
  25. package/corpus/core/src/integrations/adapters/google-docs.ts +4 -1
  26. package/corpus/core/src/integrations/adapters/microsoft-teams.ts +3 -1
  27. package/corpus/core/src/integrations/adapters/slack.ts +38 -9
  28. package/corpus/core/src/integrations/adapters/telegram.ts +32 -10
  29. package/corpus/core/src/integrations/adapters/whatsapp.ts +5 -1
  30. package/corpus/core/src/integrations/index.ts +1 -0
  31. package/corpus/core/src/integrations/types.ts +9 -2
  32. package/corpus/core/src/integrations/webhook-handler.ts +69 -45
  33. package/corpus/core/src/server/agent-chat/action-filters-a2a.ts +1 -0
  34. package/corpus/core/src/server/auth.ts +25 -1
  35. package/corpus/core/src/server/builder-browser.ts +353 -1
  36. package/corpus/core/src/server/core-routes-plugin.ts +601 -223
  37. package/corpus/templates/calendar/app/components/calendar/DayView.tsx +80 -1
  38. package/corpus/templates/calendar/app/components/calendar/EventCard.tsx +28 -3
  39. package/corpus/templates/calendar/app/components/calendar/EventDetailPanel.tsx +5 -1
  40. package/corpus/templates/calendar/app/components/calendar/EventDetailPopover.tsx +30 -3
  41. package/corpus/templates/calendar/app/components/calendar/OutOfOfficeEvent.tsx +196 -0
  42. package/corpus/templates/calendar/app/components/calendar/WeekView.tsx +115 -1
  43. package/corpus/templates/calendar/app/lib/out-of-office.ts +45 -0
  44. package/corpus/templates/calendar/changelog/2026-07-14-out-of-office-blocks-now-sit-behind-meetings-and-event-stack.md +6 -0
  45. package/corpus/templates/content/.agents/skills/content/SKILL.md +31 -0
  46. package/corpus/templates/content/.agents/skills/document-editing/SKILL.md +35 -21
  47. package/corpus/templates/content/AGENTS.md +32 -9
  48. package/corpus/templates/content/actions/_builder-cms-intent-lookup.ts +87 -0
  49. package/corpus/templates/content/actions/_builder-cms-read-client.ts +173 -25
  50. package/corpus/templates/content/actions/_builder-cms-source-adapter.ts +1 -0
  51. package/corpus/templates/content/actions/_builder-cms-write-adapter.ts +427 -18
  52. package/corpus/templates/content/actions/_builder-cms-write-client.ts +17 -73
  53. package/corpus/templates/content/actions/_builder-cms-write-settings.ts +27 -7
  54. package/corpus/templates/content/actions/_builder-source-execution-claim.ts +124 -0
  55. package/corpus/templates/content/actions/_builder-source-execution-preservation.ts +21 -0
  56. package/corpus/templates/content/actions/_builder-source-timings.ts +57 -0
  57. package/corpus/templates/content/actions/_database-source-utils.ts +1071 -170
  58. package/corpus/templates/content/actions/_database-utils.ts +104 -13
  59. package/corpus/templates/content/actions/_local-file-documents.ts +53 -1
  60. package/corpus/templates/content/actions/_preview-document-draft.ts +31 -0
  61. package/corpus/templates/content/actions/_property-utils.ts +7 -1
  62. package/corpus/templates/content/actions/cancel-prepared-builder-source-update.ts +309 -0
  63. package/corpus/templates/content/actions/configure-document-property.ts +11 -0
  64. package/corpus/templates/content/actions/create-content-database.ts +11 -0
  65. package/corpus/templates/content/actions/create-document.ts +9 -0
  66. package/corpus/templates/content/actions/create-inline-content-database.ts +3 -0
  67. package/corpus/templates/content/actions/execute-builder-source-batch.ts +120 -49
  68. package/corpus/templates/content/actions/execute-builder-source-execution.ts +608 -264
  69. package/corpus/templates/content/actions/export-content-source.ts +1 -0
  70. package/corpus/templates/content/actions/get-content-database.ts +13 -1
  71. package/corpus/templates/content/actions/get-document.ts +12 -2
  72. package/corpus/templates/content/actions/get-preview-document-draft.ts +26 -0
  73. package/corpus/templates/content/actions/import-content-source.ts +6 -0
  74. package/corpus/templates/content/actions/list-documents.ts +3 -0
  75. package/corpus/templates/content/actions/materialize-builder-required-fields.ts +550 -0
  76. package/corpus/templates/content/actions/prepare-builder-source-execution.ts +112 -65
  77. package/corpus/templates/content/actions/prepare-builder-source-review.ts +463 -170
  78. package/corpus/templates/content/actions/preview-builder-source-review.ts +99 -0
  79. package/corpus/templates/content/actions/pull-document.ts +2 -0
  80. package/corpus/templates/content/actions/search-documents.ts +5 -1
  81. package/corpus/templates/content/actions/set-content-database-source-write-mode.ts +2 -2
  82. package/corpus/templates/content/actions/update-document.ts +16 -1
  83. package/corpus/templates/content/actions/update-preview-document-draft.ts +153 -0
  84. package/corpus/templates/content/actions/view-screen.ts +15 -2
  85. package/corpus/templates/content/app/components/editor/DescriptionField.tsx +121 -0
  86. package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +14 -0
  87. package/corpus/templates/content/app/components/editor/DocumentProperties.tsx +250 -49
  88. package/corpus/templates/content/app/components/editor/VisualEditor.tsx +57 -5
  89. package/corpus/templates/content/app/components/editor/body-hydration.ts +27 -0
  90. package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +2414 -589
  91. package/corpus/templates/content/app/components/editor/database/settings.tsx +13 -10
  92. package/corpus/templates/content/app/components/editor/database-sources/BuilderSourceReviewDialog.tsx +358 -75
  93. package/corpus/templates/content/app/components/editor/previewDocumentSaveController.ts +124 -29
  94. package/corpus/templates/content/app/components/editor/previewDocumentSaveRegistry.ts +2 -0
  95. package/corpus/templates/content/app/global.css +18 -1
  96. package/corpus/templates/content/app/hooks/use-content-database.ts +68 -0
  97. package/corpus/templates/content/app/hooks/use-documents.ts +53 -4
  98. package/corpus/templates/content/app/i18n/zh-TW.ts +30 -0
  99. package/corpus/templates/content/app/i18n-data.ts +339 -0
  100. package/corpus/templates/content/changelog/2026-07-10-builder-backed-preview-edits-are-preserved-when-article-bodi.md +6 -0
  101. package/corpus/templates/content/changelog/2026-07-10-builder-cms-sources-can-now-be-enabled-for-guarded-staged-or.md +6 -0
  102. package/corpus/templates/content/changelog/2026-07-13-builder-connection-setup-is-now-available-directly-from-conn.md +6 -0
  103. package/corpus/templates/content/changelog/2026-07-13-builder-reviews-now-hide-already-applied-writes-detect-nativ.md +6 -0
  104. package/corpus/templates/content/changelog/2026-07-13-required-builder-publishing-fields-can-now-be-added-directly.md +6 -0
  105. package/corpus/templates/content/changelog/2026-07-13-you-can-now-cancel-a-prepared-builder-update-safely-before-i.md +6 -0
  106. package/corpus/templates/content/changelog/2026-07-14-builder-account-connection-now-remains-available-when-branch.md +6 -0
  107. package/corpus/templates/content/changelog/2026-07-14-builder-backed-articles-now-preserve-fifth-and-sixth-level-h.md +6 -0
  108. package/corpus/templates/content/changelog/2026-07-14-builder-sources-now-review-and-publish-rich-article-updates-.md +6 -0
  109. package/corpus/templates/content/changelog/2026-07-14-builder-writes-now-stop-retries-when-reconciliation-is-needed.md +6 -0
  110. package/corpus/templates/content/changelog/2026-07-14-file-and-media-links-now-validate-before-saving.md +6 -0
  111. package/corpus/templates/content/changelog/2026-07-14-pages-and-databases-can-describe-themselves-with-guidance-fo.md +6 -0
  112. package/corpus/templates/content/changelog/2026-07-14-slack-follow-ups-now-retain-created-content-identity-and-per.md +6 -0
  113. package/corpus/templates/content/parity/matrix.md +27 -25
  114. package/corpus/templates/content/parity/matrix.ts +50 -2
  115. package/corpus/templates/content/server/db/schema.ts +51 -0
  116. package/corpus/templates/content/server/lib/document-context.ts +76 -0
  117. package/corpus/templates/content/server/lib/public-documents.ts +3 -0
  118. package/corpus/templates/content/server/plugins/auth.ts +1 -0
  119. package/corpus/templates/content/server/plugins/db.ts +59 -0
  120. package/corpus/templates/content/server/routes/api/document-agent-context.json.get.ts +5 -0
  121. package/corpus/templates/content/shared/api.ts +87 -1
  122. package/corpus/templates/content/shared/builder-mdx.ts +705 -15
  123. package/corpus/templates/content/shared/content-source.ts +7 -0
  124. package/corpus/templates/content/shared/nfm.ts +3 -3
  125. package/corpus/templates/content/shared/properties.ts +2 -0
  126. package/corpus/templates/design/app/components/design/ReadOnlyDesignBanner.tsx +52 -7
  127. package/corpus/templates/design/app/components/design/ReviewCommentsPanel.tsx +36 -4
  128. package/corpus/templates/design/app/components/visual-editor/ReviewCanvasPins.tsx +4 -1
  129. package/corpus/templates/design/app/i18n/ar-SA.ts +1 -0
  130. package/corpus/templates/design/app/i18n/de-DE.ts +1 -0
  131. package/corpus/templates/design/app/i18n/en-US.ts +1 -0
  132. package/corpus/templates/design/app/i18n/es-ES.ts +1 -0
  133. package/corpus/templates/design/app/i18n/fr-FR.ts +1 -0
  134. package/corpus/templates/design/app/i18n/hi-IN.ts +1 -0
  135. package/corpus/templates/design/app/i18n/ja-JP.ts +1 -0
  136. package/corpus/templates/design/app/i18n/ko-KR.ts +1 -0
  137. package/corpus/templates/design/app/i18n/pt-BR.ts +1 -0
  138. package/corpus/templates/design/app/i18n/zh-CN.ts +1 -0
  139. package/corpus/templates/design/app/i18n/zh-TW.ts +1 -0
  140. package/corpus/templates/design/app/pages/design-editor/selection-state.ts +8 -0
  141. package/corpus/templates/design/app/pages/design-editor/tool-state.ts +11 -0
  142. package/corpus/templates/design/changelog/2026-07-14-reviewers-can-now-pin-comments-from-the-canvas-toolbar-targe.md +6 -0
  143. package/corpus/templates/design/changelog/2026-07-15-viewer-comment-controls-now-sit-inside-the-read-only-notice-.md +6 -0
  144. package/corpus/templates/design/shared/review-anchor.ts +48 -0
  145. package/corpus/templates/plan/server/plan-mdx.ts +53 -7
  146. package/dist/a2a/artifact-response.d.ts +15 -0
  147. package/dist/a2a/artifact-response.d.ts.map +1 -1
  148. package/dist/a2a/artifact-response.js +204 -5
  149. package/dist/a2a/artifact-response.js.map +1 -1
  150. package/dist/agent/thread-data-builder.d.ts +6 -0
  151. package/dist/agent/thread-data-builder.d.ts.map +1 -1
  152. package/dist/agent/thread-data-builder.js +71 -8
  153. package/dist/agent/thread-data-builder.js.map +1 -1
  154. package/dist/client/review/ReviewCommentComposer.d.ts +2 -1
  155. package/dist/client/review/ReviewCommentComposer.d.ts.map +1 -1
  156. package/dist/client/review/ReviewCommentComposer.js +3 -3
  157. package/dist/client/review/ReviewCommentComposer.js.map +1 -1
  158. package/dist/client/review/ReviewThreadPanel.d.ts +9 -1
  159. package/dist/client/review/ReviewThreadPanel.d.ts.map +1 -1
  160. package/dist/client/review/ReviewThreadPanel.js +6 -4
  161. package/dist/client/review/ReviewThreadPanel.js.map +1 -1
  162. package/dist/client/settings/SettingsPanel.d.ts +3 -0
  163. package/dist/client/settings/SettingsPanel.d.ts.map +1 -1
  164. package/dist/client/settings/SettingsPanel.js +11 -4
  165. package/dist/client/settings/SettingsPanel.js.map +1 -1
  166. package/dist/client/settings/useBuilderStatus.d.ts +8 -4
  167. package/dist/client/settings/useBuilderStatus.d.ts.map +1 -1
  168. package/dist/client/settings/useBuilderStatus.js +19 -6
  169. package/dist/client/settings/useBuilderStatus.js.map +1 -1
  170. package/dist/client/setup-connections/BuilderConnectCard.d.ts.map +1 -1
  171. package/dist/client/setup-connections/BuilderConnectCard.js +2 -5
  172. package/dist/client/setup-connections/BuilderConnectCard.js.map +1 -1
  173. package/dist/collab/awareness.d.ts +2 -2
  174. package/dist/collab/awareness.d.ts.map +1 -1
  175. package/dist/integrations/a2a-continuation-processor.d.ts.map +1 -1
  176. package/dist/integrations/a2a-continuation-processor.js +72 -6
  177. package/dist/integrations/a2a-continuation-processor.js.map +1 -1
  178. package/dist/integrations/adapters/discord.d.ts.map +1 -1
  179. package/dist/integrations/adapters/discord.js +1 -0
  180. package/dist/integrations/adapters/discord.js.map +1 -1
  181. package/dist/integrations/adapters/email.d.ts.map +1 -1
  182. package/dist/integrations/adapters/email.js +2 -0
  183. package/dist/integrations/adapters/email.js.map +1 -1
  184. package/dist/integrations/adapters/google-docs.d.ts.map +1 -1
  185. package/dist/integrations/adapters/google-docs.js +2 -0
  186. package/dist/integrations/adapters/google-docs.js.map +1 -1
  187. package/dist/integrations/adapters/microsoft-teams.d.ts.map +1 -1
  188. package/dist/integrations/adapters/microsoft-teams.js +1 -0
  189. package/dist/integrations/adapters/microsoft-teams.js.map +1 -1
  190. package/dist/integrations/adapters/slack.d.ts.map +1 -1
  191. package/dist/integrations/adapters/slack.js +21 -6
  192. package/dist/integrations/adapters/slack.js.map +1 -1
  193. package/dist/integrations/adapters/telegram.d.ts.map +1 -1
  194. package/dist/integrations/adapters/telegram.js +11 -2
  195. package/dist/integrations/adapters/telegram.js.map +1 -1
  196. package/dist/integrations/adapters/whatsapp.d.ts.map +1 -1
  197. package/dist/integrations/adapters/whatsapp.js +3 -0
  198. package/dist/integrations/adapters/whatsapp.js.map +1 -1
  199. package/dist/integrations/index.d.ts +1 -1
  200. package/dist/integrations/index.d.ts.map +1 -1
  201. package/dist/integrations/index.js.map +1 -1
  202. package/dist/integrations/types.d.ts +8 -2
  203. package/dist/integrations/types.d.ts.map +1 -1
  204. package/dist/integrations/types.js.map +1 -1
  205. package/dist/integrations/webhook-handler.d.ts.map +1 -1
  206. package/dist/integrations/webhook-handler.js +40 -43
  207. package/dist/integrations/webhook-handler.js.map +1 -1
  208. package/dist/observability/routes.d.ts +5 -5
  209. package/dist/provider-api/corpus-jobs.d.ts +2 -2
  210. package/dist/secrets/routes.d.ts +9 -9
  211. package/dist/server/agent-chat/action-filters-a2a.d.ts.map +1 -1
  212. package/dist/server/agent-chat/action-filters-a2a.js +1 -0
  213. package/dist/server/agent-chat/action-filters-a2a.js.map +1 -1
  214. package/dist/server/auth.d.ts +1 -0
  215. package/dist/server/auth.d.ts.map +1 -1
  216. package/dist/server/auth.js +19 -2
  217. package/dist/server/auth.js.map +1 -1
  218. package/dist/server/builder-browser.d.ts +78 -0
  219. package/dist/server/builder-browser.d.ts.map +1 -1
  220. package/dist/server/builder-browser.js +246 -1
  221. package/dist/server/builder-browser.js.map +1 -1
  222. package/dist/server/core-routes-plugin.d.ts +41 -0
  223. package/dist/server/core-routes-plugin.d.ts.map +1 -1
  224. package/dist/server/core-routes-plugin.js +260 -14
  225. package/dist/server/core-routes-plugin.js.map +1 -1
  226. package/docs/content/deployment.mdx +9 -7
  227. package/docs/content/locales/ar-SA/deployment.mdx +9 -7
  228. package/docs/content/locales/de-DE/deployment.mdx +9 -7
  229. package/docs/content/locales/es-ES/deployment.mdx +9 -7
  230. package/docs/content/locales/fr-FR/deployment.mdx +9 -7
  231. package/docs/content/locales/hi-IN/deployment.mdx +9 -7
  232. package/docs/content/locales/ja-JP/deployment.mdx +9 -7
  233. package/docs/content/locales/ko-KR/deployment.mdx +9 -7
  234. package/docs/content/locales/pt-BR/deployment.mdx +9 -7
  235. package/docs/content/locales/zh-CN/deployment.mdx +9 -7
  236. package/docs/content/locales/zh-TW/deployment.mdx +9 -7
  237. 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
  }
@@ -357,6 +357,11 @@ const messages = {
357
357
  pasteFileOrMediaLink: "貼上檔案或媒體連結",
358
358
  personOrEmail: "個人或電子郵件",
359
359
  propertyMenuFor: "{{name}} 的屬性選單",
360
+ description: "說明",
361
+ addPageDescription: "新增此頁面的說明…",
362
+ addDatabaseDescription: "新增此資料庫的說明…",
363
+ addPropertyDescription: "說明此屬性應包含的內容…",
364
+ addOptionDescription: "何時應使用此選項?",
360
365
  propertyName: "物業名稱",
361
366
  readOnly: "唯讀",
362
367
  remove: "刪除",
@@ -573,6 +578,13 @@ const messages = {
573
578
  addDetailsToExistingItems: "為現有項目新增詳細資料",
574
579
  addedFieldsFromSource: "已從此來源新增 {{count}} 個欄位。",
575
580
  addedOneFieldFromSource: "已從此來源新增 1 個欄位。",
581
+ addRequiredFields: "新增必填欄位",
582
+ publishingFieldsAdded: "已新增發布欄位",
583
+ requiredBuilderFieldsReady:
584
+ "{{count}} 個 Builder 必填欄位已可在 Content 中編輯。",
585
+ requiredPublishingFields: "必填發布欄位",
586
+ requiredPublishingFieldsDescription:
587
+ "新增此集合發布前 Builder 所需的中繼資料:{{fields}}。",
576
588
  addingDetails: "正在新增詳細資料",
577
589
  addingDetailsMatchedOn: "正在新增以 {{field}} 比對的詳細資料。",
578
590
  addingItems: "正在新增項目",
@@ -623,6 +635,7 @@ const messages = {
623
635
  noPendingLocalBuilderChanges: "尚無待處理的本機 Builder 變更。",
624
636
  needsAttention: "需要處理",
625
637
  failedYouCanRetry: "失敗 — 您可以重試",
638
+ reconciliationRequired: "需要核對 — 請勿重試",
626
639
  needsAFreshReview: "需要重新審查",
627
640
  working: "處理中…",
628
641
  ready: "就緒",
@@ -648,6 +661,11 @@ const messages = {
648
661
  builderBodySyncing: "內容仍在從 Builder 同步",
649
662
  builderBodySyncingDescription:
650
663
  "同步 Builder 正文完成前會暫停編輯,避免覆寫既有文章內容。",
664
+ builderDraftConflictTitle: "你編輯期間收到了新的 Builder 內容",
665
+ builderDraftConflictDescription:
666
+ "你的本機草稿已保留。你可以保留草稿以在本機取代新同步的正文,或重新載入 Builder 正文。",
667
+ keepLocalDraft: "保留本機草稿",
668
+ reloadBuilderBody: "重新載入 Builder 正文",
651
669
  builderBodiesHydrated: "已同步 {{hydrated}} / {{total}} 個正文",
652
670
  builderReviewShowingRows:
653
671
  "此次審查顯示 {{total}} 個 Builder 列中的 {{shown}} 個。",
@@ -865,6 +883,8 @@ const messages = {
865
883
  retry: "Retry",
866
884
  reviewBeforeTheyReachBuilder: "在傳送到 Builder 前審核。",
867
885
  reviewDiff: "審核差異",
886
+ reviewDiffDescription: "在任何內容傳送到 Builder 前載入完整的內容差異。",
887
+ loadingCompleteBuilderDiff: "正在載入完整的 Builder 差異…",
868
888
  sampleMatches: "範例匹配",
869
889
  showAll: "顯示全部",
870
890
  sortAscending: "升序排序",
@@ -912,6 +932,7 @@ const messages = {
912
932
  builderModel: "Builder 模型",
913
933
  builderPreparingGate: "正在準備 Builder gate,並透過受保護路徑送出。",
914
934
  builderPushFinished: "Builder 推送已完成。",
935
+ builderPushAlreadyRunning: "Builder 推送已在執行中。未送出第二次寫入。",
915
936
  builderPushedReconciled: "已送出至 Builder 並完成本機對帳。",
916
937
  builderPushWillSend: "送出會透過受保護的 Builder 路徑寫入 autosave。",
917
938
  builderWritesDisabledCheckOnly: "Builder 寫入已停用。送出只會檢查更新。",
@@ -921,6 +942,15 @@ const messages = {
921
942
  checksOnly: "僅檢查",
922
943
  close: "關閉",
923
944
  closeBuilderUpdateReview: "關閉 Builder 更新審查",
945
+ cancelPreparedUpdate: "取消已準備的更新",
946
+ cancelPreparedUpdateQuestion: "要取消這個已準備的更新嗎?",
947
+ cancelPreparedUpdateDescription:
948
+ "這會在送出前停止本機執行並保留稽核記錄。不會向 Builder 傳送任何內容。",
949
+ keepPreparedUpdate: "保留已準備的更新",
950
+ cancellingPreparedUpdate: "正在取消…",
951
+ preparedUpdateCancelled: "已取消準備好的 Builder 更新",
952
+ preparedUpdateAlreadyCancelled: "準備好的 Builder 更新已被取消",
953
+ cancelPreparedUpdateFailed: "未能取消準備好的 Builder 更新",
924
954
  disabled: "已停用",
925
955
  draft: "草稿",
926
956
  enabled: "已啟用",