Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
902 changes: 902 additions & 0 deletions docs/plans/2026-07-29-clips-display-name-prompt.md

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions templates/clips/actions/get-recording-player-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,7 @@ export default defineAction({
spaceIds: parseSpaceIds(rec.spaceIds),
createdAt: rec.createdAt,
updatedAt: rec.updatedAt,
archivedAt: rec.archivedAt,
},
transcript: transcript
? {
Expand Down
100 changes: 97 additions & 3 deletions templates/clips/app/components/player/delete-recording-menu.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
import { useActionMutation } from "@agent-native/core/client/hooks";
import { useT } from "@agent-native/core/client/i18n";
import { IconDots, IconDownload, IconTrash } from "@tabler/icons-react";
import {
IconArchive,
IconArchiveOff,
IconDots,
IconDownload,
IconGif,
IconTrash,
} from "@tabler/icons-react";
import { useCallback, useState } from "react";
import { toast } from "sonner";

Expand Down Expand Up @@ -35,6 +42,12 @@ interface RecordingOptionsMenuProps extends DeleteRecordingMenuProps {
downloadLabel?: string;
downloadingLabel?: string;
onDownload?: () => void;
canDownloadGif?: boolean;
gifPending?: boolean;
onDownloadGif?: () => void;
canArchive?: boolean;
isArchived?: boolean;
onArchiveChanged?: () => void;
}

export function RecordingOptionsMenu({
Expand All @@ -46,11 +59,19 @@ export function RecordingOptionsMenu({
downloadLabel,
downloadingLabel,
onDownload,
canDownloadGif = false,
gifPending = false,
onDownloadGif,
canArchive = false,
isArchived = false,
onArchiveChanged,
}: RecordingOptionsMenuProps) {
const t = useT();
const [open, setOpen] = useState(false);
const [menuOpen, setMenuOpen] = useState(false);
const showDownload = canDownload && Boolean(onDownload);
const showDownloadGif = canDownloadGif && Boolean(onDownloadGif);
const showArchive = canArchive;
const showDelete = canDelete;
const trashRecording = useActionMutation<any, { id: string }>(
"trash-recording",
Expand All @@ -70,12 +91,57 @@ export function RecordingOptionsMenu({
trashRecording.mutate({ id: recordingId });
}, [recordingId, trashRecording]);

const archiveRecording = useActionMutation<any, { id: string }>(
"archive-recording",
{
onSuccess: () => {
toast.success(t("deleteRecordingMenu.archived"));
onArchiveChanged?.();
},
onError: (err: any) =>
toast.error(err?.message ?? t("deleteRecordingMenu.archiveFailed")),
},
);
const restoreRecording = useActionMutation<any, { id: string }>(
"restore-recording",
{
onSuccess: () => {
toast.success(t("deleteRecordingMenu.restoredFromArchive"));
onArchiveChanged?.();
},
onError: (err: any) =>
toast.error(err?.message ?? t("deleteRecordingMenu.unarchiveFailed")),
},
);
const archivePending =
archiveRecording.isPending || restoreRecording.isPending;

const handleDownload = useCallback(() => {
setMenuOpen(false);
onDownload?.();
}, [onDownload]);

if (!showDownload && !showDelete) return null;
const handleDownloadGif = useCallback(() => {
setMenuOpen(false);
onDownloadGif?.();
}, [onDownloadGif]);

const handleArchive = useCallback(() => {
if (archivePending) return;
setMenuOpen(false);
if (isArchived) restoreRecording.mutate({ id: recordingId });
Comment on lines +129 to +132

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Do not unarchive trashed recordings through restore-recording

This selects restore-recording solely from isArchived. The player data exposes archivedAt but not trashedAt, while restore-recording clears both archivedAt and trashedAt; opening an archived-and-trashed clip by URL and choosing Unarchive therefore silently restores it from Trash as well. Gate lifecycle controls for trashed clips or use an archive-only restore action that preserves trashedAt.

Additional Info
Found by 1 of 4 code-review agents; browser planner independently confirmed the action clears both flags, though visual execution was unavailable.

Fix in Builder

else archiveRecording.mutate({ id: recordingId });
}, [
archivePending,
archiveRecording,
isArchived,
recordingId,
restoreRecording,
]);

if (!showDownload && !showDownloadGif && !showArchive && !showDelete) {
return null;
}

return (
<AlertDialog
Expand Down Expand Up @@ -107,7 +173,35 @@ export function RecordingOptionsMenu({
: (downloadLabel ?? t("recordRoute.downloadRecording"))}
</DropdownMenuItem>
) : null}
{showDownload && showDelete ? <DropdownMenuSeparator /> : null}
{showDownloadGif ? (
<DropdownMenuItem
onSelect={handleDownloadGif}
disabled={gifPending}
>
<IconGif className="me-2 h-4 w-4" />
{gifPending
? t("deleteRecordingMenu.buildingGif")
: t("deleteRecordingMenu.downloadAsGif")}
</DropdownMenuItem>
) : null}
{showArchive ? (
<DropdownMenuItem
onSelect={handleArchive}
disabled={archivePending}
>
{isArchived ? (
<IconArchiveOff className="me-2 h-4 w-4" />
) : (
<IconArchive className="me-2 h-4 w-4" />
)}
{isArchived
? t("deleteRecordingMenu.unarchive")
: t("deleteRecordingMenu.archive")}
</DropdownMenuItem>
) : null}
{(showDownload || showDownloadGif || showArchive) && showDelete ? (
<DropdownMenuSeparator />
) : null}
{showDelete ? (
<DropdownMenuItem
onSelect={(event) => {
Expand Down
10 changes: 10 additions & 0 deletions templates/clips/app/i18n/ar-SA.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,8 @@ const messages = {
pageTitle: "اجتماع · Clips",
},
recordingPage: {
buildingGif: "جارٍ إنشاء GIF… {{percent}}%",
gifExportFailed: "تعذر إنشاء GIF من هذا المقطع",
untitledClip: "مقطع بدون عنوان",
recordingNotFound: "لم يتم العثور على التسجيل",
noAccess: "قد لا يكون لديك حق الوصول إلى هذا المقطع.",
Expand Down Expand Up @@ -963,6 +965,14 @@ const messages = {
updateTranscriptSharingFailed: "تعذر تحديث مشاركة النص",
},
deleteRecordingMenu: {
downloadAsGif: "تنزيل كملف GIF",
buildingGif: "جارٍ إنشاء GIF…",
archive: "أرشفة",
unarchive: "إلغاء الأرشفة",
archived: "تمت أرشفة المقطع",
restoredFromArchive: "تمت استعادة المقطع من الأرشيف",
archiveFailed: "تعذرت أرشفة المقطع",
unarchiveFailed: "تعذرت استعادة المقطع",
movedToTrash: "تم نقل المقطع إلى المهملات",
deleteFailed: "فشل حذف المقطع",
clipOptions: "خيارات المقطع",
Expand Down
10 changes: 10 additions & 0 deletions templates/clips/app/i18n/de-DE.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,8 @@ const messages = {
pageTitle: "Treffen · Clips",
},
recordingPage: {
buildingGif: "GIF wird erstellt… {{percent}} %",
gifExportFailed: "GIF konnte aus diesem Clip nicht erstellt werden",
untitledClip: "Unbenannter Clip",
recordingNotFound: "Aufnahme nicht gefunden",
noAccess: "Möglicherweise haben Sie keinen Zugriff auf diesen Clip.",
Expand Down Expand Up @@ -986,6 +988,14 @@ Alle sichtbaren Änderungen für Clips-Nutzer werden hier dokumentiert. Du kanns
"Die Transkriptfreigabe konnte nicht aktualisiert werden",
},
deleteRecordingMenu: {
downloadAsGif: "Als GIF herunterladen",
buildingGif: "GIF wird erstellt…",
archive: "Archivieren",
unarchive: "Archivierung aufheben",
archived: "Clip archiviert",
restoredFromArchive: "Clip aus dem Archiv wiederhergestellt",
archiveFailed: "Clip konnte nicht archiviert werden",
unarchiveFailed: "Clip konnte nicht wiederhergestellt werden",
movedToTrash: "Clip in den Papierkorb verschoben",
deleteFailed: "Clip konnte nicht gelöscht werden",
clipOptions: "Clip-Optionen",
Expand Down
10 changes: 10 additions & 0 deletions templates/clips/app/i18n/en-US.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,8 @@ const messages = {
pageTitle: "Meeting · Clips",
},
recordingPage: {
buildingGif: "Building GIF… {{percent}}%",
gifExportFailed: "Couldn't build a GIF from this clip",
untitledClip: "Untitled Clip",
recordingNotFound: "Recording not found",
noAccess: "You may not have access to this clip.",
Expand Down Expand Up @@ -954,6 +956,14 @@ All notable user-facing changes to Clips are documented here. Open it any time f
updateTranscriptSharingFailed: "Couldn't update transcript sharing",
},
deleteRecordingMenu: {
downloadAsGif: "Download as GIF",
buildingGif: "Building GIF...",
archive: "Archive",
unarchive: "Unarchive",
archived: "Clip archived",
restoredFromArchive: "Clip restored from archive",
archiveFailed: "Failed to archive clip",
unarchiveFailed: "Failed to restore clip",
movedToTrash: "Clip moved to trash",
deleteFailed: "Failed to delete clip",
clipOptions: "Clip options",
Expand Down
10 changes: 10 additions & 0 deletions templates/clips/app/i18n/es-ES.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,8 @@ const messages = {
pageTitle: "Reunión · Clips",
},
recordingPage: {
buildingGif: "Creando GIF… {{percent}}%",
gifExportFailed: "No se pudo crear un GIF de este clip",
untitledClip: "Clip sin título",
recordingNotFound: "Grabación no encontrada",
noAccess: "Es posible que no tengas acceso a este clip.",
Expand Down Expand Up @@ -979,6 +981,14 @@ Todos los cambios visibles para los usuarios de Clips se documentan aquí. Puede
"No se pudo actualizar el uso compartido de la transcripción",
},
deleteRecordingMenu: {
downloadAsGif: "Descargar como GIF",
buildingGif: "Creando GIF…",
archive: "Archivar",
unarchive: "Desarchivar",
archived: "Clip archivado",
restoredFromArchive: "Clip restaurado desde el archivo",
archiveFailed: "No se pudo archivar el clip",
unarchiveFailed: "No se pudo restaurar el clip",
movedToTrash: "Clip movido a la papelera",
deleteFailed: "No se pudo eliminar el clip",
clipOptions: "Opciones del clip",
Expand Down
10 changes: 10 additions & 0 deletions templates/clips/app/i18n/fr-FR.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,8 @@ const messages = {
pageTitle: "Réunion · Clips",
},
recordingPage: {
buildingGif: "Création du GIF… {{percent}} %",
gifExportFailed: "Impossible de créer un GIF à partir de ce clip",
untitledClip: "Extrait sans titre",
recordingNotFound: "Enregistrement introuvable",
noAccess: "Vous n’aurez peut-être pas accès à ce clip.",
Expand Down Expand Up @@ -981,6 +983,14 @@ Tous les changements visibles par les utilisateurs de Clips sont documentés ici
"Impossible de modifier le partage de la transcription",
},
deleteRecordingMenu: {
downloadAsGif: "Télécharger en GIF",
buildingGif: "Création du GIF…",
archive: "Archiver",
unarchive: "Désarchiver",
archived: "Clip archivé",
restoredFromArchive: "Clip restauré depuis l’archive",
archiveFailed: "Échec de l’archivage du clip",
unarchiveFailed: "Échec de la restauration du clip",
movedToTrash: "Clip déplacé vers la corbeille",
deleteFailed: "Échec de la suppression du clip",
clipOptions: "Options du clip",
Expand Down
10 changes: 10 additions & 0 deletions templates/clips/app/i18n/hi-IN.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,8 @@ const messages = {
pageTitle: "मीटिंग · Clips",
},
recordingPage: {
buildingGif: "GIF बन रहा है… {{percent}}%",
gifExportFailed: "इस क्लिप से GIF नहीं बनाया जा सका",
untitledClip: "शीर्षक रहित क्लिप",
recordingNotFound: "रिकॉर्डिंग नहीं मिली",
noAccess: "हो सकता है कि आपके पास इस क्लिप तक पहुंच न हो.",
Expand Down Expand Up @@ -947,6 +949,14 @@ Clips में उपयोगकर्ताओं को दिखने व
updateTranscriptSharingFailed: "ट्रांसक्रिप्ट शेयरिंग अपडेट नहीं हो सकी",
},
deleteRecordingMenu: {
downloadAsGif: "GIF के रूप में डाउनलोड करें",
buildingGif: "GIF बन रहा है…",
archive: "आर्काइव",
unarchive: "आर्काइव से हटाएं",
archived: "क्लिप आर्काइव किया गया",
restoredFromArchive: "क्लिप आर्काइव से पुनर्स्थापित",
archiveFailed: "क्लिप आर्काइव नहीं किया जा सका",
unarchiveFailed: "क्लिप पुनर्स्थापित नहीं किया जा सका",
movedToTrash: "क्लिप ट्रैश में ले जाया गया",
deleteFailed: "क्लिप हटाने में विफल",
clipOptions: "क्लिप विकल्प",
Expand Down
10 changes: 10 additions & 0 deletions templates/clips/app/i18n/ja-JP.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,8 @@ const messages = {
pageTitle: "ミーティング · Clips",
},
recordingPage: {
buildingGif: "GIF を作成中… {{percent}}%",
gifExportFailed: "このクリップから GIF を作成できませんでした",
untitledClip: "無題のクリップ",
recordingNotFound: "録画が見つかりません",
noAccess: "このクリップにアクセスできない可能性があります。",
Expand Down Expand Up @@ -966,6 +968,14 @@ Clips のユーザー向けの主な変更はここに記録されます。コ
updateTranscriptSharingFailed: "文字起こしの共有を更新できませんでした",
},
deleteRecordingMenu: {
downloadAsGif: "GIF としてダウンロード",
buildingGif: "GIF を作成中…",
archive: "アーカイブ",
unarchive: "アーカイブ解除",
archived: "クリップをアーカイブしました",
restoredFromArchive: "アーカイブからクリップを復元しました",
archiveFailed: "クリップをアーカイブできませんでした",
unarchiveFailed: "クリップを復元できませんでした",
movedToTrash: "クリップをゴミ箱に移動しました",
deleteFailed: "クリップの削除に失敗しました",
clipOptions: "クリップオプション",
Expand Down
10 changes: 10 additions & 0 deletions templates/clips/app/i18n/ko-KR.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,8 @@ const messages = {
pageTitle: "회의 · Clips",
},
recordingPage: {
buildingGif: "GIF 만드는 중… {{percent}}%",
gifExportFailed: "이 클립에서 GIF를 만들지 못했습니다",
untitledClip: "제목 없는 클립",
recordingNotFound: "녹화를 찾을 수 없습니다",
noAccess: "이 클립에 액세스할 수 없을 수도 있습니다.",
Expand Down Expand Up @@ -955,6 +957,14 @@ Clips의 모든 사용자 대상 변경 사항은 여기에 기록됩니다. 명
updateTranscriptSharingFailed: "스크립트 공유를 업데이트하지 못했습니다",
},
deleteRecordingMenu: {
downloadAsGif: "GIF로 다운로드",
buildingGif: "GIF 만드는 중…",
archive: "보관",
unarchive: "보관 해제",
archived: "클립을 보관했습니다",
restoredFromArchive: "보관함에서 클립을 복원했습니다",
archiveFailed: "클립을 보관하지 못했습니다",
unarchiveFailed: "클립을 복원하지 못했습니다",
movedToTrash: "클립이 휴지통으로 이동됨",
deleteFailed: "클립 삭제 실패",
clipOptions: "클립 옵션",
Expand Down
10 changes: 10 additions & 0 deletions templates/clips/app/i18n/pt-BR.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,8 @@ const messages = {
pageTitle: "Reunião · Clips",
},
recordingPage: {
buildingGif: "Gerando GIF… {{percent}}%",
gifExportFailed: "Não foi possível gerar um GIF deste clipe",
untitledClip: "Clipe sem título",
recordingNotFound: "Gravação não encontrada",
noAccess: "Você pode não ter acesso a este clipe.",
Expand Down Expand Up @@ -975,6 +977,14 @@ Todas as mudanças visíveis para usuários do Clips são documentadas aqui. Voc
"Não foi possível atualizar o compartilhamento da transcrição",
},
deleteRecordingMenu: {
downloadAsGif: "Baixar como GIF",
buildingGif: "Gerando GIF…",
archive: "Arquivar",
unarchive: "Desarquivar",
archived: "Clipe arquivado",
restoredFromArchive: "Clipe restaurado do arquivo",
archiveFailed: "Não foi possível arquivar o clipe",
unarchiveFailed: "Não foi possível restaurar o clipe",
movedToTrash: "Clipe movido para a lixeira",
deleteFailed: "Falha ao excluir clipe",
clipOptions: "Opções do clipe",
Expand Down
10 changes: 10 additions & 0 deletions templates/clips/app/i18n/zh-CN.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,8 @@ const messages = {
pageTitle: "会议 · Clips",
},
recordingPage: {
buildingGif: "正在生成 GIF… {{percent}}%",
gifExportFailed: "无法从此剪辑生成 GIF",
untitledClip: "无标题剪辑",
recordingNotFound: "找不到录制",
noAccess: "您可能无权访问此剪辑。",
Expand Down Expand Up @@ -917,6 +919,14 @@ Clips 中所有面向用户的重要更改都会记录在这里。你可以随
updateTranscriptSharingFailed: "无法更新转写共享设置",
},
deleteRecordingMenu: {
downloadAsGif: "下载为 GIF",
buildingGif: "正在生成 GIF…",
archive: "归档",
unarchive: "取消归档",
archived: "剪辑已归档",
restoredFromArchive: "已从归档恢复剪辑",
archiveFailed: "归档剪辑失败",
unarchiveFailed: "恢复剪辑失败",
movedToTrash: "剪辑已移至废纸篓",
deleteFailed: "删除剪辑失败",
clipOptions: "剪辑选项",
Expand Down
Loading
Loading