@agent-native/core 0.77.8 → 0.77.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 (216) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +35 -0
  3. package/corpus/core/package.json +3 -3
  4. package/corpus/core/src/a2a/task-store.ts +25 -3
  5. package/corpus/core/src/agent/harness/store.ts +89 -55
  6. package/corpus/core/src/agent/observational-memory/store.ts +40 -14
  7. package/corpus/core/src/agent/run-store.ts +116 -59
  8. package/corpus/core/src/application-state/store.ts +36 -2
  9. package/corpus/core/src/audit/store.ts +37 -6
  10. package/corpus/core/src/browser-sessions/store.ts +31 -12
  11. package/corpus/core/src/chat-threads/store.ts +67 -3
  12. package/corpus/core/src/checkpoints/store.ts +14 -3
  13. package/corpus/core/src/cli/create.ts +9 -0
  14. package/corpus/core/src/collab/awareness.ts +12 -9
  15. package/corpus/core/src/collab/client.ts +4 -2
  16. package/corpus/core/src/collab/storage.ts +20 -2
  17. package/corpus/core/src/db/ddl-guard.ts +112 -0
  18. package/corpus/core/src/extensions/store.ts +76 -3
  19. package/corpus/core/src/integrations/a2a-continuations-store.ts +69 -26
  20. package/corpus/core/src/integrations/config-store.ts +19 -18
  21. package/corpus/core/src/integrations/pending-tasks-store.ts +40 -16
  22. package/corpus/core/src/integrations/remote-commands-store.ts +37 -23
  23. package/corpus/core/src/integrations/remote-devices-store.ts +64 -20
  24. package/corpus/core/src/integrations/remote-push-store.ts +75 -35
  25. package/corpus/core/src/integrations/remote-run-events-store.ts +38 -12
  26. package/corpus/core/src/integrations/thread-mapping-store.ts +11 -2
  27. package/corpus/core/src/mcp/connect-store.ts +53 -16
  28. package/corpus/core/src/mcp/oauth-store.ts +32 -7
  29. package/corpus/core/src/notifications/store.ts +20 -4
  30. package/corpus/core/src/oauth-tokens/store.ts +45 -2
  31. package/corpus/core/src/observability/store.ts +154 -47
  32. package/corpus/core/src/progress/store.ts +36 -21
  33. package/corpus/core/src/provider-api/corpus-jobs-store.ts +25 -5
  34. package/corpus/core/src/provider-api/custom-registry.ts +15 -4
  35. package/corpus/core/src/provider-api/quota-governor.ts +15 -3
  36. package/corpus/core/src/provider-api/staged-datasets-store.ts +22 -5
  37. package/corpus/core/src/resources/store.ts +66 -31
  38. package/corpus/core/src/secrets/storage.ts +18 -22
  39. package/corpus/core/src/server/agent-teams-run-queue.ts +22 -10
  40. package/corpus/core/src/server/auth.ts +22 -4
  41. package/corpus/core/src/server/better-auth-instance.ts +49 -15
  42. package/corpus/core/src/server/embed-session.ts +19 -4
  43. package/corpus/core/src/server/identity-sso-store.ts +40 -11
  44. package/corpus/core/src/server/recap-image-store.ts +28 -9
  45. package/corpus/core/src/settings/store.ts +14 -19
  46. package/corpus/core/src/usage/store.ts +51 -16
  47. package/corpus/core/src/workspace-connections/store.ts +200 -22
  48. package/corpus/templates/calendar/changelog/2026-06-25-booking-link-previews-now-show-the-content-directly-on-the-g.md +6 -0
  49. package/corpus/templates/calendar/server/lib/booking-og-image.ts +0 -2
  50. package/corpus/templates/clips/chrome-extension/package.json +1 -1
  51. package/corpus/templates/content/actions/list-trashed-content-databases.ts +36 -2
  52. package/corpus/templates/content/actions/move-document.ts +79 -33
  53. package/corpus/templates/content/actions/restore-content-database.ts +40 -3
  54. package/corpus/templates/content/app/components/editor/BubbleToolbar.tsx +84 -2
  55. package/corpus/templates/content/app/components/editor/CommentsSidebar.tsx +177 -49
  56. package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +393 -219
  57. package/corpus/templates/content/app/components/editor/DocumentToolbar.tsx +632 -467
  58. package/corpus/templates/content/app/components/editor/SlashCommandMenu.tsx +114 -14
  59. package/corpus/templates/content/app/components/editor/VisualEditor.tsx +56 -15
  60. package/corpus/templates/content/app/components/editor/extensions/NotionExtensions.tsx +6 -5
  61. package/corpus/templates/content/app/components/sidebar/DocumentTreeItem.tsx +12 -4
  62. package/corpus/templates/content/app/global.css +9 -0
  63. package/corpus/templates/content/app/hooks/use-create-page.ts +36 -7
  64. package/corpus/templates/content/app/i18n-data.ts +140 -10
  65. package/corpus/templates/content/changelog/2026-06-25-comment-cards-now-track-their-highlighted-text-while-scrolli.md +6 -0
  66. package/corpus/templates/content/changelog/2026-06-25-comments-now-scroll-with-the-document-and-stay-below-the-pag.md +6 -0
  67. package/corpus/templates/content/changelog/2026-06-25-creating-a-database-from-the-slash-menu-no-longer-leaves-sta.md +6 -0
  68. package/corpus/templates/content/changelog/2026-06-25-sidebar-hover-controls-no-longer-leave-a-lingering-fade-when.md +6 -0
  69. package/corpus/templates/content/changelog/2026-06-25-sidebar-page-actions-are-now-easier-to-see-when-hovering-ina.md +6 -0
  70. package/corpus/templates/content/changelog/2026-06-25-text-selections-in-the-editor-no-longer-show-white-gaps-when.md +6 -0
  71. package/corpus/templates/content/changelog/2026-06-25-the-editor-toolbar-now-has-a-one-click-page-link-copy-button.md +6 -0
  72. package/corpus/templates/content/changelog/2026-06-25-the-editor-toolbar-now-shows-page-breadcrumbs-and-the-latest.md +6 -0
  73. package/corpus/templates/content/changelog/2026-06-25-the-page-command-now-leaves-a-notion-style-page-reference-an.md +6 -0
  74. package/corpus/templates/content/shared/nfm.ts +16 -0
  75. package/corpus/templates/forms/changelog/2026-06-25-form-previews-now-show-the-content-directly-on-the-grid-back.md +6 -0
  76. package/corpus/templates/forms/server/lib/form-og-image.ts +0 -2
  77. package/corpus/templates/plan/app/global.css +6 -11
  78. package/corpus/templates/plan/app/pages/PlansPage.tsx +12 -10
  79. package/corpus/templates/plan/changelog/2026-06-25-plan-access-request-pages-stay-visible-during-background-ref.md +6 -0
  80. package/corpus/templates/plan/changelog/2026-06-25-plan-documents-keep-their-full-reading-width-below-1200px-an.md +6 -0
  81. package/dist/a2a/task-store.d.ts.map +1 -1
  82. package/dist/a2a/task-store.js +19 -3
  83. package/dist/a2a/task-store.js.map +1 -1
  84. package/dist/agent/harness/store.d.ts.map +1 -1
  85. package/dist/agent/harness/store.js +63 -34
  86. package/dist/agent/harness/store.js.map +1 -1
  87. package/dist/agent/observational-memory/store.d.ts.map +1 -1
  88. package/dist/agent/observational-memory/store.js +30 -8
  89. package/dist/agent/observational-memory/store.js.map +1 -1
  90. package/dist/agent/run-store.d.ts.map +1 -1
  91. package/dist/agent/run-store.js +103 -46
  92. package/dist/agent/run-store.js.map +1 -1
  93. package/dist/application-state/store.d.ts.map +1 -1
  94. package/dist/application-state/store.js +28 -2
  95. package/dist/application-state/store.js.map +1 -1
  96. package/dist/audit/store.d.ts.map +1 -1
  97. package/dist/audit/store.js +18 -3
  98. package/dist/audit/store.js.map +1 -1
  99. package/dist/browser-sessions/store.d.ts.map +1 -1
  100. package/dist/browser-sessions/store.js +22 -7
  101. package/dist/browser-sessions/store.js.map +1 -1
  102. package/dist/chat-threads/store.d.ts.map +1 -1
  103. package/dist/chat-threads/store.js +51 -3
  104. package/dist/chat-threads/store.js.map +1 -1
  105. package/dist/checkpoints/store.d.ts.map +1 -1
  106. package/dist/checkpoints/store.js +12 -3
  107. package/dist/checkpoints/store.js.map +1 -1
  108. package/dist/cli/create.d.ts.map +1 -1
  109. package/dist/cli/create.js +9 -0
  110. package/dist/cli/create.js.map +1 -1
  111. package/dist/collab/awareness.d.ts +1 -1
  112. package/dist/collab/awareness.d.ts.map +1 -1
  113. package/dist/collab/awareness.js +12 -8
  114. package/dist/collab/awareness.js.map +1 -1
  115. package/dist/collab/client.d.ts.map +1 -1
  116. package/dist/collab/client.js +4 -3
  117. package/dist/collab/client.js.map +1 -1
  118. package/dist/collab/routes.d.ts +1 -1
  119. package/dist/collab/storage.d.ts.map +1 -1
  120. package/dist/collab/storage.js +14 -2
  121. package/dist/collab/storage.js.map +1 -1
  122. package/dist/db/ddl-guard.d.ts +63 -0
  123. package/dist/db/ddl-guard.d.ts.map +1 -1
  124. package/dist/db/ddl-guard.js +85 -0
  125. package/dist/db/ddl-guard.js.map +1 -1
  126. package/dist/extensions/store.d.ts.map +1 -1
  127. package/dist/extensions/store.js +32 -3
  128. package/dist/extensions/store.js.map +1 -1
  129. package/dist/integrations/a2a-continuations-store.d.ts.map +1 -1
  130. package/dist/integrations/a2a-continuations-store.js +44 -24
  131. package/dist/integrations/a2a-continuations-store.js.map +1 -1
  132. package/dist/integrations/config-store.d.ts.map +1 -1
  133. package/dist/integrations/config-store.js +17 -11
  134. package/dist/integrations/config-store.js.map +1 -1
  135. package/dist/integrations/pending-tasks-store.d.ts.map +1 -1
  136. package/dist/integrations/pending-tasks-store.js +24 -16
  137. package/dist/integrations/pending-tasks-store.js.map +1 -1
  138. package/dist/integrations/remote-commands-store.d.ts.map +1 -1
  139. package/dist/integrations/remote-commands-store.js +29 -21
  140. package/dist/integrations/remote-commands-store.js.map +1 -1
  141. package/dist/integrations/remote-devices-store.d.ts.map +1 -1
  142. package/dist/integrations/remote-devices-store.js +33 -18
  143. package/dist/integrations/remote-devices-store.js.map +1 -1
  144. package/dist/integrations/remote-push-store.d.ts.map +1 -1
  145. package/dist/integrations/remote-push-store.js +53 -31
  146. package/dist/integrations/remote-push-store.js.map +1 -1
  147. package/dist/integrations/remote-run-events-store.d.ts.map +1 -1
  148. package/dist/integrations/remote-run-events-store.js +26 -10
  149. package/dist/integrations/remote-run-events-store.js.map +1 -1
  150. package/dist/integrations/thread-mapping-store.d.ts.map +1 -1
  151. package/dist/integrations/thread-mapping-store.js +10 -2
  152. package/dist/integrations/thread-mapping-store.js.map +1 -1
  153. package/dist/mcp/connect-store.d.ts.map +1 -1
  154. package/dist/mcp/connect-store.js +34 -16
  155. package/dist/mcp/connect-store.js.map +1 -1
  156. package/dist/mcp/oauth-store.d.ts.map +1 -1
  157. package/dist/mcp/oauth-store.js +22 -7
  158. package/dist/mcp/oauth-store.js.map +1 -1
  159. package/dist/notifications/store.d.ts.map +1 -1
  160. package/dist/notifications/store.js +15 -3
  161. package/dist/notifications/store.js.map +1 -1
  162. package/dist/oauth-tokens/store.d.ts.map +1 -1
  163. package/dist/oauth-tokens/store.js +33 -2
  164. package/dist/oauth-tokens/store.js.map +1 -1
  165. package/dist/observability/store.d.ts.map +1 -1
  166. package/dist/observability/store.js +72 -29
  167. package/dist/observability/store.js.map +1 -1
  168. package/dist/progress/store.d.ts.map +1 -1
  169. package/dist/progress/store.js +31 -16
  170. package/dist/progress/store.js.map +1 -1
  171. package/dist/provider-api/corpus-jobs-store.d.ts.map +1 -1
  172. package/dist/provider-api/corpus-jobs-store.js +16 -5
  173. package/dist/provider-api/corpus-jobs-store.js.map +1 -1
  174. package/dist/provider-api/custom-registry.d.ts.map +1 -1
  175. package/dist/provider-api/custom-registry.js +14 -4
  176. package/dist/provider-api/custom-registry.js.map +1 -1
  177. package/dist/provider-api/quota-governor.d.ts.map +1 -1
  178. package/dist/provider-api/quota-governor.js +12 -3
  179. package/dist/provider-api/quota-governor.js.map +1 -1
  180. package/dist/provider-api/staged-datasets-store.d.ts.map +1 -1
  181. package/dist/provider-api/staged-datasets-store.js +16 -5
  182. package/dist/provider-api/staged-datasets-store.js.map +1 -1
  183. package/dist/resources/store.d.ts.map +1 -1
  184. package/dist/resources/store.js +56 -25
  185. package/dist/resources/store.js.map +1 -1
  186. package/dist/secrets/storage.d.ts.map +1 -1
  187. package/dist/secrets/storage.js +10 -14
  188. package/dist/secrets/storage.js.map +1 -1
  189. package/dist/server/agent-teams-run-queue.d.ts.map +1 -1
  190. package/dist/server/agent-teams-run-queue.js +15 -4
  191. package/dist/server/agent-teams-run-queue.js.map +1 -1
  192. package/dist/server/auth.d.ts.map +1 -1
  193. package/dist/server/auth.js +16 -2
  194. package/dist/server/auth.js.map +1 -1
  195. package/dist/server/better-auth-instance.d.ts.map +1 -1
  196. package/dist/server/better-auth-instance.js +55 -21
  197. package/dist/server/better-auth-instance.js.map +1 -1
  198. package/dist/server/embed-session.d.ts.map +1 -1
  199. package/dist/server/embed-session.js +15 -4
  200. package/dist/server/embed-session.js.map +1 -1
  201. package/dist/server/identity-sso-store.d.ts.map +1 -1
  202. package/dist/server/identity-sso-store.js +30 -11
  203. package/dist/server/identity-sso-store.js.map +1 -1
  204. package/dist/server/recap-image-store.d.ts.map +1 -1
  205. package/dist/server/recap-image-store.js +21 -7
  206. package/dist/server/recap-image-store.js.map +1 -1
  207. package/dist/settings/store.d.ts.map +1 -1
  208. package/dist/settings/store.js +11 -13
  209. package/dist/settings/store.js.map +1 -1
  210. package/dist/usage/store.d.ts.map +1 -1
  211. package/dist/usage/store.js +36 -11
  212. package/dist/usage/store.js.map +1 -1
  213. package/dist/workspace-connections/store.d.ts.map +1 -1
  214. package/dist/workspace-connections/store.js +57 -7
  215. package/dist/workspace-connections/store.js.map +1 -1
  216. package/package.json +3 -3
@@ -1,4 +1,5 @@
1
1
  import { useSendToAgentChat, useT } from "@agent-native/core/client";
2
+ import { collapseExactRepeatedNfm, docToNfm } from "@shared/nfm";
2
3
  import {
3
4
  IconTypography,
4
5
  IconH1,
@@ -44,6 +45,8 @@ import { buildRegistrySlashItems } from "./registrySlashItems";
44
45
  interface SlashCommandMenuProps {
45
46
  editor: Editor;
46
47
  documentId?: string;
48
+ onDraftCommitted?: () => void | Promise<void>;
49
+ onDraftPersisted?: (markdown: string) => boolean | Promise<boolean>;
47
50
  /**
48
51
  * The open document's linked Notion page id, when it has one. When set, the
49
52
  * registry-derived block slash items are filtered to specs that round-trip to
@@ -65,7 +68,35 @@ interface CommandItem {
65
68
  searchText?: string;
66
69
  shortcut?: string;
67
70
  icon: React.ElementType;
68
- action: (editor: Editor) => void | boolean | Promise<void>;
71
+ preserveSlashRange?: boolean;
72
+ action: (
73
+ editor: Editor,
74
+ context: { slashRange: { from: number; to: number } | null },
75
+ ) => void | boolean | Promise<void>;
76
+ }
77
+
78
+ function getActiveSlashCommandRange(editor: Editor) {
79
+ const { state } = editor;
80
+ if (!state.selection.empty) return null;
81
+ const { from, $from } = state.selection;
82
+ if (!$from.parent.isTextblock) return null;
83
+
84
+ const blockStart = $from.start();
85
+ const textBefore = state.doc.textBetween(blockStart, from, "\n");
86
+ const slashQuery = parseSlashCommandQuery(textBefore);
87
+ if (slashQuery === null) return null;
88
+
89
+ const slashIndex = textBefore.lastIndexOf("/");
90
+ return {
91
+ from: blockStart + slashIndex,
92
+ to: from,
93
+ };
94
+ }
95
+
96
+ function waitForEditorUpdateFrame() {
97
+ return new Promise<void>((resolve) => {
98
+ requestAnimationFrame(() => requestAnimationFrame(() => resolve()));
99
+ });
69
100
  }
70
101
 
71
102
  interface CommandTemplate extends Omit<CommandItem, "title" | "description"> {
@@ -369,11 +400,13 @@ export function SlashCommandMenu({
369
400
  editor,
370
401
  documentId,
371
402
  notionPageId,
403
+ onDraftCommitted,
404
+ onDraftPersisted,
372
405
  }: SlashCommandMenuProps) {
373
406
  const t = useT();
374
407
  const { send } = useSendToAgentChat();
375
408
  const navigate = useNavigate();
376
- const createPage = useCreatePage();
409
+ const createPage = useCreatePage({ navigate: false, awaitPersist: true });
377
410
  const createDatabase = useCreateContentDatabase(documentId ?? null);
378
411
 
379
412
  const [isOpen, setIsOpen] = useState(false);
@@ -505,12 +538,70 @@ export function SlashCommandMenu({
505
538
  title: t("editor.slash.page"),
506
539
  description: t("editor.slash.pageDescription"),
507
540
  icon: IconFileText,
508
- action: async () => {
541
+ preserveSlashRange: true,
542
+ action: async (_editor, { slashRange }) => {
509
543
  if (!documentId) {
510
544
  toast.error(t("editor.noDocumentSelected"));
511
545
  return;
512
546
  }
513
- await createPage(documentId);
547
+ let pageId: string;
548
+ try {
549
+ pageId = await createPage(documentId);
550
+ } catch {
551
+ return;
552
+ }
553
+ const pageReference = {
554
+ type: "notionBlockAtom",
555
+ attrs: {
556
+ tagName: "page",
557
+ attrsJson: JSON.stringify({
558
+ id: pageId,
559
+ }),
560
+ label: "Untitled",
561
+ },
562
+ };
563
+ const insertContent = [pageReference, { type: "paragraph" }];
564
+ const range = slashRange
565
+ ? (() => {
566
+ const $from = editor.state.doc.resolve(slashRange.from);
567
+ return $from.parent.isTextblock
568
+ ? { from: $from.before(), to: $from.after() }
569
+ : slashRange;
570
+ })()
571
+ : null;
572
+
573
+ if (range) {
574
+ editor.chain().focus().insertContentAt(range, insertContent).run();
575
+ } else {
576
+ const { $from } = editor.state.selection;
577
+ editor
578
+ .chain()
579
+ .focus()
580
+ .insertContentAt($from.after(), insertContent)
581
+ .run();
582
+ }
583
+ await waitForEditorUpdateFrame();
584
+ try {
585
+ const content = collapseExactRepeatedNfm(
586
+ docToNfm(editor.getJSON() as any),
587
+ {
588
+ requiredText: `id="${pageId}"`,
589
+ },
590
+ );
591
+ if (onDraftPersisted) {
592
+ const persisted = await onDraftPersisted(content);
593
+ if (!persisted) throw new Error(t("empty.genericError"));
594
+ } else {
595
+ await onDraftCommitted?.();
596
+ }
597
+ } catch (error) {
598
+ toast.error(t("editor.failedToCreatePage"), {
599
+ description:
600
+ error instanceof Error ? error.message : t("empty.genericError"),
601
+ });
602
+ return;
603
+ }
604
+ navigate(`/page/${pageId}`, { flushSync: true });
514
605
  },
515
606
  };
516
607
 
@@ -621,19 +712,19 @@ export function SlashCommandMenu({
621
712
 
622
713
  const executeCommand = useCallback(
623
714
  async (cmd: CommandItem) => {
624
- if (slashPosRef.current !== null) {
625
- const { from } = editor.state.selection;
626
- editor
627
- .chain()
628
- .focus()
629
- .deleteRange({ from: slashPosRef.current, to: from })
630
- .run();
715
+ const slashRange =
716
+ getActiveSlashCommandRange(editor) ??
717
+ (slashPosRef.current !== null
718
+ ? { from: slashPosRef.current, to: editor.state.selection.from }
719
+ : null);
720
+ if (slashRange && !cmd.preserveSlashRange) {
721
+ editor.chain().focus().deleteRange(slashRange).run();
631
722
  }
632
723
  setIsOpen(false);
633
724
  setIsTurnInto(false);
634
725
  setQuery("");
635
726
  slashPosRef.current = null;
636
- await cmd.action(editor);
727
+ await cmd.action(editor, { slashRange });
637
728
  },
638
729
  [editor],
639
730
  );
@@ -688,24 +779,29 @@ export function SlashCommandMenu({
688
779
 
689
780
  if (e.key === "ArrowDown") {
690
781
  e.preventDefault();
782
+ e.stopPropagation();
691
783
  if (filteredCommands.length === 0) return;
692
784
  setSelectedIndex((i) => (i + 1) % filteredCommands.length);
693
785
  } else if (e.key === "ArrowUp") {
694
786
  e.preventDefault();
787
+ e.stopPropagation();
695
788
  if (filteredCommands.length === 0) return;
696
789
  setSelectedIndex(
697
790
  (i) => (i - 1 + filteredCommands.length) % filteredCommands.length,
698
791
  );
699
792
  } else if (e.key === "Enter") {
700
793
  e.preventDefault();
794
+ e.stopPropagation();
701
795
  if (filteredCommands[selectedIndex]) {
702
796
  executeCommand(filteredCommands[selectedIndex]);
703
797
  }
704
798
  } else if (e.key === "Escape") {
799
+ e.stopPropagation();
705
800
  setIsOpen(false);
706
801
  setIsTurnInto(false);
707
802
  setQuery("");
708
803
  slashPosRef.current = null;
804
+ onDraftCommitted?.();
709
805
  }
710
806
  };
711
807
 
@@ -717,6 +813,8 @@ export function SlashCommandMenu({
717
813
  filteredCommands,
718
814
  executeCommand,
719
815
  editor,
816
+ onDraftCommitted,
817
+ onDraftPersisted,
720
818
  openGeneratePopover,
721
819
  readInlineGenerateCommand,
722
820
  submitGeneratePrompt,
@@ -956,8 +1054,10 @@ function CommandButton({
956
1054
  return (
957
1055
  <button
958
1056
  ref={buttonRef}
959
- onMouseDown={(event) => event.preventDefault()}
960
- onClick={onExecute}
1057
+ onMouseDown={(event) => {
1058
+ event.preventDefault();
1059
+ onExecute();
1060
+ }}
961
1061
  onMouseEnter={onHover}
962
1062
  className={cn(
963
1063
  "flex min-h-9 w-full items-center gap-3 px-3 py-1 text-left transition-colors",
@@ -735,6 +735,7 @@ interface VisualEditorProps {
735
735
  */
736
736
  contentUpdatedAt?: string | null;
737
737
  onChange: (markdown: string) => void;
738
+ onSaveContent?: (markdown: string) => boolean | Promise<boolean>;
738
739
  /** Yjs document for collaborative editing. */
739
740
  ydoc?: YDoc | null;
740
741
  /** Shared awareness instance for collaborative cursors/presence. */
@@ -870,6 +871,17 @@ export function shouldPersistLocalFileEditorUpdate({
870
871
  return Boolean(transactionUiEvent);
871
872
  }
872
873
 
874
+ function isActiveSlashCommandDraft(editor: CoreEditor): boolean {
875
+ const { state } = editor;
876
+ if (!state.selection.empty) return false;
877
+ const { from, $from } = state.selection;
878
+ if (!$from.parent.isTextblock) return false;
879
+
880
+ const blockStart = $from.start();
881
+ const textBefore = state.doc.textBetween(blockStart, from, "\n");
882
+ return /^\s*\/[a-zA-Z0-9]*$/.test(textBefore);
883
+ }
884
+
873
885
  interface VisualEditorExtensionOptions {
874
886
  documentId?: string;
875
887
  ydoc?: YDoc | null;
@@ -1550,6 +1562,7 @@ export function VisualEditor({
1550
1562
  content,
1551
1563
  contentUpdatedAt,
1552
1564
  onChange,
1565
+ onSaveContent,
1553
1566
  ydoc,
1554
1567
  awareness,
1555
1568
  user,
@@ -1569,6 +1582,8 @@ export function VisualEditor({
1569
1582
  const [isDraggingMedia, setIsDraggingMedia] = useState(false);
1570
1583
  const onChangeRef = useRef(onChange);
1571
1584
  onChangeRef.current = onChange;
1585
+ const onSaveContentRef = useRef(onSaveContent);
1586
+ onSaveContentRef.current = onSaveContent;
1572
1587
  const notionPageLinksRef = useRef(notionPageLinks);
1573
1588
  notionPageLinksRef.current = notionPageLinks;
1574
1589
  const resolveNotionPageLink = useCallback((notionPageId: string) => {
@@ -1576,6 +1591,7 @@ export function VisualEditor({
1576
1591
  return (
1577
1592
  notionPageLinksRef.current.find(
1578
1593
  (link) =>
1594
+ link.notionPageId === notionPageId ||
1579
1595
  link.notionPageId.replace(/-/g, "").toLowerCase() === normalized,
1580
1596
  ) ?? null
1581
1597
  );
@@ -1604,9 +1620,10 @@ export function VisualEditor({
1604
1620
  // Clean up awareness on unmount
1605
1621
  useEffect(() => {
1606
1622
  return () => {
1623
+ localAwareness?.setLocalState(null);
1607
1624
  fallbackAwareness?.destroy();
1608
1625
  };
1609
- }, [fallbackAwareness]);
1626
+ }, [fallbackAwareness, localAwareness]);
1610
1627
 
1611
1628
  const extensions = useMemo(
1612
1629
  () =>
@@ -1643,6 +1660,36 @@ export function VisualEditor({
1643
1660
  const markUserEditIntent = useCallback(() => {
1644
1661
  lastUserEditIntentAtRef.current = Date.now();
1645
1662
  }, []);
1663
+ const persistEditorContent = useCallback(
1664
+ (
1665
+ editorToPersist: CoreEditor,
1666
+ options?: { markdown?: string; immediate?: boolean },
1667
+ ) => {
1668
+ const guards = guardsRef.current;
1669
+ if (!guards) return false;
1670
+ try {
1671
+ const normalized =
1672
+ options?.markdown ?? docToNfm(editorToPersist.getJSON() as any);
1673
+ if (localFileMode && normalized === content) return true;
1674
+ if (options?.immediate && onSaveContentRef.current) {
1675
+ return onSaveContentRef.current(normalized);
1676
+ }
1677
+ // Don't persist an empty doc before Collaboration has seeded (would
1678
+ // clobber DB content with an empty string). `registerEmitted` records
1679
+ // this as the last-emitted value and returns false to skip the save.
1680
+ if (!guards.registerEmitted(normalized)) return true;
1681
+ queueMicrotask(() => onChangeRef.current(normalized));
1682
+ return true;
1683
+ } catch (err: any) {
1684
+ toast.error(
1685
+ t("editor.markdownSerializationError", { message: err.message }),
1686
+ );
1687
+ console.error("Markdown serialization error:", err);
1688
+ return false;
1689
+ }
1690
+ },
1691
+ [content, localFileMode, t],
1692
+ );
1646
1693
 
1647
1694
  const editor = useEditor({
1648
1695
  extensions,
@@ -1796,20 +1843,8 @@ export function VisualEditor({
1796
1843
  ) {
1797
1844
  return;
1798
1845
  }
1799
- try {
1800
- const normalized = docToNfm(editor.getJSON() as any);
1801
- if (localFileMode && normalized === content) return;
1802
- // Don't persist an empty doc before Collaboration has seeded (would
1803
- // clobber DB content with an empty string). `registerEmitted` records
1804
- // this as the last-emitted value and returns false to skip the save.
1805
- if (!guards.registerEmitted(normalized)) return;
1806
- queueMicrotask(() => onChangeRef.current(normalized));
1807
- } catch (err: any) {
1808
- toast.error(
1809
- t("editor.markdownSerializationError", { message: err.message }),
1810
- );
1811
- console.error("Markdown serialization error:", err);
1812
- }
1846
+ if (isActiveSlashCommandDraft(editor)) return;
1847
+ persistEditorContent(editor);
1813
1848
  },
1814
1849
  });
1815
1850
 
@@ -2038,6 +2073,12 @@ export function VisualEditor({
2038
2073
  editor={editor}
2039
2074
  documentId={documentId}
2040
2075
  notionPageId={notionPageId}
2076
+ onDraftCommitted={() => {
2077
+ void persistEditorContent(editor);
2078
+ }}
2079
+ onDraftPersisted={(markdown) =>
2080
+ persistEditorContent(editor, { markdown, immediate: true })
2081
+ }
2041
2082
  />
2042
2083
  ) : null}
2043
2084
  <LinkHoverPreview editor={editor} editable={editable} />
@@ -144,11 +144,12 @@ function normalizeNotionPageId(input: string | null | undefined) {
144
144
 
145
145
  function getNotionPageId(attrs: Record<string, string>) {
146
146
  return (
147
- normalizeNotionPageId(attrs.url) ??
148
- normalizeNotionPageId(attrs.href) ??
149
- normalizeNotionPageId(attrs.id) ??
150
- normalizeNotionPageId(attrs.pageId) ??
151
- normalizeNotionPageId(attrs.page_id)
147
+ attrs["data-agent-native-document-id"] ||
148
+ attrs.id ||
149
+ (normalizeNotionPageId(attrs.url) ??
150
+ normalizeNotionPageId(attrs.href) ??
151
+ normalizeNotionPageId(attrs.pageId) ??
152
+ normalizeNotionPageId(attrs.page_id))
152
153
  );
153
154
  }
154
155
 
@@ -148,7 +148,7 @@ export function DocumentTreeItem({
148
148
  isDragging && "bg-accent/70 text-accent-foreground shadow-sm",
149
149
  isActive
150
150
  ? "bg-accent text-accent-foreground"
151
- : "text-muted-foreground hover:bg-accent/50 hover:text-foreground",
151
+ : "text-muted-foreground hover:bg-accent hover:text-foreground",
152
152
  )}
153
153
  style={{
154
154
  paddingInlineStart: `${indent}px`,
@@ -208,14 +208,20 @@ export function DocumentTreeItem({
208
208
  </span>
209
209
 
210
210
  <div
211
- className="absolute end-1 top-1/2 -translate-y-1/2 opacity-0 group-hover:opacity-100 flex items-center gap-0.5 flex-shrink-0 bg-inherit"
211
+ className={cn(
212
+ "pointer-events-none absolute right-1 top-1/2 flex flex-shrink-0 -translate-y-1/2 items-center gap-0.5 rounded-md pl-1 opacity-0 group-hover:pointer-events-auto group-hover:opacity-100 group-focus-within:pointer-events-auto group-focus-within:opacity-100",
213
+ "bg-accent text-foreground",
214
+ isActive && "text-accent-foreground",
215
+ )}
212
216
  onPointerDown={(e) => e.stopPropagation()}
213
217
  >
214
218
  {hasMenuActions && (
215
219
  <DropdownMenu>
216
220
  <DropdownMenuTrigger asChild>
217
221
  <button
218
- className="w-6 h-6 flex items-center justify-center rounded hover:bg-accent"
222
+ type="button"
223
+ className="flex h-6 w-6 items-center justify-center rounded text-current hover:bg-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
224
+ aria-label={`More actions for ${node.title || "Untitled"}`}
219
225
  onClick={(e) => e.stopPropagation()}
220
226
  >
221
227
  <IconDots size={14} />
@@ -259,7 +265,9 @@ export function DocumentTreeItem({
259
265
  <Tooltip>
260
266
  <TooltipTrigger asChild>
261
267
  <button
262
- className="w-7 h-7 flex items-center justify-center rounded hover:bg-accent"
268
+ type="button"
269
+ className="flex h-7 w-7 items-center justify-center rounded text-current hover:bg-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
270
+ aria-label={`Add sub-page to ${node.title || "Untitled"}`}
263
271
  onClick={(e) => {
264
272
  e.stopPropagation();
265
273
  onCreateChild(node.id);
@@ -2119,6 +2119,15 @@ audio.media-block__content {
2119
2119
  background: hsl(210 100% 52% / 0.2);
2120
2120
  }
2121
2121
 
2122
+ .notion-editor .notion-selection-fill {
2123
+ background: hsl(210 100% 52% / 0.08);
2124
+ box-decoration-break: clone;
2125
+ -webkit-box-decoration-break: clone;
2126
+ box-shadow:
2127
+ 0 0.24em 0 hsl(210 100% 52% / 0.08),
2128
+ 0 -0.08em 0 hsl(210 100% 52% / 0.08);
2129
+ }
2130
+
2122
2131
  @media print {
2123
2132
  @page {
2124
2133
  margin: 0.65in;
@@ -19,11 +19,17 @@ function nanoid(size = 12): string {
19
19
  return Array.from(bytes, (b) => chars[b % chars.length]).join("");
20
20
  }
21
21
 
22
- export function useCreatePage(opts?: { onAfterNavigate?: () => void }) {
22
+ export function useCreatePage(opts?: {
23
+ onAfterNavigate?: () => void;
24
+ navigate?: boolean;
25
+ awaitPersist?: boolean;
26
+ }) {
23
27
  const navigate = useNavigate();
24
28
  const queryClient = useQueryClient();
25
29
  const createDocument = useCreateDocument();
26
30
  const onAfterNavigate = opts?.onAfterNavigate;
31
+ const shouldNavigate = opts?.navigate ?? true;
32
+ const shouldAwaitPersist = opts?.awaitPersist ?? true;
27
33
 
28
34
  return useCallback(
29
35
  async (parentId?: string) => {
@@ -50,10 +56,12 @@ export function useCreatePage(opts?: { onAfterNavigate?: () => void }) {
50
56
  });
51
57
  queryClient.setQueryData(["action", "get-document", { id }], tempDoc);
52
58
 
53
- navigate(`/page/${id}`, { flushSync: true });
54
- onAfterNavigate?.();
59
+ if (shouldNavigate) {
60
+ navigate(`/page/${id}`, { flushSync: true });
61
+ onAfterNavigate?.();
62
+ }
55
63
 
56
- try {
64
+ const persist = async () => {
57
65
  await createDocument.mutateAsync({
58
66
  id,
59
67
  title: "",
@@ -67,21 +75,42 @@ export function useCreatePage(opts?: { onAfterNavigate?: () => void }) {
67
75
  queryClient.invalidateQueries({
68
76
  queryKey: ["action", "list-documents"],
69
77
  });
70
- } catch (err) {
78
+ };
79
+
80
+ const onPersistError = (err: unknown) => {
71
81
  queryClient.invalidateQueries({
72
82
  queryKey: ["action", "list-documents"],
73
83
  });
74
84
  queryClient.removeQueries({
75
85
  queryKey: ["action", "get-document", { id }],
76
86
  });
77
- navigate("/");
87
+ if (shouldNavigate) navigate("/");
78
88
  toast.error("Failed to create page", {
79
89
  description:
80
90
  err instanceof Error ? err.message : "Something went wrong",
81
91
  });
92
+ };
93
+
94
+ if (shouldAwaitPersist) {
95
+ try {
96
+ await persist();
97
+ } catch (err) {
98
+ onPersistError(err);
99
+ throw err;
100
+ }
101
+ } else {
102
+ void persist().catch(onPersistError);
82
103
  }
104
+
83
105
  return id;
84
106
  },
85
- [createDocument, navigate, onAfterNavigate, queryClient],
107
+ [
108
+ createDocument,
109
+ navigate,
110
+ onAfterNavigate,
111
+ queryClient,
112
+ shouldAwaitPersist,
113
+ shouldNavigate,
114
+ ],
86
115
  );
87
116
  }