@agent-native/core 0.69.0 → 0.70.1
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 +15 -15
- package/corpus/README.md +2 -2
- package/corpus/core/CHANGELOG.md +109 -0
- package/corpus/core/docs/design/durable-agent-runs.md +217 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/action.ts +89 -0
- package/corpus/core/src/agent/model-config.ts +0 -2
- package/corpus/core/src/agent/run-loop-with-resume.ts +48 -0
- package/corpus/core/src/changelog/parse.ts +245 -0
- package/corpus/core/src/cli/changelog.ts +199 -0
- package/corpus/core/src/cli/index.ts +19 -0
- package/corpus/core/src/cli/recap.ts +24 -1
- package/corpus/core/src/cli/skills.ts +74 -47
- package/corpus/core/src/client/AgentPanel.tsx +5 -3
- package/corpus/core/src/client/AssistantChat.tsx +8 -0
- package/corpus/core/src/client/CommandMenu.tsx +183 -75
- package/corpus/core/src/client/agent-chat-adapter.ts +44 -7
- package/corpus/core/src/client/analytics.ts +181 -0
- package/corpus/core/src/client/changelog/Changelog.tsx +321 -0
- package/corpus/core/src/client/client-surface.ts +41 -0
- package/corpus/core/src/client/composer/TiptapComposer.tsx +123 -12
- package/corpus/core/src/client/feedback-context.ts +4 -0
- package/corpus/core/src/client/index.ts +12 -0
- package/corpus/core/src/client/mcp-apps/McpAppRenderer.tsx +54 -0
- package/corpus/core/src/client/settings/useBuilderStatus.ts +82 -20
- package/corpus/core/src/client/sse-event-processor.ts +7 -1
- package/corpus/core/src/mcp/build-server.ts +57 -1
- package/corpus/core/src/mcp/embed-app.ts +197 -7
- package/corpus/core/src/mcp/oauth-route.ts +109 -13
- package/corpus/core/src/server/attribution.ts +211 -0
- package/corpus/core/src/server/auth.ts +6 -0
- package/corpus/core/src/server/better-auth-instance.ts +49 -5
- package/corpus/core/src/server/builder-browser.ts +27 -1
- package/corpus/core/src/server/core-routes-plugin.ts +136 -0
- package/corpus/core/src/server/google-oauth.ts +19 -6
- package/corpus/core/src/server/onboarding-html.ts +35 -0
- package/corpus/core/src/shared/mcp-embed-headers.ts +1 -1
- package/corpus/core/src/styles/agent-conversation.css +21 -0
- package/corpus/core/src/templates/workspace-core/.agents/skills/changelog/SKILL.md +111 -0
- package/corpus/core/src/templates/workspace-core/.agents/skills/reliable-mutations/SKILL.md +72 -0
- package/corpus/core/src/templates/workspace-core/.agents/skills/tracking/SKILL.md +46 -14
- package/corpus/templates/analytics/.agents/skills/dashboard-management/SKILL.md +22 -0
- package/corpus/templates/analytics/AGENTS.md +8 -1
- package/corpus/templates/analytics/CHANGELOG.md +10 -0
- package/corpus/templates/analytics/actions/install-dashboard-template.ts +99 -4
- package/corpus/templates/analytics/actions/open-traffic-dashboard.ts +1 -1
- package/corpus/templates/analytics/actions/update-dashboard.ts +39 -8
- package/corpus/templates/analytics/app/components/dashboard/SqlChart.tsx +30 -3
- package/corpus/templates/analytics/app/components/layout/CommandPalette.tsx +180 -144
- package/corpus/templates/analytics/app/components/layout/Header.tsx +2 -3
- package/corpus/templates/analytics/app/components/layout/Layout.tsx +7 -5
- package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +221 -215
- package/corpus/templates/analytics/app/hooks/use-navigation-state.ts +6 -6
- package/corpus/templates/analytics/app/lib/demo-dashboard-path.ts +1 -1
- package/corpus/templates/analytics/app/lib/last-opened.ts +2 -1
- package/corpus/templates/analytics/app/pages/Settings.tsx +4 -0
- package/corpus/templates/analytics/app/pages/adhoc/explorer-dashboard/index.tsx +3 -3
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/DashboardFilterBar.tsx +2 -2
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/SqlChartCard.tsx +32 -2
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +101 -99
- package/corpus/templates/analytics/app/pages/overview/OverviewPage.tsx +1 -1
- package/corpus/templates/analytics/app/routes/adhoc.$id.tsx +18 -5
- package/corpus/templates/analytics/app/routes/catalog.tsx +1 -1
- package/corpus/templates/analytics/app/routes/dashboards.$id.tsx +9 -0
- package/corpus/templates/analytics/app/routes/traffic.tsx +1 -1
- package/corpus/templates/analytics/changelog/2026-06-23-long-dashboard-and-analysis-names-in-the-sidebar-now-truncat.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-23-open-any-dashboard-chart-full-screen-in-a-modal-from-the-pan.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-23-the-dashboards-and-analyses-sidebar-settings-buttons-now-sta.md +6 -0
- package/corpus/templates/analytics/seeds/dashboards/agent-native-templates-first-party.json +145 -0
- package/corpus/templates/analytics/server/db/index.ts +1 -1
- package/corpus/templates/analytics/server/lib/dashboard-catalog.ts +9 -2
- package/corpus/templates/analytics/server/lib/demo-dashboards.ts +1 -1
- package/corpus/templates/analytics/server/plugins/agent-chat.ts +2 -2
- package/corpus/templates/analytics/server/plugins/core-routes.ts +6 -6
- package/corpus/templates/assets/CHANGELOG.md +10 -0
- package/corpus/templates/assets/actions/generate-image.ts +24 -0
- package/corpus/templates/assets/app/components/layout/Sidebar.tsx +2 -2
- package/corpus/templates/assets/app/global.css +17 -1
- package/corpus/templates/assets/app/root.tsx +7 -1
- package/corpus/templates/assets/app/routes/_index.tsx +94 -14
- package/corpus/templates/assets/app/routes/settings.tsx +6 -0
- package/corpus/templates/assets/changelog/2026-06-23-chat-dates-in-the-sidebar-no-longer-wrap-onto-two-lines.md +6 -0
- package/corpus/templates/assets/changelog/2026-06-23-clicking-image-video-or-refine-on-the-start-screen-now-drops.md +6 -0
- package/corpus/templates/assets/changelog/2026-06-23-pick-your-image-generation-model-right-from-the-chat-model-m.md +6 -0
- package/corpus/templates/brain/CHANGELOG.md +10 -0
- package/corpus/templates/brain/app/root.tsx +7 -1
- package/corpus/templates/brain/app/routes/settings.tsx +8 -1
- package/corpus/templates/calendar/CHANGELOG.md +10 -0
- package/corpus/templates/calendar/app/pages/Settings.tsx +4 -0
- package/corpus/templates/calendar/app/root.tsx +7 -1
- package/corpus/templates/chat/CHANGELOG.md +10 -0
- package/corpus/templates/chat/app/root.tsx +7 -1
- package/corpus/templates/clips/CHANGELOG.md +10 -0
- package/corpus/templates/clips/app/components/library/library-grid.tsx +1 -20
- package/corpus/templates/clips/app/components/player/share-dialog.tsx +33 -7
- package/corpus/templates/clips/app/components/player/sign-in-prompt-dialog.tsx +8 -0
- package/corpus/templates/clips/app/components/player/video-player.tsx +150 -20
- package/corpus/templates/clips/app/components/recorder/countdown-overlay.tsx +72 -48
- package/corpus/templates/clips/app/components/recorder/pre-record-panel.tsx +7 -11
- package/corpus/templates/clips/app/components/recorder/recorder-engine.ts +31 -0
- package/corpus/templates/clips/app/components/sharing/slack-share-hint.tsx +71 -0
- package/corpus/templates/clips/app/lib/countdown-audio-cue.ts +35 -17
- package/corpus/templates/clips/app/root.tsx +7 -1
- package/corpus/templates/clips/app/routes/_app.settings._index.tsx +15 -1
- package/corpus/templates/clips/app/routes/record.tsx +78 -5
- package/corpus/templates/clips/app/routes/share.$shareId.tsx +86 -5
- package/corpus/templates/clips/changelog/2026-06-23-clips-shared-in-slack-no-longer-show-a-could-not-start-playb.md +6 -0
- package/corpus/templates/clips/changelog/2026-06-23-made-the-play-button-on-shared-and-embedded-clips-scale-with.md +6 -0
- package/corpus/templates/clips/changelog/2026-06-23-public-clip-share-links-now-show-whether-they-ll-play-inline.md +6 -0
- package/corpus/templates/clips/changelog/2026-06-23-removed-select-button-from-library-hover-a-clip-to-select-it.md +6 -0
- package/corpus/templates/clips/changelog/2026-06-23-screen-camera-recordings-now-keep-the-presenter-s-face-in-th.md +6 -0
- package/corpus/templates/clips/changelog/2026-06-23-shared-video-links-now-play-for-anyone-without-signing-in.md +6 -0
- package/corpus/templates/clips/changelog/2026-06-23-skip-or-cancel-the-recording-countdown-with-buttons-beside-t.md +6 -0
- package/corpus/templates/clips/changelog/2026-06-23-the-desktop-camera-bubble-now-looks-sharp-the-instant-it-ope.md +6 -0
- package/corpus/templates/clips/changelog/2026-06-23-the-desktop-recording-widget-now-grows-downward-only-when-yo.md +6 -0
- package/corpus/templates/clips/changelog/2026-06-23-the-recording-start-sound-is-now-a-softer-more-modern-chime.md +6 -0
- package/corpus/templates/clips/chrome-extension/public/manifest.json +17 -8
- package/corpus/templates/clips/chrome-extension/src/background.ts +769 -180
- package/corpus/templates/clips/chrome-extension/src/content-script.ts +177 -0
- package/corpus/templates/clips/chrome-extension/src/offscreen.ts +466 -329
- package/corpus/templates/clips/chrome-extension/src/overlay.css +377 -0
- package/corpus/templates/clips/chrome-extension/src/overlay.html +12 -0
- package/corpus/templates/clips/chrome-extension/src/overlay.ts +330 -0
- package/corpus/templates/clips/chrome-extension/src/permission.html +227 -0
- package/corpus/templates/clips/chrome-extension/src/permission.ts +105 -0
- package/corpus/templates/clips/chrome-extension/src/popup.html +3 -0
- package/corpus/templates/clips/chrome-extension/src/popup.ts +50 -2
- package/corpus/templates/clips/chrome-extension/src/styles.css +50 -0
- package/corpus/templates/clips/chrome-extension/vite.config.ts +3 -0
- package/corpus/templates/clips/desktop/src/lib/audio-cue.ts +41 -24
- package/corpus/templates/clips/desktop/src/lib/bubble-webrtc.ts +119 -2
- package/corpus/templates/clips/desktop/src/lib/recorder.ts +20 -0
- package/corpus/templates/clips/desktop/src/main.tsx +3 -0
- package/corpus/templates/clips/desktop/src/overlays/countdown.tsx +21 -2
- package/corpus/templates/clips/desktop/src/overlays/region-record-border.tsx +76 -0
- package/corpus/templates/clips/desktop/src/styles.css +119 -2
- package/corpus/templates/clips/desktop/src-tauri/capabilities/default.json +2 -1
- package/corpus/templates/clips/desktop/src-tauri/src/clips/mod.rs +140 -1
- package/corpus/templates/clips/desktop/src-tauri/src/lib.rs +2 -0
- package/corpus/templates/clips/desktop/src-tauri/src/util.rs +4 -1
- package/corpus/templates/clips/server/routes/api/video/[recordingId].get.ts +85 -6
- package/corpus/templates/clips/shared/share-attribution.ts +79 -0
- package/corpus/templates/content/AGENTS.md +20 -3
- package/corpus/templates/content/CHANGELOG.md +10 -0
- package/corpus/templates/content/actions/_database-utils.ts +15 -0
- package/corpus/templates/content/actions/_property-utils.ts +371 -4
- package/corpus/templates/content/actions/configure-document-property.ts +35 -1
- package/corpus/templates/content/actions/create-content-database.ts +5 -1
- package/corpus/templates/content/actions/delete-document-property.ts +52 -2
- package/corpus/templates/content/actions/duplicate-document-property.ts +34 -17
- package/corpus/templates/content/actions/reorder-document-property.ts +79 -0
- package/corpus/templates/content/actions/set-document-property.ts +39 -0
- package/corpus/templates/content/app/components/editor/DocumentBlockFields.tsx +806 -0
- package/corpus/templates/content/app/components/editor/DocumentDatabase.tsx +226 -68
- package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +59 -30
- package/corpus/templates/content/app/components/editor/DocumentProperties.tsx +25 -1
- package/corpus/templates/content/app/components/editor/blockFieldSaveController.ts +180 -0
- package/corpus/templates/content/app/components/editor/blockFieldSaveRegistry.ts +179 -0
- package/corpus/templates/content/app/components/editor/previewDocumentSaveController.ts +244 -0
- package/corpus/templates/content/app/components/editor/previewDocumentSaveRegistry.ts +132 -0
- package/corpus/templates/content/app/global.css +9 -0
- package/corpus/templates/content/app/hooks/use-document-properties.ts +21 -0
- package/corpus/templates/content/app/root.tsx +7 -1
- package/corpus/templates/content/server/db/schema.ts +30 -0
- package/corpus/templates/content/server/plugins/db.ts +87 -0
- package/corpus/templates/content/shared/api.ts +7 -0
- package/corpus/templates/content/shared/properties.ts +109 -0
- package/corpus/templates/design/CHANGELOG.md +10 -0
- package/corpus/templates/design/actions/generate-design.ts +4 -0
- package/corpus/templates/design/app/root.tsx +7 -1
- package/corpus/templates/design/app/routes/settings.tsx +10 -2
- package/corpus/templates/dispatch/CHANGELOG.md +10 -0
- package/corpus/templates/dispatch/app/root.tsx +7 -1
- package/corpus/templates/forms/.agents/skills/form-responses/SKILL.md +10 -0
- package/corpus/templates/forms/CHANGELOG.md +10 -0
- package/corpus/templates/forms/actions/export-responses.ts +6 -0
- package/corpus/templates/forms/actions/list-responses.ts +2 -0
- package/corpus/templates/forms/actions/response-insights.ts +2 -0
- package/corpus/templates/forms/app/pages/ResponsesPage.tsx +138 -1
- package/corpus/templates/forms/app/root.tsx +7 -1
- package/corpus/templates/forms/changelog/2026-06-23-response-tables-now-show-the-page-each-submission-came-from-.md +6 -0
- package/corpus/templates/forms/changelog/2026-06-23-response-tables-now-show-whether-feedback-came-from-the-web-.md +6 -0
- package/corpus/templates/forms/server/db/schema.ts +7 -0
- package/corpus/templates/forms/server/handlers/submissions.ts +19 -2
- package/corpus/templates/forms/server/lib/integrations.ts +76 -2
- package/corpus/templates/forms/server/plugins/db.ts +20 -0
- package/corpus/templates/forms/shared/types.ts +12 -0
- package/corpus/templates/macros/CHANGELOG.md +10 -0
- package/corpus/templates/macros/app/root.tsx +7 -1
- package/corpus/templates/mail/CHANGELOG.md +10 -0
- package/corpus/templates/mail/app/components/layout/CommandPalette.tsx +3 -0
- package/corpus/templates/mail/app/pages/SettingsPage.tsx +27 -0
- package/corpus/templates/plan/.agents/skills/visual-plan/SKILL.md +31 -17
- package/corpus/templates/plan/.agents/skills/visual-plan/references/wireframe.md +7 -4
- package/corpus/templates/plan/.agents/skills/visual-recap/SKILL.md +34 -24
- package/corpus/templates/plan/.agents/skills/visual-recap/references/wireframe.md +7 -4
- package/corpus/templates/plan/AGENTS.md +4 -0
- package/corpus/templates/plan/CHANGELOG.md +10 -0
- package/corpus/templates/plan/actions/view-screen.ts +5 -0
- package/corpus/templates/plan/app/components/layout/Sidebar.tsx +98 -1
- package/corpus/templates/plan/app/components/plan/wireframe/html-artboard.css +3 -1
- package/corpus/templates/plan/app/pages/PlansPage.tsx +107 -7
- package/corpus/templates/plan/app/root.tsx +7 -1
- package/corpus/templates/plan/changelog/2026-06-23-large-diagrams-in-a-plan-now-scroll-within-their-block-inste.md +6 -0
- package/corpus/templates/plan/shared/share-attribution.ts +72 -0
- package/corpus/templates/slides/CHANGELOG.md +10 -0
- package/corpus/templates/slides/app/root.tsx +7 -1
- package/corpus/templates/videos/CHANGELOG.md +4 -151
- package/corpus/templates/videos/app/root.tsx +7 -1
- package/dist/action.js +87 -0
- package/dist/action.js.map +1 -1
- package/dist/agent/engine/builder-engine.d.ts +1 -1
- package/dist/agent/engine/builder-engine.d.ts.map +1 -1
- package/dist/agent/model-config.d.ts +2 -2
- package/dist/agent/model-config.d.ts.map +1 -1
- package/dist/agent/model-config.js +0 -2
- package/dist/agent/model-config.js.map +1 -1
- package/dist/agent/run-loop-with-resume.d.ts +13 -0
- package/dist/agent/run-loop-with-resume.d.ts.map +1 -1
- package/dist/agent/run-loop-with-resume.js +44 -0
- package/dist/agent/run-loop-with-resume.js.map +1 -1
- package/dist/changelog/parse.d.ts +70 -0
- package/dist/changelog/parse.d.ts.map +1 -0
- package/dist/changelog/parse.js +200 -0
- package/dist/changelog/parse.js.map +1 -0
- package/dist/cli/changelog.d.ts +4 -0
- package/dist/cli/changelog.d.ts.map +1 -0
- package/dist/cli/changelog.js +166 -0
- package/dist/cli/changelog.js.map +1 -0
- package/dist/cli/index.js +18 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/recap.d.ts.map +1 -1
- package/dist/cli/recap.js +22 -1
- package/dist/cli/recap.js.map +1 -1
- package/dist/cli/skills.d.ts +3 -3
- package/dist/cli/skills.d.ts.map +1 -1
- package/dist/cli/skills.js +74 -47
- package/dist/cli/skills.js.map +1 -1
- package/dist/client/AgentPanel.d.ts.map +1 -1
- package/dist/client/AgentPanel.js +5 -3
- package/dist/client/AgentPanel.js.map +1 -1
- package/dist/client/AssistantChat.d.ts +6 -1
- package/dist/client/AssistantChat.d.ts.map +1 -1
- package/dist/client/AssistantChat.js +2 -2
- package/dist/client/AssistantChat.js.map +1 -1
- package/dist/client/CommandMenu.d.ts +17 -1
- package/dist/client/CommandMenu.d.ts.map +1 -1
- package/dist/client/CommandMenu.js +49 -13
- package/dist/client/CommandMenu.js.map +1 -1
- package/dist/client/agent-chat-adapter.d.ts.map +1 -1
- package/dist/client/agent-chat-adapter.js +43 -6
- package/dist/client/agent-chat-adapter.js.map +1 -1
- package/dist/client/analytics.d.ts +18 -0
- package/dist/client/analytics.d.ts.map +1 -1
- package/dist/client/analytics.js +177 -0
- package/dist/client/analytics.js.map +1 -1
- package/dist/client/changelog/Changelog.d.ts +47 -0
- package/dist/client/changelog/Changelog.d.ts.map +1 -0
- package/dist/client/changelog/Changelog.js +138 -0
- package/dist/client/changelog/Changelog.js.map +1 -0
- package/dist/client/client-surface.d.ts +17 -0
- package/dist/client/client-surface.d.ts.map +1 -0
- package/dist/client/client-surface.js +24 -0
- package/dist/client/client-surface.js.map +1 -0
- package/dist/client/composer/TiptapComposer.d.ts +26 -1
- package/dist/client/composer/TiptapComposer.d.ts.map +1 -1
- package/dist/client/composer/TiptapComposer.js +27 -5
- package/dist/client/composer/TiptapComposer.js.map +1 -1
- package/dist/client/feedback-context.d.ts +3 -0
- package/dist/client/feedback-context.d.ts.map +1 -1
- package/dist/client/feedback-context.js +2 -0
- package/dist/client/feedback-context.js.map +1 -1
- package/dist/client/index.d.ts +3 -1
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +3 -1
- package/dist/client/index.js.map +1 -1
- package/dist/client/mcp-apps/McpAppRenderer.d.ts +1 -0
- package/dist/client/mcp-apps/McpAppRenderer.d.ts.map +1 -1
- package/dist/client/mcp-apps/McpAppRenderer.js +42 -1
- package/dist/client/mcp-apps/McpAppRenderer.js.map +1 -1
- package/dist/client/settings/useBuilderStatus.d.ts +2 -0
- package/dist/client/settings/useBuilderStatus.d.ts.map +1 -1
- package/dist/client/settings/useBuilderStatus.js +58 -10
- package/dist/client/settings/useBuilderStatus.js.map +1 -1
- package/dist/client/sse-event-processor.d.ts.map +1 -1
- package/dist/client/sse-event-processor.js +7 -1
- package/dist/client/sse-event-processor.js.map +1 -1
- package/dist/mcp/build-server.d.ts +21 -0
- package/dist/mcp/build-server.d.ts.map +1 -1
- package/dist/mcp/build-server.js +43 -1
- package/dist/mcp/build-server.js.map +1 -1
- package/dist/mcp/embed-app.d.ts.map +1 -1
- package/dist/mcp/embed-app.js +197 -7
- package/dist/mcp/embed-app.js.map +1 -1
- package/dist/mcp/oauth-route.d.ts.map +1 -1
- package/dist/mcp/oauth-route.js +95 -12
- package/dist/mcp/oauth-route.js.map +1 -1
- package/dist/server/attribution.d.ts +83 -0
- package/dist/server/attribution.d.ts.map +1 -0
- package/dist/server/attribution.js +178 -0
- package/dist/server/attribution.js.map +1 -0
- package/dist/server/auth.d.ts.map +1 -1
- package/dist/server/auth.js +7 -0
- package/dist/server/auth.js.map +1 -1
- package/dist/server/better-auth-instance.d.ts +9 -1
- package/dist/server/better-auth-instance.d.ts.map +1 -1
- package/dist/server/better-auth-instance.js +32 -2
- package/dist/server/better-auth-instance.js.map +1 -1
- package/dist/server/builder-browser.d.ts +4 -0
- package/dist/server/builder-browser.d.ts.map +1 -1
- package/dist/server/builder-browser.js +20 -1
- package/dist/server/builder-browser.js.map +1 -1
- package/dist/server/core-routes-plugin.d.ts +25 -0
- package/dist/server/core-routes-plugin.d.ts.map +1 -1
- package/dist/server/core-routes-plugin.js +101 -0
- package/dist/server/core-routes-plugin.js.map +1 -1
- package/dist/server/google-oauth.d.ts.map +1 -1
- package/dist/server/google-oauth.js +16 -2
- package/dist/server/google-oauth.js.map +1 -1
- package/dist/server/onboarding-html.d.ts.map +1 -1
- package/dist/server/onboarding-html.js +35 -0
- package/dist/server/onboarding-html.js.map +1 -1
- package/dist/shared/mcp-embed-headers.js +1 -1
- package/dist/shared/mcp-embed-headers.js.map +1 -1
- package/dist/styles/agent-conversation.css +21 -0
- package/dist/templates/workspace-core/.agents/skills/changelog/SKILL.md +111 -0
- package/dist/templates/workspace-core/.agents/skills/reliable-mutations/SKILL.md +72 -0
- package/dist/templates/workspace-core/.agents/skills/tracking/SKILL.md +46 -14
- package/docs/design/durable-agent-runs.md +217 -0
- package/package.json +1 -1
- package/src/templates/workspace-core/.agents/skills/changelog/SKILL.md +111 -0
- package/src/templates/workspace-core/.agents/skills/reliable-mutations/SKILL.md +72 -0
- package/src/templates/workspace-core/.agents/skills/tracking/SKILL.md +46 -14
- package/corpus/templates/analytics/app/pages/About.tsx +0 -108
- package/corpus/templates/analytics/app/routes/about.tsx +0 -9
- /package/corpus/templates/analytics/app/routes/{dashboards.tsx → dashboards._index.tsx} +0 -0
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
// Module-level registry of ONE save controller per PEEK document id.
|
|
2
|
+
//
|
|
3
|
+
// PROBLEM (the rebase race class — what the per-doc lane could NOT fix):
|
|
4
|
+
// The row peek renders a SINGLE `DatabaseItemPreview` instance whose `item`
|
|
5
|
+
// prop changes on row-switch (it has no per-row `key`, so it does not remount).
|
|
6
|
+
// The old design gave it ONE controller whose target document id was REBASED on
|
|
7
|
+
// every row-switch, plus a shared cross-doc serialization lane. That produced
|
|
8
|
+
// two timing bugs no patch could fully close:
|
|
9
|
+
// - lane queue-jump: a `running`/`tail` microtask gap let a new enqueue
|
|
10
|
+
// dispatch synchronously AHEAD of already-queued saves; and
|
|
11
|
+
// - stale completion after rebase: an OLD-row in-flight save resolving AFTER
|
|
12
|
+
// the row-switch `mark()` overwrote the SHARED controller's `lastSaved` with
|
|
13
|
+
// the old payload and triggered a redundant save against the NEW row.
|
|
14
|
+
// Both are properties of sharing one mutable-target controller across rows.
|
|
15
|
+
//
|
|
16
|
+
// FIX (one controller per document id, ref-counted — mirrors blockFieldSaveRegistry):
|
|
17
|
+
// There is exactly ONE controller per document id. A controller's target doc id
|
|
18
|
+
// is fixed at creation and NEVER changes. Switching rows RELEASES the old doc's
|
|
19
|
+
// controller (whose release flushes its pending save synchronously, bound to its
|
|
20
|
+
// own id) and ACQUIRES the new doc's controller. No `saveTargetIdRef` rebase on
|
|
21
|
+
// a live controller; no cross-doc shared mutable target; no cross-doc lane.
|
|
22
|
+
//
|
|
23
|
+
// With per-doc controllers each is single-flight + trailing for ITS doc only:
|
|
24
|
+
// - A stale completion can only ever advance ITS OWN controller's `lastSaved`
|
|
25
|
+
// (correct) — never another row's. The stale-completion-after-rebase bug is
|
|
26
|
+
// structurally impossible.
|
|
27
|
+
// - Single-flight per doc means at most one save in flight per id, so there is
|
|
28
|
+
// nothing left for a serialization lane to order. The lane is deleted.
|
|
29
|
+
//
|
|
30
|
+
// - acquire(id, factory): returns the SAME controller for a doc id, creating it
|
|
31
|
+
// once via `factory` and bumping a ref-count.
|
|
32
|
+
// - release(id): decrements the ref-count. At 0 we do NOT evict immediately: we
|
|
33
|
+
// flush-then-evict. The final flush still dispatches synchronously bound to
|
|
34
|
+
// this doc id, and a quick reopen BEFORE the flush settles re-acquires the
|
|
35
|
+
// SAME instance (eviction cancelled) — so a quick reopen reuses the live
|
|
36
|
+
// controller and there is never a competing second controller for the id.
|
|
37
|
+
|
|
38
|
+
import type { PreviewDocumentSaveController } from "./previewDocumentSaveController";
|
|
39
|
+
|
|
40
|
+
interface Entry {
|
|
41
|
+
controller: PreviewDocumentSaveController;
|
|
42
|
+
refCount: number;
|
|
43
|
+
// Set while a flush-then-evict is pending after refCount hit 0. If a reopen
|
|
44
|
+
// re-acquires before the flush settles, we clear this so the entry is NOT
|
|
45
|
+
// evicted out from under the live instance.
|
|
46
|
+
evicting: boolean;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const registry = new Map<string, Entry>();
|
|
50
|
+
|
|
51
|
+
function payloadsEqual(
|
|
52
|
+
a: PreviewDocumentSaveController["pending"],
|
|
53
|
+
b: PreviewDocumentSaveController["pending"],
|
|
54
|
+
) {
|
|
55
|
+
return a.title === b.title && a.content === b.content;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function controllerIsDirty(controller: PreviewDocumentSaveController): boolean {
|
|
59
|
+
return !payloadsEqual(controller.pending, controller.lastSaved);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Acquire the controller for `documentId`, creating it once via `factory`.
|
|
64
|
+
* Increments the ref-count and cancels any in-progress eviction so a reopen
|
|
65
|
+
* reuses the live instance rather than racing a fresh one.
|
|
66
|
+
*/
|
|
67
|
+
export function acquirePreviewDocumentSaveController(
|
|
68
|
+
documentId: string,
|
|
69
|
+
factory: () => PreviewDocumentSaveController,
|
|
70
|
+
): PreviewDocumentSaveController {
|
|
71
|
+
let entry = registry.get(documentId);
|
|
72
|
+
if (!entry) {
|
|
73
|
+
entry = { controller: factory(), refCount: 0, evicting: false };
|
|
74
|
+
registry.set(documentId, entry);
|
|
75
|
+
}
|
|
76
|
+
entry.refCount += 1;
|
|
77
|
+
// A reopen before a pending eviction settled: keep the instance alive.
|
|
78
|
+
entry.evicting = false;
|
|
79
|
+
return entry.controller;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Return the EXISTING controller for `documentId`, or undefined if none is
|
|
84
|
+
* registered. Does NOT create an entry and does NOT change the ref-count.
|
|
85
|
+
*/
|
|
86
|
+
export function peekPreviewDocumentSaveController(
|
|
87
|
+
documentId: string,
|
|
88
|
+
): PreviewDocumentSaveController | undefined {
|
|
89
|
+
return registry.get(documentId)?.controller;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Release one reference to the controller for `documentId`. When the last
|
|
94
|
+
* reference is released we flush-then-evict: flush the latest dirty payload so a
|
|
95
|
+
* debounce that hadn't fired is not dropped (dispatched synchronously, bound to
|
|
96
|
+
* THIS doc id), then remove the entry ONLY if it is still unreferenced after the
|
|
97
|
+
* flush settles (a reopen during the flush re-acquires the same instance and
|
|
98
|
+
* cancels the eviction).
|
|
99
|
+
*/
|
|
100
|
+
export function releasePreviewDocumentSaveController(documentId: string): void {
|
|
101
|
+
const entry = registry.get(documentId);
|
|
102
|
+
if (!entry) return;
|
|
103
|
+
entry.refCount -= 1;
|
|
104
|
+
if (entry.refCount > 0) return;
|
|
105
|
+
|
|
106
|
+
entry.evicting = true;
|
|
107
|
+
const settle = () => {
|
|
108
|
+
const current = registry.get(documentId);
|
|
109
|
+
// Evict only if it is the SAME entry, still unreferenced, and still marked
|
|
110
|
+
// for eviction (a reopen would have flipped `evicting` off / refCount up).
|
|
111
|
+
if (current === entry && current.refCount === 0 && current.evicting) {
|
|
112
|
+
if (controllerIsDirty(current.controller)) {
|
|
113
|
+
current.evicting = false;
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
registry.delete(documentId);
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
// flush() dispatches the final save synchronously (so it lands before any
|
|
120
|
+
// teardown/navigation) and resolves once it has settled, before we drop state.
|
|
121
|
+
Promise.resolve(entry.controller.flush()).then(settle, settle);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** Test-only: how many controllers the registry currently holds. */
|
|
125
|
+
export function activePreviewControllerCount(): number {
|
|
126
|
+
return registry.size;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** Test-only: reset the registry between tests. */
|
|
130
|
+
export function __resetPreviewDocumentSaveRegistry(): void {
|
|
131
|
+
registry.clear();
|
|
132
|
+
}
|
|
@@ -296,6 +296,15 @@
|
|
|
296
296
|
min-height: max(320px, 50vh);
|
|
297
297
|
}
|
|
298
298
|
|
|
299
|
+
/* Block fields stack multiple editors, so each surface uses a compact
|
|
300
|
+
min-height instead of the full-page max(320px, 50vh) — otherwise every field
|
|
301
|
+
reserves ~half the viewport and the gap between fields balloons. 6rem is the
|
|
302
|
+
Tailwind `min-h-24` step (~70% smaller than 320px). */
|
|
303
|
+
.block-field-content .visual-editor-wrapper,
|
|
304
|
+
.block-field-content .visual-editor-wrapper .notion-editor {
|
|
305
|
+
min-height: 6rem;
|
|
306
|
+
}
|
|
307
|
+
|
|
299
308
|
.notion-editor > *:first-child {
|
|
300
309
|
margin-top: 0;
|
|
301
310
|
}
|
|
@@ -5,6 +5,7 @@ import type {
|
|
|
5
5
|
DeleteDocumentPropertyRequest,
|
|
6
6
|
DocumentPropertiesResponse,
|
|
7
7
|
DuplicateDocumentPropertyRequest,
|
|
8
|
+
ReorderDocumentPropertyRequest,
|
|
8
9
|
SetDocumentPropertyRequest,
|
|
9
10
|
} from "@shared/api";
|
|
10
11
|
|
|
@@ -79,6 +80,26 @@ export function useDuplicateDocumentProperty(documentId: string) {
|
|
|
79
80
|
});
|
|
80
81
|
}
|
|
81
82
|
|
|
83
|
+
export function useReorderDocumentProperty(documentId: string) {
|
|
84
|
+
const queryClient = useQueryClient();
|
|
85
|
+
return useActionMutation<
|
|
86
|
+
DocumentPropertiesResponse,
|
|
87
|
+
ReorderDocumentPropertyRequest
|
|
88
|
+
>("reorder-document-property", {
|
|
89
|
+
onSuccess: () => {
|
|
90
|
+
queryClient.invalidateQueries({
|
|
91
|
+
queryKey: ["action", "list-document-properties", { documentId }],
|
|
92
|
+
});
|
|
93
|
+
queryClient.invalidateQueries({
|
|
94
|
+
queryKey: ["action", "get-document", { id: documentId }],
|
|
95
|
+
});
|
|
96
|
+
queryClient.invalidateQueries({
|
|
97
|
+
queryKey: ["action", "get-content-database"],
|
|
98
|
+
});
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
82
103
|
export function useDeleteDocumentProperty(documentId: string) {
|
|
83
104
|
const queryClient = useQueryClient();
|
|
84
105
|
return useActionMutation<
|
|
@@ -26,6 +26,7 @@ import {
|
|
|
26
26
|
} from "@agent-native/core/client";
|
|
27
27
|
import { useDbSync } from "./hooks/use-db-sync";
|
|
28
28
|
import { useNavigationState } from "./hooks/use-navigation-state";
|
|
29
|
+
import changelog from "../CHANGELOG.md?raw";
|
|
29
30
|
import type { LinksFunction } from "react-router";
|
|
30
31
|
import stylesheet from "./global.css?url";
|
|
31
32
|
import { configureTracking } from "@agent-native/core/client";
|
|
@@ -247,7 +248,12 @@ export default function Root() {
|
|
|
247
248
|
>
|
|
248
249
|
<AppSetup />
|
|
249
250
|
<Toaster />
|
|
250
|
-
<CommandMenu
|
|
251
|
+
<CommandMenu
|
|
252
|
+
open={cmdkOpen}
|
|
253
|
+
onOpenChange={setCmdkOpen}
|
|
254
|
+
changelog={changelog}
|
|
255
|
+
changelogKey="content"
|
|
256
|
+
>
|
|
251
257
|
<CommandMenu.Group heading="Content">
|
|
252
258
|
<CommandMenu.Item onSelect={() => {}}>
|
|
253
259
|
Search documents
|
|
@@ -102,6 +102,17 @@ export const contentDatabases = table("content_databases", {
|
|
|
102
102
|
documentId: text("document_id").notNull(),
|
|
103
103
|
title: text("title").notNull().default("Untitled database"),
|
|
104
104
|
viewConfigJson: text("view_config_json").notNull().default("{}"),
|
|
105
|
+
// Single source of truth for the primary "Content" Blocks field — the one
|
|
106
|
+
// backed by `documents.content`. A DB-enforced single-primary invariant: at
|
|
107
|
+
// most one property id lives here, so two concurrent seeds can never produce
|
|
108
|
+
// two aliasing primaries. NULL means there is currently no primary Blocks
|
|
109
|
+
// field (never seeded, or the primary was intentionally deleted).
|
|
110
|
+
primaryBlocksPropertyId: text("primary_blocks_property_id"),
|
|
111
|
+
// 1 once a database has been seeded with its primary Blocks field at least
|
|
112
|
+
// once. Distinguishes "never seeded" (legacy database needing backfill) from
|
|
113
|
+
// "primary intentionally deleted" (seeded once, then removed — must NOT be
|
|
114
|
+
// reseeded). See delete-document-property.
|
|
115
|
+
blocksSeeded: integer("blocks_seeded").notNull().default(0),
|
|
105
116
|
createdAt: text("created_at").notNull().default(now()),
|
|
106
117
|
updatedAt: text("updated_at").notNull().default(now()),
|
|
107
118
|
});
|
|
@@ -243,4 +254,23 @@ export const documentPropertyValues = table("document_property_values", {
|
|
|
243
254
|
updatedAt: text("updated_at").notNull().default(now()),
|
|
244
255
|
});
|
|
245
256
|
|
|
257
|
+
// Independent backing store for ADDITIONAL "Blocks" property fields. The
|
|
258
|
+
// default/primary Blocks field ("Content") is backed by `documents.content`
|
|
259
|
+
// (so the existing TipTap/Yjs editor, collab, and existing data migrate for
|
|
260
|
+
// free). Every other Blocks field on a row gets its OWN content here, keyed by
|
|
261
|
+
// (documentId, propertyId) — guaranteeing no two Blocks fields ever alias the
|
|
262
|
+
// same content. Stored as markdown, same shape as `documents.content`.
|
|
263
|
+
export const documentBlockFieldContents = table(
|
|
264
|
+
"document_block_field_contents",
|
|
265
|
+
{
|
|
266
|
+
id: text("id").primaryKey(),
|
|
267
|
+
ownerEmail: text("owner_email").notNull().default("local@localhost"),
|
|
268
|
+
documentId: text("document_id").notNull(),
|
|
269
|
+
propertyId: text("property_id").notNull(),
|
|
270
|
+
content: text("content").notNull().default(""),
|
|
271
|
+
createdAt: text("created_at").notNull().default(now()),
|
|
272
|
+
updatedAt: text("updated_at").notNull().default(now()),
|
|
273
|
+
},
|
|
274
|
+
);
|
|
275
|
+
|
|
246
276
|
export const documentShares = createSharesTable("document_shares");
|
|
@@ -1,4 +1,17 @@
|
|
|
1
1
|
import { runMigrations } from "@agent-native/core/db";
|
|
2
|
+
import { repairUnseededBlocksFields } from "../../actions/_property-utils.js";
|
|
3
|
+
|
|
4
|
+
function scheduleBlocksRepairRetry(attempt = 1): void {
|
|
5
|
+
const delayMs = Math.min(30_000 * attempt, 5 * 60_000);
|
|
6
|
+
const timeout = setTimeout(() => {
|
|
7
|
+
repairUnseededBlocksFields().catch(() => {
|
|
8
|
+
if (attempt < 5) scheduleBlocksRepairRetry(attempt + 1);
|
|
9
|
+
});
|
|
10
|
+
}, delayMs);
|
|
11
|
+
if (typeof timeout === "object" && "unref" in timeout) {
|
|
12
|
+
timeout.unref();
|
|
13
|
+
}
|
|
14
|
+
}
|
|
2
15
|
|
|
3
16
|
const runContentMigrations = runMigrations(
|
|
4
17
|
[
|
|
@@ -418,6 +431,68 @@ const runContentMigrations = runMigrations(
|
|
|
418
431
|
CREATE INDEX IF NOT EXISTS content_database_source_executions_change_set_idx ON content_database_source_executions (change_set_id);
|
|
419
432
|
CREATE INDEX IF NOT EXISTS content_database_source_executions_idempotency_idx ON content_database_source_executions (idempotency_key)`,
|
|
420
433
|
},
|
|
434
|
+
{
|
|
435
|
+
// Independent backing store for ADDITIONAL "Blocks" property fields. The
|
|
436
|
+
// primary "Content" Blocks field is backed by documents.content; every
|
|
437
|
+
// other Blocks field on a row stores its own content here, keyed by
|
|
438
|
+
// (document_id, property_id), so no two Blocks fields ever share content.
|
|
439
|
+
version: 48,
|
|
440
|
+
sql: `CREATE TABLE IF NOT EXISTS document_block_field_contents (
|
|
441
|
+
id TEXT PRIMARY KEY,
|
|
442
|
+
owner_email TEXT NOT NULL DEFAULT 'local@localhost',
|
|
443
|
+
document_id TEXT NOT NULL,
|
|
444
|
+
property_id TEXT NOT NULL,
|
|
445
|
+
content TEXT NOT NULL DEFAULT '',
|
|
446
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
447
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
448
|
+
)`,
|
|
449
|
+
},
|
|
450
|
+
{
|
|
451
|
+
version: 49,
|
|
452
|
+
sql: `CREATE INDEX IF NOT EXISTS document_block_field_contents_document_idx ON document_block_field_contents (document_id);
|
|
453
|
+
CREATE UNIQUE INDEX IF NOT EXISTS document_block_field_contents_doc_prop_idx ON document_block_field_contents (document_id, property_id)`,
|
|
454
|
+
},
|
|
455
|
+
// v50-v52: DB-enforced single-primary Blocks invariant. `primary_blocks_property_id`
|
|
456
|
+
// is the one source of truth for which property backs `documents.content`;
|
|
457
|
+
// `blocks_seeded` records that a database was seeded once, so an intentionally
|
|
458
|
+
// deleted primary is never silently recreated. Both are additive and safe on
|
|
459
|
+
// existing data.
|
|
460
|
+
{
|
|
461
|
+
version: 50,
|
|
462
|
+
sql: `ALTER TABLE content_databases ADD COLUMN IF NOT EXISTS primary_blocks_property_id TEXT`,
|
|
463
|
+
},
|
|
464
|
+
{
|
|
465
|
+
version: 51,
|
|
466
|
+
sql: `ALTER TABLE content_databases ADD COLUMN IF NOT EXISTS blocks_seeded INTEGER NOT NULL DEFAULT 0`,
|
|
467
|
+
},
|
|
468
|
+
// v52: one-time backfill for LEGACY databases that already had a primary
|
|
469
|
+
// "Content" Blocks definition (seeded by the previous read-path safety net)
|
|
470
|
+
// before these columns existed. Point primary_blocks_property_id at that
|
|
471
|
+
// definition and mark the database seeded. Idempotent: only fills rows that
|
|
472
|
+
// are still NULL, and re-running is a no-op. Databases with NO primary
|
|
473
|
+
// definition are intentionally left unseeded — the startup repair seeds them
|
|
474
|
+
// exactly once via the authenticated path. The correlated subquery picks the
|
|
475
|
+
// primary definition by its options JSON marker (`"primary":true`); the
|
|
476
|
+
// simple `%...%` LIKE is portable across SQLite and Postgres.
|
|
477
|
+
{
|
|
478
|
+
version: 52,
|
|
479
|
+
sql: `UPDATE content_databases
|
|
480
|
+
SET primary_blocks_property_id = (
|
|
481
|
+
SELECT d.id FROM document_property_definitions d
|
|
482
|
+
WHERE d.database_id = content_databases.id
|
|
483
|
+
AND d.type = 'blocks'
|
|
484
|
+
AND d.options_json LIKE '%"primary":true%'
|
|
485
|
+
LIMIT 1
|
|
486
|
+
),
|
|
487
|
+
blocks_seeded = 1
|
|
488
|
+
WHERE primary_blocks_property_id IS NULL
|
|
489
|
+
AND EXISTS (
|
|
490
|
+
SELECT 1 FROM document_property_definitions d
|
|
491
|
+
WHERE d.database_id = content_databases.id
|
|
492
|
+
AND d.type = 'blocks'
|
|
493
|
+
AND d.options_json LIKE '%"primary":true%'
|
|
494
|
+
)`,
|
|
495
|
+
},
|
|
421
496
|
],
|
|
422
497
|
{ table: "content_migrations" },
|
|
423
498
|
);
|
|
@@ -453,4 +528,16 @@ export default async function contentDatabasePlugin(
|
|
|
453
528
|
) {
|
|
454
529
|
await runContentMigrations(nitroApp);
|
|
455
530
|
await runContentSourceMigrations(nitroApp);
|
|
531
|
+
// One-time, boot-time repair: seed the primary "Content" Blocks field for any
|
|
532
|
+
// legacy database that has never been seeded (blocks_seeded = 0). Idempotent —
|
|
533
|
+
// the atomic claim in seedDefaultBlocksField makes re-runs no-ops, and it
|
|
534
|
+
// never runs from a read path, so opening a shared/legacy row stays a pure
|
|
535
|
+
// read. Failures here must not crash boot; migrations themselves succeeded.
|
|
536
|
+
try {
|
|
537
|
+
await repairUnseededBlocksFields();
|
|
538
|
+
} catch {
|
|
539
|
+
// Retry in-process so a transient boot-time repair failure does not leave
|
|
540
|
+
// legacy databases without their primary Blocks field until a full reboot.
|
|
541
|
+
scheduleBlocksRepairRetry();
|
|
542
|
+
}
|
|
456
543
|
}
|
|
@@ -194,6 +194,13 @@ export interface DeleteDocumentPropertyRequest {
|
|
|
194
194
|
propertyId: string;
|
|
195
195
|
}
|
|
196
196
|
|
|
197
|
+
export interface ReorderDocumentPropertyRequest {
|
|
198
|
+
documentId: string;
|
|
199
|
+
propertyId: string;
|
|
200
|
+
targetPropertyId: string;
|
|
201
|
+
position?: "before" | "after";
|
|
202
|
+
}
|
|
203
|
+
|
|
197
204
|
export interface ContentDatabase {
|
|
198
205
|
id: string;
|
|
199
206
|
documentId: string;
|
|
@@ -13,6 +13,11 @@ export const EDITABLE_DOCUMENT_PROPERTY_TYPES = [
|
|
|
13
13
|
"email",
|
|
14
14
|
"phone",
|
|
15
15
|
"relation",
|
|
16
|
+
// Capacities-style rich-text body field. Each Blocks field is its OWN
|
|
17
|
+
// independent content (NOT an alias of the page body). The default "Content"
|
|
18
|
+
// field is backed by `documents.content`; additional Blocks fields each get
|
|
19
|
+
// their own row in `document_block_field_contents`.
|
|
20
|
+
"blocks",
|
|
16
21
|
] as const;
|
|
17
22
|
|
|
18
23
|
export const COMPUTED_DOCUMENT_PROPERTY_TYPES = [
|
|
@@ -44,6 +49,7 @@ export const CREATABLE_DOCUMENT_PROPERTY_TYPES = [
|
|
|
44
49
|
"url",
|
|
45
50
|
"email",
|
|
46
51
|
"phone",
|
|
52
|
+
"blocks",
|
|
47
53
|
"id",
|
|
48
54
|
"created_time",
|
|
49
55
|
"created_by",
|
|
@@ -91,6 +97,13 @@ export interface DocumentPropertyOptions {
|
|
|
91
97
|
relation?: {
|
|
92
98
|
databaseId?: string | null;
|
|
93
99
|
};
|
|
100
|
+
// Set on the default/primary "Content" Blocks field. The primary field is the
|
|
101
|
+
// one whose content is backed by `documents.content` (the page body editor).
|
|
102
|
+
// Exactly one Blocks field per database should be primary; additional Blocks
|
|
103
|
+
// fields store their content independently in `document_block_field_contents`.
|
|
104
|
+
blocks?: {
|
|
105
|
+
primary?: boolean;
|
|
106
|
+
};
|
|
94
107
|
rollup?: {
|
|
95
108
|
relationPropertyId?: string | null;
|
|
96
109
|
targetPropertyId?: string | null;
|
|
@@ -137,6 +150,7 @@ export const DOCUMENT_PROPERTY_TYPE_LABELS: Record<
|
|
|
137
150
|
email: "Email",
|
|
138
151
|
phone: "Phone",
|
|
139
152
|
relation: "Relation",
|
|
153
|
+
blocks: "Blocks",
|
|
140
154
|
formula: "Formula",
|
|
141
155
|
rollup: "Rollup",
|
|
142
156
|
id: "ID",
|
|
@@ -161,6 +175,88 @@ export function isComputedPropertyType(
|
|
|
161
175
|
return (COMPUTED_DOCUMENT_PROPERTY_TYPES as readonly string[]).includes(type);
|
|
162
176
|
}
|
|
163
177
|
|
|
178
|
+
// The default name a database's seeded Blocks field gets.
|
|
179
|
+
export const DEFAULT_BLOCKS_FIELD_NAME = "Content";
|
|
180
|
+
|
|
181
|
+
export function isBlocksPropertyType(type: DocumentPropertyType): boolean {
|
|
182
|
+
return type === "blocks";
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// The primary Blocks field is the one backed by `documents.content`. There is
|
|
186
|
+
// at most one per database; it is the field seeded by default and is what the
|
|
187
|
+
// page-body editor reads/writes.
|
|
188
|
+
export function isPrimaryBlocksField(
|
|
189
|
+
options: DocumentPropertyOptions,
|
|
190
|
+
): boolean {
|
|
191
|
+
return options.blocks?.primary === true;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Word count for a Blocks field's markdown content. Strips the lightest layer
|
|
195
|
+
// of markdown punctuation so a "412 words" table cell reflects prose, not
|
|
196
|
+
// syntax. Kept dependency-free because `shared/properties` is bundled into the
|
|
197
|
+
// browser.
|
|
198
|
+
export function countWords(content: string | null | undefined): number {
|
|
199
|
+
const text = (content ?? "")
|
|
200
|
+
// Drop fenced code blocks wholesale — they're not prose.
|
|
201
|
+
.replace(/```[\s\S]*?```/g, " ")
|
|
202
|
+
// Strip inline/other markdown punctuation that would split or pad tokens.
|
|
203
|
+
.replace(/[#>*_`~\-+=|[\]()!]/g, " ")
|
|
204
|
+
.replace(/\s+/g, " ")
|
|
205
|
+
.trim();
|
|
206
|
+
if (!text) return 0;
|
|
207
|
+
return text.split(" ").filter(Boolean).length;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// "412 words" / "1 word" / "Empty" for table cells.
|
|
211
|
+
export function formatWordCount(content: string | null | undefined): string {
|
|
212
|
+
const count = countWords(content);
|
|
213
|
+
if (count === 0) return "Empty";
|
|
214
|
+
return `${count.toLocaleString()} ${count === 1 ? "word" : "words"}`;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Render decision for a set of Blocks-field types on one row:
|
|
218
|
+
// - 0 or 1 Blocks field → "solo" (chromeless: no header, just the body).
|
|
219
|
+
// - 2+ → "multi" (each field gets a header + is collapsible).
|
|
220
|
+
export function blocksRenderMode(blocksFieldCount: number): "solo" | "multi" {
|
|
221
|
+
return blocksFieldCount >= 2 ? "multi" : "solo";
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Whether deleting a property triggers the "only Blocks field" warning — i.e.
|
|
225
|
+
// it is a Blocks field and it is the last one in the type, so removing it drops
|
|
226
|
+
// the body for every object of this type.
|
|
227
|
+
export function isOnlyBlocksFieldDeletion(args: {
|
|
228
|
+
type: DocumentPropertyType;
|
|
229
|
+
blocksFieldCount: number;
|
|
230
|
+
}): boolean {
|
|
231
|
+
return isBlocksPropertyType(args.type) && args.blocksFieldCount <= 1;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Where a Blocks field's content is stored. The primary "Content" field lives
|
|
235
|
+
// on `documents.content` (the body); every other Blocks field has its own row
|
|
236
|
+
// in the block-field content store. This single decision keeps reads and writes
|
|
237
|
+
// in lockstep and guarantees no two fields ever share a backing location.
|
|
238
|
+
export type BlocksStorageTarget = "document_body" | "block_field_store";
|
|
239
|
+
|
|
240
|
+
export function blocksStorageTarget(
|
|
241
|
+
options: DocumentPropertyOptions,
|
|
242
|
+
): BlocksStorageTarget {
|
|
243
|
+
return isPrimaryBlocksField(options) ? "document_body" : "block_field_store";
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// Resolve a Blocks field's value for one row given the document body and the
|
|
247
|
+
// (additional) block-field content store. Each field reads from exactly one
|
|
248
|
+
// place — the primary from the body, others from their own keyed entry — so two
|
|
249
|
+
// Blocks fields can never resolve to the same content.
|
|
250
|
+
export function resolveBlocksFieldValue(args: {
|
|
251
|
+
options: DocumentPropertyOptions;
|
|
252
|
+
documentBody: string | null | undefined;
|
|
253
|
+
blockFieldContent: string | null | undefined;
|
|
254
|
+
}): string {
|
|
255
|
+
return blocksStorageTarget(args.options) === "document_body"
|
|
256
|
+
? (args.documentBody ?? "")
|
|
257
|
+
: (args.blockFieldContent ?? "");
|
|
258
|
+
}
|
|
259
|
+
|
|
164
260
|
export function defaultPropertyOptions(
|
|
165
261
|
type: DocumentPropertyType,
|
|
166
262
|
): DocumentPropertyOptions {
|
|
@@ -198,6 +294,12 @@ export function defaultPropertyOptions(
|
|
|
198
294
|
};
|
|
199
295
|
}
|
|
200
296
|
|
|
297
|
+
// A manually-added Blocks field is independent (non-primary). The seeded
|
|
298
|
+
// default "Content" field is marked primary explicitly at seed time.
|
|
299
|
+
if (type === "blocks") {
|
|
300
|
+
return { blocks: { primary: false } };
|
|
301
|
+
}
|
|
302
|
+
|
|
201
303
|
return {};
|
|
202
304
|
}
|
|
203
305
|
|
|
@@ -221,6 +323,10 @@ export function parsePropertyOptions(
|
|
|
221
323
|
: null,
|
|
222
324
|
}
|
|
223
325
|
: undefined,
|
|
326
|
+
blocks:
|
|
327
|
+
parsed.blocks && typeof parsed.blocks === "object"
|
|
328
|
+
? { primary: parsed.blocks.primary === true }
|
|
329
|
+
: undefined,
|
|
224
330
|
rollup:
|
|
225
331
|
parsed.rollup && typeof parsed.rollup === "object"
|
|
226
332
|
? {
|
|
@@ -436,6 +542,9 @@ export function normalizePropertyValue(
|
|
|
436
542
|
case "url":
|
|
437
543
|
case "email":
|
|
438
544
|
case "phone":
|
|
545
|
+
// A Blocks field's value is its markdown content — a plain string, same
|
|
546
|
+
// shape as `documents.content`.
|
|
547
|
+
case "blocks":
|
|
439
548
|
return String(value);
|
|
440
549
|
case "date":
|
|
441
550
|
return normalizeDatePropertyValue(value);
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable user-facing changes to Design are documented here. Open it any time
|
|
4
|
+
from the command menu (Cmd+K → "What's new") or from Settings.
|
|
5
|
+
|
|
6
|
+
## 2026-06-23
|
|
7
|
+
|
|
8
|
+
### Added
|
|
9
|
+
|
|
10
|
+
- See what's new right inside Design — a changelog now lives in the command menu (Cmd+K) and in Settings.
|
|
@@ -29,6 +29,10 @@ export default defineAction({
|
|
|
29
29
|
"The agent calls this after generating HTML/CSS/JSX content to persist it " +
|
|
30
30
|
"as files in the design project. Creates or updates files as needed. " +
|
|
31
31
|
"Returns the saved files and design URL path for iframe rendering. " +
|
|
32
|
+
"Keep the first save compact and working; for large designs, persist a minimal " +
|
|
33
|
+
"version then refine individual files with `edit-design` (search/replace) rather " +
|
|
34
|
+
"than resending a big multi-file payload — a single oversized payload can get cut " +
|
|
35
|
+
"off mid-stream and stall the turn. " +
|
|
32
36
|
"Do not report a design as ready until this action succeeds.",
|
|
33
37
|
schema: z.object({
|
|
34
38
|
designId: z.string().describe("Design project ID to save content to"),
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
configureTracking,
|
|
15
15
|
} from "@agent-native/core/client";
|
|
16
16
|
import { IconSun, IconMoon } from "@tabler/icons-react";
|
|
17
|
+
import changelog from "../CHANGELOG.md?raw";
|
|
17
18
|
import { Toaster } from "@/components/ui/sonner";
|
|
18
19
|
import { Layout as AppLayout } from "@/components/layout/Layout";
|
|
19
20
|
import type { LinksFunction } from "react-router";
|
|
@@ -99,7 +100,12 @@ export default function Root() {
|
|
|
99
100
|
<AppProviders queryClient={queryClient}>
|
|
100
101
|
<DbSyncSetup />
|
|
101
102
|
<Toaster richColors position="bottom-left" />
|
|
102
|
-
<CommandMenu
|
|
103
|
+
<CommandMenu
|
|
104
|
+
open={cmdkOpen}
|
|
105
|
+
onOpenChange={setCmdkOpen}
|
|
106
|
+
changelog={changelog}
|
|
107
|
+
changelogKey="design"
|
|
108
|
+
>
|
|
103
109
|
<CommandMenu.Group heading="Actions">
|
|
104
110
|
<CommandMenu.Item onSelect={() => {}}>Search</CommandMenu.Item>
|
|
105
111
|
</CommandMenu.Group>
|
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
SettingsPanel,
|
|
3
|
+
useDevMode,
|
|
4
|
+
ChangelogSettingsCard,
|
|
5
|
+
} from "@agent-native/core/client";
|
|
6
|
+
import changelog from "../../CHANGELOG.md?raw";
|
|
2
7
|
|
|
3
8
|
export function meta() {
|
|
4
9
|
return [{ title: "Settings — Design" }];
|
|
@@ -8,12 +13,15 @@ export default function SettingsRoute() {
|
|
|
8
13
|
const { isDevMode, canToggle, setDevMode } = useDevMode();
|
|
9
14
|
|
|
10
15
|
return (
|
|
11
|
-
<div className="flex h-full min-h-0 flex-col bg-background">
|
|
16
|
+
<div className="flex h-full min-h-0 flex-col overflow-y-auto bg-background">
|
|
12
17
|
<SettingsPanel
|
|
13
18
|
isDevMode={isDevMode}
|
|
14
19
|
onToggleDevMode={() => setDevMode(!isDevMode)}
|
|
15
20
|
showDevToggle={canToggle}
|
|
16
21
|
/>
|
|
22
|
+
<div className="mx-auto w-full max-w-2xl px-4 pb-8">
|
|
23
|
+
<ChangelogSettingsCard markdown={changelog} />
|
|
24
|
+
</div>
|
|
17
25
|
</div>
|
|
18
26
|
);
|
|
19
27
|
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable user-facing changes to Agent-Native Dispatch are documented here. Open it any
|
|
4
|
+
time from the command menu (Cmd+K → "What's new").
|
|
5
|
+
|
|
6
|
+
## 2026-06-23
|
|
7
|
+
|
|
8
|
+
### Added
|
|
9
|
+
|
|
10
|
+
- See what's new right inside Agent-Native Dispatch — a changelog now lives in the command menu (Cmd+K).
|
|
@@ -25,6 +25,7 @@ import { useTheme } from "next-themes";
|
|
|
25
25
|
import { Layout as AppLayout } from "@agent-native/dispatch/components";
|
|
26
26
|
import type { LinksFunction } from "react-router";
|
|
27
27
|
import { dispatchExtensions } from "./dispatch-extensions";
|
|
28
|
+
import changelog from "../CHANGELOG.md?raw";
|
|
28
29
|
import stylesheet from "./global.css?url";
|
|
29
30
|
|
|
30
31
|
configureTracking({
|
|
@@ -166,7 +167,12 @@ function AppContent() {
|
|
|
166
167
|
return (
|
|
167
168
|
<>
|
|
168
169
|
<DbSyncSetup />
|
|
169
|
-
<CommandMenu
|
|
170
|
+
<CommandMenu
|
|
171
|
+
open={cmdkOpen}
|
|
172
|
+
onOpenChange={setCmdkOpen}
|
|
173
|
+
changelog={changelog}
|
|
174
|
+
changelogKey="dispatch"
|
|
175
|
+
>
|
|
170
176
|
<CommandMenu.Group heading="Actions">
|
|
171
177
|
<CommandMenu.Item onSelect={() => {}}>Search</CommandMenu.Item>
|
|
172
178
|
</CommandMenu.Group>
|
|
@@ -72,10 +72,20 @@ Each response is stored in the `responses` SQL table:
|
|
|
72
72
|
| `submittedAt` | text | ISO timestamp |
|
|
73
73
|
| `ip` | text | Submitter IP when available |
|
|
74
74
|
| `submitterEmail` | text | Submitter email hint when known |
|
|
75
|
+
| `pageUrl` | text | Page the respondent was on, if sent |
|
|
76
|
+
| `clientSurface` | text | App surface: web, electron, or tauri |
|
|
75
77
|
|
|
76
78
|
`submitterEmail` may come from the logged-in Forms session or from trusted
|
|
77
79
|
feedback clients that pass the logged-in user email as submission metadata.
|
|
78
80
|
|
|
81
|
+
`pageUrl` and `clientSurface` are hidden pass-through fields: trusted embeds
|
|
82
|
+
(e.g. the framework FeedbackButton) forward the URL of the page the respondent
|
|
83
|
+
was on and the runtime shell they were in (`web`, `electron`, or `tauri`) as
|
|
84
|
+
submission metadata, so owners can see which screen and which app feedback came
|
|
85
|
+
from. Both are null for direct fills that send no context, and `clientSurface`
|
|
86
|
+
is allowlisted server-side (unknown values are dropped). The responses table
|
|
87
|
+
surfaces them as "Page" and "Source" columns when any response carries them.
|
|
88
|
+
|
|
79
89
|
The `data` JSON maps field IDs to values:
|
|
80
90
|
|
|
81
91
|
```json
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable user-facing changes to Agent-Native Forms are documented here. Open it any
|
|
4
|
+
time from the command menu (Cmd+K → "What's new").
|
|
5
|
+
|
|
6
|
+
## 2026-06-23
|
|
7
|
+
|
|
8
|
+
### Added
|
|
9
|
+
|
|
10
|
+
- See what's new right inside Agent-Native Forms — a changelog now lives in the command menu (Cmd+K).
|