@agent-native/core 0.92.9 → 0.92.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.
- package/corpus/README.md +1 -1
- package/corpus/core/CHANGELOG.md +29 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/agent/production-agent.ts +181 -15
- package/corpus/core/src/agent/run-manager.ts +31 -4
- package/corpus/core/src/agent/run-store.ts +80 -0
- package/corpus/core/src/cli/design-connect.ts +433 -151
- package/corpus/core/src/cli/skills.ts +14 -0
- package/corpus/core/src/client/AssistantChat.tsx +25 -0
- package/corpus/core/src/client/MultiTabAssistantChat.tsx +40 -6
- package/corpus/core/src/client/agent-chat.ts +165 -1
- package/corpus/core/src/client/chat/index.ts +9 -1
- package/corpus/core/src/client/extensions/EmbeddedExtension.tsx +38 -2
- package/corpus/core/src/client/index.ts +5 -0
- package/corpus/core/src/client/rich-markdown-editor/useCollabReconcile.ts +18 -7
- package/corpus/core/src/collab/awareness-store.ts +23 -12
- package/corpus/core/src/collab/client.ts +16 -5
- package/corpus/core/src/collab/index.ts +4 -0
- package/corpus/core/src/collab/recent-edits.ts +65 -2
- package/corpus/core/src/deploy/build.ts +133 -5
- package/corpus/core/src/index.browser.ts +1 -0
- package/corpus/core/src/server/agent-chat-plugin.ts +98 -11
- package/corpus/core/src/server/builder-design-systems.ts +30 -4
- package/corpus/templates/clips/.agents/skills/ai-video-tools/SKILL.md +1 -1
- package/corpus/templates/clips/.agents/skills/video-sharing/SKILL.md +12 -6
- package/corpus/templates/clips/AGENTS.md +4 -0
- package/corpus/templates/clips/actions/create-recording.ts +2 -0
- package/corpus/templates/clips/actions/lib/create-recording-schema.ts +5 -3
- package/corpus/templates/clips/actions/request-transcript.ts +102 -33
- package/corpus/templates/clips/app/components/library/library-grid.tsx +1 -0
- package/corpus/templates/clips/app/components/player/share-dialog.tsx +59 -19
- package/corpus/templates/clips/app/components/player/transcript-panel.tsx +48 -4
- package/corpus/templates/clips/app/components/player/video-player.tsx +17 -12
- package/corpus/templates/clips/app/i18n/ar-SA.ts +1 -0
- package/corpus/templates/clips/app/i18n/de-DE.ts +1 -0
- package/corpus/templates/clips/app/i18n/en-US.ts +1 -0
- package/corpus/templates/clips/app/i18n/es-ES.ts +1 -0
- package/corpus/templates/clips/app/i18n/fr-FR.ts +1 -0
- package/corpus/templates/clips/app/i18n/hi-IN.ts +1 -0
- package/corpus/templates/clips/app/i18n/ja-JP.ts +1 -0
- package/corpus/templates/clips/app/i18n/ko-KR.ts +1 -0
- package/corpus/templates/clips/app/i18n/pt-BR.ts +1 -0
- package/corpus/templates/clips/app/i18n/zh-CN.ts +1 -0
- package/corpus/templates/clips/app/i18n/zh-TW.ts +1 -0
- package/corpus/templates/clips/app/routes/r.$recordingId.tsx +70 -37
- package/corpus/templates/clips/app/routes/record.tsx +17 -4
- package/corpus/templates/clips/app/routes/share.$shareId.tsx +2 -0
- package/corpus/templates/clips/changelog/2026-07-09-desktop-popover-sits-closer-to-the-menu-bar.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-09-desktop-recording-optimization-progress-stays-visible-until-the-clip-opens.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-09-fixed-large-video-file-uploads-failing-before-their-resumabl.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-09-fixed-new-video-uploads-defaulting-to-private.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-09-long-recordings-now-get-enough-time-to-download-their-media-.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-09-meeting-notes-no-longer-lowers-your-microphone-volume-for-ot.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-09-on-mobile-tapping-a-playing-clip-reveals-playback-controls-w.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-09-regenerate-any-clip-transcript-from-its-saved-recording-or-r.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-09-slack-previews-now-show-the-clip-length-alongside-its-descri.md +6 -0
- package/corpus/templates/clips/desktop/src/app.tsx +12 -2
- package/corpus/templates/clips/desktop/src/hooks/useMeetingTranscription.ts +8 -1
- package/corpus/templates/clips/desktop/src/lib/recorder.ts +8 -5
- package/corpus/templates/clips/desktop/src/lib/transcription-engine.ts +5 -5
- package/corpus/templates/clips/desktop/src/lib/voice-dictation.ts +3 -0
- package/corpus/templates/clips/desktop/src/styles.css +6 -3
- package/corpus/templates/clips/desktop/src-tauri/src/clips/mod.rs +29 -11
- package/corpus/templates/clips/desktop/src-tauri/src/native_screen.rs +1 -1
- package/corpus/templates/clips/desktop/src-tauri/src/system_audio.rs +103 -21
- package/corpus/templates/clips/desktop/src-tauri/src/whisper_speech.rs +117 -39
- package/corpus/templates/clips/server/lib/slack-unfurls.ts +20 -1
- package/corpus/templates/clips/server/lib/streaming-upload-mode.ts +10 -4
- package/corpus/templates/clips/shared/recording-core.ts +12 -0
- package/corpus/templates/content/AGENTS.md +7 -6
- package/corpus/templates/content/actions/_document-flush.ts +132 -17
- package/corpus/templates/content/actions/_notion-action-utils.ts +15 -0
- package/corpus/templates/content/actions/create-and-link-notion-page.ts +2 -0
- package/corpus/templates/content/actions/link-notion-page.ts +2 -0
- package/corpus/templates/content/actions/pull-document.ts +5 -7
- package/corpus/templates/content/actions/pull-notion-page.ts +2 -0
- package/corpus/templates/content/actions/push-notion-page.ts +10 -0
- package/corpus/templates/content/actions/resolve-notion-sync-conflict.ts +2 -0
- package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +88 -23
- package/corpus/templates/content/app/components/editor/VisualEditor.tsx +32 -2
- package/corpus/templates/content/app/hooks/use-notion.ts +6 -6
- package/corpus/templates/content/app/i18n/zh-TW.ts +1 -0
- package/corpus/templates/content/app/i18n-data.ts +18 -0
- package/corpus/templates/content/changelog/2026-07-09-notion-conflict-choices-and-version-restores-preserve-docume.md +6 -0
- package/corpus/templates/content/server/lib/notion-sync.ts +80 -64
- package/corpus/templates/design/.agents/skills/design-generation/SKILL.md +29 -2
- package/corpus/templates/design/.agents/skills/design-systems/SKILL.md +86 -0
- package/corpus/templates/design/.agents/skills/export-handoff/SKILL.md +57 -0
- package/corpus/templates/design/.agents/skills/visual-edit/SKILL.md +11 -0
- package/corpus/templates/design/AGENTS.md +45 -10
- package/corpus/templates/design/actions/add-localhost-screens.ts +66 -13
- package/corpus/templates/design/actions/apply-component-prop-edit.ts +45 -36
- package/corpus/templates/design/actions/apply-motion-edit.ts +25 -31
- package/corpus/templates/design/actions/apply-shader-fill.ts +29 -42
- package/corpus/templates/design/actions/detach-component-instance.ts +337 -0
- package/corpus/templates/design/actions/export-design-as-figma-svg.ts +219 -0
- package/corpus/templates/design/actions/generate-design.ts +79 -12
- package/corpus/templates/design/actions/generate-screens.ts +57 -12
- package/corpus/templates/design/actions/get-figma-styles.ts +115 -0
- package/corpus/templates/design/actions/go-to-main-component.ts +254 -0
- package/corpus/templates/design/actions/grant-localhost-write-consent.ts +4 -3
- package/corpus/templates/design/actions/import-design-source.ts +4 -31
- package/corpus/templates/design/actions/import-figma-clipboard.ts +134 -0
- package/corpus/templates/design/actions/import-figma-frame.ts +95 -0
- package/corpus/templates/design/actions/index-design-system-with-builder.ts +16 -1
- package/corpus/templates/design/actions/list-design-components.ts +104 -0
- package/corpus/templates/design/actions/list-figma-library-assets.ts +1 -24
- package/corpus/templates/design/actions/open-component-source.ts +2 -4
- package/corpus/templates/design/actions/present-design-variants.ts +412 -16
- package/corpus/templates/design/actions/preview-source-edit.ts +14 -1
- package/corpus/templates/design/actions/run-design-audit.ts +25 -3
- package/corpus/templates/design/actions/show-design-questions.ts +10 -0
- package/corpus/templates/design/actions/swap-component-instance.ts +544 -0
- package/corpus/templates/design/actions/take-design-screenshot.ts +45 -60
- package/corpus/templates/design/actions/write-local-file.ts +25 -1
- package/corpus/templates/design/app/components/design/CanvasContextMenu.tsx +152 -0
- package/corpus/templates/design/app/components/design/DesignCanvas.tsx +1077 -58
- package/corpus/templates/design/app/components/design/DesignEditorSkeleton.tsx +5 -5
- package/corpus/templates/design/app/components/design/DesignExtensionsPanel.tsx +73 -21
- package/corpus/templates/design/app/components/design/EditPanel.tsx +16 -11
- package/corpus/templates/design/app/components/design/KeyboardShortcutsPanel.tsx +364 -0
- package/corpus/templates/design/app/components/design/LayersPanel.tsx +180 -53
- package/corpus/templates/design/app/components/design/LocalhostWriteConsentDialog.tsx +0 -2
- package/corpus/templates/design/app/components/design/MotionDock.tsx +144 -21
- package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +1152 -167
- package/corpus/templates/design/app/components/design/QuestionFlow.tsx +27 -5
- package/corpus/templates/design/app/components/design/ReviewPanel.tsx +23 -2
- package/corpus/templates/design/app/components/design/TokensPanel.tsx +63 -49
- package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +1293 -54
- package/corpus/templates/design/app/components/design/bridge/embedded-wheel.bridge.ts +7 -0
- package/corpus/templates/design/app/components/design/bridge/hit-test.bridge.ts +6 -1
- package/corpus/templates/design/app/components/design/bridge/motion-preview.bridge.ts +23 -6
- package/corpus/templates/design/app/components/design/bridge/sample.bridge.ts +7 -0
- package/corpus/templates/design/app/components/design/code-workbench/CodeWorkbench.tsx +5 -0
- package/corpus/templates/design/app/components/design/code-workbench/editor/MonacoHost.tsx +14 -2
- package/corpus/templates/design/app/components/design/code-workbench/explorer/FileTree.tsx +37 -2
- package/corpus/templates/design/app/components/design/code-workbench/format/format-on-open-guard.ts +25 -0
- package/corpus/templates/design/app/components/design/code-workbench/format/format-on-open.ts +19 -1
- package/corpus/templates/design/app/components/design/code-workbench/model-registry.ts +43 -5
- package/corpus/templates/design/app/components/design/code-workbench/search/SearchView.tsx +27 -2
- package/corpus/templates/design/app/components/design/code-workbench/search/search-engine.ts +10 -1
- package/corpus/templates/design/app/components/design/code-workbench/store.tsx +19 -2
- package/corpus/templates/design/app/components/design/design-canvas/annotation-snapshot.ts +265 -0
- package/corpus/templates/design/app/components/design/design-canvas/annotation-submit.ts +16 -5
- package/corpus/templates/design/app/components/design/design-canvas/coordinate-transforms.ts +16 -2
- package/corpus/templates/design/app/components/design/design-canvas/embedded-frame.ts +7 -2
- package/corpus/templates/design/app/components/design/design-canvas/iframe-events.ts +3 -1
- package/corpus/templates/design/app/components/design/edit-panel/code-inspect-helpers.tsx +78 -8
- package/corpus/templates/design/app/components/design/edit-panel/component-section.tsx +451 -73
- package/corpus/templates/design/app/components/design/edit-panel/document-colors.ts +12 -4
- package/corpus/templates/design/app/components/design/edit-panel/effects-properties.tsx +398 -272
- package/corpus/templates/design/app/components/design/edit-panel/element-classification.ts +41 -7
- package/corpus/templates/design/app/components/design/edit-panel/element-identity.ts +16 -3
- package/corpus/templates/design/app/components/design/edit-panel/field-primitives.tsx +24 -4
- package/corpus/templates/design/app/components/design/edit-panel/fill-gradient-helpers.ts +222 -4
- package/corpus/templates/design/app/components/design/edit-panel/fill-properties.tsx +171 -78
- package/corpus/templates/design/app/components/design/edit-panel/inspector-controls.tsx +45 -5
- package/corpus/templates/design/app/components/design/edit-panel/layout-properties.tsx +299 -28
- package/corpus/templates/design/app/components/design/edit-panel/panel-primitives.tsx +120 -150
- package/corpus/templates/design/app/components/design/edit-panel/position-helpers.ts +53 -1
- package/corpus/templates/design/app/components/design/edit-panel/position-layout-properties.tsx +284 -117
- package/corpus/templates/design/app/components/design/edit-panel/selection-helpers.ts +81 -0
- package/corpus/templates/design/app/components/design/edit-panel/stroke-properties.tsx +19 -14
- package/corpus/templates/design/app/components/design/edit-panel/style-options.ts +7 -0
- package/corpus/templates/design/app/components/design/edit-panel/typography-helpers.ts +155 -0
- package/corpus/templates/design/app/components/design/edit-panel/typography-properties.tsx +268 -46
- package/corpus/templates/design/app/components/design/inspector/AutoLayoutMatrix.tsx +350 -31
- package/corpus/templates/design/app/components/design/inspector/ConstraintsWidget.tsx +13 -4
- package/corpus/templates/design/app/components/design/inspector/DesignColorPicker.tsx +233 -23
- package/corpus/templates/design/app/components/design/inspector/GradientEditor.tsx +72 -2
- package/corpus/templates/design/app/components/design/inspector/ScrubInput.tsx +51 -11
- package/corpus/templates/design/app/components/design/inspector/ShaderFillsPanel.tsx +84 -11
- package/corpus/templates/design/app/components/design/inspector/index.ts +3 -0
- package/corpus/templates/design/app/components/design/keyboard-shortcuts.ts +552 -0
- package/corpus/templates/design/app/components/design/multi-screen/board-surface-html.ts +157 -0
- package/corpus/templates/design/app/components/design/multi-screen/frame-geometry.ts +53 -0
- package/corpus/templates/design/app/components/design/multi-screen/overview-layout.ts +184 -0
- package/corpus/templates/design/app/components/design/multi-screen/primitive-drop-target.ts +403 -26
- package/corpus/templates/design/app/components/design/multi-screen/types.ts +20 -1
- package/corpus/templates/design/app/components/design/types.ts +33 -0
- package/corpus/templates/design/app/components/editor/PromptDialog.tsx +59 -7
- package/corpus/templates/design/app/components/visual-editor/CanvasCommentPins.tsx +119 -17
- package/corpus/templates/design/app/components/visual-editor/DrawOverlay.tsx +275 -63
- package/corpus/templates/design/app/hooks/use-question-flow.ts +14 -3
- package/corpus/templates/design/app/hooks/useDesignHotkeys.ts +67 -10
- package/corpus/templates/design/app/i18n/zh-TW.ts +91 -1
- package/corpus/templates/design/app/i18n-data.ts +1217 -10
- package/corpus/templates/design/app/i18n-keyboard-shortcuts.ts +577 -0
- package/corpus/templates/design/app/lib/agent-chat.ts +23 -0
- package/corpus/templates/design/app/lib/design-import.ts +24 -0
- package/corpus/templates/design/app/lib/figma-clipboard.ts +157 -0
- package/corpus/templates/design/app/lib/figma-svg-copy.ts +221 -0
- package/corpus/templates/design/app/pages/design-editor/code-layer-state.ts +143 -30
- package/corpus/templates/design/app/pages/design-editor/collab-sync.ts +15 -0
- package/corpus/templates/design/app/pages/design-editor/editor-session.ts +33 -0
- package/corpus/templates/design/app/pages/design-editor/editor-state.ts +38 -0
- package/corpus/templates/design/app/pages/design-editor/overview-annotation-context.ts +157 -0
- package/corpus/templates/design/app/pages/design-editor/overview-camera.ts +62 -6
- package/corpus/templates/design/app/pages/design-editor/pending-edits.ts +245 -0
- package/corpus/templates/design/app/pages/design-editor/react-semantic-handoff.ts +586 -0
- package/corpus/templates/design/app/pages/design-editor/screen-auto-layout.ts +169 -0
- package/corpus/templates/design/app/pages/design-editor/selection-state.ts +201 -1
- package/corpus/templates/design/changelog/2026-07-09-added-underline-strikethrough-and-text-case-uppercase-lowerc.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-annotate-now-draws-across-the-all-screens-canvas-without-swi.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-auto-layout-children-can-now-be-dragged-into-freeform-screen.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-board-shapes-now-stay-visible-and-nest-correctly-across-the-.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-component-source-navigation-now-opens-reliably-from-the-insp.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-cross-screen-local-react-layer-moves-now-hand-off-exact-sour.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-cross-screen-moves-undo-and-redo-now-keep-both-screens-stabl.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-detach-a-component-instance-into-plain-editable-eleme.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-dragging-layers-into-auto-layout-now-respects-ignore-auto-la.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-drawing-annotations-sent-to-the-agent-now-include-a-rendered.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-editor-sidebars-now-use-figma-matched-default-widths-for-a-r.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-empty-frames-and-rectangles-now-expose-full-auto-layout-cont.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-export-any-design-screen-as-a-real-vector-svg-for-figma-with.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-grid-auto-layout-now-uses-true-responsive-rows-and-columns-w.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-hidden-and-locked-screens-now-behave-consistently-between-la.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-image-fills-now-preserve-valid-urls-and-layered-fit-settings.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-import-figma-frames-as-pixel-accurate-design-screens-via-a-s.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-inspect-code-now-hides-internal-styling-metadata-and-wraps-l.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-inspector-blur-and-shadow-effects-now-preserve-other-effects.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-jump-to-a-components-other-instances-from-the-inspecto.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-keyboard-shortcuts-are-now-discoverable-in-a-figma-style-bot.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-layer-selection-now-keeps-cmd-or-ctrl-toggles-and-shift-rang.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-layers-dropped-into-auto-layout-now-participate-correctly-in.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-local-live-edit-screens-now-enter-and-leave-full-view-withou.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-local-preview-urls-now-live-in-the-inspector-instead-of-taki.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-local-react-layers-can-now-be-hidden-and-locked-from-layers-.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-local-react-layers-now-keep-exact-source-provenance-and-supp.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-local-react-visual-editing-now-loads-reliably-and-shows-live.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-paste-from-figma-cmd-v-now-imports-exact-editable-nodes-when.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-pressing-escape-after-selecting-a-screen-now-reveals-page-pr.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-right-click-overlapping-or-nested-objects-to-choose-the-exac.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-scale-edge-stretch-and-center-constraints-now-preserve-layer.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-screen-rows-can-now-be-reordered-in-layers-with-matching-can.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-screen-selection-stays-responsive-in-designs-with-hundreds-o.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-shift-a-now-enables-auto-layout-on-a-selected-screen-with-fl.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-swap-a-component-instance-for-another-from-the-libra.md +6 -0
- package/corpus/templates/design/changelog/2026-07-10-drawing-annotations-now-stay-available-to-retry-when-the.md +6 -0
- package/corpus/templates/design/e2e/global-setup.ts +3 -6
- package/corpus/templates/design/e2e/helpers.ts +27 -6
- package/corpus/templates/design/server/lib/design-to-figma-svg.ts +1412 -0
- package/corpus/templates/design/server/lib/figma-clipboard-match.ts +196 -0
- package/corpus/templates/design/server/lib/figma-node-import.ts +311 -0
- package/corpus/templates/design/server/lib/figma-node-to-html.ts +1334 -0
- package/corpus/templates/design/server/lib/figma-paste-fallback.ts +64 -0
- package/corpus/templates/design/server/lib/playwright-runtime.ts +78 -0
- package/corpus/templates/design/server/plugins/db.ts +41 -0
- package/corpus/templates/design/server/source-workspace.ts +64 -0
- package/corpus/templates/design/shared/board-file.ts +13 -2
- package/corpus/templates/design/shared/canvas-math.ts +231 -33
- package/corpus/templates/design/shared/code-layer.ts +122 -8
- package/corpus/templates/design/shared/component-library.ts +161 -0
- package/corpus/templates/design/shared/db-conflict.ts +27 -0
- package/corpus/templates/design/shared/design-source-capabilities.ts +2 -1
- package/corpus/templates/design/shared/figma-url.ts +149 -0
- package/corpus/templates/design/shared/pen-path.ts +16 -7
- package/corpus/templates/design/shared/source-mode.ts +1 -0
- package/corpus/templates/plan/app/components/plan/CanvasArea.tsx +7 -19
- package/corpus/templates/plan/changelog/2026-07-09-canvas-panning-now-keeps-zoom-fixed-even-at-the-edges.md +6 -0
- package/corpus/templates/slides/actions/index-design-system-with-builder.ts +16 -1
- package/corpus/templates/slides/app/context/DeckContext.tsx +311 -70
- package/corpus/templates/slides/changelog/2026-07-09-fixed-the-slide-rail-going-permanently-stale-during-a-long-a.md +6 -0
- package/dist/agent/production-agent.d.ts +79 -6
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +161 -14
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/agent/run-manager.d.ts.map +1 -1
- package/dist/agent/run-manager.js +29 -5
- package/dist/agent/run-manager.js.map +1 -1
- package/dist/agent/run-store.d.ts +46 -0
- package/dist/agent/run-store.d.ts.map +1 -1
- package/dist/agent/run-store.js +61 -0
- package/dist/agent/run-store.js.map +1 -1
- package/dist/cli/design-connect.d.ts +6 -0
- package/dist/cli/design-connect.d.ts.map +1 -1
- package/dist/cli/design-connect.js +336 -139
- package/dist/cli/design-connect.js.map +1 -1
- package/dist/cli/skills.d.ts.map +1 -1
- package/dist/cli/skills.js +14 -0
- package/dist/cli/skills.js.map +1 -1
- package/dist/client/AssistantChat.d.ts +2 -0
- package/dist/client/AssistantChat.d.ts.map +1 -1
- package/dist/client/AssistantChat.js +19 -3
- package/dist/client/AssistantChat.js.map +1 -1
- package/dist/client/MultiTabAssistantChat.d.ts.map +1 -1
- package/dist/client/MultiTabAssistantChat.js +38 -18
- package/dist/client/MultiTabAssistantChat.js.map +1 -1
- package/dist/client/agent-chat.d.ts +60 -0
- package/dist/client/agent-chat.d.ts.map +1 -1
- package/dist/client/agent-chat.js +122 -1
- package/dist/client/agent-chat.js.map +1 -1
- package/dist/client/chat/index.d.ts +1 -1
- package/dist/client/chat/index.d.ts.map +1 -1
- package/dist/client/chat/index.js +1 -1
- package/dist/client/chat/index.js.map +1 -1
- package/dist/client/extensions/EmbeddedExtension.d.ts +12 -0
- package/dist/client/extensions/EmbeddedExtension.d.ts.map +1 -1
- package/dist/client/extensions/EmbeddedExtension.js +36 -2
- package/dist/client/extensions/EmbeddedExtension.js.map +1 -1
- package/dist/client/index.d.ts +1 -1
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +1 -1
- package/dist/client/index.js.map +1 -1
- package/dist/client/rich-markdown-editor/useCollabReconcile.d.ts.map +1 -1
- package/dist/client/rich-markdown-editor/useCollabReconcile.js +18 -6
- package/dist/client/rich-markdown-editor/useCollabReconcile.js.map +1 -1
- package/dist/collab/awareness-store.d.ts +5 -0
- package/dist/collab/awareness-store.d.ts.map +1 -1
- package/dist/collab/awareness-store.js +19 -12
- package/dist/collab/awareness-store.js.map +1 -1
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/awareness.d.ts.map +1 -1
- package/dist/collab/client.d.ts.map +1 -1
- package/dist/collab/client.js +13 -5
- package/dist/collab/client.js.map +1 -1
- package/dist/collab/index.d.ts +1 -0
- package/dist/collab/index.d.ts.map +1 -1
- package/dist/collab/index.js +1 -0
- package/dist/collab/index.js.map +1 -1
- package/dist/collab/recent-edits.d.ts +3 -1
- package/dist/collab/recent-edits.d.ts.map +1 -1
- package/dist/collab/recent-edits.js +57 -2
- package/dist/collab/recent-edits.js.map +1 -1
- package/dist/collab/routes.d.ts +1 -1
- package/dist/deploy/build.d.ts +24 -0
- package/dist/deploy/build.d.ts.map +1 -1
- package/dist/deploy/build.js +102 -5
- package/dist/deploy/build.js.map +1 -1
- package/dist/index.browser.d.ts +1 -1
- package/dist/index.browser.d.ts.map +1 -1
- package/dist/index.browser.js +1 -1
- package/dist/index.browser.js.map +1 -1
- package/dist/notifications/routes.d.ts +1 -1
- package/dist/observability/routes.d.ts +5 -5
- package/dist/progress/routes.d.ts +1 -1
- package/dist/server/agent-chat-plugin.d.ts.map +1 -1
- package/dist/server/agent-chat-plugin.js +87 -10
- package/dist/server/agent-chat-plugin.js.map +1 -1
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/builder-design-systems.d.ts +15 -0
- package/dist/server/builder-design-systems.d.ts.map +1 -1
- package/dist/server/builder-design-systems.js +9 -4
- package/dist/server/builder-design-systems.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,62 +1,177 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
|
|
1
3
|
import {
|
|
2
4
|
appStateDelete,
|
|
3
5
|
appStateGet,
|
|
4
6
|
appStatePut,
|
|
5
7
|
} from "@agent-native/core/application-state";
|
|
6
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
AGENT_CLIENT_ID,
|
|
10
|
+
hasCollabState,
|
|
11
|
+
loadAwarenessRowsStrict,
|
|
12
|
+
} from "@agent-native/core/collab";
|
|
7
13
|
import { getRequestUserEmail } from "@agent-native/core/server/request-context";
|
|
8
14
|
|
|
9
15
|
const FLUSH_POLL_INTERVAL_MS = 200;
|
|
10
16
|
const FLUSH_TIMEOUT_MS = 4000;
|
|
11
17
|
|
|
18
|
+
function parseAwarenessState(state: string): {
|
|
19
|
+
canFlushDocument?: unknown;
|
|
20
|
+
visible?: boolean;
|
|
21
|
+
user?: { email?: unknown };
|
|
22
|
+
} | null {
|
|
23
|
+
try {
|
|
24
|
+
return JSON.parse(state) as {
|
|
25
|
+
canFlushDocument?: unknown;
|
|
26
|
+
visible?: boolean;
|
|
27
|
+
user?: { email?: unknown };
|
|
28
|
+
};
|
|
29
|
+
} catch {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function awarenessFlushCandidate(entry: {
|
|
35
|
+
clientId: number;
|
|
36
|
+
state: string;
|
|
37
|
+
}): { sessionEmail: string | null; required: boolean } | null {
|
|
38
|
+
if (entry.clientId === AGENT_CLIENT_ID) return null;
|
|
39
|
+
const state = parseAwarenessState(entry.state);
|
|
40
|
+
if (!state || state.visible === false || !state.user) return null;
|
|
41
|
+
// New clients publish an exact boolean. `false` is a known read-only viewer
|
|
42
|
+
// and must never block. A missing field is a pre-deploy client which may be
|
|
43
|
+
// an editor, so offer it the old handshake on a best-effort basis. Invalid
|
|
44
|
+
// values are neither a trustworthy editor capability nor a legacy omission.
|
|
45
|
+
if (state.canFlushDocument !== true && state.canFlushDocument !== undefined) {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
const email = state.user.email;
|
|
49
|
+
return {
|
|
50
|
+
sessionEmail:
|
|
51
|
+
typeof email === "string" && email.trim() ? email.trim() : null,
|
|
52
|
+
required: state.canFlushDocument === true,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
12
56
|
export async function flushOpenDocumentEditorToSql(args: {
|
|
13
57
|
documentId: string;
|
|
14
58
|
ownerEmail?: string | null;
|
|
15
59
|
}) {
|
|
16
60
|
// If a live Yjs collab session is open, the in-memory editor doc is fresher
|
|
17
61
|
// than the SQL column. Ask the open editor to serialize + save, then wait
|
|
18
|
-
// for
|
|
62
|
+
// for an explicit request-id-matched acknowledgement.
|
|
19
63
|
if (!(await hasCollabState(args.documentId))) return;
|
|
20
64
|
|
|
65
|
+
// Persisted Yjs state outlives browser tabs. Modern clients distinguish
|
|
66
|
+
// editors (`true`) from viewers (`false`), so only the former are a hard
|
|
67
|
+
// freshness barrier. Pre-deploy tabs omit the field; they still know how to
|
|
68
|
+
// service this request, but may also be legacy viewers, so offer them the
|
|
69
|
+
// bounded handshake without failing if they stay silent. This preserves live
|
|
70
|
+
// legacy editor changes without making viewer-only tabs time out sync actions.
|
|
71
|
+
const awarenessRows = await loadAwarenessRowsStrict(args.documentId);
|
|
72
|
+
const flushCandidates = awarenessRows
|
|
73
|
+
.map(awarenessFlushCandidate)
|
|
74
|
+
.filter((candidate): candidate is NonNullable<typeof candidate> => {
|
|
75
|
+
return candidate !== null;
|
|
76
|
+
});
|
|
77
|
+
if (flushCandidates.length === 0) return;
|
|
78
|
+
const acknowledgementRequired = flushCandidates.some(
|
|
79
|
+
(candidate) => candidate.required,
|
|
80
|
+
);
|
|
81
|
+
const activeSessionEmails = flushCandidates
|
|
82
|
+
.map((candidate) => candidate.sessionEmail)
|
|
83
|
+
.filter((email): email is string => !!email);
|
|
84
|
+
|
|
21
85
|
const flushKey = `flush-request-${args.documentId}`;
|
|
22
86
|
// The editor polls `flush-request-<id>` via the framework app-state route,
|
|
23
|
-
// which scopes reads to the logged-in browser user
|
|
24
|
-
//
|
|
25
|
-
//
|
|
87
|
+
// which scopes reads to the logged-in browser user. Target every active
|
|
88
|
+
// collaborator email plus owner/caller fallbacks so shared editors and
|
|
89
|
+
// cross-instance actions reach the tab that can serialize the live Y.Doc.
|
|
26
90
|
const callerEmail = getRequestUserEmail() || undefined;
|
|
27
91
|
const targetSessions = Array.from(
|
|
28
92
|
new Set(
|
|
29
|
-
[
|
|
30
|
-
|
|
31
|
-
|
|
93
|
+
[
|
|
94
|
+
...activeSessionEmails,
|
|
95
|
+
args.ownerEmail ?? undefined,
|
|
96
|
+
callerEmail,
|
|
97
|
+
].filter((s): s is string => typeof s === "string" && s.length > 0),
|
|
32
98
|
),
|
|
33
99
|
);
|
|
34
|
-
if (targetSessions.length === 0)
|
|
100
|
+
if (targetSessions.length === 0) {
|
|
101
|
+
if (!acknowledgementRequired) return;
|
|
102
|
+
throw new Error("Could not identify the open document editor to flush.");
|
|
103
|
+
}
|
|
35
104
|
|
|
36
|
-
const
|
|
37
|
-
|
|
105
|
+
const requestId = randomUUID();
|
|
106
|
+
const flushValue = {
|
|
107
|
+
id: args.documentId,
|
|
108
|
+
ts: Date.now(),
|
|
109
|
+
requestId,
|
|
110
|
+
status: "pending",
|
|
111
|
+
};
|
|
112
|
+
const writes = await Promise.allSettled(
|
|
38
113
|
targetSessions.map((session) =>
|
|
39
114
|
appStatePut(session, flushKey, flushValue, {
|
|
40
115
|
requestSource: "agent",
|
|
41
|
-
})
|
|
116
|
+
}),
|
|
42
117
|
),
|
|
43
118
|
);
|
|
119
|
+
const writtenSessions = targetSessions.filter(
|
|
120
|
+
(_session, index) => writes[index]?.status === "fulfilled",
|
|
121
|
+
);
|
|
122
|
+
if (writtenSessions.length === 0) {
|
|
123
|
+
if (!acknowledgementRequired) return;
|
|
124
|
+
throw new Error("Could not ask the open document editor to save.");
|
|
125
|
+
}
|
|
44
126
|
|
|
45
127
|
const deadline = Date.now() + FLUSH_TIMEOUT_MS;
|
|
128
|
+
let flushError: string | null = null;
|
|
129
|
+
let acknowledged = false;
|
|
46
130
|
while (Date.now() < deadline) {
|
|
47
131
|
await new Promise((resolve) => setTimeout(resolve, FLUSH_POLL_INTERVAL_MS));
|
|
48
|
-
const
|
|
49
|
-
|
|
132
|
+
const reads = await Promise.allSettled(
|
|
133
|
+
writtenSessions.map((session) => appStateGet(session, flushKey)),
|
|
134
|
+
);
|
|
135
|
+
const responses = reads.flatMap((result) =>
|
|
136
|
+
result.status === "fulfilled" && result.value ? [result.value] : [],
|
|
137
|
+
);
|
|
138
|
+
const failed = responses.find(
|
|
139
|
+
(
|
|
140
|
+
value,
|
|
141
|
+
): value is {
|
|
142
|
+
requestId: string;
|
|
143
|
+
status: "error";
|
|
144
|
+
error?: string;
|
|
145
|
+
} => value.requestId === requestId && value.status === "error",
|
|
146
|
+
);
|
|
147
|
+
if (failed) {
|
|
148
|
+
flushError =
|
|
149
|
+
typeof failed.error === "string" && failed.error.trim()
|
|
150
|
+
? failed.error
|
|
151
|
+
: "The live document could not be saved before syncing.";
|
|
152
|
+
break;
|
|
153
|
+
}
|
|
154
|
+
acknowledged = responses.some(
|
|
155
|
+
(value) => value.requestId === requestId && value.status === "success",
|
|
50
156
|
);
|
|
51
|
-
if (
|
|
157
|
+
if (acknowledged) break;
|
|
52
158
|
}
|
|
53
159
|
|
|
54
|
-
// Best-effort cleanup
|
|
160
|
+
// Best-effort cleanup after success, explicit failure, or timeout.
|
|
55
161
|
await Promise.all(
|
|
56
|
-
|
|
162
|
+
writtenSessions.map((session) =>
|
|
57
163
|
appStateDelete(session, flushKey, { requestSource: "agent" }).catch(
|
|
58
164
|
() => {},
|
|
59
165
|
),
|
|
60
166
|
),
|
|
61
167
|
);
|
|
168
|
+
|
|
169
|
+
if (flushError) {
|
|
170
|
+
throw new Error(flushError);
|
|
171
|
+
}
|
|
172
|
+
if (!acknowledged && acknowledgementRequired) {
|
|
173
|
+
throw new Error(
|
|
174
|
+
"The open document editor did not finish saving before sync timed out.",
|
|
175
|
+
);
|
|
176
|
+
}
|
|
62
177
|
}
|
|
@@ -4,6 +4,8 @@ import {
|
|
|
4
4
|
} from "@agent-native/core/server";
|
|
5
5
|
import { assertAccess } from "@agent-native/core/sharing";
|
|
6
6
|
|
|
7
|
+
import { flushOpenDocumentEditorToSql } from "./_document-flush.js";
|
|
8
|
+
|
|
7
9
|
export function getCurrentNotionOwner() {
|
|
8
10
|
const owner = getRequestUserEmail();
|
|
9
11
|
if (!owner) throw new Error("no authenticated user");
|
|
@@ -23,6 +25,19 @@ export async function getNotionDocumentOwner(documentId: string) {
|
|
|
23
25
|
return owner;
|
|
24
26
|
}
|
|
25
27
|
|
|
28
|
+
/**
|
|
29
|
+
* Flush the live collaborative editor before a user-triggered Notion operation
|
|
30
|
+
* reads or replaces SQL content. The Y.Doc can be ahead of the debounced
|
|
31
|
+
* documents row; without this handshake "Use local" can push a stale snapshot,
|
|
32
|
+
* while "Use Notion" can discard edits that never reached version history.
|
|
33
|
+
*/
|
|
34
|
+
export async function flushNotionDocumentEditor(
|
|
35
|
+
documentId: string,
|
|
36
|
+
ownerEmail: string,
|
|
37
|
+
) {
|
|
38
|
+
await flushOpenDocumentEditorToSql({ documentId, ownerEmail });
|
|
39
|
+
}
|
|
40
|
+
|
|
26
41
|
export function resolveDocumentId(args: { documentId?: string; id?: string }) {
|
|
27
42
|
const documentId = args.documentId?.trim() || args.id?.trim();
|
|
28
43
|
if (!documentId) {
|
|
@@ -3,6 +3,7 @@ import { z } from "zod";
|
|
|
3
3
|
|
|
4
4
|
import { createAndLinkNotionPage } from "../server/lib/notion-sync.js";
|
|
5
5
|
import {
|
|
6
|
+
flushNotionDocumentEditor,
|
|
6
7
|
getNotionDocumentOwner,
|
|
7
8
|
resolveDocumentId,
|
|
8
9
|
} from "./_notion-action-utils.js";
|
|
@@ -18,6 +19,7 @@ export default defineAction({
|
|
|
18
19
|
run: async (args) => {
|
|
19
20
|
const documentId = resolveDocumentId(args);
|
|
20
21
|
const owner = await getNotionDocumentOwner(documentId);
|
|
22
|
+
await flushNotionDocumentEditor(documentId, owner);
|
|
21
23
|
return createAndLinkNotionPage(owner, documentId, args.parentPageIdOrUrl);
|
|
22
24
|
},
|
|
23
25
|
});
|
|
@@ -3,6 +3,7 @@ import { z } from "zod";
|
|
|
3
3
|
|
|
4
4
|
import { linkDocumentToNotionPage } from "../server/lib/notion-sync.js";
|
|
5
5
|
import {
|
|
6
|
+
flushNotionDocumentEditor,
|
|
6
7
|
getNotionDocumentOwner,
|
|
7
8
|
resolveDocumentId,
|
|
8
9
|
} from "./_notion-action-utils.js";
|
|
@@ -26,6 +27,7 @@ export default defineAction({
|
|
|
26
27
|
}
|
|
27
28
|
|
|
28
29
|
const owner = await getNotionDocumentOwner(documentId);
|
|
30
|
+
await flushNotionDocumentEditor(documentId, owner);
|
|
29
31
|
return linkDocumentToNotionPage(owner, documentId, pageIdOrUrl);
|
|
30
32
|
},
|
|
31
33
|
});
|
|
@@ -29,12 +29,10 @@ import {
|
|
|
29
29
|
* never saw the key, so every external `pull-document` waited the full
|
|
30
30
|
* timeout and returned stale DB content.
|
|
31
31
|
* 2. The editor polls that key, serializes its current Y.Doc to markdown
|
|
32
|
-
* through its existing serializer, calls `update-document`, then
|
|
33
|
-
*
|
|
34
|
-
* 3. We poll
|
|
35
|
-
*
|
|
36
|
-
* clears (no editor actually open), we fall back to the DB column, which
|
|
37
|
-
* is the best available snapshot.
|
|
32
|
+
* through its existing serializer, calls `update-document`, then writes an
|
|
33
|
+
* explicit success/error acknowledgement for that request id.
|
|
34
|
+
* 3. We poll every active collaborator session for the acknowledgement, fail
|
|
35
|
+
* closed on editor errors/timeouts, and then read the now-fresh row.
|
|
38
36
|
*
|
|
39
37
|
* When there is no live collab session the DB column is authoritative and we
|
|
40
38
|
* skip the handshake entirely. The helper bounds the wait so stale collab
|
|
@@ -90,7 +88,7 @@ export default defineAction({
|
|
|
90
88
|
|
|
91
89
|
// If a live Yjs collab session is open, the in-memory editor doc is fresher
|
|
92
90
|
// than the SQL column. Ask the open editor to serialize + save, then wait
|
|
93
|
-
// for
|
|
91
|
+
// for its explicit request-id-matched acknowledgement.
|
|
94
92
|
await flushOpenDocumentEditorToSql({
|
|
95
93
|
documentId: id,
|
|
96
94
|
ownerEmail: (access.resource.ownerEmail as string | undefined) || null,
|
|
@@ -3,6 +3,7 @@ import { z } from "zod";
|
|
|
3
3
|
|
|
4
4
|
import { pullDocumentFromNotion } from "../server/lib/notion-sync.js";
|
|
5
5
|
import {
|
|
6
|
+
flushNotionDocumentEditor,
|
|
6
7
|
getNotionDocumentOwner,
|
|
7
8
|
resolveDocumentId,
|
|
8
9
|
} from "./_notion-action-utils.js";
|
|
@@ -17,6 +18,7 @@ export default defineAction({
|
|
|
17
18
|
run: async (args) => {
|
|
18
19
|
const documentId = resolveDocumentId(args);
|
|
19
20
|
const owner = await getNotionDocumentOwner(documentId);
|
|
21
|
+
await flushNotionDocumentEditor(documentId, owner);
|
|
20
22
|
return pullDocumentFromNotion(owner, documentId, true);
|
|
21
23
|
},
|
|
22
24
|
});
|
|
@@ -3,6 +3,7 @@ import { z } from "zod";
|
|
|
3
3
|
|
|
4
4
|
import { pushDocumentToNotion } from "../server/lib/notion-sync.js";
|
|
5
5
|
import {
|
|
6
|
+
flushNotionDocumentEditor,
|
|
6
7
|
getNotionDocumentOwner,
|
|
7
8
|
resolveDocumentId,
|
|
8
9
|
} from "./_notion-action-utils.js";
|
|
@@ -12,11 +13,20 @@ export default defineAction({
|
|
|
12
13
|
schema: z.object({
|
|
13
14
|
documentId: z.string().optional().describe("Document ID (required)"),
|
|
14
15
|
id: z.string().optional().describe("Alias for --documentId"),
|
|
16
|
+
flushOpenEditor: z
|
|
17
|
+
.boolean()
|
|
18
|
+
.default(true)
|
|
19
|
+
.describe(
|
|
20
|
+
"Flush an open collaborative editor before pushing (disable only when the caller just persisted the exact editor content)",
|
|
21
|
+
),
|
|
15
22
|
}),
|
|
16
23
|
http: { method: "POST" },
|
|
17
24
|
run: async (args) => {
|
|
18
25
|
const documentId = resolveDocumentId(args);
|
|
19
26
|
const owner = await getNotionDocumentOwner(documentId);
|
|
27
|
+
if (args.flushOpenEditor) {
|
|
28
|
+
await flushNotionDocumentEditor(documentId, owner);
|
|
29
|
+
}
|
|
20
30
|
return pushDocumentToNotion(owner, documentId);
|
|
21
31
|
},
|
|
22
32
|
});
|
|
@@ -3,6 +3,7 @@ import { z } from "zod";
|
|
|
3
3
|
|
|
4
4
|
import { resolveDocumentSyncConflict } from "../server/lib/notion-sync.js";
|
|
5
5
|
import {
|
|
6
|
+
flushNotionDocumentEditor,
|
|
6
7
|
getNotionDocumentOwner,
|
|
7
8
|
resolveDocumentId,
|
|
8
9
|
} from "./_notion-action-utils.js";
|
|
@@ -18,6 +19,7 @@ export default defineAction({
|
|
|
18
19
|
run: async (args) => {
|
|
19
20
|
const documentId = resolveDocumentId(args);
|
|
20
21
|
const owner = await getNotionDocumentOwner(documentId);
|
|
22
|
+
await flushNotionDocumentEditor(documentId, owner);
|
|
21
23
|
return resolveDocumentSyncConflict(owner, documentId, args.direction);
|
|
22
24
|
},
|
|
23
25
|
});
|
|
@@ -477,6 +477,18 @@ function DocumentEditorBody({ documentId, document }: DocumentEditorBodyProps) {
|
|
|
477
477
|
canEdit && !bodyHydrationPending && (isLocalFileDocument || !collabLoading);
|
|
478
478
|
canEditRef.current = editorCanEdit;
|
|
479
479
|
|
|
480
|
+
// Viewers intentionally join awareness so they receive live cursors, but
|
|
481
|
+
// only an editor runs the app-state flush poller below. Publish that exact
|
|
482
|
+
// capability so server-side pull/push/conflict actions do not wait on a
|
|
483
|
+
// read-only tab that can never acknowledge their request.
|
|
484
|
+
useEffect(() => {
|
|
485
|
+
if (!awareness || !collabEnabled) return;
|
|
486
|
+
awareness.setLocalStateField("canFlushDocument", editorCanEdit);
|
|
487
|
+
return () => {
|
|
488
|
+
awareness.setLocalStateField("canFlushDocument", false);
|
|
489
|
+
};
|
|
490
|
+
}, [awareness, collabEnabled, editorCanEdit]);
|
|
491
|
+
|
|
480
492
|
// Initialize from fetched document, reset on document switch
|
|
481
493
|
useEffect(() => {
|
|
482
494
|
if (!document) return;
|
|
@@ -813,6 +825,10 @@ function DocumentEditorBody({ documentId, document }: DocumentEditorBodyProps) {
|
|
|
813
825
|
try {
|
|
814
826
|
const next = await pushDocumentToNotion.mutateAsync({
|
|
815
827
|
documentId,
|
|
828
|
+
// The exact editor value was persisted immediately above. Avoid
|
|
829
|
+
// a redundant live-editor flush handshake on every auto-sync
|
|
830
|
+
// save; manual pushes/conflict choices keep the safe default.
|
|
831
|
+
flushOpenEditor: false,
|
|
816
832
|
});
|
|
817
833
|
queryClient.setQueryData(
|
|
818
834
|
documentSyncStatusQueryKey(documentId, { autoSync }),
|
|
@@ -1030,8 +1046,21 @@ function DocumentEditorBody({ documentId, document }: DocumentEditorBodyProps) {
|
|
|
1030
1046
|
try {
|
|
1031
1047
|
const res = await fetch(flushPath);
|
|
1032
1048
|
if (res.ok) {
|
|
1033
|
-
const pending = (await res.json()) as {
|
|
1049
|
+
const pending = (await res.json()) as {
|
|
1050
|
+
id?: string;
|
|
1051
|
+
ts?: number;
|
|
1052
|
+
requestId?: string;
|
|
1053
|
+
status?: "pending" | "success" | "error";
|
|
1054
|
+
error?: string;
|
|
1055
|
+
} | null;
|
|
1034
1056
|
if (pending && active) {
|
|
1057
|
+
// A terminal acknowledgement waits for the requesting action to
|
|
1058
|
+
// read and clear it. Retrying here could hide a failed flush or
|
|
1059
|
+
// replace the explicit success signal before the server sees it.
|
|
1060
|
+
if (pending.status === "error" || pending.status === "success") {
|
|
1061
|
+
if (active) setTimeout(poll, 600);
|
|
1062
|
+
return;
|
|
1063
|
+
}
|
|
1035
1064
|
const title = localTitleRef.current;
|
|
1036
1065
|
const content = localContentRef.current;
|
|
1037
1066
|
const updates: Record<string, string> = {};
|
|
@@ -1043,30 +1072,60 @@ function DocumentEditorBody({ documentId, document }: DocumentEditorBodyProps) {
|
|
|
1043
1072
|
try {
|
|
1044
1073
|
if (Object.keys(updates).length > 0) {
|
|
1045
1074
|
const saved = await persistDocumentUpdates(updates);
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
const savedAt = saved?.updatedAt ?? new Date().toISOString();
|
|
1053
|
-
adoptConfirmedSaveWatermarks({
|
|
1054
|
-
saved,
|
|
1055
|
-
savedAt,
|
|
1056
|
-
title,
|
|
1057
|
-
content,
|
|
1058
|
-
updates,
|
|
1059
|
-
lastSavedTitleRef,
|
|
1060
|
-
lastSavedContentRef,
|
|
1061
|
-
});
|
|
1075
|
+
if (isDocumentUpdateConflict(saved)) {
|
|
1076
|
+
// Do not acknowledge a CAS loss as a successful flush. The
|
|
1077
|
+
// requester must stop instead of pushing/replacing stale SQL.
|
|
1078
|
+
throw new Error(
|
|
1079
|
+
"The document changed while preparing it for sync.",
|
|
1080
|
+
);
|
|
1062
1081
|
}
|
|
1082
|
+
const savedAt = saved?.updatedAt ?? new Date().toISOString();
|
|
1083
|
+
adoptConfirmedSaveWatermarks({
|
|
1084
|
+
saved,
|
|
1085
|
+
savedAt,
|
|
1086
|
+
title,
|
|
1087
|
+
content,
|
|
1088
|
+
updates,
|
|
1089
|
+
lastSavedTitleRef,
|
|
1090
|
+
lastSavedContentRef,
|
|
1091
|
+
});
|
|
1063
1092
|
}
|
|
1064
|
-
|
|
1065
|
-
//
|
|
1066
|
-
//
|
|
1093
|
+
// Explicitly acknowledge this exact request only after the live
|
|
1094
|
+
// editor state is confirmed in SQL (or nothing needed saving).
|
|
1095
|
+
// A delete is ambiguous with a transient app-state read failure.
|
|
1067
1096
|
await fetch(flushPath, {
|
|
1068
|
-
method: "
|
|
1069
|
-
headers: {
|
|
1097
|
+
method: "PUT",
|
|
1098
|
+
headers: {
|
|
1099
|
+
"Content-Type": "application/json",
|
|
1100
|
+
"X-Agent-Native-CSRF": "1",
|
|
1101
|
+
},
|
|
1102
|
+
body: JSON.stringify({
|
|
1103
|
+
id: pending.id ?? documentId,
|
|
1104
|
+
ts: pending.ts ?? Date.now(),
|
|
1105
|
+
requestId: pending.requestId,
|
|
1106
|
+
status: "success",
|
|
1107
|
+
}),
|
|
1108
|
+
}).catch(() => {});
|
|
1109
|
+
} catch (error) {
|
|
1110
|
+
// Keep a durable negative acknowledgement so the requesting
|
|
1111
|
+
// Notion action can fail closed instead of timing out and using a
|
|
1112
|
+
// stale documents row. The server clears this after reading it.
|
|
1113
|
+
await fetch(flushPath, {
|
|
1114
|
+
method: "PUT",
|
|
1115
|
+
headers: {
|
|
1116
|
+
"Content-Type": "application/json",
|
|
1117
|
+
"X-Agent-Native-CSRF": "1",
|
|
1118
|
+
},
|
|
1119
|
+
body: JSON.stringify({
|
|
1120
|
+
id: pending.id ?? documentId,
|
|
1121
|
+
ts: pending.ts ?? Date.now(),
|
|
1122
|
+
requestId: pending.requestId,
|
|
1123
|
+
status: "error",
|
|
1124
|
+
error:
|
|
1125
|
+
error instanceof Error
|
|
1126
|
+
? error.message
|
|
1127
|
+
: t("editor.liveDocumentSaveBeforeSyncFailed"),
|
|
1128
|
+
}),
|
|
1070
1129
|
}).catch(() => {});
|
|
1071
1130
|
}
|
|
1072
1131
|
}
|
|
@@ -1082,7 +1141,13 @@ function DocumentEditorBody({ documentId, document }: DocumentEditorBodyProps) {
|
|
|
1082
1141
|
active = false;
|
|
1083
1142
|
clearTimeout(timer);
|
|
1084
1143
|
};
|
|
1085
|
-
}, [
|
|
1144
|
+
}, [
|
|
1145
|
+
documentId,
|
|
1146
|
+
editorCanEdit,
|
|
1147
|
+
isLocalFileDocument,
|
|
1148
|
+
persistDocumentUpdates,
|
|
1149
|
+
t,
|
|
1150
|
+
]);
|
|
1086
1151
|
|
|
1087
1152
|
const handleTitleChange = useCallback(
|
|
1088
1153
|
(newTitle: string) => {
|
|
@@ -822,6 +822,28 @@ export function shouldSeedCollaborativeContent({
|
|
|
822
822
|
return !!content.trim() && (fragmentLength === 0 || !semanticMarkdown);
|
|
823
823
|
}
|
|
824
824
|
|
|
825
|
+
/**
|
|
826
|
+
* Parse authoritative Content NFM with Content's exact NFM parser before the
|
|
827
|
+
* shared reconcile computes its top-level surgical diff.
|
|
828
|
+
*
|
|
829
|
+
* Falling back to the shared CommonMark parser is lossy here: canonical NFM
|
|
830
|
+
* stores one Notion block per line without blank paragraph separators, while
|
|
831
|
+
* CommonMark merges those consecutive lines into one paragraph. That made
|
|
832
|
+
* external replacements such as Notion conflict resolution and version
|
|
833
|
+
* restores look correct in the non-collaborative history preview, then collapse
|
|
834
|
+
* into one wrapped paragraph when reconciled into the live Y.Doc.
|
|
835
|
+
*/
|
|
836
|
+
export function parseNfmForCollabReconcile(
|
|
837
|
+
editor: CoreEditor,
|
|
838
|
+
value: string,
|
|
839
|
+
): ProseMirrorNode | null {
|
|
840
|
+
try {
|
|
841
|
+
return editor.schema.nodeFromJSON(nfmToDoc(value) as any);
|
|
842
|
+
} catch {
|
|
843
|
+
return null;
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
|
|
825
847
|
export function shouldApplyExternalContentSync({
|
|
826
848
|
docChanged,
|
|
827
849
|
content,
|
|
@@ -1669,10 +1691,14 @@ export function VisualEditor({
|
|
|
1669
1691
|
// Clean up awareness on unmount
|
|
1670
1692
|
useEffect(() => {
|
|
1671
1693
|
return () => {
|
|
1672
|
-
|
|
1694
|
+
// Only the fallback instance is owned by this editor. A provided
|
|
1695
|
+
// awareness belongs to the shared useCollaborativeDoc connection; clearing
|
|
1696
|
+
// it here races StrictMode/remounts and can erase the tab's durable
|
|
1697
|
+
// presence while the shared connection is still active.
|
|
1698
|
+
fallbackAwareness?.setLocalState(null);
|
|
1673
1699
|
fallbackAwareness?.destroy();
|
|
1674
1700
|
};
|
|
1675
|
-
}, [fallbackAwareness
|
|
1701
|
+
}, [fallbackAwareness]);
|
|
1676
1702
|
|
|
1677
1703
|
const extensions = useMemo(
|
|
1678
1704
|
() =>
|
|
@@ -1941,6 +1967,10 @@ export function VisualEditor({
|
|
|
1941
1967
|
e.commands.setContent(doc);
|
|
1942
1968
|
},
|
|
1943
1969
|
normalizeValue: canonicalizeNfm,
|
|
1970
|
+
// The shared fallback parser is CommonMark. Content stores canonical NFM,
|
|
1971
|
+
// whose adjacent lines are separate Notion blocks, so always provide the
|
|
1972
|
+
// exact NFM parser for the surgical reconcile path.
|
|
1973
|
+
parseValue: parseNfmForCollabReconcile,
|
|
1944
1974
|
shouldSeed: ({ value, currentMarkdown, fragmentLength }) =>
|
|
1945
1975
|
editable &&
|
|
1946
1976
|
shouldSeedCollaborativeContent({
|
|
@@ -253,12 +253,12 @@ export function usePullDocumentFromNotion(documentId: string) {
|
|
|
253
253
|
|
|
254
254
|
export function usePushDocumentToNotion(documentId: string) {
|
|
255
255
|
const queryClient = useQueryClient();
|
|
256
|
-
return useActionMutation<
|
|
257
|
-
|
|
258
|
-
{
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
);
|
|
256
|
+
return useActionMutation<
|
|
257
|
+
DocumentSyncStatus,
|
|
258
|
+
{ documentId: string; flushOpenEditor?: boolean }
|
|
259
|
+
>("push-notion-page", {
|
|
260
|
+
onSuccess: () => invalidateDocumentQueries(queryClient, documentId),
|
|
261
|
+
});
|
|
262
262
|
}
|
|
263
263
|
|
|
264
264
|
export function useResolveDocumentSyncConflict(documentId: string) {
|
|
@@ -94,6 +94,7 @@ const messages = {
|
|
|
94
94
|
couldNotReadLocalSourceFile: "無法讀取本機來源檔案",
|
|
95
95
|
couldNotSaveLocalFile: "無法儲存本機檔案",
|
|
96
96
|
collabConnectingReadOnly: "正在連接即時編輯器。顯示唯讀快照。",
|
|
97
|
+
liveDocumentSaveBeforeSyncFailed: "即時文件無法在同步前儲存。",
|
|
97
98
|
documentTitle: "檔案標題",
|
|
98
99
|
builderBodySyncing: "內容仍在從 Builder 同步",
|
|
99
100
|
builderBodySyncingDescription:
|