@agent-native/core 0.178.0 → 0.178.1-nightly-20260909221943

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.
Files changed (88) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/clips/.agents/skills/dictate/SKILL.md +1 -0
  3. package/corpus/templates/clips/DESIGN.md +10 -7
  4. package/corpus/templates/clips/actions/list-organization-state.ts +56 -3
  5. package/corpus/templates/clips/actions/list-recordings.ts +2 -0
  6. package/corpus/templates/clips/actions/search-dictations.ts +81 -0
  7. package/corpus/templates/clips/app/components/agent-destination-logos.tsx +50 -0
  8. package/corpus/templates/clips/app/components/bug-report/bug-report-dialog.tsx +60 -0
  9. package/corpus/templates/clips/app/components/bug-report/bug-report-form.tsx +206 -0
  10. package/corpus/templates/clips/app/components/clips-command-menu.tsx +653 -0
  11. package/corpus/templates/clips/app/components/library/empty-state.tsx +52 -17
  12. package/corpus/templates/clips/app/components/library/folder-card.tsx +34 -0
  13. package/corpus/templates/clips/app/components/library/folder-tree.tsx +109 -64
  14. package/corpus/templates/clips/app/components/library/library-action-hrefs.ts +27 -0
  15. package/corpus/templates/clips/app/components/library/library-grid.tsx +295 -103
  16. package/corpus/templates/clips/app/components/library/library-layout.tsx +381 -203
  17. package/corpus/templates/clips/app/components/library/library-primary-actions.tsx +48 -0
  18. package/corpus/templates/clips/app/components/library/page-header.tsx +48 -8
  19. package/corpus/templates/clips/app/components/library/recording-card.tsx +58 -55
  20. package/corpus/templates/clips/app/components/library/search-bar.tsx +5 -19
  21. package/corpus/templates/clips/app/components/library/sidebar-feedback-button.tsx +48 -0
  22. package/corpus/templates/clips/app/components/player/comments-panel.tsx +2 -2
  23. package/corpus/templates/clips/app/components/player/share-dialog.tsx +344 -260
  24. package/corpus/templates/clips/app/components/player/viewer-controls.tsx +2 -2
  25. package/corpus/templates/clips/app/components/recorder/camera-visualizer.tsx +0 -33
  26. package/corpus/templates/clips/app/components/sharing/share-ui.tsx +5 -1
  27. package/corpus/templates/clips/app/components/ui/tabs.tsx +1 -1
  28. package/corpus/templates/clips/app/hooks/use-command-search.ts +53 -0
  29. package/corpus/templates/clips/app/hooks/use-navigation-state.ts +32 -13
  30. package/corpus/templates/clips/app/hooks/use-upload-video-picker.tsx +1 -0
  31. package/corpus/templates/clips/app/i18n/en-US.ts +7 -0
  32. package/corpus/templates/clips/app/lib/agent-share.ts +16 -0
  33. package/corpus/templates/clips/app/lib/command-events.ts +7 -0
  34. package/corpus/templates/clips/app/root.tsx +6 -82
  35. package/corpus/templates/clips/app/routes/_app.dictate.tsx +25 -5
  36. package/corpus/templates/clips/app/routes/_app.library._index.tsx +2 -30
  37. package/corpus/templates/clips/app/routes/_app.library.folder.$folderId.tsx +13 -2
  38. package/corpus/templates/clips/app/routes/_app.meetings._index.tsx +4 -17
  39. package/corpus/templates/clips/app/routes/_app.r.$recordingId.tsx +111 -114
  40. package/corpus/templates/clips/app/routes/_app.spaces.$spaceId.folder.$folderId.tsx +25 -2
  41. package/corpus/templates/clips/app/routes/_app.spaces.$spaceId.tsx +14 -61
  42. package/corpus/templates/clips/app/routes/_app.spaces._index.tsx +34 -24
  43. package/corpus/templates/clips/app/routes/_app.trash.tsx +1 -1
  44. package/corpus/templates/clips/app/routes/_app.tsx +2 -4
  45. package/corpus/templates/clips/app/routes/bug-report.tsx +11 -189
  46. package/corpus/templates/clips/app/routes/share.$shareId.tsx +148 -123
  47. package/corpus/templates/clips/changelog/2026-09-08-comments-and-desktop-window-controls.md +6 -0
  48. package/corpus/templates/clips/changelog/2026-09-08-global-command-search.md +6 -0
  49. package/corpus/templates/clips/changelog/2026-09-09-agent-sidebar-fits-below-toolbar.md +6 -0
  50. package/corpus/templates/clips/changelog/2026-09-09-desktop-switches-stay-clear-in-dark-mode.md +6 -0
  51. package/corpus/templates/clips/changelog/2026-09-09-recording-agent-uses-global-panel.md +6 -0
  52. package/corpus/templates/clips/desktop/src/app.tsx +2 -2
  53. package/corpus/templates/clips/desktop/src/components/MediaDeviceRow.tsx +5 -3
  54. package/corpus/templates/clips/desktop/src/components/SourceRow.tsx +1 -0
  55. package/corpus/templates/clips/desktop/src/components/Switch.tsx +2 -5
  56. package/corpus/templates/clips/desktop/src/components/ui/switch.tsx +21 -3
  57. package/corpus/templates/clips/desktop/src/hooks/useMediaDevices.ts +72 -52
  58. package/corpus/templates/clips/desktop/src/styles.css +18 -19
  59. package/corpus/templates/clips/desktop/src/tailwind.css +3 -0
  60. package/corpus/templates/clips/desktop/src-tauri/src/lib.rs +18 -10
  61. package/corpus/templates/clips/desktop/src-tauri/src/util.rs +3 -5
  62. package/corpus/templates/design/.agents/skills/export-handoff/SKILL.md +14 -3
  63. package/corpus/templates/design/AGENTS.md +2 -1
  64. package/corpus/templates/design/actions/export-png.ts +119 -0
  65. package/corpus/templates/design/actions/take-design-screenshot.ts +47 -13
  66. package/corpus/templates/design/server/plugins/agent-chat.ts +3 -1
  67. package/corpus/templates/factory/actions/dispatch-factory-item.ts +51 -22
  68. package/corpus/templates/factory/actions/list-triage-items.ts +21 -11
  69. package/corpus/templates/factory/actions/poll-slack-channel.ts +7 -2
  70. package/corpus/templates/factory/changelog/2026-09-09-slack-feedback-keeps-claimed-markers-after-poll.md +6 -0
  71. package/corpus/templates/factory/changelog/2026-09-09-slack-feedback-preserves-started-claimed-threads.md +6 -0
  72. package/corpus/templates/factory/changelog/2026-09-09-slack-feedback-skips-claimed-parents.md +6 -0
  73. package/corpus/templates/factory/server/lib/factory-automation-config.ts +1 -1
  74. package/corpus/templates/factory/server/lib/slack-feedback-prompt.ts +47 -0
  75. package/corpus/templates/factory/server/plugins/agent-chat.ts +5 -2
  76. package/corpus/templates/factory/server/plugins/factory-scheduler-job.ts +5 -4
  77. package/corpus/templates/factory/server/triage/review-skill-alignment.ts +12 -7
  78. package/corpus/templates/factory/server/triage/slack-review-window.ts +39 -0
  79. package/dist/client/CommandMenu.js +13 -1
  80. package/dist/client/analytics.js +10 -0
  81. package/dist/collab/routes.d.ts +1 -1
  82. package/dist/file-upload/actions/upload-image.d.ts +1 -1
  83. package/dist/notifications/routes.d.ts +2 -2
  84. package/dist/observability/routes.d.ts +5 -5
  85. package/dist/vite/client.js +25 -7
  86. package/dist/vite/sentry-source-maps.d.ts +12 -0
  87. package/dist/vite/sentry-source-maps.js +105 -0
  88. package/package.json +2 -1
@@ -0,0 +1,653 @@
1
+ import { useT } from "@agent-native/core/client/i18n";
2
+ import {
3
+ CommandMenu,
4
+ useCommandMenuShortcut,
5
+ } from "@agent-native/core/client/navigation";
6
+ import { useOrgRole } from "@agent-native/core/client/org";
7
+ import {
8
+ AGENT_SIDEBAR_QUERY_PARAM,
9
+ AGENT_SIDEBAR_QUERY_VALUE_OPEN,
10
+ docsUrl,
11
+ } from "@agent-native/core/shared";
12
+ import {
13
+ IconArchive,
14
+ IconCalendar,
15
+ IconFileText,
16
+ IconFolder,
17
+ IconFolderPlus,
18
+ IconHierarchy2,
19
+ IconMessage,
20
+ IconMicrophone2,
21
+ IconMoon,
22
+ IconSearch,
23
+ IconSettings,
24
+ IconSun,
25
+ IconTrash,
26
+ IconUsersGroup,
27
+ IconVideo,
28
+ IconVideoPlus,
29
+ } from "@tabler/icons-react";
30
+ import { useTheme } from "next-themes";
31
+ import { type ComponentProps, useCallback, useMemo } from "react";
32
+ import { useLocation, useNavigate } from "react-router";
33
+
34
+ import {
35
+ useDictationCommandSearch,
36
+ useMeetingCommandSearch,
37
+ type DictationCommandSearchHit,
38
+ type MeetingCommandSearchHit,
39
+ } from "@/hooks/use-command-search";
40
+ import {
41
+ useFolders,
42
+ useOrganizations,
43
+ useRecordingSearch,
44
+ useSpaces,
45
+ type SearchHit,
46
+ } from "@/hooks/use-library";
47
+ import {
48
+ OPEN_CREATE_FOLDER_EVENT,
49
+ OPEN_CREATE_SPACE_EVENT,
50
+ openBugReportDialog,
51
+ } from "@/lib/command-events";
52
+ import { SEARCH_FOCUS_PATH } from "@/lib/search-focus";
53
+
54
+ import changelog from "../../CHANGELOG.md?raw";
55
+
56
+ interface ClipsCommandMenuProps {
57
+ open: boolean;
58
+ onOpenChange: (open: boolean) => void;
59
+ }
60
+
61
+ type CommandRouteContext =
62
+ | { kind: "recording"; recordingId: string }
63
+ | { kind: "meeting"; meetingId: string }
64
+ | { kind: "folder"; folderId: string; spaceId?: string }
65
+ | { kind: "space"; spaceId: string }
66
+ | {
67
+ kind:
68
+ | "library"
69
+ | "shared"
70
+ | "spaces"
71
+ | "meetings"
72
+ | "dictate"
73
+ | "archive"
74
+ | "trash"
75
+ | "settings"
76
+ | "record"
77
+ | "other";
78
+ };
79
+
80
+ function commandRouteContext(pathname: string): CommandRouteContext {
81
+ const recording = pathname.match(/^\/r\/([^/]+)\/?$/);
82
+ if (recording) return { kind: "recording", recordingId: recording[1] };
83
+
84
+ const spaceFolder = pathname.match(/^\/spaces\/([^/]+)\/folder\/([^/]+)\/?$/);
85
+ if (spaceFolder) {
86
+ return {
87
+ kind: "folder",
88
+ spaceId: spaceFolder[1],
89
+ folderId: spaceFolder[2],
90
+ };
91
+ }
92
+
93
+ const libraryFolder = pathname.match(/^\/library\/folder\/([^/]+)\/?$/);
94
+ if (libraryFolder) return { kind: "folder", folderId: libraryFolder[1] };
95
+
96
+ const space = pathname.match(/^\/spaces\/([^/]+)\/?$/);
97
+ if (space) return { kind: "space", spaceId: space[1] };
98
+
99
+ if (pathname === "/library") return { kind: "library" };
100
+ if (pathname === "/shared") return { kind: "shared" };
101
+ if (pathname === "/spaces") return { kind: "spaces" };
102
+ if (pathname === "/meetings") return { kind: "meetings" };
103
+ if (pathname.startsWith("/meetings/")) {
104
+ return { kind: "meeting", meetingId: pathname.slice("/meetings/".length) };
105
+ }
106
+ if (pathname === "/dictate") return { kind: "dictate" };
107
+ if (pathname === "/archive") return { kind: "archive" };
108
+ if (pathname === "/trash") return { kind: "trash" };
109
+ if (pathname === "/record") return { kind: "record" };
110
+ if (pathname.startsWith("/settings")) return { kind: "settings" };
111
+ return { kind: "other" };
112
+ }
113
+
114
+ function withQuery(pathname: string, entries: Record<string, string>) {
115
+ const params = new URLSearchParams(entries);
116
+ const query = params.toString();
117
+ return query ? `${pathname}?${query}` : pathname;
118
+ }
119
+
120
+ function openCreateFolder() {
121
+ window.dispatchEvent(new Event(OPEN_CREATE_FOLDER_EVENT));
122
+ }
123
+
124
+ function openCreateSpace() {
125
+ window.dispatchEvent(new Event(OPEN_CREATE_SPACE_EVENT));
126
+ }
127
+
128
+ function formatSearchSnippet(text: string | null | undefined) {
129
+ return text?.replace(/\s+/g, " ").trim() || null;
130
+ }
131
+
132
+ function RecordingSearchResults({
133
+ results,
134
+ navigate,
135
+ t,
136
+ }: {
137
+ results: SearchHit[];
138
+ navigate: ReturnType<typeof useNavigate>;
139
+ t: ReturnType<typeof useT>;
140
+ }) {
141
+ if (results.length === 0) return null;
142
+ return (
143
+ <CommandMenu.Group heading={t("navigation.recordings")}>
144
+ {results.slice(0, 8).map((hit) => (
145
+ <CommandMenu.Item
146
+ key={`recording:${hit.id}:${hit.matchPanel ?? "metadata"}`}
147
+ onSelect={() => {
148
+ const params: Record<string, string> = {};
149
+ if (hit.matchPanel) params.panel = hit.matchPanel;
150
+ if (
151
+ typeof hit.matchMs === "number" &&
152
+ Number.isFinite(hit.matchMs)
153
+ ) {
154
+ params.t = Math.max(0, Math.floor(hit.matchMs / 1000)).toString();
155
+ }
156
+ void navigate(withQuery(`/r/${hit.id}`, params));
157
+ }}
158
+ keywords={[
159
+ hit.title,
160
+ hit.description,
161
+ hit.snippet ?? "",
162
+ "recording",
163
+ "clip",
164
+ ]}
165
+ className="items-start py-2"
166
+ >
167
+ <IconVideo className="mt-0.5 size-4 shrink-0 text-muted-foreground" />
168
+ <span className="min-w-0 flex-1">
169
+ <span className="block truncate font-medium">{hit.title}</span>
170
+ {formatSearchSnippet(hit.snippet ?? hit.description) ? (
171
+ <span className="mt-0.5 block line-clamp-2 text-xs leading-snug text-muted-foreground">
172
+ {formatSearchSnippet(hit.snippet ?? hit.description)}
173
+ </span>
174
+ ) : null}
175
+ </span>
176
+ {hit.matchPanel ? (
177
+ <span className="shrink-0 text-xs text-muted-foreground">
178
+ {hit.matchPanel === "comments"
179
+ ? t("sharePage.comments")
180
+ : t("recordingPage.transcript")}
181
+ </span>
182
+ ) : null}
183
+ </CommandMenu.Item>
184
+ ))}
185
+ </CommandMenu.Group>
186
+ );
187
+ }
188
+
189
+ function MeetingSearchResults({
190
+ results,
191
+ navigate,
192
+ t,
193
+ }: {
194
+ results: MeetingCommandSearchHit[];
195
+ navigate: ReturnType<typeof useNavigate>;
196
+ t: ReturnType<typeof useT>;
197
+ }) {
198
+ if (results.length === 0) return null;
199
+ return (
200
+ <CommandMenu.Group heading={t("navigation.meetings")}>
201
+ {results.map((meeting) => (
202
+ <CommandMenu.Item
203
+ key={`meeting:${meeting.id}`}
204
+ onSelect={() => void navigate(`/meetings/${meeting.id}`)}
205
+ keywords={[meeting.title, meeting.snippet ?? "", "meeting", "call"]}
206
+ className="items-start py-2"
207
+ >
208
+ <IconCalendar className="mt-0.5 size-4 shrink-0 text-muted-foreground" />
209
+ <span className="min-w-0 flex-1">
210
+ <span className="block truncate font-medium">{meeting.title}</span>
211
+ {formatSearchSnippet(meeting.snippet) ? (
212
+ <span className="mt-0.5 block line-clamp-2 text-xs leading-snug text-muted-foreground">
213
+ {formatSearchSnippet(meeting.snippet)}
214
+ </span>
215
+ ) : null}
216
+ </span>
217
+ </CommandMenu.Item>
218
+ ))}
219
+ </CommandMenu.Group>
220
+ );
221
+ }
222
+
223
+ function DictationSearchResults({
224
+ results,
225
+ navigate,
226
+ t,
227
+ }: {
228
+ results: DictationCommandSearchHit[];
229
+ navigate: ReturnType<typeof useNavigate>;
230
+ t: ReturnType<typeof useT>;
231
+ }) {
232
+ if (results.length === 0) return null;
233
+ return (
234
+ <CommandMenu.Group heading={t("navigation.dictate")}>
235
+ {results.map((dictation) => (
236
+ <CommandMenu.Item
237
+ key={`dictation:${dictation.id}`}
238
+ onSelect={() =>
239
+ void navigate(withQuery("/dictate", { dictationId: dictation.id }))
240
+ }
241
+ keywords={[
242
+ dictation.fullText,
243
+ dictation.cleanedText ?? "",
244
+ "dictation",
245
+ "voice",
246
+ ]}
247
+ className="items-start py-2"
248
+ >
249
+ <IconMicrophone2 className="mt-0.5 size-4 shrink-0 text-muted-foreground" />
250
+ <span className="min-w-0 flex-1">
251
+ <span className="block line-clamp-2 font-medium">
252
+ {formatSearchSnippet(dictation.snippet) ?? dictation.fullText}
253
+ </span>
254
+ {dictation.targetApp ? (
255
+ <span className="mt-0.5 block truncate text-xs text-muted-foreground">
256
+ {dictation.targetApp}
257
+ </span>
258
+ ) : null}
259
+ </span>
260
+ </CommandMenu.Item>
261
+ ))}
262
+ </CommandMenu.Group>
263
+ );
264
+ }
265
+
266
+ function LocalSearchResults({
267
+ query,
268
+ folders,
269
+ spaces,
270
+ navigate,
271
+ t,
272
+ }: {
273
+ query: string;
274
+ folders: Array<{ id: string; name: string; spaceId?: string | null }>;
275
+ spaces: Array<{ id: string; name: string }>;
276
+ navigate: ReturnType<typeof useNavigate>;
277
+ t: ReturnType<typeof useT>;
278
+ }) {
279
+ const needle = query.trim().toLowerCase();
280
+ if (needle.length < 2) return null;
281
+ const matchingFolders = folders
282
+ .filter((folder) => folder.name.toLowerCase().includes(needle))
283
+ .slice(0, 8);
284
+ const matchingSpaces = spaces
285
+ .filter((space) => space.name.toLowerCase().includes(needle))
286
+ .slice(0, 8);
287
+
288
+ return (
289
+ <>
290
+ {matchingFolders.length > 0 && (
291
+ <CommandMenu.Group heading={t("navigation.folders")}>
292
+ {matchingFolders.map((folder) => (
293
+ <CommandMenu.Item
294
+ key={`folder:${folder.id}`}
295
+ onSelect={() =>
296
+ void navigate(
297
+ folder.spaceId
298
+ ? `/spaces/${folder.spaceId}/folder/${folder.id}`
299
+ : `/library/folder/${folder.id}`,
300
+ )
301
+ }
302
+ keywords={[folder.name, "folder"]}
303
+ >
304
+ <IconFolder className="size-4 text-muted-foreground" />
305
+ <span className="truncate">{folder.name}</span>
306
+ </CommandMenu.Item>
307
+ ))}
308
+ </CommandMenu.Group>
309
+ )}
310
+ {matchingSpaces.length > 0 && (
311
+ <CommandMenu.Group heading={t("navigation.spaces")}>
312
+ {matchingSpaces.map((space) => (
313
+ <CommandMenu.Item
314
+ key={`space:${space.id}`}
315
+ onSelect={() => void navigate(`/spaces/${space.id}`)}
316
+ keywords={[space.name, "space"]}
317
+ >
318
+ <IconUsersGroup className="size-4 text-muted-foreground" />
319
+ <span className="truncate">{space.name}</span>
320
+ </CommandMenu.Item>
321
+ ))}
322
+ </CommandMenu.Group>
323
+ )}
324
+ </>
325
+ );
326
+ }
327
+
328
+ function ClipsCommandSearchResults({
329
+ search,
330
+ folders,
331
+ spaces,
332
+ navigate,
333
+ t,
334
+ }: {
335
+ search: string;
336
+ folders: Array<{ id: string; name: string; spaceId?: string | null }>;
337
+ spaces: Array<{ id: string; name: string }>;
338
+ navigate: ReturnType<typeof useNavigate>;
339
+ t: ReturnType<typeof useT>;
340
+ }) {
341
+ const recordingSearch = useRecordingSearch(search.trim());
342
+ const meetingSearch = useMeetingCommandSearch(search);
343
+ const dictationSearch = useDictationCommandSearch(search);
344
+
345
+ if (search.trim().length < 2) return null;
346
+ return (
347
+ <>
348
+ <LocalSearchResults
349
+ query={search}
350
+ folders={folders}
351
+ spaces={spaces}
352
+ navigate={navigate}
353
+ t={t}
354
+ />
355
+ <RecordingSearchResults
356
+ results={recordingSearch.data?.results ?? []}
357
+ navigate={navigate}
358
+ t={t}
359
+ />
360
+ <MeetingSearchResults
361
+ results={meetingSearch.data?.meetings ?? []}
362
+ navigate={navigate}
363
+ t={t}
364
+ />
365
+ <DictationSearchResults
366
+ results={dictationSearch.data?.dictations ?? []}
367
+ navigate={navigate}
368
+ t={t}
369
+ />
370
+ </>
371
+ );
372
+ }
373
+
374
+ function ThemeToggleItem() {
375
+ const { resolvedTheme, setTheme } = useTheme();
376
+ const t = useT();
377
+ const isDark = resolvedTheme === "dark";
378
+ return (
379
+ <CommandMenu.Item
380
+ onSelect={() => setTheme(isDark ? "light" : "dark")}
381
+ keywords={["theme", "dark", "light", "mode"]}
382
+ >
383
+ {isDark ? <IconSun size={16} /> : <IconMoon size={16} />}
384
+ {t("root.toggleTheme")}
385
+ </CommandMenu.Item>
386
+ );
387
+ }
388
+
389
+ export function ClipsCommandMenu({
390
+ open,
391
+ onOpenChange,
392
+ }: ClipsCommandMenuProps) {
393
+ const location = useLocation();
394
+ const navigate = useNavigate();
395
+ const t = useT();
396
+ const { canManageOrg } = useOrgRole();
397
+ const context = useMemo(
398
+ () => commandRouteContext(location.pathname),
399
+ [location.pathname],
400
+ );
401
+ const { data: organizations } = useOrganizations();
402
+ const organizationId =
403
+ organizations?.currentId ?? organizations?.organizations?.[0]?.id;
404
+ const { data: folderData } = useFolders(
405
+ { organizationId },
406
+ { enabled: open && Boolean(organizationId) },
407
+ );
408
+ const { data: spaceData } = useSpaces(organizationId, {
409
+ enabled: open && Boolean(organizationId),
410
+ });
411
+ const folders = (folderData?.folders ?? []) as Array<{
412
+ id: string;
413
+ name: string;
414
+ spaceId?: string | null;
415
+ }>;
416
+ const spaces = (spaceData?.spaces ?? []) as Array<{
417
+ id: string;
418
+ name: string;
419
+ }>;
420
+ const renderResults = useCallback(
421
+ (search: string) => (
422
+ <ClipsCommandSearchResults
423
+ search={search}
424
+ folders={folders}
425
+ spaces={spaces}
426
+ navigate={navigate}
427
+ t={t}
428
+ />
429
+ ),
430
+ [folders, navigate, spaces, t],
431
+ );
432
+
433
+ useCommandMenuShortcut(useCallback(() => onOpenChange(true), [onOpenChange]));
434
+
435
+ const scopedRecordPath =
436
+ context.kind === "folder"
437
+ ? withQuery("/record", {
438
+ folderId: context.folderId,
439
+ ...(context.spaceId ? { spaceId: context.spaceId } : {}),
440
+ })
441
+ : context.kind === "space"
442
+ ? withQuery("/record", { spaceId: context.spaceId })
443
+ : "/record";
444
+
445
+ return (
446
+ <CommandMenu
447
+ open={open}
448
+ onOpenChange={onOpenChange}
449
+ changelog={changelog}
450
+ changelogLabel={t("settings.whatsNew")}
451
+ changelogKey="clips"
452
+ renderResults={renderResults}
453
+ >
454
+ <CommandMenu.Group heading={t("root.commandActions")}>
455
+ {context.kind === "recording" && (
456
+ <>
457
+ <CommandMenu.Item
458
+ onSelect={() =>
459
+ void navigate(
460
+ withQuery(`/r/${context.recordingId}`, {
461
+ panel: "comments",
462
+ }),
463
+ )
464
+ }
465
+ keywords={["comments", "discussion", "feedback", "recording"]}
466
+ >
467
+ <IconMessage size={16} />
468
+ {t("sharePage.comments")}
469
+ </CommandMenu.Item>
470
+ <CommandMenu.Item
471
+ onSelect={() =>
472
+ void navigate(
473
+ withQuery(`/r/${context.recordingId}`, {
474
+ panel: "transcript",
475
+ }),
476
+ )
477
+ }
478
+ keywords={["transcript", "captions", "recording"]}
479
+ >
480
+ <IconFileText size={16} />
481
+ {t("recordingPage.transcript")}
482
+ </CommandMenu.Item>
483
+ <CommandMenu.Item
484
+ onSelect={() =>
485
+ void navigate(
486
+ withQuery(`/r/${context.recordingId}`, {
487
+ [AGENT_SIDEBAR_QUERY_PARAM]: AGENT_SIDEBAR_QUERY_VALUE_OPEN,
488
+ }),
489
+ )
490
+ }
491
+ keywords={["agent", "ask", "recording"]}
492
+ >
493
+ <IconHierarchy2 size={16} />
494
+ {t("recordingPage.agent")}
495
+ </CommandMenu.Item>
496
+ <CommandMenu.Item
497
+ onSelect={() =>
498
+ void navigate(
499
+ withQuery(`/r/${context.recordingId}`, {
500
+ panel: "settings",
501
+ }),
502
+ )
503
+ }
504
+ keywords={["settings", "recording"]}
505
+ >
506
+ <IconSettings size={16} />
507
+ {t("recordingPage.settings")}
508
+ </CommandMenu.Item>
509
+ </>
510
+ )}
511
+ {(context.kind === "library" ||
512
+ context.kind === "folder" ||
513
+ context.kind === "space") && (
514
+ <CommandMenu.Item
515
+ onSelect={() => void navigate(scopedRecordPath)}
516
+ keywords={["record", "recording", "capture", "new"]}
517
+ >
518
+ <IconVideoPlus size={16} />
519
+ {t("navigation.newRecording")}
520
+ </CommandMenu.Item>
521
+ )}
522
+ {(context.kind === "library" ||
523
+ context.kind === "folder" ||
524
+ context.kind === "space") && (
525
+ <CommandMenu.Item
526
+ onSelect={openCreateFolder}
527
+ keywords={["folder", "new", "create"]}
528
+ >
529
+ <IconFolderPlus size={16} />
530
+ {t("navigation.newFolder")}
531
+ </CommandMenu.Item>
532
+ )}
533
+ {context.kind === "spaces" && canManageOrg && (
534
+ <CommandMenu.Item
535
+ onSelect={openCreateSpace}
536
+ keywords={["space", "new", "create"]}
537
+ >
538
+ <IconUsersGroup size={16} />
539
+ {t("createSpaceDialog.newSpace")}
540
+ </CommandMenu.Item>
541
+ )}
542
+ <CommandMenu.Item
543
+ onSelect={() => void navigate(SEARCH_FOCUS_PATH)}
544
+ keywords={["search", "recordings", "meetings", "dictations", "find"]}
545
+ >
546
+ <IconSearch size={16} />
547
+ {t("root.commandSearch")}
548
+ </CommandMenu.Item>
549
+ <CommandMenu.Item
550
+ onSelect={() => void navigate("/settings/agent")}
551
+ keywords={["agent", "assistant", "chat"]}
552
+ >
553
+ <IconHierarchy2 size={16} />
554
+ {t("root.openAgent")}
555
+ </CommandMenu.Item>
556
+ <CommandMenu.Item
557
+ onSelect={openBugReportDialog}
558
+ keywords={["feedback", "bug", "report"]}
559
+ >
560
+ <IconMessage size={16} />
561
+ {t("bugReportRoute.sidebarCta")}
562
+ </CommandMenu.Item>
563
+ </CommandMenu.Group>
564
+
565
+ <CommandMenu.Group heading={t("navigation.library")}>
566
+ <CommandMenu.Item
567
+ onSelect={() => void navigate("/library")}
568
+ keywords={["library", "recordings"]}
569
+ >
570
+ <IconVideo size={16} />
571
+ {t("navigation.library")}
572
+ <CommandMenu.Shortcut>G L</CommandMenu.Shortcut>
573
+ </CommandMenu.Item>
574
+ <CommandMenu.Item
575
+ onSelect={() => void navigate("/shared")}
576
+ keywords={["shared", "clips"]}
577
+ >
578
+ <IconUsersGroup size={16} />
579
+ {t("navigation.sharedWithMe")}
580
+ </CommandMenu.Item>
581
+ <CommandMenu.Item
582
+ onSelect={() => void navigate("/spaces")}
583
+ keywords={["spaces", "team"]}
584
+ >
585
+ <IconUsersGroup size={16} />
586
+ {t("navigation.spaces")}
587
+ <CommandMenu.Shortcut>G S</CommandMenu.Shortcut>
588
+ </CommandMenu.Item>
589
+ <CommandMenu.Item
590
+ onSelect={() => void navigate("/meetings")}
591
+ keywords={["meetings", "calls"]}
592
+ >
593
+ <IconCalendar size={16} />
594
+ {t("navigation.meetings")}
595
+ <CommandMenu.Shortcut>G M</CommandMenu.Shortcut>
596
+ </CommandMenu.Item>
597
+ <CommandMenu.Item
598
+ onSelect={() => void navigate("/dictate")}
599
+ keywords={["dictate", "dictations", "voice"]}
600
+ >
601
+ <IconMicrophone2 size={16} />
602
+ {t("navigation.dictate")}
603
+ <CommandMenu.Shortcut>G D</CommandMenu.Shortcut>
604
+ </CommandMenu.Item>
605
+ <CommandMenu.Item
606
+ onSelect={() => void navigate("/archive")}
607
+ keywords={["archive"]}
608
+ >
609
+ <IconArchive size={16} />
610
+ {t("navigation.archive")}
611
+ <CommandMenu.Shortcut>G A</CommandMenu.Shortcut>
612
+ </CommandMenu.Item>
613
+ <CommandMenu.Item
614
+ onSelect={() => void navigate("/trash")}
615
+ keywords={["trash", "deleted"]}
616
+ >
617
+ <IconTrash size={16} />
618
+ {t("navigation.trash")}
619
+ <CommandMenu.Shortcut>G T</CommandMenu.Shortcut>
620
+ </CommandMenu.Item>
621
+ </CommandMenu.Group>
622
+
623
+ <CommandMenu.Group heading={t("root.commandAppearance")}>
624
+ <ThemeToggleItem />
625
+ </CommandMenu.Group>
626
+
627
+ <CommandMenu.DocsGroup docs={CLIPS_COMMAND_DOCS} />
628
+ </CommandMenu>
629
+ );
630
+ }
631
+
632
+ const CLIPS_COMMAND_DOCS = [
633
+ {
634
+ title: "Use the Chrome extension for browser logs", // i18n-ignore: documentation metadata is intentionally kept in the source language.
635
+ description:
636
+ "Record a browser tab with redacted console logs, JavaScript exceptions, and fetch/XHR diagnostics.",
637
+ href: docsUrl("template-clips-capture-everywhere", {
638
+ hash: "browser-logs-with-the-chrome-extension",
639
+ }),
640
+ keywords: [
641
+ "logs",
642
+ "browser logs",
643
+ "developer logs",
644
+ "console logs",
645
+ "network logs",
646
+ "fetch",
647
+ "xhr",
648
+ "diagnostics",
649
+ "chrome extension",
650
+ "recording",
651
+ ],
652
+ },
653
+ ] satisfies ComponentProps<typeof CommandMenu.DocsGroup>["docs"];