From e0e56814f281de6a24da107fbf3cdff0de3c3f76 Mon Sep 17 00:00:00 2001 From: Milos Petrovic Date: Sun, 23 Aug 2026 18:20:09 +0200 Subject: [PATCH 1/2] Various clips fixes --- templates/clips/actions/list-recordings.ts | 3 ++ .../meetings/meeting-history-row.test.ts | 32 ++++++++++-- .../meetings/meeting-history-row.tsx | 35 ++++++++++++- .../player/delete-recording-menu.tsx | 2 +- templates/clips/app/i18n/ar-SA.ts | 1 + templates/clips/app/i18n/de-DE.ts | 1 + templates/clips/app/i18n/en-US.ts | 1 + templates/clips/app/i18n/es-ES.ts | 1 + templates/clips/app/i18n/fr-FR.ts | 1 + templates/clips/app/i18n/hi-IN.ts | 1 + templates/clips/app/i18n/ja-JP.ts | 1 + templates/clips/app/i18n/ko-KR.ts | 1 + templates/clips/app/i18n/pt-BR.ts | 1 + templates/clips/app/i18n/zh-CN.ts | 1 + templates/clips/app/i18n/zh-TW.ts | 1 + .../clips/app/routes/_app.meetings._index.tsx | 1 + .../clips/desktop/src-tauri/src/clips/mod.rs | 49 ++++++++++++++++--- templates/clips/desktop/src-tauri/src/tray.rs | 12 +++++ templates/clips/desktop/src/app.tsx | 44 ++++++++++------- 19 files changed, 159 insertions(+), 30 deletions(-) diff --git a/templates/clips/actions/list-recordings.ts b/templates/clips/actions/list-recordings.ts index 94d190a9da..c50265e8cd 100644 --- a/templates/clips/actions/list-recordings.ts +++ b/templates/clips/actions/list-recordings.ts @@ -210,6 +210,9 @@ export default defineAction({ // Lifecycle view filters if (args.view === "trash") { whereClauses.push(isNotNull(schema.recordings.trashedAt)); + if (orgId) { + whereClauses.push(eq(schema.recordings.organizationId, orgId)); + } } else { whereClauses.push(isNull(schema.recordings.trashedAt)); if (args.view === "archive") { diff --git a/templates/clips/app/components/meetings/meeting-history-row.test.ts b/templates/clips/app/components/meetings/meeting-history-row.test.ts index 091c2fb4df..cc56d1c6e1 100644 --- a/templates/clips/app/components/meetings/meeting-history-row.test.ts +++ b/templates/clips/app/components/meetings/meeting-history-row.test.ts @@ -1,8 +1,13 @@ import { describe, expect, it } from "vitest"; -import { formatParticipantNames } from "./meeting-history-row"; +import { formatOwnerHint, formatParticipantNames } from "./meeting-history-row"; const viewer = "dev@local.test"; +const fakeT = (key: string, options?: Record) => { + if (key === "meetingDetail.recordedBy") return `Recorded by ${options?.name}`; + if (key === "meetingDetail.me") return "Me"; + return key; +}; describe("formatParticipantNames", () => { it("names the one other person on a 1:1", () => { @@ -32,8 +37,9 @@ describe("formatParticipantNames", () => { ).toBe("Jason, Elaine & 2 others"); }); - // A solo note renders a document icon instead, so the subtitle must go empty - // rather than telling the reader they were in a meeting with themselves. + // The attendee subtitle must go empty rather than telling the reader they + // were in a meeting with themselves; `formatOwnerHint` is what still + // surfaces the owner's avatar/name on a solo note. it("returns nothing when the viewer is the only attendee", () => { expect( formatParticipantNames([{ email: viewer, name: "Dev" }], viewer), @@ -68,3 +74,23 @@ describe("formatParticipantNames", () => { ); }); }); + +describe("formatOwnerHint", () => { + it("names the owner of a shared meeting", () => { + expect(formatOwnerHint("sidharth@builder.io", viewer, fakeT)).toBe( + "Recorded by sidharth", + ); + }); + + it("shows the owner even on the viewer's own meetings, as 'Me'", () => { + expect(formatOwnerHint(viewer, viewer, fakeT)).toBe("Recorded by Me"); + expect(formatOwnerHint(" DEV@Local.TEST ", viewer, fakeT)).toBe( + "Recorded by Me", + ); + }); + + it("returns nothing without an owner", () => { + expect(formatOwnerHint(null, viewer, fakeT)).toBe(""); + expect(formatOwnerHint(undefined, viewer, fakeT)).toBe(""); + }); +}); diff --git a/templates/clips/app/components/meetings/meeting-history-row.tsx b/templates/clips/app/components/meetings/meeting-history-row.tsx index 754d49189d..ef94387fc8 100644 --- a/templates/clips/app/components/meetings/meeting-history-row.tsx +++ b/templates/clips/app/components/meetings/meeting-history-row.tsx @@ -26,6 +26,7 @@ export interface MeetingHistoryItem { actualEnd?: string | null; createdAt?: string | null; participants?: AttendeeStackParticipant[]; + ownerEmail?: string | null; } function formatTime(iso?: string | null): string { @@ -56,6 +57,28 @@ export function formatParticipantNames( return `${names.slice(0, 2).join(", ")} & ${names.length - 2} others`; } +/** + * A meeting's owner — who actually recorded it in Clips — isn't necessarily + * on the attendee list (an ad-hoc note has none at all), and two attendees on + * the same call can each hold their own copy. Unlike the attendee subtitle, + * this is shown unconditionally, including the viewer's own meetings, so + * ownership is never ambiguous once a meeting is shared. + */ +export function formatOwnerHint( + ownerEmail: string | null | undefined, + viewerEmail: string | null | undefined, + t: ReturnType, +): string { + const owner = ownerEmail?.trim(); + if (!owner) return ""; + const viewer = viewerEmail?.trim().toLowerCase(); + const name = + viewer && owner.toLowerCase() === viewer + ? t("meetingDetail.me") + : owner.replace(/@.*$/, ""); + return t("meetingDetail.recordedBy", { name }); +} + export function MeetingHistoryRow({ meeting, snippet, @@ -66,11 +89,19 @@ export function MeetingHistoryRow({ const t = useT(); const { session } = useSession(); const participants = meeting.participants ?? []; + const ownerHint = formatOwnerHint(meeting.ownerEmail, session?.email, t); const subtitle = - snippet?.trim() || formatParticipantNames(participants, session?.email); + snippet?.trim() || + [formatParticipantNames(participants, session?.email), ownerHint] + .filter(Boolean) + .join(" · "); const time = formatTime( meeting.actualStart ?? meeting.scheduledStart ?? meeting.createdAt, ); + const soloOwnerAvatar: AttendeeStackParticipant[] = + participants.length === 0 && ownerHint && meeting.ownerEmail + ? [{ email: meeting.ownerEmail }] + : []; return ( {participants.length > 0 ? ( + ) : soloOwnerAvatar.length > 0 ? ( + ) : ( diff --git a/templates/clips/app/components/player/delete-recording-menu.tsx b/templates/clips/app/components/player/delete-recording-menu.tsx index 1c10346cf0..32240fd58b 100644 --- a/templates/clips/app/components/player/delete-recording-menu.tsx +++ b/templates/clips/app/components/player/delete-recording-menu.tsx @@ -94,7 +94,7 @@ export function RecordingOptionsMenu({