@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
|
@@ -832,6 +832,38 @@ function contentAfterContinuationPrefix(
|
|
|
832
832
|
return [...delta, ...content.slice(contentIndex)];
|
|
833
833
|
}
|
|
834
834
|
|
|
835
|
+
// Concrete "ship a compact first version, then refine incrementally" guidance
|
|
836
|
+
// keyed by the large-payload action the model was cut off while preparing. A run
|
|
837
|
+
// cut off mid-stream while streaming one big tool input (extension HTML, a full
|
|
838
|
+
// design file set, an entire dashboard config) re-streams the SAME oversized
|
|
839
|
+
// payload on every continuation and never finishes inside the soft-timeout
|
|
840
|
+
// window — the classic thrash loop. Pointing the resumed model at the
|
|
841
|
+
// incremental-edit path for that specific action is what breaks the loop.
|
|
842
|
+
// Returns undefined for tools with no known incremental counterpart so the
|
|
843
|
+
// caller falls back to generic compact-first advice.
|
|
844
|
+
function incrementalActionGuidance(tool: string): string | undefined {
|
|
845
|
+
switch (tool) {
|
|
846
|
+
case "create-extension":
|
|
847
|
+
case "update-extension":
|
|
848
|
+
return "create a compact working v1 with `create-extension`, then use focused `update-extension` edits for refinements";
|
|
849
|
+
case "generate-design":
|
|
850
|
+
case "update-design":
|
|
851
|
+
return "persist a minimal first version (fewer files) with `generate-design`, then refine individual files with `edit-design` search/replace instead of resending everything";
|
|
852
|
+
case "create-visual-plan":
|
|
853
|
+
case "create-ui-plan":
|
|
854
|
+
case "create-plan-design":
|
|
855
|
+
case "create-prototype-plan":
|
|
856
|
+
return "create the plan with its core sections or first screen, then expand it with `update-visual-plan`/`patch-visual-plan-source` follow-up edits";
|
|
857
|
+
case "update-visual-plan":
|
|
858
|
+
case "patch-visual-plan-source":
|
|
859
|
+
return "apply smaller, targeted `patch-visual-plan-source` edits rather than rewriting the whole plan in one call";
|
|
860
|
+
case "update-dashboard":
|
|
861
|
+
return "save a small dashboard first, then add panels one at a time with `update-dashboard` incremental `ops` edits instead of authoring the whole config in one call";
|
|
862
|
+
default:
|
|
863
|
+
return undefined;
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
|
|
835
867
|
function autoContinueMessage(signal: AgentAutoContinueSignal): string {
|
|
836
868
|
const tool = lastActivityTool(signal.activityTrail);
|
|
837
869
|
const reason =
|
|
@@ -845,15 +877,20 @@ function autoContinueMessage(signal: AgentAutoContinueSignal): string {
|
|
|
845
877
|
? "The previous stream ended before the agent sent a final completion signal."
|
|
846
878
|
: "The previous run reached an internal execution budget.";
|
|
847
879
|
// A run_timeout or a mid-stream cutoff while assembling one large action
|
|
848
|
-
// payload (e.g. inlining a big pasted HTML file into `create-extension
|
|
849
|
-
//
|
|
850
|
-
//
|
|
880
|
+
// payload (e.g. inlining a big pasted HTML file into `create-extension`, a
|
|
881
|
+
// full file set into `generate-design`, or a whole `update-dashboard` config)
|
|
882
|
+
// is the classic trigger for the repetition/cutoff loop. Nudge the model
|
|
883
|
+
// toward a compact first version it can actually finish in a single run, then
|
|
884
|
+
// incremental refinements — never re-streaming the same oversized payload.
|
|
851
885
|
const cutoffPreparingAction =
|
|
852
886
|
signal.reason === "run_timeout" || signal.reason === "stream_ended";
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
887
|
+
let actionInputNote = "";
|
|
888
|
+
if (cutoffPreparingAction && tool) {
|
|
889
|
+
const guidance = incrementalActionGuidance(tool);
|
|
890
|
+
actionInputNote = guidance
|
|
891
|
+
? `\n\nThe previous run was cut off while preparing the \`${tool}\` action input before the action could finish. Avoid spending another whole run assembling one large tool payload — ${guidance}.`
|
|
892
|
+
: `\n\nThe previous run was cut off while preparing the \`${tool}\` action input before the action could finish. Avoid re-assembling one large tool payload: produce a compact first result you can finish in a single run, then refine it with smaller follow-up edits.`;
|
|
893
|
+
}
|
|
857
894
|
return `${AUTO_CONTINUE_PROMPT}\n\n${AUTO_CONTINUE_COMPLETION_GUARD}\n\nInternal note: ${reason}${actionInputNote}`;
|
|
858
895
|
}
|
|
859
896
|
|
|
@@ -61,6 +61,44 @@ const LLM_CONNECTION_CACHE_TTL_MS = 5 * 60 * 1000;
|
|
|
61
61
|
// session over multiple visits.
|
|
62
62
|
const SESSION_IDLE_TIMEOUT_MS = 30 * 60 * 1000;
|
|
63
63
|
|
|
64
|
+
// First-touch referral attribution (viral attribution). Captured once on the
|
|
65
|
+
// visitor's first page load and persisted across the signup boundary so the
|
|
66
|
+
// server-side `signup` event can record where the user came from. First-write
|
|
67
|
+
// wins — an existing value is never overwritten.
|
|
68
|
+
const FIRST_TOUCH_STORAGE_KEY = "an_attribution";
|
|
69
|
+
const FIRST_TOUCH_COOKIE_NAME = "an_ft";
|
|
70
|
+
// 30 days, matching the session cookie lifetime — long enough to bridge a
|
|
71
|
+
// "land today, sign up next week" path without retaining attribution forever.
|
|
72
|
+
const FIRST_TOUCH_COOKIE_MAX_AGE_SECONDS = 2592000;
|
|
73
|
+
const FIRST_TOUCH_MAX_FIELD_LENGTH = 120;
|
|
74
|
+
// Keep the serialized cookie well under the ~4KB browser cap; we bail rather
|
|
75
|
+
// than write a runaway cookie if some field combination blows past this.
|
|
76
|
+
const FIRST_TOUCH_MAX_COOKIE_BYTES = 1500;
|
|
77
|
+
const FIRST_TOUCH_QUERY_FIELDS = [
|
|
78
|
+
"ref",
|
|
79
|
+
"via",
|
|
80
|
+
"utm_source",
|
|
81
|
+
"utm_medium",
|
|
82
|
+
"utm_campaign",
|
|
83
|
+
"utm_content",
|
|
84
|
+
"utm_term",
|
|
85
|
+
] as const;
|
|
86
|
+
|
|
87
|
+
let _firstTouchCaptured = false;
|
|
88
|
+
|
|
89
|
+
export interface FirstTouchAttribution {
|
|
90
|
+
ref?: string;
|
|
91
|
+
via?: string;
|
|
92
|
+
utm_source?: string;
|
|
93
|
+
utm_medium?: string;
|
|
94
|
+
utm_campaign?: string;
|
|
95
|
+
utm_content?: string;
|
|
96
|
+
utm_term?: string;
|
|
97
|
+
landing_path?: string;
|
|
98
|
+
landing_referrer?: string;
|
|
99
|
+
landed_at?: string;
|
|
100
|
+
}
|
|
101
|
+
|
|
64
102
|
function generateVisitorId(): string {
|
|
65
103
|
try {
|
|
66
104
|
if (
|
|
@@ -214,6 +252,148 @@ function getOrCreateSessionId(): string | undefined {
|
|
|
214
252
|
return id;
|
|
215
253
|
}
|
|
216
254
|
|
|
255
|
+
function truncateFirstTouchField(value: string | null | undefined): string {
|
|
256
|
+
if (!value) return "";
|
|
257
|
+
const trimmed = value.trim();
|
|
258
|
+
if (!trimmed) return "";
|
|
259
|
+
return trimmed.slice(0, FIRST_TOUCH_MAX_FIELD_LENGTH);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Extract just the host of a referrer URL — never the full URL or query
|
|
264
|
+
* string (those can carry tokens). Returns "" when there's no usable host or
|
|
265
|
+
* the referrer is same-origin (a same-site navigation isn't a referral).
|
|
266
|
+
*/
|
|
267
|
+
function scrubReferrerHost(referrer: string | undefined): string {
|
|
268
|
+
if (!referrer) return "";
|
|
269
|
+
try {
|
|
270
|
+
const url = new URL(referrer);
|
|
271
|
+
const host = url.host;
|
|
272
|
+
if (!host) return "";
|
|
273
|
+
if (
|
|
274
|
+
typeof window !== "undefined" &&
|
|
275
|
+
host.toLowerCase() === window.location.host.toLowerCase()
|
|
276
|
+
) {
|
|
277
|
+
return "";
|
|
278
|
+
}
|
|
279
|
+
return truncateFirstTouchField(host);
|
|
280
|
+
} catch {
|
|
281
|
+
return "";
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function buildFirstTouchAttribution(): FirstTouchAttribution {
|
|
286
|
+
const attribution: FirstTouchAttribution = {};
|
|
287
|
+
let params: URLSearchParams | null = null;
|
|
288
|
+
try {
|
|
289
|
+
params = new URLSearchParams(window.location.search);
|
|
290
|
+
} catch {
|
|
291
|
+
params = null;
|
|
292
|
+
}
|
|
293
|
+
if (params) {
|
|
294
|
+
for (const field of FIRST_TOUCH_QUERY_FIELDS) {
|
|
295
|
+
const value = truncateFirstTouchField(params.get(field));
|
|
296
|
+
if (value) attribution[field] = value;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
const landingPath = truncateFirstTouchField(window.location.pathname);
|
|
300
|
+
if (landingPath) attribution.landing_path = landingPath;
|
|
301
|
+
const landingReferrer =
|
|
302
|
+
typeof document !== "undefined" ? scrubReferrerHost(document.referrer) : "";
|
|
303
|
+
if (landingReferrer) attribution.landing_referrer = landingReferrer;
|
|
304
|
+
attribution.landed_at = new Date().toISOString();
|
|
305
|
+
return attribution;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
function readFirstTouchCookie(): string | null {
|
|
309
|
+
if (typeof document === "undefined") return null;
|
|
310
|
+
try {
|
|
311
|
+
const cookies = document.cookie ? document.cookie.split(";") : [];
|
|
312
|
+
for (const part of cookies) {
|
|
313
|
+
const eq = part.indexOf("=");
|
|
314
|
+
if (eq === -1) continue;
|
|
315
|
+
const name = part.slice(0, eq).trim();
|
|
316
|
+
if (name === FIRST_TOUCH_COOKIE_NAME) {
|
|
317
|
+
return part.slice(eq + 1).trim();
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
} catch {
|
|
321
|
+
// document.cookie can throw in sandboxed iframes — best-effort.
|
|
322
|
+
}
|
|
323
|
+
return null;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
function writeFirstTouchCookie(encodedValue: string): void {
|
|
327
|
+
if (typeof document === "undefined") return;
|
|
328
|
+
// Non-sensitive, written by client JS, so no HttpOnly. SameSite=Lax keeps it
|
|
329
|
+
// on top-level navigations (which is how share links arrive) without leaking
|
|
330
|
+
// it to cross-site subresource requests.
|
|
331
|
+
const cookie =
|
|
332
|
+
`${FIRST_TOUCH_COOKIE_NAME}=${encodedValue}; path=/; ` +
|
|
333
|
+
`max-age=${FIRST_TOUCH_COOKIE_MAX_AGE_SECONDS}; SameSite=Lax`;
|
|
334
|
+
if (cookie.length > FIRST_TOUCH_MAX_COOKIE_BYTES) return;
|
|
335
|
+
try {
|
|
336
|
+
document.cookie = cookie;
|
|
337
|
+
} catch {
|
|
338
|
+
// best-effort
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* Capture the visitor's first-touch referral attribution exactly once. Reads
|
|
344
|
+
* the current URL query params + landing info and, IF no attribution is
|
|
345
|
+
* already stored (first-write-wins), persists it to both `localStorage`
|
|
346
|
+
* (`an_attribution`) and the first-party `an_ft` cookie. Fully defensive and
|
|
347
|
+
* SSR-safe — any failure is swallowed so it can never break app boot.
|
|
348
|
+
*/
|
|
349
|
+
function captureFirstTouchAttribution(): void {
|
|
350
|
+
if (_firstTouchCaptured) return;
|
|
351
|
+
_firstTouchCaptured = true;
|
|
352
|
+
if (typeof window === "undefined") return;
|
|
353
|
+
try {
|
|
354
|
+
const existing = safeStorageGet(FIRST_TOUCH_STORAGE_KEY);
|
|
355
|
+
if (existing) {
|
|
356
|
+
// Already captured in a prior visit. Backfill the cookie if it expired
|
|
357
|
+
// or was cleared so the signup boundary still sees first-touch data, but
|
|
358
|
+
// never overwrite the stored value itself (first-write-wins).
|
|
359
|
+
if (!readFirstTouchCookie()) {
|
|
360
|
+
try {
|
|
361
|
+
writeFirstTouchCookie(encodeURIComponent(existing));
|
|
362
|
+
} catch {
|
|
363
|
+
// ignore
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
const attribution = buildFirstTouchAttribution();
|
|
369
|
+
const json = JSON.stringify(attribution);
|
|
370
|
+
safeStorageSet(FIRST_TOUCH_STORAGE_KEY, json);
|
|
371
|
+
writeFirstTouchCookie(encodeURIComponent(json));
|
|
372
|
+
} catch {
|
|
373
|
+
// Attribution is best-effort telemetry; never let it break boot.
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/**
|
|
378
|
+
* Return the parsed first-touch referral attribution captured for this
|
|
379
|
+
* visitor, or `null` when none is stored. Reads from `localStorage`
|
|
380
|
+
* (`an_attribution`). SSR-safe and defensive.
|
|
381
|
+
*/
|
|
382
|
+
export function getFirstTouchAttribution(): FirstTouchAttribution | null {
|
|
383
|
+
if (typeof window === "undefined") return null;
|
|
384
|
+
try {
|
|
385
|
+
const raw = safeStorageGet(FIRST_TOUCH_STORAGE_KEY);
|
|
386
|
+
if (!raw) return null;
|
|
387
|
+
const parsed = JSON.parse(raw) as unknown;
|
|
388
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
389
|
+
return null;
|
|
390
|
+
}
|
|
391
|
+
return parsed as FirstTouchAttribution;
|
|
392
|
+
} catch {
|
|
393
|
+
return null;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
217
397
|
function isLocalAnalyticsHostname(hostname: string | undefined): boolean {
|
|
218
398
|
const h = (hostname || "").toLowerCase();
|
|
219
399
|
return (
|
|
@@ -531,6 +711,7 @@ export function configureTracking(options: {
|
|
|
531
711
|
if (typeof window !== "undefined") {
|
|
532
712
|
ensureSentry();
|
|
533
713
|
ensureAmplitude();
|
|
714
|
+
captureFirstTouchAttribution();
|
|
534
715
|
installLlmConnectionRefresh();
|
|
535
716
|
installPageviewTracking();
|
|
536
717
|
}
|
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Changelog UI — renders an app's CHANGELOG.md as an in-app "What's new"
|
|
3
|
+
* surface. Core owns all of this; a template just passes its own
|
|
4
|
+
* `CHANGELOG.md?raw` content in (Vite inlines it at build time, so this works
|
|
5
|
+
* on every host with no runtime file access or server route).
|
|
6
|
+
*
|
|
7
|
+
* Surfaces:
|
|
8
|
+
* - <ChangelogDialog> a self-contained modal listing every release.
|
|
9
|
+
* - <ChangelogSettingsCard> a settings-page card with the latest updates.
|
|
10
|
+
* - useChangelogSeen() tracks the last release a user has seen (so the
|
|
11
|
+
* command menu can show an "unseen" dot).
|
|
12
|
+
*
|
|
13
|
+
* The command menu's built-in `changelog` prop (see CommandMenu.tsx) wires the
|
|
14
|
+
* dialog automatically — most templates never touch these directly.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import React, { useEffect, useMemo, useState } from "react";
|
|
18
|
+
import { IconX, IconHistory } from "@tabler/icons-react";
|
|
19
|
+
|
|
20
|
+
import { parseChangelog, type ChangelogEntry } from "../../changelog/parse.js";
|
|
21
|
+
import {
|
|
22
|
+
markdownModule,
|
|
23
|
+
remarkGfmFn,
|
|
24
|
+
useMarkdownReady,
|
|
25
|
+
markdownUrlTransform,
|
|
26
|
+
} from "../chat/markdown-renderer.js";
|
|
27
|
+
import { cn } from "../utils.js";
|
|
28
|
+
|
|
29
|
+
// ─── Date formatting ──────────────────────────────────────────────────────────
|
|
30
|
+
|
|
31
|
+
function formatEntryHeading(entry: ChangelogEntry): string {
|
|
32
|
+
if (entry.date) {
|
|
33
|
+
// Parse as a plain calendar date (avoid TZ shifting YYYY-MM-DD back a day).
|
|
34
|
+
const [y, m, d] = entry.date.split("-").map(Number);
|
|
35
|
+
if (y && m && d) {
|
|
36
|
+
const formatted = new Date(y, m - 1, d).toLocaleDateString(undefined, {
|
|
37
|
+
year: "numeric",
|
|
38
|
+
month: "long",
|
|
39
|
+
day: "numeric",
|
|
40
|
+
});
|
|
41
|
+
return entry.version ? `${entry.version} · ${formatted}` : formatted;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return entry.title;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// ─── Markdown body ────────────────────────────────────────────────────────────
|
|
48
|
+
// A small, self-contained renderer for a release body. Avoids depending on the
|
|
49
|
+
// typography plugin (`prose`) being generated in the host template by applying
|
|
50
|
+
// explicit utility classes that Tailwind scans from core's compiled output.
|
|
51
|
+
|
|
52
|
+
const changelogMarkdownComponents = {
|
|
53
|
+
h3: (props: React.HTMLAttributes<HTMLHeadingElement>) => (
|
|
54
|
+
<h3
|
|
55
|
+
{...props}
|
|
56
|
+
className="mb-1.5 mt-3 text-xs font-semibold uppercase tracking-wide text-muted-foreground first:mt-0"
|
|
57
|
+
/>
|
|
58
|
+
),
|
|
59
|
+
ul: (props: React.HTMLAttributes<HTMLUListElement>) => (
|
|
60
|
+
<ul
|
|
61
|
+
{...props}
|
|
62
|
+
className="mb-2 ml-1 list-disc space-y-1 pl-4 text-sm text-foreground marker:text-muted-foreground"
|
|
63
|
+
/>
|
|
64
|
+
),
|
|
65
|
+
ol: (props: React.HTMLAttributes<HTMLOListElement>) => (
|
|
66
|
+
<ol
|
|
67
|
+
{...props}
|
|
68
|
+
className="mb-2 ml-1 list-decimal space-y-1 pl-4 text-sm text-foreground marker:text-muted-foreground"
|
|
69
|
+
/>
|
|
70
|
+
),
|
|
71
|
+
li: (props: React.HTMLAttributes<HTMLLIElement>) => (
|
|
72
|
+
<li {...props} className="leading-relaxed" />
|
|
73
|
+
),
|
|
74
|
+
p: (props: React.HTMLAttributes<HTMLParagraphElement>) => (
|
|
75
|
+
<p {...props} className="mb-2 text-sm leading-relaxed text-foreground" />
|
|
76
|
+
),
|
|
77
|
+
a: (props: React.AnchorHTMLAttributes<HTMLAnchorElement>) => (
|
|
78
|
+
<a
|
|
79
|
+
{...props}
|
|
80
|
+
target="_blank"
|
|
81
|
+
rel="noreferrer"
|
|
82
|
+
className="font-medium underline underline-offset-2"
|
|
83
|
+
/>
|
|
84
|
+
),
|
|
85
|
+
code: (props: React.HTMLAttributes<HTMLElement>) => (
|
|
86
|
+
<code
|
|
87
|
+
{...props}
|
|
88
|
+
className="rounded bg-muted px-1 py-0.5 font-mono text-[0.85em]"
|
|
89
|
+
/>
|
|
90
|
+
),
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
function ChangelogBody({ markdown }: { markdown: string }) {
|
|
94
|
+
const ready = useMarkdownReady();
|
|
95
|
+
const ReactMarkdown = markdownModule?.default;
|
|
96
|
+
const gfm = remarkGfmFn;
|
|
97
|
+
|
|
98
|
+
if (!ready || !ReactMarkdown || !gfm) {
|
|
99
|
+
// The react-markdown chunk loads on module eval; this is typically only one
|
|
100
|
+
// frame. Show readable plain text rather than nothing in the meantime.
|
|
101
|
+
return (
|
|
102
|
+
<div className="whitespace-pre-wrap text-sm text-foreground">
|
|
103
|
+
{markdown}
|
|
104
|
+
</div>
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
return (
|
|
108
|
+
<ReactMarkdown
|
|
109
|
+
remarkPlugins={[gfm]}
|
|
110
|
+
components={changelogMarkdownComponents}
|
|
111
|
+
urlTransform={markdownUrlTransform}
|
|
112
|
+
>
|
|
113
|
+
{markdown}
|
|
114
|
+
</ReactMarkdown>
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// ─── Unseen tracking ──────────────────────────────────────────────────────────
|
|
119
|
+
|
|
120
|
+
function seenStorageKey(appKey: string): string {
|
|
121
|
+
return `an:changelog-seen:${appKey}`;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Tracks the latest release a user has already seen (per browser, via
|
|
126
|
+
* localStorage). Returns whether there's an unseen release and a `markSeen`
|
|
127
|
+
* callback to clear the indicator once the changelog is opened.
|
|
128
|
+
*/
|
|
129
|
+
export function useChangelogSeen(
|
|
130
|
+
appKey: string,
|
|
131
|
+
latestId: string | undefined,
|
|
132
|
+
): { unseen: boolean; markSeen: () => void } {
|
|
133
|
+
const [seenId, setSeenId] = useState<string | null>(null);
|
|
134
|
+
const [hydrated, setHydrated] = useState(false);
|
|
135
|
+
|
|
136
|
+
useEffect(() => {
|
|
137
|
+
try {
|
|
138
|
+
setSeenId(window.localStorage.getItem(seenStorageKey(appKey)));
|
|
139
|
+
} catch {
|
|
140
|
+
// Private mode / disabled storage — treat as "nothing seen yet".
|
|
141
|
+
}
|
|
142
|
+
setHydrated(true);
|
|
143
|
+
}, [appKey]);
|
|
144
|
+
|
|
145
|
+
const markSeen = React.useCallback(() => {
|
|
146
|
+
if (!latestId) return;
|
|
147
|
+
setSeenId(latestId);
|
|
148
|
+
try {
|
|
149
|
+
window.localStorage.setItem(seenStorageKey(appKey), latestId);
|
|
150
|
+
} catch {
|
|
151
|
+
// Ignore storage failures; the dot just won't persist.
|
|
152
|
+
}
|
|
153
|
+
}, [appKey, latestId]);
|
|
154
|
+
|
|
155
|
+
// Don't flag "unseen" until hydrated, and never on a first-ever visit (no
|
|
156
|
+
// stored value) — only once the user has seen *something* and a newer
|
|
157
|
+
// release appears. This avoids nagging brand-new users.
|
|
158
|
+
const unseen =
|
|
159
|
+
hydrated && !!latestId && seenId !== null && seenId !== latestId;
|
|
160
|
+
|
|
161
|
+
return { unseen, markSeen };
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// ─── Shared markup ────────────────────────────────────────────────────────────
|
|
165
|
+
|
|
166
|
+
function ChangelogEntries({
|
|
167
|
+
entries,
|
|
168
|
+
emptyText,
|
|
169
|
+
}: {
|
|
170
|
+
entries: ChangelogEntry[];
|
|
171
|
+
emptyText: string;
|
|
172
|
+
}) {
|
|
173
|
+
if (entries.length === 0) {
|
|
174
|
+
return <p className="text-sm text-muted-foreground">{emptyText}</p>;
|
|
175
|
+
}
|
|
176
|
+
return (
|
|
177
|
+
<div className="space-y-6">
|
|
178
|
+
{entries.map((entry) => (
|
|
179
|
+
<section key={entry.id}>
|
|
180
|
+
<h4 className="mb-2 text-sm font-semibold text-foreground">
|
|
181
|
+
{formatEntryHeading(entry)}
|
|
182
|
+
</h4>
|
|
183
|
+
<ChangelogBody markdown={entry.body} />
|
|
184
|
+
</section>
|
|
185
|
+
))}
|
|
186
|
+
</div>
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// ─── Dialog ───────────────────────────────────────────────────────────────────
|
|
191
|
+
|
|
192
|
+
export interface ChangelogDialogProps {
|
|
193
|
+
open: boolean;
|
|
194
|
+
onOpenChange: (open: boolean) => void;
|
|
195
|
+
/** Raw CHANGELOG.md contents (e.g. `import md from "../CHANGELOG.md?raw"`). */
|
|
196
|
+
markdown: string;
|
|
197
|
+
/** Dialog heading. Default: "What's new". */
|
|
198
|
+
title?: string;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
export function ChangelogDialog({
|
|
202
|
+
open,
|
|
203
|
+
onOpenChange,
|
|
204
|
+
markdown,
|
|
205
|
+
title = "What's new",
|
|
206
|
+
}: ChangelogDialogProps) {
|
|
207
|
+
const entries = useMemo(() => parseChangelog(markdown), [markdown]);
|
|
208
|
+
|
|
209
|
+
useEffect(() => {
|
|
210
|
+
if (!open) return;
|
|
211
|
+
const onKey = (e: KeyboardEvent) => {
|
|
212
|
+
if (e.key === "Escape") {
|
|
213
|
+
e.preventDefault();
|
|
214
|
+
onOpenChange(false);
|
|
215
|
+
}
|
|
216
|
+
};
|
|
217
|
+
document.addEventListener("keydown", onKey);
|
|
218
|
+
return () => document.removeEventListener("keydown", onKey);
|
|
219
|
+
}, [open, onOpenChange]);
|
|
220
|
+
|
|
221
|
+
if (!open) return null;
|
|
222
|
+
|
|
223
|
+
return (
|
|
224
|
+
<div
|
|
225
|
+
className="fixed inset-0 z-50 flex items-start justify-center bg-black/50 p-4"
|
|
226
|
+
onMouseDown={(e) => {
|
|
227
|
+
if (e.target === e.currentTarget) onOpenChange(false);
|
|
228
|
+
}}
|
|
229
|
+
>
|
|
230
|
+
<div
|
|
231
|
+
role="dialog"
|
|
232
|
+
aria-modal="true"
|
|
233
|
+
aria-label={title}
|
|
234
|
+
className="mt-[8vh] flex max-h-[80vh] w-full max-w-xl flex-col rounded-lg border border-border bg-popover text-popover-foreground shadow-lg"
|
|
235
|
+
>
|
|
236
|
+
<div className="flex items-center justify-between border-b border-border px-5 py-3.5">
|
|
237
|
+
<div className="flex items-center gap-2">
|
|
238
|
+
<IconHistory className="h-4 w-4 text-muted-foreground" />
|
|
239
|
+
<h3 className="text-sm font-semibold">{title}</h3>
|
|
240
|
+
</div>
|
|
241
|
+
<button
|
|
242
|
+
type="button"
|
|
243
|
+
onClick={() => onOpenChange(false)}
|
|
244
|
+
aria-label="Close"
|
|
245
|
+
className="rounded-sm p-1 text-muted-foreground transition-colors hover:bg-accent hover:text-accent-foreground"
|
|
246
|
+
>
|
|
247
|
+
<IconX className="h-4 w-4" />
|
|
248
|
+
</button>
|
|
249
|
+
</div>
|
|
250
|
+
<div className="overflow-y-auto px-5 py-4">
|
|
251
|
+
<ChangelogEntries
|
|
252
|
+
entries={entries}
|
|
253
|
+
emptyText="No updates have been published yet."
|
|
254
|
+
/>
|
|
255
|
+
</div>
|
|
256
|
+
</div>
|
|
257
|
+
</div>
|
|
258
|
+
);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// ─── Settings card ────────────────────────────────────────────────────────────
|
|
262
|
+
|
|
263
|
+
export interface ChangelogSettingsCardProps {
|
|
264
|
+
/** Raw CHANGELOG.md contents (e.g. `import md from "../CHANGELOG.md?raw"`). */
|
|
265
|
+
markdown: string;
|
|
266
|
+
/** How many recent releases to show inline before "View all". Default: 2. */
|
|
267
|
+
limit?: number;
|
|
268
|
+
/** Card heading. Default: "What's new". */
|
|
269
|
+
title?: string;
|
|
270
|
+
className?: string;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
export function ChangelogSettingsCard({
|
|
274
|
+
markdown,
|
|
275
|
+
limit = 2,
|
|
276
|
+
title = "What's new",
|
|
277
|
+
className,
|
|
278
|
+
}: ChangelogSettingsCardProps) {
|
|
279
|
+
const entries = useMemo(() => parseChangelog(markdown), [markdown]);
|
|
280
|
+
const [dialogOpen, setDialogOpen] = useState(false);
|
|
281
|
+
|
|
282
|
+
if (entries.length === 0) return null;
|
|
283
|
+
|
|
284
|
+
const shown = entries.slice(0, limit);
|
|
285
|
+
const hasMore = entries.length > shown.length;
|
|
286
|
+
|
|
287
|
+
return (
|
|
288
|
+
<div
|
|
289
|
+
className={cn(
|
|
290
|
+
"rounded-lg border border-border bg-card text-card-foreground",
|
|
291
|
+
className,
|
|
292
|
+
)}
|
|
293
|
+
>
|
|
294
|
+
<div className="flex items-center gap-2 border-b border-border px-5 py-4">
|
|
295
|
+
<IconHistory className="h-4 w-4 text-muted-foreground" />
|
|
296
|
+
<h3 className="text-sm font-semibold">{title}</h3>
|
|
297
|
+
</div>
|
|
298
|
+
<div className="px-5 py-4">
|
|
299
|
+
<ChangelogEntries entries={shown} emptyText="No updates yet." />
|
|
300
|
+
{hasMore && (
|
|
301
|
+
<button
|
|
302
|
+
type="button"
|
|
303
|
+
onClick={() => setDialogOpen(true)}
|
|
304
|
+
className="mt-4 text-sm font-medium text-foreground underline underline-offset-2"
|
|
305
|
+
>
|
|
306
|
+
View all updates
|
|
307
|
+
</button>
|
|
308
|
+
)}
|
|
309
|
+
</div>
|
|
310
|
+
<ChangelogDialog
|
|
311
|
+
open={dialogOpen}
|
|
312
|
+
onOpenChange={setDialogOpen}
|
|
313
|
+
markdown={markdown}
|
|
314
|
+
title={title}
|
|
315
|
+
/>
|
|
316
|
+
</div>
|
|
317
|
+
);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
export { parseChangelog };
|
|
321
|
+
export type { ChangelogEntry };
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The runtime shell the web UI is rendering inside. Used as pass-through
|
|
3
|
+
* feedback metadata so form owners can tell whether a submission came from the
|
|
4
|
+
* Agent Native desktop app (Electron), a Tauri desktop shell (e.g. Clips), or a
|
|
5
|
+
* plain browser — without surfacing it as a visible form field.
|
|
6
|
+
*/
|
|
7
|
+
export type ClientSurface = "web" | "electron" | "tauri";
|
|
8
|
+
|
|
9
|
+
interface SurfaceGlobals {
|
|
10
|
+
// Tauri v2 always injects this into the webview; `__TAURI__` only exists when
|
|
11
|
+
// the app opts into `withGlobalTauri`, so check both.
|
|
12
|
+
__TAURI_INTERNALS__?: unknown;
|
|
13
|
+
__TAURI__?: unknown;
|
|
14
|
+
// Exposed by the Agent Native desktop (Electron) preload bridges.
|
|
15
|
+
agentNativeDesktop?: unknown;
|
|
16
|
+
electronAPI?: unknown;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Best-effort, side-effect-free detection of the current client surface.
|
|
21
|
+
* Returns "web" during SSR and in any plain browser.
|
|
22
|
+
*
|
|
23
|
+
* Electron detection keys off the `AgentNativeDesktop` User-Agent token that the
|
|
24
|
+
* desktop shell appends app-wide (see `app.userAgentFallback`), plus the preload
|
|
25
|
+
* globals, so it stays specific to our app rather than every Electron webview.
|
|
26
|
+
*/
|
|
27
|
+
export function getClientSurface(): ClientSurface {
|
|
28
|
+
if (typeof window === "undefined") return "web";
|
|
29
|
+
const w = window as unknown as SurfaceGlobals;
|
|
30
|
+
if (w.__TAURI_INTERNALS__ || w.__TAURI__) return "tauri";
|
|
31
|
+
const ua = typeof navigator !== "undefined" ? navigator.userAgent || "" : "";
|
|
32
|
+
if (
|
|
33
|
+
/AgentNativeDesktop/i.test(ua) ||
|
|
34
|
+
/\bElectron\b/i.test(ua) ||
|
|
35
|
+
w.agentNativeDesktop ||
|
|
36
|
+
w.electronAPI
|
|
37
|
+
) {
|
|
38
|
+
return "electron";
|
|
39
|
+
}
|
|
40
|
+
return "web";
|
|
41
|
+
}
|