@agent-native/core 0.68.3 → 0.70.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/corpus/README.md +2 -2
- package/corpus/core/CHANGELOG.md +54 -0
- package/corpus/core/package.json +1 -1
- 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 +72 -45
- 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/index.ts +11 -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/extensions/web-search-tool.ts +45 -4
- package/corpus/core/src/mcp/build-server.ts +1 -1
- package/corpus/core/src/mcp/embed-app.ts +112 -6
- package/corpus/core/src/secrets/register-framework-secrets.ts +7 -0
- 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 +58 -0
- package/corpus/core/src/server/google-oauth.ts +19 -6
- 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/tracking/SKILL.md +46 -14
- package/corpus/templates/analytics/CHANGELOG.md +10 -0
- package/corpus/templates/analytics/actions/install-dashboard-template.ts +2 -2
- package/corpus/templates/analytics/actions/open-traffic-dashboard.ts +1 -1
- package/corpus/templates/analytics/actions/update-dashboard.ts +5 -3
- package/corpus/templates/analytics/app/components/layout/CommandPalette.tsx +180 -144
- package/corpus/templates/analytics/app/components/layout/Header.tsx +2 -2
- package/corpus/templates/analytics/app/components/layout/Layout.tsx +7 -5
- package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +221 -213
- package/corpus/templates/analytics/app/hooks/use-navigation-state.ts +6 -3
- 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/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-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/app/root.tsx +7 -1
- package/corpus/templates/assets/app/routes/settings.tsx +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/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/pre-record-panel.tsx +72 -20
- package/corpus/templates/clips/app/components/recorder/recorder-engine.ts +69 -2
- package/corpus/templates/clips/app/components/sharing/slack-share-hint.tsx +71 -0
- package/corpus/templates/clips/app/lib/recorder-preferences.ts +67 -0
- 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/_app.trash.tsx +21 -0
- package/corpus/templates/clips/app/routes/record.tsx +92 -8
- 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-screen-camera-recordings-now-keep-the-presenter-s-face-in-th.md +6 -0
- 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/region-record-border.tsx +76 -0
- package/corpus/templates/clips/desktop/src/styles.css +49 -0
- package/corpus/templates/clips/desktop/src-tauri/capabilities/default.json +2 -1
- package/corpus/templates/clips/desktop/src-tauri/src/clips/mod.rs +67 -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 +51 -1
- package/corpus/templates/clips/shared/share-attribution.ts +79 -0
- package/corpus/templates/content/CHANGELOG.md +10 -0
- package/corpus/templates/content/app/root.tsx +7 -1
- package/corpus/templates/content/vitest.config.ts +3 -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/CHANGELOG.md +10 -0
- package/corpus/templates/forms/app/root.tsx +7 -1
- 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/pages/PlansPage.tsx +107 -7
- package/corpus/templates/plan/app/root.tsx +7 -1
- 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/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 +72 -45
- package/dist/cli/skills.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/index.d.ts +2 -1
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +2 -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/extensions/web-search-tool.d.ts +5 -3
- package/dist/extensions/web-search-tool.d.ts.map +1 -1
- package/dist/extensions/web-search-tool.js +36 -4
- package/dist/extensions/web-search-tool.js.map +1 -1
- package/dist/mcp/build-server.js +1 -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 +112 -6
- package/dist/mcp/embed-app.js.map +1 -1
- package/dist/secrets/register-framework-secrets.d.ts.map +1 -1
- package/dist/secrets/register-framework-secrets.js +6 -0
- package/dist/secrets/register-framework-secrets.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 +38 -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/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/tracking/SKILL.md +46 -14
- package/package.json +1 -1
- package/src/templates/workspace-core/.agents/skills/changelog/SKILL.md +111 -0
- package/src/templates/workspace-core/.agents/skills/tracking/SKILL.md +46 -14
- /package/corpus/templates/analytics/app/routes/{dashboards.tsx → dashboards._index.tsx} +0 -0
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable user-facing changes to Agent-Native Calendar are documented here. Open it any
|
|
4
|
+
time from the command menu (Cmd+K → "What's new") or from Settings.
|
|
5
|
+
|
|
6
|
+
## 2026-06-23
|
|
7
|
+
|
|
8
|
+
### Added
|
|
9
|
+
|
|
10
|
+
- See what's new right inside Agent-Native Calendar — a changelog now lives in the command menu (Cmd+K) and in Settings.
|
|
@@ -26,8 +26,10 @@ import { useSettings, useUpdateSettings } from "@/hooks/use-settings";
|
|
|
26
26
|
import {
|
|
27
27
|
AppearancePicker,
|
|
28
28
|
callAction,
|
|
29
|
+
ChangelogSettingsCard,
|
|
29
30
|
type AppearancePresetId,
|
|
30
31
|
} from "@agent-native/core/client";
|
|
32
|
+
import changelog from "../../CHANGELOG.md?raw";
|
|
31
33
|
import {
|
|
32
34
|
useGoogleAuthStatus,
|
|
33
35
|
useGoogleAuthUrl,
|
|
@@ -151,6 +153,8 @@ export default function Settings() {
|
|
|
151
153
|
</p>
|
|
152
154
|
</div>
|
|
153
155
|
|
|
156
|
+
<ChangelogSettingsCard markdown={changelog} />
|
|
157
|
+
|
|
154
158
|
{/* Google Calendar Connection */}
|
|
155
159
|
<Card>
|
|
156
160
|
<CardHeader>
|
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
useDbSync,
|
|
23
23
|
} from "@agent-native/core/client";
|
|
24
24
|
import { IconSun, IconMoon } from "@tabler/icons-react";
|
|
25
|
+
import changelog from "../CHANGELOG.md?raw";
|
|
25
26
|
import type { LinksFunction } from "react-router";
|
|
26
27
|
import stylesheet from "./global.css?url";
|
|
27
28
|
configureTracking({
|
|
@@ -137,7 +138,12 @@ function AppContent() {
|
|
|
137
138
|
return (
|
|
138
139
|
<>
|
|
139
140
|
<DbSyncSetup />
|
|
140
|
-
<CommandMenu
|
|
141
|
+
<CommandMenu
|
|
142
|
+
open={cmdkOpen}
|
|
143
|
+
onOpenChange={setCmdkOpen}
|
|
144
|
+
changelog={changelog}
|
|
145
|
+
changelogKey="calendar"
|
|
146
|
+
>
|
|
141
147
|
<CommandMenu.Group heading="Actions">
|
|
142
148
|
<CommandMenu.Item onSelect={() => {}}>Search</CommandMenu.Item>
|
|
143
149
|
</CommandMenu.Group>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable user-facing changes to Chat are documented here. Open it any
|
|
4
|
+
time from the command menu (Cmd+K → "What's new").
|
|
5
|
+
|
|
6
|
+
## 2026-06-23
|
|
7
|
+
|
|
8
|
+
### Added
|
|
9
|
+
|
|
10
|
+
- See what's new right inside Chat — a changelog now lives in the command menu (Cmd+K).
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
useCommandMenuShortcut,
|
|
15
15
|
} from "@agent-native/core/client";
|
|
16
16
|
import { IconSun, IconMoon } from "@tabler/icons-react";
|
|
17
|
+
import changelog from "../CHANGELOG.md?raw";
|
|
17
18
|
import { Layout as AppLayout } from "@/components/layout/Layout";
|
|
18
19
|
import { TAB_ID } from "@/lib/tab-id";
|
|
19
20
|
import { APP_TITLE } from "@/lib/app-config";
|
|
@@ -97,7 +98,12 @@ function AppContent() {
|
|
|
97
98
|
useCommandMenuShortcut(useCallback(() => setCmdkOpen(true), []));
|
|
98
99
|
return (
|
|
99
100
|
<>
|
|
100
|
-
<CommandMenu
|
|
101
|
+
<CommandMenu
|
|
102
|
+
open={cmdkOpen}
|
|
103
|
+
onOpenChange={setCmdkOpen}
|
|
104
|
+
changelog={changelog}
|
|
105
|
+
changelogKey="chat"
|
|
106
|
+
>
|
|
101
107
|
<CommandMenu.Group heading="Actions">
|
|
102
108
|
<CommandMenu.Item onSelect={() => {}}>Search</CommandMenu.Item>
|
|
103
109
|
</CommandMenu.Group>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable user-facing changes to Clips are documented here. Open it any time
|
|
4
|
+
from the command menu (Cmd+K → "What's new") or from Settings.
|
|
5
|
+
|
|
6
|
+
## 2026-06-23
|
|
7
|
+
|
|
8
|
+
### Added
|
|
9
|
+
|
|
10
|
+
- See what's new right inside Clips — a changelog now lives in the command menu (Cmd+K) and in Settings.
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
appBasePath,
|
|
10
10
|
appPath,
|
|
11
11
|
useActionQuery,
|
|
12
|
+
useSession,
|
|
12
13
|
} from "@agent-native/core/client";
|
|
13
14
|
import {
|
|
14
15
|
Popover,
|
|
@@ -34,8 +35,10 @@ import {
|
|
|
34
35
|
type SharesResponse,
|
|
35
36
|
type Visibility,
|
|
36
37
|
} from "@/components/sharing/share-ui";
|
|
38
|
+
import { SlackShareHint } from "@/components/sharing/slack-share-hint";
|
|
37
39
|
import { buildAgentApiUrls } from "../../../shared/agent-context";
|
|
38
40
|
import { isLoomEmbedUrl } from "../../../shared/loom";
|
|
41
|
+
import { withShareAttribution } from "../../../shared/share-attribution";
|
|
39
42
|
|
|
40
43
|
const PUBLIC_DESCRIPTION =
|
|
41
44
|
"Anyone with the link can view — sign in to comment or react";
|
|
@@ -156,11 +159,6 @@ function ShareRecordingContent({
|
|
|
156
159
|
hasPassword?: boolean;
|
|
157
160
|
reserveCloseButton?: boolean;
|
|
158
161
|
}) {
|
|
159
|
-
const shareUrl =
|
|
160
|
-
typeof window === "undefined"
|
|
161
|
-
? ""
|
|
162
|
-
: absoluteAppUrl(`/share/${recordingId}`);
|
|
163
|
-
|
|
164
162
|
const sharesQuery = useActionQuery<SharesResponse>("list-resource-shares", {
|
|
165
163
|
resourceType: "recording",
|
|
166
164
|
resourceId: recordingId,
|
|
@@ -168,6 +166,23 @@ function ShareRecordingContent({
|
|
|
168
166
|
|
|
169
167
|
const data = sharesQuery.data;
|
|
170
168
|
const canManage = data?.role === "owner" || data?.role === "admin";
|
|
169
|
+
|
|
170
|
+
// Attribution `via` must be a stable non-PII id, never an email. The only
|
|
171
|
+
// owner id available client-side is the *current* session's userId, which is
|
|
172
|
+
// the clip owner only when the viewer is the owner. Anyone else (e.g. a
|
|
173
|
+
// share-admin) gets an untagged `via` so we never attribute the link to the
|
|
174
|
+
// wrong person or leak the owner's email.
|
|
175
|
+
const { session } = useSession();
|
|
176
|
+
const ownerViaId =
|
|
177
|
+
data?.role === "owner" ? (session?.userId ?? undefined) : undefined;
|
|
178
|
+
|
|
179
|
+
const shareUrl =
|
|
180
|
+
typeof window === "undefined"
|
|
181
|
+
? ""
|
|
182
|
+
: withShareAttribution(
|
|
183
|
+
absoluteAppUrl(`/share/${recordingId}`),
|
|
184
|
+
ownerViaId,
|
|
185
|
+
);
|
|
171
186
|
const titleText = recordingTitle
|
|
172
187
|
? `Share "${recordingTitle}"`
|
|
173
188
|
: "Share recording";
|
|
@@ -224,6 +239,7 @@ function ShareRecordingContent({
|
|
|
224
239
|
recordingId={recordingId}
|
|
225
240
|
sharesQuery={sharesQuery}
|
|
226
241
|
canManage={canManage}
|
|
242
|
+
ownerViaId={ownerViaId}
|
|
227
243
|
/>
|
|
228
244
|
</TabsContent>
|
|
229
245
|
</Tabs>
|
|
@@ -321,6 +337,10 @@ function LinkTab({
|
|
|
321
337
|
disabled={isPending || (!isPublic && canManage)}
|
|
322
338
|
/>
|
|
323
339
|
|
|
340
|
+
{/* Public links unfurl into a playable video in Slack; surface that here
|
|
341
|
+
(and a connect link) instead of leaving it buried in Settings. */}
|
|
342
|
+
{isPublic ? <SlackShareHint canManage={canManage} /> : null}
|
|
343
|
+
|
|
324
344
|
<CopyField
|
|
325
345
|
label="Share with agents"
|
|
326
346
|
value={agentContextUrl}
|
|
@@ -389,10 +409,12 @@ function ClipsEmbedConfigurator({
|
|
|
389
409
|
recordingId,
|
|
390
410
|
sharesQuery,
|
|
391
411
|
canManage,
|
|
412
|
+
ownerViaId,
|
|
392
413
|
}: {
|
|
393
414
|
recordingId: string;
|
|
394
415
|
sharesQuery: SharesQuery;
|
|
395
416
|
canManage: boolean;
|
|
417
|
+
ownerViaId?: string;
|
|
396
418
|
}) {
|
|
397
419
|
const [autoplay, setAutoplay] = useState(false);
|
|
398
420
|
const [startMs, setStartMs] = useState(0);
|
|
@@ -416,8 +438,12 @@ function ClipsEmbedConfigurator({
|
|
|
416
438
|
if (autoplay) params.push("autoplay=1");
|
|
417
439
|
if (startMs > 0) params.push(`t=${Math.round(startMs / 1000)}`);
|
|
418
440
|
const qs = params.length ? `?${params.join("&")}` : "";
|
|
419
|
-
|
|
420
|
-
|
|
441
|
+
// Keep autoplay/t intact and also self-attribute the embed.
|
|
442
|
+
return withShareAttribution(
|
|
443
|
+
absoluteAppUrl(`/embed/${recordingId}${qs}`),
|
|
444
|
+
ownerViaId,
|
|
445
|
+
);
|
|
446
|
+
}, [recordingId, autoplay, startMs, ownerViaId]);
|
|
421
447
|
|
|
422
448
|
const code =
|
|
423
449
|
mode === "responsive"
|
|
@@ -21,6 +21,12 @@ export interface SignInPromptDialogProps {
|
|
|
21
21
|
* fires there and forwards to `returnTo` once the viewer is signed in.
|
|
22
22
|
*/
|
|
23
23
|
returnTo?: string;
|
|
24
|
+
/**
|
|
25
|
+
* Fired when the viewer activates the "Sign in" button, before navigation.
|
|
26
|
+
* Used by the public share page to emit the signin funnel event. Must not
|
|
27
|
+
* change navigation behavior.
|
|
28
|
+
*/
|
|
29
|
+
onSignIn?: () => void;
|
|
24
30
|
}
|
|
25
31
|
|
|
26
32
|
function buildSignInHref(returnTo: string | undefined): string {
|
|
@@ -34,6 +40,7 @@ export function SignInPromptDialog({
|
|
|
34
40
|
onOpenChange,
|
|
35
41
|
intent,
|
|
36
42
|
returnTo,
|
|
43
|
+
onSignIn,
|
|
37
44
|
}: SignInPromptDialogProps) {
|
|
38
45
|
return (
|
|
39
46
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
|
@@ -51,6 +58,7 @@ export function SignInPromptDialog({
|
|
|
51
58
|
</Button>
|
|
52
59
|
<Button
|
|
53
60
|
onClick={() => {
|
|
61
|
+
onSignIn?.();
|
|
54
62
|
window.location.href = buildSignInHref(returnTo);
|
|
55
63
|
}}
|
|
56
64
|
>
|
|
@@ -232,7 +232,10 @@ export const VideoPlayer = forwardRef<VideoPlayerHandle, VideoPlayerProps>(
|
|
|
232
232
|
const [currentMs, setCurrentMs] = useState(startMs ?? 0);
|
|
233
233
|
const [loomStartMs, setLoomStartMs] = useState<number | null>(null);
|
|
234
234
|
const [volume, setVolume] = useState(1);
|
|
235
|
-
|
|
235
|
+
// Autoplaying players (e.g. the Slack unfurl embed, `?autoplay=1`) must
|
|
236
|
+
// start muted or the browser blocks autoplay with a NotAllowedError. The
|
|
237
|
+
// share page (no autoplay) keeps full sound.
|
|
238
|
+
const [muted, setMuted] = useState(() => !!autoPlay);
|
|
236
239
|
const [speed, setSpeed] = useState(() =>
|
|
237
240
|
readPlaybackSpeedPreference(defaultSpeed),
|
|
238
241
|
);
|
|
@@ -354,18 +357,30 @@ export const VideoPlayer = forwardRef<VideoPlayerHandle, VideoPlayerProps>(
|
|
|
354
357
|
setIsPreparing(false);
|
|
355
358
|
}, []);
|
|
356
359
|
|
|
357
|
-
const rejectPlayAttempt = useCallback(
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
360
|
+
const rejectPlayAttempt = useCallback(
|
|
361
|
+
(attemptId: number, err: unknown) => {
|
|
362
|
+
if (attemptId !== playAttemptIdRef.current) return;
|
|
363
|
+
playAttemptPendingRef.current = false;
|
|
364
|
+
setIsPlayPending(false);
|
|
365
|
+
setIsBuffering(false);
|
|
366
|
+
|
|
367
|
+
const name = err instanceof DOMException ? err.name : "";
|
|
368
|
+
// AbortError: a newer load/seek superseded this play() — not a failure.
|
|
369
|
+
// NotAllowedError: the browser blocked autoplay because there was no
|
|
370
|
+
// user gesture (this is what happens inside Slack's cross-origin unfurl
|
|
371
|
+
// iframe). Both are expected — fall back to the click-to-play overlay
|
|
372
|
+
// instead of showing a scary "Could not start playback" message.
|
|
373
|
+
if (name === "AbortError" || name === "NotAllowedError") return;
|
|
374
|
+
|
|
375
|
+
console.warn("[clips] playback start failed", err);
|
|
376
|
+
reportPlaybackIssue("play-start-failed", err, videoRef.current, {
|
|
377
|
+
recordingId,
|
|
378
|
+
autoPlay: !!autoPlay,
|
|
379
|
+
});
|
|
380
|
+
setPlayError("Could not start playback. Try again.");
|
|
381
|
+
},
|
|
382
|
+
[autoPlay, recordingId],
|
|
383
|
+
);
|
|
369
384
|
|
|
370
385
|
const attachPlayPromise = useCallback(
|
|
371
386
|
(playPromise: Promise<void> | undefined, attemptId: number) => {
|
|
@@ -882,7 +897,10 @@ export const VideoPlayer = forwardRef<VideoPlayerHandle, VideoPlayerProps>(
|
|
|
882
897
|
<div
|
|
883
898
|
ref={containerRef}
|
|
884
899
|
className={cn(
|
|
885
|
-
|
|
900
|
+
// `@container` lets the center play button scale with the player
|
|
901
|
+
// width (see CenterPlaybackOverlay) so it isn't oversized inside
|
|
902
|
+
// small embeds like the Slack unfurl iframe.
|
|
903
|
+
"relative @container bg-black overflow-hidden select-none group",
|
|
886
904
|
theaterMode ? "fixed inset-0 z-40" : "rounded-xl",
|
|
887
905
|
className,
|
|
888
906
|
)}
|
|
@@ -910,12 +928,19 @@ export const VideoPlayer = forwardRef<VideoPlayerHandle, VideoPlayerProps>(
|
|
|
910
928
|
ref={videoRef}
|
|
911
929
|
src={activeVideoSrc}
|
|
912
930
|
poster={resolveLocalUrl(thumbnailUrl)}
|
|
913
|
-
crossOrigin
|
|
931
|
+
// `crossOrigin` is only needed so the owner's canvas thumbnail
|
|
932
|
+
// capture isn't tainted. For everyone else (viewers, and the Slack
|
|
933
|
+
// unfurl embed) it adds nothing — but if the player is ever framed
|
|
934
|
+
// into a sandboxed/opaque-origin context (Slack double-iframes the
|
|
935
|
+
// embed), the resulting CORS check on the media bytes fails and the
|
|
936
|
+
// video won't load. Scope it to owners so embeds load cleanly.
|
|
937
|
+
crossOrigin={role === "owner" ? "anonymous" : undefined}
|
|
914
938
|
className={cn(
|
|
915
939
|
"w-full h-full",
|
|
916
940
|
cover ? "object-cover" : "object-contain",
|
|
917
941
|
)}
|
|
918
942
|
autoPlay={autoPlay}
|
|
943
|
+
muted={muted}
|
|
919
944
|
playsInline
|
|
920
945
|
onLoadStart={() => {
|
|
921
946
|
setCanPlay(false);
|
|
@@ -1014,12 +1039,26 @@ export const VideoPlayer = forwardRef<VideoPlayerHandle, VideoPlayerProps>(
|
|
|
1014
1039
|
setIsBuffering(false);
|
|
1015
1040
|
onEnded?.();
|
|
1016
1041
|
}}
|
|
1017
|
-
onError={() => {
|
|
1042
|
+
onError={(e) => {
|
|
1018
1043
|
playAttemptPendingRef.current = false;
|
|
1019
1044
|
setIsPlayPending(false);
|
|
1020
1045
|
setIsBuffering(false);
|
|
1021
1046
|
setIsPreparing(false);
|
|
1022
|
-
|
|
1047
|
+
const desc = describeMediaError(e.currentTarget.error);
|
|
1048
|
+
reportPlaybackIssue(
|
|
1049
|
+
"media-load-failed",
|
|
1050
|
+
e.currentTarget.error,
|
|
1051
|
+
e.currentTarget,
|
|
1052
|
+
{
|
|
1053
|
+
recordingId,
|
|
1054
|
+
videoSrc: activeVideoSrc,
|
|
1055
|
+
},
|
|
1056
|
+
);
|
|
1057
|
+
setPlayError(
|
|
1058
|
+
desc
|
|
1059
|
+
? `Video could not be loaded (${desc.label}).`
|
|
1060
|
+
: "Video could not be loaded.",
|
|
1061
|
+
);
|
|
1023
1062
|
}}
|
|
1024
1063
|
onVolumeChange={(e) => {
|
|
1025
1064
|
setVolume(e.currentTarget.volume);
|
|
@@ -1039,7 +1078,17 @@ export const VideoPlayer = forwardRef<VideoPlayerHandle, VideoPlayerProps>(
|
|
|
1039
1078
|
durationMs={resolvedDurationMs}
|
|
1040
1079
|
speed={speed}
|
|
1041
1080
|
playError={playError}
|
|
1042
|
-
onPlay={
|
|
1081
|
+
onPlay={() => {
|
|
1082
|
+
// An explicit click means the user wants to watch with sound, so
|
|
1083
|
+
// undo the muted-autoplay default (see `muted` state) before we
|
|
1084
|
+
// start playback.
|
|
1085
|
+
const v = videoRef.current;
|
|
1086
|
+
if (v && v.muted) {
|
|
1087
|
+
v.muted = false;
|
|
1088
|
+
setMuted(false);
|
|
1089
|
+
}
|
|
1090
|
+
requestPlay();
|
|
1091
|
+
}}
|
|
1043
1092
|
onSpeedChange={applySpeed}
|
|
1044
1093
|
menuPortalContainer={fullscreenMenuContainer}
|
|
1045
1094
|
/>
|
|
@@ -1193,9 +1242,9 @@ function CenterPlaybackOverlay({
|
|
|
1193
1242
|
e.stopPropagation();
|
|
1194
1243
|
onPlay();
|
|
1195
1244
|
}}
|
|
1196
|
-
className="pointer-events-auto flex h-
|
|
1245
|
+
className="pointer-events-auto flex h-[clamp(3rem,13cqw,6rem)] w-[clamp(3rem,13cqw,6rem)] items-center justify-center rounded-full bg-white text-black shadow-2xl ring-1 ring-white/35 transition-transform duration-150 hover:scale-105 hover:bg-white focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-offset-2 focus-visible:ring-offset-black"
|
|
1197
1246
|
>
|
|
1198
|
-
<IconPlayerPlay className="ml-
|
|
1247
|
+
<IconPlayerPlay className="ml-[6%] h-[clamp(1.5rem,6.5cqw,3rem)] w-[clamp(1.5rem,6.5cqw,3rem)] fill-current" />
|
|
1199
1248
|
</button>
|
|
1200
1249
|
|
|
1201
1250
|
<div
|
|
@@ -1302,6 +1351,87 @@ function formatSpeedLabel(rate: number): string {
|
|
|
1302
1351
|
return `${Number.isInteger(rate) ? rate : rate.toFixed(1)}x`;
|
|
1303
1352
|
}
|
|
1304
1353
|
|
|
1354
|
+
/** Human-readable label for an HTMLMediaElement `error` (MediaError). */
|
|
1355
|
+
function describeMediaError(
|
|
1356
|
+
err: MediaError | null,
|
|
1357
|
+
): { code: number; label: string } | null {
|
|
1358
|
+
if (!err) return null;
|
|
1359
|
+
const labels: Record<number, string> = {
|
|
1360
|
+
1: "load aborted",
|
|
1361
|
+
2: "network error",
|
|
1362
|
+
3: "decode error",
|
|
1363
|
+
4: "format not supported",
|
|
1364
|
+
};
|
|
1365
|
+
return { code: err.code, label: labels[err.code] ?? "unknown error" };
|
|
1366
|
+
}
|
|
1367
|
+
|
|
1368
|
+
/**
|
|
1369
|
+
* Surface a playback failure to the console and Sentry with enough context to
|
|
1370
|
+
* debug it remotely — e.g. when a clip "could not be loaded" inside a Slack
|
|
1371
|
+
* unfurl where there's no visible console. Best-effort; never throws. Expected,
|
|
1372
|
+
* benign cases (AbortError / autoplay-blocked NotAllowedError) are filtered out
|
|
1373
|
+
* by the callers and never reach here.
|
|
1374
|
+
*/
|
|
1375
|
+
function reportPlaybackIssue(
|
|
1376
|
+
reason: string,
|
|
1377
|
+
err: unknown,
|
|
1378
|
+
video: HTMLVideoElement | null,
|
|
1379
|
+
extra: Record<string, unknown>,
|
|
1380
|
+
) {
|
|
1381
|
+
const mediaError =
|
|
1382
|
+
err && typeof err === "object" && "code" in err
|
|
1383
|
+
? (err as MediaError)
|
|
1384
|
+
: (video?.error ?? null);
|
|
1385
|
+
const name =
|
|
1386
|
+
err instanceof DOMException || err instanceof Error ? err.name : undefined;
|
|
1387
|
+
const message = err instanceof Error ? err.message : undefined;
|
|
1388
|
+
|
|
1389
|
+
let videoHost: string | undefined;
|
|
1390
|
+
try {
|
|
1391
|
+
if (video?.currentSrc) videoHost = new URL(video.currentSrc).host;
|
|
1392
|
+
} catch {
|
|
1393
|
+
// ignore unparseable src
|
|
1394
|
+
}
|
|
1395
|
+
let inIframe = false;
|
|
1396
|
+
try {
|
|
1397
|
+
inIframe = typeof window !== "undefined" && window.self !== window.top;
|
|
1398
|
+
} catch {
|
|
1399
|
+
inIframe = true;
|
|
1400
|
+
}
|
|
1401
|
+
|
|
1402
|
+
const detail = {
|
|
1403
|
+
...extra,
|
|
1404
|
+
errorName: name,
|
|
1405
|
+
errorMessage: message,
|
|
1406
|
+
mediaErrorCode: mediaError?.code,
|
|
1407
|
+
mediaErrorLabel: describeMediaError(mediaError)?.label,
|
|
1408
|
+
videoHost,
|
|
1409
|
+
inIframe,
|
|
1410
|
+
readyState: video?.readyState,
|
|
1411
|
+
networkState: video?.networkState,
|
|
1412
|
+
};
|
|
1413
|
+
console.warn(`[clips] playback issue: ${reason}`, detail);
|
|
1414
|
+
|
|
1415
|
+
try {
|
|
1416
|
+
const reportable =
|
|
1417
|
+
err instanceof Error
|
|
1418
|
+
? err
|
|
1419
|
+
: new Error(
|
|
1420
|
+
`clips playback ${reason}: ${message ?? name ?? describeMediaError(mediaError)?.label ?? "unknown"}`,
|
|
1421
|
+
);
|
|
1422
|
+
captureClientException(reportable, {
|
|
1423
|
+
tags: {
|
|
1424
|
+
area: "clips-player",
|
|
1425
|
+
playbackIssue: reason,
|
|
1426
|
+
inIframe: String(inIframe),
|
|
1427
|
+
},
|
|
1428
|
+
extra: detail,
|
|
1429
|
+
});
|
|
1430
|
+
} catch {
|
|
1431
|
+
// Diagnostics must never break playback UI.
|
|
1432
|
+
}
|
|
1433
|
+
}
|
|
1434
|
+
|
|
1305
1435
|
function formatWatchDuration(ms: number): string {
|
|
1306
1436
|
if (!Number.isFinite(ms) || ms <= 0) return "0 sec";
|
|
1307
1437
|
const totalSeconds = Math.max(1, Math.round(ms / 1000));
|
|
@@ -48,6 +48,10 @@ import {
|
|
|
48
48
|
type RecordingMode,
|
|
49
49
|
} from "./recorder-engine";
|
|
50
50
|
import type { CameraBubbleSize } from "./camera-bubble";
|
|
51
|
+
import {
|
|
52
|
+
loadRecorderPreferences,
|
|
53
|
+
saveRecorderPreferences,
|
|
54
|
+
} from "@/lib/recorder-preferences";
|
|
51
55
|
import { CameraVisualizer, type CameraTestStatus } from "./camera-visualizer";
|
|
52
56
|
import {
|
|
53
57
|
MicrophoneVisualizer,
|
|
@@ -184,11 +188,14 @@ export function PreRecordPanel({
|
|
|
184
188
|
}: PreRecordPanelProps) {
|
|
185
189
|
const fileInputRef = useRef<HTMLInputElement>(null);
|
|
186
190
|
const loomInputRef = useRef<HTMLInputElement>(null);
|
|
191
|
+
// Saved selections from the last visit. A `?mode=`/`?surface=` deep link
|
|
192
|
+
// (initialMode/initialDisplaySurface) still takes precedence over them.
|
|
193
|
+
const savedPrefs = useMemo(() => loadRecorderPreferences(), []);
|
|
187
194
|
const [mode, setMode] = useState<RecordingMode>(
|
|
188
|
-
() => initialMode ?? "screen+camera",
|
|
195
|
+
() => initialMode ?? savedPrefs.mode ?? "screen+camera",
|
|
189
196
|
);
|
|
190
197
|
const [displaySurface, setDisplaySurface] = useState<DisplaySurface>(
|
|
191
|
-
() => initialDisplaySurface ?? "window",
|
|
198
|
+
() => initialDisplaySurface ?? savedPrefs.displaySurface ?? "window",
|
|
192
199
|
);
|
|
193
200
|
const [sourceOpen, setSourceOpen] = useState(false);
|
|
194
201
|
const [deviceSettingsOpen, setDeviceSettingsOpen] = useState(false);
|
|
@@ -197,8 +204,12 @@ export function PreRecordPanel({
|
|
|
197
204
|
const [loomError, setLoomError] = useState<string | null>(null);
|
|
198
205
|
const [mics, setMics] = useState<MediaDeviceInfo[]>([]);
|
|
199
206
|
const [cameras, setCameras] = useState<MediaDeviceInfo[]>([]);
|
|
200
|
-
const [micId, setMicId] = useState<string>(
|
|
201
|
-
|
|
207
|
+
const [micId, setMicId] = useState<string>(
|
|
208
|
+
() => savedPrefs.micId ?? "default",
|
|
209
|
+
);
|
|
210
|
+
const [cameraId, setCameraId] = useState<string>(
|
|
211
|
+
() => savedPrefs.cameraId ?? "default",
|
|
212
|
+
);
|
|
202
213
|
const [enumError, setEnumError] = useState<string | null>(null);
|
|
203
214
|
const [micAccessStatus, setMicAccessStatus] =
|
|
204
215
|
useState<DeviceAccessStatus>("idle");
|
|
@@ -274,13 +285,57 @@ export function PreRecordPanel({
|
|
|
274
285
|
[mics],
|
|
275
286
|
);
|
|
276
287
|
|
|
288
|
+
// Reset to "default" only once a populated list genuinely excludes the saved
|
|
289
|
+
// device — an empty list means "not enumerated yet", and resetting then would
|
|
290
|
+
// wipe a restored preference before devices load.
|
|
277
291
|
useEffect(() => {
|
|
278
292
|
if (micId === "default" || micId === NO_MIC_DEVICE_ID) return;
|
|
279
|
-
if (!mics.some((mic) => mic.deviceId === micId)) {
|
|
293
|
+
if (mics.length > 0 && !mics.some((mic) => mic.deviceId === micId)) {
|
|
280
294
|
setMicId("default");
|
|
281
295
|
}
|
|
282
296
|
}, [micId, mics]);
|
|
283
297
|
|
|
298
|
+
// Same guard for cameras. Not persisted, so a temporarily missing device
|
|
299
|
+
// doesn't erase the saved choice.
|
|
300
|
+
useEffect(() => {
|
|
301
|
+
if (cameraId === "default" || cameraId === NO_CAMERA_DEVICE_ID) return;
|
|
302
|
+
if (
|
|
303
|
+
cameras.length > 0 &&
|
|
304
|
+
!cameras.some((camera) => camera.deviceId === cameraId)
|
|
305
|
+
) {
|
|
306
|
+
setCameraId("default");
|
|
307
|
+
}
|
|
308
|
+
}, [cameraId, cameras]);
|
|
309
|
+
|
|
310
|
+
// Camera-only mode needs a camera — coerce a restored "off" sentinel to
|
|
311
|
+
// "default" so Start doesn't forward it as an exact deviceId. This is the
|
|
312
|
+
// single owner of that coercion: it covers both the mode-button click and the
|
|
313
|
+
// ?mode=camera deep-link/restore path.
|
|
314
|
+
useEffect(() => {
|
|
315
|
+
if (mode === "camera" && cameraId === NO_CAMERA_DEVICE_ID) {
|
|
316
|
+
setCameraId("default");
|
|
317
|
+
}
|
|
318
|
+
}, [mode, cameraId]);
|
|
319
|
+
|
|
320
|
+
// Persist deliberate picks only (not the resets above), so an unavailable
|
|
321
|
+
// device on load can't clobber the stored preference.
|
|
322
|
+
const chooseMode = useCallback((value: RecordingMode) => {
|
|
323
|
+
setMode(value);
|
|
324
|
+
saveRecorderPreferences({ mode: value });
|
|
325
|
+
}, []);
|
|
326
|
+
const chooseDisplaySurface = useCallback((value: DisplaySurface) => {
|
|
327
|
+
setDisplaySurface(value);
|
|
328
|
+
saveRecorderPreferences({ displaySurface: value });
|
|
329
|
+
}, []);
|
|
330
|
+
const chooseMic = useCallback((value: string) => {
|
|
331
|
+
setMicId(value);
|
|
332
|
+
saveRecorderPreferences({ micId: value });
|
|
333
|
+
}, []);
|
|
334
|
+
const chooseCamera = useCallback((value: string) => {
|
|
335
|
+
setCameraId(value);
|
|
336
|
+
saveRecorderPreferences({ cameraId: value });
|
|
337
|
+
}, []);
|
|
338
|
+
|
|
284
339
|
const requestMicrophoneChoices = useCallback(async () => {
|
|
285
340
|
if (!navigator.mediaDevices?.getUserMedia) {
|
|
286
341
|
setMicAccessStatus("error");
|
|
@@ -376,9 +431,9 @@ export function PreRecordPanel({
|
|
|
376
431
|
void requestMicrophoneChoices();
|
|
377
432
|
return;
|
|
378
433
|
}
|
|
379
|
-
|
|
434
|
+
chooseMic(value);
|
|
380
435
|
},
|
|
381
|
-
[requestMicrophoneChoices],
|
|
436
|
+
[requestMicrophoneChoices, chooseMic],
|
|
382
437
|
);
|
|
383
438
|
|
|
384
439
|
const handleMicSignalChange = useCallback((hasSignal: boolean) => {
|
|
@@ -529,15 +584,10 @@ export function PreRecordPanel({
|
|
|
529
584
|
<button
|
|
530
585
|
key={opt.value}
|
|
531
586
|
type="button"
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
cameraId === NO_CAMERA_DEVICE_ID
|
|
537
|
-
) {
|
|
538
|
-
setCameraId("default");
|
|
539
|
-
}
|
|
540
|
-
}}
|
|
587
|
+
// Camera-only mode needs a camera; the [mode, cameraId] effect
|
|
588
|
+
// below coerces a restored "off" sentinel to "default" for both
|
|
589
|
+
// this click and the ?mode=camera deep-link path.
|
|
590
|
+
onClick={() => chooseMode(opt.value)}
|
|
541
591
|
className={cn(
|
|
542
592
|
"flex min-h-20 min-w-0 flex-col justify-between rounded-xl border p-3 text-left transition-colors",
|
|
543
593
|
active
|
|
@@ -605,7 +655,7 @@ export function PreRecordPanel({
|
|
|
605
655
|
<button
|
|
606
656
|
key={opt.value}
|
|
607
657
|
type="button"
|
|
608
|
-
onClick={() =>
|
|
658
|
+
onClick={() => chooseDisplaySurface(opt.value)}
|
|
609
659
|
className={cn(
|
|
610
660
|
"flex min-h-[76px] flex-col rounded-lg border p-2 text-left transition-colors",
|
|
611
661
|
active
|
|
@@ -703,7 +753,7 @@ export function PreRecordPanel({
|
|
|
703
753
|
<Switch
|
|
704
754
|
checked={audioEnabled}
|
|
705
755
|
onCheckedChange={(checked) =>
|
|
706
|
-
|
|
756
|
+
chooseMic(checked ? "default" : NO_MIC_DEVICE_ID)
|
|
707
757
|
}
|
|
708
758
|
disabled={busy}
|
|
709
759
|
aria-label="Include audio in this recording"
|
|
@@ -740,7 +790,7 @@ export function PreRecordPanel({
|
|
|
740
790
|
</div>
|
|
741
791
|
<Select
|
|
742
792
|
value={cameraId}
|
|
743
|
-
onValueChange={
|
|
793
|
+
onValueChange={chooseCamera}
|
|
744
794
|
disabled={!needsCamera}
|
|
745
795
|
>
|
|
746
796
|
<SelectTrigger className="h-9 min-w-0 flex-1 border-0 bg-transparent px-2 shadow-none hover:bg-muted/45 focus:ring-0 focus:ring-offset-0 disabled:opacity-60">
|
|
@@ -762,7 +812,9 @@ export function PreRecordPanel({
|
|
|
762
812
|
<Switch
|
|
763
813
|
checked={needsCamera}
|
|
764
814
|
onCheckedChange={(checked) =>
|
|
765
|
-
|
|
815
|
+
chooseCamera(
|
|
816
|
+
checked ? "default" : NO_CAMERA_DEVICE_ID,
|
|
817
|
+
)
|
|
766
818
|
}
|
|
767
819
|
disabled={busy}
|
|
768
820
|
aria-label="Include camera in this recording"
|