@agent-native/core 0.69.0 → 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 +48 -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/mcp/build-server.ts +1 -1
- package/corpus/core/src/mcp/embed-app.ts +112 -6
- 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 +7 -11
- package/corpus/templates/clips/app/components/recorder/recorder-engine.ts +31 -0
- package/corpus/templates/clips/app/components/sharing/slack-share-hint.tsx +71 -0
- package/corpus/templates/clips/app/root.tsx +7 -1
- package/corpus/templates/clips/app/routes/_app.settings._index.tsx +15 -1
- package/corpus/templates/clips/app/routes/record.tsx +78 -5
- package/corpus/templates/clips/app/routes/share.$shareId.tsx +86 -5
- package/corpus/templates/clips/changelog/2026-06-23-clips-shared-in-slack-no-longer-show-a-could-not-start-playb.md +6 -0
- package/corpus/templates/clips/changelog/2026-06-23-made-the-play-button-on-shared-and-embedded-clips-scale-with.md +6 -0
- package/corpus/templates/clips/changelog/2026-06-23-public-clip-share-links-now-show-whether-they-ll-play-inline.md +6 -0
- package/corpus/templates/clips/changelog/2026-06-23-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/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/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/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));
|
|
@@ -308,8 +308,9 @@ export function PreRecordPanel({
|
|
|
308
308
|
}, [cameraId, cameras]);
|
|
309
309
|
|
|
310
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
|
|
312
|
-
//
|
|
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.
|
|
313
314
|
useEffect(() => {
|
|
314
315
|
if (mode === "camera" && cameraId === NO_CAMERA_DEVICE_ID) {
|
|
315
316
|
setCameraId("default");
|
|
@@ -583,15 +584,10 @@ export function PreRecordPanel({
|
|
|
583
584
|
<button
|
|
584
585
|
key={opt.value}
|
|
585
586
|
type="button"
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
cameraId === NO_CAMERA_DEVICE_ID
|
|
591
|
-
) {
|
|
592
|
-
chooseCamera("default");
|
|
593
|
-
}
|
|
594
|
-
}}
|
|
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)}
|
|
595
591
|
className={cn(
|
|
596
592
|
"flex min-h-20 min-w-0 flex-col justify-between rounded-xl border p-3 text-left transition-colors",
|
|
597
593
|
active
|
|
@@ -420,6 +420,13 @@ export class RecorderEngine {
|
|
|
420
420
|
*/
|
|
421
421
|
private cameraDisconnectNotified = false;
|
|
422
422
|
private micDisconnectNotified = false;
|
|
423
|
+
/**
|
|
424
|
+
* Set when a stale/unavailable `micDeviceId` forces a fallback to the system
|
|
425
|
+
* default mic during `acquire()`. The recording then runs on the default
|
|
426
|
+
* device, so live transcription (which can only ever use the default mic) is
|
|
427
|
+
* safe to start even though a specific mic was originally requested.
|
|
428
|
+
*/
|
|
429
|
+
private micFellBackToDefault = false;
|
|
423
430
|
|
|
424
431
|
private state: RecorderState = "idle";
|
|
425
432
|
|
|
@@ -448,10 +455,30 @@ export class RecorderEngine {
|
|
|
448
455
|
return this.cameraStream;
|
|
449
456
|
}
|
|
450
457
|
|
|
458
|
+
/**
|
|
459
|
+
* True when `acquire()` had to fall back from the requested `micDeviceId` to
|
|
460
|
+
* the system default mic. Lets the UI start live transcription for this
|
|
461
|
+
* session even though a specific mic was originally selected.
|
|
462
|
+
*/
|
|
463
|
+
didMicFallBackToDefault(): boolean {
|
|
464
|
+
return this.micFellBackToDefault;
|
|
465
|
+
}
|
|
466
|
+
|
|
451
467
|
getPreviewStream(): MediaStream | null {
|
|
452
468
|
return this.previewStream;
|
|
453
469
|
}
|
|
454
470
|
|
|
471
|
+
/**
|
|
472
|
+
* The composited screen+camera canvas stream that actually gets recorded
|
|
473
|
+
* (screen with the camera bubble drawn in), or `null` for screen-only /
|
|
474
|
+
* camera-only modes where the visible preview already matches the recording.
|
|
475
|
+
* Used to grab a thumbnail that includes the presenter's camera — the raw
|
|
476
|
+
* preview stream in screen+camera mode is screen-only and has no face.
|
|
477
|
+
*/
|
|
478
|
+
getCompositeStream(): MediaStream | null {
|
|
479
|
+
return this.cameraComposite?.stream ?? null;
|
|
480
|
+
}
|
|
481
|
+
|
|
455
482
|
getElapsedMs(): number {
|
|
456
483
|
if (this.startedAtMs === null) return 0;
|
|
457
484
|
const now = performance.now();
|
|
@@ -515,6 +542,7 @@ export class RecorderEngine {
|
|
|
515
542
|
const wantsCamera =
|
|
516
543
|
this.opts.mode === "camera" || this.opts.mode === "screen+camera";
|
|
517
544
|
const wantsMic = this.opts.micDeviceId !== NO_MIC_DEVICE_ID;
|
|
545
|
+
this.micFellBackToDefault = false;
|
|
518
546
|
|
|
519
547
|
try {
|
|
520
548
|
if (!isBrowserSecureContext()) {
|
|
@@ -634,6 +662,9 @@ export class RecorderEngine {
|
|
|
634
662
|
audio: voiceFocusedAudioConstraints(),
|
|
635
663
|
video: false,
|
|
636
664
|
});
|
|
665
|
+
// Recording is now on the system default mic, so live
|
|
666
|
+
// transcription can run for this session after all.
|
|
667
|
+
this.micFellBackToDefault = true;
|
|
637
668
|
} catch (retryErr) {
|
|
638
669
|
throw this.friendlyError(retryErr, "microphone");
|
|
639
670
|
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { IconBrandSlack } from "@tabler/icons-react";
|
|
2
|
+
import { appPath, useActionQuery } from "@agent-native/core/client";
|
|
3
|
+
|
|
4
|
+
interface SlackInstallation {
|
|
5
|
+
status: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
interface SlackInstallationsResponse {
|
|
9
|
+
oauthConfigured: boolean;
|
|
10
|
+
installations: SlackInstallation[];
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Contextual nudge shown in the share popover's Link tab for public clips.
|
|
15
|
+
*
|
|
16
|
+
* A public Clips link unfurls into a playable video when pasted in a connected
|
|
17
|
+
* Slack workspace, but that integration is otherwise only discoverable in
|
|
18
|
+
* Settings. When a workspace is connected we confirm the payoff; when none is,
|
|
19
|
+
* clip owners/admins get a one-click link to connect one in Settings.
|
|
20
|
+
*
|
|
21
|
+
* Only rendered for public clips (the caller gates on `isPublic`), so the
|
|
22
|
+
* `list-slack-installations` query never runs for private shares.
|
|
23
|
+
*/
|
|
24
|
+
export function SlackShareHint({ canManage }: { canManage: boolean }) {
|
|
25
|
+
const slack = useActionQuery<SlackInstallationsResponse>(
|
|
26
|
+
"list-slack-installations",
|
|
27
|
+
undefined,
|
|
28
|
+
{ retry: false },
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
const data = slack.data;
|
|
32
|
+
const connected =
|
|
33
|
+
data?.installations?.some((i) => i.status === "connected") ?? false;
|
|
34
|
+
|
|
35
|
+
if (connected) {
|
|
36
|
+
return (
|
|
37
|
+
<div className="flex items-center gap-2.5 rounded-md border border-border bg-muted/40 px-3 py-2">
|
|
38
|
+
<IconBrandSlack className="h-4 w-4 shrink-0 text-muted-foreground" />
|
|
39
|
+
<div className="min-w-0">
|
|
40
|
+
<div className="text-xs font-medium">Plays inline in Slack</div>
|
|
41
|
+
<p className="text-[11px] text-muted-foreground">
|
|
42
|
+
Paste this link in any connected workspace to play it inline.
|
|
43
|
+
</p>
|
|
44
|
+
</div>
|
|
45
|
+
</div>
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// No workspace connected yet. Connecting needs the deployment's Slack OAuth
|
|
50
|
+
// credentials, so only point managers at Settings when it's actually set up —
|
|
51
|
+
// otherwise the Connect button there is disabled and the link is a dead end.
|
|
52
|
+
// While the query is loading or errored, `data` is undefined and we render
|
|
53
|
+
// nothing rather than guess at the workspace state.
|
|
54
|
+
if (!data || !canManage || !data.oauthConfigured) return null;
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
<a
|
|
58
|
+
href={appPath("/settings#slack")}
|
|
59
|
+
className="flex items-center gap-2.5 rounded-md border border-border px-3 py-2 transition-colors hover:bg-muted/50"
|
|
60
|
+
>
|
|
61
|
+
<IconBrandSlack className="h-4 w-4 shrink-0 text-muted-foreground" />
|
|
62
|
+
<div className="min-w-0 flex-1">
|
|
63
|
+
<div className="text-xs font-medium">Make it play inline in Slack</div>
|
|
64
|
+
<p className="text-[11px] text-muted-foreground">
|
|
65
|
+
Connect a workspace so this link unfurls as a video.
|
|
66
|
+
</p>
|
|
67
|
+
</div>
|
|
68
|
+
<span className="shrink-0 text-xs font-medium text-primary">Connect</span>
|
|
69
|
+
</a>
|
|
70
|
+
);
|
|
71
|
+
}
|
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
} from "@agent-native/core/client";
|
|
22
22
|
import { IconCheck, IconSun, IconMoon } from "@tabler/icons-react";
|
|
23
23
|
import { useTheme } from "next-themes";
|
|
24
|
+
import changelog from "../CHANGELOG.md?raw";
|
|
24
25
|
import { Toaster } from "@/components/ui/sonner";
|
|
25
26
|
import { Button } from "@/components/ui/button";
|
|
26
27
|
import {
|
|
@@ -258,7 +259,12 @@ function AppContent() {
|
|
|
258
259
|
{standalonePublic ? null : <DbSyncSetup />}
|
|
259
260
|
{standalonePublic ? null : <ClipsExtensionAuthBridge />}
|
|
260
261
|
{standalonePublic ? null : (
|
|
261
|
-
<CommandMenu
|
|
262
|
+
<CommandMenu
|
|
263
|
+
open={cmdkOpen}
|
|
264
|
+
onOpenChange={setCmdkOpen}
|
|
265
|
+
changelog={changelog}
|
|
266
|
+
changelogKey="clips"
|
|
267
|
+
>
|
|
262
268
|
<CommandMenu.Group heading="Actions">
|
|
263
269
|
<CommandMenu.Item onSelect={() => {}}>Search</CommandMenu.Item>
|
|
264
270
|
</CommandMenu.Group>
|