@agent-native/core 0.101.7 → 0.101.11

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 (219) 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/client.ts +23 -1
  16. package/corpus/core/src/a2a/handlers.ts +65 -1
  17. package/corpus/core/src/a2a/server.ts +124 -0
  18. package/corpus/core/src/a2a/task-store.ts +253 -0
  19. package/corpus/core/src/a2a/types.ts +26 -0
  20. package/corpus/core/src/agent/production-agent.ts +5 -2
  21. package/corpus/core/src/client/review/ReviewCommentComposer.tsx +9 -1
  22. package/corpus/core/src/client/review/ReviewThreadPanel.tsx +17 -2
  23. package/corpus/core/src/client/settings/SettingsPanel.tsx +41 -21
  24. package/corpus/core/src/client/settings/useBuilderStatus.ts +28 -9
  25. package/corpus/core/src/client/setup-connections/BuilderConnectCard.tsx +2 -7
  26. package/corpus/core/src/mcp/builtin-tools.ts +25 -1
  27. package/corpus/core/src/scripts/call-agent.ts +22 -3
  28. package/corpus/core/src/server/agent-chat-plugin.ts +86 -1
  29. package/corpus/core/src/server/auth.ts +25 -1
  30. package/corpus/core/src/server/builder-browser.ts +353 -1
  31. package/corpus/core/src/server/core-routes-plugin.ts +601 -223
  32. package/corpus/core/src/templates/workspace-core/.agents/skills/a2a-protocol/SKILL.md +25 -0
  33. package/corpus/templates/calendar/app/components/calendar/DayView.tsx +80 -1
  34. package/corpus/templates/calendar/app/components/calendar/EventCard.tsx +28 -3
  35. package/corpus/templates/calendar/app/components/calendar/EventDetailPanel.tsx +5 -1
  36. package/corpus/templates/calendar/app/components/calendar/EventDetailPopover.tsx +30 -3
  37. package/corpus/templates/calendar/app/components/calendar/OutOfOfficeEvent.tsx +196 -0
  38. package/corpus/templates/calendar/app/components/calendar/WeekView.tsx +115 -1
  39. package/corpus/templates/calendar/app/lib/out-of-office.ts +45 -0
  40. package/corpus/templates/calendar/changelog/2026-07-14-out-of-office-blocks-now-sit-behind-meetings-and-event-stack.md +6 -0
  41. package/corpus/templates/content/.agents/skills/document-editing/SKILL.md +23 -0
  42. package/corpus/templates/content/AGENTS.md +3 -2
  43. package/corpus/templates/content/actions/_builder-cms-intent-lookup.ts +87 -0
  44. package/corpus/templates/content/actions/_builder-cms-read-client.ts +173 -25
  45. package/corpus/templates/content/actions/_builder-cms-source-adapter.ts +1 -0
  46. package/corpus/templates/content/actions/_builder-cms-write-adapter.ts +427 -18
  47. package/corpus/templates/content/actions/_builder-cms-write-client.ts +17 -73
  48. package/corpus/templates/content/actions/_builder-cms-write-settings.ts +27 -7
  49. package/corpus/templates/content/actions/_builder-source-execution-claim.ts +124 -0
  50. package/corpus/templates/content/actions/_builder-source-execution-preservation.ts +21 -0
  51. package/corpus/templates/content/actions/_builder-source-timings.ts +57 -0
  52. package/corpus/templates/content/actions/_database-source-utils.ts +1041 -154
  53. package/corpus/templates/content/actions/_database-utils.ts +89 -12
  54. package/corpus/templates/content/actions/_preview-document-draft.ts +31 -0
  55. package/corpus/templates/content/actions/cancel-prepared-builder-source-update.ts +309 -0
  56. package/corpus/templates/content/actions/execute-builder-source-batch.ts +120 -49
  57. package/corpus/templates/content/actions/execute-builder-source-execution.ts +608 -264
  58. package/corpus/templates/content/actions/get-preview-document-draft.ts +26 -0
  59. package/corpus/templates/content/actions/materialize-builder-required-fields.ts +550 -0
  60. package/corpus/templates/content/actions/prepare-builder-source-execution.ts +112 -65
  61. package/corpus/templates/content/actions/prepare-builder-source-review.ts +463 -170
  62. package/corpus/templates/content/actions/preview-builder-source-review.ts +99 -0
  63. package/corpus/templates/content/actions/set-content-database-source-write-mode.ts +2 -2
  64. package/corpus/templates/content/actions/update-preview-document-draft.ts +153 -0
  65. package/corpus/templates/content/app/components/editor/DocumentProperties.tsx +27 -15
  66. package/corpus/templates/content/app/components/editor/MathRenderer.tsx +43 -0
  67. package/corpus/templates/content/app/components/editor/SlashCommandMenu.tsx +260 -4
  68. package/corpus/templates/content/app/components/editor/VisualEditor.tsx +57 -5
  69. package/corpus/templates/content/app/components/editor/body-hydration.ts +27 -0
  70. package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +2353 -576
  71. package/corpus/templates/content/app/components/editor/database/settings.tsx +13 -10
  72. package/corpus/templates/content/app/components/editor/database-sources/BuilderSourceReviewDialog.tsx +358 -75
  73. package/corpus/templates/content/app/components/editor/extensions/NotionExtensions.tsx +78 -0
  74. package/corpus/templates/content/app/components/editor/previewDocumentSaveController.ts +124 -29
  75. package/corpus/templates/content/app/components/editor/previewDocumentSaveRegistry.ts +2 -0
  76. package/corpus/templates/content/app/global.css +71 -1
  77. package/corpus/templates/content/app/hooks/use-content-database.ts +68 -0
  78. package/corpus/templates/content/app/hooks/use-documents.ts +53 -4
  79. package/corpus/templates/content/app/i18n/zh-TW.ts +38 -0
  80. package/corpus/templates/content/app/i18n-data.ts +450 -0
  81. package/corpus/templates/content/app/root.tsx +2 -0
  82. package/corpus/templates/content/changelog/2026-07-10-builder-backed-preview-edits-are-preserved-when-article-bodi.md +6 -0
  83. package/corpus/templates/content/changelog/2026-07-10-builder-cms-sources-can-now-be-enabled-for-guarded-staged-or.md +6 -0
  84. package/corpus/templates/content/changelog/2026-07-13-builder-connection-setup-is-now-available-directly-from-conn.md +6 -0
  85. package/corpus/templates/content/changelog/2026-07-13-builder-reviews-now-hide-already-applied-writes-detect-nativ.md +6 -0
  86. package/corpus/templates/content/changelog/2026-07-13-required-builder-publishing-fields-can-now-be-added-directly.md +6 -0
  87. package/corpus/templates/content/changelog/2026-07-13-you-can-now-cancel-a-prepared-builder-update-safely-before-i.md +6 -0
  88. package/corpus/templates/content/changelog/2026-07-14-builder-account-connection-now-remains-available-when-branch.md +6 -0
  89. package/corpus/templates/content/changelog/2026-07-14-builder-backed-articles-now-preserve-fifth-and-sixth-level-h.md +6 -0
  90. package/corpus/templates/content/changelog/2026-07-14-builder-sources-now-review-and-publish-rich-article-updates-.md +6 -0
  91. package/corpus/templates/content/changelog/2026-07-14-builder-writes-now-stop-retries-when-reconciliation-is-needed.md +6 -0
  92. package/corpus/templates/content/changelog/2026-07-14-file-and-media-links-now-validate-before-saving.md +6 -0
  93. package/corpus/templates/content/changelog/2026-07-14-latex-equations-render-in-documents-public-pages-and-exports.md +6 -0
  94. package/corpus/templates/content/package.json +1 -0
  95. package/corpus/templates/content/parity/matrix.md +27 -25
  96. package/corpus/templates/content/parity/matrix.ts +50 -2
  97. package/corpus/templates/content/server/db/schema.ts +47 -0
  98. package/corpus/templates/content/server/plugins/auth.ts +1 -0
  99. package/corpus/templates/content/server/plugins/db.ts +53 -0
  100. package/corpus/templates/content/shared/api.ts +70 -1
  101. package/corpus/templates/content/shared/builder-mdx.ts +705 -15
  102. package/corpus/templates/content/shared/document-export.ts +296 -25
  103. package/corpus/templates/content/shared/inline-math.ts +128 -0
  104. package/corpus/templates/content/shared/math-rendering.ts +50 -0
  105. package/corpus/templates/content/shared/nfm.ts +20 -17
  106. package/corpus/templates/content/vite.config.ts +0 -1
  107. package/corpus/templates/design/app/components/design/ReadOnlyDesignBanner.tsx +52 -7
  108. package/corpus/templates/design/app/components/design/ReviewCommentsPanel.tsx +36 -4
  109. package/corpus/templates/design/app/components/visual-editor/ReviewCanvasPins.tsx +4 -1
  110. package/corpus/templates/design/app/i18n/ar-SA.ts +1 -0
  111. package/corpus/templates/design/app/i18n/de-DE.ts +1 -0
  112. package/corpus/templates/design/app/i18n/en-US.ts +1 -0
  113. package/corpus/templates/design/app/i18n/es-ES.ts +1 -0
  114. package/corpus/templates/design/app/i18n/fr-FR.ts +1 -0
  115. package/corpus/templates/design/app/i18n/hi-IN.ts +1 -0
  116. package/corpus/templates/design/app/i18n/ja-JP.ts +1 -0
  117. package/corpus/templates/design/app/i18n/ko-KR.ts +1 -0
  118. package/corpus/templates/design/app/i18n/pt-BR.ts +1 -0
  119. package/corpus/templates/design/app/i18n/zh-CN.ts +1 -0
  120. package/corpus/templates/design/app/i18n/zh-TW.ts +1 -0
  121. package/corpus/templates/design/app/pages/design-editor/selection-state.ts +8 -0
  122. package/corpus/templates/design/app/pages/design-editor/tool-state.ts +11 -0
  123. package/corpus/templates/design/changelog/2026-07-14-reviewers-can-now-pin-comments-from-the-canvas-toolbar-targe.md +6 -0
  124. package/corpus/templates/design/changelog/2026-07-15-viewer-comment-controls-now-sit-inside-the-read-only-notice-.md +6 -0
  125. package/corpus/templates/design/shared/review-anchor.ts +48 -0
  126. package/corpus/templates/mail/.agents/skills/email-drafts/SKILL.md +17 -0
  127. package/corpus/templates/mail/AGENTS.md +21 -19
  128. package/corpus/templates/mail/actions/create-attachment-upload.ts +49 -0
  129. package/corpus/templates/mail/changelog/2026-07-14-connected-agents-can-now-securely-upload-local-files-for-mai.md +6 -0
  130. package/corpus/templates/mail/server/handlers/media.ts +84 -61
  131. package/corpus/templates/mail/server/lib/attachment-upload-ticket.ts +234 -0
  132. package/corpus/templates/mail/server/lib/mail-connector-catalog.ts +8 -4
  133. package/corpus/templates/mail/server/lib/media-upload.ts +114 -0
  134. package/corpus/templates/mail/server/plugins/auth.ts +9 -1
  135. package/corpus/templates/mail/server/routes/api/media/attachment-upload/[uploadId].put.ts +1 -0
  136. package/corpus/templates/plan/server/plan-mdx.ts +53 -7
  137. package/dist/a2a/client.d.ts +5 -1
  138. package/dist/a2a/client.d.ts.map +1 -1
  139. package/dist/a2a/client.js +18 -1
  140. package/dist/a2a/client.js.map +1 -1
  141. package/dist/a2a/handlers.d.ts.map +1 -1
  142. package/dist/a2a/handlers.js +57 -8
  143. package/dist/a2a/handlers.js.map +1 -1
  144. package/dist/a2a/server.d.ts.map +1 -1
  145. package/dist/a2a/server.js +87 -1
  146. package/dist/a2a/server.js.map +1 -1
  147. package/dist/a2a/task-store.d.ts +27 -0
  148. package/dist/a2a/task-store.d.ts.map +1 -1
  149. package/dist/a2a/task-store.js +216 -0
  150. package/dist/a2a/task-store.js.map +1 -1
  151. package/dist/a2a/types.d.ts +24 -0
  152. package/dist/a2a/types.d.ts.map +1 -1
  153. package/dist/a2a/types.js.map +1 -1
  154. package/dist/agent/production-agent.d.ts +1 -0
  155. package/dist/agent/production-agent.d.ts.map +1 -1
  156. package/dist/agent/production-agent.js +5 -2
  157. package/dist/agent/production-agent.js.map +1 -1
  158. package/dist/client/review/ReviewCommentComposer.d.ts +2 -1
  159. package/dist/client/review/ReviewCommentComposer.d.ts.map +1 -1
  160. package/dist/client/review/ReviewCommentComposer.js +3 -3
  161. package/dist/client/review/ReviewCommentComposer.js.map +1 -1
  162. package/dist/client/review/ReviewThreadPanel.d.ts +9 -1
  163. package/dist/client/review/ReviewThreadPanel.d.ts.map +1 -1
  164. package/dist/client/review/ReviewThreadPanel.js +6 -4
  165. package/dist/client/review/ReviewThreadPanel.js.map +1 -1
  166. package/dist/client/settings/SettingsPanel.d.ts +3 -0
  167. package/dist/client/settings/SettingsPanel.d.ts.map +1 -1
  168. package/dist/client/settings/SettingsPanel.js +11 -4
  169. package/dist/client/settings/SettingsPanel.js.map +1 -1
  170. package/dist/client/settings/useBuilderStatus.d.ts +8 -4
  171. package/dist/client/settings/useBuilderStatus.d.ts.map +1 -1
  172. package/dist/client/settings/useBuilderStatus.js +19 -6
  173. package/dist/client/settings/useBuilderStatus.js.map +1 -1
  174. package/dist/client/setup-connections/BuilderConnectCard.d.ts.map +1 -1
  175. package/dist/client/setup-connections/BuilderConnectCard.js +2 -5
  176. package/dist/client/setup-connections/BuilderConnectCard.js.map +1 -1
  177. package/dist/collab/awareness.d.ts +2 -2
  178. package/dist/collab/awareness.d.ts.map +1 -1
  179. package/dist/collab/struct-routes.d.ts +1 -1
  180. package/dist/file-upload/actions/upload-image.d.ts +1 -1
  181. package/dist/mcp/builtin-tools.d.ts.map +1 -1
  182. package/dist/mcp/builtin-tools.js +22 -3
  183. package/dist/mcp/builtin-tools.js.map +1 -1
  184. package/dist/notifications/routes.d.ts +3 -3
  185. package/dist/resources/handlers.d.ts +2 -2
  186. package/dist/scripts/call-agent.d.ts +1 -1
  187. package/dist/scripts/call-agent.d.ts.map +1 -1
  188. package/dist/scripts/call-agent.js +19 -1
  189. package/dist/scripts/call-agent.js.map +1 -1
  190. package/dist/server/agent-chat-plugin.d.ts.map +1 -1
  191. package/dist/server/agent-chat-plugin.js +71 -2
  192. package/dist/server/agent-chat-plugin.js.map +1 -1
  193. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  194. package/dist/server/auth.d.ts +1 -0
  195. package/dist/server/auth.d.ts.map +1 -1
  196. package/dist/server/auth.js +19 -2
  197. package/dist/server/auth.js.map +1 -1
  198. package/dist/server/builder-browser.d.ts +78 -0
  199. package/dist/server/builder-browser.d.ts.map +1 -1
  200. package/dist/server/builder-browser.js +246 -1
  201. package/dist/server/builder-browser.js.map +1 -1
  202. package/dist/server/core-routes-plugin.d.ts +41 -0
  203. package/dist/server/core-routes-plugin.d.ts.map +1 -1
  204. package/dist/server/core-routes-plugin.js +260 -14
  205. package/dist/server/core-routes-plugin.js.map +1 -1
  206. package/dist/templates/workspace-core/.agents/skills/a2a-protocol/SKILL.md +25 -0
  207. package/docs/content/deployment.mdx +9 -7
  208. package/docs/content/locales/ar-SA/deployment.mdx +9 -7
  209. package/docs/content/locales/de-DE/deployment.mdx +9 -7
  210. package/docs/content/locales/es-ES/deployment.mdx +9 -7
  211. package/docs/content/locales/fr-FR/deployment.mdx +9 -7
  212. package/docs/content/locales/hi-IN/deployment.mdx +9 -7
  213. package/docs/content/locales/ja-JP/deployment.mdx +9 -7
  214. package/docs/content/locales/ko-KR/deployment.mdx +9 -7
  215. package/docs/content/locales/pt-BR/deployment.mdx +9 -7
  216. package/docs/content/locales/zh-CN/deployment.mdx +9 -7
  217. package/docs/content/locales/zh-TW/deployment.mdx +9 -7
  218. package/package.json +2 -2
  219. package/src/templates/workspace-core/.agents/skills/a2a-protocol/SKILL.md +25 -0
@@ -1,3 +1,4 @@
1
+ import { findTrailingPlainInlineMath } from "@shared/inline-math";
1
2
  import {
2
3
  escapeHtml,
3
4
  indentMarkdown,
@@ -10,6 +11,7 @@ import {
10
11
  IconExternalLink,
11
12
  IconFileText,
12
13
  } from "@tabler/icons-react";
14
+ import { InputRule } from "@tiptap/core";
13
15
  import type { Fragment, Node as ProseMirrorNode } from "@tiptap/pm/model";
14
16
  import {
15
17
  Mark,
@@ -21,6 +23,8 @@ import {
21
23
  type NodeViewProps,
22
24
  } from "@tiptap/react";
23
25
 
26
+ import { MathRenderer } from "../MathRenderer";
27
+
24
28
  const BLOCK_ATOM_TAGS = [
25
29
  "page",
26
30
  "database",
@@ -452,6 +456,17 @@ function BlockAtomView({ node, extension }: NodeViewProps) {
452
456
  const canOpenLocalPage = Boolean(pageLink && options.onOpenPageLink);
453
457
  const externalUrl = safeExternalPageUrl(attrs.url || attrs.href || null);
454
458
 
459
+ if (tagName === "equation") {
460
+ return (
461
+ <NodeViewWrapper
462
+ className="content-equation"
463
+ data-latex={label || attrs.latex || ""}
464
+ >
465
+ <MathRenderer latex={label || attrs.latex || ""} displayMode={true} />
466
+ </NodeViewWrapper>
467
+ );
468
+ }
469
+
455
470
  if (tagName === "page") {
456
471
  const openPage = () => {
457
472
  if (pageLink && options.onOpenPageLink) {
@@ -511,6 +526,36 @@ function BlockAtomView({ node, extension }: NodeViewProps) {
511
526
  );
512
527
  }
513
528
 
529
+ function InlineAtomView({ node }: NodeViewProps) {
530
+ const tagName = (node.attrs.tagName || "mention") as string;
531
+ const label = (node.attrs.label || "") as string;
532
+ const attrs = parseAttrsJson(node.attrs.attrsJson as string);
533
+
534
+ if (tagName === "math") {
535
+ const latex = label || attrs.latex || "";
536
+ return (
537
+ <NodeViewWrapper
538
+ as="span"
539
+ className="content-inline-equation"
540
+ contentEditable={false}
541
+ data-latex={latex}
542
+ >
543
+ <MathRenderer latex={latex} displayMode={false} />
544
+ </NodeViewWrapper>
545
+ );
546
+ }
547
+
548
+ return (
549
+ <NodeViewWrapper
550
+ as="span"
551
+ className="notion-inline-atom"
552
+ contentEditable={false}
553
+ >
554
+ {label || humanizeTag(tagName)}
555
+ </NodeViewWrapper>
556
+ );
557
+ }
558
+
514
559
  export const NotionSpanMark = Mark.create({
515
560
  name: "notionSpan",
516
561
 
@@ -975,6 +1020,35 @@ export const NotionInlineAtom = Node.create({
975
1020
  atom: true,
976
1021
  selectable: false,
977
1022
 
1023
+ addInputRules() {
1024
+ return [
1025
+ new InputRule({
1026
+ find: (text) => {
1027
+ const match = findTrailingPlainInlineMath(text);
1028
+ if (!match) return null;
1029
+ return {
1030
+ index: match.from,
1031
+ text: text.slice(match.from, match.to),
1032
+ data: { latex: match.latex },
1033
+ };
1034
+ },
1035
+ handler: ({ state, range, match }) => {
1036
+ const latex = match.data?.latex;
1037
+ if (typeof latex !== "string") return null;
1038
+
1039
+ const mathNode = state.schema.nodes.notionInlineAtom?.create({
1040
+ tagName: "math",
1041
+ attrsJson: "{}",
1042
+ label: latex,
1043
+ });
1044
+ if (!mathNode) return null;
1045
+
1046
+ state.tr.replaceWith(range.from, range.to, mathNode).scrollIntoView();
1047
+ },
1048
+ }),
1049
+ ];
1050
+ },
1051
+
978
1052
  addAttributes() {
979
1053
  return {
980
1054
  tagName: { default: "mention-user" },
@@ -1023,6 +1097,10 @@ export const NotionInlineAtom = Node.create({
1023
1097
  ];
1024
1098
  },
1025
1099
 
1100
+ addNodeView() {
1101
+ return ReactNodeViewRenderer(InlineAtomView);
1102
+ },
1103
+
1026
1104
  addStorage() {
1027
1105
  return {
1028
1106
  markdown: {
@@ -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));
@@ -1201,6 +1218,59 @@ div[data-notion-column] > :last-child {
1201
1218
  white-space: nowrap;
1202
1219
  }
1203
1220
 
1221
+ .content-inline-equation {
1222
+ display: inline-block;
1223
+ max-width: 100%;
1224
+ margin: 0 0.08em;
1225
+ vertical-align: -0.08em;
1226
+ }
1227
+
1228
+ .content-equation {
1229
+ display: block;
1230
+ max-width: 100%;
1231
+ margin: 1em 0;
1232
+ overflow-x: auto;
1233
+ padding: 0.25rem 0;
1234
+ }
1235
+
1236
+ .content-math {
1237
+ color: inherit;
1238
+ }
1239
+
1240
+ .content-math--inline {
1241
+ display: inline-block;
1242
+ }
1243
+
1244
+ .content-math--block {
1245
+ display: block;
1246
+ min-width: max-content;
1247
+ text-align: center;
1248
+ }
1249
+
1250
+ .content-math-error {
1251
+ border: 1px solid hsl(var(--destructive) / 0.3);
1252
+ border-radius: 0.375rem;
1253
+ background: hsl(var(--destructive) / 0.06);
1254
+ color: hsl(var(--foreground));
1255
+ font-family:
1256
+ "JetBrains Mono Variable", ui-monospace, SFMono-Regular, Menlo, Consolas,
1257
+ monospace;
1258
+ white-space: pre-wrap;
1259
+ }
1260
+
1261
+ .content-math-error--inline {
1262
+ padding: 0.05rem 0.25rem;
1263
+ font-size: 0.9em;
1264
+ }
1265
+
1266
+ .content-math-error--block {
1267
+ display: block;
1268
+ width: 100%;
1269
+ padding: 0.75rem 0.9rem;
1270
+ overflow-wrap: anywhere;
1271
+ text-align: left;
1272
+ }
1273
+
1204
1274
  .notion-editor span[data-notion-span="true"][underline="true"] {
1205
1275
  text-decoration: underline;
1206
1276
  text-underline-offset: 3px;
@@ -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
  }