@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,6 @@
1
+ import { matchInlineMathAt } from "./inline-math.js";
2
+ import { KATEX_STYLESHEET_URL, renderMathToHtml } from "./math-rendering.js";
3
+
1
4
  export type DocumentExportFormat = "pdf" | "markdown" | "html";
2
5
 
3
6
  export interface DocumentExportInput {
@@ -100,18 +103,37 @@ export function markdownWithTitle(
100
103
  return `${`# ${safeTitle}`}${body ? `\n\n${body}` : ""}\n`;
101
104
  }
102
105
 
103
- function inlineMarkdownToHtml(text: string): string {
106
+ interface InlineExportToken {
107
+ marker: string;
108
+ html: string;
109
+ source: string;
110
+ }
111
+
112
+ function restoreTokenSources(
113
+ text: string,
114
+ tokens: InlineExportToken[],
115
+ ): string {
116
+ return tokens.reduce(
117
+ (restored, token) =>
118
+ restored.split(token.marker).join(escapeHtml(token.source)),
119
+ text,
120
+ );
121
+ }
122
+
123
+ function inlineMarkdownSegmentToHtml(
124
+ text: string,
125
+ tokens: InlineExportToken[],
126
+ ): string {
104
127
  return escapeHtml(text)
105
128
  .replace(
106
129
  /!\[([^\]]*)\]\(([^)\s]+)(?:\s+"[^&]*")?\)/g,
107
130
  (_m, alt, src) => {
108
- return `<img src="${safeExportUrl(src, "image")}" alt="${alt}" />`;
131
+ return `<img src="${safeExportUrl(src, "image")}" alt="${restoreTokenSources(alt, tokens)}" />`;
109
132
  },
110
133
  )
111
134
  .replace(/\[([^\]]+)\]\(([^)\s]+)\)/g, (_m, label, href) => {
112
135
  return `<a href="${safeExportUrl(href, "link")}">${label}</a>`;
113
136
  })
114
- .replace(/`([^`]+)`/g, "<code>$1</code>")
115
137
  .replace(/\*\*([^*]+)\*\*/g, "<strong>$1</strong>")
116
138
  .replace(/__([^_]+)__/g, "<strong>$1</strong>")
117
139
  .replace(/~~([^~]+)~~/g, "<s>$1</s>")
@@ -119,24 +141,229 @@ function inlineMarkdownToHtml(text: string): string {
119
141
  .replace(/(^|[^_])_([^_]+)_/g, "$1<em>$2</em>");
120
142
  }
121
143
 
122
- function listItemsToHtml(lines: string[], ordered: boolean): string {
123
- const items = lines
124
- .map((line) => {
125
- const text = ordered
126
- ? line.replace(/^\s*\d+[.)]\s+/, "")
127
- : line.replace(/^\s*[-*+]\s+/, "");
128
- const task = text.match(/^\[( |x|X)\]\s+(.*)$/);
144
+ function isEscapedDelimiter(text: string, index: number): boolean {
145
+ let backslashes = 0;
146
+ for (let cursor = index - 1; cursor >= 0 && text[cursor] === "\\"; cursor--) {
147
+ backslashes++;
148
+ }
149
+ return backslashes % 2 === 1;
150
+ }
151
+
152
+ function mathErrorHtml(
153
+ source: string,
154
+ error: string,
155
+ displayMode: boolean,
156
+ ): string {
157
+ const className = displayMode
158
+ ? "math-error math-error-block"
159
+ : "math-error math-error-inline";
160
+ const tagName = displayMode ? "pre" : "code";
161
+ return `<${tagName} class="${className}" title="${escapeHtml(error)}">${escapeHtml(source)}</${tagName}>`;
162
+ }
163
+
164
+ function inlineMarkdownToHtml(text: string): string {
165
+ const tokens: InlineExportToken[] = [];
166
+ let markerStart = "\uE000agent-native-inline-";
167
+ while (text.includes(markerStart)) markerStart += "-";
168
+
169
+ const protectedText: string[] = [];
170
+ let cursor = 0;
171
+
172
+ while (cursor < text.length) {
173
+ if (text.startsWith("$`", cursor) && isEscapedDelimiter(text, cursor)) {
174
+ const close = text.indexOf("`$", cursor + 2);
175
+ if (close !== -1) {
176
+ protectedText.push(text.slice(cursor, close + 2));
177
+ cursor = close + 2;
178
+ continue;
179
+ }
180
+ }
181
+
182
+ if (text[cursor] === "$") {
183
+ const inlineMath = matchInlineMathAt(text, cursor);
184
+ if (inlineMath) {
185
+ const { latex } = inlineMath;
186
+ const source = text.slice(cursor, inlineMath.to);
187
+ const math = renderMathToHtml(latex, false);
188
+ const marker = `${markerStart}${tokens.length}\uE001`;
189
+ tokens.push({
190
+ marker,
191
+ source,
192
+ html: math.ok
193
+ ? `<span class="math-inline">${math.html}</span>`
194
+ : mathErrorHtml(source, math.error, false),
195
+ });
196
+ protectedText.push(marker);
197
+ cursor = inlineMath.to;
198
+ continue;
199
+ }
200
+ }
201
+
202
+ if (text[cursor] === "`" && !isEscapedDelimiter(text, cursor)) {
203
+ let delimiterLength = 1;
204
+ while (text[cursor + delimiterLength] === "`") delimiterLength++;
205
+ const delimiter = "`".repeat(delimiterLength);
206
+ let close = text.indexOf(delimiter, cursor + delimiterLength);
207
+ while (
208
+ close !== -1 &&
209
+ (text[close - 1] === "`" || text[close + delimiterLength] === "`")
210
+ ) {
211
+ close = text.indexOf(delimiter, close + delimiterLength);
212
+ }
213
+
214
+ if (close !== -1) {
215
+ const source = text.slice(cursor, close + delimiterLength);
216
+ let code = text
217
+ .slice(cursor + delimiterLength, close)
218
+ .replace(/\r?\n/g, " ");
219
+ if (
220
+ code.startsWith(" ") &&
221
+ code.endsWith(" ") &&
222
+ code.trim().length > 0
223
+ ) {
224
+ code = code.slice(1, -1);
225
+ }
226
+ const marker = `${markerStart}${tokens.length}\uE001`;
227
+ tokens.push({
228
+ marker,
229
+ source,
230
+ html: `<code>${escapeHtml(code)}</code>`,
231
+ });
232
+ protectedText.push(marker);
233
+ cursor = close + delimiterLength;
234
+ continue;
235
+ }
236
+ }
237
+
238
+ protectedText.push(text[cursor]);
239
+ cursor++;
240
+ }
241
+
242
+ return tokens.reduce(
243
+ (html, token) => html.split(token.marker).join(token.html),
244
+ inlineMarkdownSegmentToHtml(protectedText.join(""), tokens),
245
+ );
246
+ }
247
+
248
+ function listItemsToHtml(items: string[][], ordered: boolean): string {
249
+ const renderedItems = items
250
+ .map((lines) => {
251
+ const task = lines[0].match(/^\[( |x|X)\]\s+(.*)$/);
129
252
  if (task) {
130
253
  const checked = task[1].toLowerCase() === "x";
131
254
  return `<li class="task"><input type="checkbox" disabled${
132
255
  checked ? " checked" : ""
133
- } /> <span>${inlineMarkdownToHtml(task[2])}</span></li>`;
256
+ } /> <span>${inlineMarkdownToHtml(task[2])}</span>${
257
+ lines.length > 1 ? markdownToHtml(lines.slice(1).join("\n")) : ""
258
+ }</li>`;
134
259
  }
135
- return `<li>${inlineMarkdownToHtml(text)}</li>`;
260
+ return lines.length === 1
261
+ ? `<li>${inlineMarkdownToHtml(lines[0])}</li>`
262
+ : `<li>${markdownToHtml(lines.join("\n"))}</li>`;
136
263
  })
137
264
  .join("\n");
138
265
 
139
- return ordered ? `<ol>\n${items}\n</ol>` : `<ul>\n${items}\n</ul>`;
266
+ return ordered
267
+ ? `<ol>\n${renderedItems}\n</ol>`
268
+ : `<ul>\n${renderedItems}\n</ul>`;
269
+ }
270
+
271
+ interface ListMarkerMatch {
272
+ baseIndent: number;
273
+ contentIndent: number;
274
+ content: string;
275
+ }
276
+
277
+ function indentationWidth(value: string): number {
278
+ let width = 0;
279
+ for (const character of value) {
280
+ width += character === "\t" ? 4 - (width % 4) : 1;
281
+ }
282
+ return width;
283
+ }
284
+
285
+ function matchListMarker(
286
+ line: string,
287
+ ordered: boolean,
288
+ ): ListMarkerMatch | null {
289
+ const match = line.match(
290
+ ordered
291
+ ? /^([ \t]*)(?:\d+[.)])([ \t]+)(.*)$/
292
+ : /^([ \t]*)(?:[-*+])([ \t]+)(.*)$/,
293
+ );
294
+ if (!match) return null;
295
+ const contentStart = match[0].length - match[3].length;
296
+ return {
297
+ baseIndent: indentationWidth(match[1]),
298
+ contentIndent: indentationWidth(line.slice(0, contentStart)),
299
+ content: match[3],
300
+ };
301
+ }
302
+
303
+ function leadingIndentWidth(line: string): number {
304
+ return indentationWidth(line.match(/^[ \t]*/)?.[0] ?? "");
305
+ }
306
+
307
+ function stripLeadingIndent(line: string, targetWidth: number): string {
308
+ let width = 0;
309
+ let index = 0;
310
+ while (index < line.length && width < targetWidth) {
311
+ const character = line[index];
312
+ if (character !== " " && character !== "\t") break;
313
+ width += character === "\t" ? 4 - (width % 4) : 1;
314
+ index++;
315
+ }
316
+ return line.slice(index);
317
+ }
318
+
319
+ function collectListItems(
320
+ lines: string[],
321
+ start: number,
322
+ ordered: boolean,
323
+ ): { items: string[][]; nextIndex: number } {
324
+ const firstMarker = matchListMarker(lines[start], ordered);
325
+ if (!firstMarker) return { items: [], nextIndex: start };
326
+
327
+ const baseIndent = firstMarker.baseIndent;
328
+ const items: string[][] = [];
329
+ let index = start;
330
+
331
+ while (index < lines.length) {
332
+ const itemMarker = matchListMarker(lines[index], ordered);
333
+ if (!itemMarker || itemMarker.baseIndent !== baseIndent) break;
334
+
335
+ const item = [itemMarker.content];
336
+ index++;
337
+
338
+ while (index < lines.length) {
339
+ const nextMarker = matchListMarker(lines[index], ordered);
340
+ if (nextMarker?.baseIndent === baseIndent) break;
341
+ if (leadingIndentWidth(lines[index]) >= itemMarker.contentIndent) {
342
+ item.push(stripLeadingIndent(lines[index], itemMarker.contentIndent));
343
+ index++;
344
+ continue;
345
+ }
346
+ if (!lines[index].trim()) {
347
+ let nextContent = index + 1;
348
+ while (nextContent < lines.length && !lines[nextContent].trim()) {
349
+ nextContent++;
350
+ }
351
+ if (
352
+ nextContent < lines.length &&
353
+ leadingIndentWidth(lines[nextContent]) >= itemMarker.contentIndent
354
+ ) {
355
+ item.push("");
356
+ index++;
357
+ continue;
358
+ }
359
+ }
360
+ break;
361
+ }
362
+
363
+ items.push(item);
364
+ }
365
+
366
+ return { items, nextIndex: index };
140
367
  }
141
368
 
142
369
  function isEmptyBlockLine(trimmed: string): boolean {
@@ -163,6 +390,42 @@ function markdownToHtml(markdown: string): string {
163
390
  continue;
164
391
  }
165
392
 
393
+ if (trimmed === "$$") {
394
+ const source = [line];
395
+ const latex: string[] = [];
396
+ index++;
397
+ while (index < lines.length && lines[index].trim() !== "$$") {
398
+ source.push(lines[index]);
399
+ latex.push(lines[index]);
400
+ index++;
401
+ }
402
+
403
+ const closed = index < lines.length;
404
+ if (closed) {
405
+ source.push(lines[index]);
406
+ index++;
407
+ }
408
+
409
+ if (!closed) {
410
+ blocks.push(
411
+ mathErrorHtml(
412
+ source.join("\n"),
413
+ "This block equation is missing its closing delimiter.",
414
+ true,
415
+ ),
416
+ );
417
+ continue;
418
+ }
419
+
420
+ const math = renderMathToHtml(latex.join("\n"), true);
421
+ blocks.push(
422
+ math.ok
423
+ ? `<div class="math-block">${math.html}</div>`
424
+ : mathErrorHtml(source.join("\n"), math.error, true),
425
+ );
426
+ continue;
427
+ }
428
+
166
429
  const codeFence = trimmed.match(/^```(\w+)?/);
167
430
  if (codeFence) {
168
431
  const code: string[] = [];
@@ -208,22 +471,16 @@ function markdownToHtml(markdown: string): string {
208
471
  }
209
472
 
210
473
  if (/^\s*[-*+]\s+/.test(line)) {
211
- const items: string[] = [];
212
- while (index < lines.length && /^\s*[-*+]\s+/.test(lines[index])) {
213
- items.push(lines[index]);
214
- index++;
215
- }
216
- blocks.push(listItemsToHtml(items, false));
474
+ const list = collectListItems(lines, index, false);
475
+ index = list.nextIndex;
476
+ blocks.push(listItemsToHtml(list.items, false));
217
477
  continue;
218
478
  }
219
479
 
220
480
  if (/^\s*\d+[.)]\s+/.test(line)) {
221
- const items: string[] = [];
222
- while (index < lines.length && /^\s*\d+[.)]\s+/.test(lines[index])) {
223
- items.push(lines[index]);
224
- index++;
225
- }
226
- blocks.push(listItemsToHtml(items, true));
481
+ const list = collectListItems(lines, index, true);
482
+ index = list.nextIndex;
483
+ blocks.push(listItemsToHtml(list.items, true));
227
484
  continue;
228
485
  }
229
486
 
@@ -233,6 +490,7 @@ function markdownToHtml(markdown: string): string {
233
490
  index < lines.length &&
234
491
  lines[index].trim() &&
235
492
  !isEmptyBlockLine(lines[index].trim()) &&
493
+ lines[index].trim() !== "$$" &&
236
494
  !/^(#{1,6})\s+/.test(lines[index].trim()) &&
237
495
  !/^```/.test(lines[index].trim()) &&
238
496
  !/^>\s?/.test(lines[index].trim()) &&
@@ -273,6 +531,7 @@ function buildHtmlDocument(input: {
273
531
  <meta charset="utf-8" />
274
532
  <meta name="viewport" content="width=device-width, initial-scale=1" />
275
533
  <title>${escapeHtml(input.title)}</title>
534
+ <link rel="stylesheet" href="${KATEX_STYLESHEET_URL}" crossorigin="anonymous" />
276
535
  <style>
277
536
  :root { color-scheme: light; }
278
537
  body {
@@ -329,6 +588,18 @@ function buildHtmlDocument(input: {
329
588
  border-radius: 4px;
330
589
  padding: 0.12rem 0.3rem;
331
590
  }
591
+ .math-inline { display: inline-block; max-width: 100%; vertical-align: -0.08em; }
592
+ .math-block { max-width: 100%; margin: 18px 0; overflow-x: auto; padding: 4px 0; }
593
+ .math-error {
594
+ border: 1px solid #fecaca;
595
+ border-radius: 6px;
596
+ background: #fef2f2;
597
+ color: #262626;
598
+ font-family: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
599
+ white-space: pre-wrap;
600
+ }
601
+ .math-error-inline { padding: 0.05rem 0.25rem; font-size: 0.9em; }
602
+ .math-error-block { overflow-wrap: anywhere; padding: 12px 14px; }
332
603
  a { color: #2563eb; }
333
604
  img {
334
605
  display: block;
@@ -0,0 +1,128 @@
1
+ export const MAX_INLINE_MATH_LENGTH = 1_000;
2
+
3
+ export type InlineMathSyntax = "plain" | "github";
4
+
5
+ export interface InlineMathMatch {
6
+ from: number;
7
+ to: number;
8
+ latex: string;
9
+ syntax: InlineMathSyntax;
10
+ }
11
+
12
+ export function isEscapedInlineDelimiter(text: string, index: number): boolean {
13
+ let backslashes = 0;
14
+ for (let cursor = index - 1; cursor >= 0 && text[cursor] === "\\"; cursor--) {
15
+ backslashes += 1;
16
+ }
17
+ return backslashes % 2 === 1;
18
+ }
19
+
20
+ function matchGithubInlineMath(
21
+ text: string,
22
+ start: number,
23
+ ): InlineMathMatch | null {
24
+ if (!text.startsWith("$`", start)) return null;
25
+
26
+ const close = text.indexOf("`$", start + 2);
27
+ if (close === -1 || close - (start + 2) > MAX_INLINE_MATH_LENGTH) {
28
+ return null;
29
+ }
30
+
31
+ return {
32
+ from: start,
33
+ to: close + 2,
34
+ latex: text.slice(start + 2, close),
35
+ syntax: "github",
36
+ };
37
+ }
38
+
39
+ function matchPlainInlineMath(
40
+ text: string,
41
+ start: number,
42
+ ): InlineMathMatch | null {
43
+ if (
44
+ text[start] !== "$" ||
45
+ text[start + 1] === "$" ||
46
+ text[start + 1] === "`" ||
47
+ !text[start + 1] ||
48
+ /\s/.test(text[start + 1])
49
+ ) {
50
+ return null;
51
+ }
52
+
53
+ const searchLimit = Math.min(text.length, start + MAX_INLINE_MATH_LENGTH + 2);
54
+ for (let close = start + 1; close < searchLimit; close++) {
55
+ const char = text[close];
56
+ if (char === "\n" || char === "\r") return null;
57
+ if (
58
+ char !== "$" ||
59
+ isEscapedInlineDelimiter(text, close) ||
60
+ text[close - 1] === "$" ||
61
+ text[close + 1] === "$" ||
62
+ /\s/.test(text[close - 1]) ||
63
+ /\d/.test(text[close + 1] || "")
64
+ ) {
65
+ continue;
66
+ }
67
+
68
+ return {
69
+ from: start,
70
+ to: close + 1,
71
+ latex: text.slice(start + 1, close),
72
+ syntax: "plain",
73
+ };
74
+ }
75
+
76
+ return null;
77
+ }
78
+
79
+ export function matchInlineMathAt(
80
+ text: string,
81
+ start: number,
82
+ ): InlineMathMatch | null {
83
+ if (text[start] !== "$" || isEscapedInlineDelimiter(text, start)) {
84
+ return null;
85
+ }
86
+
87
+ return (
88
+ matchGithubInlineMath(text, start) ?? matchPlainInlineMath(text, start)
89
+ );
90
+ }
91
+
92
+ export function findTrailingPlainInlineMath(
93
+ text: string,
94
+ ): InlineMathMatch | null {
95
+ if (!text.endsWith("$") || text.endsWith("$$")) return null;
96
+
97
+ for (let start = text.length - 2; start >= 0; start--) {
98
+ if (text[start] !== "$") continue;
99
+ const match = matchInlineMathAt(text, start);
100
+ if (
101
+ match?.syntax === "plain" &&
102
+ match.to === text.length &&
103
+ !hasUnclosedCodeSpan(text.slice(0, start))
104
+ ) {
105
+ return match;
106
+ }
107
+ }
108
+
109
+ return null;
110
+ }
111
+
112
+ function hasUnclosedCodeSpan(text: string): boolean {
113
+ let openRun = 0;
114
+
115
+ for (let cursor = 0; cursor < text.length; cursor++) {
116
+ if (text[cursor] !== "`" || isEscapedInlineDelimiter(text, cursor)) {
117
+ continue;
118
+ }
119
+
120
+ let run = 1;
121
+ while (text[cursor + run] === "`") run += 1;
122
+ if (openRun === 0) openRun = run;
123
+ else if (openRun === run) openRun = 0;
124
+ cursor += run - 1;
125
+ }
126
+
127
+ return openRun !== 0;
128
+ }
@@ -0,0 +1,50 @@
1
+ import katex, { type KatexOptions } from "katex";
2
+
3
+ export const KATEX_VERSION = "0.17.0";
4
+ export const KATEX_STYLESHEET_URL = `https://cdn.jsdelivr.net/npm/katex@${KATEX_VERSION}/dist/katex.min.css`;
5
+
6
+ export interface MathRenderSuccess {
7
+ ok: true;
8
+ html: string;
9
+ }
10
+
11
+ export interface MathRenderFailure {
12
+ ok: false;
13
+ error: string;
14
+ }
15
+
16
+ export type MathRenderResult = MathRenderSuccess | MathRenderFailure;
17
+
18
+ export function mathRenderOptions(displayMode: boolean): KatexOptions {
19
+ return {
20
+ displayMode,
21
+ maxExpand: 1_000,
22
+ output: "htmlAndMathml",
23
+ strict: "warn",
24
+ throwOnError: true,
25
+ trust: false,
26
+ };
27
+ }
28
+
29
+ export function mathRenderErrorMessage(error: unknown): string {
30
+ if (error instanceof Error && error.message.trim()) return error.message;
31
+ return "This equation could not be rendered.";
32
+ }
33
+
34
+ export function renderMathToHtml(
35
+ latex: string,
36
+ displayMode: boolean,
37
+ ): MathRenderResult {
38
+ if (!latex.trim()) {
39
+ return { ok: false, error: "This equation is empty." };
40
+ }
41
+
42
+ try {
43
+ return {
44
+ ok: true,
45
+ html: katex.renderToString(latex, mathRenderOptions(displayMode)),
46
+ };
47
+ } catch (error) {
48
+ return { ok: false, error: mathRenderErrorMessage(error) };
49
+ }
50
+ }
@@ -41,6 +41,7 @@
41
41
  * exposing simple string props for the editor preview layer.
42
42
  */
43
43
 
44
+ import { matchInlineMathAt } from "./inline-math.js";
44
45
  import { isRegistryBlockTag, registryBlockSpecByTag } from "./nfm-registry.js";
45
46
 
46
47
  // ── Shared PM JSON types ────────────────────────────────────────────
@@ -115,7 +116,7 @@ function unescapeInlineText(text: string): string {
115
116
  // paragraph instead of being reparsed as that block type. These are matched
116
117
  // against the raw (untrimmed) text because the corresponding block parsers
117
118
  // (heading/list/task) also match against the untrimmed dedented line.
118
- const LEADING_BLOCK_MARKER = /^(#{1,4} |[-*+] |\d+[.)] |\[[ xX]\] )/;
119
+ const LEADING_BLOCK_MARKER = /^(#{1,6} |[-*+] |\d+[.)] |\[[ xX]\] )/;
119
120
 
120
121
  // Divider lookalikes ("---", "***", "___", 3+ repeats) — matched separately
121
122
  // because the divider parser trims the line before testing
@@ -279,7 +280,7 @@ function serializeInlineAtom(node: PMNode): string {
279
280
  attrs = {};
280
281
  }
281
282
  if (tagName === "math") {
282
- return "$`" + (label || attrs.latex || "") + "`$";
283
+ return "$" + (label || attrs.latex || "") + "$";
283
284
  }
284
285
  const attrEntries = Object.entries(attrs).filter(([k]) => k !== "latex");
285
286
  const attrStr = serializeAttrs(attrEntries);
@@ -446,19 +447,21 @@ function parseInline(input: string): PMNode[] {
446
447
  continue;
447
448
  }
448
449
 
449
- // Inline math $`...`$
450
- if (ch === "$" && input[i + 1] === "`") {
451
- const close = input.indexOf("`$", i + 2);
452
- if (close !== -1) {
453
- flush();
454
- const latex = input.slice(i + 2, close);
455
- out.push({
456
- type: "notionInlineAtom",
457
- attrs: { tagName: "math", attrsJson: "{}", label: latex },
458
- });
459
- i = close + 2;
460
- continue;
461
- }
450
+ // Canonical inline math is $...$; GitHub's $`...`$ form remains a
451
+ // backwards-compatible input alias and canonicalizes on the next write.
452
+ const inlineMath = ch === "$" ? matchInlineMathAt(input, i) : null;
453
+ if (inlineMath) {
454
+ flush();
455
+ out.push({
456
+ type: "notionInlineAtom",
457
+ attrs: {
458
+ tagName: "math",
459
+ attrsJson: "{}",
460
+ label: inlineMath.latex,
461
+ },
462
+ });
463
+ i = inlineMath.to;
464
+ continue;
462
465
  }
463
466
 
464
467
  // Inline code `...` — CommonMark-style variable-length delimiter: the
@@ -865,7 +868,7 @@ function serializeBlock(node: PMNode, indent: number): string[] {
865
868
  ];
866
869
  }
867
870
  case "heading": {
868
- const level = Math.min(4, Math.max(1, Number(node.attrs?.level) || 1));
871
+ const level = Math.min(6, Math.max(1, Number(node.attrs?.level) || 1));
869
872
  const inline = serializeInline(node.content);
870
873
  return [
871
874
  indentStr(ind) +
@@ -1480,7 +1483,7 @@ function parseSingleBlock(
1480
1483
  // Heading (possibly a toggle heading)
1481
1484
  const headingMatch = dedent.match(/^(#{1,6})\s+(.*)$/);
1482
1485
  if (headingMatch) {
1483
- const level = Math.min(4, headingMatch[1].length);
1486
+ const level = headingMatch[1].length;
1484
1487
  const { text, toggle, color } = splitBlockAttrs(headingMatch[2]);
1485
1488
  if (toggle) {
1486
1489
  const childRes = parseBlockSequence(lines, start + 1, indent + 1);
@@ -413,7 +413,6 @@ const cloudflareSsrStubs =
413
413
  "@xterm/addon-fit",
414
414
  "@xterm/addon-web-links",
415
415
  "@xterm/xterm",
416
- "katex",
417
416
  "lowlight",
418
417
  "prettier",
419
418
  "react-markdown",