@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
@@ -0,0 +1,99 @@
1
+ import { defineAction } from "@agent-native/core";
2
+ import { assertAccess } from "@agent-native/core/sharing";
3
+ import { z } from "zod";
4
+
5
+ import type {
6
+ PreviewBuilderSourceReviewRequest,
7
+ PreviewBuilderSourceReviewResponse,
8
+ } from "../shared/api.js";
9
+ import {
10
+ getContentDatabaseSourceSnapshotForWrite,
11
+ resolveDatabaseForSourceMutation,
12
+ } from "./_database-source-utils.js";
13
+ import {
14
+ BUILDER_SOURCE_REVIEW_PREPARE_LIMIT,
15
+ buildBuilderSourceReviewPayload,
16
+ reviewPreparePriority,
17
+ withAuthoritativeBuilderTargetRows,
18
+ } from "./prepare-builder-source-review.js";
19
+
20
+ export default defineAction({
21
+ description:
22
+ "Preview complete Builder CMS review diffs, including document body changes, without approving or writing anything.",
23
+ schema: z.object({
24
+ databaseId: z.string().optional().describe("Database ID"),
25
+ documentId: z.string().optional().describe("Database document/page ID"),
26
+ sourceId: z.string().optional().describe("Specific Builder source ID"),
27
+ scope: z.enum(["selected", "all"]).optional().default("all"),
28
+ documentIds: z
29
+ .array(z.string())
30
+ .max(BUILDER_SOURCE_REVIEW_PREPARE_LIMIT)
31
+ .optional()
32
+ .describe("Optional selected document IDs that bound the preview"),
33
+ }),
34
+ http: { method: "GET" },
35
+ readOnly: true,
36
+ run: async (
37
+ args: PreviewBuilderSourceReviewRequest,
38
+ ): Promise<PreviewBuilderSourceReviewResponse> => {
39
+ const database = await resolveDatabaseForSourceMutation(args);
40
+ if (!database) throw new Error("Database not found.");
41
+ await assertAccess("document", database.documentId, "editor");
42
+
43
+ const source = await getContentDatabaseSourceSnapshotForWrite(
44
+ database,
45
+ args.sourceId,
46
+ );
47
+ if (!source || source.sourceType !== "builder-cms") {
48
+ throw new Error("Attach a Builder CMS source before reviewing updates.");
49
+ }
50
+
51
+ const selectedDocumentIds = new Set(args.documentIds ?? []);
52
+ if (args.scope === "selected" && selectedDocumentIds.size === 0) {
53
+ throw new Error("Select at least one Builder row before reviewing it.");
54
+ }
55
+ const allReviewableChanges = source.changeSets.filter((changeSet) => {
56
+ if (
57
+ changeSet.direction !== "outbound" ||
58
+ (changeSet.state !== "pending_push" &&
59
+ changeSet.state !== "staged_revision" &&
60
+ changeSet.state !== "approved")
61
+ ) {
62
+ return false;
63
+ }
64
+ return (
65
+ args.scope === "all" ||
66
+ (!!changeSet.documentId &&
67
+ selectedDocumentIds.has(changeSet.documentId))
68
+ );
69
+ });
70
+ const changeSets = [...allReviewableChanges]
71
+ .sort(
72
+ (left, right) =>
73
+ reviewPreparePriority(left) - reviewPreparePriority(right),
74
+ )
75
+ .slice(0, BUILDER_SOURCE_REVIEW_PREPARE_LIMIT);
76
+ const authoritativeSource = await withAuthoritativeBuilderTargetRows({
77
+ source,
78
+ changeSets,
79
+ });
80
+ const review =
81
+ changeSets.length > 0
82
+ ? buildBuilderSourceReviewPayload({
83
+ source: authoritativeSource,
84
+ changeSets,
85
+ })
86
+ : null;
87
+ if (review) {
88
+ review.totalRowCount = allReviewableChanges.length;
89
+ review.preparedRowLimit = changeSets.length;
90
+ }
91
+
92
+ return {
93
+ sourceId: source.id,
94
+ sourceTable: source.sourceTable,
95
+ changeSetIds: changeSets.map((changeSet) => changeSet.id),
96
+ review,
97
+ };
98
+ },
99
+ });
@@ -38,7 +38,7 @@ function executableWriteModes(
38
38
 
39
39
  export default defineAction({
40
40
  description:
41
- "Set the tiered Builder CMS write mode for one source. Writes stay off by default and can only be enabled for the safe Builder test collection.",
41
+ "Set the tiered Builder CMS write mode for one source. Writes stay off by default and require document administrator access to enable or change.",
42
42
  schema: z.object({
43
43
  databaseId: z.string().optional().describe("Database ID"),
44
44
  documentId: z.string().optional().describe("Database document/page ID"),
@@ -79,7 +79,7 @@ export default defineAction({
79
79
  ): Promise<ContentDatabaseResponse> => {
80
80
  const database = await resolveDatabaseForSourceMutation(args);
81
81
  if (!database) throw new Error("Database not found.");
82
- await assertAccess("document", database.documentId, "editor");
82
+ await assertAccess("document", database.documentId, "admin");
83
83
 
84
84
  const db = getDb();
85
85
  const source = await getExistingSourceForWrite(database.id, args.sourceId);
@@ -0,0 +1,153 @@
1
+ import { defineAction } from "@agent-native/core";
2
+ import {
3
+ getRequestOrgId,
4
+ getRequestUserEmail,
5
+ } from "@agent-native/core/server";
6
+ import { assertAccess } from "@agent-native/core/sharing";
7
+ import { and, eq, sql } from "drizzle-orm";
8
+ import { z } from "zod";
9
+
10
+ import { getDb, schema } from "../server/db/index.js";
11
+ import { readPreviewDocumentDraft } from "./_preview-document-draft.js";
12
+
13
+ const draftPayload = z.object({
14
+ title: z.string().max(10_000),
15
+ content: z.string().max(500_000),
16
+ baseDocumentUpdatedAt: z.string().max(100).nullable(),
17
+ loadedContentWasEmpty: z.boolean(),
18
+ deferredReason: z.enum(["hydration", "conflict"]).nullable(),
19
+ });
20
+
21
+ function draftId() {
22
+ return crypto.randomUUID();
23
+ }
24
+
25
+ export default defineAction({
26
+ description:
27
+ "Atomically save or delete the current user's private preview draft.",
28
+ schema: z.discriminatedUnion("operation", [
29
+ z.object({
30
+ operation: z.literal("upsert"),
31
+ documentId: z.string().min(1),
32
+ expectedVersion: z.number().int().positive().nullable(),
33
+ draft: draftPayload,
34
+ }),
35
+ z.object({
36
+ operation: z.literal("delete"),
37
+ documentId: z.string().min(1),
38
+ expectedVersion: z.number().int().positive(),
39
+ expectedTitle: z.string().max(10_000),
40
+ expectedContent: z.string().max(500_000),
41
+ }),
42
+ ]),
43
+ agentTool: false,
44
+ toolCallable: false,
45
+ run: async (args, ctx) => {
46
+ const userEmail = getRequestUserEmail();
47
+ const orgId = getRequestOrgId() ?? "";
48
+ if (!userEmail) throw new Error("Not authenticated.");
49
+ await assertAccess("document", args.documentId, "editor");
50
+ const db = getDb();
51
+ const ownerFilter = and(
52
+ eq(schema.documentPreviewDrafts.ownerEmail, userEmail),
53
+ eq(schema.documentPreviewDrafts.orgId, orgId),
54
+ eq(schema.documentPreviewDrafts.documentId, args.documentId),
55
+ );
56
+
57
+ if (args.operation === "delete") {
58
+ const deleted = await db
59
+ .delete(schema.documentPreviewDrafts)
60
+ .where(
61
+ and(
62
+ ownerFilter,
63
+ eq(schema.documentPreviewDrafts.version, args.expectedVersion),
64
+ eq(schema.documentPreviewDrafts.title, args.expectedTitle),
65
+ eq(schema.documentPreviewDrafts.content, args.expectedContent),
66
+ ),
67
+ )
68
+ .returning({ id: schema.documentPreviewDrafts.id });
69
+ if (deleted.length > 0)
70
+ return { status: "deleted" as const, draft: null };
71
+ return {
72
+ status: "conflict" as const,
73
+ draft: await readPreviewDocumentDraft(
74
+ userEmail,
75
+ orgId,
76
+ args.documentId,
77
+ ),
78
+ };
79
+ }
80
+
81
+ const now = new Date().toISOString();
82
+ if (args.expectedVersion === null) {
83
+ const inserted = await db
84
+ .insert(schema.documentPreviewDrafts)
85
+ .values({
86
+ id: draftId(),
87
+ ownerEmail: userEmail,
88
+ orgId,
89
+ documentId: args.documentId,
90
+ title: args.draft.title,
91
+ content: args.draft.content,
92
+ baseDocumentUpdatedAt: args.draft.baseDocumentUpdatedAt,
93
+ loadedContentWasEmpty: args.draft.loadedContentWasEmpty ? 1 : 0,
94
+ deferredReason: args.draft.deferredReason,
95
+ version: 1,
96
+ createdAt: now,
97
+ updatedAt: now,
98
+ })
99
+ .onConflictDoNothing({
100
+ target: [
101
+ schema.documentPreviewDrafts.ownerEmail,
102
+ schema.documentPreviewDrafts.orgId,
103
+ schema.documentPreviewDrafts.documentId,
104
+ ],
105
+ })
106
+ .returning({ version: schema.documentPreviewDrafts.version });
107
+ if (inserted.length > 0) {
108
+ return {
109
+ status: "saved" as const,
110
+ draft: await readPreviewDocumentDraft(
111
+ userEmail,
112
+ orgId,
113
+ args.documentId,
114
+ ),
115
+ };
116
+ }
117
+ } else {
118
+ const updated = await db
119
+ .update(schema.documentPreviewDrafts)
120
+ .set({
121
+ title: args.draft.title,
122
+ content: args.draft.content,
123
+ baseDocumentUpdatedAt: args.draft.baseDocumentUpdatedAt,
124
+ loadedContentWasEmpty: args.draft.loadedContentWasEmpty ? 1 : 0,
125
+ deferredReason: args.draft.deferredReason,
126
+ version: sql`${schema.documentPreviewDrafts.version} + 1`,
127
+ updatedAt: now,
128
+ })
129
+ .where(
130
+ and(
131
+ ownerFilter,
132
+ eq(schema.documentPreviewDrafts.version, args.expectedVersion),
133
+ ),
134
+ )
135
+ .returning({ version: schema.documentPreviewDrafts.version });
136
+ if (updated.length > 0) {
137
+ return {
138
+ status: "saved" as const,
139
+ draft: await readPreviewDocumentDraft(
140
+ userEmail,
141
+ orgId,
142
+ args.documentId,
143
+ ),
144
+ };
145
+ }
146
+ }
147
+
148
+ return {
149
+ status: "conflict" as const,
150
+ draft: await readPreviewDocumentDraft(userEmail, orgId, args.documentId),
151
+ };
152
+ },
153
+ });
@@ -489,6 +489,24 @@ export function filesMediaEditorValue(value: DocumentProperty["value"]) {
489
489
  return filesMediaItems(value).join("\n");
490
490
  }
491
491
 
492
+ export function isValidFilesMediaLink(value: string) {
493
+ try {
494
+ const url = new URL(value.trim());
495
+ return url.protocol === "http:" || url.protocol === "https:";
496
+ } catch {
497
+ return false;
498
+ }
499
+ }
500
+
501
+ export function mergeFilesMediaItems(items: string[], pendingLink: string) {
502
+ const trimmed = pendingLink.trim();
503
+ if (!trimmed || !isValidFilesMediaLink(trimmed)) return items;
504
+ if (items.some((item) => item.toLowerCase() === trimmed.toLowerCase())) {
505
+ return items;
506
+ }
507
+ return [...items, trimmed];
508
+ }
509
+
492
510
  export function filesMediaLabel(value: string) {
493
511
  const trimmed = value.trim();
494
512
  if (!trimmed) return "File";
@@ -2060,16 +2078,8 @@ function FilesMediaValueEditor({
2060
2078
  }, []);
2061
2079
 
2062
2080
  function addItem(value: string) {
2063
- const trimmed = value.trim();
2064
- if (!trimmed) return;
2065
- setItems((current) => {
2066
- if (
2067
- current.some((item) => item.toLowerCase() === trimmed.toLowerCase())
2068
- ) {
2069
- return current;
2070
- }
2071
- return [...current, trimmed];
2072
- });
2081
+ if (!isValidFilesMediaLink(value)) return;
2082
+ setItems((current) => mergeFilesMediaItems(current, value));
2073
2083
  setLinkValue("");
2074
2084
  }
2075
2085
 
@@ -2126,11 +2136,11 @@ function FilesMediaValueEditor({
2126
2136
  className="grid gap-3"
2127
2137
  onSubmit={(event) => {
2128
2138
  event.preventDefault();
2129
- if (linkValue.trim()) {
2130
- addItem(linkValue);
2139
+ if (linkValue.trim() && !isValidFilesMediaLink(linkValue)) {
2140
+ linkInputRef.current?.reportValidity();
2131
2141
  return;
2132
2142
  }
2133
- void save();
2143
+ void save(mergeFilesMediaItems(items, linkValue));
2134
2144
  }}
2135
2145
  >
2136
2146
  <div className="grid max-h-48 gap-1 overflow-auto">
@@ -2174,9 +2184,11 @@ function FilesMediaValueEditor({
2174
2184
  <div className="flex gap-1">
2175
2185
  <Input
2176
2186
  ref={linkInputRef}
2177
- aria-label={t("editor.properties.addPropertyLink", {
2187
+ aria-label={t("editor.properties.editValue", {
2178
2188
  name: property.definition.name,
2179
2189
  })}
2190
+ type="url"
2191
+ pattern="[hH][tT][tT][pP][sS]?://.*"
2180
2192
  value={linkValue}
2181
2193
  placeholder={t("editor.properties.pasteFileOrMediaLink")}
2182
2194
  onChange={(event) => setLinkValue(event.target.value)}
@@ -2193,7 +2205,7 @@ function FilesMediaValueEditor({
2193
2205
  size="sm"
2194
2206
  className="shrink-0"
2195
2207
  onClick={() => addItem(linkValue)}
2196
- disabled={!linkValue.trim() || mutation.isPending}
2208
+ disabled={!isValidFilesMediaLink(linkValue) || mutation.isPending}
2197
2209
  >
2198
2210
  <IconPlus className="size-3.5" />
2199
2211
  {t("editor.properties.add")}
@@ -0,0 +1,43 @@
1
+ import { renderMathToHtml } from "@shared/math-rendering";
2
+ import { useMemo } from "react";
3
+
4
+ interface MathRendererProps {
5
+ latex: string;
6
+ displayMode: boolean;
7
+ }
8
+
9
+ export function MathRenderer({ latex, displayMode }: MathRendererProps) {
10
+ const rendered = useMemo(
11
+ () => renderMathToHtml(latex, displayMode),
12
+ [displayMode, latex],
13
+ );
14
+
15
+ if (!rendered.ok) {
16
+ return (
17
+ <code
18
+ className={
19
+ displayMode
20
+ ? "content-math-error content-math-error--block"
21
+ : "content-math-error content-math-error--inline"
22
+ }
23
+ title={rendered.error}
24
+ >
25
+ {latex || "Empty equation"}
26
+ </code>
27
+ );
28
+ }
29
+
30
+ return displayMode ? (
31
+ <span
32
+ className="content-math content-math--block"
33
+ contentEditable={false}
34
+ dangerouslySetInnerHTML={{ __html: rendered.html }}
35
+ />
36
+ ) : (
37
+ <span
38
+ className="content-math content-math--inline"
39
+ contentEditable={false}
40
+ dangerouslySetInnerHTML={{ __html: rendered.html }}
41
+ />
42
+ );
43
+ }