@agent-native/core 0.37.3 → 0.38.0
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/README.md +19 -6
- package/dist/action.d.ts +60 -2
- package/dist/action.d.ts.map +1 -1
- package/dist/action.js +6 -2
- package/dist/action.js.map +1 -1
- package/dist/agent/production-agent.d.ts +12 -6
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +161 -11
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/agent/types.d.ts +2 -0
- package/dist/agent/types.d.ts.map +1 -1
- package/dist/agent/types.js.map +1 -1
- package/dist/catalog.json +2 -2
- package/dist/cli/connect.d.ts.map +1 -1
- package/dist/cli/connect.js +15 -0
- package/dist/cli/connect.js.map +1 -1
- package/dist/cli/index.js +10 -6
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/plan-publish-store.d.ts +52 -0
- package/dist/cli/plan-publish-store.d.ts.map +1 -0
- package/dist/cli/plan-publish-store.js +103 -0
- package/dist/cli/plan-publish-store.js.map +1 -0
- package/dist/cli/skills.d.ts +29 -4
- package/dist/cli/skills.d.ts.map +1 -1
- package/dist/cli/skills.js +851 -275
- package/dist/cli/skills.js.map +1 -1
- package/dist/cli/templates-meta.js +12 -12
- package/dist/cli/templates-meta.js.map +1 -1
- package/dist/client/AssistantChat.d.ts +3 -1
- package/dist/client/AssistantChat.d.ts.map +1 -1
- package/dist/client/AssistantChat.js +65 -15
- package/dist/client/AssistantChat.js.map +1 -1
- package/dist/client/MultiTabAssistantChat.d.ts.map +1 -1
- package/dist/client/MultiTabAssistantChat.js +20 -2
- package/dist/client/MultiTabAssistantChat.js.map +1 -1
- package/dist/client/agent-chat-adapter.d.ts.map +1 -1
- package/dist/client/agent-chat-adapter.js +12 -0
- package/dist/client/agent-chat-adapter.js.map +1 -1
- package/dist/client/agent-engine-key.d.ts +24 -0
- package/dist/client/agent-engine-key.d.ts.map +1 -0
- package/dist/client/agent-engine-key.js +49 -0
- package/dist/client/agent-engine-key.js.map +1 -0
- package/dist/client/analytics.d.ts.map +1 -1
- package/dist/client/analytics.js +34 -0
- package/dist/client/analytics.js.map +1 -1
- package/dist/client/blocks/BlockView.d.ts +26 -0
- package/dist/client/blocks/BlockView.d.ts.map +1 -0
- package/dist/client/blocks/BlockView.js +24 -0
- package/dist/client/blocks/BlockView.js.map +1 -0
- package/dist/client/blocks/SchemaBlockEditor.d.ts +25 -0
- package/dist/client/blocks/SchemaBlockEditor.d.ts.map +1 -0
- package/dist/client/blocks/SchemaBlockEditor.js +72 -0
- package/dist/client/blocks/SchemaBlockEditor.js.map +1 -0
- package/dist/client/blocks/agent.d.ts +30 -0
- package/dist/client/blocks/agent.d.ts.map +1 -0
- package/dist/client/blocks/agent.js +61 -0
- package/dist/client/blocks/agent.js.map +1 -0
- package/dist/client/blocks/index.d.ts +34 -0
- package/dist/client/blocks/index.d.ts.map +1 -0
- package/dist/client/blocks/index.js +42 -0
- package/dist/client/blocks/index.js.map +1 -0
- package/dist/client/blocks/library/checklist.config.d.ts +36 -0
- package/dist/client/blocks/library/checklist.config.d.ts.map +1 -0
- package/dist/client/blocks/library/checklist.config.js +25 -0
- package/dist/client/blocks/library/checklist.config.js.map +1 -0
- package/dist/client/blocks/library/checklist.d.ts +26 -0
- package/dist/client/blocks/library/checklist.d.ts.map +1 -0
- package/dist/client/blocks/library/checklist.js +76 -0
- package/dist/client/blocks/library/checklist.js.map +1 -0
- package/dist/client/blocks/library/code-tabs.config.d.ts +36 -0
- package/dist/client/blocks/library/code-tabs.config.d.ts.map +1 -0
- package/dist/client/blocks/library/code-tabs.config.js +30 -0
- package/dist/client/blocks/library/code-tabs.config.js.map +1 -0
- package/dist/client/blocks/library/code-tabs.d.ts +3 -0
- package/dist/client/blocks/library/code-tabs.d.ts.map +1 -0
- package/dist/client/blocks/library/code-tabs.js +165 -0
- package/dist/client/blocks/library/code-tabs.js.map +1 -0
- package/dist/client/blocks/library/html.config.d.ts +37 -0
- package/dist/client/blocks/library/html.config.d.ts.map +1 -0
- package/dist/client/blocks/library/html.config.js +46 -0
- package/dist/client/blocks/library/html.config.js.map +1 -0
- package/dist/client/blocks/library/html.d.ts +21 -0
- package/dist/client/blocks/library/html.d.ts.map +1 -0
- package/dist/client/blocks/library/html.js +69 -0
- package/dist/client/blocks/library/html.js.map +1 -0
- package/dist/client/blocks/library/table.config.d.ts +30 -0
- package/dist/client/blocks/library/table.config.d.ts.map +1 -0
- package/dist/client/blocks/library/table.config.js +22 -0
- package/dist/client/blocks/library/table.config.js.map +1 -0
- package/dist/client/blocks/library/table.d.ts +8 -0
- package/dist/client/blocks/library/table.d.ts.map +1 -0
- package/dist/client/blocks/library/table.js +107 -0
- package/dist/client/blocks/library/table.js.map +1 -0
- package/dist/client/blocks/library/tabs.config.d.ts +56 -0
- package/dist/client/blocks/library/tabs.config.d.ts.map +1 -0
- package/dist/client/blocks/library/tabs.config.js +36 -0
- package/dist/client/blocks/library/tabs.config.js.map +1 -0
- package/dist/client/blocks/library/tabs.d.ts +20 -0
- package/dist/client/blocks/library/tabs.d.ts.map +1 -0
- package/dist/client/blocks/library/tabs.js +123 -0
- package/dist/client/blocks/library/tabs.js.map +1 -0
- package/dist/client/blocks/mdx.d.ts +74 -0
- package/dist/client/blocks/mdx.d.ts.map +1 -0
- package/dist/client/blocks/mdx.js +205 -0
- package/dist/client/blocks/mdx.js.map +1 -0
- package/dist/client/blocks/provider.d.ts +25 -0
- package/dist/client/blocks/provider.d.ts.map +1 -0
- package/dist/client/blocks/provider.js +19 -0
- package/dist/client/blocks/provider.js.map +1 -0
- package/dist/client/blocks/registry.d.ts +24 -0
- package/dist/client/blocks/registry.d.ts.map +1 -0
- package/dist/client/blocks/registry.js +50 -0
- package/dist/client/blocks/registry.js.map +1 -0
- package/dist/client/blocks/schema-form/introspect.d.ts +31 -0
- package/dist/client/blocks/schema-form/introspect.d.ts.map +1 -0
- package/dist/client/blocks/schema-form/introspect.js +164 -0
- package/dist/client/blocks/schema-form/introspect.js.map +1 -0
- package/dist/client/blocks/server.d.ts +22 -0
- package/dist/client/blocks/server.d.ts.map +1 -0
- package/dist/client/blocks/server.js +25 -0
- package/dist/client/blocks/server.js.map +1 -0
- package/dist/client/blocks/types.d.ts +212 -0
- package/dist/client/blocks/types.d.ts.map +1 -0
- package/dist/client/blocks/types.js +5 -0
- package/dist/client/blocks/types.js.map +1 -0
- package/dist/client/composer/ComposerPlusMenu.js +10 -1
- package/dist/client/composer/ComposerPlusMenu.js.map +1 -1
- package/dist/client/guided-questions.d.ts +68 -0
- package/dist/client/guided-questions.d.ts.map +1 -1
- package/dist/client/guided-questions.js +158 -3
- package/dist/client/guided-questions.js.map +1 -1
- package/dist/client/index.d.ts +5 -1
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +15 -1
- package/dist/client/index.js.map +1 -1
- package/dist/client/rich-markdown-editor/BubbleToolbar.d.ts +37 -0
- package/dist/client/rich-markdown-editor/BubbleToolbar.d.ts.map +1 -0
- package/dist/client/rich-markdown-editor/BubbleToolbar.js +161 -0
- package/dist/client/rich-markdown-editor/BubbleToolbar.js.map +1 -0
- package/dist/client/rich-markdown-editor/ImageExtension.d.ts +63 -0
- package/dist/client/rich-markdown-editor/ImageExtension.d.ts.map +1 -0
- package/dist/client/rich-markdown-editor/ImageExtension.js +242 -0
- package/dist/client/rich-markdown-editor/ImageExtension.js.map +1 -0
- package/dist/client/rich-markdown-editor/RichMarkdownEditor.d.ts +51 -0
- package/dist/client/rich-markdown-editor/RichMarkdownEditor.d.ts.map +1 -0
- package/dist/client/rich-markdown-editor/RichMarkdownEditor.js +37 -0
- package/dist/client/rich-markdown-editor/RichMarkdownEditor.js.map +1 -0
- package/dist/client/rich-markdown-editor/SharedRichEditor.d.ts +61 -0
- package/dist/client/rich-markdown-editor/SharedRichEditor.d.ts.map +1 -0
- package/dist/client/rich-markdown-editor/SharedRichEditor.js +121 -0
- package/dist/client/rich-markdown-editor/SharedRichEditor.js.map +1 -0
- package/dist/client/rich-markdown-editor/SlashCommandMenu.d.ts +36 -0
- package/dist/client/rich-markdown-editor/SlashCommandMenu.d.ts.map +1 -0
- package/dist/client/rich-markdown-editor/SlashCommandMenu.js +193 -0
- package/dist/client/rich-markdown-editor/SlashCommandMenu.js.map +1 -0
- package/dist/client/rich-markdown-editor/extensions.d.ts +166 -0
- package/dist/client/rich-markdown-editor/extensions.d.ts.map +1 -0
- package/dist/client/rich-markdown-editor/extensions.js +222 -0
- package/dist/client/rich-markdown-editor/extensions.js.map +1 -0
- package/dist/client/rich-markdown-editor/index.d.ts +9 -0
- package/dist/client/rich-markdown-editor/index.d.ts.map +1 -0
- package/dist/client/rich-markdown-editor/index.js +9 -0
- package/dist/client/rich-markdown-editor/index.js.map +1 -0
- package/dist/client/rich-markdown-editor/uploadEditorImage.d.ts +18 -0
- package/dist/client/rich-markdown-editor/uploadEditorImage.d.ts.map +1 -0
- package/dist/client/rich-markdown-editor/uploadEditorImage.js +57 -0
- package/dist/client/rich-markdown-editor/uploadEditorImage.js.map +1 -0
- package/dist/client/rich-markdown-editor/useCollabReconcile.d.ts +91 -0
- package/dist/client/rich-markdown-editor/useCollabReconcile.d.ts.map +1 -0
- package/dist/client/rich-markdown-editor/useCollabReconcile.js +342 -0
- package/dist/client/rich-markdown-editor/useCollabReconcile.js.map +1 -0
- package/dist/client/track.d.ts +25 -0
- package/dist/client/track.d.ts.map +1 -0
- package/dist/client/track.js +53 -0
- package/dist/client/track.js.map +1 -0
- package/dist/client/use-action.d.ts.map +1 -1
- package/dist/client/use-action.js +6 -0
- package/dist/client/use-action.js.map +1 -1
- package/dist/client/use-session.d.ts +3 -2
- package/dist/client/use-session.d.ts.map +1 -1
- package/dist/client/use-session.js +3 -2
- package/dist/client/use-session.js.map +1 -1
- package/dist/deploy/build.d.ts +5 -0
- package/dist/deploy/build.d.ts.map +1 -1
- package/dist/deploy/build.js +67 -1
- package/dist/deploy/build.js.map +1 -1
- package/dist/extensions/schema.d.ts +1 -1
- package/dist/mcp/build-server.d.ts.map +1 -1
- package/dist/mcp/build-server.js +9 -2
- package/dist/mcp/build-server.js.map +1 -1
- package/dist/mcp/server.d.ts +1 -1
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +35 -2
- package/dist/mcp/server.js.map +1 -1
- package/dist/provider-api/index.d.ts +1 -1
- package/dist/provider-api/index.d.ts.map +1 -1
- package/dist/scripts/docs/search.d.ts.map +1 -1
- package/dist/scripts/docs/search.js +5 -2
- package/dist/scripts/docs/search.js.map +1 -1
- package/dist/scripts/runner.d.ts.map +1 -1
- package/dist/scripts/runner.js +16 -3
- package/dist/scripts/runner.js.map +1 -1
- package/dist/server/action-discovery.d.ts.map +1 -1
- package/dist/server/action-discovery.js +2 -0
- package/dist/server/action-discovery.js.map +1 -1
- package/dist/server/action-routes.d.ts.map +1 -1
- package/dist/server/action-routes.js +30 -4
- package/dist/server/action-routes.js.map +1 -1
- package/dist/server/agent-chat-plugin.d.ts.map +1 -1
- package/dist/server/agent-chat-plugin.js +65 -19
- package/dist/server/agent-chat-plugin.js.map +1 -1
- package/dist/server/agent-teams.d.ts.map +1 -1
- package/dist/server/agent-teams.js +8 -1
- package/dist/server/agent-teams.js.map +1 -1
- package/dist/server/agents-bundle.d.ts +27 -1
- package/dist/server/agents-bundle.d.ts.map +1 -1
- package/dist/server/agents-bundle.js +41 -3
- package/dist/server/agents-bundle.js.map +1 -1
- package/dist/server/auth.d.ts.map +1 -1
- package/dist/server/auth.js +76 -3
- package/dist/server/auth.js.map +1 -1
- package/dist/server/core-routes-plugin.d.ts.map +1 -1
- package/dist/server/core-routes-plugin.js +60 -0
- package/dist/server/core-routes-plugin.js.map +1 -1
- package/dist/server/onboarding-html.d.ts.map +1 -1
- package/dist/server/onboarding-html.js +160 -22
- package/dist/server/onboarding-html.js.map +1 -1
- package/dist/server/sentry.d.ts.map +1 -1
- package/dist/server/sentry.js +6 -0
- package/dist/server/sentry.js.map +1 -1
- package/dist/server/social-og-image.d.ts +2 -1
- package/dist/server/social-og-image.d.ts.map +1 -1
- package/dist/server/social-og-image.js +24 -4
- package/dist/server/social-og-image.js.map +1 -1
- package/dist/sharing/schema.d.ts +1 -1
- package/dist/styles/agent-native.css +1 -0
- package/dist/styles/rich-markdown-editor.css +439 -0
- package/dist/templates/default/.agents/skills/actions/SKILL.md +4 -1
- package/dist/templates/default/.agents/skills/security/SKILL.md +13 -4
- package/dist/templates/default/.agents/skills/storing-data/SKILL.md +15 -3
- package/dist/templates/default/AGENTS.md +1 -0
- package/dist/templates/default/DEVELOPING.md +2 -0
- package/dist/templates/workspace-core/.agents/skills/a2a-protocol/SKILL.md +10 -3
- package/dist/templates/workspace-core/.agents/skills/actions/SKILL.md +98 -10
- package/dist/templates/workspace-core/.agents/skills/adding-a-feature/SKILL.md +45 -3
- package/dist/templates/workspace-core/.agents/skills/address-feedback/SKILL.md +2 -0
- package/dist/templates/workspace-core/.agents/skills/authentication/SKILL.md +37 -4
- package/dist/templates/workspace-core/.agents/skills/automations/SKILL.md +9 -4
- package/dist/templates/workspace-core/.agents/skills/capture-learnings/SKILL.md +2 -0
- package/dist/templates/workspace-core/.agents/skills/client-methods/SKILL.md +106 -0
- package/dist/templates/workspace-core/.agents/skills/client-methods/references/legacy-client-fetch-audit-2026-06-03.md +53 -0
- package/dist/templates/workspace-core/.agents/skills/client-side-routing/SKILL.md +2 -0
- package/dist/templates/workspace-core/.agents/skills/context-awareness/SKILL.md +62 -61
- package/dist/templates/workspace-core/.agents/skills/context-xray/SKILL.md +47 -0
- package/dist/templates/workspace-core/.agents/skills/create-skill/SKILL.md +28 -0
- package/dist/templates/workspace-core/.agents/skills/delegate-to-agent/SKILL.md +52 -1
- package/dist/templates/workspace-core/.agents/skills/extension-points/SKILL.md +2 -0
- package/dist/templates/workspace-core/.agents/skills/extensions/SKILL.md +95 -433
- package/dist/templates/workspace-core/.agents/skills/extensions/references/api.md +285 -0
- package/dist/templates/workspace-core/.agents/skills/extensions/references/examples.md +259 -0
- package/dist/templates/workspace-core/.agents/skills/external-agents/SKILL.md +398 -0
- package/dist/templates/workspace-core/.agents/skills/external-agents/references/mcp-apps-embedding.md +157 -0
- package/dist/templates/workspace-core/.agents/skills/frontend-design/SKILL.md +17 -0
- package/dist/templates/workspace-core/.agents/skills/integration-webhooks/SKILL.md +13 -2
- package/dist/templates/workspace-core/.agents/skills/mvp-followup/SKILL.md +51 -0
- package/dist/templates/workspace-core/.agents/skills/observability/SKILL.md +14 -4
- package/dist/templates/workspace-core/.agents/skills/onboarding/SKILL.md +13 -1
- package/dist/templates/workspace-core/.agents/skills/portability/SKILL.md +27 -5
- package/dist/templates/workspace-core/.agents/skills/qa/SKILL.md +24 -8
- package/dist/templates/workspace-core/.agents/skills/real-time-collab/SKILL.md +53 -7
- package/dist/templates/workspace-core/.agents/skills/real-time-sync/SKILL.md +43 -10
- package/dist/templates/workspace-core/.agents/skills/recurring-jobs/SKILL.md +2 -0
- package/dist/templates/workspace-core/.agents/skills/secrets/SKILL.md +43 -14
- package/dist/templates/workspace-core/.agents/skills/security/SKILL.md +50 -1
- package/dist/templates/workspace-core/.agents/skills/self-modifying-code/SKILL.md +4 -2
- package/dist/templates/workspace-core/.agents/skills/server-plugins/SKILL.md +11 -1
- package/dist/templates/workspace-core/.agents/skills/shadcn-ui/SKILL.md +15 -0
- package/dist/templates/workspace-core/.agents/skills/sharing/SKILL.md +5 -1
- package/dist/templates/workspace-core/.agents/skills/storing-data/SKILL.md +48 -19
- package/dist/templates/workspace-core/.agents/skills/tracking/SKILL.md +7 -3
- package/dist/templates/workspace-core/.agents/skills/voice-transcription/SKILL.md +13 -6
- package/dist/templates/workspace-core/.agents/skills/writing-agent-instructions/SKILL.md +236 -0
- package/dist/templates/workspace-core/AGENTS.md +5 -1
- package/dist/templates/workspace-root/AGENTS.md +5 -2
- package/dist/tracking/route.d.ts +43 -0
- package/dist/tracking/route.d.ts.map +1 -0
- package/dist/tracking/route.js +85 -0
- package/dist/tracking/route.js.map +1 -0
- package/dist/vite/client.d.ts.map +1 -1
- package/dist/vite/client.js +15 -0
- package/dist/vite/client.js.map +1 -1
- package/docs/content/a2a-protocol.md +18 -4
- package/docs/content/actions.md +87 -0
- package/docs/content/agent-mentions.md +2 -1
- package/docs/content/authentication.md +2 -1
- package/docs/content/client.md +64 -13
- package/docs/content/cloneable-saas.md +1 -1
- package/docs/content/code-agents-ui.md +17 -11
- package/docs/content/context-awareness.md +23 -28
- package/docs/content/creating-templates.md +1 -1
- package/docs/content/drop-in-agent.md +2 -0
- package/docs/content/getting-started.md +2 -2
- package/docs/content/key-concepts.md +2 -2
- package/docs/content/messaging.md +57 -15
- package/docs/content/migration-workbench.md +1 -1
- package/docs/content/multi-app-workspace.md +1 -1
- package/docs/content/multi-tenancy.md +17 -15
- package/docs/content/real-time-collaboration.md +1 -1
- package/docs/content/recurring-jobs.md +1 -1
- package/docs/content/security.md +2 -2
- package/docs/content/server.md +4 -4
- package/docs/content/skills-guide.md +30 -0
- package/docs/content/template-analytics.md +2 -2
- package/docs/content/template-assets.md +17 -1
- package/docs/content/template-brain.md +2 -2
- package/docs/content/template-calendar.md +1 -1
- package/docs/content/template-clips.md +3 -3
- package/docs/content/template-content.md +2 -2
- package/docs/content/template-design.md +2 -2
- package/docs/content/template-dispatch.md +3 -3
- package/docs/content/template-forms.md +14 -2
- package/docs/content/template-mail.md +1 -3
- package/docs/content/template-plan.md +118 -0
- package/docs/content/template-slides.md +5 -4
- package/docs/content/template-starter.md +4 -4
- package/docs/content/template-videos.md +6 -11
- package/docs/content/tracking.md +21 -1
- package/docs/content/visual-plans.md +72 -0
- package/docs/content/workspace.md +9 -9
- package/package.json +26 -11
- package/src/templates/default/.agents/skills/actions/SKILL.md +4 -1
- package/src/templates/default/.agents/skills/security/SKILL.md +13 -4
- package/src/templates/default/.agents/skills/storing-data/SKILL.md +15 -3
- package/src/templates/default/AGENTS.md +1 -0
- package/src/templates/default/DEVELOPING.md +2 -0
- package/src/templates/workspace-core/.agents/skills/a2a-protocol/SKILL.md +10 -3
- package/src/templates/workspace-core/.agents/skills/actions/SKILL.md +98 -10
- package/src/templates/workspace-core/.agents/skills/adding-a-feature/SKILL.md +45 -3
- package/src/templates/workspace-core/.agents/skills/address-feedback/SKILL.md +2 -0
- package/src/templates/workspace-core/.agents/skills/authentication/SKILL.md +37 -4
- package/src/templates/workspace-core/.agents/skills/automations/SKILL.md +9 -4
- package/src/templates/workspace-core/.agents/skills/capture-learnings/SKILL.md +2 -0
- package/src/templates/workspace-core/.agents/skills/client-methods/SKILL.md +106 -0
- package/src/templates/workspace-core/.agents/skills/client-methods/references/legacy-client-fetch-audit-2026-06-03.md +53 -0
- package/src/templates/workspace-core/.agents/skills/client-side-routing/SKILL.md +2 -0
- package/src/templates/workspace-core/.agents/skills/context-awareness/SKILL.md +62 -61
- package/src/templates/workspace-core/.agents/skills/context-xray/SKILL.md +47 -0
- package/src/templates/workspace-core/.agents/skills/create-skill/SKILL.md +28 -0
- package/src/templates/workspace-core/.agents/skills/delegate-to-agent/SKILL.md +52 -1
- package/src/templates/workspace-core/.agents/skills/extension-points/SKILL.md +2 -0
- package/src/templates/workspace-core/.agents/skills/extensions/SKILL.md +95 -433
- package/src/templates/workspace-core/.agents/skills/extensions/references/api.md +285 -0
- package/src/templates/workspace-core/.agents/skills/extensions/references/examples.md +259 -0
- package/src/templates/workspace-core/.agents/skills/external-agents/SKILL.md +398 -0
- package/src/templates/workspace-core/.agents/skills/external-agents/references/mcp-apps-embedding.md +157 -0
- package/src/templates/workspace-core/.agents/skills/frontend-design/SKILL.md +17 -0
- package/src/templates/workspace-core/.agents/skills/integration-webhooks/SKILL.md +13 -2
- package/src/templates/workspace-core/.agents/skills/mvp-followup/SKILL.md +51 -0
- package/src/templates/workspace-core/.agents/skills/observability/SKILL.md +14 -4
- package/src/templates/workspace-core/.agents/skills/onboarding/SKILL.md +13 -1
- package/src/templates/workspace-core/.agents/skills/portability/SKILL.md +27 -5
- package/src/templates/workspace-core/.agents/skills/qa/SKILL.md +24 -8
- package/src/templates/workspace-core/.agents/skills/real-time-collab/SKILL.md +53 -7
- package/src/templates/workspace-core/.agents/skills/real-time-sync/SKILL.md +43 -10
- package/src/templates/workspace-core/.agents/skills/recurring-jobs/SKILL.md +2 -0
- package/src/templates/workspace-core/.agents/skills/secrets/SKILL.md +43 -14
- package/src/templates/workspace-core/.agents/skills/security/SKILL.md +50 -1
- package/src/templates/workspace-core/.agents/skills/self-modifying-code/SKILL.md +4 -2
- package/src/templates/workspace-core/.agents/skills/server-plugins/SKILL.md +11 -1
- package/src/templates/workspace-core/.agents/skills/shadcn-ui/SKILL.md +15 -0
- package/src/templates/workspace-core/.agents/skills/sharing/SKILL.md +5 -1
- package/src/templates/workspace-core/.agents/skills/storing-data/SKILL.md +48 -19
- package/src/templates/workspace-core/.agents/skills/tracking/SKILL.md +7 -3
- package/src/templates/workspace-core/.agents/skills/voice-transcription/SKILL.md +13 -6
- package/src/templates/workspace-core/.agents/skills/writing-agent-instructions/SKILL.md +236 -0
- package/src/templates/workspace-core/AGENTS.md +5 -1
- package/src/templates/workspace-root/AGENTS.md +5 -2
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"extensions.js","sourceRoot":"","sources":["../../../src/client/rich-markdown-editor/extensions.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Extension, Node, Mark } from \"@tiptap/core\";\nimport StarterKit from \"@tiptap/starter-kit\";\nimport type { StarterKitOptions } from \"@tiptap/starter-kit\";\nimport Placeholder from \"@tiptap/extension-placeholder\";\nimport Link from \"@tiptap/extension-link\";\nimport TaskList from \"@tiptap/extension-task-list\";\nimport TaskItem from \"@tiptap/extension-task-item\";\nimport { Table } from \"@tiptap/extension-table\";\nimport { TableRow } from \"@tiptap/extension-table-row\";\nimport { TableCell } from \"@tiptap/extension-table-cell\";\nimport { TableHeader } from \"@tiptap/extension-table-header\";\nimport { Markdown } from \"tiptap-markdown\";\nimport { CodeBlockLowlight } from \"@tiptap/extension-code-block-lowlight\";\nimport { createLowlight } from \"lowlight\";\nimport bash from \"highlight.js/lib/languages/bash\";\nimport css from \"highlight.js/lib/languages/css\";\nimport javascript from \"highlight.js/lib/languages/javascript\";\nimport json from \"highlight.js/lib/languages/json\";\nimport markdown from \"highlight.js/lib/languages/markdown\";\nimport python from \"highlight.js/lib/languages/python\";\nimport sql from \"highlight.js/lib/languages/sql\";\nimport typescript from \"highlight.js/lib/languages/typescript\";\nimport xml from \"highlight.js/lib/languages/xml\";\nimport yaml from \"highlight.js/lib/languages/yaml\";\nimport Collaboration from \"@tiptap/extension-collaboration\";\nimport CollaborationCaret from \"@tiptap/extension-collaboration-caret\";\n\n/**\n * Shared lowlight instance for the editor's syntax-highlighted code blocks. A\n * curated grammar set (aliases like ts/tsx, js/jsx, html, sh, py, yml, md come\n * for free from each grammar) keeps the editor bundle lean while matching the\n * languages the read-side Shiki surfaces (`code-tabs`) support. highlight.js is\n * synchronous, which is what a live ProseMirror editor needs — Shiki is async\n * and only used for read-only render paths.\n */\nconst codeLowlight = createLowlight({\n bash,\n css,\n javascript,\n json,\n markdown,\n python,\n sql,\n typescript,\n xml,\n yaml,\n});\nimport type { Doc as YDoc } from \"yjs\";\nimport type { Awareness } from \"y-protocols/awareness\";\nimport { createImageExtension, type ImageUploadFn } from \"./ImageExtension.js\";\n\n/**\n * Markdown dialect the editor parses/serializes.\n *\n * - `gfm` — GitHub-Flavored Markdown. No raw HTML passthrough. The byte-stable\n * serialization used by Plans (see RichMarkdownEditor.roundtrip.spec.ts).\n * - `nfm` — the Notion-Flavored Markdown superset used by the Content editor,\n * which opts into inline HTML so Notion-specific blocks round-trip.\n */\nexport type RichMarkdownDialect = \"gfm\" | \"nfm\";\n\n/**\n * Editor preset. Schema-neutral today (both presets share the base schema),\n * but threaded through so an app can branch schema/behavior per preset without\n * a new factory. The collab/markdown wiring is preset-independent.\n */\nexport type RichMarkdownEditorPreset = \"plan\" | \"content\";\n\n/** User info used to label this client's collaborative cursor. */\nexport interface RichMarkdownCollabUser {\n name: string;\n color: string;\n email?: string;\n}\n\n/** Optional collaborative-editing inputs for the shared editor. */\nexport interface SharedEditorCollab {\n /**\n * Yjs document for collaborative editing. When present the editor binds the\n * shared {@link Collaboration} (+ {@link CollaborationCaret} when awareness\n * is set) extensions and StarterKit's built-in undo/redo is disabled (Yjs\n * owns history). When absent the editor is a controlled `value`/`onChange`\n * editor.\n */\n ydoc?: YDoc | null;\n /** Shared awareness instance for live multi-user cursors. */\n awareness?: Awareness | null;\n /** Current user info for the collaborative cursor label. */\n user?: RichMarkdownCollabUser | null;\n}\n\n/** Toggle the optional base extensions on/off per app. All default to `true`. */\nexport interface SharedEditorFeatures {\n /** GFM pipe tables (Table + TableRow + TableHeader + TableCell). */\n tables?: boolean;\n /** Task / checklist lists (TaskList + TaskItem). */\n tasks?: boolean;\n /** Inline links (the `Link` mark). When off, links fall back to plain text. */\n link?: boolean;\n /** Fenced code blocks. Disabling lets an app inject its own code-block node. */\n codeBlock?: boolean;\n /**\n * The built-in {@link Placeholder} extension. Default `true`. Apps that need a\n * bespoke placeholder resolver (per-node-type labels, ancestor-aware text)\n * disable this and supply their own Placeholder via `extraExtensions`.\n */\n placeholder?: boolean;\n /**\n * The built-in dialect-keyed {@link Markdown} serializer. Default `true`.\n * Apps with a custom serializer (e.g. Content's NFM converter, which does NOT\n * round-trip through tiptap-markdown's storage) disable this and own the\n * serialize/parse pipeline themselves. The Markdown extension is still added\n * so paste/clipboard transforms work — disable it only when supplying your own\n * Markdown configuration via the {@link CreateSharedEditorExtensionsOptions.markdown}\n * option instead.\n */\n markdown?: boolean;\n /**\n * The shared block-level image node (`@tiptap/extension-image`). Default\n * `false` so existing embedders are unchanged. When `true`, images\n * serialize to GFM `` (source-syncable) and — when an\n * {@link CreateSharedEditorExtensionsOptions.onImageUpload} function is\n * supplied — paste / drop of local image files uploads through it. Content\n * leaves this off and injects its own richer image node via\n * `extraExtensions`, so the two never collide.\n */\n image?: boolean;\n}\n\nexport interface CreateSharedEditorExtensionsOptions {\n /** Markdown dialect; selects the keyed {@link Markdown} config. */\n dialect?: RichMarkdownDialect;\n /** Preset hook (schema-neutral today). */\n preset?: RichMarkdownEditorPreset;\n /** Empty-block placeholder text (headings get their own labels). */\n placeholder?: string;\n /** Toggle individual base extensions. */\n features?: SharedEditorFeatures;\n /**\n * Extra StarterKit options merged over the shared defaults. Lets an app turn\n * off StarterKit nodes it replaces (Content swaps in its own paragraph /\n * blockquote / code block) or pass a custom dropcursor, while still sharing\n * the rest of the StarterKit base + the collab undo/redo gating. The shared\n * defaults (`heading` levels 1-4, `link: false`, the default dropcursor, and\n * `undoRedo: false` in collab mode) are applied first and can be overridden\n * key-by-key here.\n */\n starterKit?: Partial<StarterKitOptions>;\n /**\n * Custom {@link Markdown} configuration. Replaces the dialect-keyed config from\n * {@link MARKDOWN_DIALECT_CONFIG} when provided. Only used when\n * `features.markdown !== false`; apps that own the whole markdown pipeline (no\n * tiptap-markdown serialization at all) should set `features.markdown: false`\n * and add their own configured Markdown extension via `extraExtensions`.\n */\n markdown?: Parameters<typeof Markdown.configure>[0];\n /**\n * App-specific extensions (Notion nodes, media, drag handles, comment\n * anchors, etc.) appended LAST so they bind over the shared base schema and\n * the optional Collaboration extensions still mount after them.\n */\n extraExtensions?: Array<Extension | Node | Mark>;\n /** Optional collaborative-editing wiring. */\n collab?: SharedEditorCollab | null;\n /**\n * Injectable image uploader for the shared image block. Only used when\n * `features.image` is on. Turns a picked / pasted / dropped image File into a\n * hosted `{ src, alt? }`. Plans pass `uploadEditorImage` (the framework\n * `upload-image` action). When omitted, the image block still renders and\n * round-trips `` markdown but cannot ingest local files.\n */\n onImageUpload?: ImageUploadFn | null;\n}\n\n/**\n * tiptap-markdown configuration, keyed by dialect. This is the single source of\n * truth for how each dialect parses/serializes markdown so the editor component\n * and the round-trip fidelity test can never drift apart.\n *\n * tiptap-markdown re-serializes the whole document on every edit, so the goal\n * for GFM is `serialize(parse(markdown)) === markdown` for the markdown plans\n * actually contain. We deliberately keep tiptap-markdown's own defaults\n * (`bulletListMarker: \"-\"`, `tightLists: true`, `linkify: false`,\n * `breaks: false`) because those produce the most byte-stable GFM. See\n * RichMarkdownEditor.roundtrip.spec.ts for the pinned corpus.\n *\n * NFM (Content) opts into inline HTML passthrough (`html: true`) so\n * Notion-specific blocks survive a markdown round-trip; the rest mirrors the\n * Content editor's existing `Markdown.configure` call.\n */\nexport const MARKDOWN_DIALECT_CONFIG: Record<\n RichMarkdownDialect,\n Parameters<typeof Markdown.configure>[0]\n> = {\n gfm: {\n // GFM plans are the common case and must never gain raw HTML as a second\n // representation.\n html: false,\n // Keep tiptap-markdown's defaults that minimise first-edit normalisation\n // churn (see roundtrip spec). Listed explicitly so the contract is\n // self-documenting rather than relying on the package defaults.\n bulletListMarker: \"-\",\n tightLists: true,\n linkify: false,\n breaks: false,\n transformPastedText: true,\n transformCopiedText: true,\n },\n nfm: {\n // NFM is a superset that allows inline HTML so Notion blocks round-trip.\n html: true,\n transformPastedText: true,\n transformCopiedText: true,\n },\n};\n\nconst DEFAULT_FEATURES: Required<SharedEditorFeatures> = {\n tables: true,\n tasks: true,\n link: true,\n codeBlock: true,\n placeholder: true,\n markdown: true,\n // Off by default: only Plans opt in today. Content injects its own richer\n // image node via `extraExtensions` and must not get a second `image` node.\n image: false,\n};\n\n/**\n * The ONE editor extension factory shared by every embedder (Plans today,\n * Content next). It assembles the base Tiptap schema (StarterKit + Placeholder\n * + Link + tasks + tables + code block), the dialect-keyed {@link Markdown}\n * serializer, the optional Collaboration stack, and finally any app-specific\n * `extraExtensions`.\n *\n * Ordering matters:\n * 1. Base schema (StarterKit first so its nodes/marks register; `starterKit`\n * overrides let an app disable replaced nodes / swap the dropcursor).\n * 2. dialect-keyed Markdown serializer (suppressible via `features.markdown`\n * for apps that own the whole serialize/parse pipeline, e.g. Content's NFM).\n * 3. `extraExtensions` (Notion/media/etc.) — appended before Collaboration so\n * apps can extend the schema and Collaboration still binds over the full\n * schema.\n * 4. Collaboration (+ CollaborationCaret) LAST so they bind over everything.\n *\n * Content (the NFM editor) drives this factory with `features.placeholder` and\n * `features.markdown` off, `features.tasks/tables/link` off where it ships its\n * own, a `starterKit` override disabling paragraph/blockquote/codeBlock, and all\n * Notion/media/fidelity nodes + its own Markdown(NFM)/Placeholder via\n * `extraExtensions` — so it shares the StarterKit base + the collab wiring while\n * owning its byte-identical NFM serializer.\n */\nexport function createSharedEditorExtensions({\n dialect = \"gfm\",\n // `preset` is accepted and forwarded for future preset-specific schema\n // branches; it is currently schema-neutral.\n preset: _preset = \"plan\",\n placeholder = \"Type '/' for commands...\",\n features,\n starterKit,\n markdown,\n extraExtensions = [],\n collab = null,\n onImageUpload = null,\n}: CreateSharedEditorExtensionsOptions = {}): Array<Extension | Node | Mark> {\n const feat = { ...DEFAULT_FEATURES, ...(features ?? {}) };\n const ydoc = collab?.ydoc ?? null;\n const awareness = collab?.awareness ?? null;\n const user = collab?.user ?? null;\n\n const exts: Array<Extension | Node | Mark> = [\n StarterKit.configure({\n heading: { levels: [1, 2, 3, 4] },\n link: false,\n // StarterKit's plain code block is always disabled; when enabled we add the\n // syntax-highlighting `CodeBlockLowlight` (same `codeBlock` node) below.\n codeBlock: false,\n dropcursor: { color: \"hsl(var(--ring))\", width: 2 },\n // Yjs owns undo/redo when Collaboration is active; the StarterKit history\n // plugin and the CRDT cannot both track undo without corrupting state.\n ...(ydoc ? { undoRedo: false } : {}),\n // App overrides last so embedders can disable replaced nodes (paragraph,\n // blockquote, code block) or swap the dropcursor while keeping the shared\n // base + the collab undo/redo gating above.\n ...(starterKit ?? {}),\n }),\n ];\n\n // Syntax-highlighted code block (replaces StarterKit's plain one) only when the\n // embedder opts in via `features.codeBlock`. Content disables it and ships its\n // own code node, so this affects Plans (and future opt-in apps) alone.\n if (feat.codeBlock) {\n exts.push(\n CodeBlockLowlight.configure({\n lowlight: codeLowlight,\n defaultLanguage: null,\n }),\n );\n }\n\n if (feat.placeholder) {\n exts.push(\n Placeholder.configure({\n placeholder: ({ node }) => {\n if (node.type.name === \"heading\") {\n const level = node.attrs.level;\n if (level === 1) return \"Heading 1\";\n if (level === 2) return \"Heading 2\";\n if (level === 3) return \"Heading 3\";\n return \"Heading 4\";\n }\n return placeholder;\n },\n showOnlyWhenEditable: true,\n showOnlyCurrent: true,\n }),\n );\n }\n\n if (feat.link) {\n exts.push(\n Link.configure({\n openOnClick: false,\n HTMLAttributes: { class: \"an-rich-md-link\" },\n }),\n );\n }\n\n if (feat.tasks) {\n exts.push(\n TaskList.configure({\n HTMLAttributes: { class: \"an-rich-md-task-list\" },\n }),\n TaskItem.configure({ nested: true }),\n );\n }\n\n if (feat.tables) {\n exts.push(\n Table.configure({\n resizable: false,\n HTMLAttributes: { class: \"an-rich-md-table\" },\n }),\n TableRow,\n TableHeader,\n TableCell,\n );\n }\n\n if (feat.markdown) {\n exts.push(Markdown.configure(markdown ?? MARKDOWN_DIALECT_CONFIG[dialect]));\n }\n\n // Shared block-level image node. The node is named `image`, so when\n // `features.markdown` is on, tiptap-markdown serializes it through its\n // built-in `defaultMarkdownSerializer.nodes.image` fallback → ``\n // (no width-as-HTML override here, so GFM stays byte-stable and\n // source-syncable). With an `onImageUpload` it accepts paste/drop uploads.\n if (feat.image) {\n exts.push(createImageExtension({ onImageUpload }));\n }\n\n // App-specific extensions (Notion/media/drag handles/comments). Appended\n // before Collaboration so they can extend the schema and Collaboration binds\n // over the full set.\n if (extraExtensions.length > 0) {\n exts.push(...extraExtensions);\n }\n\n // Collaborative editing via the shared Y.Doc. Markdown stays the canonical\n // saved representation (onChange serializes it); the Y.Doc is transient live\n // state only. Appended last so it binds over the configured schema above.\n if (ydoc) {\n exts.push(Collaboration.configure({ document: ydoc }));\n // Live multi-user cursors. Only mounted alongside a Y.Doc so the standalone\n // controlled editor (today's plan/content behavior) is untouched.\n if (awareness) {\n exts.push(\n CollaborationCaret.configure({\n provider: { awareness },\n user: user ?? { name: \"Anonymous\", color: \"#999\" },\n }),\n );\n }\n }\n\n return exts;\n}\n"]}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { createSharedEditorExtensions, MARKDOWN_DIALECT_CONFIG, type RichMarkdownDialect, type RichMarkdownEditorPreset, type RichMarkdownCollabUser, type SharedEditorCollab, type SharedEditorFeatures, type CreateSharedEditorExtensionsOptions, } from "./extensions.js";
|
|
2
|
+
export { useCollabReconcile, getEditorMarkdown, type UseCollabReconcileOptions, type UseCollabReconcileResult, } from "./useCollabReconcile.js";
|
|
3
|
+
export { SlashCommandMenu, DEFAULT_SLASH_COMMANDS, createImageSlashCommand, type SlashCommandItem, type SlashCommandMenuProps, } from "./SlashCommandMenu.js";
|
|
4
|
+
export { SharedImage, createImageExtension, pickAndInsertImage, type ImageUploadFn, type SharedImageOptions, } from "./ImageExtension.js";
|
|
5
|
+
export { uploadEditorImage } from "./uploadEditorImage.js";
|
|
6
|
+
export { BubbleToolbar, buildDefaultBubbleItems, type BubbleToolbarItem, type BubbleToolbarProps, } from "./BubbleToolbar.js";
|
|
7
|
+
export { SharedRichEditor, type SharedRichEditorProps, } from "./SharedRichEditor.js";
|
|
8
|
+
export { RichMarkdownEditor, createRichMarkdownExtensions, type RichMarkdownEditorProps, type CreateRichMarkdownExtensionsOptions, } from "./RichMarkdownEditor.js";
|
|
9
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/client/rich-markdown-editor/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,4BAA4B,EAC5B,uBAAuB,EACvB,KAAK,mBAAmB,EACxB,KAAK,wBAAwB,EAC7B,KAAK,sBAAsB,EAC3B,KAAK,kBAAkB,EACvB,KAAK,oBAAoB,EACzB,KAAK,mCAAmC,GACzC,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,kBAAkB,EAClB,iBAAiB,EACjB,KAAK,yBAAyB,EAC9B,KAAK,wBAAwB,GAC9B,MAAM,yBAAyB,CAAC;AACjC,OAAO,EACL,gBAAgB,EAChB,sBAAsB,EACtB,uBAAuB,EACvB,KAAK,gBAAgB,EACrB,KAAK,qBAAqB,GAC3B,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,WAAW,EACX,oBAAoB,EACpB,kBAAkB,EAClB,KAAK,aAAa,EAClB,KAAK,kBAAkB,GACxB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,EACL,aAAa,EACb,uBAAuB,EACvB,KAAK,iBAAiB,EACtB,KAAK,kBAAkB,GACxB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,gBAAgB,EAChB,KAAK,qBAAqB,GAC3B,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,kBAAkB,EAClB,4BAA4B,EAC5B,KAAK,uBAAuB,EAC5B,KAAK,mCAAmC,GACzC,MAAM,yBAAyB,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { createSharedEditorExtensions, MARKDOWN_DIALECT_CONFIG, } from "./extensions.js";
|
|
2
|
+
export { useCollabReconcile, getEditorMarkdown, } from "./useCollabReconcile.js";
|
|
3
|
+
export { SlashCommandMenu, DEFAULT_SLASH_COMMANDS, createImageSlashCommand, } from "./SlashCommandMenu.js";
|
|
4
|
+
export { SharedImage, createImageExtension, pickAndInsertImage, } from "./ImageExtension.js";
|
|
5
|
+
export { uploadEditorImage } from "./uploadEditorImage.js";
|
|
6
|
+
export { BubbleToolbar, buildDefaultBubbleItems, } from "./BubbleToolbar.js";
|
|
7
|
+
export { SharedRichEditor, } from "./SharedRichEditor.js";
|
|
8
|
+
export { RichMarkdownEditor, createRichMarkdownExtensions, } from "./RichMarkdownEditor.js";
|
|
9
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/client/rich-markdown-editor/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,4BAA4B,EAC5B,uBAAuB,GAOxB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,kBAAkB,EAClB,iBAAiB,GAGlB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EACL,gBAAgB,EAChB,sBAAsB,EACtB,uBAAuB,GAGxB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,WAAW,EACX,oBAAoB,EACpB,kBAAkB,GAGnB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,EACL,aAAa,EACb,uBAAuB,GAGxB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,gBAAgB,GAEjB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,kBAAkB,EAClB,4BAA4B,GAG7B,MAAM,yBAAyB,CAAC","sourcesContent":["export {\n createSharedEditorExtensions,\n MARKDOWN_DIALECT_CONFIG,\n type RichMarkdownDialect,\n type RichMarkdownEditorPreset,\n type RichMarkdownCollabUser,\n type SharedEditorCollab,\n type SharedEditorFeatures,\n type CreateSharedEditorExtensionsOptions,\n} from \"./extensions.js\";\nexport {\n useCollabReconcile,\n getEditorMarkdown,\n type UseCollabReconcileOptions,\n type UseCollabReconcileResult,\n} from \"./useCollabReconcile.js\";\nexport {\n SlashCommandMenu,\n DEFAULT_SLASH_COMMANDS,\n createImageSlashCommand,\n type SlashCommandItem,\n type SlashCommandMenuProps,\n} from \"./SlashCommandMenu.js\";\nexport {\n SharedImage,\n createImageExtension,\n pickAndInsertImage,\n type ImageUploadFn,\n type SharedImageOptions,\n} from \"./ImageExtension.js\";\nexport { uploadEditorImage } from \"./uploadEditorImage.js\";\nexport {\n BubbleToolbar,\n buildDefaultBubbleItems,\n type BubbleToolbarItem,\n type BubbleToolbarProps,\n} from \"./BubbleToolbar.js\";\nexport {\n SharedRichEditor,\n type SharedRichEditorProps,\n} from \"./SharedRichEditor.js\";\nexport {\n RichMarkdownEditor,\n createRichMarkdownExtensions,\n type RichMarkdownEditorProps,\n type CreateRichMarkdownExtensionsOptions,\n} from \"./RichMarkdownEditor.js\";\n"]}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { ImageUploadFn } from "./ImageExtension.js";
|
|
2
|
+
/**
|
|
3
|
+
* The shared editor's default image uploader: upload a picked / pasted /
|
|
4
|
+
* dropped image File through the framework `upload-image` action and return the
|
|
5
|
+
* hosted CDN URL.
|
|
6
|
+
*
|
|
7
|
+
* This is the {@link ImageUploadFn} embedders pass to the shared image block so
|
|
8
|
+
* any app gets a real uploading image block with zero per-app upload code. The
|
|
9
|
+
* action re-hosts the bytes on the configured provider (Builder.io by default),
|
|
10
|
+
* is session-scoped, and returns a stable `` source — so a plan's
|
|
11
|
+
* inserted image autosaves as plain markdown through the existing
|
|
12
|
+
* `update-rich-text` path with no new persistence channel.
|
|
13
|
+
*
|
|
14
|
+
* @throws when the file cannot be read, the action returns no URL, or upload is
|
|
15
|
+
* not configured (with the action's "connect Builder.io" guidance).
|
|
16
|
+
*/
|
|
17
|
+
export declare const uploadEditorImage: ImageUploadFn;
|
|
18
|
+
//# sourceMappingURL=uploadEditorImage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"uploadEditorImage.d.ts","sourceRoot":"","sources":["../../../src/client/rich-markdown-editor/uploadEditorImage.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAgCzD;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,iBAAiB,EAAE,aAsB/B,CAAC"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { callAction } from "../use-action.js";
|
|
2
|
+
/**
|
|
3
|
+
* Read a {@link File} as a base64 data URL (`data:image/...;base64,...`).
|
|
4
|
+
*
|
|
5
|
+
* The framework `upload-image` action accepts a data URL (`data`) or a remote
|
|
6
|
+
* URL (`url`) — not raw multipart — so a browser file-picker must convert the
|
|
7
|
+
* File to a data URL first.
|
|
8
|
+
*/
|
|
9
|
+
function fileToDataUrl(file) {
|
|
10
|
+
return new Promise((resolve, reject) => {
|
|
11
|
+
const reader = new FileReader();
|
|
12
|
+
reader.onload = () => {
|
|
13
|
+
const result = reader.result;
|
|
14
|
+
if (typeof result === "string") {
|
|
15
|
+
resolve(result);
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
reject(new Error("Failed to read image file."));
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
reader.onerror = () => reject(reader.error ?? new Error("Failed to read image file."));
|
|
22
|
+
reader.readAsDataURL(file);
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* The shared editor's default image uploader: upload a picked / pasted /
|
|
27
|
+
* dropped image File through the framework `upload-image` action and return the
|
|
28
|
+
* hosted CDN URL.
|
|
29
|
+
*
|
|
30
|
+
* This is the {@link ImageUploadFn} embedders pass to the shared image block so
|
|
31
|
+
* any app gets a real uploading image block with zero per-app upload code. The
|
|
32
|
+
* action re-hosts the bytes on the configured provider (Builder.io by default),
|
|
33
|
+
* is session-scoped, and returns a stable `` source — so a plan's
|
|
34
|
+
* inserted image autosaves as plain markdown through the existing
|
|
35
|
+
* `update-rich-text` path with no new persistence channel.
|
|
36
|
+
*
|
|
37
|
+
* @throws when the file cannot be read, the action returns no URL, or upload is
|
|
38
|
+
* not configured (with the action's "connect Builder.io" guidance).
|
|
39
|
+
*/
|
|
40
|
+
export const uploadEditorImage = async (file) => {
|
|
41
|
+
if (!file.type.startsWith("image/")) {
|
|
42
|
+
throw new Error("Only image files can be uploaded.");
|
|
43
|
+
}
|
|
44
|
+
const dataUrl = await fileToDataUrl(file);
|
|
45
|
+
const result = await callAction("upload-image", {
|
|
46
|
+
data: dataUrl,
|
|
47
|
+
filename: file.name || undefined,
|
|
48
|
+
});
|
|
49
|
+
if (!result || typeof result.url !== "string" || !result.url) {
|
|
50
|
+
throw new Error(result?.error ||
|
|
51
|
+
"Image upload failed. Connect Builder.io in Settings → File uploads, then try again.");
|
|
52
|
+
}
|
|
53
|
+
// Use the filename (sans extension) as a reasonable default alt text.
|
|
54
|
+
const alt = file.name ? file.name.replace(/\.[^./\\]+$/, "") : "";
|
|
55
|
+
return { src: result.url, alt };
|
|
56
|
+
};
|
|
57
|
+
//# sourceMappingURL=uploadEditorImage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"uploadEditorImage.js","sourceRoot":"","sources":["../../../src/client/rich-markdown-editor/uploadEditorImage.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAG9C;;;;;;GAMG;AACH,SAAS,aAAa,CAAC,IAAU;IAC/B,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,MAAM,MAAM,GAAG,IAAI,UAAU,EAAE,CAAC;QAChC,MAAM,CAAC,MAAM,GAAG,GAAG,EAAE;YACnB,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC;YAC7B,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE,CAAC;gBAC/B,OAAO,CAAC,MAAM,CAAC,CAAC;YAClB,CAAC;iBAAM,CAAC;gBACN,MAAM,CAAC,IAAI,KAAK,CAAC,4BAA4B,CAAC,CAAC,CAAC;YAClD,CAAC;QACH,CAAC,CAAC;QACF,MAAM,CAAC,OAAO,GAAG,GAAG,EAAE,CACpB,MAAM,CAAC,MAAM,CAAC,KAAK,IAAI,IAAI,KAAK,CAAC,4BAA4B,CAAC,CAAC,CAAC;QAClE,MAAM,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC;IAC7B,CAAC,CAAC,CAAC;AACL,CAAC;AAQD;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAkB,KAAK,EAAE,IAAU,EAAE,EAAE;IACnE,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;QACpC,MAAM,IAAI,KAAK,CAAC,mCAAmC,CAAC,CAAC;IACvD,CAAC;IAED,MAAM,OAAO,GAAG,MAAM,aAAa,CAAC,IAAI,CAAC,CAAC;IAE1C,MAAM,MAAM,GAAG,MAAM,UAAU,CAA0B,cAAc,EAAE;QACvE,IAAI,EAAE,OAAO;QACb,QAAQ,EAAE,IAAI,CAAC,IAAI,IAAI,SAAS;KACjC,CAAC,CAAC;IAEH,IAAI,CAAC,MAAM,IAAI,OAAO,MAAM,CAAC,GAAG,KAAK,QAAQ,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC;QAC7D,MAAM,IAAI,KAAK,CACb,MAAM,EAAE,KAAK;YACX,qFAAqF,CACxF,CAAC;IACJ,CAAC;IAED,sEAAsE;IACtE,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,aAAa,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IAClE,OAAO,EAAE,GAAG,EAAE,MAAM,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC;AAClC,CAAC,CAAC","sourcesContent":["import { callAction } from \"../use-action.js\";\nimport type { ImageUploadFn } from \"./ImageExtension.js\";\n\n/**\n * Read a {@link File} as a base64 data URL (`data:image/...;base64,...`).\n *\n * The framework `upload-image` action accepts a data URL (`data`) or a remote\n * URL (`url`) — not raw multipart — so a browser file-picker must convert the\n * File to a data URL first.\n */\nfunction fileToDataUrl(file: File): Promise<string> {\n return new Promise((resolve, reject) => {\n const reader = new FileReader();\n reader.onload = () => {\n const result = reader.result;\n if (typeof result === \"string\") {\n resolve(result);\n } else {\n reject(new Error(\"Failed to read image file.\"));\n }\n };\n reader.onerror = () =>\n reject(reader.error ?? new Error(\"Failed to read image file.\"));\n reader.readAsDataURL(file);\n });\n}\n\ninterface UploadImageActionResult {\n url?: string;\n error?: string;\n configured?: boolean;\n}\n\n/**\n * The shared editor's default image uploader: upload a picked / pasted /\n * dropped image File through the framework `upload-image` action and return the\n * hosted CDN URL.\n *\n * This is the {@link ImageUploadFn} embedders pass to the shared image block so\n * any app gets a real uploading image block with zero per-app upload code. The\n * action re-hosts the bytes on the configured provider (Builder.io by default),\n * is session-scoped, and returns a stable `` source — so a plan's\n * inserted image autosaves as plain markdown through the existing\n * `update-rich-text` path with no new persistence channel.\n *\n * @throws when the file cannot be read, the action returns no URL, or upload is\n * not configured (with the action's \"connect Builder.io\" guidance).\n */\nexport const uploadEditorImage: ImageUploadFn = async (file: File) => {\n if (!file.type.startsWith(\"image/\")) {\n throw new Error(\"Only image files can be uploaded.\");\n }\n\n const dataUrl = await fileToDataUrl(file);\n\n const result = await callAction<UploadImageActionResult>(\"upload-image\", {\n data: dataUrl,\n filename: file.name || undefined,\n });\n\n if (!result || typeof result.url !== \"string\" || !result.url) {\n throw new Error(\n result?.error ||\n \"Image upload failed. Connect Builder.io in Settings → File uploads, then try again.\",\n );\n }\n\n // Use the filename (sans extension) as a reasonable default alt text.\n const alt = file.name ? file.name.replace(/\\.[^./\\\\]+$/, \"\") : \"\";\n return { src: result.url, alt };\n};\n"]}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { type MutableRefObject } from "react";
|
|
2
|
+
import type { Editor } from "@tiptap/react";
|
|
3
|
+
import type { Transaction } from "@tiptap/pm/state";
|
|
4
|
+
import type { Doc as YDoc } from "yjs";
|
|
5
|
+
import type { Awareness } from "y-protocols/awareness";
|
|
6
|
+
/** Reads the current markdown out of the tiptap-markdown storage. */
|
|
7
|
+
export declare function getEditorMarkdown(editor: Editor): string;
|
|
8
|
+
export interface UseCollabReconcileOptions {
|
|
9
|
+
/** The live editor, or null until it mounts. */
|
|
10
|
+
editor: Editor | null;
|
|
11
|
+
/** Shared Y.Doc when collaborating; null disables all collab paths. */
|
|
12
|
+
ydoc?: YDoc | null;
|
|
13
|
+
/** Shared awareness; null keeps the sole-client lead path. */
|
|
14
|
+
awareness?: Awareness | null;
|
|
15
|
+
/** Authoritative markdown value (SQL source of truth). */
|
|
16
|
+
value: string;
|
|
17
|
+
/** Timestamp of the authoritative value; gates newer-than reconcile. */
|
|
18
|
+
contentUpdatedAt?: string | null;
|
|
19
|
+
/** Whether the editor accepts edits. Reconcile/seed only run for the live editor. */
|
|
20
|
+
editable: boolean;
|
|
21
|
+
/**
|
|
22
|
+
* Reads the current markdown from the editor. Injected so a dialect could
|
|
23
|
+
* swap serializers; defaults to the tiptap-markdown storage reader. For an app
|
|
24
|
+
* with a custom serializer (e.g. Content's `docToNfm(editor.getJSON())`), pass
|
|
25
|
+
* it here so the seed/reconcile equality checks compare like-for-like.
|
|
26
|
+
*/
|
|
27
|
+
getMarkdown?: (editor: Editor) => string;
|
|
28
|
+
/**
|
|
29
|
+
* Applies the authoritative `value` into the editor. Defaults to passing the
|
|
30
|
+
* raw markdown string to `editor.commands.setContent`. Apps whose serializer
|
|
31
|
+
* is NOT tiptap-markdown (Content parses `nfmToDoc(value)` into a PM doc)
|
|
32
|
+
* override this so seed + reconcile write the correct content shape. The
|
|
33
|
+
* supplied `options` carry the history/whitespace flags the default path uses;
|
|
34
|
+
* a custom implementation should forward them when relevant.
|
|
35
|
+
*/
|
|
36
|
+
setContent?: (editor: Editor, value: string, options: {
|
|
37
|
+
emitUpdate?: boolean;
|
|
38
|
+
addToHistory?: boolean;
|
|
39
|
+
}) => void;
|
|
40
|
+
/**
|
|
41
|
+
* Normalizes the authoritative `value` to the canonical markdown the editor
|
|
42
|
+
* would emit, so the "already in sync / our own echo" equality checks match a
|
|
43
|
+
* serializer that re-canonicalizes (Content's `canonicalizeNfm`). Defaults to
|
|
44
|
+
* identity (GFM already round-trips byte-stably).
|
|
45
|
+
*/
|
|
46
|
+
normalizeValue?: (value: string) => string;
|
|
47
|
+
/**
|
|
48
|
+
* Decides whether the empty-doc seed should run for the current shared
|
|
49
|
+
* fragment. Defaults to "fragment has no nodes, or the editor holds no
|
|
50
|
+
* semantic markdown". Apps with sentinel-empty content (Content's
|
|
51
|
+
* `<empty-block/>` filler) override this. Receives the live fragment length
|
|
52
|
+
* and the editor's current markdown.
|
|
53
|
+
*/
|
|
54
|
+
shouldSeed?: (info: {
|
|
55
|
+
value: string;
|
|
56
|
+
currentMarkdown: string;
|
|
57
|
+
fragmentLength: number;
|
|
58
|
+
}) => boolean;
|
|
59
|
+
/**
|
|
60
|
+
* The initial "applied" watermark. Default mirrors `contentUpdatedAt`, so a
|
|
61
|
+
* fresh mount whose Y.Doc already matches SQL doesn't re-apply. Pass `null`
|
|
62
|
+
* to force the first reconcile pass to adopt authoritative SQL even at the
|
|
63
|
+
* same timestamp — Content does this so a stale persisted Y.Doc (an agent that
|
|
64
|
+
* edited the CLOSED doc) is corrected on open. The editor is keyed per
|
|
65
|
+
* document upstream, so this only affects the first mount of each doc.
|
|
66
|
+
*/
|
|
67
|
+
initialAppliedUpdatedAt?: string | null;
|
|
68
|
+
}
|
|
69
|
+
export interface UseCollabReconcileResult {
|
|
70
|
+
/** True when a Y.Doc is bound (collaborative editing active). */
|
|
71
|
+
collab: boolean;
|
|
72
|
+
/**
|
|
73
|
+
* Set true around any programmatic `setContent` so the editor's `onUpdate`
|
|
74
|
+
* can ignore the resulting transaction (it isn't a user edit).
|
|
75
|
+
*/
|
|
76
|
+
isSettingContentRef: MutableRefObject<boolean>;
|
|
77
|
+
/**
|
|
78
|
+
* Call from `onUpdate` BEFORE serializing. Returns true when the update must
|
|
79
|
+
* be ignored: editor not editable, mid-programmatic-setContent, or (in collab
|
|
80
|
+
* mode) a remote-origin transaction. Also records the local typing time.
|
|
81
|
+
*/
|
|
82
|
+
shouldIgnoreUpdate: (transaction: Transaction) => boolean;
|
|
83
|
+
/**
|
|
84
|
+
* Call from `onUpdate` AFTER computing the markdown to emit. Returns false
|
|
85
|
+
* when the value must NOT be persisted yet (an empty collab doc before the
|
|
86
|
+
* seed has run); records it as the last-emitted value otherwise.
|
|
87
|
+
*/
|
|
88
|
+
registerEmitted: (markdown: string) => boolean;
|
|
89
|
+
}
|
|
90
|
+
export declare function useCollabReconcile({ editor, ydoc, awareness, value, contentUpdatedAt, editable, getMarkdown, setContent, normalizeValue, shouldSeed, initialAppliedUpdatedAt, }: UseCollabReconcileOptions): UseCollabReconcileResult;
|
|
91
|
+
//# sourceMappingURL=useCollabReconcile.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useCollabReconcile.d.ts","sourceRoot":"","sources":["../../../src/client/rich-markdown-editor/useCollabReconcile.ts"],"names":[],"mappings":"AAAA,OAAO,EAA+B,KAAK,gBAAgB,EAAE,MAAM,OAAO,CAAC;AAC3E,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAEpD,OAAO,KAAK,EAAE,GAAG,IAAI,IAAI,EAAE,MAAM,KAAK,CAAC;AACvC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAIvD,qEAAqE;AACrE,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAKxD;AAED,MAAM,WAAW,yBAAyB;IACxC,gDAAgD;IAChD,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,uEAAuE;IACvE,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IACnB,8DAA8D;IAC9D,SAAS,CAAC,EAAE,SAAS,GAAG,IAAI,CAAC;IAC7B,0DAA0D;IAC1D,KAAK,EAAE,MAAM,CAAC;IACd,wEAAwE;IACxE,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,qFAAqF;IACrF,QAAQ,EAAE,OAAO,CAAC;IAClB;;;;;OAKG;IACH,WAAW,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,MAAM,CAAC;IACzC;;;;;;;OAOG;IACH,UAAU,CAAC,EAAE,CACX,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,EACb,OAAO,EAAE;QAAE,UAAU,CAAC,EAAE,OAAO,CAAC;QAAC,YAAY,CAAC,EAAE,OAAO,CAAA;KAAE,KACtD,IAAI,CAAC;IACV;;;;;OAKG;IACH,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;IAC3C;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE;QAClB,KAAK,EAAE,MAAM,CAAC;QACd,eAAe,EAAE,MAAM,CAAC;QACxB,cAAc,EAAE,MAAM,CAAC;KACxB,KAAK,OAAO,CAAC;IACd;;;;;;;OAOG;IACH,uBAAuB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACzC;AAED,MAAM,WAAW,wBAAwB;IACvC,iEAAiE;IACjE,MAAM,EAAE,OAAO,CAAC;IAChB;;;OAGG;IACH,mBAAmB,EAAE,gBAAgB,CAAC,OAAO,CAAC,CAAC;IAC/C;;;;OAIG;IACH,kBAAkB,EAAE,CAAC,WAAW,EAAE,WAAW,KAAK,OAAO,CAAC;IAC1D;;;;OAIG;IACH,eAAe,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC;CAChD;AAoED,wBAAgB,kBAAkB,CAAC,EACjC,MAAM,EACN,IAAW,EACX,SAAgB,EAChB,KAAK,EACL,gBAAgB,EAChB,QAAQ,EACR,WAA+B,EAC/B,UAA8B,EAC9B,cAAyB,EACzB,UAA8B,EAC9B,uBAAuB,GACxB,EAAE,yBAAyB,GAAG,wBAAwB,CAiStD"}
|
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
import { useEffect, useRef, useState } from "react";
|
|
2
|
+
import { isChangeOrigin } from "@tiptap/extension-collaboration";
|
|
3
|
+
import { isReconcileLeadClient } from "../../collab/client.js";
|
|
4
|
+
import { AGENT_CLIENT_ID } from "../../collab/agent-identity.js";
|
|
5
|
+
/** Reads the current markdown out of the tiptap-markdown storage. */
|
|
6
|
+
export function getEditorMarkdown(editor) {
|
|
7
|
+
const markdownStorage = editor.storage;
|
|
8
|
+
return markdownStorage.markdown?.getMarkdown?.() ?? "";
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* The subtle seed / reconcile / lead-client logic for the shared markdown
|
|
12
|
+
* editor, extracted once so it can never be duplicated across embedders.
|
|
13
|
+
*
|
|
14
|
+
* Responsibilities (reproducing the Plan editor's behavior exactly):
|
|
15
|
+
* - Track whether THIS client is the reconcile lead (sole client always leads;
|
|
16
|
+
* otherwise elected via {@link isReconcileLeadClient}) and how many other
|
|
17
|
+
* visible human peers are present.
|
|
18
|
+
* - Seed an empty shared Y.Doc once from `value` — lead client only — so two
|
|
19
|
+
* clients opening a brand-new block don't both insert the content.
|
|
20
|
+
* - Reconcile authoritative external markdown (agent edit, source patch, peer
|
|
21
|
+
* edit mirrored to SQL) into the editor: in collab mode only the lead client
|
|
22
|
+
* applies it through `setContent` and Yjs propagates; in non-collab mode this
|
|
23
|
+
* is the original controlled-value reconcile.
|
|
24
|
+
* - Provide the `onUpdate` guards (`shouldIgnoreUpdate`, `registerEmitted`) so
|
|
25
|
+
* the component never persists remote-origin or pre-seed empty content.
|
|
26
|
+
*/
|
|
27
|
+
/** Default seed predicate: seed only when the shared doc is genuinely empty. */
|
|
28
|
+
function defaultShouldSeed({ currentMarkdown, fragmentLength, }) {
|
|
29
|
+
return fragmentLength === 0 || !currentMarkdown.trim();
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Default content writer: hand the raw markdown string to `setContent`, which
|
|
33
|
+
* tiptap-markdown overrides to parse the markdown into a ProseMirror doc.
|
|
34
|
+
*
|
|
35
|
+
* IMPORTANT: do NOT pass `parseOptions: { preserveWhitespace: "full" }` here.
|
|
36
|
+
* In tiptap v3 the core `setContent` command routes `preserveWhitespace: "full"`
|
|
37
|
+
* through `insertContentAt`, which tiptap-markdown ALSO overrides to re-run its
|
|
38
|
+
* markdown parser. That double-parse stringifies the already-parsed PM doc and
|
|
39
|
+
* re-parses it as HTML, so a clean heading/list/code block comes back as the
|
|
40
|
+
* escaped, non-idempotent `<h1>…` — which then escalates every reconcile
|
|
41
|
+
* cycle (`<p>` → `<p>` → `&lt;p&gt;` …). Letting the markdown
|
|
42
|
+
* override parse the string directly (no `parseOptions`) round-trips byte-stably
|
|
43
|
+
* for the GFM corpus, including code-block and empty-line whitespace. Content's
|
|
44
|
+
* NFM path supplies its own `setContent` (it passes a pre-parsed PM doc) and is
|
|
45
|
+
* unaffected by this default.
|
|
46
|
+
*/
|
|
47
|
+
function defaultSetContent(editor, value, options) {
|
|
48
|
+
if (options.addToHistory === false) {
|
|
49
|
+
editor
|
|
50
|
+
.chain()
|
|
51
|
+
.command(({ tr }) => {
|
|
52
|
+
// addToHistory:false so cmd+z (or Yjs undo) doesn't erase
|
|
53
|
+
// externally-loaded content.
|
|
54
|
+
tr.setMeta("addToHistory", false);
|
|
55
|
+
return true;
|
|
56
|
+
})
|
|
57
|
+
.setContent(value, { emitUpdate: options.emitUpdate })
|
|
58
|
+
.run();
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
editor.commands.setContent(value);
|
|
62
|
+
}
|
|
63
|
+
export function useCollabReconcile({ editor, ydoc = null, awareness = null, value, contentUpdatedAt, editable, getMarkdown = getEditorMarkdown, setContent = defaultSetContent, normalizeValue = (v) => v, shouldSeed = defaultShouldSeed, initialAppliedUpdatedAt, }) {
|
|
64
|
+
const collab = !!ydoc;
|
|
65
|
+
const isSettingContentRef = useRef(false);
|
|
66
|
+
const lastEmittedRef = useRef("");
|
|
67
|
+
const lastTypedAtRef = useRef(0);
|
|
68
|
+
// The raw authoritative `value` string the reconcile last applied. When the
|
|
69
|
+
// SAME raw string is re-fetched (a lagging poll, or a source-sync that keeps
|
|
70
|
+
// re-supplying the same stored markdown), applying it again would only
|
|
71
|
+
// reproduce the doc we already hold — and if `value` is NON-idempotent
|
|
72
|
+
// (serialize(parse(value)) !== value) re-applying compounds the divergence
|
|
73
|
+
// every cycle (`<p>` → `<p>` → `&lt;p&gt;` …). Tracked so the
|
|
74
|
+
// identical re-fetch is recognized and skipped.
|
|
75
|
+
const lastAppliedValueRef = useRef(null);
|
|
76
|
+
// The editor's SERIALIZED output captured right AFTER the last reconcile/seed
|
|
77
|
+
// apply (`getMarkdown(editor)` once the content settled). For non-idempotent
|
|
78
|
+
// input this is what autosave actually persists, so the NEXT poll hands it
|
|
79
|
+
// back as the new `value`. Comparing the incoming value against this lets the
|
|
80
|
+
// reconcile recognize its own echo even when the raw string changed once, so
|
|
81
|
+
// it never re-parses content the editor already represents. This is the
|
|
82
|
+
// doc-equivalence guard that breaks the escalation loop.
|
|
83
|
+
const lastAppliedSerializedRef = useRef(null);
|
|
84
|
+
const lastAppliedUpdatedAtRef = useRef(initialAppliedUpdatedAt !== undefined
|
|
85
|
+
? initialAppliedUpdatedAt
|
|
86
|
+
: (contentUpdatedAt ?? null));
|
|
87
|
+
// Whether THIS client is the one that seeds the empty shared doc / applies an
|
|
88
|
+
// authoritative external snapshot into it. Exactly one client does, so the
|
|
89
|
+
// content isn't inserted once per open editor. A sole client always leads.
|
|
90
|
+
const [isLeadClient, setIsLeadClient] = useState(true);
|
|
91
|
+
// Count of OTHER visible human collaborators. When >0, a peer's edit also
|
|
92
|
+
// arrives via Yjs, so external markdown reconcile must defer (avoid applying
|
|
93
|
+
// the same change through both Yjs and setContent).
|
|
94
|
+
const peerCountRef = useRef(0);
|
|
95
|
+
useEffect(() => {
|
|
96
|
+
if (!collab || !awareness || !ydoc) {
|
|
97
|
+
setIsLeadClient(true);
|
|
98
|
+
peerCountRef.current = 0;
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
const update = () => {
|
|
102
|
+
setIsLeadClient(isReconcileLeadClient(awareness, ydoc.clientID));
|
|
103
|
+
let peers = 0;
|
|
104
|
+
awareness.getStates().forEach((state, clientId) => {
|
|
105
|
+
if (clientId === ydoc.clientID)
|
|
106
|
+
return; // self
|
|
107
|
+
if (clientId === AGENT_CLIENT_ID)
|
|
108
|
+
return; // agent isn't a Yjs editor
|
|
109
|
+
const s = state;
|
|
110
|
+
if (s && s.user && s.visible !== false)
|
|
111
|
+
peers += 1;
|
|
112
|
+
});
|
|
113
|
+
peerCountRef.current = peers;
|
|
114
|
+
};
|
|
115
|
+
update();
|
|
116
|
+
awareness.on("change", update);
|
|
117
|
+
document.addEventListener("visibilitychange", update);
|
|
118
|
+
return () => {
|
|
119
|
+
awareness.off("change", update);
|
|
120
|
+
document.removeEventListener("visibilitychange", update);
|
|
121
|
+
};
|
|
122
|
+
}, [collab, awareness, ydoc]);
|
|
123
|
+
// Collab seed: populate an empty shared Y.Doc from the markdown `value` once.
|
|
124
|
+
// The Collaboration extension does NOT auto-seed; only the lead client does,
|
|
125
|
+
// so two clients opening a brand-new block at once don't both seed (which
|
|
126
|
+
// would duplicate the content via concurrent inserts at the same position).
|
|
127
|
+
const seededRef = useRef(false);
|
|
128
|
+
useEffect(() => {
|
|
129
|
+
if (!collab || !editor || editor.isDestroyed || !ydoc)
|
|
130
|
+
return;
|
|
131
|
+
if (seededRef.current)
|
|
132
|
+
return;
|
|
133
|
+
if (!isLeadClient)
|
|
134
|
+
return;
|
|
135
|
+
if (!value.trim())
|
|
136
|
+
return;
|
|
137
|
+
const fragment = ydoc.getXmlFragment("default");
|
|
138
|
+
const currentMarkdown = getMarkdown(editor);
|
|
139
|
+
// Seed only when the shared doc is genuinely empty — either the fragment has
|
|
140
|
+
// no nodes yet, or it holds no semantic markdown (an empty paragraph, or an
|
|
141
|
+
// app's sentinel-empty filler via a custom `shouldSeed`).
|
|
142
|
+
if (shouldSeed({ value, currentMarkdown, fragmentLength: fragment.length })) {
|
|
143
|
+
isSettingContentRef.current = true;
|
|
144
|
+
setContent(editor, value, {});
|
|
145
|
+
isSettingContentRef.current = false;
|
|
146
|
+
const serialized = getMarkdown(editor);
|
|
147
|
+
lastEmittedRef.current = serialized;
|
|
148
|
+
lastAppliedValueRef.current = value;
|
|
149
|
+
lastAppliedSerializedRef.current = serialized;
|
|
150
|
+
if (contentUpdatedAt)
|
|
151
|
+
lastAppliedUpdatedAtRef.current = contentUpdatedAt;
|
|
152
|
+
}
|
|
153
|
+
seededRef.current = true;
|
|
154
|
+
}, [
|
|
155
|
+
collab,
|
|
156
|
+
editor,
|
|
157
|
+
ydoc,
|
|
158
|
+
value,
|
|
159
|
+
isLeadClient,
|
|
160
|
+
contentUpdatedAt,
|
|
161
|
+
getMarkdown,
|
|
162
|
+
setContent,
|
|
163
|
+
shouldSeed,
|
|
164
|
+
]);
|
|
165
|
+
// Reconcile authoritative external markdown (agent edit, source patch, or a
|
|
166
|
+
// peer edit mirrored to SQL) into the live editor. In collab mode only the
|
|
167
|
+
// lead client applies it through setContent; Yjs propagates the result to
|
|
168
|
+
// every other client. In non-collab mode this is the original controlled-value
|
|
169
|
+
// reconcile, unchanged.
|
|
170
|
+
useEffect(() => {
|
|
171
|
+
if (!editor || editor.isDestroyed)
|
|
172
|
+
return;
|
|
173
|
+
let cancelled = false;
|
|
174
|
+
let retry = null;
|
|
175
|
+
// With peers present, a peer's edit also arrives via Yjs. Defer one poll
|
|
176
|
+
// cycle (+margin) and re-check before applying via setContent so the same
|
|
177
|
+
// change isn't inserted twice (Yjs + setContent → duplicated region).
|
|
178
|
+
const PEER_SETTLE_MS = 2500;
|
|
179
|
+
const apply = (deferred = false) => {
|
|
180
|
+
if (cancelled || editor.isDestroyed)
|
|
181
|
+
return;
|
|
182
|
+
// In collab mode, defer all reconcile until the shared doc is seeded so we
|
|
183
|
+
// never setContent over an unseeded fragment.
|
|
184
|
+
if (collab && !seededRef.current) {
|
|
185
|
+
retry = setTimeout(() => apply(deferred), 300);
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
const currentMarkdown = getMarkdown(editor);
|
|
189
|
+
// Compare against the canonical form the editor would emit so a serializer
|
|
190
|
+
// that re-normalizes (Content's NFM) still recognizes "already in sync".
|
|
191
|
+
const normalizedValue = normalizeValue(value);
|
|
192
|
+
// Whether the editor still holds exactly what THIS hook last applied (the
|
|
193
|
+
// user hasn't edited since). Only then are the round-trip echo guards
|
|
194
|
+
// below safe: if the user has since edited away from the applied content,
|
|
195
|
+
// an external snapshot equal to a previously-applied value is a real
|
|
196
|
+
// revert and must NOT be swallowed as an echo.
|
|
197
|
+
const editorUnchangedSinceApply = lastAppliedSerializedRef.current !== null &&
|
|
198
|
+
currentMarkdown === lastAppliedSerializedRef.current;
|
|
199
|
+
// Doc-equivalence skip. Never re-apply content the editor already
|
|
200
|
+
// represents — comparing by DOC EQUIVALENCE, not raw strings/timestamps:
|
|
201
|
+
// 1. `currentMarkdown === normalizedValue` — the editor's CURRENT
|
|
202
|
+
// serialized doc already equals the (normalized) incoming value.
|
|
203
|
+
// 2. `value === lastEmittedRef.current` — the incoming value is our own
|
|
204
|
+
// just-emitted markdown echoing back.
|
|
205
|
+
// 3. `value === lastAppliedValueRef.current` — the SAME raw value we
|
|
206
|
+
// already applied is being re-supplied (a lagging poll or a
|
|
207
|
+
// source-sync re-handing the same stored markdown). Applying it again
|
|
208
|
+
// would only reproduce the doc we hold; for NON-idempotent input it
|
|
209
|
+
// would compound divergence. Guarded by `editorUnchangedSinceApply`
|
|
210
|
+
// so a deliberate revert-to-previous after a local edit still lands.
|
|
211
|
+
// 4. `normalizedValue === lastAppliedSerializedRef.current` — the
|
|
212
|
+
// incoming value round-trips to the serialized output we last
|
|
213
|
+
// produced (our own autosaved echo coming back from SQL). For
|
|
214
|
+
// non-idempotent input the raw string differs from what we were
|
|
215
|
+
// handed, but it is doc-equivalent to what the editor already shows,
|
|
216
|
+
// so re-parsing it must be skipped. This is the guard that stops the
|
|
217
|
+
// `<p>` → `<p>` → `&lt;p&gt;` escalation.
|
|
218
|
+
if (currentMarkdown === normalizedValue ||
|
|
219
|
+
value === lastEmittedRef.current ||
|
|
220
|
+
(editorUnchangedSinceApply &&
|
|
221
|
+
(value === lastAppliedValueRef.current ||
|
|
222
|
+
normalizedValue === lastAppliedSerializedRef.current))) {
|
|
223
|
+
if (contentUpdatedAt) {
|
|
224
|
+
lastAppliedUpdatedAtRef.current = contentUpdatedAt;
|
|
225
|
+
}
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
const externalNewer = !lastAppliedUpdatedAtRef.current ||
|
|
229
|
+
!contentUpdatedAt ||
|
|
230
|
+
contentUpdatedAt > lastAppliedUpdatedAtRef.current;
|
|
231
|
+
// Only the lead client applies an authoritative snapshot into the shared
|
|
232
|
+
// Y.Doc; peers receive it through Yjs sync.
|
|
233
|
+
if (collab && !isLeadClient) {
|
|
234
|
+
if (contentUpdatedAt && !externalNewer) {
|
|
235
|
+
lastAppliedUpdatedAtRef.current = contentUpdatedAt;
|
|
236
|
+
}
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
// Never clobber an in-progress edit. While the user is actively typing
|
|
240
|
+
// (focused and a keystroke landed within the window) defer and re-check —
|
|
241
|
+
// applying external content now would yank text out from under them and,
|
|
242
|
+
// for non-idempotent input, fight every keystroke. Newer external content
|
|
243
|
+
// retries so it still lands once they pause; older-or-equal content is a
|
|
244
|
+
// stale poll and is dropped outright while focused.
|
|
245
|
+
const typingRecently = editor.isFocused && Date.now() - lastTypedAtRef.current < 1500;
|
|
246
|
+
if (typingRecently) {
|
|
247
|
+
if (externalNewer) {
|
|
248
|
+
retry = setTimeout(() => apply(deferred), 700);
|
|
249
|
+
}
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
if (!externalNewer && editor.isFocused)
|
|
253
|
+
return;
|
|
254
|
+
// Race guard: with peers present, let Yjs deliver a peer's edit first.
|
|
255
|
+
// Defer once and re-check — a peer edit makes the equality check above
|
|
256
|
+
// no-op next pass; an agent/source edit still differs and applies.
|
|
257
|
+
if (collab && externalNewer && !deferred && peerCountRef.current > 0) {
|
|
258
|
+
retry = setTimeout(() => apply(true), PEER_SETTLE_MS);
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
queueMicrotask(() => {
|
|
262
|
+
if (cancelled || editor.isDestroyed)
|
|
263
|
+
return;
|
|
264
|
+
// Re-check doc-equivalence at apply time. Between the decision above and
|
|
265
|
+
// this microtask a peer/Yjs edit (or our own prior apply) may have made
|
|
266
|
+
// the editor already represent this value — re-applying would be a
|
|
267
|
+
// wasted setContent that, for non-idempotent input, re-triggers the
|
|
268
|
+
// loop. Skip when the editor's current serialization already matches the
|
|
269
|
+
// normalized value, or the value round-trips to what we last produced.
|
|
270
|
+
const beforeMarkdown = getMarkdown(editor);
|
|
271
|
+
const normalized = normalizeValue(value);
|
|
272
|
+
const unchangedSinceApply = lastAppliedSerializedRef.current !== null &&
|
|
273
|
+
beforeMarkdown === lastAppliedSerializedRef.current;
|
|
274
|
+
if (beforeMarkdown === normalized ||
|
|
275
|
+
(unchangedSinceApply &&
|
|
276
|
+
normalized === lastAppliedSerializedRef.current)) {
|
|
277
|
+
lastAppliedValueRef.current = value;
|
|
278
|
+
if (contentUpdatedAt) {
|
|
279
|
+
lastAppliedUpdatedAtRef.current = contentUpdatedAt;
|
|
280
|
+
}
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
isSettingContentRef.current = true;
|
|
284
|
+
setContent(editor, value, { emitUpdate: false, addToHistory: false });
|
|
285
|
+
isSettingContentRef.current = false;
|
|
286
|
+
// Capture the SERIALIZED result, not the raw value. For non-idempotent
|
|
287
|
+
// input these differ; recording the serialized output is what lets the
|
|
288
|
+
// next poll (which returns this serialized form) be recognized as our
|
|
289
|
+
// own echo and skipped — stabilizing the doc after exactly one apply.
|
|
290
|
+
const serialized = getMarkdown(editor);
|
|
291
|
+
lastEmittedRef.current = serialized;
|
|
292
|
+
lastAppliedValueRef.current = value;
|
|
293
|
+
lastAppliedSerializedRef.current = serialized;
|
|
294
|
+
if (contentUpdatedAt) {
|
|
295
|
+
lastAppliedUpdatedAtRef.current = contentUpdatedAt;
|
|
296
|
+
}
|
|
297
|
+
});
|
|
298
|
+
};
|
|
299
|
+
apply();
|
|
300
|
+
return () => {
|
|
301
|
+
cancelled = true;
|
|
302
|
+
if (retry)
|
|
303
|
+
clearTimeout(retry);
|
|
304
|
+
};
|
|
305
|
+
}, [
|
|
306
|
+
contentUpdatedAt,
|
|
307
|
+
editor,
|
|
308
|
+
value,
|
|
309
|
+
collab,
|
|
310
|
+
isLeadClient,
|
|
311
|
+
getMarkdown,
|
|
312
|
+
setContent,
|
|
313
|
+
normalizeValue,
|
|
314
|
+
]);
|
|
315
|
+
const shouldIgnoreUpdate = (transaction) => {
|
|
316
|
+
if (!editable || isSettingContentRef.current)
|
|
317
|
+
return true;
|
|
318
|
+
// In collab mode, never persist remote-originated changes (the initial Yjs
|
|
319
|
+
// state load or a peer's edit arriving via sync). Each client saves only its
|
|
320
|
+
// OWN local edits; a peer's edit is saved by that peer. Without this, a
|
|
321
|
+
// lagging Y.Doc load would write stale markdown over newer SQL.
|
|
322
|
+
if (collab && transaction && isChangeOrigin(transaction))
|
|
323
|
+
return true;
|
|
324
|
+
lastTypedAtRef.current = Date.now();
|
|
325
|
+
return false;
|
|
326
|
+
};
|
|
327
|
+
const registerEmitted = (markdown) => {
|
|
328
|
+
// Don't persist an empty doc before Collaboration has seeded — that would
|
|
329
|
+
// clobber the saved block content with an empty string.
|
|
330
|
+
if (collab && !markdown.trim())
|
|
331
|
+
return false;
|
|
332
|
+
lastEmittedRef.current = markdown;
|
|
333
|
+
return true;
|
|
334
|
+
};
|
|
335
|
+
return {
|
|
336
|
+
collab,
|
|
337
|
+
isSettingContentRef,
|
|
338
|
+
shouldIgnoreUpdate,
|
|
339
|
+
registerEmitted,
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
//# sourceMappingURL=useCollabReconcile.js.map
|