@agent-native/core 0.175.4 → 0.175.5-nightly-20260827014436

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 (202) hide show
  1. package/corpus/templates/analytics/actions/get-explorer-dashboard.ts +1 -0
  2. package/corpus/templates/analytics/app/components/auth/AuthProvider.tsx +8 -6
  3. package/corpus/templates/analytics/app/components/dashboard/SqlChart.tsx +32 -0
  4. package/corpus/templates/analytics/app/components/layout/Layout.tsx +1 -1
  5. package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +66 -7
  6. package/corpus/templates/analytics/app/lib/prefetch-keys.ts +10 -0
  7. package/corpus/templates/analytics/app/pages/adhoc/explorer-dashboard/index.tsx +9 -7
  8. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +10 -11
  9. package/corpus/templates/analytics/changelog/2026-08-24-feature-flag-changes-recover-from-temporary-workspace-direct.md +6 -0
  10. package/corpus/templates/analytics/changelog/2026-08-26-analytics-distinguishes-daily-from-weekly-activity-shows-sta.md +6 -0
  11. package/corpus/templates/analytics/changelog/2026-08-26-dashboard-metadata-now-shows-the-original-creator-separately.md +6 -0
  12. package/corpus/templates/analytics/changelog/2026-08-26-dashboard-names-stay-editable-when-the-analytics-sidebar-ref.md +6 -0
  13. package/corpus/templates/analytics/changelog/2026-08-26-fixed-the-agent-re-asking-dashboard-scope-questions-you-had-.md +6 -0
  14. package/corpus/templates/analytics/server/db/schema.ts +2 -0
  15. package/corpus/templates/analytics/server/lib/agent-readable-resource-context.ts +8 -2
  16. package/corpus/templates/analytics/server/lib/dashboards-store.ts +5 -0
  17. package/corpus/templates/analytics/server/lib/first-party-metric-catalog.ts +6 -4
  18. package/corpus/templates/analytics/server/lib/migrate-analytics-artifacts.ts +19 -1
  19. package/corpus/templates/analytics/server/lib/workspace-feature-flags.ts +40 -9
  20. package/corpus/templates/analytics/server/plugins/db.ts +10 -0
  21. package/corpus/templates/analytics/server/routes/api/dashboard-agent-context.json.get.ts +1 -0
  22. package/corpus/templates/analytics/shared/feature-flags.ts +11 -1
  23. package/corpus/templates/calendar/app/components/calendar/GoogleConnectBanner.tsx +2 -9
  24. package/corpus/templates/clips/.agents/skills/ai-video-tools/SKILL.md +8 -7
  25. package/corpus/templates/clips/.agents/skills/recording/SKILL.md +9 -8
  26. package/corpus/templates/clips/actions/import-loom-recording.ts +1 -1
  27. package/corpus/templates/clips/actions/lib/loom-import-job.ts +52 -35
  28. package/corpus/templates/clips/actions/request-transcript.ts +6 -183
  29. package/corpus/templates/clips/app/components/clips-avatar.tsx +46 -0
  30. package/corpus/templates/clips/app/components/library/recording-card.tsx +8 -7
  31. package/corpus/templates/clips/app/components/library/space-card.tsx +8 -5
  32. package/corpus/templates/clips/app/components/meetings/attendee-stack.tsx +7 -8
  33. package/corpus/templates/clips/app/components/meetings/transcript-bubbles.tsx +19 -12
  34. package/corpus/templates/clips/app/components/player/transcript-panel.tsx +128 -94
  35. package/corpus/templates/clips/app/components/recorder/microphone-visualizer.tsx +25 -104
  36. package/corpus/templates/clips/app/components/workspace/members-list.tsx +8 -6
  37. package/corpus/templates/clips/app/components/workspace/notifications-list.tsx +8 -6
  38. package/corpus/templates/clips/app/components/workspace/top-creators-table.tsx +8 -6
  39. package/corpus/templates/clips/app/routes/_app.meetings.$meetingId.tsx +8 -7
  40. package/corpus/templates/clips/app/routes/_app.settings._index.tsx +0 -26
  41. package/corpus/templates/clips/app/routes/r.$recordingId.tsx +1 -4
  42. package/corpus/templates/clips/changelog/2026-08-26-detected-calls-no-longer-duplicate-meetings-or-strand-the-prompt.md +6 -0
  43. package/corpus/templates/clips/desktop/src/app.tsx +8 -10
  44. package/corpus/templates/clips/desktop/src/components/live-waveform.tsx +99 -0
  45. package/corpus/templates/clips/desktop/src/hooks/useMeetingTranscription.ts +248 -55
  46. package/corpus/templates/clips/desktop/src/lib/agent-steps.ts +477 -0
  47. package/corpus/templates/clips/desktop/src/lib/ask-sheet-layout.ts +71 -0
  48. package/corpus/templates/clips/desktop/src/lib/meeting-ask.ts +297 -0
  49. package/corpus/templates/clips/desktop/src/lib/url.ts +24 -0
  50. package/corpus/templates/clips/desktop/src/overlays/ask-steps.tsx +155 -0
  51. package/corpus/templates/clips/desktop/src/overlays/flow-bar.tsx +7 -81
  52. package/corpus/templates/clips/desktop/src/overlays/live-transcript.tsx +29 -2
  53. package/corpus/templates/clips/desktop/src/overlays/meeting-notification.tsx +80 -22
  54. package/corpus/templates/clips/desktop/src/overlays/record-pill.tsx +13 -53
  55. package/corpus/templates/clips/desktop/src/overlays/recording-pill.tsx +1017 -82
  56. package/corpus/templates/clips/desktop/src/styles.css +796 -57
  57. package/corpus/templates/clips/desktop/src/tailwind.css +23 -2
  58. package/corpus/templates/clips/desktop/src-tauri/src/adhoc_meetings_watcher.rs +1765 -219
  59. package/corpus/templates/clips/desktop/src-tauri/src/lib.rs +6 -0
  60. package/corpus/templates/clips/desktop/src-tauri/src/meetings_watcher.rs +45 -30
  61. package/corpus/templates/clips/desktop/src-tauri/src/native_screen/custom_capture.rs +5 -3
  62. package/corpus/templates/clips/desktop/src-tauri/src/notifications.rs +91 -0
  63. package/corpus/templates/clips/desktop/src-tauri/src/recording_indicator.rs +123 -27
  64. package/corpus/templates/clips/server/routes/_agent-native/google/callback.get.ts +16 -0
  65. package/corpus/templates/clips/shared/audio-meter.ts +145 -0
  66. package/corpus/templates/clips/shared/clips-ai-prefs.ts +0 -1
  67. package/corpus/templates/clips/shared/live-waveform.tsx +119 -0
  68. package/corpus/templates/content/app/components/editor/CommentsSidebar.tsx +44 -22
  69. package/corpus/templates/content/app/components/ui/avatar.tsx +1 -0
  70. package/corpus/templates/design/app/components/visual-editor/ReviewCanvasPins.tsx +4 -1
  71. package/corpus/templates/design/app/hooks/use-question-flow.ts +9 -2
  72. package/corpus/templates/design/app/pages/DesignSystemSetup.tsx +111 -37
  73. package/corpus/templates/design/changelog/2026-08-26-design-no-longer-asks-users-to-confirm-guided-questions-they-have-already-answered.md +6 -0
  74. package/corpus/templates/dispatch/changelog/2026-08-26-signing-out-goes-straight-to-the-sign-in-page.md +5 -0
  75. package/corpus/templates/dispatch/server/lib/org-apps-directory.ts +96 -47
  76. package/corpus/templates/dispatch/server/plugins/org-apps-directory.ts +73 -29
  77. package/corpus/templates/factory/.env.example +6 -2
  78. package/corpus/templates/factory/AGENTS.md +3 -3
  79. package/corpus/templates/factory/README.md +4 -2
  80. package/corpus/templates/factory/server/connectors/credentials.ts +70 -4
  81. package/corpus/templates/mail/actions/get-email.ts +68 -21
  82. package/corpus/templates/mail/actions/get-thread.ts +92 -51
  83. package/corpus/templates/mail/app/components/GoogleConnectBanner.tsx +2 -9
  84. package/corpus/templates/mail/app/hooks/use-emails.ts +7 -4
  85. package/corpus/templates/mail/server/lib/mail-connector-catalog.ts +7 -4
  86. package/corpus/templates/plan/app/pages/PlansPage.tsx +23 -15
  87. package/corpus/templates/slides/actions/add-deck.ts +20 -0
  88. package/corpus/templates/slides/actions/create-deck.ts +17 -7
  89. package/corpus/templates/slides/actions/delete-deck.ts +45 -5
  90. package/corpus/templates/slides/actions/get-deck.ts +77 -11
  91. package/corpus/templates/slides/actions/patch-deck.ts +14 -2
  92. package/corpus/templates/slides/actions/save-deck.ts +20 -0
  93. package/corpus/templates/slides/app/components/comments/SlideCommentsPanel.tsx +18 -7
  94. package/corpus/templates/slides/app/components/design-system/DesignSystemSetup.tsx +38 -14
  95. package/corpus/templates/slides/app/components/editor/EditorToolbar.tsx +2 -2
  96. package/corpus/templates/slides/app/components/ui/avatar.tsx +1 -0
  97. package/corpus/templates/slides/server/handlers/decks.ts +10 -0
  98. package/corpus/templates/slides/shared/slide-ids.ts +133 -0
  99. package/corpus/templates/tasks/DEVELOPING.md +8 -0
  100. package/corpus/templates/tasks/drizzle.config.ts +3 -1
  101. package/corpus/templates/tasks/drizzle.local.config.ts +6 -0
  102. package/corpus/templates/tasks/package.json +2 -1
  103. package/corpus/templates/tasks/server/db/migrations/meta/0000_snapshot.json +234 -0
  104. package/corpus/templates/tasks/server/db/migrations/meta/_journal.json +13 -0
  105. package/corpus/templates/tasks/server/db/migrations.ts +110 -0
  106. package/corpus/templates/tasks/server/plugins/db.ts +3 -87
  107. package/dist/client/AssistantChat.js +3 -10
  108. package/dist/client/guided-questions.d.ts +11 -1
  109. package/dist/client/guided-questions.js +24 -7
  110. package/dist/client/hooks/index.d.ts +2 -1
  111. package/dist/client/hooks/index.js +2 -1
  112. package/dist/client/org/OrgSwitcher.js +3 -17
  113. package/dist/client/review/ReviewThreadPanel.js +4 -2
  114. package/dist/client/sign-out.d.ts +15 -0
  115. package/dist/client/sign-out.js +82 -0
  116. package/dist/client/use-action.d.ts +2 -0
  117. package/dist/client/use-action.js +4 -2
  118. package/dist/client/use-session.d.ts +23 -1
  119. package/dist/client/use-session.js +46 -0
  120. package/dist/collab/client.js +20 -5
  121. package/dist/collab/presence.d.ts +3 -1
  122. package/dist/collab/presence.js +48 -45
  123. package/dist/collab/routes.d.ts +1 -1
  124. package/dist/db/drizzle-config.d.ts +7 -0
  125. package/dist/db/drizzle-config.js +35 -9
  126. package/dist/db/drizzle-migrations.d.ts +28 -0
  127. package/dist/db/drizzle-migrations.js +81 -0
  128. package/dist/db/index.d.ts +1 -1
  129. package/dist/db/migrations.d.ts +13 -1
  130. package/dist/db/migrations.js +60 -31
  131. package/dist/db/schema.js +4 -1
  132. package/dist/deploy/build.d.ts +6 -0
  133. package/dist/deploy/build.js +41 -0
  134. package/dist/file-upload/actions/upload-image.d.ts +1 -1
  135. package/dist/mcp/index.d.ts +1 -1
  136. package/dist/mcp/index.js +1 -1
  137. package/dist/mcp/org-directory.d.ts +19 -7
  138. package/dist/mcp/org-directory.js +92 -68
  139. package/dist/mcp/screen-memory-stdio.d.ts +7 -7
  140. package/dist/notifications/routes.d.ts +3 -3
  141. package/dist/org/context.d.ts +6 -0
  142. package/dist/org/context.js +22 -0
  143. package/dist/org/index.d.ts +1 -1
  144. package/dist/org/index.js +1 -1
  145. package/dist/resources/store.d.ts +12 -0
  146. package/dist/resources/store.js +55 -0
  147. package/dist/server/agent-discovery.d.ts +18 -2
  148. package/dist/server/agent-discovery.js +191 -22
  149. package/dist/server/auth.js +3 -2
  150. package/dist/server/builder-browser.d.ts +0 -4
  151. package/dist/server/builder-browser.js +23 -8
  152. package/dist/server/google-oauth.d.ts +18 -0
  153. package/dist/server/google-oauth.js +3 -3
  154. package/dist/server/onboarding-html.js +3 -3
  155. package/dist/server/poll.d.ts +4 -2
  156. package/dist/server/poll.js +8 -3
  157. package/dist/server/realtime-token.d.ts +1 -1
  158. package/dist/server/short-lived-token.d.ts +1 -1
  159. package/dist/server/short-lived-token.js +1 -1
  160. package/dist/shared/environment-lanes.d.ts +1 -0
  161. package/dist/shared/environment-lanes.js +1 -0
  162. package/dist/shared/google-profile-image.d.ts +2 -0
  163. package/dist/shared/google-profile-image.js +24 -0
  164. package/dist/shared/index.d.ts +1 -0
  165. package/dist/shared/index.js +1 -0
  166. package/dist/styles/agent-conversation.css +13 -0
  167. package/docs/content/creating-templates.mdx +10 -1
  168. package/docs/content/server-database.mdx +49 -3
  169. package/docs/content/server-plugins.mdx +39 -3
  170. package/docs/content/webmcp.mdx +2 -0
  171. package/package.json +2 -1
  172. package/corpus/templates/analytics/data/explorer-configs/_autosave.json +0 -18
  173. package/corpus/templates/analytics/data/explorer-configs/steve-chart.json +0 -18
  174. package/corpus/templates/analytics/data/explorer-dashboards/dashboard-1773260837897.json +0 -10
  175. package/corpus/templates/analytics/data/sync-config.json +0 -8
  176. package/corpus/templates/brain/data/sync-config.json +0 -1
  177. package/corpus/templates/calendar/data/availability.json +0 -32
  178. package/corpus/templates/calendar/data/settings.json +0 -6
  179. package/corpus/templates/calendar/data/sync-config.json +0 -10
  180. package/corpus/templates/chat/data/sync-config.json +0 -1
  181. package/corpus/templates/clips/data/sync-config.json +0 -1
  182. package/corpus/templates/clips/desktop/src/lib/audio-meter.ts +0 -80
  183. package/corpus/templates/clips/desktop/src/overlays/live-audio-bars.tsx +0 -113
  184. package/corpus/templates/design/data/sync-config.json +0 -1
  185. package/corpus/templates/factory/data/sync-config.json +0 -1
  186. package/corpus/templates/forms/data/settings.json +0 -8
  187. package/corpus/templates/forms/data/sync-config.json +0 -3
  188. package/corpus/templates/macros/data/sync-config.json +0 -4
  189. package/corpus/templates/mail/data/emails.defaults.json +0 -1
  190. package/corpus/templates/mail/data/labels.defaults.json +0 -59
  191. package/corpus/templates/mail/data/settings.defaults.json +0 -9
  192. package/corpus/templates/mail/data/sync-config.json +0 -8
  193. package/corpus/templates/slides/data/sync-config.json +0 -8
  194. package/corpus/templates/tasks/data/sync-config.json +0 -1
  195. package/dist/templates/chat/data/.gitkeep +0 -0
  196. package/dist/templates/chat/data/sync-config.json +0 -1
  197. package/dist/templates/default/data/.gitkeep +0 -0
  198. package/dist/templates/default/data/sync-config.json +0 -1
  199. package/src/templates/chat/data/.gitkeep +0 -0
  200. package/src/templates/chat/data/sync-config.json +0 -1
  201. package/src/templates/default/data/.gitkeep +0 -0
  202. package/src/templates/default/data/sync-config.json +0 -1
@@ -1,4 +1,5 @@
1
1
  import { useT } from "@agent-native/core/client/i18n";
2
+ import { LiveWaveform } from "@shared/live-waveform";
2
3
  import { useCallback, useEffect, useRef, useState } from "react";
3
4
 
4
5
  import { Button } from "@/components/ui/button";
@@ -142,13 +143,13 @@ export function MicrophoneVisualizer({
142
143
  onSignalChange,
143
144
  }: MicrophoneVisualizerProps) {
144
145
  const t = useT();
145
- const canvasRef = useRef<HTMLCanvasElement | null>(null);
146
146
  const rafRef = useRef<number | null>(null);
147
147
  const runIdRef = useRef(0);
148
148
  const streamRef = useRef<MediaStream | null>(null);
149
149
  const audioContextRef = useRef<AudioContext | null>(null);
150
150
  const sourceRef = useRef<MediaStreamAudioSourceNode | null>(null);
151
151
  const signalRef = useRef(false);
152
+ const [level, setLevel] = useState<number | null>(null);
152
153
  const lastSignalAtRef = useRef(0);
153
154
  const previousDeviceIdRef = useRef(deviceId);
154
155
 
@@ -166,49 +167,6 @@ export function MicrophoneVisualizer({
166
167
  [onSignalChange],
167
168
  );
168
169
 
169
- const syncCanvasSize = useCallback((canvas: HTMLCanvasElement) => {
170
- const rect = canvas.getBoundingClientRect();
171
- const dpr = window.devicePixelRatio || 1;
172
- const width = Math.max(1, Math.floor(rect.width * dpr));
173
- const height = Math.max(1, Math.floor(rect.height * dpr));
174
- if (canvas.width !== width || canvas.height !== height) {
175
- canvas.width = width;
176
- canvas.height = height;
177
- }
178
- return { width, height, dpr };
179
- }, []);
180
-
181
- const drawIdle = useCallback(() => {
182
- const canvas = canvasRef.current;
183
- if (!canvas) return;
184
- const ctx = canvas.getContext("2d");
185
- if (!ctx) return;
186
- const { width, height, dpr } = syncCanvasSize(canvas);
187
- ctx.clearRect(0, 0, width, height);
188
-
189
- ctx.strokeStyle = "rgba(14, 165, 233, 0.24)";
190
- ctx.lineWidth = Math.max(1, dpr);
191
- ctx.beginPath();
192
- ctx.moveTo(0, height / 2);
193
- ctx.lineTo(width, height / 2);
194
- ctx.stroke();
195
-
196
- ctx.strokeStyle = "rgba(14, 165, 233, 0.42)";
197
- ctx.lineWidth = Math.max(1.75, 1.75 * dpr);
198
- ctx.beginPath();
199
- const points = 72;
200
- for (let i = 0; i <= points; i++) {
201
- const x = (i / points) * width;
202
- const envelope = Math.sin((i / points) * Math.PI);
203
- const y =
204
- height / 2 +
205
- Math.sin((i / points) * Math.PI * 6) * envelope * height * 0.13;
206
- if (i === 0) ctx.moveTo(x, y);
207
- else ctx.lineTo(x, y);
208
- }
209
- ctx.stroke();
210
- }, [syncCanvasSize]);
211
-
212
170
  const stopCurrent = useCallback(
213
171
  (emitSignal = true) => {
214
172
  if (rafRef.current !== null) {
@@ -234,21 +192,15 @@ export function MicrophoneVisualizer({
234
192
  } else {
235
193
  signalRef.current = false;
236
194
  }
237
- drawIdle();
238
195
  },
239
- [drawIdle, setSignal],
196
+ [setSignal],
240
197
  );
241
198
 
242
199
  const drawLive = useCallback(
243
200
  (analyser: AnalyserNode) => {
244
- const canvas = canvasRef.current;
245
- if (!canvas) return;
246
- const ctx = canvas.getContext("2d");
247
- if (!ctx) return;
248
201
  const data = new Uint8Array(analyser.fftSize);
249
202
 
250
203
  const draw = () => {
251
- const { width, height, dpr } = syncCanvasSize(canvas);
252
204
  analyser.getByteTimeDomainData(data);
253
205
 
254
206
  let sum = 0;
@@ -265,44 +217,16 @@ export function MicrophoneVisualizer({
265
217
  setSignal(false);
266
218
  }
267
219
 
268
- ctx.clearRect(0, 0, width, height);
269
- ctx.strokeStyle = "rgba(14, 165, 233, 0.18)";
270
- ctx.lineWidth = Math.max(1, dpr);
271
- ctx.beginPath();
272
- ctx.moveTo(0, height / 2);
273
- ctx.lineTo(width, height / 2);
274
- ctx.stroke();
275
-
276
- const gradient = ctx.createLinearGradient(0, 0, width, 0);
277
- gradient.addColorStop(0, "rgba(14, 165, 233, 0.74)");
278
- gradient.addColorStop(0.5, "rgba(34, 211, 238, 1)");
279
- gradient.addColorStop(1, "rgba(37, 99, 235, 0.82)");
280
- ctx.strokeStyle = gradient;
281
- ctx.lineWidth = Math.max(2.25 * dpr, Math.min(6 * dpr, rms * 34 * dpr));
282
- ctx.shadowColor = "rgba(14, 165, 233, 0.35)";
283
- ctx.shadowBlur = Math.max(4 * dpr, Math.min(12 * dpr, rms * 80 * dpr));
284
- ctx.lineJoin = "round";
285
- ctx.lineCap = "round";
286
- ctx.beginPath();
287
- const step = width / Math.max(1, data.length - 1);
288
- const gain = Math.min(5.5, 2.6 + rms * 24);
289
- for (let i = 0; i < data.length; i++) {
290
- const x = i * step;
291
- const normalized = ((data[i] - 128) / 128) * gain;
292
- const y =
293
- height / 2 + Math.max(-1, Math.min(1, normalized)) * height * 0.42;
294
- if (i === 0) ctx.moveTo(x, y);
295
- else ctx.lineTo(x, y);
296
- }
297
- ctx.stroke();
298
- ctx.shadowBlur = 0;
299
-
220
+ // The analyser is the only part of the meter this app owns; the shape
221
+ // and the level math are shared with the desktop app so a microphone
222
+ // reads the same here as it does in the recorder and the meeting pill.
223
+ setLevel(rms);
300
224
  rafRef.current = requestAnimationFrame(draw);
301
225
  };
302
226
 
303
227
  draw();
304
228
  },
305
- [setSignal, syncCanvasSize],
229
+ [setSignal],
306
230
  );
307
231
 
308
232
  const stopTest = useCallback(() => {
@@ -415,17 +339,8 @@ export function MicrophoneVisualizer({
415
339
  setError(message);
416
340
  setStatus("error");
417
341
  onStatusChange?.("error", { error: message });
418
- drawIdle();
419
342
  }
420
- }, [
421
- deviceId,
422
- disabled,
423
- drawIdle,
424
- drawLive,
425
- onStatusChange,
426
- setSignal,
427
- stopCurrent,
428
- ]);
343
+ }, [deviceId, disabled, drawLive, onStatusChange, setSignal, stopCurrent]);
429
344
 
430
345
  useEffect(() => {
431
346
  if (disabled) {
@@ -433,7 +348,6 @@ export function MicrophoneVisualizer({
433
348
  if (status === "live" || status === "starting") {
434
349
  stopTest();
435
350
  } else {
436
- drawIdle();
437
351
  }
438
352
  return;
439
353
  }
@@ -441,14 +355,14 @@ export function MicrophoneVisualizer({
441
355
  previousDeviceIdRef.current = deviceId;
442
356
  if (status === "live" || status === "starting") {
443
357
  void startTest();
444
- } else {
445
- drawIdle();
446
358
  }
447
- }, [deviceId, disabled, drawIdle, startTest, status, stopTest]);
359
+ }, [deviceId, disabled, startTest, status, stopTest]);
448
360
 
361
+ // Idle and error both rest the meter by holding `level` at null, so there is
362
+ // nothing left to draw on a state change.
449
363
  useEffect(() => {
450
- if (status === "idle" || status === "error") drawIdle();
451
- }, [drawIdle, status]);
364
+ if (status === "idle" || status === "error") setLevel(null);
365
+ }, [status]);
452
366
 
453
367
  useEffect(() => {
454
368
  return () => {
@@ -480,11 +394,18 @@ export function MicrophoneVisualizer({
480
394
  live && hasSignal ? "border-foreground/35" : "border-border",
481
395
  )}
482
396
  >
483
- <canvas
484
- ref={canvasRef}
397
+ <span
398
+ role="img"
485
399
  aria-label={t("clipsFinalRaw.selectedMicrophoneWaveform")}
486
- className="h-full w-full opacity-75"
487
- />
400
+ className="absolute inset-0 flex items-center justify-center"
401
+ >
402
+ <LiveWaveform
403
+ level={live ? level : null}
404
+ bars={18}
405
+ barWidth={2}
406
+ barGap={3}
407
+ />
408
+ </span>
488
409
  {statusLabel ? (
489
410
  <span
490
411
  className={cn(
@@ -5,6 +5,7 @@ import { useQueryClient } from "@tanstack/react-query";
5
5
  import { useState } from "react";
6
6
  import { toast } from "sonner";
7
7
 
8
+ import { ClipsAvatar } from "@/components/clips-avatar";
8
9
  import {
9
10
  AlertDialog,
10
11
  AlertDialogAction,
@@ -15,7 +16,6 @@ import {
15
16
  AlertDialogHeader,
16
17
  AlertDialogTitle,
17
18
  } from "@/components/ui/alert-dialog";
18
- import { Avatar, AvatarFallback } from "@/components/ui/avatar";
19
19
  import { Badge } from "@/components/ui/badge";
20
20
  import { Button } from "@/components/ui/button";
21
21
  import {
@@ -152,11 +152,13 @@ export function MembersList({
152
152
  <TableRow key={m.id}>
153
153
  <TableCell>
154
154
  <div className="flex items-center gap-2 min-w-0">
155
- <Avatar className="h-8 w-8 flex-shrink-0">
156
- <AvatarFallback className="text-xs bg-primary text-primary-foreground">
157
- {initials(m.email)}
158
- </AvatarFallback>
159
- </Avatar>
155
+ <ClipsAvatar
156
+ email={m.email}
157
+ alt={m.email}
158
+ fallback={initials(m.email)}
159
+ className="h-8 w-8 flex-shrink-0"
160
+ fallbackClassName="text-xs bg-primary text-primary-foreground"
161
+ />
160
162
  <div className="min-w-0">
161
163
  <div className="truncate font-medium flex items-center gap-1.5">
162
164
  {m.email}
@@ -9,7 +9,7 @@ import {
9
9
  } from "@tabler/icons-react";
10
10
  import { Link } from "react-router";
11
11
 
12
- import { Avatar, AvatarFallback } from "@/components/ui/avatar";
12
+ import { ClipsAvatar } from "@/components/clips-avatar";
13
13
 
14
14
  export type NotificationKind = "comment" | "reaction" | "mention" | "share";
15
15
 
@@ -62,11 +62,13 @@ export function NotificationsList({ items, onReply }: NotificationsListProps) {
62
62
  <ul className="divide-y">
63
63
  {items.map((item) => (
64
64
  <li key={item.id} className="py-3 flex items-start gap-3">
65
- <Avatar className="h-9 w-9 flex-shrink-0">
66
- <AvatarFallback className="text-xs bg-primary text-primary-foreground">
67
- {initials(item.authorEmail)}
68
- </AvatarFallback>
69
- </Avatar>
65
+ <ClipsAvatar
66
+ email={item.authorEmail}
67
+ alt={item.authorEmail ?? t("clipsFinalRaw.someone")}
68
+ fallback={initials(item.authorEmail)}
69
+ className="h-9 w-9 flex-shrink-0"
70
+ fallbackClassName="text-xs bg-primary text-primary-foreground"
71
+ />
70
72
  <div className="min-w-0 flex-1">
71
73
  <div className="flex items-center gap-2 text-sm">
72
74
  <KindIcon kind={item.kind} />
@@ -1,6 +1,6 @@
1
1
  import { useT } from "@agent-native/core/client/i18n";
2
2
 
3
- import { Avatar, AvatarFallback } from "@/components/ui/avatar";
3
+ import { ClipsAvatar } from "@/components/clips-avatar";
4
4
  import {
5
5
  Table,
6
6
  TableBody,
@@ -58,11 +58,13 @@ export function TopCreatorsTable({ rows }: TopCreatorsTableProps) {
58
58
  <TableRow key={row.email}>
59
59
  <TableCell>
60
60
  <div className="flex items-center gap-2 min-w-0">
61
- <Avatar className="h-7 w-7 flex-shrink-0">
62
- <AvatarFallback className="text-xs bg-primary text-primary-foreground">
63
- {initials(row.email)}
64
- </AvatarFallback>
65
- </Avatar>
61
+ <ClipsAvatar
62
+ email={row.email}
63
+ alt={row.email}
64
+ fallback={initials(row.email)}
65
+ className="h-7 w-7 flex-shrink-0"
66
+ fallbackClassName="text-xs bg-primary text-primary-foreground"
67
+ />
66
68
  <span className="truncate">{row.email}</span>
67
69
  </div>
68
70
  </TableCell>
@@ -28,6 +28,7 @@ import { useEffect, useMemo, useRef, useState } from "react";
28
28
  import { NavLink, useNavigate, useParams } from "react-router";
29
29
  import { toast } from "sonner";
30
30
 
31
+ import { ClipsAvatar } from "@/components/clips-avatar";
31
32
  import { PageHeader } from "@/components/library/page-header";
32
33
  import {
33
34
  AttendeeStack,
@@ -52,7 +53,6 @@ import {
52
53
  AlertDialogHeader,
53
54
  AlertDialogTitle,
54
55
  } from "@/components/ui/alert-dialog";
55
- import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
56
56
  import { Badge } from "@/components/ui/badge";
57
57
  import { Button } from "@/components/ui/button";
58
58
  import {
@@ -258,12 +258,13 @@ function ActionItemsByPerson({
258
258
  {grouped.map(([who, list]) => (
259
259
  <div key={who} className="space-y-1.5">
260
260
  <div className="flex items-center gap-2">
261
- <Avatar className="h-5 w-5">
262
- <AvatarImage alt={who} />
263
- <AvatarFallback className="text-[9px]">
264
- {attendeeInitials(who || t("meetingDetail.unassigned"))}
265
- </AvatarFallback>
266
- </Avatar>
261
+ <ClipsAvatar
262
+ email={who || null}
263
+ alt={who || t("meetingDetail.unassigned")}
264
+ fallback={attendeeInitials(who || t("meetingDetail.unassigned"))}
265
+ className="h-5 w-5"
266
+ fallbackClassName="text-[9px]"
267
+ />
267
268
  <span className="text-xs font-medium">
268
269
  {who || t("meetingDetail.unassigned")}
269
270
  </span>
@@ -48,7 +48,6 @@ const SPEEDS = ["1", "1.2", "1.5", "1.75", "2"];
48
48
  interface ClipsUserSettings {
49
49
  defaultPlaybackSpeed?: string;
50
50
  emailNotifications?: boolean;
51
- transcriptCleanupEnabled?: boolean;
52
51
  includeFullVideoInAi?: boolean;
53
52
  defaultRecordingVisibility?: ClipsDefaultVisibility;
54
53
  }
@@ -133,9 +132,6 @@ export default function SettingsIndexRoute() {
133
132
  const [emailNotifications, setEmailNotifications] = useState(true);
134
133
  const [defaultVisibility, setDefaultVisibility] =
135
134
  useState<ClipsDefaultVisibility>(DEFAULT_CLIPS_RECORDING_VISIBILITY);
136
- const [transcriptCleanupEnabled, setTranscriptCleanupEnabled] =
137
- useState(true);
138
-
139
135
  useEffect(() => {
140
136
  let cancelled = false;
141
137
  loadSettings().then((v) => {
@@ -145,7 +141,6 @@ export default function SettingsIndexRoute() {
145
141
  setDefaultVisibility(
146
142
  v.defaultRecordingVisibility ?? DEFAULT_CLIPS_RECORDING_VISIBILITY,
147
143
  );
148
- setTranscriptCleanupEnabled(v.transcriptCleanupEnabled !== false);
149
144
  setLoading(false);
150
145
  });
151
146
  return () => {
@@ -194,7 +189,6 @@ export default function SettingsIndexRoute() {
194
189
  await saveSettings({
195
190
  defaultPlaybackSpeed: defaultSpeed,
196
191
  emailNotifications,
197
- transcriptCleanupEnabled,
198
192
  defaultRecordingVisibility: defaultVisibility,
199
193
  });
200
194
  toast.success(t("settings.saved"));
@@ -249,12 +243,6 @@ export default function SettingsIndexRoute() {
249
243
  keywords: "playback speed video default",
250
244
  hash: "playback",
251
245
  },
252
- {
253
- id: "clips-transcript",
254
- label: t("settings.transcript"),
255
- keywords: "transcript cleanup captions",
256
- hash: "transcript",
257
- },
258
246
  {
259
247
  id: "clips-sharing",
260
248
  label: t("settings.sharing"),
@@ -356,20 +344,6 @@ export default function SettingsIndexRoute() {
356
344
  </Select>
357
345
  }
358
346
  />
359
- <SettingsRow
360
- id="transcript"
361
- label={t("settings.transcriptCleanup")}
362
- description={t("settings.transcriptCleanupDescription")}
363
- control={
364
- <Switch
365
- id="transcript-cleanup"
366
- aria-label={t("settings.transcriptCleanup")}
367
- checked={transcriptCleanupEnabled}
368
- onCheckedChange={setTranscriptCleanupEnabled}
369
- disabled={loading}
370
- />
371
- }
372
- />
373
347
  <SettingsRow
374
348
  id="notifications"
375
349
  label={t("settings.emailNotifications")}
@@ -388,7 +388,6 @@ export default function RecordingPage() {
388
388
  // Also keep polling while a transcript is pending so "Transcribing…"
389
389
  // auto-flips to the ready transcript (or to the failure card).
390
390
  if (data?.transcript?.status === "pending") return 3000;
391
- if (data?.transcript?.cleanup?.status === "running") return 2000;
392
391
  // And keep polling while the title is still the server-seeded
393
392
  // default — the agent will land a generated title via
394
393
  // `update-recording` and we want the skeleton to swap in promptly.
@@ -460,7 +459,6 @@ export default function RecordingPage() {
460
459
  const transcriptFullText = playerDataQ.data?.transcript?.fullText ?? null;
461
460
  const transcriptStatus = playerDataQ.data?.transcript?.status;
462
461
  const transcriptFailureReason = playerDataQ.data?.transcript?.failureReason;
463
- const transcriptCleanup = playerDataQ.data?.transcript?.cleanup ?? null;
464
462
  const ctas = playerDataQ.data?.ctas ?? [];
465
463
  const canEdit = role === "owner" || role === "admin" || role === "editor";
466
464
  // Reaching this page already requires a signed-in session with at least
@@ -1265,7 +1263,7 @@ export default function RecordingPage() {
1265
1263
  </TabsContent>
1266
1264
  <TabsContent
1267
1265
  value="transcript"
1268
- className="flex-1 min-h-0 mt-3 data-[state=inactive]:hidden"
1266
+ className="mt-0 flex-1 min-h-0 data-[state=inactive]:hidden"
1269
1267
  >
1270
1268
  <TranscriptPanel
1271
1269
  segments={transcriptSegments}
@@ -1279,7 +1277,6 @@ export default function RecordingPage() {
1279
1277
  : transcriptStatus
1280
1278
  }
1281
1279
  failureReason={transcriptFailureReason}
1282
- cleanup={transcriptCleanup}
1283
1280
  recordingTitle={recording.title}
1284
1281
  onRetry={
1285
1282
  canEdit
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-08-26
4
+ ---
5
+
6
+ Detected Zoom and Teams calls no longer create duplicate meetings or leave a prompt on screen after recording starts
@@ -160,7 +160,11 @@ import {
160
160
  retryUpdateCheck,
161
161
  useUpdateStatus,
162
162
  } from "./lib/updater";
163
- import { normalizeServerUrl } from "./lib/url";
163
+ import {
164
+ DEFAULT_SERVER_URL,
165
+ normalizeServerUrl,
166
+ SERVER_URL_STORAGE_KEY,
167
+ } from "./lib/url";
164
168
  import { cn } from "./lib/utils";
165
169
  import {
166
170
  installDesktopVoiceDictation,
@@ -392,7 +396,8 @@ function isStorageSetupFailureMessage(message: string | null | undefined) {
392
396
  return STORAGE_SETUP_FAILURE_RE.test(message ?? "");
393
397
  }
394
398
 
395
- const STORAGE_KEY = "clips:server-url";
399
+ // Shared with overlays via lib/url.ts — the meeting pill reads the same key.
400
+ const STORAGE_KEY = SERVER_URL_STORAGE_KEY;
396
401
  const MODE_KEY = "clips:last-mode";
397
402
  const VOICE_SHORTCUT_KEY = "clips:voice-shortcut";
398
403
  const VOICE_SHORTCUT_CONFIGURED_KEY = "clips:voice-shortcut-configured";
@@ -414,14 +419,7 @@ const VIDEO_STORAGE_CONFIGURED_KEY = "clips:video-storage-configured";
414
419
  const REWIND_DOCS_URL =
415
420
  "https://www.agent-native.com/docs/template-clips-capture-everywhere#rewind";
416
421
 
417
- // Sensible defaults so the user never has to type a URL on first launch.
418
- // Dev builds point at the local dev server; production builds point at the
419
- // hosted Clips instance. The user can still override from Settings.
420
- // Dev points at the Clips dev server (shared-app-config says 8094).
421
- // Prod points at the hosted Clips instance. User can override from Settings.
422
- const DEFAULT_URL = import.meta.env.DEV
423
- ? "http://localhost:8094"
424
- : "https://clips.agent-native.com";
422
+ const DEFAULT_URL = DEFAULT_SERVER_URL;
425
423
 
426
424
  function normalizeCaptureSource(value: string): CaptureSource {
427
425
  if (value === "region" && isMacPlatform()) return "region";
@@ -0,0 +1,99 @@
1
+ /**
2
+ * The shared meter, wired to desktop capture.
3
+ *
4
+ * The shape and the level math live in `shared/live-waveform.tsx` with the web
5
+ * app; only the transport is here, because that is the one part that genuinely
6
+ * differs — Tauri emits `voice:audio-level`, the browser reads an
7
+ * `AnalyserNode`. Anything visual belongs in the shared component so the two
8
+ * apps cannot drift apart again.
9
+ */
10
+
11
+ import { listen } from "@tauri-apps/api/event";
12
+ import { useEffect, useRef, useState } from "react";
13
+
14
+ import {
15
+ combinedMeterLevel,
16
+ EMPTY_METER_SOURCES,
17
+ foldMeterSources,
18
+ type MeterSource,
19
+ type MeterSourceLevels,
20
+ } from "../../../shared/audio-meter";
21
+ import { LiveWaveform as SharedLiveWaveform } from "../../../shared/live-waveform";
22
+
23
+ interface LiveWaveformProps {
24
+ className?: string;
25
+ bars?: number;
26
+ barWidth?: number;
27
+ barGap?: number;
28
+ /** Drive from a caller-supplied level instead of capture events (demos). */
29
+ level?: number | null;
30
+ dimmed?: boolean;
31
+ /**
32
+ * Which capture to meter. "all" rides whichever of mic and system audio is
33
+ * louder, which is what a meeting is; "mic" answers the narrower question a
34
+ * recorder asks — am I being heard.
35
+ */
36
+ sources?: "all" | "mic";
37
+ }
38
+
39
+ export function LiveWaveform({
40
+ className,
41
+ bars,
42
+ barWidth,
43
+ barGap,
44
+ level = null,
45
+ dimmed = false,
46
+ sources = "all",
47
+ }: LiveWaveformProps) {
48
+ const [captured, setCaptured] = useState<number | null>(null);
49
+ const sourcesRef = useRef<MeterSourceLevels>(EMPTY_METER_SOURCES);
50
+ const external = level !== null && level !== undefined;
51
+
52
+ useEffect(() => {
53
+ if (external) return;
54
+ let stopped = false;
55
+ let unlisten: (() => void) | null = null;
56
+ // Mic and system audio interleave on one event and each decays on its own
57
+ // frames: a shared level lets a silent mic halve whoever is actually
58
+ // talking on every other event, which pins the meter to its floor.
59
+ listen<{ level?: number; source?: MeterSource }>(
60
+ "voice:audio-level",
61
+ (event) => {
62
+ const source: MeterSource =
63
+ event.payload?.source === "system" ? "system" : "mic";
64
+ if (sources === "mic" && source !== "mic") return;
65
+ sourcesRef.current = foldMeterSources(
66
+ sourcesRef.current,
67
+ source,
68
+ Number(event.payload?.level),
69
+ );
70
+ setCaptured(
71
+ sources === "mic"
72
+ ? sourcesRef.current.mic
73
+ : combinedMeterLevel(sourcesRef.current),
74
+ );
75
+ },
76
+ )
77
+ .then((cleanup) => {
78
+ if (stopped) cleanup();
79
+ else unlisten = cleanup;
80
+ })
81
+ .catch(() => {});
82
+
83
+ return () => {
84
+ stopped = true;
85
+ unlisten?.();
86
+ };
87
+ }, [external, sources]);
88
+
89
+ return (
90
+ <SharedLiveWaveform
91
+ level={external ? level : captured}
92
+ className={className}
93
+ bars={bars}
94
+ barWidth={barWidth}
95
+ barGap={barGap}
96
+ dimmed={dimmed}
97
+ />
98
+ );
99
+ }