@agent-native/core 0.91.1 → 0.92.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/blueprints/action/crud.md +1 -1
- package/blueprints/channel/discord.md +1 -1
- package/blueprints/provider/stripe.md +1 -1
- package/blueprints/sandbox/docker.md +1 -1
- package/corpus/README.md +2 -2
- package/corpus/core/CHANGELOG.md +31 -0
- package/corpus/core/blueprints/action/crud.md +1 -1
- package/corpus/core/blueprints/channel/discord.md +1 -1
- package/corpus/core/blueprints/provider/stripe.md +1 -1
- package/corpus/core/blueprints/sandbox/docker.md +1 -1
- package/corpus/core/docs/content/faq.mdx +17 -0
- package/corpus/core/docs/content/notifications.mdx +12 -6
- package/corpus/core/package.json +6 -5
- package/corpus/core/src/agent/production-agent.ts +140 -46
- package/corpus/core/src/agent/run-manager.ts +23 -2
- package/corpus/core/src/agent/run-store.ts +2 -2
- package/corpus/core/src/agent/tool-search.ts +71 -4
- package/corpus/core/src/cli/add.ts +1 -1
- package/corpus/core/src/cli/create.ts +8 -3
- package/corpus/core/src/cli/index.ts +25 -4
- package/corpus/core/src/cli/upgrade.ts +798 -0
- package/corpus/core/src/client/AssistantChat.tsx +1 -1
- package/corpus/core/src/client/HighlightedCodeBlock.tsx +20 -3
- package/corpus/core/src/client/agent-chat-adapter.ts +31 -6
- package/corpus/core/src/client/chat/message-components.tsx +85 -8
- package/corpus/core/src/client/chat/tool-call-display.tsx +355 -144
- package/corpus/core/src/client/code-agent-chat-adapter.ts +2 -9
- package/corpus/core/src/client/extensions/ExtensionSlot.tsx +7 -0
- package/corpus/core/src/client/settings/SettingsPanel.tsx +533 -389
- package/corpus/core/src/client/settings/SettingsSection.tsx +154 -17
- package/corpus/core/src/client/sse-event-processor.ts +29 -2
- package/corpus/core/src/extensions/actions.ts +113 -5
- package/corpus/core/src/jobs/scheduler.ts +30 -1
- package/corpus/core/src/localization/default-messages.ts +21 -0
- package/corpus/core/src/notifications/channels.ts +88 -25
- package/corpus/core/src/notifications/registry.ts +17 -3
- package/corpus/core/src/notifications/types.ts +4 -1
- package/corpus/core/src/server/agent-chat-plugin.ts +7 -3
- package/corpus/core/src/server/request-context.ts +7 -0
- package/corpus/core/src/styles/agent-conversation.css +1 -1
- package/corpus/core/src/templates/default/.agents/skills/self-modifying-code/SKILL.md +20 -2
- package/corpus/core/src/templates/default/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
- package/corpus/core/src/templates/default/AGENTS.md +10 -3
- package/corpus/core/src/templates/default/app/i18n/ar-SA.ts +21 -0
- package/corpus/core/src/templates/default/app/i18n/de-DE.ts +21 -0
- package/corpus/core/src/templates/default/app/i18n/en-US.ts +21 -0
- package/corpus/core/src/templates/default/app/i18n/es-ES.ts +21 -0
- package/corpus/core/src/templates/default/app/i18n/fr-FR.ts +21 -0
- package/corpus/core/src/templates/default/app/i18n/hi-IN.ts +20 -0
- package/corpus/core/src/templates/default/app/i18n/ja-JP.ts +21 -0
- package/corpus/core/src/templates/default/app/i18n/ko-KR.ts +20 -0
- package/corpus/core/src/templates/default/app/i18n/pt-BR.ts +21 -0
- package/corpus/core/src/templates/default/app/i18n/zh-CN.ts +20 -0
- package/corpus/core/src/templates/default/app/i18n/zh-TW.ts +19 -0
- package/corpus/core/src/templates/default/package.json +4 -3
- package/corpus/core/src/templates/headless/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
- package/corpus/core/src/templates/headless/AGENTS.md +9 -3
- package/corpus/core/src/templates/headless/package.json +4 -3
- package/corpus/core/src/templates/workspace-core/.agents/skills/self-modifying-code/SKILL.md +20 -2
- package/corpus/core/src/templates/workspace-core/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
- package/corpus/core/src/templates/workspace-core/package.json +6 -6
- package/corpus/core/src/templates/workspace-root/AGENTS.md +10 -4
- package/corpus/core/src/templates/workspace-root/package.json +3 -2
- package/corpus/core/src/triggers/dispatcher.ts +30 -2
- package/corpus/core/src/vite/client.ts +96 -1
- package/corpus/templates/analytics/.agents/skills/self-modifying-code/SKILL.md +20 -2
- package/corpus/templates/analytics/.agents/skills/session-replay/SKILL.md +20 -2
- package/corpus/templates/analytics/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
- package/corpus/templates/analytics/AGENTS.md +7 -5
- package/corpus/templates/analytics/actions/save-monitor.ts +14 -0
- package/corpus/templates/analytics/app/components/dashboard/DataTable.tsx +41 -6
- package/corpus/templates/analytics/app/components/dashboard/SqlChart.tsx +122 -11
- package/corpus/templates/analytics/app/global.css +26 -10
- package/corpus/templates/analytics/app/i18n/zh-TW.ts +2 -0
- package/corpus/templates/analytics/app/i18n-data.ts +25 -0
- package/corpus/templates/analytics/app/pages/monitoring/MonitoringPage.tsx +8 -2
- package/corpus/templates/analytics/app/pages/monitoring/uptime/MonitorFormPage.tsx +151 -32
- package/corpus/templates/analytics/app/pages/monitoring/uptime/i18n.ts +17 -2
- package/corpus/templates/analytics/app/pages/monitoring/uptime/types.ts +4 -0
- package/corpus/templates/analytics/app/pages/sessions/SessionDetailPage.tsx +461 -286
- package/corpus/templates/analytics/app/pages/sessions/SessionDevToolsPanel.tsx +285 -294
- package/corpus/templates/analytics/changelog/2026-07-08-console-details-expand-inline-under-each-row-and-the-event-t.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-08-dashboard-tables-keep-their-size-while-loading.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-08-monitoring-uptime-and-errors-tabs-now-clearly-show-which-view-is-active.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-08-session-replay-keeps-a-usable-stage-when-dev-tools-is-open-a.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-08-session-replay-resumes-after-scrubbing-and-expands-console-r.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-08-session-replay-uses-stock-rrweb-sizing-again-so-recorded.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-08-session-replays-wait-for-full-load-keep-a-normal-viewport-an.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-08-sidebar-dashboard-menus-now-open-above-the-dashboard-canvas.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-08-uptime-alerting-clarifies-in-app-inbox-vs-email-and-lets-you-paste-slack-or-webhook-urls-per-monitor.md +6 -0
- package/corpus/templates/analytics/docs/uptime-monitoring.md +13 -11
- package/corpus/templates/analytics/package.json +2 -2
- package/corpus/templates/analytics/server/db/schema-monitoring.ts +4 -0
- package/corpus/templates/analytics/server/lib/uptime-monitors.ts +60 -12
- package/corpus/templates/analytics/server/plugins/db.ts +10 -0
- package/corpus/templates/assets/.agents/skills/self-modifying-code/SKILL.md +20 -2
- package/corpus/templates/assets/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
- package/corpus/templates/assets/package.json +2 -2
- package/corpus/templates/brain/.agents/skills/self-modifying-code/SKILL.md +20 -2
- package/corpus/templates/brain/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
- package/corpus/templates/brain/package.json +2 -2
- package/corpus/templates/calendar/.agents/skills/self-modifying-code/SKILL.md +20 -2
- package/corpus/templates/calendar/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
- package/corpus/templates/calendar/AGENTS.md +10 -0
- package/corpus/templates/calendar/actions/create-event.ts +5 -1
- package/corpus/templates/calendar/actions/event-action-helpers.ts +41 -0
- package/corpus/templates/calendar/actions/get-attendee-timezones.ts +28 -0
- package/corpus/templates/calendar/actions/set-attendee-timezone.ts +68 -0
- package/corpus/templates/calendar/app/components/calendar/EventAttendeesSection.tsx +150 -11
- package/corpus/templates/calendar/app/components/calendar/EventDetailPanel.tsx +36 -1
- package/corpus/templates/calendar/app/components/calendar/EventDetailPopover.tsx +32 -0
- package/corpus/templates/calendar/app/hooks/use-attendee-timezones.ts +24 -0
- package/corpus/templates/calendar/app/i18n/zh-TW.ts +2 -0
- package/corpus/templates/calendar/app/i18n-data.ts +20 -0
- package/corpus/templates/calendar/app/lib/attendee-local-time.ts +122 -0
- package/corpus/templates/calendar/changelog/2026-07-08-event-details-now-support-extension-widgets-and-you-can-see-.md +6 -0
- package/corpus/templates/calendar/changelog/2026-07-08-when-you-invite-guests-to-a-new-event-you-now-appear-in.md +6 -0
- package/corpus/templates/calendar/package.json +2 -2
- package/corpus/templates/calendar/server/handlers/events.ts +5 -1
- package/corpus/templates/calendar/shared/api.ts +6 -0
- package/corpus/templates/chat/.agents/skills/self-modifying-code/SKILL.md +20 -2
- package/corpus/templates/chat/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
- package/corpus/templates/chat/package.json +2 -2
- package/corpus/templates/clips/.agents/skills/meetings/SKILL.md +6 -2
- package/corpus/templates/clips/.agents/skills/self-modifying-code/SKILL.md +20 -2
- package/corpus/templates/clips/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
- package/corpus/templates/clips/actions/create-meeting.ts +8 -3
- package/corpus/templates/clips/actions/list-meetings.ts +31 -3
- package/corpus/templates/clips/changelog/2026-07-08-after-finishing-a-desktop-recording-reopening-clips-restor.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-08-clips-now-pops-up-to-take-notes-when-you-join-an-adhoc-zoom-.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-08-desktop-clip-recordings-are-louder-after-capture-mic-only.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-08-desktop-meeting-notes-stay-active-from-tray-and-shortcut-controls.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-08-desktop-meeting-popovers-can-start-notes-and-join-the-meetin.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-08-meeting-notes-keep-their-recording-state-when-the-desktop.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-08-meeting-reminder-dropdowns-now-expand-fully-instead-of-being.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-08-meeting-reminders-now-appear-1-minute-before-start-stay-unti.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-08-the-desktop-meetings-panel-has-room-for-its-drop-shadow-and-.md +6 -0
- package/corpus/templates/clips/chrome-extension/package.json +2 -2
- package/corpus/templates/clips/desktop/package.json +2 -2
- package/corpus/templates/clips/desktop/src/app.tsx +214 -37
- package/corpus/templates/clips/desktop/src/hooks/useMeetingTranscription.ts +30 -6
- package/corpus/templates/clips/desktop/src/lib/meeting-notification-timing.ts +83 -0
- package/corpus/templates/clips/desktop/src/lib/native-recording-warm.ts +21 -0
- package/corpus/templates/clips/desktop/src/lib/recorder.ts +98 -20
- package/corpus/templates/clips/desktop/src/overlays/meeting-notification.tsx +197 -50
- package/corpus/templates/clips/desktop/src/styles.css +150 -53
- package/corpus/templates/clips/desktop/src-tauri/src/adhoc_meetings_watcher.rs +362 -0
- package/corpus/templates/clips/desktop/src-tauri/src/clips/mod.rs +41 -7
- package/corpus/templates/clips/desktop/src-tauri/src/lib.rs +9 -2
- package/corpus/templates/clips/desktop/src-tauri/src/meetings_watcher.rs +89 -11
- package/corpus/templates/clips/desktop/src-tauri/src/native_screen.rs +71 -17
- package/corpus/templates/clips/desktop/src-tauri/src/native_speech.rs +36 -1
- package/corpus/templates/clips/desktop/src-tauri/src/notifications.rs +62 -10
- package/corpus/templates/clips/desktop/src-tauri/src/shortcuts.rs +3 -3
- package/corpus/templates/clips/desktop/src-tauri/src/silence_detector.rs +1 -25
- package/corpus/templates/clips/desktop/src-tauri/src/state.rs +5 -0
- package/corpus/templates/clips/desktop/src-tauri/src/tray.rs +18 -6
- package/corpus/templates/clips/desktop/src-tauri/src/util.rs +36 -1
- package/corpus/templates/clips/package.json +2 -2
- package/corpus/templates/content/.agents/skills/self-modifying-code/SKILL.md +20 -2
- package/corpus/templates/content/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
- package/corpus/templates/content/actions/_batch-utils.ts +8 -0
- package/corpus/templates/content/actions/_builder-cms-read-client.ts +7 -5
- package/corpus/templates/content/actions/_content-database-personal-view.ts +80 -0
- package/corpus/templates/content/actions/_database-source-utils.ts +776 -319
- package/corpus/templates/content/actions/_database-utils.ts +6 -1
- package/corpus/templates/content/actions/_property-utils.ts +168 -0
- package/corpus/templates/content/actions/add-content-database-source-field-property.ts +19 -12
- package/corpus/templates/content/actions/bind-content-database-source-field.ts +19 -12
- package/corpus/templates/content/actions/delete-document.ts +9 -13
- package/corpus/templates/content/actions/get-content-database-personal-view.ts +27 -0
- package/corpus/templates/content/actions/refresh-content-database-source.ts +5 -5
- package/corpus/templates/content/actions/update-content-database-personal-view.ts +31 -0
- package/corpus/templates/content/actions/update-content-database-view.ts +3 -0
- package/corpus/templates/content/app/components/editor/DocumentProperties.tsx +1 -1
- package/corpus/templates/content/app/components/editor/body-hydration.ts +35 -11
- package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +2631 -551
- package/corpus/templates/content/app/components/editor/database/filter-sort.ts +220 -18
- package/corpus/templates/content/app/hooks/use-content-database.ts +32 -0
- package/corpus/templates/content/app/hooks/use-documents.ts +8 -2
- package/corpus/templates/content/app/i18n/zh-TW.ts +16 -0
- package/corpus/templates/content/app/i18n-data.ts +154 -0
- package/corpus/templates/content/changelog/2026-07-08-builder-source-sync-now-completes-reliably-on-hosted-databases.md +6 -0
- package/corpus/templates/content/changelog/2026-07-08-database-filters-now-support-notion-style-reset-save-for-eve.md +6 -0
- package/corpus/templates/content/changelog/2026-07-08-date-filters-can-now-target-a-between-range.md +6 -0
- package/corpus/templates/content/changelog/2026-07-08-fixed-builder-database-sync-so-large-sources-continue-past-t.md +6 -0
- package/corpus/templates/content/package.json +2 -2
- package/corpus/templates/content/parity/matrix.md +1 -1
- package/corpus/templates/content/parity/matrix.ts +2 -0
- package/corpus/templates/content/server/plugins/db.ts +19 -0
- package/corpus/templates/content/shared/api.ts +24 -0
- package/corpus/templates/design/.agents/skills/self-modifying-code/SKILL.md +20 -2
- package/corpus/templates/design/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
- package/corpus/templates/design/package.json +2 -2
- package/corpus/templates/dispatch/.agents/skills/self-modifying-code/SKILL.md +20 -2
- package/corpus/templates/dispatch/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
- package/corpus/templates/dispatch/app/i18n/zh-TW.ts +34 -1
- package/corpus/templates/dispatch/app/i18n-data.ts +387 -10
- package/corpus/templates/dispatch/app/routes/automations.tsx +1 -0
- package/corpus/templates/dispatch/changelog/2026-07-08-added-an-automations-page-to-review-pause-and-create.md +6 -0
- package/corpus/templates/dispatch/changelog/2026-07-08-destinations-now-shows-outbound-delivery-queue-health.md +6 -0
- package/corpus/templates/dispatch/package.json +2 -2
- package/corpus/templates/forms/.agents/skills/self-modifying-code/SKILL.md +20 -2
- package/corpus/templates/forms/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
- package/corpus/templates/forms/package.json +2 -2
- package/corpus/templates/macros/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
- package/corpus/templates/macros/package.json +2 -2
- package/corpus/templates/mail/.agents/skills/self-modifying-code/SKILL.md +20 -2
- package/corpus/templates/mail/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
- package/corpus/templates/mail/app/hooks/use-emails.ts +56 -44
- package/corpus/templates/mail/app/lib/gmail-mutation-queue.ts +325 -0
- package/corpus/templates/mail/changelog/2026-07-08-rapid-archive-and-mark-read-now-batch-gmail-updates-so-the-i.md +6 -0
- package/corpus/templates/mail/package.json +2 -2
- package/corpus/templates/plan/.agents/skills/self-modifying-code/SKILL.md +20 -2
- package/corpus/templates/plan/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
- package/corpus/templates/plan/package.json +2 -2
- package/corpus/templates/slides/.agents/skills/self-modifying-code/SKILL.md +20 -2
- package/corpus/templates/slides/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
- package/corpus/templates/slides/app/components/editor/ImageDropPromptPopover.tsx +46 -29
- package/corpus/templates/slides/app/context/DeckContext.tsx +45 -18
- package/corpus/templates/slides/app/i18n/ar-SA.ts +4 -0
- package/corpus/templates/slides/app/i18n/de-DE.ts +4 -0
- package/corpus/templates/slides/app/i18n/en-US.ts +4 -0
- package/corpus/templates/slides/app/i18n/es-ES.ts +4 -0
- package/corpus/templates/slides/app/i18n/fr-FR.ts +4 -0
- package/corpus/templates/slides/app/i18n/hi-IN.ts +4 -0
- package/corpus/templates/slides/app/i18n/ja-JP.ts +4 -0
- package/corpus/templates/slides/app/i18n/ko-KR.ts +4 -0
- package/corpus/templates/slides/app/i18n/pt-BR.ts +4 -0
- package/corpus/templates/slides/app/i18n/zh-CN.ts +4 -0
- package/corpus/templates/slides/app/i18n/zh-TW.ts +4 -0
- package/corpus/templates/slides/app/lib/image-drop-to-agent.ts +115 -0
- package/corpus/templates/slides/app/pages/DeckEditor.tsx +22 -13
- package/corpus/templates/slides/changelog/2026-07-08-dropping-an-image-onto-a-slide-and-sending-it-to-the-agent-w.md +6 -0
- package/corpus/templates/slides/changelog/2026-07-08-undo-works-after-the-agent-edits-a-slide-so-you-can-reverse.md +6 -0
- package/corpus/templates/slides/package.json +2 -2
- package/corpus/templates/videos/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
- package/corpus/templates/voice/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
- package/dist/agent/production-agent.d.ts +5 -1
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +112 -42
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/agent/run-manager.d.ts.map +1 -1
- package/dist/agent/run-manager.js +23 -2
- package/dist/agent/run-manager.js.map +1 -1
- package/dist/agent/run-store.js +2 -2
- package/dist/agent/run-store.js.map +1 -1
- package/dist/agent/tool-search.d.ts +2 -0
- package/dist/agent/tool-search.d.ts.map +1 -1
- package/dist/agent/tool-search.js +60 -4
- package/dist/agent/tool-search.js.map +1 -1
- package/dist/catalog.json +2 -2
- package/dist/cli/add.js +1 -1
- package/dist/cli/add.js.map +1 -1
- package/dist/cli/create.d.ts.map +1 -1
- package/dist/cli/create.js +8 -3
- package/dist/cli/create.js.map +1 -1
- package/dist/cli/index.js +24 -4
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/upgrade.d.ts +100 -0
- package/dist/cli/upgrade.d.ts.map +1 -0
- package/dist/cli/upgrade.js +678 -0
- package/dist/cli/upgrade.js.map +1 -0
- package/dist/client/AssistantChat.js +1 -1
- package/dist/client/AssistantChat.js.map +1 -1
- package/dist/client/HighlightedCodeBlock.d.ts +2 -0
- package/dist/client/HighlightedCodeBlock.d.ts.map +1 -1
- package/dist/client/HighlightedCodeBlock.js +10 -1
- package/dist/client/HighlightedCodeBlock.js.map +1 -1
- package/dist/client/agent-chat-adapter.d.ts.map +1 -1
- package/dist/client/agent-chat-adapter.js +32 -4
- package/dist/client/agent-chat-adapter.js.map +1 -1
- package/dist/client/chat/message-components.d.ts.map +1 -1
- package/dist/client/chat/message-components.js +61 -8
- package/dist/client/chat/message-components.js.map +1 -1
- package/dist/client/chat/tool-call-display.d.ts +12 -0
- package/dist/client/chat/tool-call-display.d.ts.map +1 -1
- package/dist/client/chat/tool-call-display.js +136 -42
- package/dist/client/chat/tool-call-display.js.map +1 -1
- package/dist/client/code-agent-chat-adapter.js +2 -9
- package/dist/client/code-agent-chat-adapter.js.map +1 -1
- package/dist/client/extensions/ExtensionSlot.js +6 -0
- package/dist/client/extensions/ExtensionSlot.js.map +1 -1
- package/dist/client/settings/SettingsPanel.d.ts.map +1 -1
- package/dist/client/settings/SettingsPanel.js +52 -19
- package/dist/client/settings/SettingsPanel.js.map +1 -1
- package/dist/client/settings/SettingsSection.d.ts +19 -4
- package/dist/client/settings/SettingsSection.d.ts.map +1 -1
- package/dist/client/settings/SettingsSection.js +33 -4
- package/dist/client/settings/SettingsSection.js.map +1 -1
- package/dist/client/sse-event-processor.d.ts +9 -0
- package/dist/client/sse-event-processor.d.ts.map +1 -1
- package/dist/client/sse-event-processor.js +20 -2
- package/dist/client/sse-event-processor.js.map +1 -1
- package/dist/collab/routes.d.ts +1 -1
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/extensions/actions.d.ts.map +1 -1
- package/dist/extensions/actions.js +85 -5
- package/dist/extensions/actions.js.map +1 -1
- package/dist/file-upload/actions/upload-image.d.ts +1 -1
- package/dist/jobs/scheduler.js +26 -1
- package/dist/jobs/scheduler.js.map +1 -1
- package/dist/localization/default-messages.d.ts +19 -0
- package/dist/localization/default-messages.d.ts.map +1 -1
- package/dist/localization/default-messages.js +19 -0
- package/dist/localization/default-messages.js.map +1 -1
- package/dist/notifications/channels.d.ts +8 -3
- package/dist/notifications/channels.d.ts.map +1 -1
- package/dist/notifications/channels.js +61 -22
- package/dist/notifications/channels.js.map +1 -1
- package/dist/notifications/registry.d.ts.map +1 -1
- package/dist/notifications/registry.js +14 -3
- package/dist/notifications/registry.js.map +1 -1
- package/dist/notifications/routes.d.ts +1 -1
- package/dist/notifications/types.d.ts +4 -1
- package/dist/notifications/types.d.ts.map +1 -1
- package/dist/notifications/types.js.map +1 -1
- package/dist/observability/routes.d.ts +1 -1
- package/dist/provider-api/corpus-jobs.d.ts +2 -2
- package/dist/resources/handlers.d.ts +3 -3
- package/dist/server/agent-chat-plugin.d.ts +5 -1
- package/dist/server/agent-chat-plugin.d.ts.map +1 -1
- package/dist/server/agent-chat-plugin.js +2 -2
- package/dist/server/agent-chat-plugin.js.map +1 -1
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/request-context.d.ts +7 -0
- package/dist/server/request-context.d.ts.map +1 -1
- package/dist/server/request-context.js.map +1 -1
- package/dist/styles/agent-conversation.css +1 -1
- package/dist/templates/default/.agents/skills/self-modifying-code/SKILL.md +20 -2
- package/dist/templates/default/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
- package/dist/templates/default/AGENTS.md +10 -3
- package/dist/templates/default/app/i18n/ar-SA.ts +21 -0
- package/dist/templates/default/app/i18n/de-DE.ts +21 -0
- package/dist/templates/default/app/i18n/en-US.ts +21 -0
- package/dist/templates/default/app/i18n/es-ES.ts +21 -0
- package/dist/templates/default/app/i18n/fr-FR.ts +21 -0
- package/dist/templates/default/app/i18n/hi-IN.ts +20 -0
- package/dist/templates/default/app/i18n/ja-JP.ts +21 -0
- package/dist/templates/default/app/i18n/ko-KR.ts +20 -0
- package/dist/templates/default/app/i18n/pt-BR.ts +21 -0
- package/dist/templates/default/app/i18n/zh-CN.ts +20 -0
- package/dist/templates/default/app/i18n/zh-TW.ts +19 -0
- package/dist/templates/default/package.json +4 -3
- package/dist/templates/headless/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
- package/dist/templates/headless/AGENTS.md +9 -3
- package/dist/templates/headless/package.json +4 -3
- package/dist/templates/workspace-core/.agents/skills/self-modifying-code/SKILL.md +20 -2
- package/dist/templates/workspace-core/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
- package/dist/templates/workspace-core/package.json +6 -6
- package/dist/templates/workspace-root/AGENTS.md +10 -4
- package/dist/templates/workspace-root/package.json +3 -2
- package/dist/triggers/dispatcher.js +27 -2
- package/dist/triggers/dispatcher.js.map +1 -1
- package/dist/vite/client.d.ts.map +1 -1
- package/dist/vite/client.js +70 -1
- package/dist/vite/client.js.map +1 -1
- package/docs/content/faq.mdx +17 -0
- package/docs/content/notifications.mdx +12 -6
- package/package.json +7 -6
- package/src/templates/default/.agents/skills/self-modifying-code/SKILL.md +20 -2
- package/src/templates/default/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
- package/src/templates/default/AGENTS.md +10 -3
- package/src/templates/default/app/i18n/ar-SA.ts +21 -0
- package/src/templates/default/app/i18n/de-DE.ts +21 -0
- package/src/templates/default/app/i18n/en-US.ts +21 -0
- package/src/templates/default/app/i18n/es-ES.ts +21 -0
- package/src/templates/default/app/i18n/fr-FR.ts +21 -0
- package/src/templates/default/app/i18n/hi-IN.ts +20 -0
- package/src/templates/default/app/i18n/ja-JP.ts +21 -0
- package/src/templates/default/app/i18n/ko-KR.ts +20 -0
- package/src/templates/default/app/i18n/pt-BR.ts +21 -0
- package/src/templates/default/app/i18n/zh-CN.ts +20 -0
- package/src/templates/default/app/i18n/zh-TW.ts +19 -0
- package/src/templates/default/package.json +4 -3
- package/src/templates/headless/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
- package/src/templates/headless/AGENTS.md +9 -3
- package/src/templates/headless/package.json +4 -3
- package/src/templates/workspace-core/.agents/skills/self-modifying-code/SKILL.md +20 -2
- package/src/templates/workspace-core/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
- package/src/templates/workspace-core/package.json +6 -6
- package/src/templates/workspace-root/AGENTS.md +10 -4
- package/src/templates/workspace-root/package.json +3 -2
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
IconPlayerSkipForward,
|
|
24
24
|
IconPlayerTrackNext,
|
|
25
25
|
IconRoute,
|
|
26
|
+
IconSearch,
|
|
26
27
|
IconTerminal2,
|
|
27
28
|
IconTimelineEvent,
|
|
28
29
|
} from "@tabler/icons-react";
|
|
@@ -34,11 +35,13 @@ import {
|
|
|
34
35
|
useMemo,
|
|
35
36
|
useRef,
|
|
36
37
|
useState,
|
|
38
|
+
type FormEvent,
|
|
37
39
|
} from "react";
|
|
38
40
|
import { Link, useParams } from "react-router";
|
|
39
41
|
|
|
40
42
|
import { Button } from "@/components/ui/button";
|
|
41
43
|
import { Card, CardContent } from "@/components/ui/card";
|
|
44
|
+
import { Input } from "@/components/ui/input";
|
|
42
45
|
import {
|
|
43
46
|
Popover,
|
|
44
47
|
PopoverContent,
|
|
@@ -153,6 +156,8 @@ type ReplayViewportDimensions = {
|
|
|
153
156
|
|
|
154
157
|
const DEFAULT_PLAYER_WIDTH = 1024;
|
|
155
158
|
const DEFAULT_PLAYER_HEIGHT = 640;
|
|
159
|
+
const MIN_REPLAY_DISPLAY_ASPECT_RATIO = 0.45;
|
|
160
|
+
const MAX_REPLAY_DISPLAY_ASPECT_RATIO = 3;
|
|
156
161
|
const DEFAULT_SPEED = 2;
|
|
157
162
|
const SPEED_OPTIONS = [0.5, 1, 2, 4, 8];
|
|
158
163
|
const SKIP_STEP_MS = 5000;
|
|
@@ -160,8 +165,15 @@ const MIN_IDLE_SKIP_MS = 8000;
|
|
|
160
165
|
const IDLE_EDGE_PAD_MS = 1200;
|
|
161
166
|
const REPLAY_CHUNK_FETCH_CONCURRENCY = 6;
|
|
162
167
|
const REPLAY_CHUNK_UNAVAILABLE_MESSAGE = "Session replay chunk is unavailable";
|
|
163
|
-
const DEFAULT_DEVTOOLS_HEIGHT =
|
|
168
|
+
const DEFAULT_DEVTOOLS_HEIGHT = 220;
|
|
169
|
+
const MIN_STAGE_HEIGHT_PX = 240;
|
|
164
170
|
const SCRUBBER_MARKER_LIMIT = 500;
|
|
171
|
+
const TIMELINE_MARKER_LIMIT = 300;
|
|
172
|
+
const TIMELINE_FOLLOW_PAUSE_MS = 4000;
|
|
173
|
+
/** Toast/snackbar noise only — keep insertStyleRules minimal like builder-internal. */
|
|
174
|
+
const SUPPRESS_OVERLAYS_CSS = [
|
|
175
|
+
"[data-radix-popper-content-wrapper], .Toastify, [class*='toast'], [class*='Toast'], [class*='Snackbar'] { display: none !important; }",
|
|
176
|
+
];
|
|
165
177
|
const SESSION_REPLAY_CONSOLE_EVENT_TAG = "agent-native.console";
|
|
166
178
|
const SESSION_REPLAY_NETWORK_EVENT_TAG = "agent-native.network";
|
|
167
179
|
|
|
@@ -219,6 +231,7 @@ const INCREMENTAL_SOURCE = {
|
|
|
219
231
|
Mutation: 0,
|
|
220
232
|
MouseInteraction: 2,
|
|
221
233
|
Scroll: 3,
|
|
234
|
+
ViewportResize: 4,
|
|
222
235
|
Input: 5,
|
|
223
236
|
TouchMove: 6,
|
|
224
237
|
} as const;
|
|
@@ -471,17 +484,30 @@ function ReplayPlayer({
|
|
|
471
484
|
const [skipInactive, setSkipInactive] = useState(true);
|
|
472
485
|
const [devToolsOpen, setDevToolsOpen] = useState(false);
|
|
473
486
|
const [devToolsHeight, setDevToolsHeight] = useState(DEFAULT_DEVTOOLS_HEIGHT);
|
|
487
|
+
const [maxDevToolsHeight, setMaxDevToolsHeight] = useState(420);
|
|
488
|
+
const playerShellRef = useRef<HTMLDivElement>(null);
|
|
474
489
|
const [streamedDims, setStreamedDims] = useState<{
|
|
475
490
|
width: number;
|
|
476
491
|
height: number;
|
|
477
492
|
} | null>(null);
|
|
478
493
|
const [fitScale, setFitScale] = useState(1);
|
|
479
494
|
const initialDims = useMemo(() => replayViewportDimensions(events), [events]);
|
|
495
|
+
const eventsRef = useLiveRef(events);
|
|
496
|
+
// Stable identity for the loaded event set so progressive chunk publishes
|
|
497
|
+
// that only grow the array do not tear down a healthy Replayer mid-playback.
|
|
498
|
+
const eventsIdentity = useMemo(
|
|
499
|
+
() =>
|
|
500
|
+
`${events.length}:${Number(events[0]?.timestamp ?? 0)}:${Number(
|
|
501
|
+
events[events.length - 1]?.timestamp ?? 0,
|
|
502
|
+
)}`,
|
|
503
|
+
[events],
|
|
504
|
+
);
|
|
505
|
+
const scrubbingRef = useRef(false);
|
|
506
|
+
const scrubResumePlayingRef = useRef(false);
|
|
480
507
|
|
|
481
|
-
const
|
|
482
|
-
|
|
483
|
-
const playerHeight =
|
|
484
|
-
streamedDims?.height ?? initialDims?.height ?? DEFAULT_PLAYER_HEIGHT;
|
|
508
|
+
const displayDims = clampReplayDisplayDimensions(streamedDims ?? initialDims);
|
|
509
|
+
const playerWidth = displayDims?.width ?? DEFAULT_PLAYER_WIDTH;
|
|
510
|
+
const playerHeight = displayDims?.height ?? DEFAULT_PLAYER_HEIGHT;
|
|
485
511
|
const skipRanges = useMemo(() => buildIdleSkipRanges(events), [events]);
|
|
486
512
|
const skipRangesRef = useLiveRef(skipRanges);
|
|
487
513
|
const skipInactiveRef = useLiveRef(skipInactive);
|
|
@@ -556,6 +582,26 @@ function ReplayPlayer({
|
|
|
556
582
|
return () => observer.disconnect();
|
|
557
583
|
}, [playerHeight, playerWidth]);
|
|
558
584
|
|
|
585
|
+
// Keep Dev Tools from eating the stage. On short viewports the panel used to
|
|
586
|
+
// shrink the replay area into a ribbon even when Meta dimensions were fine.
|
|
587
|
+
useEffect(() => {
|
|
588
|
+
const el = playerShellRef.current;
|
|
589
|
+
if (!el) return;
|
|
590
|
+
const update = () => {
|
|
591
|
+
const chromeBudget = 140;
|
|
592
|
+
const available = Math.max(
|
|
593
|
+
160,
|
|
594
|
+
el.clientHeight - MIN_STAGE_HEIGHT_PX - chromeBudget,
|
|
595
|
+
);
|
|
596
|
+
setMaxDevToolsHeight(available);
|
|
597
|
+
setDevToolsHeight((current) => Math.min(current, available));
|
|
598
|
+
};
|
|
599
|
+
update();
|
|
600
|
+
const observer = new ResizeObserver(update);
|
|
601
|
+
observer.observe(el);
|
|
602
|
+
return () => observer.disconnect();
|
|
603
|
+
}, [devToolsOpen]);
|
|
604
|
+
|
|
559
605
|
const updateTime = useCallback(
|
|
560
606
|
(next: number) => {
|
|
561
607
|
setCurrentTime(next);
|
|
@@ -586,6 +632,27 @@ function ReplayPlayer({
|
|
|
586
632
|
[playingRef, status, totalTime, updateTime],
|
|
587
633
|
);
|
|
588
634
|
|
|
635
|
+
const beginScrub = useCallback(
|
|
636
|
+
(ms: number) => {
|
|
637
|
+
if (!scrubbingRef.current) {
|
|
638
|
+
scrubResumePlayingRef.current = playingRef.current;
|
|
639
|
+
}
|
|
640
|
+
scrubbingRef.current = true;
|
|
641
|
+
seek(ms, false);
|
|
642
|
+
},
|
|
643
|
+
[playingRef, seek],
|
|
644
|
+
);
|
|
645
|
+
|
|
646
|
+
const endScrub = useCallback(
|
|
647
|
+
(ms: number) => {
|
|
648
|
+
const resume = scrubResumePlayingRef.current;
|
|
649
|
+
scrubbingRef.current = false;
|
|
650
|
+
scrubResumePlayingRef.current = false;
|
|
651
|
+
seek(ms, resume);
|
|
652
|
+
},
|
|
653
|
+
[seek],
|
|
654
|
+
);
|
|
655
|
+
|
|
589
656
|
useEffect(() => {
|
|
590
657
|
registerSeek(seek);
|
|
591
658
|
}, [registerSeek, seek]);
|
|
@@ -596,21 +663,29 @@ function ReplayPlayer({
|
|
|
596
663
|
let localReplayer: any = null;
|
|
597
664
|
|
|
598
665
|
async function loadReplay() {
|
|
599
|
-
|
|
600
|
-
|
|
666
|
+
// Wait for the full recording before creating the Replayer. Progressive
|
|
667
|
+
// chunk publishes used to rebuild the player on every append, which
|
|
668
|
+
// reset the clock, disabled the scrubber, and desynced the playhead.
|
|
669
|
+
const replayEvents = eventsRef.current;
|
|
670
|
+
if (!response.isComplete) {
|
|
601
671
|
setStatus("loading");
|
|
602
672
|
setError(null);
|
|
603
673
|
setPlaying(false);
|
|
604
674
|
return;
|
|
605
675
|
}
|
|
606
|
-
if (
|
|
676
|
+
if (replayEvents.length < 2) {
|
|
607
677
|
throw new Error(t("sessions.noReplayEvents"));
|
|
608
678
|
}
|
|
609
679
|
if (
|
|
610
|
-
!
|
|
680
|
+
!replayEvents.some(
|
|
681
|
+
(event) => event.type === RRWEB_EVENT_TYPE.FullSnapshot,
|
|
682
|
+
)
|
|
611
683
|
) {
|
|
612
684
|
throw new Error(t("sessions.noReplayEvents"));
|
|
613
685
|
}
|
|
686
|
+
if (!hasPlayableReplayEvents(replayEvents)) {
|
|
687
|
+
throw new Error(t("sessions.noReplayEvents"));
|
|
688
|
+
}
|
|
614
689
|
setStatus("loading");
|
|
615
690
|
setError(null);
|
|
616
691
|
await import("@rrweb/replay/dist/style.css");
|
|
@@ -618,36 +693,31 @@ function ReplayPlayer({
|
|
|
618
693
|
if (cancelled || !stageRootRef.current) return;
|
|
619
694
|
|
|
620
695
|
stageRootRef.current.innerHTML = "";
|
|
621
|
-
|
|
622
|
-
|
|
696
|
+
// Match builder-internal: pass events through untouched and let rrweb own
|
|
697
|
+
// iframe sizing via Meta / ViewportResize. Only use dimensions for CSS
|
|
698
|
+
// fit-to-stage scaling of the outer wrapper — never rewrite Meta or force
|
|
699
|
+
// iframe width/height (that mismatches the FullSnapshot DOM and blanks
|
|
700
|
+
// the stage).
|
|
701
|
+
setStreamedDims(replayViewportDimensions(replayEvents));
|
|
702
|
+
localReplayer = new Replayer(replayEvents as any[], {
|
|
623
703
|
root: stageRootRef.current,
|
|
624
704
|
speed: speedRef.current,
|
|
625
705
|
skipInactive: false,
|
|
626
706
|
showWarning: false,
|
|
627
707
|
showDebug: false,
|
|
628
708
|
triggerFocus: false,
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
mouseTail: {
|
|
632
|
-
strokeStyle: "rgba(59, 130, 246, 0.35)",
|
|
633
|
-
lineWidth: 2,
|
|
634
|
-
duration: 600,
|
|
635
|
-
lineCap: "round",
|
|
636
|
-
},
|
|
637
|
-
insertStyleRules: [
|
|
638
|
-
"[data-radix-popper-content-wrapper], .Toastify, [class*='toast'], [class*='Toast'] { display: none !important; }",
|
|
639
|
-
],
|
|
709
|
+
mouseTail: false,
|
|
710
|
+
insertStyleRules: SUPPRESS_OVERLAYS_CSS,
|
|
640
711
|
});
|
|
641
712
|
replayerRef.current = localReplayer;
|
|
642
713
|
const meta = localReplayer.getMetaData?.();
|
|
643
|
-
const total = Number(meta?.totalTime ?? replayDuration(
|
|
714
|
+
const total = Number(meta?.totalTime ?? replayDuration(replayEvents));
|
|
644
715
|
setTotalTime(Number.isFinite(total) ? total : 0);
|
|
645
716
|
const startAt = clamp(
|
|
646
717
|
currentTimeRef.current,
|
|
647
718
|
0,
|
|
648
719
|
Number.isFinite(total) ? total : 0,
|
|
649
720
|
);
|
|
650
|
-
applyReplayFrameDimensions(localReplayer, initialDims);
|
|
651
721
|
localReplayer.on?.("finish", () => {
|
|
652
722
|
setPlaying(false);
|
|
653
723
|
const finalTime = Number(localReplayer.getCurrentTime?.() ?? total);
|
|
@@ -655,20 +725,20 @@ function ReplayPlayer({
|
|
|
655
725
|
});
|
|
656
726
|
localReplayer.on?.("resize", (payload: unknown) => {
|
|
657
727
|
const dims = payload as { width?: unknown; height?: unknown };
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
728
|
+
if (
|
|
729
|
+
typeof dims.width === "number" &&
|
|
730
|
+
typeof dims.height === "number" &&
|
|
731
|
+
Number.isFinite(dims.width) &&
|
|
732
|
+
Number.isFinite(dims.height) &&
|
|
733
|
+
dims.width > 0 &&
|
|
734
|
+
dims.height > 0
|
|
735
|
+
) {
|
|
736
|
+
setStreamedDims({
|
|
737
|
+
width: Math.round(dims.width),
|
|
738
|
+
height: Math.round(dims.height),
|
|
739
|
+
});
|
|
663
740
|
}
|
|
664
741
|
});
|
|
665
|
-
localReplayer.on?.("fullsnapshot-rebuilded", () => {
|
|
666
|
-
applyReplayFrameDimensions(
|
|
667
|
-
localReplayer,
|
|
668
|
-
replayViewportDimensions(events) ?? initialDims,
|
|
669
|
-
);
|
|
670
|
-
revealReplayFrame(localReplayer);
|
|
671
|
-
});
|
|
672
742
|
updateTime(startAt);
|
|
673
743
|
setStatus("ready");
|
|
674
744
|
try {
|
|
@@ -683,13 +753,6 @@ function ReplayPlayer({
|
|
|
683
753
|
}
|
|
684
754
|
setPlaying(false);
|
|
685
755
|
}
|
|
686
|
-
window.requestAnimationFrame(() => {
|
|
687
|
-
applyReplayFrameDimensions(
|
|
688
|
-
localReplayer,
|
|
689
|
-
replayViewportDimensions(events) ?? initialDims,
|
|
690
|
-
);
|
|
691
|
-
revealReplayFrame(localReplayer);
|
|
692
|
-
});
|
|
693
756
|
}
|
|
694
757
|
|
|
695
758
|
void loadReplay().catch((loadError: any) => {
|
|
@@ -713,10 +776,13 @@ function ReplayPlayer({
|
|
|
713
776
|
replayerRef.current = null;
|
|
714
777
|
if (stageRootRef.current) stageRootRef.current.innerHTML = "";
|
|
715
778
|
};
|
|
779
|
+
// Key only on isComplete + a stable events identity. Including the events
|
|
780
|
+
// array reference would rebuild the Replayer when the final publish()
|
|
781
|
+
// replaces the chunks object even though the event set is unchanged.
|
|
716
782
|
}, [
|
|
717
783
|
currentTimeRef,
|
|
718
|
-
|
|
719
|
-
|
|
784
|
+
eventsIdentity,
|
|
785
|
+
eventsRef,
|
|
720
786
|
response.isComplete,
|
|
721
787
|
speedRef,
|
|
722
788
|
t,
|
|
@@ -732,7 +798,7 @@ function ReplayPlayer({
|
|
|
732
798
|
|
|
733
799
|
const tick = () => {
|
|
734
800
|
const replayer = replayerRef.current;
|
|
735
|
-
if (replayer) {
|
|
801
|
+
if (replayer && !scrubbingRef.current) {
|
|
736
802
|
let nextTime = Number(
|
|
737
803
|
replayer.getCurrentTime?.() ?? currentTimeRef.current,
|
|
738
804
|
);
|
|
@@ -812,210 +878,218 @@ function ReplayPlayer({
|
|
|
812
878
|
|
|
813
879
|
return (
|
|
814
880
|
<TooltipProvider>
|
|
815
|
-
<
|
|
816
|
-
<
|
|
817
|
-
<
|
|
818
|
-
|
|
881
|
+
<div ref={playerShellRef} className="flex min-h-0 flex-1 flex-col">
|
|
882
|
+
<Card className="flex min-h-0 flex-1 overflow-hidden">
|
|
883
|
+
<CardContent className="flex min-h-0 flex-1 flex-col p-0">
|
|
884
|
+
<div className="flex min-h-0 flex-1 flex-col bg-muted/20 p-2">
|
|
885
|
+
{currentUrl ? (
|
|
886
|
+
<div
|
|
887
|
+
className="flex h-8 shrink-0 items-center rounded-t-md border border-b-0 bg-background px-3 font-mono text-xs text-muted-foreground"
|
|
888
|
+
title={currentUrl}
|
|
889
|
+
>
|
|
890
|
+
<span className="truncate">{currentUrl}</span>
|
|
891
|
+
</div>
|
|
892
|
+
) : null}
|
|
819
893
|
<div
|
|
820
|
-
|
|
821
|
-
|
|
894
|
+
ref={stageAreaRef}
|
|
895
|
+
className={cn(
|
|
896
|
+
"relative min-h-[200px] flex-1 overflow-hidden border bg-white dark:bg-zinc-950",
|
|
897
|
+
currentUrl ? "rounded-b-md" : "rounded-md",
|
|
898
|
+
!devToolsOpen && "min-h-[320px]",
|
|
899
|
+
)}
|
|
822
900
|
>
|
|
823
|
-
<
|
|
901
|
+
<div
|
|
902
|
+
ref={stageRootRef}
|
|
903
|
+
className="an-replay-stage-root absolute left-1/2 top-1/2"
|
|
904
|
+
style={{
|
|
905
|
+
width: playerWidth,
|
|
906
|
+
height: playerHeight,
|
|
907
|
+
transform: `translate(-50%, -50%) scale(${fitScale})`,
|
|
908
|
+
transformOrigin: "center center",
|
|
909
|
+
}}
|
|
910
|
+
/>
|
|
911
|
+
<button
|
|
912
|
+
type="button"
|
|
913
|
+
className="absolute inset-0 z-20 cursor-pointer rounded-[inherit] border-0 bg-transparent p-0 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-default"
|
|
914
|
+
disabled={disabled}
|
|
915
|
+
aria-label={
|
|
916
|
+
playing ? t("sessions.pause") : t("sessions.play")
|
|
917
|
+
}
|
|
918
|
+
onClick={togglePlay}
|
|
919
|
+
/>
|
|
920
|
+
{status === "loading" ? (
|
|
921
|
+
<div className="absolute inset-0 z-30 grid place-items-center bg-background/70 p-6 text-center text-sm text-muted-foreground">
|
|
922
|
+
<div className="w-full max-w-sm">
|
|
923
|
+
<p>{t("sessions.replayLoading")}</p>
|
|
924
|
+
<div className="mt-3 h-1.5 overflow-hidden rounded-full bg-muted">
|
|
925
|
+
<div
|
|
926
|
+
className="h-full rounded-full bg-primary transition-[width]"
|
|
927
|
+
style={{
|
|
928
|
+
width: `${Math.round(loadingPercent * 100)}%`,
|
|
929
|
+
}}
|
|
930
|
+
/>
|
|
931
|
+
</div>
|
|
932
|
+
{response.totalChunks > 0 ? (
|
|
933
|
+
<p className="mt-2 font-mono text-[11px]">
|
|
934
|
+
{t("sessions.replayLoadingProgress", {
|
|
935
|
+
loaded: String(response.loadedChunks),
|
|
936
|
+
total: String(response.totalChunks),
|
|
937
|
+
})}
|
|
938
|
+
</p>
|
|
939
|
+
) : null}
|
|
940
|
+
</div>
|
|
941
|
+
</div>
|
|
942
|
+
) : null}
|
|
943
|
+
{status === "error" && error ? (
|
|
944
|
+
<div className="absolute inset-0 z-30 grid place-items-center bg-background/85 p-6 text-center text-sm text-destructive">
|
|
945
|
+
{t("sessions.loadFailed", { message: error })}
|
|
946
|
+
</div>
|
|
947
|
+
) : null}
|
|
824
948
|
</div>
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
"
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
style={{
|
|
837
|
-
width: playerWidth,
|
|
838
|
-
height: playerHeight,
|
|
839
|
-
transform: `translate(-50%, -50%) scale(${fitScale})`,
|
|
840
|
-
transformOrigin: "center center",
|
|
841
|
-
}}
|
|
842
|
-
/>
|
|
843
|
-
<button
|
|
949
|
+
</div>
|
|
950
|
+
|
|
951
|
+
<div className="flex shrink-0 flex-wrap items-center gap-2 border-t px-3 py-2">
|
|
952
|
+
<ReplayIconButton
|
|
953
|
+
label={t("sessions.skipBack")}
|
|
954
|
+
disabled={disabled}
|
|
955
|
+
onClick={() => seek(currentTime - SKIP_STEP_MS)}
|
|
956
|
+
>
|
|
957
|
+
<IconPlayerSkipBack className="h-4 w-4" />
|
|
958
|
+
</ReplayIconButton>
|
|
959
|
+
<Button
|
|
844
960
|
type="button"
|
|
845
|
-
|
|
961
|
+
size="icon"
|
|
846
962
|
disabled={disabled}
|
|
847
|
-
aria-label={playing ? t("sessions.pause") : t("sessions.play")}
|
|
848
963
|
onClick={togglePlay}
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
loaded: String(response.loadedChunks),
|
|
866
|
-
total: String(response.totalChunks),
|
|
867
|
-
})}
|
|
868
|
-
</p>
|
|
869
|
-
) : null}
|
|
870
|
-
</div>
|
|
871
|
-
</div>
|
|
872
|
-
) : null}
|
|
873
|
-
{status === "error" && error ? (
|
|
874
|
-
<div className="absolute inset-0 z-30 grid place-items-center bg-background/85 p-6 text-center text-sm text-destructive">
|
|
875
|
-
{t("sessions.loadFailed", { message: error })}
|
|
876
|
-
</div>
|
|
877
|
-
) : null}
|
|
878
|
-
</div>
|
|
879
|
-
</div>
|
|
964
|
+
aria-label={playing ? t("sessions.pause") : t("sessions.play")}
|
|
965
|
+
className="h-8 w-8"
|
|
966
|
+
>
|
|
967
|
+
{playing ? (
|
|
968
|
+
<IconPlayerPause className="h-4 w-4" />
|
|
969
|
+
) : (
|
|
970
|
+
<IconPlayerPlay className="h-4 w-4" />
|
|
971
|
+
)}
|
|
972
|
+
</Button>
|
|
973
|
+
<ReplayIconButton
|
|
974
|
+
label={t("sessions.skipForward")}
|
|
975
|
+
disabled={disabled}
|
|
976
|
+
onClick={() => seek(currentTime + SKIP_STEP_MS)}
|
|
977
|
+
>
|
|
978
|
+
<IconPlayerSkipForward className="h-4 w-4" />
|
|
979
|
+
</ReplayIconButton>
|
|
880
980
|
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
{playing ? (
|
|
898
|
-
<IconPlayerPause className="h-4 w-4" />
|
|
899
|
-
) : (
|
|
900
|
-
<IconPlayerPlay className="h-4 w-4" />
|
|
901
|
-
)}
|
|
902
|
-
</Button>
|
|
903
|
-
<ReplayIconButton
|
|
904
|
-
label={t("sessions.skipForward")}
|
|
905
|
-
disabled={disabled}
|
|
906
|
-
onClick={() => seek(currentTime + SKIP_STEP_MS)}
|
|
907
|
-
>
|
|
908
|
-
<IconPlayerSkipForward className="h-4 w-4" />
|
|
909
|
-
</ReplayIconButton>
|
|
910
|
-
|
|
911
|
-
<span className="w-12 text-center font-mono text-xs text-muted-foreground">
|
|
912
|
-
{formatClock(currentTime)}
|
|
913
|
-
</span>
|
|
914
|
-
<ReplayScrubber
|
|
915
|
-
currentTime={currentTime}
|
|
916
|
-
totalTime={totalTime}
|
|
917
|
-
markers={markers}
|
|
918
|
-
skipRanges={skipRanges}
|
|
919
|
-
skipInactive={skipInactive}
|
|
920
|
-
disabled={disabled}
|
|
921
|
-
onSeek={(ms) => seek(ms)}
|
|
922
|
-
/>
|
|
923
|
-
<span className="w-12 text-center font-mono text-xs text-muted-foreground">
|
|
924
|
-
{formatClock(totalTime)}
|
|
925
|
-
</span>
|
|
981
|
+
<span className="w-12 text-center font-mono text-xs text-muted-foreground">
|
|
982
|
+
{formatClock(currentTime)}
|
|
983
|
+
</span>
|
|
984
|
+
<ReplayScrubber
|
|
985
|
+
currentTime={currentTime}
|
|
986
|
+
totalTime={totalTime}
|
|
987
|
+
markers={markers}
|
|
988
|
+
skipRanges={skipRanges}
|
|
989
|
+
skipInactive={skipInactive}
|
|
990
|
+
disabled={disabled}
|
|
991
|
+
onScrub={beginScrub}
|
|
992
|
+
onScrubEnd={endScrub}
|
|
993
|
+
/>
|
|
994
|
+
<span className="w-12 text-center font-mono text-xs text-muted-foreground">
|
|
995
|
+
{formatClock(totalTime)}
|
|
996
|
+
</span>
|
|
926
997
|
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
998
|
+
<div className="flex items-center rounded-md bg-muted p-1">
|
|
999
|
+
{SPEED_OPTIONS.map((option) => (
|
|
1000
|
+
<button
|
|
1001
|
+
key={option}
|
|
1002
|
+
type="button"
|
|
1003
|
+
className={cn(
|
|
1004
|
+
"rounded px-2 py-1 text-xs font-medium text-muted-foreground transition-colors hover:text-foreground",
|
|
1005
|
+
speed === option &&
|
|
1006
|
+
"bg-background text-foreground shadow-sm",
|
|
1007
|
+
)}
|
|
1008
|
+
onClick={() => updateSpeed(option)}
|
|
1009
|
+
>
|
|
1010
|
+
{option}x
|
|
1011
|
+
</button>
|
|
1012
|
+
))}
|
|
1013
|
+
</div>
|
|
943
1014
|
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
<button
|
|
968
|
-
type="button"
|
|
969
|
-
className={cn(
|
|
970
|
-
"inline-flex h-8 items-center gap-1.5 rounded-md border px-2 text-xs font-medium transition-colors hover:bg-muted",
|
|
971
|
-
devToolsOpen && "border-primary/40 bg-primary/10 text-primary",
|
|
972
|
-
)}
|
|
973
|
-
onClick={() => setDevToolsOpen((value) => !value)}
|
|
974
|
-
aria-pressed={devToolsOpen}
|
|
975
|
-
aria-expanded={devToolsOpen}
|
|
976
|
-
>
|
|
977
|
-
<IconTerminal2 className="h-4 w-4" />
|
|
978
|
-
{t("sessions.devtools")}
|
|
979
|
-
{devToolsIssueCount > 0 ? (
|
|
980
|
-
<span
|
|
981
|
-
className="inline-flex h-4 min-w-4 items-center justify-center rounded-full bg-red-500 px-1 font-mono text-[10px] font-semibold leading-none text-white"
|
|
982
|
-
aria-label={t("sessions.devtoolsIssueCount", {
|
|
983
|
-
count: String(devToolsIssueCount),
|
|
984
|
-
})}
|
|
985
|
-
>
|
|
986
|
-
{devToolsIssueCount > 99 ? "99+" : devToolsIssueCount}
|
|
987
|
-
</span>
|
|
988
|
-
) : null}
|
|
989
|
-
</button>
|
|
1015
|
+
<Tooltip>
|
|
1016
|
+
<TooltipTrigger asChild>
|
|
1017
|
+
<button
|
|
1018
|
+
type="button"
|
|
1019
|
+
className={cn(
|
|
1020
|
+
"inline-flex h-8 items-center gap-1.5 rounded-md border px-2 text-xs font-medium transition-colors hover:bg-muted",
|
|
1021
|
+
skipInactive &&
|
|
1022
|
+
"border-primary/40 bg-primary/10 text-primary",
|
|
1023
|
+
)}
|
|
1024
|
+
onClick={() => setSkipInactive((value) => !value)}
|
|
1025
|
+
aria-pressed={skipInactive}
|
|
1026
|
+
>
|
|
1027
|
+
<IconPlayerTrackNext className="h-4 w-4" />
|
|
1028
|
+
{t("sessions.skipInactive")}
|
|
1029
|
+
</button>
|
|
1030
|
+
</TooltipTrigger>
|
|
1031
|
+
<TooltipContent>
|
|
1032
|
+
{skipInactive
|
|
1033
|
+
? t("sessions.skipInactiveOn")
|
|
1034
|
+
: t("sessions.skipInactiveOff")}
|
|
1035
|
+
</TooltipContent>
|
|
1036
|
+
</Tooltip>
|
|
990
1037
|
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
1038
|
+
<button
|
|
1039
|
+
type="button"
|
|
1040
|
+
className={cn(
|
|
1041
|
+
"inline-flex h-8 items-center gap-1.5 rounded-md border px-2 text-xs font-medium transition-colors hover:bg-muted",
|
|
1042
|
+
devToolsOpen &&
|
|
1043
|
+
"border-primary/40 bg-primary/10 text-primary",
|
|
1044
|
+
)}
|
|
1045
|
+
onClick={() => setDevToolsOpen((value) => !value)}
|
|
1046
|
+
aria-pressed={devToolsOpen}
|
|
1047
|
+
aria-expanded={devToolsOpen}
|
|
1048
|
+
>
|
|
1049
|
+
<IconTerminal2 className="h-4 w-4" />
|
|
1050
|
+
{t("sessions.devtools")}
|
|
1051
|
+
{devToolsIssueCount > 0 ? (
|
|
1052
|
+
<span
|
|
1053
|
+
className="inline-flex h-4 min-w-4 items-center justify-center rounded-full bg-red-500 px-1 font-mono text-[10px] font-semibold leading-none text-white"
|
|
1054
|
+
aria-label={t("sessions.devtoolsIssueCount", {
|
|
1055
|
+
count: String(devToolsIssueCount),
|
|
1056
|
+
})}
|
|
1057
|
+
>
|
|
1058
|
+
{devToolsIssueCount > 99 ? "99+" : devToolsIssueCount}
|
|
1059
|
+
</span>
|
|
1060
|
+
) : null}
|
|
1061
|
+
</button>
|
|
1062
|
+
|
|
1063
|
+
<span className="ms-auto hidden text-xs text-muted-foreground lg:inline">
|
|
1064
|
+
{t("sessions.replayEventCount", {
|
|
1065
|
+
events: String(response.eventCount),
|
|
1066
|
+
})}
|
|
1067
|
+
{response.truncated ? ` ${t("sessions.truncated")}` : ""}
|
|
1068
|
+
</span>
|
|
1069
|
+
</div>
|
|
998
1070
|
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1071
|
+
{devToolsOpen ? (
|
|
1072
|
+
<SessionDevToolsPanel
|
|
1073
|
+
diagnostics={diagnostics}
|
|
1074
|
+
currentTime={currentTime}
|
|
1075
|
+
height={Math.min(devToolsHeight, maxDevToolsHeight)}
|
|
1076
|
+
maxHeight={maxDevToolsHeight}
|
|
1077
|
+
onHeightChange={setDevToolsHeight}
|
|
1078
|
+
onSeek={(ms) => seek(ms, true)}
|
|
1079
|
+
issueMatches={issueMatches}
|
|
1080
|
+
/>
|
|
1081
|
+
) : null}
|
|
1009
1082
|
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1083
|
+
{response.unavailableChunks > 0 ? (
|
|
1084
|
+
<div className="border-t px-4 py-2 text-xs text-muted-foreground">
|
|
1085
|
+
{t("sessions.unavailableChunks", {
|
|
1086
|
+
count: String(response.unavailableChunks),
|
|
1087
|
+
})}
|
|
1088
|
+
</div>
|
|
1089
|
+
) : null}
|
|
1090
|
+
</CardContent>
|
|
1091
|
+
</Card>
|
|
1092
|
+
</div>
|
|
1019
1093
|
</TooltipProvider>
|
|
1020
1094
|
);
|
|
1021
1095
|
}
|
|
@@ -1027,7 +1101,8 @@ function ReplayScrubber({
|
|
|
1027
1101
|
skipRanges,
|
|
1028
1102
|
skipInactive,
|
|
1029
1103
|
disabled,
|
|
1030
|
-
|
|
1104
|
+
onScrub,
|
|
1105
|
+
onScrubEnd,
|
|
1031
1106
|
}: {
|
|
1032
1107
|
currentTime: number;
|
|
1033
1108
|
totalTime: number;
|
|
@@ -1035,13 +1110,19 @@ function ReplayScrubber({
|
|
|
1035
1110
|
skipRanges: SkipRange[];
|
|
1036
1111
|
skipInactive: boolean;
|
|
1037
1112
|
disabled: boolean;
|
|
1038
|
-
|
|
1113
|
+
onScrub: (ms: number) => void;
|
|
1114
|
+
onScrubEnd: (ms: number) => void;
|
|
1039
1115
|
}) {
|
|
1040
1116
|
const t = useT();
|
|
1041
1117
|
const scrubberMarkers = useMemo(
|
|
1042
1118
|
() => visibleScrubberMarkers(markers, totalTime),
|
|
1043
1119
|
[markers, totalTime],
|
|
1044
1120
|
);
|
|
1121
|
+
|
|
1122
|
+
function handleScrub(event: FormEvent<HTMLInputElement>) {
|
|
1123
|
+
onScrub(Number(event.currentTarget.value));
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1045
1126
|
return (
|
|
1046
1127
|
<div className="relative min-h-8 min-w-[180px] flex-1">
|
|
1047
1128
|
<input
|
|
@@ -1050,9 +1131,16 @@ function ReplayScrubber({
|
|
|
1050
1131
|
min={0}
|
|
1051
1132
|
max={Math.max(0, totalTime)}
|
|
1052
1133
|
step={50}
|
|
1053
|
-
value={Math.min(currentTime, totalTime)}
|
|
1134
|
+
value={Math.min(currentTime, Math.max(totalTime, 0))}
|
|
1054
1135
|
disabled={disabled}
|
|
1055
|
-
|
|
1136
|
+
onInput={handleScrub}
|
|
1137
|
+
onChange={handleScrub}
|
|
1138
|
+
onPointerUp={(event) =>
|
|
1139
|
+
onScrubEnd(Number((event.target as HTMLInputElement).value))
|
|
1140
|
+
}
|
|
1141
|
+
onKeyUp={(event) =>
|
|
1142
|
+
onScrubEnd(Number((event.target as HTMLInputElement).value))
|
|
1143
|
+
}
|
|
1056
1144
|
aria-label={t("sessions.replayTimeline")}
|
|
1057
1145
|
/>
|
|
1058
1146
|
<div className="pointer-events-none absolute inset-x-0 top-1/2 z-10 h-2 -translate-y-1/2 overflow-hidden rounded-full">
|
|
@@ -1081,7 +1169,7 @@ function ReplayScrubber({
|
|
|
1081
1169
|
<span
|
|
1082
1170
|
key={marker.id}
|
|
1083
1171
|
className={cn(
|
|
1084
|
-
"absolute top-1/2 h-
|
|
1172
|
+
"absolute top-1/2 h-1.5 w-1.5 -translate-x-1/2 -translate-y-1/2 rounded-full opacity-90 ring-1 ring-background/80",
|
|
1085
1173
|
marker.kind === "navigation"
|
|
1086
1174
|
? "bg-amber-500"
|
|
1087
1175
|
: marker.kind === "input"
|
|
@@ -1147,38 +1235,94 @@ function ReplayTimeline({
|
|
|
1147
1235
|
}) {
|
|
1148
1236
|
const t = useT();
|
|
1149
1237
|
const [expandedMarkerId, setExpandedMarkerId] = useState<string | null>(null);
|
|
1150
|
-
const
|
|
1238
|
+
const [query, setQuery] = useState("");
|
|
1239
|
+
const listRef = useRef<HTMLDivElement>(null);
|
|
1240
|
+
const lastManualScrollAtRef = useRef(0);
|
|
1241
|
+
const activeRowRef = useRef<HTMLDivElement | null>(null);
|
|
1242
|
+
|
|
1243
|
+
const filteredMarkers = useMemo(
|
|
1244
|
+
() => filterReplayMarkers(markers, query).slice(0, TIMELINE_MARKER_LIMIT),
|
|
1245
|
+
[markers, query],
|
|
1246
|
+
);
|
|
1247
|
+
|
|
1248
|
+
useEffect(() => {
|
|
1249
|
+
if (!activeMarkerId) return;
|
|
1250
|
+
if (Date.now() - lastManualScrollAtRef.current < TIMELINE_FOLLOW_PAUSE_MS) {
|
|
1251
|
+
return;
|
|
1252
|
+
}
|
|
1253
|
+
const row = activeRowRef.current;
|
|
1254
|
+
const list = listRef.current;
|
|
1255
|
+
if (!row || !list) return;
|
|
1256
|
+
const rowTop = row.offsetTop;
|
|
1257
|
+
const rowBottom = rowTop + row.offsetHeight;
|
|
1258
|
+
if (
|
|
1259
|
+
rowTop >= list.scrollTop &&
|
|
1260
|
+
rowBottom <= list.scrollTop + list.clientHeight
|
|
1261
|
+
) {
|
|
1262
|
+
return;
|
|
1263
|
+
}
|
|
1264
|
+
list.scrollTo({
|
|
1265
|
+
top: Math.max(0, rowTop - list.clientHeight / 3),
|
|
1266
|
+
behavior: "smooth",
|
|
1267
|
+
});
|
|
1268
|
+
}, [activeMarkerId, filteredMarkers]);
|
|
1269
|
+
|
|
1151
1270
|
return (
|
|
1152
1271
|
<Card className="analytics-session-detail-timeline min-h-0 overflow-hidden">
|
|
1153
1272
|
<CardContent className="flex min-h-0 flex-1 flex-col p-0">
|
|
1154
|
-
<div className="shrink-0 border-b px-3 py-2">
|
|
1273
|
+
<div className="shrink-0 space-y-2 border-b px-3 py-2">
|
|
1155
1274
|
<div className="flex items-center gap-2 text-sm font-semibold">
|
|
1156
1275
|
<IconTimelineEvent className="h-4 w-4" />
|
|
1157
1276
|
{t("sessions.timeline")}
|
|
1158
1277
|
</div>
|
|
1159
|
-
<p className="
|
|
1160
|
-
{
|
|
1278
|
+
<p className="text-xs text-muted-foreground">
|
|
1279
|
+
{filteredMarkers.length
|
|
1161
1280
|
? t("sessions.timelineDescription", {
|
|
1162
|
-
count: String(
|
|
1281
|
+
count: String(filteredMarkers.length),
|
|
1163
1282
|
total: String(markers.length),
|
|
1164
1283
|
})
|
|
1165
1284
|
: t("sessions.noTimelineEvents")}
|
|
1166
1285
|
</p>
|
|
1286
|
+
<div className="relative">
|
|
1287
|
+
<IconSearch className="pointer-events-none absolute start-2 top-1/2 h-3.5 w-3.5 -translate-y-1/2 text-muted-foreground" />
|
|
1288
|
+
<Input
|
|
1289
|
+
type="search"
|
|
1290
|
+
className="h-7 ps-7 text-xs"
|
|
1291
|
+
value={query}
|
|
1292
|
+
placeholder={t("sessions.timelineSearch")}
|
|
1293
|
+
onChange={(event) => setQuery(event.target.value)}
|
|
1294
|
+
/>
|
|
1295
|
+
</div>
|
|
1167
1296
|
</div>
|
|
1168
|
-
<div
|
|
1169
|
-
{
|
|
1297
|
+
<div
|
|
1298
|
+
ref={listRef}
|
|
1299
|
+
className="min-h-0 flex-1 overflow-auto"
|
|
1300
|
+
onWheel={() => {
|
|
1301
|
+
lastManualScrollAtRef.current = Date.now();
|
|
1302
|
+
}}
|
|
1303
|
+
onPointerDown={() => {
|
|
1304
|
+
lastManualScrollAtRef.current = Date.now();
|
|
1305
|
+
}}
|
|
1306
|
+
>
|
|
1307
|
+
{filteredMarkers.length ? (
|
|
1170
1308
|
<div className="divide-y">
|
|
1171
|
-
{
|
|
1309
|
+
{filteredMarkers.map((marker) => {
|
|
1172
1310
|
const expanded = marker.id === expandedMarkerId;
|
|
1311
|
+
const active = marker.id === activeMarkerId;
|
|
1173
1312
|
return (
|
|
1174
1313
|
<div
|
|
1175
1314
|
key={marker.id}
|
|
1176
|
-
|
|
1315
|
+
ref={active ? activeRowRef : undefined}
|
|
1316
|
+
className={cn(
|
|
1317
|
+
"transition-colors duration-300",
|
|
1318
|
+
active && "bg-primary/[0.06] dark:bg-primary/[0.09]",
|
|
1319
|
+
)}
|
|
1177
1320
|
>
|
|
1178
1321
|
<button
|
|
1179
1322
|
type="button"
|
|
1180
1323
|
className="flex w-full gap-2.5 px-3 py-2.5 text-left transition-colors hover:bg-muted/50"
|
|
1181
1324
|
aria-expanded={expanded}
|
|
1325
|
+
aria-current={active ? "true" : undefined}
|
|
1182
1326
|
onClick={() => {
|
|
1183
1327
|
onSeek(marker.offsetMs);
|
|
1184
1328
|
setExpandedMarkerId((current) =>
|
|
@@ -1236,7 +1380,9 @@ function ReplayTimeline({
|
|
|
1236
1380
|
</div>
|
|
1237
1381
|
) : (
|
|
1238
1382
|
<div className="p-4 text-sm text-muted-foreground">
|
|
1239
|
-
{
|
|
1383
|
+
{query.trim()
|
|
1384
|
+
? t("sessions.timelineNoMatches")
|
|
1385
|
+
: t("sessions.noTimelineEvents")}
|
|
1240
1386
|
</div>
|
|
1241
1387
|
)}
|
|
1242
1388
|
</div>
|
|
@@ -1323,21 +1469,18 @@ function useSessionReplayPlayback(recordingId: string) {
|
|
|
1323
1469
|
let loadedBytes = 0;
|
|
1324
1470
|
let unavailableChunks = 0;
|
|
1325
1471
|
let nextIndex = 0;
|
|
1326
|
-
let firstPlayablePublished = false;
|
|
1327
1472
|
|
|
1328
1473
|
const publish = (force = false) => {
|
|
1329
1474
|
const complete = loadedCount >= manifest.chunks.length;
|
|
1330
1475
|
const chunks = loadedChunks.filter(
|
|
1331
1476
|
(chunk): chunk is ReplayChunkEvents => Boolean(chunk),
|
|
1332
1477
|
);
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
hasPlayableReplayEvents(chunks.flatMap((chunk) => chunk.events)));
|
|
1478
|
+
// Only hand events to the player once every chunk is in. Partial
|
|
1479
|
+
// publishes used to rebuild the Replayer mid-playback and break the
|
|
1480
|
+
// scrubber; the loading bar still updates while chunks stream in.
|
|
1481
|
+
const shouldPublishEvents = force || complete;
|
|
1338
1482
|
|
|
1339
1483
|
if (shouldPublishEvents) {
|
|
1340
|
-
firstPlayablePublished = true;
|
|
1341
1484
|
setState({
|
|
1342
1485
|
data: playbackResponseFromChunks(manifest, chunks, {
|
|
1343
1486
|
isComplete: complete,
|
|
@@ -1365,6 +1508,8 @@ function useSessionReplayPlayback(recordingId: string) {
|
|
|
1365
1508
|
loadedBytes,
|
|
1366
1509
|
unavailableChunks,
|
|
1367
1510
|
}),
|
|
1511
|
+
// Keep the page shell mounted so the player loading bar can show
|
|
1512
|
+
// chunk progress; the Replayer itself still waits for isComplete.
|
|
1368
1513
|
isLoading: false,
|
|
1369
1514
|
error: null,
|
|
1370
1515
|
}));
|
|
@@ -2008,18 +2153,33 @@ function hasPlayableReplayEvents(events: unknown[]): boolean {
|
|
|
2008
2153
|
export function replayViewportDimensions(
|
|
2009
2154
|
events: AnyReplayEvent[],
|
|
2010
2155
|
): ReplayViewportDimensions | null {
|
|
2156
|
+
// Latest Meta / ViewportResize for CSS fit-to-stage only. Never rewrite these
|
|
2157
|
+
// into the event stream — rrweb must keep Meta in sync with the FullSnapshot.
|
|
2158
|
+
let best: ReplayViewportDimensions | null = null;
|
|
2011
2159
|
for (const event of events) {
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2160
|
+
const dims = dimensionsFromReplayEvent(event);
|
|
2161
|
+
if (dims) best = dims;
|
|
2162
|
+
}
|
|
2163
|
+
return best;
|
|
2164
|
+
}
|
|
2165
|
+
|
|
2166
|
+
function dimensionsFromReplayEvent(
|
|
2167
|
+
event: AnyReplayEvent,
|
|
2168
|
+
): ReplayViewportDimensions | null {
|
|
2169
|
+
if (event.type === RRWEB_EVENT_TYPE.Meta) {
|
|
2170
|
+
return normalizeReplayDimensions(event.data?.width, event.data?.height);
|
|
2171
|
+
}
|
|
2172
|
+
if (
|
|
2173
|
+
event.type === RRWEB_EVENT_TYPE.IncrementalSnapshot &&
|
|
2174
|
+
event.data?.source === INCREMENTAL_SOURCE.ViewportResize
|
|
2175
|
+
) {
|
|
2176
|
+
return normalizeReplayDimensions(event.data?.width, event.data?.height);
|
|
2018
2177
|
}
|
|
2019
2178
|
return null;
|
|
2020
2179
|
}
|
|
2021
2180
|
|
|
2022
|
-
|
|
2181
|
+
/** Read raw positive finite dimensions; no aspect clamping. */
|
|
2182
|
+
export function normalizeReplayDimensions(
|
|
2023
2183
|
width: unknown,
|
|
2024
2184
|
height: unknown,
|
|
2025
2185
|
): ReplayViewportDimensions | null {
|
|
@@ -2039,30 +2199,45 @@ function normalizeReplayDimensions(
|
|
|
2039
2199
|
};
|
|
2040
2200
|
}
|
|
2041
2201
|
|
|
2042
|
-
function
|
|
2043
|
-
replayer: any,
|
|
2202
|
+
export function clampReplayDisplayDimensions(
|
|
2044
2203
|
dims: ReplayViewportDimensions | null,
|
|
2045
|
-
) {
|
|
2046
|
-
|
|
2047
|
-
const
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
element.style.width = `${width}px`;
|
|
2054
|
-
element.style.height = `${height}px`;
|
|
2204
|
+
): ReplayViewportDimensions | null {
|
|
2205
|
+
if (!dims) return null;
|
|
2206
|
+
const aspect = dims.width / dims.height;
|
|
2207
|
+
if (aspect > MAX_REPLAY_DISPLAY_ASPECT_RATIO) {
|
|
2208
|
+
return {
|
|
2209
|
+
width: Math.round(dims.height * MAX_REPLAY_DISPLAY_ASPECT_RATIO),
|
|
2210
|
+
height: dims.height,
|
|
2211
|
+
};
|
|
2055
2212
|
}
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2213
|
+
if (aspect < MIN_REPLAY_DISPLAY_ASPECT_RATIO) {
|
|
2214
|
+
return {
|
|
2215
|
+
width: dims.width,
|
|
2216
|
+
height: Math.round(dims.width / MIN_REPLAY_DISPLAY_ASPECT_RATIO),
|
|
2217
|
+
};
|
|
2060
2218
|
}
|
|
2219
|
+
return dims;
|
|
2061
2220
|
}
|
|
2062
2221
|
|
|
2063
|
-
function
|
|
2064
|
-
|
|
2065
|
-
|
|
2222
|
+
export function filterReplayMarkers(
|
|
2223
|
+
markers: ReplayMarker[],
|
|
2224
|
+
query: string,
|
|
2225
|
+
): ReplayMarker[] {
|
|
2226
|
+
const needle = query.trim().toLowerCase();
|
|
2227
|
+
if (!needle) return markers;
|
|
2228
|
+
return markers.filter((marker) => {
|
|
2229
|
+
const haystack = [
|
|
2230
|
+
marker.label,
|
|
2231
|
+
marker.detail,
|
|
2232
|
+
marker.kind,
|
|
2233
|
+
marker.severity,
|
|
2234
|
+
...(marker.fields?.flatMap((field) => [field.label, field.value]) ?? []),
|
|
2235
|
+
]
|
|
2236
|
+
.filter(Boolean)
|
|
2237
|
+
.join(" ")
|
|
2238
|
+
.toLowerCase();
|
|
2239
|
+
return haystack.includes(needle);
|
|
2240
|
+
});
|
|
2066
2241
|
}
|
|
2067
2242
|
|
|
2068
2243
|
function replayStartedAt(events: AnyReplayEvent[]): number {
|