@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
@@ -85,7 +85,7 @@ export const ViewerTabsList = React.forwardRef<
85
85
  ref={ref}
86
86
  variant="line"
87
87
  className={cn(
88
- "h-10 w-full shrink-0 justify-start overflow-x-auto rounded-none p-0",
88
+ "h-10 min-h-10 w-fit max-w-full shrink-0 justify-start overflow-x-auto rounded-none px-3 py-0",
89
89
  className,
90
90
  )}
91
91
  {...props}
@@ -100,7 +100,7 @@ export const ViewerTabsTrigger = React.forwardRef<
100
100
  <TabsTrigger
101
101
  ref={ref}
102
102
  className={cn(
103
- "h-10 min-w-0 rounded-none px-2 py-0 text-xs after:bottom-0",
103
+ "h-10 min-w-0 flex-none rounded-none px-2 py-0 text-sm data-[state=active]:after:bottom-0 data-[state=active]:after:inset-x-2",
104
104
  className,
105
105
  )}
106
106
  {...props}
@@ -14,7 +14,6 @@ import {
14
14
  } from "react";
15
15
 
16
16
  import { Button } from "@/components/ui/button";
17
- import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";
18
17
  import {
19
18
  createBackgroundBlurStream,
20
19
  DEFAULT_BLUR_PX,
@@ -35,7 +34,6 @@ export interface CameraVisualizerProps {
35
34
  /** Background blur radius (px) reflected live in the test preview. */
36
35
  blurRadius?: number;
37
36
  size?: CameraBubbleSize;
38
- onSizeChange?: (size: CameraBubbleSize) => void;
39
37
  onStatusChange?: (
40
38
  status: CameraTestStatus,
41
39
  detail?: { error?: string | null },
@@ -49,12 +47,6 @@ const CAMERA_BUBBLE_SIZE_PX: Record<CameraBubbleSize, number> = {
49
47
  lg: 320,
50
48
  };
51
49
 
52
- const CAMERA_SIZE_OPTIONS: Array<{ value: CameraBubbleSize; label: string }> = [
53
- { value: "sm", label: "S" },
54
- { value: "md", label: "M" },
55
- { value: "lg", label: "L" },
56
- ];
57
-
58
50
  const CAMERA_FRAME_TIMEOUT_MS = 5_000;
59
51
 
60
52
  type CameraPermissionState = PermissionState | "unknown";
@@ -190,7 +182,6 @@ export function CameraVisualizer({
190
182
  blur = false,
191
183
  blurRadius = DEFAULT_BLUR_PX,
192
184
  size = "md",
193
- onSizeChange,
194
185
  onStatusChange,
195
186
  onPreviewChange,
196
187
  }: CameraVisualizerProps) {
@@ -527,30 +518,6 @@ export function CameraVisualizer({
527
518
  )}
528
519
  <span className="truncate">{statusLabel}</span>
529
520
  </div>
530
- <ToggleGroup
531
- type="single"
532
- value={size}
533
- onValueChange={(value) => {
534
- if (value) onSizeChange?.(value as CameraBubbleSize);
535
- }}
536
- variant="outline"
537
- aria-label={t("cameraVisualizer.bubble")}
538
- className="grid shrink-0 grid-cols-3 gap-0.5 rounded-md bg-muted p-0.5"
539
- >
540
- {CAMERA_SIZE_OPTIONS.map((option) => (
541
- <ToggleGroupItem
542
- key={option.value}
543
- value={option.value}
544
- disabled={disabled}
545
- aria-label={t("cameraVisualizer.setBubbleSize", {
546
- size: option.label,
547
- })}
548
- className="h-6 min-w-6 rounded border-0 px-1.5 text-[11px] text-muted-foreground shadow-none data-[state=on]:bg-background data-[state=on]:text-foreground data-[state=on]:shadow-sm"
549
- >
550
- {option.label}
551
- </ToggleGroupItem>
552
- ))}
553
- </ToggleGroup>
554
521
  <Button
555
522
  type="button"
556
523
  variant={live ? "outline" : "secondary"}
@@ -630,7 +630,11 @@ export function PeopleAccessSection({
630
630
  ? first
631
631
  : t("shareUi.othersCount", { count: rest.length, email: first })
632
632
  }
633
- meta={open && first ? firstRole : t("shareUi.canAccess")}
633
+ meta={
634
+ first && (open || rest.length === 0)
635
+ ? firstRole
636
+ : t("shareUi.canAccess")
637
+ }
634
638
  disabled={sharesQuery.isLoading}
635
639
  open={open}
636
640
  onOpenChange={setOpen}
@@ -62,7 +62,7 @@ function TabsTrigger({
62
62
  <TabsPrimitive.Trigger
63
63
  data-slot="tabs-trigger"
64
64
  className={cn(
65
- "relative inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-2 py-1 text-sm font-medium whitespace-nowrap text-foreground/60 transition-[color,background-color,border-color,box-shadow] group-data-[orientation=vertical]/tabs:w-full group-data-[orientation=vertical]/tabs:justify-start hover:text-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:outline-1 focus-visible:outline-ring disabled:pointer-events-none disabled:opacity-50 group-data-[variant=default]/tabs-list:data-[state=active]:shadow-sm group-data-[variant=line]/tabs-list:data-[state=active]:shadow-none dark:text-muted-foreground dark:hover:text-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
65
+ "relative inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-2 py-1 text-sm font-medium whitespace-nowrap text-foreground/60 transition-all group-data-[orientation=vertical]/tabs:w-full group-data-[orientation=vertical]/tabs:justify-start hover:text-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:outline-1 focus-visible:outline-ring disabled:pointer-events-none disabled:opacity-50 group-data-[variant=default]/tabs-list:data-[state=active]:shadow-sm group-data-[variant=line]/tabs-list:data-[state=active]:shadow-none dark:text-muted-foreground dark:hover:text-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
66
66
  "group-data-[variant=line]/tabs-list:bg-transparent group-data-[variant=line]/tabs-list:data-[state=active]:bg-transparent dark:group-data-[variant=line]/tabs-list:data-[state=active]:border-transparent dark:group-data-[variant=line]/tabs-list:data-[state=active]:bg-transparent",
67
67
  "data-[state=active]:bg-background data-[state=active]:text-foreground dark:data-[state=active]:border-input dark:data-[state=active]:bg-input/30 dark:data-[state=active]:text-foreground",
68
68
  "after:absolute after:bg-foreground after:opacity-0 after:transition-opacity group-data-[orientation=horizontal]/tabs:after:inset-x-0 group-data-[orientation=horizontal]/tabs:after:bottom-[-5px] group-data-[orientation=horizontal]/tabs:after:h-0.5 group-data-[orientation=vertical]/tabs:after:inset-y-0 group-data-[orientation=vertical]/tabs:after:-right-1 group-data-[orientation=vertical]/tabs:after:w-0.5 group-data-[variant=line]/tabs-list:data-[state=active]:after:opacity-100",
@@ -0,0 +1,53 @@
1
+ import { useActionQuery } from "@agent-native/core/client/hooks";
2
+
3
+ export interface MeetingCommandSearchHit {
4
+ id: string;
5
+ title: string;
6
+ recordingId?: string | null;
7
+ scheduledStart?: string | null;
8
+ actualStart?: string | null;
9
+ snippet?: string | null;
10
+ matchType?: string;
11
+ }
12
+
13
+ export interface DictationCommandSearchHit {
14
+ id: string;
15
+ fullText: string;
16
+ cleanedText?: string | null;
17
+ snippet?: string | null;
18
+ durationMs: number;
19
+ source: string;
20
+ targetApp?: string | null;
21
+ startedAt: string;
22
+ createdAt: string;
23
+ }
24
+
25
+ export function useMeetingCommandSearch(query: string) {
26
+ const trimmedQuery = query.trim();
27
+ return useActionQuery<{
28
+ query: string;
29
+ meetings: MeetingCommandSearchHit[];
30
+ }>(
31
+ "search-meetings",
32
+ trimmedQuery ? { query: trimmedQuery, limit: 8 } : undefined,
33
+ {
34
+ enabled: trimmedQuery.length >= 2,
35
+ retry: false,
36
+ },
37
+ );
38
+ }
39
+
40
+ export function useDictationCommandSearch(query: string) {
41
+ const trimmedQuery = query.trim();
42
+ return useActionQuery<{
43
+ query: string;
44
+ dictations: DictationCommandSearchHit[];
45
+ }>(
46
+ "search-dictations",
47
+ trimmedQuery ? { query: trimmedQuery, limit: 8 } : undefined,
48
+ {
49
+ enabled: trimmedQuery.length >= 2,
50
+ retry: false,
51
+ },
52
+ );
53
+ }
@@ -1,5 +1,9 @@
1
1
  import { getBrowserTabId } from "@agent-native/core/client/hooks";
2
2
  import { useAgentRouteState } from "@agent-native/core/client/navigation";
3
+ import {
4
+ AGENT_SIDEBAR_QUERY_PARAM,
5
+ AGENT_SIDEBAR_QUERY_VALUE_OPEN,
6
+ } from "@agent-native/core/shared";
3
7
 
4
8
  import { parseTimeParam } from "@/lib/time-param";
5
9
 
@@ -100,19 +104,23 @@ export function stateFromLocation(
100
104
  const recordingId = decodePathSegment(recordingMatch[1]);
101
105
  if (!recordingId) return { view: "library" };
102
106
  const panel = params.get("panel");
107
+ const agentSidebarOpen =
108
+ params.get(AGENT_SIDEBAR_QUERY_PARAM) === AGENT_SIDEBAR_QUERY_VALUE_OPEN;
103
109
  const atParam = params.get("at") ?? params.get("t");
104
110
  const atMs = atParam == null ? undefined : parseTimeParam(atParam);
105
111
  return {
106
112
  view: panel === "insights" ? "insights" : "recording",
107
113
  recordingId,
108
- ...(panel === "comments" ||
109
- panel === "transcript" ||
110
- panel === "agent" ||
111
- panel === "debug" ||
112
- panel === "insights" ||
113
- panel === "settings"
114
- ? { panel }
115
- : {}),
114
+ ...(agentSidebarOpen
115
+ ? { panel: "agent" as const }
116
+ : panel === "comments" ||
117
+ panel === "transcript" ||
118
+ panel === "agent" ||
119
+ panel === "debug" ||
120
+ panel === "insights" ||
121
+ panel === "settings"
122
+ ? { panel }
123
+ : {}),
116
124
  ...(Number.isFinite(atMs) && atMs! >= 0 ? { atMs } : {}),
117
125
  ...(searchTerm ? { search: searchTerm } : {}),
118
126
  };
@@ -164,11 +172,13 @@ export function stateFromLocation(
164
172
  };
165
173
  }
166
174
 
167
- // /dictate (optionally /dictate/:dictationId in the future)
175
+ // /dictate?dictationId=:dictationId (optionally /dictate/:dictationId in the future)
168
176
  const dictateMatch = p.match(/^\/dictate(?:\/([^/]+))?$/);
169
177
  if (dictateMatch) {
170
- const dictationId = decodePathSegment(dictateMatch[1]);
171
- if (dictateMatch[1] && !dictationId) return { view: "library" };
178
+ const pathDictationId = decodePathSegment(dictateMatch[1]);
179
+ if (dictateMatch[1] && !pathDictationId) return { view: "library" };
180
+ const queryDictationId = params.get("dictationId")?.trim() || undefined;
181
+ const dictationId = pathDictationId ?? queryDictationId;
172
182
  return {
173
183
  view: "dictate",
174
184
  ...(dictationId ? { dictationId } : {}),
@@ -210,7 +220,14 @@ export function pathFromCommand(cmd: NavigateCommand): string {
210
220
  case "recording":
211
221
  if (!cmd.recordingId) return "/library";
212
222
  const recordingParams = new URLSearchParams();
213
- if (cmd.panel) recordingParams.set("panel", cmd.panel);
223
+ if (cmd.panel === "agent") {
224
+ recordingParams.set(
225
+ AGENT_SIDEBAR_QUERY_PARAM,
226
+ AGENT_SIDEBAR_QUERY_VALUE_OPEN,
227
+ );
228
+ } else if (cmd.panel) {
229
+ recordingParams.set("panel", cmd.panel);
230
+ }
214
231
  if (typeof cmd.atMs === "number" && Number.isFinite(cmd.atMs)) {
215
232
  // Viewer routes use the public `at` query parameter in seconds while
216
233
  // navigation commands expose timestamps in milliseconds.
@@ -265,7 +282,9 @@ export function pathFromCommand(cmd: NavigateCommand): string {
265
282
  ? `/meetings/${encodeURIComponent(cmd.meetingId)}`
266
283
  : "/meetings";
267
284
  case "dictate":
268
- return "/dictate";
285
+ return cmd.dictationId
286
+ ? `/dictate?dictationId=${encodeURIComponent(cmd.dictationId)}`
287
+ : "/dictate";
269
288
  case "library":
270
289
  default:
271
290
  if (cmd.folderId) {
@@ -37,6 +37,7 @@ export function useUploadVideoPicker(): {
37
37
  type="file"
38
38
  accept={VIDEO_ACCEPT}
39
39
  className="hidden"
40
+ data-button-group-ignore="true"
40
41
  onChange={handleChange}
41
42
  />
42
43
  ),
@@ -64,6 +64,7 @@ const messages = {
64
64
  extensions: "Extensions",
65
65
  newRecording: "New recording",
66
66
  folders: "Folders",
67
+ recordings: "Recordings",
67
68
  newFolder: "New folder",
68
69
  noSpaces: "No spaces yet",
69
70
  desktopCta: "Get desktop app",
@@ -502,6 +503,11 @@ const messages = {
502
503
  shareLink: "Share link",
503
504
  shareWithHumans: "Share with humans",
504
505
  shareWithAgents: "Share with agents",
506
+ people: "People",
507
+ agents: "Agents",
508
+ openInClaude: "Open in Claude",
509
+ openInClaudeCode: "Open in Claude Code",
510
+ openInCodex: "Open in Codex",
505
511
  copyAgentPrompt: "Copy agent prompt",
506
512
  agentPrompt:
507
513
  "Fetch this Clips agent context URL: {{agentContextUrl}}. Use transcript.segments for spoken context, fetch recommendedFrames or the frame API URLs to see the screen, and check browserDiagnostics if present for redacted console logs and fetch/XHR request metadata.",
@@ -1707,6 +1713,7 @@ const messages = {
1707
1713
  donePageTitle: "Bug report submitted · Clips",
1708
1714
  eyebrow: "Bug report",
1709
1715
  title: "Record a bug report",
1716
+ sidebarCta: "Send feedback",
1710
1717
  description:
1711
1718
  "Capture a short repro with screen, voice, and redacted browser context for your team.",
1712
1719
  issueTitleLabel: "Issue title",
@@ -0,0 +1,16 @@
1
+ export type AgentShareDestination = "claude" | "claude-code" | "codex";
2
+
3
+ const AGENT_SHARE_DESTINATION_URLS: Record<AgentShareDestination, string> = {
4
+ claude: "claude://claude.ai/new",
5
+ "claude-code": "claude://code/new",
6
+ codex: "codex://threads/new",
7
+ };
8
+
9
+ export function buildAgentShareDeepLink(
10
+ destination: AgentShareDestination,
11
+ prompt: string,
12
+ ): string {
13
+ const url = new URL(AGENT_SHARE_DESTINATION_URLS[destination]);
14
+ url.searchParams.set("q", prompt);
15
+ return url.toString();
16
+ }
@@ -0,0 +1,7 @@
1
+ export const OPEN_CREATE_FOLDER_EVENT = "clips:open-create-folder";
2
+ export const OPEN_CREATE_SPACE_EVENT = "clips:open-create-space";
3
+ export const OPEN_BUG_REPORT_EVENT = "clips:open-bug-report";
4
+
5
+ export function openBugReportDialog() {
6
+ window.dispatchEvent(new Event(OPEN_BUG_REPORT_EVENT));
7
+ }
@@ -13,22 +13,11 @@ import {
13
13
  type LocalizationPreference,
14
14
  useT,
15
15
  } from "@agent-native/core/client/i18n";
16
- import {
17
- CommandMenu,
18
- useCommandMenuShortcut,
19
- } from "@agent-native/core/client/navigation";
20
16
  import { getThemeInitScript } from "@agent-native/core/client/ui";
21
17
  import { resolveLocaleFromRequest } from "@agent-native/core/server";
22
- import { docsUrl } from "@agent-native/core/shared";
23
- import {
24
- IconHierarchy2,
25
- IconCheck,
26
- IconSun,
27
- IconMoon,
28
- } from "@tabler/icons-react";
18
+ import { IconCheck } from "@tabler/icons-react";
29
19
  import { useQueryClient } from "@tanstack/react-query";
30
- import { useTheme } from "next-themes";
31
- import { useCallback, useEffect, useState } from "react";
20
+ import { useEffect, useState } from "react";
32
21
  import {
33
22
  Links,
34
23
  Meta,
@@ -37,11 +26,12 @@ import {
37
26
  ScrollRestoration,
38
27
  useLoaderData,
39
28
  useLocation,
40
- useNavigate,
41
29
  useRouteLoaderData,
42
30
  } from "react-router";
43
31
  import type { LinksFunction, LoaderFunctionArgs } from "react-router";
44
32
 
33
+ import { BugReportDialog } from "@/components/bug-report/bug-report-dialog";
34
+ import { ClipsCommandMenu } from "@/components/clips-command-menu";
45
35
  import { Button } from "@/components/ui/button";
46
36
  import {
47
37
  Dialog,
@@ -55,9 +45,7 @@ import { Toaster } from "@/components/ui/sonner";
55
45
  import { AppToolkitProvider } from "@/components/ui/toolkit-provider";
56
46
  import { useNavigationState } from "@/hooks/use-navigation-state";
57
47
  import { isStandalonePublicPath } from "@/lib/public-ssr-paths";
58
- import { SEARCH_FOCUS_PATH } from "@/lib/search-focus";
59
48
 
60
- import changelog from "../CHANGELOG.md?raw";
61
49
  import { i18nCatalog, loadI18nMessages } from "./i18n";
62
50
 
63
51
  import stylesheet from "./global.css?url";
@@ -200,21 +188,6 @@ function DbSyncSetup() {
200
188
  return null;
201
189
  }
202
190
 
203
- function ThemeToggleItem() {
204
- const { resolvedTheme, setTheme } = useTheme();
205
- const t = useT();
206
- const isDark = resolvedTheme === "dark";
207
- return (
208
- <CommandMenu.Item
209
- onSelect={() => setTheme(isDark ? "light" : "dark")}
210
- keywords={["theme", "dark", "light", "mode"]}
211
- >
212
- {isDark ? <IconSun size={16} /> : <IconMoon size={16} />}
213
- {t("root.toggleTheme")}
214
- </CommandMenu.Item>
215
- );
216
- }
217
-
218
191
  type ExternalChromeRuntime = {
219
192
  lastError?: { message?: string };
220
193
  sendMessage: (
@@ -224,29 +197,6 @@ type ExternalChromeRuntime = {
224
197
  ) => void;
225
198
  };
226
199
 
227
- const CLIPS_COMMAND_DOCS = [
228
- {
229
- title: "Use the Chrome extension for browser logs",
230
- description:
231
- "Record a browser tab with redacted console logs, JavaScript exceptions, and fetch/XHR diagnostics.",
232
- href: docsUrl("template-clips-capture-everywhere", {
233
- hash: "browser-logs-with-the-chrome-extension",
234
- }),
235
- keywords: [
236
- "logs",
237
- "browser logs",
238
- "developer logs",
239
- "console logs",
240
- "network logs",
241
- "fetch",
242
- "xhr",
243
- "diagnostics",
244
- "chrome extension",
245
- "recording",
246
- ],
247
- },
248
- ] satisfies React.ComponentProps<typeof CommandMenu.DocsGroup>["docs"];
249
-
250
200
  function ClipsExtensionAuthBridge() {
251
201
  const location = useLocation();
252
202
  const t = useT();
@@ -336,43 +286,17 @@ function AppContent() {
336
286
 
337
287
  function PrivateAppContent() {
338
288
  const location = useLocation();
339
- const navigate = useNavigate();
340
- const t = useT();
341
289
  const standalonePublic = isStandalonePublicPath(location.pathname);
342
290
  const [cmdkOpen, setCmdkOpen] = useState(false);
343
- useCommandMenuShortcut(
344
- useCallback(() => {
345
- if (!standalonePublic) setCmdkOpen(true);
346
- }, [standalonePublic]),
347
- );
348
291
 
349
292
  return (
350
293
  <>
351
294
  {standalonePublic ? null : <DbSyncSetup />}
352
295
  {standalonePublic ? null : <ClipsExtensionAuthBridge />}
353
296
  {standalonePublic ? null : (
354
- <CommandMenu
355
- open={cmdkOpen}
356
- onOpenChange={setCmdkOpen}
357
- changelog={changelog}
358
- changelogLabel={t("settings.whatsNew")}
359
- changelogKey="clips"
360
- >
361
- <CommandMenu.Group heading={t("root.commandActions")}>
362
- <CommandMenu.Item onSelect={() => navigate("/settings/agent")}>
363
- <IconHierarchy2 size={16} />
364
- {t("root.openAgent")}
365
- </CommandMenu.Item>
366
- <CommandMenu.Item onSelect={() => navigate(SEARCH_FOCUS_PATH)}>
367
- {t("root.commandSearch")}
368
- </CommandMenu.Item>
369
- </CommandMenu.Group>
370
- <CommandMenu.DocsGroup docs={CLIPS_COMMAND_DOCS} />
371
- <CommandMenu.Group heading={t("root.commandAppearance")}>
372
- <ThemeToggleItem />
373
- </CommandMenu.Group>
374
- </CommandMenu>
297
+ <ClipsCommandMenu open={cmdkOpen} onOpenChange={setCmdkOpen} />
375
298
  )}
299
+ {standalonePublic ? null : <BugReportDialog />}
376
300
  {standalonePublic ? null : <DevOverlay />}
377
301
  <Outlet />
378
302
  </>
@@ -20,7 +20,7 @@ import {
20
20
  } from "@tabler/icons-react";
21
21
  import { useQueryClient } from "@tanstack/react-query";
22
22
  import { useCallback, useEffect, useMemo, useRef, useState } from "react";
23
- import { Navigate } from "react-router";
23
+ import { Navigate, useSearchParams } from "react-router";
24
24
  import { toast } from "sonner";
25
25
 
26
26
  import { CaptureInstallButton } from "@/components/capture-install-options";
@@ -393,9 +393,16 @@ function WebDictationPanel({
393
393
  );
394
394
  }
395
395
 
396
- function DictationRow({ dictation }: { dictation: Dictation }) {
396
+ function DictationRow({
397
+ dictation,
398
+ initialExpanded = false,
399
+ }: {
400
+ dictation: Dictation;
401
+ initialExpanded?: boolean;
402
+ }) {
397
403
  const t = useT();
398
- const [expanded, setExpanded] = useState(false);
404
+ const [expanded, setExpanded] = useState(initialExpanded);
405
+ const rowRef = useRef<HTMLDivElement>(null);
399
406
  const qc = useQueryClient();
400
407
  const cleanup = useActionMutation<any, { id: string }>("cleanup-dictation");
401
408
  const replaceOriginal = useActionMutation<
@@ -404,6 +411,12 @@ function DictationRow({ dictation }: { dictation: Dictation }) {
404
411
  >("update-dictation");
405
412
  const { label, icon } = sourceMeta(dictation.source, t);
406
413
 
414
+ useEffect(() => {
415
+ if (!initialExpanded) return;
416
+ setExpanded(true);
417
+ rowRef.current?.scrollIntoView({ behavior: "smooth", block: "center" });
418
+ }, [initialExpanded]);
419
+
407
420
  const preview = (dictation.cleanedText || dictation.fullText || "").slice(
408
421
  0,
409
422
  140,
@@ -458,6 +471,7 @@ function DictationRow({ dictation }: { dictation: Dictation }) {
458
471
 
459
472
  return (
460
473
  <div
474
+ ref={rowRef}
461
475
  className={cn(
462
476
  "border-b border-border last:border-b-0 cursor-pointer",
463
477
  expanded ? "bg-accent/20" : "hover:bg-accent/10",
@@ -721,6 +735,8 @@ function DictateEmptyState({
721
735
  export default function DictateRoute() {
722
736
  const experiment = useExperimentState(CLIPS_WISPRFLOW.key);
723
737
  const t = useT();
738
+ const [searchParams] = useSearchParams();
739
+ const selectedDictationId = searchParams.get("dictationId");
724
740
  const { isDesktopApp } = useDesktopPromo();
725
741
  const [filter, setFilter] = useState<SourceFilter>("all");
726
742
  const [listening, setListening] = useState(false);
@@ -975,7 +991,7 @@ export default function DictateRoute() {
975
991
  return (
976
992
  <>
977
993
  <PageHeader>
978
- <PageBreadcrumb label={t("navigation.dictate")} />
994
+ <PageBreadcrumb items={[{ label: t("navigation.dictate") }]} />
979
995
  <VocabularyManager />
980
996
  </PageHeader>
981
997
  <div className="mx-auto w-full max-w-4xl px-4 py-5 sm:px-6 sm:py-6">
@@ -1045,7 +1061,11 @@ export default function DictateRoute() {
1045
1061
  </div>
1046
1062
  <div>
1047
1063
  {items.map((d) => (
1048
- <DictationRow key={d.id} dictation={d} />
1064
+ <DictationRow
1065
+ key={d.id}
1066
+ dictation={d}
1067
+ initialExpanded={d.id === selectedDictationId}
1068
+ />
1049
1069
  ))}
1050
1070
  </div>
1051
1071
  </div>
@@ -1,13 +1,7 @@
1
1
  import { useT } from "@agent-native/core/client/i18n";
2
- import { IconVideoPlus } from "@tabler/icons-react";
3
- import { NavLink } from "react-router";
4
2
 
5
- import { ImportMenu } from "@/components/import-menu";
6
3
  import { LibraryGrid } from "@/components/library/library-grid";
7
- import {
8
- PageHeaderActionGroup,
9
- PageHeaderPrimaryAction,
10
- } from "@/components/library/page-header";
4
+ import { LibraryPrimaryActions } from "@/components/library/library-primary-actions";
11
5
 
12
6
  const SEO_TITLE = "Clips - Open Source screen recorder";
13
7
  const SEO_DESCRIPTION =
@@ -33,29 +27,7 @@ export default function LibraryIndexRoute() {
33
27
  view="library"
34
28
  folderId={null}
35
29
  title={t("navigation.library")}
36
- extraActions={
37
- <PageHeaderActionGroup>
38
- <PageHeaderPrimaryAction asChild>
39
- <NavLink to="/record" aria-label={t("navigation.newRecording")}>
40
- <IconVideoPlus />
41
- <span className="hidden sm:inline">
42
- {t("navigation.newRecording")}
43
- </span>
44
- </NavLink>
45
- </PageHeaderPrimaryAction>
46
- <ImportMenu
47
- uploadHref="/record?autoUpload=1"
48
- importLoomHref="/import"
49
- iconOnly
50
- triggerIcon="chevron"
51
- size="sm"
52
- variant="default"
53
- className="w-8 self-stretch border-s border-primary-foreground/20 px-0 shadow-none"
54
- menuSide="bottom"
55
- menuAlign="end"
56
- />
57
- </PageHeaderActionGroup>
58
- }
30
+ extraActions={<LibraryPrimaryActions />}
59
31
  />
60
32
  );
61
33
  }
@@ -3,7 +3,8 @@ import { useMemo } from "react";
3
3
  import { useParams } from "react-router";
4
4
 
5
5
  import { LibraryGrid } from "@/components/library/library-grid";
6
- import { useFolders } from "@/hooks/use-library";
6
+ import { LibraryPrimaryActions } from "@/components/library/library-primary-actions";
7
+ import { useFolders, useOrganizations } from "@/hooks/use-library";
7
8
  import enMessages from "@/i18n/en-US";
8
9
 
9
10
  export function meta() {
@@ -14,7 +15,12 @@ export default function LibraryFolderRoute() {
14
15
  const t = useT();
15
16
  const { folderId } = useParams<{ folderId: string }>();
16
17
 
17
- const { data: folders } = useFolders({});
18
+ const { data: organizations } = useOrganizations();
19
+ const currentOrganizationId =
20
+ organizations?.currentId ?? organizations?.organizations?.[0]?.id;
21
+ const { data: folders } = useFolders({
22
+ organizationId: currentOrganizationId,
23
+ });
18
24
  const folder = useMemo(
19
25
  () =>
20
26
  (folders?.folders ?? []).find((f: any) => f.id === folderId) as
@@ -29,6 +35,11 @@ export default function LibraryFolderRoute() {
29
35
  folderId={folderId}
30
36
  emptyKind="folder"
31
37
  title={folder?.name ?? t("navigation.folder")}
38
+ breadcrumbItems={[
39
+ { label: t("navigation.library"), to: "/library" },
40
+ { label: folder?.name ?? t("navigation.folder") },
41
+ ]}
42
+ extraActions={<LibraryPrimaryActions folderId={folderId} />}
32
43
  />
33
44
  );
34
45
  }
@@ -603,21 +603,8 @@ function MeetingsHeader({
603
603
 
604
604
  useEffect(() => {
605
605
  const onKeyDown = (event: KeyboardEvent) => {
606
- const target = event.target as HTMLElement | null;
607
- const tagName = target?.tagName.toLowerCase();
608
- if (
609
- event.key === "/" &&
610
- !event.metaKey &&
611
- !event.ctrlKey &&
612
- !event.altKey &&
613
- tagName !== "input" &&
614
- tagName !== "textarea" &&
615
- !target?.isContentEditable
616
- ) {
617
- event.preventDefault();
618
- inputRef.current?.focus();
619
- inputRef.current?.select();
620
- }
606
+ if (event.key !== "Escape") return;
607
+ inputRef.current?.blur();
621
608
  };
622
609
 
623
610
  window.addEventListener("keydown", onKeyDown);
@@ -628,7 +615,7 @@ function MeetingsHeader({
628
615
  <PageHeader>
629
616
  <div className="flex min-w-0 flex-1 items-center gap-3 lg:grid lg:grid-cols-[minmax(0,1fr)_24rem_minmax(0,1fr)]">
630
617
  <div className="hidden min-w-0 lg:block">
631
- <PageBreadcrumb label={t("meetingsRoute.title")} />
618
+ <PageBreadcrumb items={[{ label: t("meetingsRoute.title") }]} />
632
619
  </div>
633
620
  <div className="relative min-w-0 flex-1 lg:w-full">
634
621
  <IconSearch className="pointer-events-none absolute start-2.5 top-1/2 size-3.5 -translate-y-1/2 text-muted-foreground" />
@@ -660,7 +647,7 @@ function MeetingsHeader({
660
647
  aria-hidden="true"
661
648
  className="absolute end-1.5 top-1/2 h-5 -translate-y-1/2 px-1 font-mono text-[10px]"
662
649
  >
663
- {shortcutLabel("/")}
650
+ {shortcutLabel("cmd+k")}
664
651
  </Kbd>
665
652
  )}
666
653
  </div>