Skip to content

Add Archive and Download as GIF to watch page menu - #2496

Open
timmilazzo wants to merge 3 commits into
mainfrom
ai_main_327c555e48854cd98e8e
Open

Add Archive and Download as GIF to watch page menu#2496
timmilazzo wants to merge 3 commits into
mainfrom
ai_main_327c555e48854cd98e8e

Conversation

@timmilazzo

@timmilazzo timmilazzo commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds "Archive" and "Download as GIF" options to the three-dot menu on the Clips watch/view page, bringing it in line with the options already available in the library.

Problem

The watch page's clip options menu only offered "Download recording" and "Delete". Archive was already supported from the library view but missing here, and there was no way to export a clip as a GIF at all.

Solution

  • Wired the existing RecordingOptionsMenu on the watch page to support archiving and to trigger a new GIF export flow using the existing ffmpeg-based export pipeline, reusing canEdit/canDelete permission logic for gating visibility.
  • Extended exportGif to accept configurable width/fps options so the watch page can request a larger, smoother download than the existing library thumbnail use case.

Key Changes

  • app/routes/r.$recordingId.tsx: added canDownloadGif / canArchiveRecording derived flags, a downloadGif callback that calls exportGif with progress toasts and triggers a file download, and passes new canDownloadGif, gifPending, onDownloadGif, canArchive, isArchived, and onArchiveChanged props into RecordingOptionsMenu.
  • app/lib/ffmpeg-export.ts: added a GifOptions interface (width, fps) to exportGif, defaulting to the previous 320px/15fps values, with the watch page using 480px/12fps for shareable full-length GIFs.
  • Added i18n strings (recordingPage.buildingGif, recordingPage.gifExportFailed, and deleteRecordingMenu.downloadAsGif, buildingGif, archive, unarchive, archived, restoredFromArchive, archiveFailed, unarchiveFailed) across locale catalogs (pt-BR, zh-CN, zh-TW, etc.).
  • Added a changelog entry documenting the new Archive and Download as GIF options.
  • Added an implementation plan document (docs/plans/2026-07-29-clips-display-name-prompt.md) covering related future display-name prompt work.

Edit in Builder  Preview


To clone this PR locally use the Github CLI with command gh pr checkout 2496

You can tag me at @BuilderIO for anything you want me to fix or change

@netlify

This comment has been minimized.

@builder-io-integration builder-io-integration Bot changed the title Update from the Builder.io agent Add Archive and Download as GIF to watch page menu Jul 29, 2026
@netlify

This comment has been minimized.

@netlify

This comment has been minimized.

@steve8708

Copy link
Copy Markdown
Contributor

How important do you think gifs are? Any clip longer than a few seconds would generate a massive gif that would be best for people to not use

@timmilazzo

Copy link
Copy Markdown
Contributor Author

Gifs aren't critical. I played with the idea of a gif replacing the static thumbnail in the recipient share invite, but we went with static + play button.

@builder-io-integration builder-io-integration Bot left a comment

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.

Builder reviewed your changes and found 3 potential issues 🟡

Review Details

Code Review Summary

PR #2496 adds Archive/Unarchive and Download as GIF to the Clips watch-page options menu. The menu reuses the existing action surface and editor-level access checks, and the player action now exposes archivedAt so the archive state can be represented correctly. The localized labels and disabled/pending states are also wired consistently. This is a standard-risk UI plus client-side media-processing change.

Key Findings

  • 🟡 MEDIUM — Full-length GIF exports are unbounded and can exhaust browser/ffmpeg.wasm memory on ordinary long recordings.
  • 🟡 MEDIUM — A failed GIF export can leave the fetched input and partial output in the shared ffmpeg filesystem, affecting later exports in the same tab.
  • 🟡 MEDIUM — Unarchive on a recording that is both archived and trashed invokes restore-recording, which clears both lifecycle flags and silently resurrects it from Trash.

The archive mutation wiring and permission boundary otherwise align with the existing action contract. Browser testing was attempted with a healthy Clips dev server, but all executor sessions lacked the required Chrome browser tools, so no visual test case produced evidence.

🧪 Browser testing: Could not verify — 16 planned cases escalated because browser automation tools were unavailable in executor sessions.

Comment on lines +574 to +575
0,
recording.durationMs,

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.

🟡 Bound full-length GIF exports before invoking ffmpeg

downloadGif passes the recording's entire durationMs to browser-side ffmpeg.wasm at 480px/12fps. Long recordings can exhaust tab memory or produce an impractically large GIF; add a bounded duration/size policy or require an explicit safe-range confirmation before starting the export.

Additional Info
Found by 2 of 4 code-review agents; browser planner also targeted this risk but could not execute due unavailable browser tools.

Fix in Builder

t("recordingPage.buildingGif", { percent: 0 }),
);
try {
const blob = await exportGif(

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.

🟡 Clean up ffmpeg files when GIF encoding fails

The new GIF path calls exportGif, whose input/output files are deleted only after a successful exec and readFile. If encoding fails, the helper's finally removes only the progress listener, leaving the fetched source and any partial output in the singleton ffmpeg filesystem for the tab. Move per-export file deletion into a best-effort finally so failed exports release their media too.

Additional Info
Found by 1 of 4 code-review agents; confirmed by inspection of exportGif's failure path.

Fix in Builder

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

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants