@agent-native/core 0.130.1 → 0.130.2

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 (57) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +6 -0
  3. package/corpus/core/package.json +1 -1
  4. package/corpus/core/src/server/email-template.ts +43 -13
  5. package/corpus/core/src/sharing/actions/share-resource.ts +26 -4
  6. package/corpus/core/src/sharing/index.ts +1 -0
  7. package/corpus/core/src/sharing/registry.ts +23 -0
  8. package/corpus/templates/clips/app/components/editor/editor-toolbar.tsx +3 -1
  9. package/corpus/templates/clips/app/components/library/library-layout.tsx +1 -1
  10. package/corpus/templates/clips/app/components/meetings/day-header.tsx +1 -1
  11. package/corpus/templates/clips/app/components/meetings/transcript-bubbles.tsx +1 -22
  12. package/corpus/templates/clips/app/components/workspace/branding-editor.tsx +26 -1
  13. package/corpus/templates/clips/app/components/workspace/organization-identity-card.tsx +107 -0
  14. package/corpus/templates/clips/app/i18n/en-US.ts +3 -4
  15. package/corpus/templates/clips/app/routes/_app.meetings.$meetingId.tsx +0 -10
  16. package/corpus/templates/clips/app/routes/_app.settings._index.tsx +23 -51
  17. package/corpus/templates/clips/app/routes/share.meeting.$meetingId.tsx +1 -8
  18. package/corpus/templates/clips/changelog/2026-07-29-clearing-all-recording-edits-no-longer-leaves-the-editor-unr.md +6 -0
  19. package/corpus/templates/clips/changelog/2026-07-29-clip-share-emails-now-open-with-who-shared-the-clip-add-a-su.md +6 -0
  20. package/corpus/templates/clips/changelog/2026-07-29-finished-meeting-pills-now-have-a-clear-close-button.md +6 -0
  21. package/corpus/templates/clips/changelog/2026-07-29-meeting-cards-no-longer-flicker-or-disappear-when-hovered-in.md +6 -0
  22. package/corpus/templates/clips/changelog/2026-07-29-organization-name-logo-brand-color-and-default-visibility-no.md +6 -0
  23. package/corpus/templates/clips/changelog/2026-07-29-transcript-lines-on-the-meeting-page-no-longer-jump-to-the-r.md +6 -0
  24. package/corpus/templates/clips/changelog/2026-07-29-when-a-meeting-wraps-up-the-recording-pill-now-shows-a-green.md +6 -0
  25. package/corpus/templates/clips/desktop/src/hooks/useMeetingTranscription.ts +6 -3
  26. package/corpus/templates/clips/desktop/src/overlays/recording-pill.tsx +112 -32
  27. package/corpus/templates/clips/desktop/src/styles.css +42 -2
  28. package/corpus/templates/clips/server/db/index.ts +6 -0
  29. package/corpus/templates/clips/server/lib/share-email-hero.ts +33 -0
  30. package/corpus/templates/clips/shared/clips-ai-prefs.ts +0 -1
  31. package/dist/collab/awareness.d.ts +2 -2
  32. package/dist/collab/awareness.d.ts.map +1 -1
  33. package/dist/collab/struct-routes.d.ts +1 -1
  34. package/dist/file-upload/actions/upload-image.d.ts +1 -1
  35. package/dist/notifications/routes.d.ts +3 -3
  36. package/dist/progress/routes.d.ts +1 -1
  37. package/dist/resources/handlers.d.ts +1 -1
  38. package/dist/secrets/routes.d.ts +9 -9
  39. package/dist/server/email-template.d.ts +4 -0
  40. package/dist/server/email-template.d.ts.map +1 -1
  41. package/dist/server/email-template.js +25 -8
  42. package/dist/server/email-template.js.map +1 -1
  43. package/dist/sharing/actions/share-resource.d.ts.map +1 -1
  44. package/dist/sharing/actions/share-resource.js +23 -4
  45. package/dist/sharing/actions/share-resource.js.map +1 -1
  46. package/dist/sharing/index.d.ts +1 -1
  47. package/dist/sharing/index.d.ts.map +1 -1
  48. package/dist/sharing/index.js.map +1 -1
  49. package/dist/sharing/registry.d.ts +23 -0
  50. package/dist/sharing/registry.d.ts.map +1 -1
  51. package/dist/sharing/registry.js.map +1 -1
  52. package/package.json +1 -1
  53. package/src/server/email-template.ts +43 -13
  54. package/src/sharing/actions/share-resource.ts +26 -4
  55. package/src/sharing/index.ts +1 -0
  56. package/src/sharing/registry.ts +23 -0
  57. package/corpus/templates/clips/app/routes/_app.settings.organization.tsx +0 -232
@@ -3,9 +3,9 @@ import {
3
3
  appApiPath,
4
4
  } from "@agent-native/core/client/api-path";
5
5
  import { ChangelogSettingsCard } from "@agent-native/core/client/changelog";
6
- import { useSession, useActionQuery } from "@agent-native/core/client/hooks";
6
+ import { useActionQuery } from "@agent-native/core/client/hooks";
7
7
  import { LanguagePicker, useT } from "@agent-native/core/client/i18n";
8
- import { TeamPage, useOrg, useSwitchOrg } from "@agent-native/core/client/org";
8
+ import { useOrg, useSwitchOrg } from "@agent-native/core/client/org";
9
9
  import {
10
10
  AccountSettingsCard,
11
11
  useBuilderConnectFlow,
@@ -30,7 +30,6 @@ import {
30
30
  IconLoader2,
31
31
  IconServer,
32
32
  IconTrash,
33
- IconUser,
34
33
  IconUsersGroup,
35
34
  } from "@tabler/icons-react";
36
35
  import { useCallback, useEffect, useMemo, useState } from "react";
@@ -71,6 +70,7 @@ import {
71
70
  SelectValue,
72
71
  } from "@/components/ui/select";
73
72
  import { Switch } from "@/components/ui/switch";
73
+ import { OrganizationIdentityCard } from "@/components/workspace/organization-identity-card";
74
74
  import { useVideoStorageStatus } from "@/hooks/use-video-storage-status";
75
75
  import enMessages from "@/i18n/en-US";
76
76
  import { cn } from "@/lib/utils";
@@ -166,7 +166,6 @@ const AI_PROVIDER_FIELDS = [
166
166
  interface ClipsUserSettings {
167
167
  defaultPlaybackSpeed?: string;
168
168
  emailNotifications?: boolean;
169
- displayName?: string;
170
169
  transcriptCleanupEnabled?: boolean;
171
170
  includeFullVideoInAi?: boolean;
172
171
  }
@@ -393,12 +392,29 @@ async function saveRegisteredSecret(key: string, value: string): Promise<void> {
393
392
  }
394
393
 
395
394
  export default function SettingsIndexRoute() {
396
- const { session } = useSession();
397
395
  const t = useT();
398
396
  const agentSettingsTabs = useAgentSettingsTabs();
397
+ // Organization identity (name, logo, brand color) belongs with membership,
398
+ // so it rides on the framework's Organization tab rather than a second one.
399
+ const settingsTabs = useMemo(
400
+ () =>
401
+ agentSettingsTabs.map((tab) =>
402
+ tab.id === "organization"
403
+ ? {
404
+ ...tab,
405
+ content: (
406
+ <div className="mx-auto w-full max-w-2xl space-y-6">
407
+ <OrganizationIdentityCard />
408
+ {tab.content}
409
+ </div>
410
+ ),
411
+ }
412
+ : tab,
413
+ ),
414
+ [agentSettingsTabs],
415
+ );
399
416
  const { data: org } = useOrg();
400
417
  const switchOrg = useSwitchOrg();
401
- const email = session?.email ?? "";
402
418
  const storageStatus = useVideoStorageStatus();
403
419
  const builderStatus = useBuilderStatus();
404
420
  const builderConnect = useBuilderConnectFlow({
@@ -430,7 +446,6 @@ export default function SettingsIndexRoute() {
430
446
  useState<SlackInstallation | null>(null);
431
447
  const [defaultSpeed, setDefaultSpeed] = useState("1.2");
432
448
  const [emailNotifications, setEmailNotifications] = useState(true);
433
- const [displayName, setDisplayName] = useState("");
434
449
  const [transcriptCleanupEnabled, setTranscriptCleanupEnabled] =
435
450
  useState(true);
436
451
  const [s3Values, setS3Values] = useState<Record<string, string>>({});
@@ -496,7 +511,6 @@ export default function SettingsIndexRoute() {
496
511
  if (cancelled) return;
497
512
  setDefaultSpeed(v.defaultPlaybackSpeed ?? "1.2");
498
513
  setEmailNotifications(v.emailNotifications ?? true);
499
- setDisplayName(v.displayName ?? "");
500
514
  setTranscriptCleanupEnabled(v.transcriptCleanupEnabled !== false);
501
515
  setLoading(false);
502
516
  });
@@ -531,7 +545,6 @@ export default function SettingsIndexRoute() {
531
545
  await saveSettings({
532
546
  defaultPlaybackSpeed: defaultSpeed,
533
547
  emailNotifications,
534
- displayName: displayName.trim() || undefined,
535
548
  transcriptCleanupEnabled,
536
549
  });
537
550
  toast.success(t("settings.saved"));
@@ -801,12 +814,6 @@ export default function SettingsIndexRoute() {
801
814
  "ai provider api key anthropic openai gemini groq openrouter builder",
802
815
  hash: "ai-providers",
803
816
  },
804
- {
805
- id: "clips-profile",
806
- label: t("settings.profile"),
807
- keywords: "profile email display name",
808
- hash: "profile",
809
- },
810
817
  {
811
818
  id: "clips-playback",
812
819
  label: t("settings.playback"),
@@ -839,7 +846,7 @@ export default function SettingsIndexRoute() {
839
846
  <SettingsTabsPage
840
847
  account={<AccountSettingsCard />}
841
848
  whatsNewLabel={t("settings.whatsNew")}
842
- extraTabs={agentSettingsTabs}
849
+ extraTabs={settingsTabs}
843
850
  generalSearchEntries={generalSearchEntries}
844
851
  general={
845
852
  <div className="mx-auto w-full max-w-4xl space-y-6">
@@ -1477,33 +1484,6 @@ export default function SettingsIndexRoute() {
1477
1484
  </CardContent>
1478
1485
  </Card>
1479
1486
 
1480
- <Card id="profile" className="scroll-mt-16">
1481
- <CardHeader>
1482
- <CardTitle className="text-base flex items-center gap-2">
1483
- <IconUser className="size-4 text-primary" />
1484
- {t("settings.profile")}
1485
- </CardTitle>
1486
- </CardHeader>
1487
- <CardContent className="space-y-4">
1488
- <div className="space-y-1.5">
1489
- <Label htmlFor="email">{t("settings.email")}</Label>
1490
- <Input id="email" value={email} readOnly disabled />
1491
- </div>
1492
- <div className="space-y-1.5">
1493
- <Label htmlFor="display-name">
1494
- {t("settings.displayName")}
1495
- </Label>
1496
- <Input
1497
- id="display-name"
1498
- value={displayName}
1499
- onChange={(e) => setDisplayName(e.target.value)}
1500
- placeholder={t("settings.displayNamePlaceholder")}
1501
- disabled={loading}
1502
- />
1503
- </div>
1504
- </CardContent>
1505
- </Card>
1506
-
1507
1487
  <Card id="playback" className="scroll-mt-16">
1508
1488
  <CardHeader>
1509
1489
  <CardTitle className="text-base">
@@ -1605,14 +1585,6 @@ export default function SettingsIndexRoute() {
1605
1585
  </div>
1606
1586
  </div>
1607
1587
  }
1608
- team={
1609
- <div className="mx-auto w-full max-w-3xl">
1610
- <TeamPage
1611
- showTitle={false}
1612
- createOrgDescription={t("organizationSettings.description")}
1613
- />
1614
- </div>
1615
- }
1616
1588
  whatsNew={
1617
1589
  <div className="mx-auto w-full max-w-3xl">
1618
1590
  <ChangelogSettingsCard
@@ -427,8 +427,6 @@ export default function ShareMeetingRoute() {
427
427
  <TranscriptBubbles
428
428
  segments={transcript.segments}
429
429
  isLive={false}
430
- recordingId={null}
431
- onSeek={() => {}}
432
430
  />
433
431
  </div>
434
432
  ) : transcript.fullText ? (
@@ -437,12 +435,7 @@ export default function ShareMeetingRoute() {
437
435
  </div>
438
436
  ) : (
439
437
  <div className="flex min-h-40 flex-col overflow-hidden rounded-lg border border-border">
440
- <TranscriptBubbles
441
- segments={[]}
442
- isLive={false}
443
- recordingId={null}
444
- onSeek={() => {}}
445
- />
438
+ <TranscriptBubbles segments={[]} isLive={false} />
446
439
  </div>
447
440
  )}
448
441
  </section>
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-07-29
4
+ ---
5
+
6
+ Clearing all recording edits no longer leaves the editor unresponsive.
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: improved
3
+ date: 2026-07-29
4
+ ---
5
+
6
+ Clip share emails now open with who shared the clip, add a Summarize with AI button, and include a copyable agent link with reply-to the sender.
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: improved
3
+ date: 2026-07-29
4
+ ---
5
+
6
+ Finished meeting pills now have a clear close button.
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-07-29
4
+ ---
5
+
6
+ Meeting cards no longer flicker or disappear when hovered in Chrome.
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: improved
3
+ date: 2026-07-29
4
+ ---
5
+
6
+ Organization name, logo, brand color, and default visibility now live in Settings → Organization alongside members, and the duplicate Profile box was removed from General.
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-07-29
4
+ ---
5
+
6
+ Transcript lines on the meeting page no longer jump to the recording when clicked.
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: improved
3
+ date: 2026-07-29
4
+ ---
5
+
6
+ When a meeting wraps up, the recording pill now shows a green 'Meeting finished' banner with a button to open the meeting notes.
@@ -207,14 +207,17 @@ export function useMeetingTranscription({
207
207
  if (reason !== "app-quit") await finalizePromise;
208
208
  }
209
209
  // Keep completed notes in Clips instead of interrupting the user by
210
- // opening a browser tab. The pill's explicit Open notes action remains
211
- // available through the clips:open-meeting listener below.
210
+ // opening a browser tab. On a normal stop the pill stays up and
211
+ // switches to its finished banner off `meetings:transcription-stopped`;
212
+ // it hides itself once the user opens the meeting or dismisses it.
212
213
  // Guard the shared Rust-side state writes and sessionRef null-out by
213
214
  // identity. App quit and other callers can still race a stop against a
214
215
  // new start that slips in between awaits, and stale teardown must not
215
216
  // clobber the session that has since taken over.
216
217
  if (sessionRef.current === session) {
217
- await invoke("recording_pill_hide").catch(() => {});
218
+ if (reason === "app-quit" || reason === "replaced") {
219
+ await invoke("recording_pill_hide").catch(() => {});
220
+ }
218
221
  await invoke("set_recording_state", { active: false }).catch(
219
222
  () => {},
220
223
  );
@@ -9,6 +9,7 @@ import {
9
9
  IconPlayerPauseFilled,
10
10
  IconPlayerPlayFilled,
11
11
  IconPlayerStopFilled,
12
+ IconX,
12
13
  } from "@tabler/icons-react";
13
14
  import { invoke } from "@tauri-apps/api/core";
14
15
  import { emit, listen } from "@tauri-apps/api/event";
@@ -46,6 +47,10 @@ export function RecordingPill() {
46
47
  const [ctx, setCtx] = useState<PillContext>({ mode: "clip" });
47
48
  const ctxRef = useRef<PillContext>({ mode: "clip" });
48
49
  const [stopping, setStopping] = useState(false);
50
+ const [finishedMeetingId, setFinishedMeetingId] = useState<string | null>(
51
+ null,
52
+ );
53
+ const finished = finishedMeetingId !== null;
49
54
  const [error, setError] = useState<string | null>(null);
50
55
  const transcriptLinesRef = useRef<FinalLine[]>([]);
51
56
  const [hasTranscriptLines, setHasTranscriptLines] = useState(false);
@@ -113,6 +118,7 @@ export function RecordingPill() {
113
118
  // new recording session begins, otherwise the Stop button stays
114
119
  // disabled and a stale fallback timer can fire mid-session.
115
120
  setStopping(false);
121
+ setFinishedMeetingId(null);
116
122
  setError(null);
117
123
  setExpanded(false);
118
124
  // Reset transcript state for the new session.
@@ -146,6 +152,21 @@ export function RecordingPill() {
146
152
  if (lines?.length) setPreloadedLines(lines);
147
153
  }),
148
154
  );
155
+ trackListen(
156
+ listen<{ meetingId?: string | null; reason?: string }>(
157
+ "meetings:transcription-stopped",
158
+ (ev) => {
159
+ const reason = ev.payload?.reason;
160
+ // "replaced" hands straight over to the next session and "app-quit"
161
+ // is tearing the window down — neither has a user left to read this.
162
+ if (reason === "replaced" || reason === "app-quit") return;
163
+ if (ctxRef.current.mode !== "meeting") return;
164
+ const meetingId = ev.payload?.meetingId ?? activeMeetingIdRef.current;
165
+ if (!meetingId) return;
166
+ showFinished(meetingId);
167
+ },
168
+ ),
169
+ );
149
170
  trackListen(
150
171
  listen<{ error: string }>("pill:error", (ev) => {
151
172
  setError(ev.payload?.error ?? "An error occurred.");
@@ -187,7 +208,7 @@ export function RecordingPill() {
187
208
  useEffect(() => {
188
209
  // Clip recordings already broadcast their pause-aware elapsed time every
189
210
  // 500ms. Keep the local wall clock only for meeting mode.
190
- if (paused || ctx.mode === "clip") return;
211
+ if (paused || finished || ctx.mode === "clip") return;
191
212
  tickRef.current = setInterval(() => {
192
213
  setElapsed(Math.floor((Date.now() - startedAtRef.current) / 1000));
193
214
  }, 500);
@@ -195,7 +216,7 @@ export function RecordingPill() {
195
216
  if (tickRef.current) clearInterval(tickRef.current);
196
217
  tickRef.current = null;
197
218
  };
198
- }, [ctx.mode, paused]);
219
+ }, [ctx.mode, finished, paused]);
199
220
 
200
221
  async function toggleExpanded() {
201
222
  const next = !expanded;
@@ -215,12 +236,32 @@ export function RecordingPill() {
215
236
  );
216
237
  }
217
238
 
239
+ function showFinished(meetingId: string) {
240
+ if (stopFallbackRef.current) {
241
+ clearTimeout(stopFallbackRef.current);
242
+ stopFallbackRef.current = null;
243
+ }
244
+ setStopping(false);
245
+ setFinishedMeetingId(meetingId);
246
+ setExpanded(true);
247
+ invoke("recording_pill_expand", { expanded: true }).catch(() => {});
248
+ }
249
+
218
250
  async function onStopClick() {
219
251
  if (stopping) return;
220
- setStopping(true);
252
+ const meetingId = ctx.meetingId ?? activeMeetingIdRef.current;
221
253
  emit("clips:pill-stop", { meetingId: ctx.meetingId ?? null }).catch(
222
254
  () => {},
223
255
  );
256
+ // Meetings keep the pill up and switch it to the finished banner right
257
+ // away. Teardown (final flush, finalize) runs for seconds afterwards, so
258
+ // waiting on `meetings:transcription-stopped` would leave the pill looking
259
+ // stuck; the banner is what the user acts on, not the save.
260
+ if (ctxRef.current.mode === "meeting" && meetingId) {
261
+ showFinished(meetingId);
262
+ return;
263
+ }
264
+ setStopping(true);
224
265
  stopFallbackRef.current = setTimeout(() => {
225
266
  invoke("recording_pill_hide").catch(() => {});
226
267
  }, 3_000);
@@ -288,6 +329,15 @@ export function RecordingPill() {
288
329
  });
289
330
  };
290
331
 
332
+ const closeFinished = (openMeeting: boolean) => {
333
+ if (openMeeting && finishedMeetingId) {
334
+ emit("clips:open-meeting", { meetingId: finishedMeetingId }).catch(
335
+ () => {},
336
+ );
337
+ }
338
+ invoke("recording_pill_hide").catch(() => {});
339
+ };
340
+
291
341
  const mm = String(Math.floor(elapsed / 60)).padStart(2, "0");
292
342
  const ss = String(elapsed % 60).padStart(2, "0");
293
343
  const stopLabel =
@@ -323,7 +373,7 @@ export function RecordingPill() {
323
373
  <span className="pill-timer">
324
374
  {mm}:{ss}
325
375
  </span>
326
- {expanded ? (
376
+ {expanded && !finished ? (
327
377
  <button
328
378
  type="button"
329
379
  onClick={onPauseClick}
@@ -339,34 +389,49 @@ export function RecordingPill() {
339
389
  )}
340
390
  </button>
341
391
  ) : null}
342
- <button
343
- type="button"
344
- onClick={onStopClick}
345
- disabled={stopping}
346
- data-no-drag
347
- className="pill-stop-btn"
348
- aria-label={stopping ? "Stopping" : stopLabel}
349
- title={stopping ? "Stopping..." : stopLabel}
350
- >
351
- {stopping ? (
352
- <IconLoader2 className="pill-spinner" size={14} />
353
- ) : (
354
- <IconPlayerStopFilled size={14} />
355
- )}
356
- </button>
357
- <button
358
- type="button"
359
- onClick={toggleExpanded}
360
- data-no-drag
361
- className="pill-expand-btn"
362
- aria-label={expanded ? "Collapse" : "Expand"}
363
- >
364
- {expanded ? (
365
- <IconChevronUp size={16} />
366
- ) : (
367
- <IconChevronDown size={16} />
368
- )}
369
- </button>
392
+ {!finished ? (
393
+ <button
394
+ type="button"
395
+ onClick={onStopClick}
396
+ disabled={stopping}
397
+ data-no-drag
398
+ className="pill-stop-btn"
399
+ aria-label={stopping ? "Stopping" : stopLabel}
400
+ title={stopping ? "Stopping..." : stopLabel}
401
+ >
402
+ {stopping ? (
403
+ <IconLoader2 className="pill-spinner" size={14} />
404
+ ) : (
405
+ <IconPlayerStopFilled size={14} />
406
+ )}
407
+ </button>
408
+ ) : null}
409
+ {finished ? (
410
+ <button
411
+ type="button"
412
+ onClick={() => closeFinished(false)}
413
+ data-no-drag
414
+ className="pill-close-btn"
415
+ aria-label="Dismiss"
416
+ title="Dismiss"
417
+ >
418
+ <IconX size={16} />
419
+ </button>
420
+ ) : (
421
+ <button
422
+ type="button"
423
+ onClick={toggleExpanded}
424
+ data-no-drag
425
+ className="pill-expand-btn"
426
+ aria-label={expanded ? "Collapse" : "Expand"}
427
+ >
428
+ {expanded ? (
429
+ <IconChevronUp size={16} />
430
+ ) : (
431
+ <IconChevronDown size={16} />
432
+ )}
433
+ </button>
434
+ )}
370
435
  </div>
371
436
  </div>
372
437
 
@@ -389,6 +454,21 @@ export function RecordingPill() {
389
454
  }
390
455
  >
391
456
  <div className="pill-divider" />
457
+ {finished ? (
458
+ <div className="pill-finished-banner" role="status">
459
+ <span className="pill-finished-text">
460
+ Meeting finished — notes are ready
461
+ </span>
462
+ <button
463
+ type="button"
464
+ data-no-drag
465
+ className="pill-finished-open"
466
+ onClick={() => closeFinished(true)}
467
+ >
468
+ Open meeting
469
+ </button>
470
+ </div>
471
+ ) : null}
392
472
  <div className="pill-transcript-area">
393
473
  <div className="pill-pane-label pill-pane-label-row">
394
474
  <span>Transcript</span>
@@ -5652,7 +5652,8 @@ body[data-clips-route="recording-pill"] #root {
5652
5652
  opacity: 0.6;
5653
5653
  }
5654
5654
 
5655
- .pill-expand-btn {
5655
+ .pill-expand-btn,
5656
+ .pill-close-btn {
5656
5657
  margin-left: 4px;
5657
5658
  display: inline-flex;
5658
5659
  height: 28px;
@@ -5667,7 +5668,8 @@ body[data-clips-route="recording-pill"] #root {
5667
5668
  transition: background 120ms;
5668
5669
  }
5669
5670
 
5670
- .pill-expand-btn:hover {
5671
+ .pill-expand-btn:hover,
5672
+ .pill-close-btn:hover {
5671
5673
  background: rgba(255, 255, 255, 0.1);
5672
5674
  }
5673
5675
 
@@ -5862,6 +5864,44 @@ body[data-clips-route="recording-pill"] #root {
5862
5864
  background: rgba(255, 255, 255, 0.1);
5863
5865
  }
5864
5866
 
5867
+ .pill-finished-banner {
5868
+ display: flex;
5869
+ align-items: center;
5870
+ gap: 8px;
5871
+ flex-shrink: 0;
5872
+ margin: 8px 12px 2px;
5873
+ padding: 8px 10px;
5874
+ border: 1px solid rgba(34, 197, 94, 0.45);
5875
+ border-radius: 8px;
5876
+ background: rgba(34, 197, 94, 0.16);
5877
+ }
5878
+
5879
+ .pill-finished-text {
5880
+ min-width: 0;
5881
+ flex: 1;
5882
+ font-size: 12px;
5883
+ font-weight: 600;
5884
+ line-height: 1.35;
5885
+ color: #86efac;
5886
+ }
5887
+
5888
+ .pill-finished-open {
5889
+ flex-shrink: 0;
5890
+ padding: 5px 9px;
5891
+ border: none;
5892
+ border-radius: 6px;
5893
+ background: #22c55e;
5894
+ color: #052e16;
5895
+ font-size: 11px;
5896
+ font-weight: 700;
5897
+ cursor: pointer;
5898
+ transition: background 120ms;
5899
+ }
5900
+
5901
+ .pill-finished-open:hover {
5902
+ background: #4ade80;
5903
+ }
5904
+
5865
5905
  .pill-transcript-area {
5866
5906
  min-height: 0;
5867
5907
  flex: 1;
@@ -5,6 +5,7 @@ import { eq } from "drizzle-orm";
5
5
 
6
6
  import {
7
7
  absoluteUrl,
8
+ recordingShareEmailExtras,
8
9
  recordingShareHeroHtml,
9
10
  } from "../lib/share-email-hero.js";
10
11
  import * as schema from "./schema.js";
@@ -58,6 +59,11 @@ registerShareableResource({
58
59
  replyTo: ctx.sender.email,
59
60
  }),
60
61
  getHeroHtml: (recording, ctx) => recordingShareHeroHtml(recording, ctx),
62
+ getShareEmailExtras: (_recording, ctx) =>
63
+ recordingShareEmailExtras({
64
+ href: ctx.href,
65
+ senderEmail: ctx.sender.email,
66
+ }),
61
67
  getDb,
62
68
  ownerAccessIgnoresOrg: true,
63
69
  });
@@ -1,7 +1,9 @@
1
1
  import {
2
+ emailLink,
2
3
  getAppProductionUrl,
3
4
  withConfiguredAppBasePath,
4
5
  } from "@agent-native/core/server";
6
+ import type { ShareEmailExtras } from "@agent-native/core/sharing";
5
7
 
6
8
  /**
7
9
  * Origin plus the configured mount path. Deployments served under
@@ -52,6 +54,37 @@ function isHttpUrl(value: string): boolean {
52
54
  }
53
55
  }
54
56
 
57
+ /**
58
+ * Clips-specific body for the share-notification email: the AI companion CTA,
59
+ * the copyable agent link, and the closing note that routes replies to the
60
+ * person who shared the clip.
61
+ */
62
+ export function recordingShareEmailExtras(ctx: {
63
+ href: string;
64
+ senderEmail: string;
65
+ }): ShareEmailExtras {
66
+ const summarizeUrl = new URL(ctx.href);
67
+ summarizeUrl.searchParams.set("panel", "agent");
68
+ return {
69
+ // The heading already says who shared what, so the clip thumbnail follows
70
+ // it directly.
71
+ paragraphs: [],
72
+ secondaryCta: { label: "Summarize with AI", url: summarizeUrl.toString() },
73
+ linkBlock: {
74
+ intro: "Copy and paste this link for your own AI agent to summarize:",
75
+ url: ctx.href,
76
+ placement: "after-cta",
77
+ },
78
+ closingParagraphs: [
79
+ "Clips is a 100% free, open-source, Agent-Native app for sharing screengrabs with friends and colleagues. No download required.",
80
+ `Just reply to this email if you want to get back to ${emailLink(
81
+ ctx.senderEmail,
82
+ `mailto:${ctx.senderEmail}`,
83
+ )} directly.`,
84
+ ],
85
+ };
86
+ }
87
+
55
88
  export type ShareHeroRecording = {
56
89
  thumbnailUrl?: string | null;
57
90
  animatedThumbnailUrl?: string | null;
@@ -28,7 +28,6 @@ export type ClipsAiPrefs = {
28
28
  export type ClipsUserPrefs = ClipsAiPrefs & {
29
29
  defaultPlaybackSpeed?: string;
30
30
  emailNotifications?: boolean;
31
- displayName?: string;
32
31
  transcriptCleanupEnabled?: boolean;
33
32
  };
34
33
 
@@ -62,11 +62,11 @@ export declare const postAwareness: import("h3").EventHandlerWithFetch<import("h
62
62
  error: string;
63
63
  states?: undefined;
64
64
  } | {
65
+ error?: undefined;
65
66
  states: {
66
67
  clientId: number;
67
68
  state: string;
68
69
  }[];
69
- error?: undefined;
70
70
  }>>;
71
71
  /**
72
72
  * GET /_agent-native/collab/:docId/users
@@ -77,10 +77,10 @@ export declare const getActiveUsers: import("h3").EventHandlerWithFetch<import("
77
77
  error: string;
78
78
  users?: undefined;
79
79
  } | {
80
+ error?: undefined;
80
81
  users: {
81
82
  clientId: number;
82
83
  lastSeen: number;
83
84
  }[];
84
- error?: undefined;
85
85
  }>>;
86
86
  //# sourceMappingURL=awareness.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"awareness.d.ts","sourceRoot":"","sources":["../../src/collab/awareness.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAgB3C,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;CAClB;AAOD,eAAO,MAAM,sBAAsB,EAAG,kBAA2B,CAAC;AAElE,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,WAAW,CAAC;IACpB,IAAI,EAAE,kBAAkB,CAAC;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,2EAA2E;IAC3E,MAAM,EAAE,KAAK,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACnD,gFAAgF;IAChF,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,sCAAsC;IACtC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,2EAA2E;IAC3E,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,yEAAyE;IACzE,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,cAAc;IAC7B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAOD,wBAAgB,mBAAmB,IAAI,YAAY,CAElD;AAED,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,cAAc,GAAG,SAAS,GAChC,IAAI,CAON;AAED,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,KAAK,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,EAClD,KAAK,CAAC,EAAE,cAAc,GACrB,IAAI,CAgBN;AAoBD,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,EAChB,SAAS,GAAE,MAAmB,GAC7B,IAAI,CAON;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAE1E;AAiBD,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,cAAc,CAAC,CAO1E;AAED,wBAAgB,YAAY,CAAC,GAAG,EAAE,GAAG,CAAC,MAAM,EAAE,cAAc,CAAC,GAAG,IAAI,CAOnE;AAkCD;;;;;;;GAOG;AACH,eAAO,MAAM,aAAa;;;;;kBAwDa,MAAM;eAAS,MAAM;;;GAoB1D,CAAC;AAEH;;;;GAIG;AACH,eAAO,MAAM,cAAc;;;;;kBAYM,MAAM;kBAAY,MAAM;;;GAMvD,CAAC"}
1
+ {"version":3,"file":"awareness.d.ts","sourceRoot":"","sources":["../../src/collab/awareness.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAgB3C,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;CAClB;AAOD,eAAO,MAAM,sBAAsB,EAAG,kBAA2B,CAAC;AAElE,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,WAAW,CAAC;IACpB,IAAI,EAAE,kBAAkB,CAAC;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,2EAA2E;IAC3E,MAAM,EAAE,KAAK,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACnD,gFAAgF;IAChF,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,sCAAsC;IACtC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,2EAA2E;IAC3E,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,yEAAyE;IACzE,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,cAAc;IAC7B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAOD,wBAAgB,mBAAmB,IAAI,YAAY,CAElD;AAED,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,cAAc,GAAG,SAAS,GAChC,IAAI,CAON;AAED,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,KAAK,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,EAClD,KAAK,CAAC,EAAE,cAAc,GACrB,IAAI,CAgBN;AAoBD,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,EAChB,SAAS,GAAE,MAAmB,GAC7B,IAAI,CAON;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAE1E;AAiBD,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,cAAc,CAAC,CAO1E;AAED,wBAAgB,YAAY,CAAC,GAAG,EAAE,GAAG,CAAC,MAAM,EAAE,cAAc,CAAC,GAAG,IAAI,CAOnE;AAkCD;;;;;;;GAOG;AACH,eAAO,MAAM,aAAa;;;;;;kBAwDa,MAAM;eAAS,MAAM;;GAoB1D,CAAC;AAEH;;;;GAIG;AACH,eAAO,MAAM,cAAc;;;;;;kBAYM,MAAM;kBAAY,MAAM;;GAMvD,CAAC"}
@@ -13,8 +13,8 @@
13
13
  * Body: { json: any, fieldName?: string, type?: "map"|"array", requestSource?: string }
14
14
  */
15
15
  export declare const postCollabJson: import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
16
- ok?: undefined;
17
16
  error: string;
17
+ ok?: undefined;
18
18
  } | {
19
19
  error?: undefined;
20
20
  ok: boolean;