@agent-native/core 0.100.2 → 0.101.2
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 +2 -2
- package/corpus/core/CHANGELOG.md +65 -0
- package/corpus/core/docs/content/locales/ar-SA/pr-visual-recap.mdx +48 -30
- package/corpus/core/docs/content/locales/ar-SA/template-plan.mdx +7 -0
- package/corpus/core/docs/content/locales/de-DE/pr-visual-recap.mdx +48 -30
- package/corpus/core/docs/content/locales/de-DE/template-plan.mdx +7 -0
- package/corpus/core/docs/content/locales/es-ES/pr-visual-recap.mdx +48 -30
- package/corpus/core/docs/content/locales/es-ES/template-plan.mdx +14 -0
- package/corpus/core/docs/content/locales/fr-FR/pr-visual-recap.mdx +48 -30
- package/corpus/core/docs/content/locales/fr-FR/template-plan.mdx +7 -0
- package/corpus/core/docs/content/locales/hi-IN/pr-visual-recap.mdx +48 -30
- package/corpus/core/docs/content/locales/hi-IN/template-plan.mdx +7 -0
- package/corpus/core/docs/content/locales/ja-JP/pr-visual-recap.mdx +48 -30
- package/corpus/core/docs/content/locales/ja-JP/template-plan.mdx +7 -0
- package/corpus/core/docs/content/locales/ko-KR/pr-visual-recap.mdx +48 -30
- package/corpus/core/docs/content/locales/ko-KR/template-plan.mdx +7 -0
- package/corpus/core/docs/content/locales/pt-BR/pr-visual-recap.mdx +48 -30
- package/corpus/core/docs/content/locales/pt-BR/template-plan.mdx +14 -0
- package/corpus/core/docs/content/locales/zh-CN/pr-visual-recap.mdx +48 -30
- package/corpus/core/docs/content/locales/zh-CN/template-plan.mdx +7 -0
- package/corpus/core/docs/content/locales/zh-TW/pr-visual-recap.mdx +48 -30
- package/corpus/core/docs/content/locales/zh-TW/template-plan.mdx +7 -0
- package/corpus/core/docs/content/pr-visual-recap.mdx +50 -32
- package/corpus/core/docs/content/template-plan.mdx +14 -0
- package/corpus/core/docs/content/toolkit-comments-review.mdx +16 -2
- package/corpus/core/package.json +4 -1
- package/corpus/core/src/a2a/index.ts +2 -1
- package/corpus/core/src/a2a/server.ts +34 -9
- package/corpus/core/src/agent/production-agent.ts +56 -10
- package/corpus/core/src/cli/create.ts +47 -19
- package/corpus/core/src/cli/pr-visual-recap-workflow.ts +2 -3
- package/corpus/core/src/cli/recap.ts +3 -4932
- package/corpus/core/src/cli/skills-content/visual-plan-skill.ts +6 -4
- package/corpus/core/src/cli/skills-content/visual-recap-skill.ts +7 -1
- package/corpus/core/src/client/AgentPanel.tsx +19 -17
- package/corpus/core/src/client/analytics.ts +8 -1
- package/corpus/core/src/client/chat/markdown-renderer.tsx +54 -8
- package/corpus/core/src/client/chat/message-components.tsx +8 -2
- package/corpus/core/src/client/clipboard.ts +28 -1
- package/corpus/core/src/client/guided-questions.tsx +4 -0
- package/corpus/core/src/client/i18n.tsx +2 -1
- package/corpus/core/src/client/index.ts +4 -0
- package/corpus/core/src/client/review/ReviewCommentComposer.tsx +108 -0
- package/corpus/core/src/client/review/ReviewThreadPanel.tsx +482 -150
- package/corpus/core/src/client/review/index.ts +8 -0
- package/corpus/core/src/client/review/use-review.ts +35 -1
- package/corpus/core/src/client/use-agent-chat-context.ts +3 -2
- package/corpus/core/src/review/actions/create-review-comment.ts +2 -1
- package/corpus/core/src/review/actions/get-review-feedback.ts +6 -11
- package/corpus/core/src/review/actions/list-review-comments.ts +38 -4
- package/corpus/core/src/review/actions/reply-review-comment.ts +33 -22
- package/corpus/core/src/review/actions/resolve-review-thread.ts +29 -2
- package/corpus/core/src/review/actions/send-review-thread-to-agent.ts +62 -0
- package/corpus/core/src/review/identity.ts +101 -0
- package/corpus/core/src/review/index.ts +14 -0
- package/corpus/core/src/review/store.ts +315 -8
- package/corpus/core/src/review/types.ts +4 -0
- package/corpus/core/src/server/action-discovery.ts +4 -0
- package/corpus/core/src/server/action-routes.ts +125 -4
- package/corpus/core/src/server/agent-chat/plugin-options.ts +16 -0
- package/corpus/core/src/server/agent-chat-plugin.ts +1 -0
- package/corpus/core/src/server/index.ts +8 -0
- package/corpus/core/src/vite/action-types-plugin.ts +4 -0
- package/corpus/templates/analytics/changelog/2026-07-14-daily-dashboard-email-captures-authenticate-with-a-sessi.md +6 -0
- package/corpus/templates/analytics/server/lib/dashboard-report.ts +156 -3
- package/corpus/templates/assets/app/components/generation/GenerationResults.tsx +402 -142
- package/corpus/templates/assets/app/i18n/zh-TW.ts +1 -0
- package/corpus/templates/assets/app/i18n-data.ts +1 -0
- package/corpus/templates/assets/app/routes/library.tsx +187 -48
- package/corpus/templates/assets/changelog/2026-07-13-merged-the-candidates-panel-into-the-library-as-a-drafts-tab.md +6 -0
- package/corpus/templates/chat/app/lib/agent-page.tsx +40 -0
- package/corpus/templates/chat/app/routes/agent.tsx +5 -3
- package/corpus/templates/chat/changelog/2026-07-14-fixed-chat-template-startup-with-older-core-versions.md +6 -0
- package/corpus/templates/clips/.agents/skills/ai-video-tools/SKILL.md +5 -0
- package/corpus/templates/clips/AGENTS.md +4 -0
- package/corpus/templates/clips/actions/get-feature-flags.ts +30 -0
- package/corpus/templates/clips/actions/lib/transcript-preview.ts +48 -0
- package/corpus/templates/clips/actions/request-transcript.ts +41 -1
- package/corpus/templates/clips/actions/view-screen.ts +5 -9
- package/corpus/templates/clips/app/components/library/library-layout.tsx +1 -1
- package/corpus/templates/clips/app/components/player/video-player.tsx +39 -1
- package/corpus/templates/clips/app/hooks/use-desktop-promo.ts +6 -12
- package/corpus/templates/clips/app/hooks/use-mse-video-source.ts +146 -0
- package/corpus/templates/clips/app/lib/capture-install-options.ts +21 -0
- package/corpus/templates/clips/app/lib/fmp4.ts +271 -0
- package/corpus/templates/clips/app/lib/mse-video-loader.ts +585 -0
- package/corpus/templates/clips/app/routes/_app.dictate.tsx +0 -4
- package/corpus/templates/clips/app/routes/_app.meetings.$meetingId.tsx +5 -13
- package/corpus/templates/clips/app/routes/_app.meetings._index.tsx +2 -37
- package/corpus/templates/clips/app/routes/download.tsx +3 -2
- package/corpus/templates/clips/changelog/2026-07-10-shared-clips-now-start-playing-instantly-instead-of-showing-.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-14-clips-now-distinguishes-transcript-previews-from-incomplete-.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-14-desktop-app-prompts-stay-hidden-after-you-download-the-insta.md +6 -0
- package/corpus/templates/clips/desktop/src/lib/recorder.ts +5 -0
- package/corpus/templates/clips/desktop/src/overlays/recording-pill.tsx +10 -0
- package/corpus/templates/clips/desktop/src-tauri/src/lib.rs +4 -0
- package/corpus/templates/clips/desktop/src-tauri/src/native_screen/custom_capture.rs +2518 -0
- package/corpus/templates/clips/desktop/src-tauri/src/native_screen/live_upload.rs +353 -0
- package/corpus/templates/clips/desktop/src-tauri/src/native_screen.rs +558 -67
- package/corpus/templates/clips/desktop/src-tauri/src/recording_indicator.rs +11 -1
- package/corpus/templates/clips/desktop/src-tauri/src/remote_flags.rs +188 -0
- package/corpus/templates/clips/desktop/src-tauri/src/whisper_speech.rs +1 -1
- package/corpus/templates/clips/server/lib/post-finalize-dispatch.ts +1 -0
- package/corpus/templates/clips/server/plugins/agent-chat.ts +5 -0
- package/corpus/templates/clips/server/routes/api/_agent-native-background/post-finalize-worker.post.ts +5 -1
- package/corpus/templates/clips/shared/feature-flags.ts +41 -0
- package/corpus/templates/design/.agents/skills/design-generation/SKILL.md +3 -0
- package/corpus/templates/design/.agents/skills/design-review-feedback/SKILL.md +42 -0
- package/corpus/templates/design/AGENTS.md +6 -0
- package/corpus/templates/design/README.md +1 -0
- package/corpus/templates/design/actions/navigate.ts +4 -4
- package/corpus/templates/design/actions/view-screen.ts +116 -2
- package/corpus/templates/design/agent-native.app-skill.json +5 -0
- package/corpus/templates/design/app/components/design/DesignCanvas.tsx +52 -59
- package/corpus/templates/design/app/components/design/DesignExtensionsPanel.tsx +13 -9
- package/corpus/templates/design/app/components/design/EditPanel.tsx +79 -11
- package/corpus/templates/design/app/components/design/LayersPanel.tsx +78 -65
- package/corpus/templates/design/app/components/design/ReviewCommentsPanel.tsx +161 -0
- package/corpus/templates/design/app/components/design/ReviewStatusControl.tsx +96 -0
- package/corpus/templates/design/app/components/design/bridge/hit-test.bridge.ts +166 -0
- package/corpus/templates/design/app/components/layout/Layout.tsx +1 -0
- package/corpus/templates/design/app/components/visual-editor/ReviewCanvasPins.tsx +1077 -0
- package/corpus/templates/design/app/components/visual-editor/index.ts +1 -0
- package/corpus/templates/design/app/components/visual-editor/review-canvas-state.ts +42 -0
- package/corpus/templates/design/app/hooks/use-design-systems.ts +2 -2
- package/corpus/templates/design/app/hooks/use-navigation-state.ts +9 -6
- package/corpus/templates/design/app/hooks/use-question-flow.ts +7 -1
- package/corpus/templates/design/app/i18n/ar-SA.ts +56 -1
- package/corpus/templates/design/app/i18n/de-DE.ts +57 -1
- package/corpus/templates/design/app/i18n/en-US.ts +56 -1
- package/corpus/templates/design/app/i18n/es-ES.ts +56 -1
- package/corpus/templates/design/app/i18n/fr-FR.ts +57 -1
- package/corpus/templates/design/app/i18n/hi-IN.ts +56 -1
- package/corpus/templates/design/app/i18n/ja-JP.ts +56 -1
- package/corpus/templates/design/app/i18n/ko-KR.ts +56 -1
- package/corpus/templates/design/app/i18n/pt-BR.ts +57 -1
- package/corpus/templates/design/app/i18n/zh-CN.ts +56 -1
- package/corpus/templates/design/app/i18n/zh-TW.ts +51 -0
- package/corpus/templates/design/app/pages/Present.tsx +172 -14
- package/corpus/templates/design/app/pages/design-editor/overview-camera.ts +23 -0
- package/corpus/templates/design/app/pages/design-editor/tool-state.ts +10 -0
- package/corpus/templates/design/app/pages/present-review-state.ts +18 -0
- package/corpus/templates/design/app/public-routes.ts +8 -0
- package/corpus/templates/design/app/root.tsx +7 -6
- package/corpus/templates/design/changelog/2026-07-13-dragged-design-assets-now-appear-at-the-visible-drop-locatio.md +6 -0
- package/corpus/templates/design/changelog/2026-07-13-reviewers-can-pin-comments-on-shared-designs-and-apply-verif.md +6 -0
- package/corpus/templates/design/server/plugins/agent-chat.ts +10 -0
- package/corpus/templates/design/server/plugins/auth.ts +7 -5
- package/corpus/templates/design/server/plugins/review.ts +9 -0
- package/corpus/templates/design/shared/review-anchor.ts +83 -0
- package/corpus/templates/design/shared/review-summary.ts +25 -0
- package/corpus/templates/forms/.agents/skills/form-publishing/SKILL.md +2 -0
- package/corpus/templates/forms/AGENTS.md +4 -0
- package/corpus/templates/forms/actions/create-form.ts +2 -1
- package/corpus/templates/forms/actions/update-form.ts +5 -2
- package/corpus/templates/forms/app/i18n/ar-SA.ts +3 -0
- package/corpus/templates/forms/app/i18n/de-DE.ts +3 -0
- package/corpus/templates/forms/app/i18n/en-US.ts +3 -0
- package/corpus/templates/forms/app/i18n/es-ES.ts +3 -0
- package/corpus/templates/forms/app/i18n/fr-FR.ts +3 -0
- package/corpus/templates/forms/app/i18n/hi-IN.ts +3 -0
- package/corpus/templates/forms/app/i18n/ja-JP.ts +3 -0
- package/corpus/templates/forms/app/i18n/ko-KR.ts +3 -0
- package/corpus/templates/forms/app/i18n/pt-BR.ts +3 -0
- package/corpus/templates/forms/app/i18n/zh-CN.ts +3 -0
- package/corpus/templates/forms/app/i18n/zh-TW.ts +3 -0
- package/corpus/templates/forms/app/pages/FormBuilderPage.tsx +19 -0
- package/corpus/templates/forms/changelog/2026-07-14-fixed-the-ask-forms-loading-input-appearing-above-the-welcom.md +6 -0
- package/corpus/templates/forms/changelog/2026-07-14-form-social-previews-now-show-the-form-title-description-and.md +6 -0
- package/corpus/templates/forms/changelog/2026-07-14-forms-can-email-the-form-owner-when-new-responses-arrive.md +6 -0
- package/corpus/templates/forms/server/handlers/submissions.ts +17 -0
- package/corpus/templates/forms/server/lib/form-og-image.ts +64 -6
- package/corpus/templates/forms/server/lib/public-form-ssr.ts +6 -2
- package/corpus/templates/forms/server/lib/response-email.ts +65 -0
- package/corpus/templates/forms/server/plugins/agent-chat.ts +1 -0
- package/corpus/templates/forms/server/routes/api/forms/og/[...slug]/og.png.get.ts +184 -0
- package/corpus/templates/forms/shared/types.ts +2 -0
- package/corpus/templates/plan/.agents/skills/visual-plan/SKILL.md +6 -4
- package/corpus/templates/plan/.agents/skills/visual-recap/SKILL.md +7 -1
- package/dist/a2a/index.d.ts +2 -1
- package/dist/a2a/index.d.ts.map +1 -1
- package/dist/a2a/index.js +1 -1
- package/dist/a2a/index.js.map +1 -1
- package/dist/a2a/server.d.ts +25 -0
- package/dist/a2a/server.d.ts.map +1 -1
- package/dist/a2a/server.js +30 -6
- package/dist/a2a/server.js.map +1 -1
- package/dist/agent/production-agent.d.ts +2 -0
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +37 -12
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/cli/create.d.ts +5 -3
- package/dist/cli/create.d.ts.map +1 -1
- package/dist/cli/create.js +44 -19
- package/dist/cli/create.js.map +1 -1
- package/dist/cli/pr-visual-recap-workflow.d.ts +2 -2
- package/dist/cli/pr-visual-recap-workflow.d.ts.map +1 -1
- package/dist/cli/pr-visual-recap-workflow.js +2 -2
- package/dist/cli/pr-visual-recap-workflow.js.map +1 -1
- package/dist/cli/recap.d.ts +3 -562
- package/dist/cli/recap.d.ts.map +1 -1
- package/dist/cli/recap.js +3 -3875
- package/dist/cli/recap.js.map +1 -1
- package/dist/cli/skills-content/visual-plan-skill.d.ts +1 -1
- package/dist/cli/skills-content/visual-plan-skill.d.ts.map +1 -1
- package/dist/cli/skills-content/visual-plan-skill.js +6 -4
- package/dist/cli/skills-content/visual-plan-skill.js.map +1 -1
- package/dist/cli/skills-content/visual-recap-skill.d.ts +1 -1
- package/dist/cli/skills-content/visual-recap-skill.d.ts.map +1 -1
- package/dist/cli/skills-content/visual-recap-skill.js +7 -1
- package/dist/cli/skills-content/visual-recap-skill.js.map +1 -1
- package/dist/client/AgentPanel.d.ts.map +1 -1
- package/dist/client/AgentPanel.js +3 -3
- package/dist/client/AgentPanel.js.map +1 -1
- package/dist/client/analytics.d.ts +5 -0
- package/dist/client/analytics.d.ts.map +1 -1
- package/dist/client/analytics.js +3 -1
- package/dist/client/analytics.js.map +1 -1
- package/dist/client/chat/markdown-renderer.d.ts +1 -0
- package/dist/client/chat/markdown-renderer.d.ts.map +1 -1
- package/dist/client/chat/markdown-renderer.js +36 -1
- package/dist/client/chat/markdown-renderer.js.map +1 -1
- package/dist/client/chat/message-components.d.ts.map +1 -1
- package/dist/client/chat/message-components.js +5 -2
- package/dist/client/chat/message-components.js.map +1 -1
- package/dist/client/clipboard.d.ts +3 -1
- package/dist/client/clipboard.d.ts.map +1 -1
- package/dist/client/clipboard.js +24 -1
- package/dist/client/clipboard.js.map +1 -1
- package/dist/client/guided-questions.d.ts +3 -1
- package/dist/client/guided-questions.d.ts.map +1 -1
- package/dist/client/guided-questions.js +2 -1
- package/dist/client/guided-questions.js.map +1 -1
- package/dist/client/i18n.d.ts.map +1 -1
- package/dist/client/i18n.js +3 -1
- package/dist/client/i18n.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/review/ReviewCommentComposer.d.ts +18 -0
- package/dist/client/review/ReviewCommentComposer.d.ts.map +1 -0
- package/dist/client/review/ReviewCommentComposer.js +30 -0
- package/dist/client/review/ReviewCommentComposer.js.map +1 -0
- package/dist/client/review/ReviewThreadPanel.d.ts +28 -1
- package/dist/client/review/ReviewThreadPanel.d.ts.map +1 -1
- package/dist/client/review/ReviewThreadPanel.js +151 -39
- package/dist/client/review/ReviewThreadPanel.js.map +1 -1
- package/dist/client/review/index.d.ts +3 -2
- package/dist/client/review/index.d.ts.map +1 -1
- package/dist/client/review/index.js +2 -1
- package/dist/client/review/index.js.map +1 -1
- package/dist/client/review/use-review.d.ts +23 -0
- package/dist/client/review/use-review.d.ts.map +1 -1
- package/dist/client/review/use-review.js +3 -0
- package/dist/client/review/use-review.js.map +1 -1
- package/dist/client/use-agent-chat-context.d.ts +1 -1
- package/dist/client/use-agent-chat-context.d.ts.map +1 -1
- package/dist/client/use-agent-chat-context.js +4 -2
- package/dist/client/use-agent-chat-context.js.map +1 -1
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/awareness.d.ts.map +1 -1
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/notifications/routes.d.ts +3 -3
- package/dist/observability/routes.d.ts +5 -5
- package/dist/progress/routes.d.ts +1 -1
- package/dist/provider-api/corpus-jobs.d.ts +2 -2
- package/dist/resources/handlers.d.ts +2 -2
- package/dist/review/actions/create-review-comment.js +2 -1
- package/dist/review/actions/create-review-comment.js.map +1 -1
- package/dist/review/actions/get-review-feedback.js +6 -11
- package/dist/review/actions/get-review-feedback.js.map +1 -1
- package/dist/review/actions/list-review-comments.d.ts +1 -0
- package/dist/review/actions/list-review-comments.js +25 -4
- package/dist/review/actions/list-review-comments.js.map +1 -1
- package/dist/review/actions/reply-review-comment.js +12 -5
- package/dist/review/actions/reply-review-comment.js.map +1 -1
- package/dist/review/actions/resolve-review-thread.d.ts +3 -0
- package/dist/review/actions/resolve-review-thread.js +20 -3
- package/dist/review/actions/resolve-review-thread.js.map +1 -1
- package/dist/review/actions/send-review-thread-to-agent.d.ts +17 -0
- package/dist/review/actions/send-review-thread-to-agent.d.ts.map +1 -0
- package/dist/review/actions/send-review-thread-to-agent.js +49 -0
- package/dist/review/actions/send-review-thread-to-agent.js.map +1 -0
- package/dist/review/identity.d.ts +6 -0
- package/dist/review/identity.d.ts.map +1 -0
- package/dist/review/identity.js +68 -0
- package/dist/review/identity.js.map +1 -0
- package/dist/review/index.d.ts +3 -1
- package/dist/review/index.d.ts.map +1 -1
- package/dist/review/index.js +2 -1
- package/dist/review/index.js.map +1 -1
- package/dist/review/store.d.ts +36 -0
- package/dist/review/store.d.ts.map +1 -1
- package/dist/review/store.js +223 -8
- package/dist/review/store.js.map +1 -1
- package/dist/review/types.d.ts +4 -0
- package/dist/review/types.d.ts.map +1 -1
- package/dist/review/types.js.map +1 -1
- package/dist/secrets/routes.d.ts +9 -9
- package/dist/server/action-discovery.d.ts.map +1 -1
- package/dist/server/action-discovery.js +4 -0
- package/dist/server/action-discovery.js.map +1 -1
- package/dist/server/action-routes.d.ts +45 -0
- package/dist/server/action-routes.d.ts.map +1 -1
- package/dist/server/action-routes.js +71 -5
- package/dist/server/action-routes.js.map +1 -1
- package/dist/server/agent-chat/plugin-options.d.ts +16 -0
- package/dist/server/agent-chat/plugin-options.d.ts.map +1 -1
- package/dist/server/agent-chat/plugin-options.js.map +1 -1
- package/dist/server/agent-chat-plugin.d.ts.map +1 -1
- package/dist/server/agent-chat-plugin.js +1 -0
- package/dist/server/agent-chat-plugin.js.map +1 -1
- package/dist/server/index.d.ts +3 -1
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +2 -0
- package/dist/server/index.js.map +1 -1
- package/dist/vite/action-types-plugin.d.ts.map +1 -1
- package/dist/vite/action-types-plugin.js +4 -0
- package/dist/vite/action-types-plugin.js.map +1 -1
- package/docs/content/locales/ar-SA/pr-visual-recap.mdx +48 -30
- package/docs/content/locales/ar-SA/template-plan.mdx +7 -0
- package/docs/content/locales/de-DE/pr-visual-recap.mdx +48 -30
- package/docs/content/locales/de-DE/template-plan.mdx +7 -0
- package/docs/content/locales/es-ES/pr-visual-recap.mdx +48 -30
- package/docs/content/locales/es-ES/template-plan.mdx +14 -0
- package/docs/content/locales/fr-FR/pr-visual-recap.mdx +48 -30
- package/docs/content/locales/fr-FR/template-plan.mdx +7 -0
- package/docs/content/locales/hi-IN/pr-visual-recap.mdx +48 -30
- package/docs/content/locales/hi-IN/template-plan.mdx +7 -0
- package/docs/content/locales/ja-JP/pr-visual-recap.mdx +48 -30
- package/docs/content/locales/ja-JP/template-plan.mdx +7 -0
- package/docs/content/locales/ko-KR/pr-visual-recap.mdx +48 -30
- package/docs/content/locales/ko-KR/template-plan.mdx +7 -0
- package/docs/content/locales/pt-BR/pr-visual-recap.mdx +48 -30
- package/docs/content/locales/pt-BR/template-plan.mdx +14 -0
- package/docs/content/locales/zh-CN/pr-visual-recap.mdx +48 -30
- package/docs/content/locales/zh-CN/template-plan.mdx +7 -0
- package/docs/content/locales/zh-TW/pr-visual-recap.mdx +48 -30
- package/docs/content/locales/zh-TW/template-plan.mdx +7 -0
- package/docs/content/pr-visual-recap.mdx +50 -32
- package/docs/content/template-plan.mdx +14 -0
- package/docs/content/toolkit-comments-review.mdx +16 -2
- package/package.json +4 -1
- package/corpus/templates/forms/server/routes/api/forms/og/[slug]/og.png.get.ts +0 -45
|
@@ -63,6 +63,7 @@ Each form has a `settings` JSON object:
|
|
|
63
63
|
"successMessage": "Thank you! Your response has been recorded.",
|
|
64
64
|
"redirectUrl": null,
|
|
65
65
|
"showProgressBar": false,
|
|
66
|
+
"emailOnNewResponses": false,
|
|
66
67
|
"anonymous": false,
|
|
67
68
|
"integrations": []
|
|
68
69
|
}
|
|
@@ -74,6 +75,7 @@ Each form has a `settings` JSON object:
|
|
|
74
75
|
| `successMessage` | string | Message shown after successful submission |
|
|
75
76
|
| `redirectUrl` | string | URL to redirect to after submission |
|
|
76
77
|
| `showProgressBar` | boolean | Show progress bar for multi-section forms |
|
|
78
|
+
| `emailOnNewResponses` | boolean | Email the form owner's account when someone submits a response |
|
|
77
79
|
| `anonymous` | boolean | Suppress IP, submitter identity, chat/run ids, page URL, and client-surface metadata for every response |
|
|
78
80
|
| `integrations` | array | Webhook/Slack/Discord notification configs |
|
|
79
81
|
|
|
@@ -32,6 +32,10 @@ Detailed building, publishing, response, storage, and UI rules live in
|
|
|
32
32
|
`create-form` call with `status: "published"`, verify the persisted form, and
|
|
33
33
|
return the action's exact public `/f/<slug>` response URL rather than only
|
|
34
34
|
the private editor link.
|
|
35
|
+
- To email the form owner when someone submits a response, set
|
|
36
|
+
`settings.emailOnNewResponses: true` through `create-form` or `update-form`.
|
|
37
|
+
Delivery uses the configured framework email provider (`RESEND_API_KEY` or
|
|
38
|
+
`SENDGRID_API_KEY`) and sends to the form owner's account email.
|
|
35
39
|
- Form UX should stay focused: clear labels, sensible validation, minimal
|
|
36
40
|
required fields, and progressive disclosure for advanced settings.
|
|
37
41
|
- Public form submission endpoints must be intentionally public; keep management
|
|
@@ -36,7 +36,7 @@ function formDeepLink(formId: string): string {
|
|
|
36
36
|
|
|
37
37
|
export default defineAction({
|
|
38
38
|
description:
|
|
39
|
-
"Create a draft or published form. Set settings.anonymous=true to suppress submitter IP, identity, and source metadata. Published forms return a direct public response URL; drafts return an editor URL.",
|
|
39
|
+
"Create a draft or published form. Set settings.anonymous=true to suppress submitter IP, identity, and source metadata, or settings.emailOnNewResponses=true to email the form owner when responses arrive. Published forms return a direct public response URL; drafts return an editor URL.",
|
|
40
40
|
schema: z.object({
|
|
41
41
|
title: z.string().optional().describe("Form title"),
|
|
42
42
|
description: z.string().optional().describe("Form description"),
|
|
@@ -94,6 +94,7 @@ export default defineAction({
|
|
|
94
94
|
submitText: "Submit",
|
|
95
95
|
successMessage: "Thank you! Your response has been recorded.",
|
|
96
96
|
showProgressBar: false,
|
|
97
|
+
emailOnNewResponses: false,
|
|
97
98
|
};
|
|
98
99
|
|
|
99
100
|
let settings = defaultSettings;
|
|
@@ -18,7 +18,8 @@ function slugify(text: string): string {
|
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
export default defineAction({
|
|
21
|
-
description:
|
|
21
|
+
description:
|
|
22
|
+
"Update an existing form, including settings.emailOnNewResponses to email the form owner when new responses arrive.",
|
|
22
23
|
schema: z.object({
|
|
23
24
|
id: z.string().describe("Form ID (required)"),
|
|
24
25
|
title: z.string().optional().describe("New title"),
|
|
@@ -34,7 +35,9 @@ export default defineAction({
|
|
|
34
35
|
settings: z
|
|
35
36
|
.union([z.string(), z.record(z.string(), z.any())])
|
|
36
37
|
.optional()
|
|
37
|
-
.describe(
|
|
38
|
+
.describe(
|
|
39
|
+
"Form settings object (or JSON string of the same). Set emailOnNewResponses=true to email the form owner for each new response.",
|
|
40
|
+
),
|
|
38
41
|
status: z
|
|
39
42
|
.enum(["draft", "published", "closed"])
|
|
40
43
|
.optional()
|
|
@@ -197,6 +197,9 @@ const messages = {
|
|
|
197
197
|
anonymousResponses: "ردود مجهولة الهوية",
|
|
198
198
|
anonymousResponsesDescription:
|
|
199
199
|
"عدم الاحتفاظ بعناوين IP أو هوية المجيب أو بيانات المصدر الوصفية.",
|
|
200
|
+
emailNewResponses: "أرسل لي بريدًا إلكترونيًا عند وصول ردود جديدة",
|
|
201
|
+
emailNewResponsesDescription:
|
|
202
|
+
"إرسال إشعار إلى البريد الإلكتروني لحساب مالك النموذج عند إرسال أي رد.",
|
|
200
203
|
saveSettings: "حفظ الإعدادات",
|
|
201
204
|
},
|
|
202
205
|
integrations: {
|
|
@@ -196,6 +196,9 @@ const messages = {
|
|
|
196
196
|
anonymousResponses: "Anonyme Antworten",
|
|
197
197
|
anonymousResponsesDescription:
|
|
198
198
|
"IP-Adressen, Identität der antwortenden Person und Quellmetadaten nicht speichern.",
|
|
199
|
+
emailNewResponses: "Bei neuen Antworten per E-Mail benachrichtigen",
|
|
200
|
+
emailNewResponsesDescription:
|
|
201
|
+
"Sendet bei jeder Antwort eine E-Mail an die Kontoadresse des Formulareigentümers.",
|
|
199
202
|
saveSettings: "Einstellungen speichern",
|
|
200
203
|
},
|
|
201
204
|
integrations: {
|
|
@@ -194,6 +194,9 @@ const messages = {
|
|
|
194
194
|
anonymousResponses: "Anonymous responses",
|
|
195
195
|
anonymousResponsesDescription:
|
|
196
196
|
"Do not retain IP addresses, submitter identity, or source metadata.",
|
|
197
|
+
emailNewResponses: "Email me for new responses",
|
|
198
|
+
emailNewResponsesDescription:
|
|
199
|
+
"Send a notification to the form owner's account email whenever someone submits a response.",
|
|
197
200
|
saveSettings: "Save Settings",
|
|
198
201
|
},
|
|
199
202
|
integrations: {
|
|
@@ -197,6 +197,9 @@ const messages = {
|
|
|
197
197
|
anonymousResponses: "Respuestas anónimas",
|
|
198
198
|
anonymousResponsesDescription:
|
|
199
199
|
"No conservar direcciones IP, identidad del remitente ni metadatos de origen.",
|
|
200
|
+
emailNewResponses: "Enviarme un correo por respuestas nuevas",
|
|
201
|
+
emailNewResponsesDescription:
|
|
202
|
+
"Envía una notificación al correo de la cuenta propietaria del formulario cada vez que alguien responde.",
|
|
200
203
|
saveSettings: "Guardar ajustes",
|
|
201
204
|
},
|
|
202
205
|
integrations: {
|
|
@@ -198,6 +198,9 @@ const messages = {
|
|
|
198
198
|
anonymousResponses: "Réponses anonymes",
|
|
199
199
|
anonymousResponsesDescription:
|
|
200
200
|
"Ne pas conserver les adresses IP, l’identité du répondant ni les métadonnées de provenance.",
|
|
201
|
+
emailNewResponses: "M'envoyer un e-mail pour les nouvelles réponses",
|
|
202
|
+
emailNewResponsesDescription:
|
|
203
|
+
"Envoyer une notification à l'adresse e-mail du compte propriétaire du formulaire à chaque réponse.",
|
|
201
204
|
saveSettings: "Enregistrer les paramètres",
|
|
202
205
|
},
|
|
203
206
|
integrations: {
|
|
@@ -193,6 +193,9 @@ const messages = {
|
|
|
193
193
|
anonymousResponses: "गुमनाम प्रतिक्रियाएँ",
|
|
194
194
|
anonymousResponsesDescription:
|
|
195
195
|
"IP पते, प्रतिक्रिया देने वाले की पहचान या स्रोत मेटाडेटा सहेजें नहीं।",
|
|
196
|
+
emailNewResponses: "नई प्रतिक्रियाओं के लिए मुझे ईमेल करें",
|
|
197
|
+
emailNewResponsesDescription:
|
|
198
|
+
"हर प्रतिक्रिया पर फ़ॉर्म के मालिक के खाते के ईमेल पर सूचना भेजें।",
|
|
196
199
|
saveSettings: "सेटिंग्स सहेजें",
|
|
197
200
|
},
|
|
198
201
|
integrations: {
|
|
@@ -194,6 +194,9 @@ const messages = {
|
|
|
194
194
|
anonymousResponses: "匿名の回答",
|
|
195
195
|
anonymousResponsesDescription:
|
|
196
196
|
"IP アドレス、回答者の身元、送信元メタデータを保存しません。",
|
|
197
|
+
emailNewResponses: "新しい回答をメールで知らせる",
|
|
198
|
+
emailNewResponsesDescription:
|
|
199
|
+
"回答が送信されるたびに、フォーム所有者のアカウントのメールアドレスへ通知します。",
|
|
197
200
|
saveSettings: "設定を保存",
|
|
198
201
|
},
|
|
199
202
|
integrations: {
|
|
@@ -191,6 +191,9 @@ const messages = {
|
|
|
191
191
|
anonymousResponses: "익명 응답",
|
|
192
192
|
anonymousResponsesDescription:
|
|
193
193
|
"IP 주소, 응답자 신원 또는 출처 메타데이터를 저장하지 않습니다.",
|
|
194
|
+
emailNewResponses: "새 응답을 이메일로 받기",
|
|
195
|
+
emailNewResponsesDescription:
|
|
196
|
+
"응답이 제출될 때마다 양식 소유자 계정의 이메일 주소로 알림을 보냅니다.",
|
|
194
197
|
saveSettings: "설정 저장",
|
|
195
198
|
},
|
|
196
199
|
integrations: {
|
|
@@ -197,6 +197,9 @@ const messages = {
|
|
|
197
197
|
anonymousResponses: "Respostas anônimas",
|
|
198
198
|
anonymousResponsesDescription:
|
|
199
199
|
"Não guardar endereços IP, identidade do respondente nem metadados de origem.",
|
|
200
|
+
emailNewResponses: "Enviar-me um e-mail sobre novas respostas",
|
|
201
|
+
emailNewResponsesDescription:
|
|
202
|
+
"Envie uma notificação para o e-mail da conta proprietária do formulário sempre que alguém responder.",
|
|
200
203
|
saveSettings: "Salvar configurações",
|
|
201
204
|
},
|
|
202
205
|
integrations: {
|
|
@@ -182,6 +182,9 @@ const messages = {
|
|
|
182
182
|
redirectUrl: "重定向 URL(可选)",
|
|
183
183
|
anonymousResponses: "匿名回复",
|
|
184
184
|
anonymousResponsesDescription: "不保留 IP 地址、回复者身份或来源元数据。",
|
|
185
|
+
emailNewResponses: "有新回复时通过电子邮件通知我",
|
|
186
|
+
emailNewResponsesDescription:
|
|
187
|
+
"每当有人提交回复时,向表单所有者账户的电子邮件地址发送通知。",
|
|
185
188
|
saveSettings: "保存设置",
|
|
186
189
|
},
|
|
187
190
|
integrations: {
|
|
@@ -183,6 +183,9 @@ const messages = {
|
|
|
183
183
|
anonymousResponses: "匿名回覆",
|
|
184
184
|
anonymousResponsesDescription:
|
|
185
185
|
"不保留 IP 位址、回覆者身分或來源中繼資料。",
|
|
186
|
+
emailNewResponses: "有新回覆時寄信通知我",
|
|
187
|
+
emailNewResponsesDescription:
|
|
188
|
+
"每當有人提交回覆時,就通知表單擁有者帳戶的電子郵件地址。",
|
|
186
189
|
saveSettings: "儲存設定",
|
|
187
190
|
},
|
|
188
191
|
integrations: {
|
|
@@ -1621,6 +1621,25 @@ function SettingsEditor({
|
|
|
1621
1621
|
/>
|
|
1622
1622
|
</div>
|
|
1623
1623
|
|
|
1624
|
+
<div className="flex items-start justify-between gap-4 rounded-lg border border-border/60 bg-card p-3">
|
|
1625
|
+
<div className="space-y-1">
|
|
1626
|
+
<Label htmlFor="email-new-responses" className="text-xs">
|
|
1627
|
+
{t("builder.settings.emailNewResponses")}
|
|
1628
|
+
</Label>
|
|
1629
|
+
<p className="text-xs leading-5 text-muted-foreground">
|
|
1630
|
+
{t("builder.settings.emailNewResponsesDescription")}
|
|
1631
|
+
</p>
|
|
1632
|
+
</div>
|
|
1633
|
+
<Switch
|
|
1634
|
+
id="email-new-responses"
|
|
1635
|
+
checked={settings.emailOnNewResponses === true}
|
|
1636
|
+
onCheckedChange={(emailOnNewResponses) =>
|
|
1637
|
+
update({ emailOnNewResponses })
|
|
1638
|
+
}
|
|
1639
|
+
aria-label={t("builder.settings.emailNewResponses")}
|
|
1640
|
+
/>
|
|
1641
|
+
</div>
|
|
1642
|
+
|
|
1624
1643
|
<Button
|
|
1625
1644
|
onClick={() => onSave(settings)}
|
|
1626
1645
|
className="w-full active:scale-[0.96]"
|
|
@@ -29,6 +29,7 @@ import type {
|
|
|
29
29
|
} from "../../shared/types.js";
|
|
30
30
|
import { getDb, schema } from "../db/index.js";
|
|
31
31
|
import { fireIntegrations } from "../lib/integrations.js";
|
|
32
|
+
import { sendNewResponseEmail } from "../lib/response-email.js";
|
|
32
33
|
import {
|
|
33
34
|
isEmptySubmissionValue,
|
|
34
35
|
validateSubmissionField,
|
|
@@ -265,6 +266,22 @@ export const submitForm = defineEventHandler(async (event: H3Event) => {
|
|
|
265
266
|
// Non-critical — don't fail the submission
|
|
266
267
|
}
|
|
267
268
|
|
|
269
|
+
if (settings.emailOnNewResponses === true && form.ownerEmail) {
|
|
270
|
+
try {
|
|
271
|
+
await sendNewResponseEmail({
|
|
272
|
+
to: form.ownerEmail,
|
|
273
|
+
formTitle: form.title,
|
|
274
|
+
fields,
|
|
275
|
+
data,
|
|
276
|
+
submittedAt: now,
|
|
277
|
+
});
|
|
278
|
+
} catch (error) {
|
|
279
|
+
// Email is best-effort — a provider outage must not reject a public
|
|
280
|
+
// submission that was already persisted successfully.
|
|
281
|
+
console.warn("[forms] new response email failed:", error);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
268
285
|
// Fire integrations best-effort and never fail the submission. Keep this
|
|
269
286
|
// awaited: serverless hosts can freeze fire-and-forget work as soon as the
|
|
270
287
|
// HTTP response returns, which silently drops Slack/webhook delivery.
|
|
@@ -1,7 +1,14 @@
|
|
|
1
|
+
import { resolveOgFontFiles } from "@agent-native/core/server";
|
|
1
2
|
import type { RenderedImage, ResvgRenderOptions } from "@resvg/resvg-js";
|
|
2
3
|
|
|
3
4
|
export interface FormOgImageInput {
|
|
4
5
|
title?: string | null;
|
|
6
|
+
description?: string | null;
|
|
7
|
+
profileImageDataUrl?: string | null;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
interface FormOgRenderOptions {
|
|
11
|
+
fontFiles?: string[];
|
|
5
12
|
}
|
|
6
13
|
|
|
7
14
|
const WIDTH = 1200;
|
|
@@ -23,6 +30,9 @@ const LOGO_MARK = `
|
|
|
23
30
|
<path d="M89.446 0H114L76.2921 65.7704H51.7383L89.446 0Z" fill="url(#brand)"/>
|
|
24
31
|
`;
|
|
25
32
|
|
|
33
|
+
const AVATAR_DATA_URL_RE =
|
|
34
|
+
/^data:image\/(?:png|jpe?g|gif|webp);base64,[A-Za-z0-9+/]+={0,2}$/i;
|
|
35
|
+
|
|
26
36
|
function escapeSvg(value: string): string {
|
|
27
37
|
return value
|
|
28
38
|
.replace(/&/g, "&")
|
|
@@ -37,6 +47,16 @@ function cleanText(value: string | null | undefined): string {
|
|
|
37
47
|
.trim();
|
|
38
48
|
}
|
|
39
49
|
|
|
50
|
+
function validProfileImageDataUrl(
|
|
51
|
+
value: string | null | undefined,
|
|
52
|
+
): string | undefined {
|
|
53
|
+
const image = value?.trim();
|
|
54
|
+
if (!image || image.length > 2_000_000 || !AVATAR_DATA_URL_RE.test(image)) {
|
|
55
|
+
return undefined;
|
|
56
|
+
}
|
|
57
|
+
return image;
|
|
58
|
+
}
|
|
59
|
+
|
|
40
60
|
function estimateTextWidth(value: string, fontSize: number): number {
|
|
41
61
|
let units = 0;
|
|
42
62
|
for (const char of value) {
|
|
@@ -146,14 +166,24 @@ function textBlock({
|
|
|
146
166
|
|
|
147
167
|
export function renderFormOgImageSvg(input: FormOgImageInput = {}): string {
|
|
148
168
|
const title = cleanText(input.title) || "Agent-Native Form";
|
|
169
|
+
const description = cleanText(input.description);
|
|
149
170
|
const titleFitsSingleLine = estimateTextWidth(title, 82) <= 820;
|
|
150
171
|
const titleLines = titleFitsSingleLine
|
|
151
172
|
? [title]
|
|
152
173
|
: wrapText(title, 66, 820, 2);
|
|
174
|
+
const descriptionLines = description ? wrapText(description, 28, 820, 2) : [];
|
|
153
175
|
const titleFontSize = titleFitsSingleLine ? 82 : 66;
|
|
154
176
|
const titleLineHeight = titleLines.length > 1 ? 76 : 92;
|
|
155
177
|
const titleGroupY = titleLines.length > 1 ? 332 : 382;
|
|
178
|
+
const descriptionY = titleLineHeight * (titleLines.length - 1) + 88;
|
|
156
179
|
const initials = initialsFor(title);
|
|
180
|
+
const profileImageDataUrl = validProfileImageDataUrl(
|
|
181
|
+
input.profileImageDataUrl,
|
|
182
|
+
);
|
|
183
|
+
const avatarContent = profileImageDataUrl
|
|
184
|
+
? `<image x="${BADGE_CX - 86}" y="${BADGE_CY - 86}" width="172" height="172" href="${escapeSvg(profileImageDataUrl)}" preserveAspectRatio="xMidYMid slice" mask="url(#avatarMask)"/>`
|
|
185
|
+
: `<circle cx="${BADGE_CX}" cy="${BADGE_CY}" r="72" fill="url(#brand)" fill-opacity="0.2"/>
|
|
186
|
+
<text x="${BADGE_CX}" y="${BADGE_CY + 20}" text-anchor="middle" font-family="${FONT_FAMILY}" font-size="56" font-weight="800" fill="${FG}">${escapeSvg(initials)}</text>`;
|
|
157
187
|
|
|
158
188
|
return `<svg xmlns="http://www.w3.org/2000/svg" width="${WIDTH}" height="${HEIGHT}" viewBox="0 0 ${WIDTH} ${HEIGHT}">
|
|
159
189
|
<title>${escapeSvg(title)} - Agent-Native Forms preview</title>
|
|
@@ -165,6 +195,10 @@ export function renderFormOgImageSvg(input: FormOgImageInput = {}): string {
|
|
|
165
195
|
<pattern id="grid" width="48" height="48" patternUnits="userSpaceOnUse">
|
|
166
196
|
<path d="M 48 0 L 0 0 0 48" fill="none" stroke="#ffffff" stroke-opacity="0.07" stroke-width="1"/>
|
|
167
197
|
</pattern>
|
|
198
|
+
<mask id="avatarMask">
|
|
199
|
+
<rect width="${WIDTH}" height="${HEIGHT}" fill="black"/>
|
|
200
|
+
<circle cx="${BADGE_CX}" cy="${BADGE_CY}" r="78" fill="white"/>
|
|
201
|
+
</mask>
|
|
168
202
|
</defs>
|
|
169
203
|
<rect width="${WIDTH}" height="${HEIGHT}" fill="${BG}"/>
|
|
170
204
|
<rect width="${WIDTH}" height="${HEIGHT}" fill="url(#grid)"/>
|
|
@@ -177,8 +211,7 @@ export function renderFormOgImageSvg(input: FormOgImageInput = {}): string {
|
|
|
177
211
|
</g>
|
|
178
212
|
<g>
|
|
179
213
|
<circle cx="${BADGE_CX}" cy="${BADGE_CY}" r="86" fill="${SURFACE}" stroke="${BORDER}" stroke-width="2"/>
|
|
180
|
-
|
|
181
|
-
<text x="${BADGE_CX}" y="${BADGE_CY + 20}" text-anchor="middle" font-family="${FONT_FAMILY}" font-size="56" font-weight="800" fill="${FG}">${escapeSvg(initials)}</text>
|
|
214
|
+
${avatarContent}
|
|
182
215
|
<circle cx="${BADGE_CX}" cy="${BADGE_CY}" r="78" fill="none" stroke="#ffffff" stroke-opacity="0.14" stroke-width="1"/>
|
|
183
216
|
</g>
|
|
184
217
|
<g transform="translate(80 ${titleGroupY})">
|
|
@@ -191,15 +224,35 @@ export function renderFormOgImageSvg(input: FormOgImageInput = {}): string {
|
|
|
191
224
|
weight: 800,
|
|
192
225
|
fill: FG,
|
|
193
226
|
})}
|
|
227
|
+
${
|
|
228
|
+
descriptionLines.length
|
|
229
|
+
? textBlock({
|
|
230
|
+
lines: descriptionLines,
|
|
231
|
+
x: 0,
|
|
232
|
+
y: descriptionY,
|
|
233
|
+
fontSize: 28,
|
|
234
|
+
lineHeight: 38,
|
|
235
|
+
weight: 500,
|
|
236
|
+
fill: MUTED,
|
|
237
|
+
})
|
|
238
|
+
: ""
|
|
239
|
+
}
|
|
194
240
|
</g>
|
|
195
241
|
</svg>`;
|
|
196
242
|
}
|
|
197
243
|
|
|
198
|
-
function formOgResvgOptions(
|
|
244
|
+
export function formOgResvgOptions(
|
|
245
|
+
options: FormOgRenderOptions = {},
|
|
246
|
+
): ResvgRenderOptions {
|
|
247
|
+
const fontFiles = options.fontFiles?.length
|
|
248
|
+
? options.fontFiles
|
|
249
|
+
: resolveOgFontFiles();
|
|
250
|
+
const hasBundledFonts = Boolean(fontFiles?.length);
|
|
199
251
|
return {
|
|
200
252
|
fitTo: { mode: "width", value: WIDTH },
|
|
201
253
|
font: {
|
|
202
|
-
loadSystemFonts:
|
|
254
|
+
loadSystemFonts: !hasBundledFonts,
|
|
255
|
+
...(hasBundledFonts ? { fontFiles } : {}),
|
|
203
256
|
defaultFontFamily: "Liberation Sans",
|
|
204
257
|
sansSerifFamily: "Liberation Sans",
|
|
205
258
|
},
|
|
@@ -212,13 +265,18 @@ async function loadResvg(): Promise<typeof import("@resvg/resvg-js")> {
|
|
|
212
265
|
|
|
213
266
|
export async function renderFormOgImage(
|
|
214
267
|
input: FormOgImageInput = {},
|
|
268
|
+
options: FormOgRenderOptions = {},
|
|
215
269
|
): Promise<RenderedImage> {
|
|
216
270
|
const { Resvg } = await loadResvg();
|
|
217
|
-
return new Resvg(
|
|
271
|
+
return new Resvg(
|
|
272
|
+
renderFormOgImageSvg(input),
|
|
273
|
+
formOgResvgOptions(options),
|
|
274
|
+
).render();
|
|
218
275
|
}
|
|
219
276
|
|
|
220
277
|
export async function renderFormOgImagePng(
|
|
221
278
|
input: FormOgImageInput = {},
|
|
279
|
+
options: FormOgRenderOptions = {},
|
|
222
280
|
): Promise<Uint8Array> {
|
|
223
|
-
return (await renderFormOgImage(input)).asPng();
|
|
281
|
+
return (await renderFormOgImage(input, options)).asPng();
|
|
224
282
|
}
|
|
@@ -60,6 +60,8 @@ export async function getPublicFormBySlugOrId(slugOrId: string) {
|
|
|
60
60
|
slug: row.slug,
|
|
61
61
|
title: row.title,
|
|
62
62
|
description: row.description,
|
|
63
|
+
ownerEmail: row.ownerEmail,
|
|
64
|
+
updatedAt: row.updatedAt,
|
|
63
65
|
fields: JSON.parse(row.fields) as FormField[],
|
|
64
66
|
settings: toPublicFormSettings(settings),
|
|
65
67
|
};
|
|
@@ -302,6 +304,8 @@ function renderFormPage(
|
|
|
302
304
|
slug: string;
|
|
303
305
|
title: string;
|
|
304
306
|
description?: string | null;
|
|
307
|
+
ownerEmail?: string | null;
|
|
308
|
+
updatedAt?: string | null;
|
|
305
309
|
fields: FormField[];
|
|
306
310
|
settings: PublicFormSettings;
|
|
307
311
|
},
|
|
@@ -315,7 +319,7 @@ function renderFormPage(
|
|
|
315
319
|
const faviconPath = `${appBasePath}/favicon.svg`;
|
|
316
320
|
const ogImagePath = `${appBasePath}/api/forms/og/${encodeURIComponent(
|
|
317
321
|
form.slug || form.id,
|
|
318
|
-
)}/og.png`;
|
|
322
|
+
)}/og.png${form.updatedAt ? `?v=${encodeURIComponent(form.updatedAt)}` : ""}`;
|
|
319
323
|
const ogImageUrl = origin
|
|
320
324
|
? new URL(ogImagePath, origin).toString()
|
|
321
325
|
: ogImagePath;
|
|
@@ -342,7 +346,7 @@ function renderFormPage(
|
|
|
342
346
|
<meta property="og:image:alt" content="${escapeHtml(`${form.title} form preview`)}">
|
|
343
347
|
<meta name="twitter:card" content="summary_large_image">
|
|
344
348
|
<meta name="twitter:image" content="${escapeHtml(ogImageUrl)}">
|
|
345
|
-
<meta name="twitter:image:alt" content="${
|
|
349
|
+
<meta name="twitter:image:alt" content="${escapeHtml(`${form.title} form preview`)}">
|
|
346
350
|
<link rel="icon" type="image/svg+xml" href="${faviconPath}">
|
|
347
351
|
<!-- Self-hosted Inter via Bunny Fonts CDN (privacy-respecting, no tracking) -->
|
|
348
352
|
<link rel="preconnect" href="https://fonts.bunny.net">
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { emailStrong, renderEmail, sendEmail } from "@agent-native/core/server";
|
|
2
|
+
|
|
3
|
+
import type { FormField } from "../../shared/types.js";
|
|
4
|
+
|
|
5
|
+
export interface NewResponseEmailArgs {
|
|
6
|
+
to: string;
|
|
7
|
+
formTitle: string;
|
|
8
|
+
fields: FormField[];
|
|
9
|
+
data: Record<string, unknown>;
|
|
10
|
+
submittedAt: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function stripCrlf(value: string): string {
|
|
14
|
+
return value.replace(/[\r\n]+/g, " ").trim();
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function formatResponseValue(value: unknown): string {
|
|
18
|
+
if (Array.isArray(value)) return value.join(", ");
|
|
19
|
+
if (value === null || value === undefined) return "";
|
|
20
|
+
if (typeof value === "object") return JSON.stringify(value);
|
|
21
|
+
return String(value);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function renderNewResponseEmail({
|
|
25
|
+
formTitle,
|
|
26
|
+
fields,
|
|
27
|
+
data,
|
|
28
|
+
submittedAt,
|
|
29
|
+
}: Omit<NewResponseEmailArgs, "to">) {
|
|
30
|
+
const safeTitle = stripCrlf(formTitle) || "Untitled form";
|
|
31
|
+
const responseLines = fields
|
|
32
|
+
.filter((field) => data[field.id] !== undefined)
|
|
33
|
+
.map(
|
|
34
|
+
(field) =>
|
|
35
|
+
`${emailStrong(field.label)}: ${emailStrong(
|
|
36
|
+
formatResponseValue(data[field.id]),
|
|
37
|
+
)}`,
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
return {
|
|
41
|
+
subject: `New response: ${safeTitle}`,
|
|
42
|
+
...renderEmail({
|
|
43
|
+
preheader: `New response to ${safeTitle}`,
|
|
44
|
+
heading: "New form response",
|
|
45
|
+
paragraphs: [
|
|
46
|
+
`${emailStrong(safeTitle)} received a new response.`,
|
|
47
|
+
`Submitted at ${emailStrong(submittedAt)}.`,
|
|
48
|
+
responseLines.length
|
|
49
|
+
? responseLines.join("<br />")
|
|
50
|
+
: "No response fields were submitted.",
|
|
51
|
+
],
|
|
52
|
+
footer:
|
|
53
|
+
"You received this because email notifications are enabled for this form.",
|
|
54
|
+
}),
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export async function sendNewResponseEmail(
|
|
59
|
+
args: NewResponseEmailArgs,
|
|
60
|
+
): Promise<void> {
|
|
61
|
+
await sendEmail({
|
|
62
|
+
to: args.to,
|
|
63
|
+
...renderNewResponseEmail(args),
|
|
64
|
+
});
|
|
65
|
+
}
|
|
@@ -17,6 +17,7 @@ Core rules:
|
|
|
17
17
|
- Use \`create-form\`, \`update-form\`, \`patch-form-fields\`, \`restore-form\`, and \`delete-form\` for form lifecycle and field edits.
|
|
18
18
|
- When the user asks for an anonymous feedback form or survey, create the complete form atomically with \`status: "published"\`, then return the exact public response URL from the successful action result. Do not return only the private editor link, and do not claim it is live without verifying the saved status and fields.
|
|
19
19
|
- Use \`preview-form\` when the user asks about a form's setup, fields, configuration, publish state, or response count. It renders a native chat summary.
|
|
20
|
+
- When the user wants email notifications for new responses, set \`settings.emailOnNewResponses: true\` with \`create-form\` or \`update-form\`; notifications go to the form owner's account email.
|
|
20
21
|
- Use \`response-insights\` for response analytics. Do not invent SQL or fake chart data. Pass \`displayMode: "chart"\` for chart-only requests, \`displayMode: "table"\` only when the user asks for a table/rows, and \`displayMode: "insights"\` only for combined dashboards or reports.
|
|
21
22
|
- Use \`list-responses\` and \`export-responses\` for response data review and export. When the user asks to see, open, or view all responses for a form, call \`navigate\` with \`view: "responses"\` instead of listing rows in chat.
|
|
22
23
|
- Use \`navigate\` to open focused workspace views such as the forms list, builder, published preview, responses, response insights, extensions, or team/settings views. For builder sub-tabs, pass \`view=form\`, the form ID, and \`tab=edit|responses|settings|integrations\`.
|