@agent-native/core 0.79.20 → 0.79.22

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 (141) hide show
  1. package/corpus/README.md +2 -2
  2. package/corpus/core/CHANGELOG.md +16 -0
  3. package/corpus/core/docs/content/deployment.md +133 -0
  4. package/corpus/core/docs/content/faq.md +1 -1
  5. package/corpus/core/docs/content/multi-app-workspace.md +2 -0
  6. package/corpus/core/docs/content/tracking.md +3 -3
  7. package/corpus/core/package.json +1 -1
  8. package/corpus/core/src/agent/engine/openai-compatible-endpoint.ts +25 -0
  9. package/corpus/core/src/agent/engine/provider-env-vars.ts +2 -0
  10. package/corpus/core/src/agent/engine/registry.ts +59 -11
  11. package/corpus/core/src/client/FeedbackButton.tsx +11 -1
  12. package/corpus/core/src/client/agent-engine-key.ts +57 -10
  13. package/corpus/core/src/client/analytics.ts +78 -27
  14. package/corpus/core/src/client/blocks/library/HighlightedCode.tsx +1 -1
  15. package/corpus/core/src/client/index.ts +2 -0
  16. package/corpus/core/src/client/session-replay.ts +159 -17
  17. package/corpus/core/src/client/settings/SettingsPanel.tsx +119 -4
  18. package/corpus/core/src/scripts/agent-engines/manage-agent-engine.ts +5 -0
  19. package/corpus/core/src/scripts/agent-engines/set-agent-engine.ts +8 -3
  20. package/corpus/core/src/scripts/agent-engines/test-agent-engine.ts +54 -6
  21. package/corpus/core/src/server/agent-engine-api-key-route.ts +83 -12
  22. package/corpus/core/src/server/core-routes-plugin.ts +26 -2
  23. package/corpus/core/src/styles/blocks.css +23 -7
  24. package/corpus/templates/analytics/actions/list-session-recordings.ts +1 -1
  25. package/corpus/templates/analytics/app/components/layout/MobileNav.tsx +5 -10
  26. package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +22 -4
  27. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/SqlChartCard.tsx +64 -50
  28. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/dashboard-layout.ts +38 -0
  29. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/dashboard-sync.ts +89 -0
  30. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +138 -59
  31. package/corpus/templates/analytics/changelog/2026-06-27-dashboard-charts-drag-smoothly-when-rearranging-panels.md +6 -0
  32. package/corpus/templates/analytics/changelog/2026-06-27-dashboard-email-reports-still-send-when-the-live-screenshot-.md +6 -0
  33. package/corpus/templates/analytics/changelog/2026-06-27-fixed-dashboard-edits-so-charts-no-longer-briefly-snap-back-.md +6 -0
  34. package/corpus/templates/analytics/changelog/2026-06-27-mobile-navigation-chrome-and-motion.md +6 -0
  35. package/corpus/templates/analytics/changelog/2026-06-27-session-replay-lists-now-show-only-signed-in-recordings-with.md +6 -0
  36. package/corpus/templates/analytics/changelog/2026-06-27-sidebar-loading-placeholders-are-visible-in-light-mode.md +6 -0
  37. package/corpus/templates/analytics/netlify.toml +4 -0
  38. package/corpus/templates/analytics/scripts/seed-session-replay.ts +1 -0
  39. package/corpus/templates/analytics/server/jobs/dashboard-report.ts +1 -0
  40. package/corpus/templates/analytics/server/lib/dashboard-report.ts +120 -29
  41. package/corpus/templates/analytics/server/lib/first-party-metric-catalog.ts +1 -1
  42. package/corpus/templates/analytics/server/lib/session-replay.ts +158 -46
  43. package/corpus/templates/assets/app/components/layout/Layout.tsx +1 -1
  44. package/corpus/templates/assets/changelog/2026-06-27-mobile-navigation-chrome-and-motion.md +6 -0
  45. package/corpus/templates/clips/app/components/capture-install-options.tsx +0 -9
  46. package/corpus/templates/clips/app/components/recorder/pre-record-panel.tsx +20 -4
  47. package/corpus/templates/clips/app/hooks/use-desktop-promo.ts +6 -2
  48. package/corpus/templates/clips/changelog/2026-06-27-clips-no-longer-shows-desktop-app-prompts-on-mobile-screens.md +6 -0
  49. package/corpus/templates/clips/changelog/2026-06-27-desktop-storage-setup-flow.md +6 -0
  50. package/corpus/templates/clips/changelog/2026-06-27-recorder-download-options-now-show-the-chrome-extension-and-.md +6 -0
  51. package/corpus/templates/clips/changelog/2026-06-27-recorder-setup-mobile-capture-options.md +6 -0
  52. package/corpus/templates/clips/chrome-extension/package.json +1 -1
  53. package/corpus/templates/clips/chrome-extension/src/sentry.ts +17 -1
  54. package/corpus/templates/clips/desktop/package.json +1 -0
  55. package/corpus/templates/clips/desktop/scripts/check-tauri-prereqs.mjs +45 -0
  56. package/corpus/templates/clips/desktop/src/app.tsx +32 -3
  57. package/corpus/templates/clips/desktop/src/lib/sentry.ts +71 -0
  58. package/corpus/templates/clips/desktop/src/main.tsx +2 -0
  59. package/corpus/templates/clips/desktop/src/styles.css +67 -0
  60. package/corpus/templates/clips/desktop/src-tauri/Cargo.toml +2 -1
  61. package/corpus/templates/clips/desktop/src-tauri/build.rs +29 -0
  62. package/corpus/templates/clips/desktop/src-tauri/src/lib.rs +3 -0
  63. package/corpus/templates/clips/desktop/src-tauri/src/logfile.rs +1 -0
  64. package/corpus/templates/clips/desktop/src-tauri/src/sentry_report.rs +142 -0
  65. package/corpus/templates/clips/desktop/vite.config.ts +66 -0
  66. package/corpus/templates/design/app/components/layout/Layout.tsx +1 -1
  67. package/corpus/templates/design/changelog/2026-06-27-mobile-navigation-chrome-and-motion.md +6 -0
  68. package/corpus/templates/forms/actions/export-responses.ts +3 -2
  69. package/corpus/templates/forms/actions/list-responses.ts +2 -1
  70. package/corpus/templates/forms/actions/response-insights.ts +13 -4
  71. package/corpus/templates/forms/changelog/2026-06-27-anonymous-feedback-no-longer-shows-internal-agent-native-ano.md +6 -0
  72. package/corpus/templates/forms/server/handlers/submissions.ts +5 -8
  73. package/corpus/templates/forms/server/lib/integrations.ts +9 -6
  74. package/corpus/templates/forms/shared/submitter-email.ts +20 -0
  75. package/corpus/templates/forms/shared/types.ts +1 -1
  76. package/corpus/templates/plan/app/components/plan/wireframe/html-artboard.css +49 -10
  77. package/corpus/templates/slides/app/components/layout/Layout.tsx +1 -1
  78. package/corpus/templates/slides/changelog/2026-06-27-mobile-navigation-chrome-and-motion.md +6 -0
  79. package/dist/agent/engine/openai-compatible-endpoint.d.ts +3 -0
  80. package/dist/agent/engine/openai-compatible-endpoint.d.ts.map +1 -0
  81. package/dist/agent/engine/openai-compatible-endpoint.js +23 -0
  82. package/dist/agent/engine/openai-compatible-endpoint.js.map +1 -0
  83. package/dist/agent/engine/provider-env-vars.d.ts +1 -0
  84. package/dist/agent/engine/provider-env-vars.d.ts.map +1 -1
  85. package/dist/agent/engine/provider-env-vars.js +1 -0
  86. package/dist/agent/engine/provider-env-vars.js.map +1 -1
  87. package/dist/agent/engine/registry.d.ts.map +1 -1
  88. package/dist/agent/engine/registry.js +39 -12
  89. package/dist/agent/engine/registry.js.map +1 -1
  90. package/dist/client/FeedbackButton.d.ts.map +1 -1
  91. package/dist/client/FeedbackButton.js +6 -1
  92. package/dist/client/FeedbackButton.js.map +1 -1
  93. package/dist/client/agent-engine-key.d.ts +14 -0
  94. package/dist/client/agent-engine-key.d.ts.map +1 -1
  95. package/dist/client/agent-engine-key.js +35 -10
  96. package/dist/client/agent-engine-key.js.map +1 -1
  97. package/dist/client/analytics.d.ts.map +1 -1
  98. package/dist/client/analytics.js +71 -24
  99. package/dist/client/analytics.js.map +1 -1
  100. package/dist/client/blocks/library/HighlightedCode.d.ts +1 -1
  101. package/dist/client/blocks/library/HighlightedCode.js +1 -1
  102. package/dist/client/blocks/library/HighlightedCode.js.map +1 -1
  103. package/dist/client/index.d.ts +1 -1
  104. package/dist/client/index.d.ts.map +1 -1
  105. package/dist/client/index.js +1 -1
  106. package/dist/client/index.js.map +1 -1
  107. package/dist/client/session-replay.d.ts +1 -1
  108. package/dist/client/session-replay.d.ts.map +1 -1
  109. package/dist/client/session-replay.js +115 -17
  110. package/dist/client/session-replay.js.map +1 -1
  111. package/dist/client/settings/SettingsPanel.d.ts.map +1 -1
  112. package/dist/client/settings/SettingsPanel.js +42 -6
  113. package/dist/client/settings/SettingsPanel.js.map +1 -1
  114. package/dist/collab/routes.d.ts +2 -2
  115. package/dist/file-upload/actions/upload-image.d.ts +2 -2
  116. package/dist/notifications/routes.d.ts +1 -1
  117. package/dist/observability/routes.d.ts +7 -7
  118. package/dist/progress/routes.d.ts +1 -1
  119. package/dist/resources/handlers.d.ts +1 -1
  120. package/dist/scripts/agent-engines/manage-agent-engine.d.ts.map +1 -1
  121. package/dist/scripts/agent-engines/manage-agent-engine.js +4 -0
  122. package/dist/scripts/agent-engines/manage-agent-engine.js.map +1 -1
  123. package/dist/scripts/agent-engines/set-agent-engine.d.ts.map +1 -1
  124. package/dist/scripts/agent-engines/set-agent-engine.js +5 -4
  125. package/dist/scripts/agent-engines/set-agent-engine.js.map +1 -1
  126. package/dist/scripts/agent-engines/test-agent-engine.d.ts.map +1 -1
  127. package/dist/scripts/agent-engines/test-agent-engine.js +37 -5
  128. package/dist/scripts/agent-engines/test-agent-engine.js.map +1 -1
  129. package/dist/server/agent-engine-api-key-route.d.ts +5 -5
  130. package/dist/server/agent-engine-api-key-route.d.ts.map +1 -1
  131. package/dist/server/agent-engine-api-key-route.js +71 -11
  132. package/dist/server/agent-engine-api-key-route.js.map +1 -1
  133. package/dist/server/core-routes-plugin.d.ts.map +1 -1
  134. package/dist/server/core-routes-plugin.js +20 -3
  135. package/dist/server/core-routes-plugin.js.map +1 -1
  136. package/dist/styles/blocks.css +23 -7
  137. package/docs/content/deployment.md +133 -0
  138. package/docs/content/faq.md +1 -1
  139. package/docs/content/multi-app-workspace.md +2 -0
  140. package/docs/content/tracking.md +3 -3
  141. package/package.json +1 -1
@@ -41,6 +41,7 @@ import {
41
41
  SelectValue,
42
42
  } from "@/components/ui/select";
43
43
  import { Switch } from "@/components/ui/switch";
44
+ import { useIsMobile } from "@/hooks/use-mobile";
44
45
  import {
45
46
  loadRecorderPreferences,
46
47
  saveRecorderPreferences,
@@ -192,6 +193,7 @@ export function PreRecordPanel({
192
193
  error: null,
193
194
  hasPreview: false,
194
195
  });
196
+ const isMobile = useIsMobile();
195
197
 
196
198
  const modeOptions = useMemo<ModeOption[]>(
197
199
  () => [
@@ -213,6 +215,14 @@ export function PreRecordPanel({
213
215
  ],
214
216
  [t],
215
217
  );
218
+ const visibleModeOptions = useMemo(
219
+ () =>
220
+ isMobile
221
+ ? modeOptions.filter((option) => option.value === "camera")
222
+ : modeOptions,
223
+ [isMobile, modeOptions],
224
+ );
225
+
216
226
  const surfaceOptions = useMemo<SurfaceOption[]>(
217
227
  () => [
218
228
  {
@@ -238,8 +248,14 @@ export function PreRecordPanel({
238
248
  );
239
249
 
240
250
  useEffect(() => {
251
+ if (isMobile) {
252
+ // Mobile browsers do not support the screen-capture choices this panel
253
+ // offers on desktop, so keep the setup focused on recording face video.
254
+ setMode("camera");
255
+ return;
256
+ }
241
257
  if (initialMode) setMode(initialMode);
242
- }, [initialMode]);
258
+ }, [initialMode, isMobile]);
243
259
 
244
260
  useEffect(() => {
245
261
  if (initialDisplaySurface) setDisplaySurface(initialDisplaySurface);
@@ -586,7 +602,7 @@ export function PreRecordPanel({
586
602
  <div className="mx-auto w-full max-w-lg overflow-hidden rounded-2xl border border-border bg-card shadow-lg">
587
603
  <div className="p-6">
588
604
  <div className="grid gap-2 sm:grid-cols-3">
589
- {modeOptions.map((opt) => {
605
+ {visibleModeOptions.map((opt) => {
590
606
  const Icon = opt.icon;
591
607
  const active = opt.value === mode;
592
608
  return (
@@ -600,7 +616,7 @@ export function PreRecordPanel({
600
616
  className={cn(
601
617
  "flex min-h-20 min-w-0 flex-col justify-between rounded-xl border p-3 text-start transition-colors",
602
618
  active
603
- ? "border-primary bg-primary text-primary-foreground shadow-sm"
619
+ ? "border-primary/55 bg-primary/[0.12] text-foreground shadow-sm ring-1 ring-primary/15"
604
620
  : "border-border bg-background text-foreground hover:border-foreground/30 hover:bg-muted/45",
605
621
  )}
606
622
  aria-pressed={active}
@@ -609,7 +625,7 @@ export function PreRecordPanel({
609
625
  className={cn(
610
626
  "mb-3 flex h-9 w-9 items-center justify-center rounded-full",
611
627
  active
612
- ? "bg-primary-foreground/15 text-primary-foreground"
628
+ ? "bg-primary/[0.18] text-foreground"
613
629
  : "bg-muted text-muted-foreground",
614
630
  )}
615
631
  >
@@ -1,5 +1,7 @@
1
1
  import { useCallback, useEffect, useState } from "react";
2
2
 
3
+ import { useIsMobile } from "@/hooks/use-mobile";
4
+
3
5
  const DISMISSED_KEY = "clips.desktop-promo.dismissed";
4
6
 
5
7
  function detectDesktopApp(): boolean {
@@ -17,6 +19,7 @@ function detectDesktopApp(): boolean {
17
19
  }
18
20
 
19
21
  export function useDesktopPromo() {
22
+ const isMobile = useIsMobile();
20
23
  const [isDesktopApp, setIsDesktopApp] = useState(false);
21
24
  const [dismissed, setDismissed] = useState(false);
22
25
 
@@ -40,8 +43,9 @@ export function useDesktopPromo() {
40
43
 
41
44
  return {
42
45
  isDesktopApp,
43
- shouldShowPromo: !isDesktopApp && !dismissed,
44
- shouldShowSidebarLink: !isDesktopApp,
46
+ isMobile,
47
+ shouldShowPromo: !isMobile && !isDesktopApp && !dismissed,
48
+ shouldShowSidebarLink: !isMobile && !isDesktopApp,
45
49
  dismiss,
46
50
  };
47
51
  }
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-06-27
4
+ ---
5
+
6
+ Clips no longer shows desktop app prompts on mobile screens.
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: improved
3
+ date: 2026-06-27
4
+ ---
5
+
6
+ Desktop recording now guides you to connect storage with a friendly setup flow instead of a red error.
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: improved
3
+ date: 2026-06-27
4
+ ---
5
+
6
+ Recorder download options now show the Chrome extension and desktop app choices without extra setup copy.
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: improved
3
+ date: 2026-06-27
4
+ ---
5
+
6
+ Recorder setup keeps screen-capture choices desktop-only on mobile and tones down the selected mode highlight.
@@ -11,7 +11,7 @@
11
11
  "package": "pnpm build && tsx scripts/package.ts"
12
12
  },
13
13
  "dependencies": {
14
- "@sentry/browser": "10.60.0"
14
+ "@sentry/browser": "10.62.0"
15
15
  },
16
16
  "devDependencies": {
17
17
  "@types/chrome": "^0.2.0",
@@ -74,7 +74,9 @@ function shouldDropExpectedMediaPermissionError(
74
74
  const tags = context.tags ?? {};
75
75
  const isPermissionProbe =
76
76
  tags.surface === "permission" &&
77
- (tags.permission === "mic" || tags.permission === "cam");
77
+ (!tags.permission ||
78
+ tags.permission === "mic" ||
79
+ tags.permission === "cam");
78
80
  const isOptionalBubbleCamera =
79
81
  tags.surface === "overlay" && tags.overlayPart === "bubble";
80
82
  if (!isPermissionProbe && !isOptionalBubbleCamera) return false;
@@ -87,11 +89,24 @@ function shouldDropExpectedMediaPermissionError(
87
89
  return (
88
90
  /\bNotAllowedError\b/i.test(text) ||
89
91
  /\bPermission (?:denied|dismissed)\b/i.test(text) ||
92
+ /\bPermission denied by system\b/i.test(text) ||
93
+ /\bNotReadableError\b/i.test(text) ||
94
+ /\bCould not start video source\b/i.test(text) ||
90
95
  /\b(?:NotFoundError|DevicesNotFoundError)\b/i.test(text) ||
91
96
  /\bRequested device not found\b/i.test(text)
92
97
  );
93
98
  }
94
99
 
100
+ function shouldDropExpectedMediaPermissionEvent(event: Sentry.Event): boolean {
101
+ if (event.tags?.surface !== "permission") return false;
102
+ const exceptionText = (event.exception?.values ?? [])
103
+ .map((value) => `${value.type ?? ""}: ${value.value ?? ""}`)
104
+ .join(" ");
105
+ return shouldDropExpectedMediaPermissionError(exceptionText, {
106
+ tags: { surface: "permission" },
107
+ });
108
+ }
109
+
95
110
  function scrubScopeString(value: string): string {
96
111
  const scrubbed = scrubUrl(value, { redactAllQueryValues: true });
97
112
  if (scrubbed !== value) return scrubbed;
@@ -185,6 +200,7 @@ export function initExtensionSentry(surface: string): void {
185
200
  runtime: "chrome-extension",
186
201
  surface,
187
202
  };
203
+ if (shouldDropExpectedMediaPermissionEvent(event)) return null;
188
204
  return scrubEvent(event);
189
205
  },
190
206
  });
@@ -17,6 +17,7 @@
17
17
  "dependencies": {
18
18
  "@radix-ui/react-popover": "^1.1.15",
19
19
  "@radix-ui/react-tooltip": "^1.2.8",
20
+ "@sentry/browser": "10.62.0",
20
21
  "@tabler/icons-react": "^3.26.0",
21
22
  "@tauri-apps/api": "^2.11.0",
22
23
  "@tauri-apps/plugin-fs": "2.5.1",
@@ -14,6 +14,7 @@ const RED = "\x1b[31m";
14
14
  const YELLOW = "\x1b[33m";
15
15
  const CYAN = "\x1b[36m";
16
16
  const DIM = "\x1b[2m";
17
+ const MIN_RUST_VERSION = [1, 88, 0];
17
18
 
18
19
  function has(cmd) {
19
20
  try {
@@ -107,3 +108,47 @@ if (!has("cargo")) {
107
108
 
108
109
  fail(installLines);
109
110
  }
111
+
112
+ function parseRustVersion(output) {
113
+ const match = output.match(/rustc\s+(\d+)\.(\d+)\.(\d+)/);
114
+ if (!match) return null;
115
+ return match.slice(1, 4).map((part) => Number(part));
116
+ }
117
+
118
+ function compareVersions(a, b) {
119
+ for (let i = 0; i < 3; i += 1) {
120
+ if (a[i] > b[i]) return 1;
121
+ if (a[i] < b[i]) return -1;
122
+ }
123
+ return 0;
124
+ }
125
+
126
+ let rustcOutput = "";
127
+ try {
128
+ rustcOutput = execFileSync("rustc", ["--version"], {
129
+ encoding: "utf8",
130
+ });
131
+ } catch {
132
+ fail([
133
+ "",
134
+ `${YELLOW}Cargo is installed, but ${BOLD}rustc${RESET}${YELLOW} is not available.${RESET}`,
135
+ "",
136
+ `${BOLD}Fix:${RESET} update your Rust toolchain, then retry:`,
137
+ "",
138
+ ` ${CYAN}rustup update stable${RESET}`,
139
+ ]);
140
+ }
141
+
142
+ const rustVersion = parseRustVersion(rustcOutput);
143
+ if (!rustVersion || compareVersions(rustVersion, MIN_RUST_VERSION) < 0) {
144
+ fail([
145
+ "",
146
+ `${YELLOW}Clips Desktop needs Rust ${BOLD}${MIN_RUST_VERSION.join(".")}+${RESET}${YELLOW} for the current Tauri/Sentry native SDKs.${RESET}`,
147
+ "",
148
+ `Found: ${DIM}${rustcOutput.trim() || "unknown"}${RESET}`,
149
+ "",
150
+ `${BOLD}Fix:${RESET} update Rust, then retry:`,
151
+ "",
152
+ ` ${CYAN}rustup update stable${RESET}`,
153
+ ]);
154
+ }
@@ -1687,9 +1687,7 @@ export function App() {
1687
1687
  const openVideoStorageSetup = useCallback(
1688
1688
  (targetServerUrl?: string) => {
1689
1689
  const base = (targetServerUrl?.trim() || serverUrl).replace(/\/+$/, "");
1690
- setRecError(
1691
- "Connect storage before recording from desktop: Builder.io (free tier storage + AI) or S3-compatible storage. Opening storage setup...",
1692
- );
1690
+ setRecError(STORAGE_SETUP_HELP_TEXT);
1693
1691
  void openExternal(`${base}/record`).catch((err) => {
1694
1692
  setRecError(
1695
1693
  err instanceof Error
@@ -1924,6 +1922,11 @@ export function App() {
1924
1922
  setRecError(MACOS_CAPTURE_PERMISSION_MESSAGE);
1925
1923
  return;
1926
1924
  }
1925
+ if (isStorageSetupFailureMessage(message)) {
1926
+ setRecError(STORAGE_SETUP_HELP_TEXT);
1927
+ openVideoStorageSetup();
1928
+ return;
1929
+ }
1927
1930
  setRecError(message);
1928
1931
  }
1929
1932
 
@@ -2306,6 +2309,8 @@ export function App() {
2306
2309
  panes={["speech", "microphone"]}
2307
2310
  onRetry={handleStartRecording}
2308
2311
  />
2312
+ ) : isStorageSetupFailureMessage(recError) ? (
2313
+ <StorageConnectionBanner onConnect={() => openVideoStorageSetup()} />
2309
2314
  ) : (
2310
2315
  <div className="error-banner">{recError}</div>
2311
2316
  )
@@ -2450,6 +2455,30 @@ function PermissionRecoveryBanner({
2450
2455
  );
2451
2456
  }
2452
2457
 
2458
+ function StorageConnectionBanner({ onConnect }: { onConnect: () => void }) {
2459
+ return (
2460
+ <div className="storage-flow-banner">
2461
+ <div className="storage-flow-icon" aria-hidden>
2462
+ <IconUpload size={17} stroke={1.8} />
2463
+ </div>
2464
+ <div className="storage-flow-copy">
2465
+ <div className="storage-flow-title">
2466
+ Connect storage to keep recording
2467
+ </div>
2468
+ <div className="storage-flow-sub">{STORAGE_SETUP_HELP_TEXT}</div>
2469
+ </div>
2470
+ <button
2471
+ type="button"
2472
+ className="storage-flow-connect"
2473
+ onClick={onConnect}
2474
+ >
2475
+ <IconExternalLink size={14} stroke={2} />
2476
+ Connect
2477
+ </button>
2478
+ </div>
2479
+ );
2480
+ }
2481
+
2453
2482
  function PendingUploadBanner({
2454
2483
  uploads,
2455
2484
  retryingUploadId,
@@ -0,0 +1,71 @@
1
+ import * as Sentry from "@sentry/browser";
2
+
3
+ declare const __CLIPS_DESKTOP_SENTRY_DSN__: string;
4
+ declare const __CLIPS_DESKTOP_SENTRY_ENVIRONMENT__: string;
5
+ declare const __CLIPS_DESKTOP_VERSION__: string;
6
+
7
+ let initialized = false;
8
+
9
+ function configuredDsn(): string {
10
+ return typeof __CLIPS_DESKTOP_SENTRY_DSN__ === "string"
11
+ ? __CLIPS_DESKTOP_SENTRY_DSN__.trim()
12
+ : "";
13
+ }
14
+
15
+ function configuredEnvironment(): string {
16
+ return typeof __CLIPS_DESKTOP_SENTRY_ENVIRONMENT__ === "string" &&
17
+ __CLIPS_DESKTOP_SENTRY_ENVIRONMENT__
18
+ ? __CLIPS_DESKTOP_SENTRY_ENVIRONMENT__
19
+ : "production";
20
+ }
21
+
22
+ function scrubUrl(value: string): string {
23
+ try {
24
+ const url = new URL(value);
25
+ for (const key of [...url.searchParams.keys()]) {
26
+ url.searchParams.set(key, "<redacted>");
27
+ }
28
+ return url.toString();
29
+ } catch {
30
+ return value;
31
+ }
32
+ }
33
+
34
+ function scrubEvent<T extends Sentry.Event>(event: T): T {
35
+ if (event.request?.url) {
36
+ event.request.url = scrubUrl(event.request.url);
37
+ }
38
+ if (event.user) {
39
+ delete (event.user as Record<string, unknown>).ip_address;
40
+ }
41
+ return event;
42
+ }
43
+
44
+ export function initDesktopSentry(route: string): void {
45
+ if (initialized) return;
46
+ const dsn = configuredDsn();
47
+ if (!dsn) return;
48
+ initialized = true;
49
+
50
+ Sentry.init({
51
+ dsn,
52
+ environment: configuredEnvironment(),
53
+ release: `clips-desktop@${__CLIPS_DESKTOP_VERSION__ || "0.0.0"}`,
54
+ sendDefaultPii: false,
55
+ beforeSend(event) {
56
+ event.tags = {
57
+ ...event.tags,
58
+ app: "agent-native-clips",
59
+ template: "clips",
60
+ runtime: "tauri-webview",
61
+ surface: route,
62
+ };
63
+ return scrubEvent(event);
64
+ },
65
+ });
66
+
67
+ Sentry.setTag("app", "agent-native-clips");
68
+ Sentry.setTag("template", "clips");
69
+ Sentry.setTag("runtime", "tauri-webview");
70
+ Sentry.setTag("surface", route);
71
+ }
@@ -3,6 +3,7 @@ import React from "react";
3
3
  import ReactDOM from "react-dom/client";
4
4
 
5
5
  import { App } from "./app";
6
+ import { initDesktopSentry } from "./lib/sentry";
6
7
  import { Bubble } from "./overlays/bubble";
7
8
  import { Countdown } from "./overlays/countdown";
8
9
  import { Finalizing } from "./overlays/finalizing";
@@ -211,6 +212,7 @@ const rootEl = document.getElementById("root");
211
212
  if (rootEl) {
212
213
  const route = currentRoute();
213
214
  installRouteAttributes(route);
215
+ initDesktopSentry(route);
214
216
  installConsoleCapture(route);
215
217
  installBeforeUnloadCleanup();
216
218
  installHeapDebugLog();
@@ -1680,6 +1680,73 @@ body[data-clips-route="recording-pill"] #root {
1680
1680
  background: var(--surface-hover);
1681
1681
  }
1682
1682
 
1683
+ .storage-flow-banner {
1684
+ display: grid;
1685
+ grid-template-columns: auto minmax(0, 1fr);
1686
+ align-items: start;
1687
+ gap: 10px;
1688
+ padding: 10px;
1689
+ border: 1px solid #bfdbfe;
1690
+ border-radius: var(--radius);
1691
+ background: #eff6ff;
1692
+ color: #1e3a8a;
1693
+ }
1694
+
1695
+ .storage-flow-icon {
1696
+ width: 28px;
1697
+ height: 28px;
1698
+ display: inline-flex;
1699
+ align-items: center;
1700
+ justify-content: center;
1701
+ border-radius: 8px;
1702
+ background: rgba(59, 130, 246, 0.12);
1703
+ color: #1d4ed8;
1704
+ flex-shrink: 0;
1705
+ }
1706
+
1707
+ .storage-flow-copy {
1708
+ min-width: 0;
1709
+ }
1710
+
1711
+ .storage-flow-title {
1712
+ font-size: 12px;
1713
+ font-weight: 700;
1714
+ line-height: 1.25;
1715
+ }
1716
+
1717
+ .storage-flow-sub {
1718
+ margin-top: 3px;
1719
+ color: #3b5f9a;
1720
+ font-size: 11px;
1721
+ line-height: 1.35;
1722
+ }
1723
+
1724
+ .storage-flow-connect {
1725
+ grid-column: 2;
1726
+ justify-self: start;
1727
+ height: 28px;
1728
+ border: 1px solid #bfdbfe;
1729
+ border-radius: var(--radius-sm);
1730
+ background: white;
1731
+ color: #1d4ed8;
1732
+ cursor: pointer;
1733
+ display: inline-flex;
1734
+ align-items: center;
1735
+ justify-content: center;
1736
+ gap: 5px;
1737
+ padding: 0 9px;
1738
+ font-size: 11px;
1739
+ font-weight: 700;
1740
+ transition:
1741
+ background 120ms,
1742
+ border-color 120ms;
1743
+ }
1744
+
1745
+ .storage-flow-connect:hover {
1746
+ background: #dbeafe;
1747
+ border-color: #93c5fd;
1748
+ }
1749
+
1683
1750
  .pending-upload-banner {
1684
1751
  display: grid;
1685
1752
  grid-template-columns: auto minmax(0, 1fr) auto;
@@ -5,7 +5,7 @@ description = "Clips — menu-bar tray app (Tauri 2)"
5
5
  authors = ["Clips"]
6
6
  license = "MIT"
7
7
  edition = "2021"
8
- rust-version = "1.77.2"
8
+ rust-version = "1.88"
9
9
 
10
10
  # See https://doc.rust-lang.org/cargo/reference/manifest.html
11
11
 
@@ -44,6 +44,7 @@ tauri-plugin-fs = "2.5.1"
44
44
  tauri-plugin-notification = "2"
45
45
  serde = { version = "1", features = ["derive"] }
46
46
  serde_json = "1"
47
+ sentry = "0.48.3"
47
48
  url = "2"
48
49
  # Used by meetings_watcher.rs to poll the backend on a background tokio
49
50
  # task. Tauri 2 already pulls reqwest in transitively, but we declare it
@@ -2,10 +2,39 @@ use std::path::Path;
2
2
  use std::process::Command;
3
3
 
4
4
  fn main() {
5
+ emit_sentry_env_reruns();
5
6
  add_swift_runtime_rpaths();
6
7
  tauri_build::build()
7
8
  }
8
9
 
10
+ fn emit_sentry_env_reruns() {
11
+ for name in [
12
+ "CLIPS_DESKTOP_SENTRY_DSN",
13
+ "TAURI_SENTRY_DSN",
14
+ "SENTRY_DESKTOP_DSN",
15
+ "SENTRY_CLIENT_DSN",
16
+ "VITE_SENTRY_CLIENT_DSN",
17
+ "VITE_SENTRY_DSN",
18
+ "SENTRY_DSN",
19
+ "CLIPS_DESKTOP_SENTRY_CLIENT_KEY",
20
+ "SENTRY_CLIENT_KEY",
21
+ "VITE_SENTRY_CLIENT_KEY",
22
+ "CLIPS_DESKTOP_SENTRY_PROJECT_ID",
23
+ "SENTRY_PROJECT_ID",
24
+ "VITE_SENTRY_PROJECT_ID",
25
+ "CLIPS_DESKTOP_SENTRY_INGEST_HOST",
26
+ "SENTRY_INGEST_HOST",
27
+ "VITE_SENTRY_INGEST_HOST",
28
+ "CLIPS_DESKTOP_SENTRY_ENVIRONMENT",
29
+ "SENTRY_ENVIRONMENT",
30
+ "NETLIFY_CONTEXT",
31
+ "VERCEL_ENV",
32
+ "NODE_ENV",
33
+ ] {
34
+ println!("cargo:rerun-if-env-changed={name}");
35
+ }
36
+ }
37
+
9
38
  fn add_swift_runtime_rpaths() {
10
39
  if std::env::var("CARGO_CFG_TARGET_OS").as_deref() != Ok("macos") {
11
40
  return;
@@ -16,6 +16,7 @@ mod native_speech;
16
16
  mod notifications;
17
17
  mod permission_status;
18
18
  mod recording_indicator;
19
+ mod sentry_report;
19
20
  mod shortcuts;
20
21
  mod silence_detector;
21
22
  mod state;
@@ -46,6 +47,8 @@ pub(crate) const TRAY_PNG: &[u8] = include_bytes!("../icons/tray.png");
46
47
 
47
48
  #[cfg_attr(mobile, tauri::mobile_entry_point)]
48
49
  pub fn run() {
50
+ sentry_report::init();
51
+
49
52
  tauri::Builder::default()
50
53
  .plugin(tauri_plugin_single_instance::init(|app, _argv, _cwd| {
51
54
  // Second launch just focuses the popover of the already-running
@@ -98,6 +98,7 @@ fn install_panic_hook() {
98
98
  append_line(&path, &format!("[clips-tray] panic: {info}"));
99
99
  }
100
100
  prev(info);
101
+ crate::sentry_report::flush(std::time::Duration::from_secs(2));
101
102
  }));
102
103
  }
103
104