Skip to content

Save a video uploaded into a Content page as a Clip - #8183

Open
3mdistal wants to merge 12 commits into
clips-content-upload-clipsfrom
clips-content-upload
Open

3mdistal wants to merge 12 commits into
clips-content-upload-clipsfrom
clips-content-upload

Conversation

@3mdistal

@3mdistal 3mdistal commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

A video uploaded into a Content page should become a Clip: played with Clips' own player, saved in Clips rather than in Content, and visible to exactly the people who can see the page. With Clips in pages turned on (#8147), a pasted Clips link already plays as a Clip. Uploading a file into a video block, though, still saves a plain video in Content's own storage. That upload is capped at 25 MB, needs storage set up in Content with a public URL, and the video is served from that URL with none of a Clip's privacy, player or comments.

This is the Content side of step A4 of "Clips videos embedded natively in Content docs". It is stacked on the Clips side (#8182), which stacks on #8147, #8090, #8088 and #8059, and it will be rebased onto main once those merge. Everything new is behind the content.clip-embed flag, off by default. With the flag off, uploads work exactly as they do on main.

Approach

The Upload tab says where the video will go before anyone picks a file. While the flag is on, the video block's Upload tab asks Clips through the new get-clip-upload-status action and shows its answer as it is: "Saved as a Clip · 2 free clips left", "Saved as a Clip" when the editor's Clips workspace has its own storage, or which setup step is missing. Content never counts free clips itself.

Only the page's editors can start an upload, and Content checks that before Clips hears anything. Picking, dropping or pasting a video calls create-clip-upload. Both actions run the same check (authorizeClipUploader) before minting a token:

  • the caller is signed in, and isn't the public-viewer identity;
  • they can edit the page (editor, admin or owner);
  • the page isn't in Trash;
  • the flag is on for them.

Only then does Content mint a one-minute content-clip:upload token with the page-access secret it shares with Clips. The token names the editor and the page, and carries the browser's address from Netlify's x-nf-client-connection-ip so Clips' free storage counts per network. Content sends Clips the file's description only (name, size, type, the block id the Clip will have, and the uploadKey it made for this picked file), never the file. Clips makes a private Clip bound to the page and answers with a ticketed chunk URL. Content accepts that URL only if it is on one of Clips' own origins and names that recording.

A start repeated for one picked file reaches the upload it made. Each picked file gets one uploadKey and one block id, sent with every start of it, so Clips makes at most one Clip per pick (#8182). The tab repeats a start only for a file it holds after a storage refusal, from Check again or from coming back to the tab. A storage refusal leaves nothing in Clips, so the repeat reaches an earlier upload only when an earlier start of the file got to Clips and its answer was lost. Clips then answers with that upload:

  • a ticket that carries it on;
  • the Clip itself when Clips already has the whole file: the block becomes that Clip with nothing uploaded, or the tab says the video was already saved as a Clip, with Open in Clips, when the block can't go in;
  • upload_starting while Clips is still making the first start's Clip: the tab repeats the start after 2, 5 and 10 seconds, then says "The video didn't upload. Try again." and keeps the file.

The browser sends the file straight to Clips, and the file stays in that tab's memory. Once the upload starts, the video block becomes <ClipEmbed … origin="upload" /> in the page. The tab then posts the file to Clips in the ticket's chunks, one at a time. The chunks go with credentials: "omit", no headers and no content type, so the browser sends no CORS preflight and no cookie. A chunk that fails to send, or that Clips answers with a 429 or a server error, is sent again after 1, 3, 10 and 30 seconds. Any other refusal, or a chunk still failing after the last retry, ends the upload. If the answer to the last chunk was lost and a resend finds the Clip already processing or ready, that counts as sent. While the file is being sent, leaving or reloading the page asks first.

An upload this tab gives up is cancelled in Clips right away. Clips' start answer carries a cancel URL that takes the same ticket (#8182), which Content checks like the chunk URL. The tab cancels the upload, and the person is told, when:

  • the video block can't become a Clip block after Clips started the upload, because the block is gone or the person can no longer edit the page (a toast says which);
  • the Clip block has been gone from the page for 5 seconds while the file is sending (a toast says so; a block that comes back sooner, through undo or a move, keeps sending). Those 5 seconds run on if the editor closes, and a page that opens without the block starts them then;
  • a chunk fails for good, as above (the block shows "This upload didn't finish").

The cancel releases the upload's free clip in Clips. Clips' answer says what happened (#8182), and the message follows it:

  • Clips cancelled it: the message names the reason, as above.
  • Clips had the whole file first: "The video was already saved as a Clip.", with Open in Clips. A failed send that finds this shows "Making the Clip", not "didn't finish".
  • Clips confirmed neither: the upload stopped, but Clips didn't confirm the cancel.

A tab closed mid-upload can't cancel; Clips fails that upload about an hour later.

Each stage shows in the block, for the uploader and for everyone else.

Stage The uploader sees Other readers see
Sending "Uploading to Clips · 42%" "Uploading to Clips"
Clips makes the Clip "Making the Clip" "Making the Clip"
Made The player, and a "Saved as a Clip" toast with Open in Clips The player
Never finished "This upload didn't finish." with Remove The same message, without Remove

While a Clip is being made, each open block asks Clips about it after 4 seconds, then twice as long each time up to once a minute, and from 4 seconds again whenever the status changes. It never asks while its tab is hidden. After 15 minutes with no change it stops and shows Check again, and asks once more when the tab gets focus or becomes visible. The uploading tab follows its own send instead, and starts asking once the send ends. An upload that stops sending, or that Clips marks failed (including when its lease runs out), ends as "didn't finish". Removing that block takes it out of the page for everyone.

When Clips can't take the file yet, the file waits in the block. Nothing goes into the page until the upload starts.

Storage state The block says
Free clips used up "Connect Builder.io for free to keep uploading", with "2 of 2 free clips used", Connect Builder.io, Custom keys, and a "Why am I seeing this?" tooltip
No storage and no free clips "Connect storage to finish this upload"
No Clips account yet "Open Clips to finish this upload"

The links open Clips' own setup pages in a new tab. The block checks again when the tab regains focus or becomes visible, never while it is hidden, and there is also a Check again button and Remove file. As soon as Clips can take the file, the upload starts by itself. Remove file wins over a check or a start already under way: nothing uploads for the removed file, it doesn't come back, and a ticket Clips already issued for it is cancelled. A video that isn't MP4, WebM or MOV is refused before Clips is asked. Dropped or pasted, it gets a message naming those types and no block, while the supported files in the same drop still go. One larger than the editor's storage takes is refused by Clips before anything is made, and a toast says how large a video can be.

Uploads are UI-only. Both actions are agentTool: false and persistInBrowser: false: an upload needs the file's bytes in the editor's tab and a ticket only that tab can use. The agent sees this tab's uploads through view-screen's clipUploads (app state content-clip-uploads, holding each upload's recordingId, documentId, fileName, phase and percent, and never the file). An upload whose block has been gone for 5 seconds drops out of it, whatever its phase. It can still link an existing Clip by writing the ClipEmbed tag with edit-document.

What changed

  • templates/content/actions/: get-clip-upload-status (GET, read-only) and create-clip-upload, with _clip-upload.ts for the storage schema, the uploader's address, and the check that the chunk and cancel URLs are Clips' own, for that recording. create-clip-upload sends the uploadKey and reads a repeat's upload_already_finished and upload_starting answers. view-screen adds clipUploads.
  • Changes to A3's code (Play a pasted Clips link inside a Content page, with its comment markers #8147):
    • actions/_clip-embed-access.ts gains authorizeClipUploader.
    • server/lib/clips-peer.ts mints the upload token (with client_ip) and gains requestClipsUpload, which maps Clips' answers to typed results.
    • shared/clips-peer-contract.ts adds the upload scope, storage states, refusal codes (with a repeat's upload_already_finished, upload_starting and upload_key_used), CONTENT_CLIP_UPLOAD_KEY, CLIP_UPLOAD_ERRORS, the ticket's cancelUrl, and the cancel's outcomes (CLIP_CANCEL_OUTCOME).
    • ClipEmbedBlock.tsx gains the uploading, making and didn't-finish states, and a Check again link once the checks on a Clip being made stop. It splits out the player so its hooks stay in the same order from upload to playback.
    • use-clip-playback.ts checks again while a Clip is being made, on the schedule in CLIP_MAKING_CHECK_MS, and leaves the grant-expiry refresh until the Clip is made. Merged with Play a pasted Clips link inside a Content page, with its comment markers #8147's latest playback changes, those checks use its "asked" refresh, and the Clip block passes its resumeRef through to the player.
    • use-clips-links.ts adds Clips' setup and recording URLs.
    • clip-embed-content.ts lets a new Clip block take the block id Clips bound it to.
  • templates/content/app/components/editor/clip-embed/clip-uploads.ts: this tab's uploads, the chunk sender, the cancel and its typed answer, the watch for a removed Clip block (it gives up an upload still sending and forgets any other; its 5-second timers live with the uploads, not with an editor), and dropped files waiting for their block. clip-upload-notices.ts says what a cancel did, and that a Clip was already saved. ClipUploadGate.tsx is the Upload tab's hint and the waiting gate, and makes each pick's key and block id.
  • extensions/VideoBlock.tsx and VisualEditor.tsx: the flag-on Upload tab, a cancel when the block can't become a Clip block, a Clip Clips already had placed with nothing uploaded, and dropped or pasted videos sent to Clips, with types Clips won't take refused. VisualEditor starts the removed-block watch once the page's content has synced. With the flag off, both paths are main's.
  • app/clip-embed-messages.ts: every new string, in all 11 locales.
  • .agents/skills/document-editing/references/document-behavior.md: "Video uploads become Clips", for the agent.
  • parity/matrix.ts and matrix.md: a row for the two hidden actions.

Safety and operations

  • Rollout: flag content.clip-embed, off by default. With it off, the Upload tab, drops and pastes use Content's file storage as on main. Turning it off stops new uploads; Clip blocks already in pages show "Clips in pages aren't turned on." (Play a pasted Clips link inside a Content page, with its comment markers #8147).
  • Deploy order: the Clips PR must be live before the flag turns on here. Against an older Clips, the upload is refused and nothing is saved.
  • Configuration: needs CONTENT_CLIPS_PEER_SECRET on both apps (from Let Content document readers play and comment on Clips uploaded to the document #8090). Without it the Upload tab says Clips isn't connected, and nothing is minted.
  • Data: no schema change and no migration. The page body gains a <ClipEmbed … origin="upload" /> tag when an upload starts. The file never reaches Content's server or storage.
  • Free clips: an upload this tab gives up is cancelled at once, and its free clip released. One left by a closed tab holds one of the editor's free clips until Clips' lease reaper fails it, about an hour after its last chunk. A Clip whose block was removed after it was made stays in the uploader's From Content in Clips; Trash for it is A6.
  • Load: a reader tab asks about a Clip being made at most once a minute after its first minute, never while hidden, and stops after 15 minutes with no change.
  • Rollback: turn the flag off. Reverting the code would leave saved <ClipEmbed … origin="upload" /> tags with nothing to render them.

Verification

  • At ea7b351, in Content, round 3. Its tests were written first. On the Content code from before round 3 (a908891), 12 of the 140 tests in the 4 changed test files fail; all of them pass now. The 12 cover:

    • create-clip-upload sending the uploadKey and reading upload_already_finished and upload_starting. 4 failed: these two, plus the existing ticket and validation tests, which now check the key.
    • The video block. 6 failed: these five, plus the existing upload test, which now checks the key.
      • It sends one key and block id with every start of a pick.
      • It repeats a start that gets upload_starting after 2, 5 and 10 seconds, and starts the upload once a ticket comes.
      • If every repeat still gets upload_starting, it shows "The video didn't upload. Try again."
      • It places a Clip that Clips already has, or says the video was already saved when the Clip's block can't go in.
    • A removed block's 5 seconds running on after the editor closes, and starting when a page opens without the block. 2 failed.

    Three more tests guard against cancelling too much, and pass before and after: a block back in time, one put back by an outside merge, and an editor closing with the block in place.

  • At the earlier head a908891, in Content: the clip-embed folder and the video block's upload tests pass, 137 tests in 5 files. What round 2 adds:

    • Remove during a check, from Check again or from coming back to the tab, with storage then ready, asks Clips for no ticket and starts nothing. A check that finds no storage doesn't bring the file back, and a Remove during the start cancels the ticket. These 4 failed before the fix;
    • cancelClipUpload maps nine answers to cancelled, already_finished or not_cancelled. A failed send whose cancel finds the Clip made ends as making, and a given-up upload reports what its cancel did. The video block shows the cancel message, the saved-as-Clip message with Open in Clips, or the unconfirmed message;
    • a block deleted while its Clip is being made, or after a failure, loses its entry and view-screen line after 5 seconds, with no cancel; one back sooner keeps it. With the old clip-uploads.ts, 15 of these tests failed.
  • At the earlier head fb52423, in Content: the 8 test files round 1 touched pass, 171 tests. What they add:

    • the swap into a Clip block failing cancels the upload and shows the toast; that test failed before the fix (2 failed, 20 passed), and lost edit access shows its own message;
    • a refused chunk and exhausted retries cancel, and a fully sent upload doesn't;
    • a Clip block gone for 5 seconds cancels and stops sending; one back within the grace keeps sending; closing the editor, or another page's editor, gives nothing up; a repeated give-up cancels once;
    • a cancel URL that is missing, on another origin, for another recording, on the chunk route, or without a ticket is refused;
    • on a fake clock, the checks on a Clip being made back off (checks at 4, 12, 28, 60, 120, 180 and 240 seconds), restart on a new status, stop after 15 minutes with Check again, resume on focus and visibility, and never run while hidden;
    • an unsupported drop and paste are refused with no block, a mixed one keeps the MP4, and the flag off changes nothing; 4 of these failed with the filter taken out.
  • At the earlier head 11e9b0f, in Content: the touched test files pass, 262 tests in 23 files (the clip-embed folder, clips-peer, the upload actions, both video-block upload test files, and parity). They cover:

    • authorizeClipUploader: viewers and commenters, a page the caller can't read, a page in Trash, a signed-out caller and the flag off are each refused before Clips is asked;
    • the upload token naming the editor, the page and the address, and no recording;
    • only a trusted address being sent;
    • a chunk URL off Clips' origins refused;
    • chunks sent in order with no credentials, retried after a passing failure, stopped at a refusal, and a lost last answer counted as sent;
    • the leave warning;
    • the block's states for the uploader and for other readers;
    • the toast;
    • the check on a Clip being made, only while the tab shows;
    • the gate's links, its focus re-check (none while hidden), Check again, Remove file, and the held file starting by itself;
    • dropped videos;
    • the free-clip count refreshing after a start;
    • with the flag off, uploads and drops going to Content's own storage.
  • The full Content suite at ea7b351: 7827 passed, and 67 failed in 8 files.

    • 22 of the failures are known-loss tests in 5 files, which main fails by design and CI's gate leaves out (the same count as round 1's run). Run alone, those 5 files fail these 22 and nothing else.
    • The other 45 are in github-source.db.test.ts, DocumentEditor.recovery-save-state.test.tsx and document-search-ranking.perf.test.ts, where one test timed out under the full run's load and the rest followed. Run alone, all three files pass.
  • pnpm typecheck and oxfmt --check are clean in Content at ea7b351 and in Clips at 66e6440.

  • pnpm guards at ea7b351: all 93 checks pass, including guard:i18n-catalogs and guard:i18n-changed-copy. Round 3 adds no copy: a start that never gets past upload_starting reuses "The video didn't upload. Try again."

  • An independent model review of the whole A4 diff found six issues, all fixed with tests:

    • the Clip block's hooks changing order between upload and player;
    • a failed check hidden behind "Making the Clip";
    • the chunk preflight (Clips PR);
    • a lost last-chunk answer counted as a failure;
    • the Upload tab hiding a status error;
    • Clips failing a Clip while this tab showed "Making".
  • Manual QA, on local Content and Clips from this branch at 5ec7bfc251fc (only the parity matrix changed after). It used fake accounts (an editor, a viewer of the page with no Clips account, and an outsider), fake S3 standing in for Builder's free storage with a 2-clip limit, and ffmpeg test videos. The recordings are real-speed, silent 1440x900 takes of main and of this branch. They stay local and aren't attached here. Everything below passed:

    • every gate state: 2 left, 1 left, used up with the tooltip, custom keys saved in Clips and the held upload resuming by itself on return, no storage, and no account;
    • the toast and Open in Clips;
    • uploads listed under From Content in Clips, while a Clip made in Clips and linked into the page stays in Library;
    • the viewer watching "Uploading to Clips" become a player, then playing it;
    • the leave warning mid-upload, and no warning after;
    • the outsider refused on the page in Content and on the Clip in Clips;
    • an upload cut off by stopping Clips: "didn't finish" with Remove for the editor and without Remove for the viewer, after Clips' real lease reaper failed it (its one-hour lease was moved into the past for that Clip), and Remove clearing it for both;
    • with the flag off: main's Upload tab, storage gate, 25 MB limit and plain video, matching the main takes.

    That pass came before the review rounds' changes, which have tests but no manual pass:

    • the cancel and its outcomes;
    • the slower checks;
    • the drop and paste filter;
    • Remove during a check;
    • forgetting a removed block's upload;
    • repeated starts, and the removed-block timer outliving the editor.

Review focus

  • authorizeClipUploader and both actions: is there any path that mints an upload token, or reaches Clips, before the edit, Trash and flag checks pass?
  • postChunk, cancelClipUpload and isClipUploadUrl: the browser sends the file wherever the chunk URL points, and the cancel wherever the cancel URL points, so the URL check and the credential-free simple request are what keep both going only to Clips.
  • watchClipUploadBlocks: can a block that is still in the page, or comes back within the 5 seconds, get its upload cancelled or forgotten? With the timers now outliving the editor, can a page that is still loading, or open in a second editor, look like it lost the block?
  • useClipFileUpload: can anything a Remove interrupted still start an upload or bring the file back, including during the upload_starting retries? Does every start of one pick send the same key and block id?
  • cancelClipUpload and announceClipCancel: an already_finished or unconfirmed answer must never show as cancelled.
  • clip-uploads.ts's phases and ClipEmbedBlock's states: can a block get stuck in "Uploading" or "Making", or show "didn't finish" for a Clip Clips made?
  • VideoBlock.tsx and VisualEditor.tsx with the flag off: they should be main's paths exactly.

Known limits and follow-ups

  • A tab closed mid-upload holds a free clip for about an hour, until Clips' lease reaper fails it; only an open tab can cancel. A start that fails after Clips reserved a free clip, and whose cleanup Clips can't confirm, keeps that reservation; that is Clips' existing behavior.
  • Uploads don't resume after a reload or a lost tab. The file lives only in the tab that picked it. The upload ends as "didn't finish", and the editor uploads it again.
  • A block removed while the uploader is on another page isn't noticed until they open it again. The watch runs in the uploader's tab: closing the editor no longer stops it, and opening a page checks it at once. But a block a collaborator removes while that tab shows another page waits for the tab to open the page again. Until then the upload keeps sending, and the Clip can be made with no block.
  • A file whose start Clips keeps answering upload_key_used stays held. That happens once a held file's lost start is cancelled or fails in Clips, or goes ten minutes without its recording. Check again then shows "The video didn't upload. Try again." each time, and the editor removes the file and picks it again, which makes a new key.
  • A Clip whose block was removed stays in the uploader's From Content in Clips. Trash for uploaded Clips and their pages is A6. Clips already treats an upload binding in Trash as inactive (Take a video uploaded in a Content page as a private Clip bound to the page #8182), but nothing moves one to trashed until A6.
  • Local development only: the uploader sat at 95% for about 96 seconds on the last chunk. Clips' local post-upload step answers only after its thumbnail job, which keeps retrying a fetch that the SSRF guard blocks for a bucket on 127.0.0.1. Readers already saw the Clip. Clips' own uploads behave the same way locally, and production answers once the upload is accepted.
  • Found in A3 (Play a pasted Clips link inside a Content page, with its comment markers #8147), not changed here: for about 20 seconds after the flag was turned off, a reload still played two Clips. (A failed grant refresh stopping a playing Clip, also found here, is fixed in Play a pasted Clips link inside a Content page, with its comment markers #8147 at 6554fcb.)
  • Found elsewhere: guard:no-pnpm-patches scans gitignored .tmp/ folders, so a local Python virtualenv there fails it.
  • Later steps: margin comments on a Clip (A5); Trash for uploaded Clips, agent tools, the changelog entry and turning the flag on (A6). The changelog entry waits for A6, when the feature turns on.

🤖 Generated with Claude Code

@github-actions

Copy link
Copy Markdown
Contributor

Here's a visual recap of what changed:

Visual recap

Open the full interactive recap

builder-io-integration[bot]

This comment was marked as outdated.

@3mdistal

Copy link
Copy Markdown
Contributor Author

On the review's LOW item (unsupported dropped or pasted formats leaving empty blocks): fixed in fb52423.

While uploads go to Clips, drop and paste now take only the video types Clips accepts (MP4, WebM and MOV, from CONTENT_CLIP_UPLOAD_MIME_TYPES). Any other file is refused with a message naming those types, and no block is added. A mixed drop or paste keeps the supported files and refuses the rest. With Clips in pages off, nothing changes.

VisualEditor.clip-drop.test.tsx covers an unsupported drop, an unsupported paste, a mixed drop and paste, and the flag off. Four of those failed with the filter taken out.

builder-io-integration[bot]

This comment was marked as outdated.

# Conflicts:
#	templates/content/app/components/editor/clip-embed/ClipEmbedBlock.tsx
#	templates/content/app/components/editor/clip-embed/use-clip-playback.ts
…ock is gone, and upload nothing for a file removed while the gate checks
@3mdistal

Copy link
Copy Markdown
Contributor Author

Round 2 is at a908891. It merges #8182 at dad59d2, which brings in #8147 at 6554fcb.

Review 5639723530, LOW: a deleted block left its upload in clipUploads. The block watcher now covers every upload on the page, not only ones still sending. Once a block has been gone for the 5-second grace, its entry is cleared whatever the phase, so view-screen stops listing it. An upload still sending keeps its cancel and its message.

Tests delete the block while the Clip is being made and after a failure. In both, the entry and its view-screen line go away and no cancel is sent. A block that comes back within the grace keeps its entry.

Thread replies:

From the merge: the checks on a Clip being made now use #8147's refresh("asked"). The expiry refresh still skips while a Clip is being made, and the Clip block passes #8147's resumeRef through to the player.

builder-io-integration[bot]

This comment was marked as outdated.

…p Clips already has, keep a removed block's five seconds running after the editor closes, and ask again while Clips is still making the first start's Clip

@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 2 potential issues 🟡

Review Details

Incremental Review Summary

The latest changes fix the prior deleted-block cleanup issue: grace timers now outlive an editor watcher, check page state on expiry, and are covered by unmount/remount tests. I verified the fix and resolved the prior review thread. The PR still uses server-authorized, page-scoped Clips uploads and bounded status polling; the cross-app upload boundary remains high risk. I found two new medium issues and two lower-severity correctness issues.

Findings

  • 🟡 MEDIUM — A stalled chunk request has no timeout: The upload can remain in uploading indefinitely while its block remains present.
  • 🟡 MEDIUM — Storage hint claims videos are saved as Clips without storage: For none/account_required (and before the status query resolves), the UI displays success-style copy although no upload is made yet.
  • LOW — Malformed completed recording IDs can enter page content: The already_finished response validates only non-emptiness, unlike the started path.
  • LOW — Agent-visible upload state can outlive its editor or tab memory: view-screen may return a stale making/failed snapshot after the page is closed or the tab reloads.

🧪 Browser testing: Skipped — the browser-test-planner agent is unavailable in this environment.

// A body with no type and no headers keeps this a simple CORS request:
// Clips' auth layer answers every preflight, and the ticket in the URL is
// the only authority the request carries.
const response = await fetch(url, {

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.

🟡 Chunk upload requests can hang without a timeout

The chunk fetch only uses the user-controlled abort signal and has no request timeout. If Clips accepts a chunk but never settles the response, sendClipChunks never reaches its bounded retry loop and the block remains in the uploading state indefinitely while it stays in the page; apply a per-chunk timeout and treat it as retryable.

Fix in Builder

: t("clipEmbed.savedAsClipFreeNone")
: storage?.state === "used_up"
? t("clipEmbed.savedAsClipFreeNone")
: t("clipEmbed.savedAsClip");

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.

🟡 Storage hint says videos are saved when Clips has no storage

This fallback displays “Saved as a Clip” for none, account_required, and while storage is still undefined. In the first two states the file is held and no upload starts, so this contradicts the setup gate; show neutral/loading copy or a message that accurately describes the pending setup instead.

Fix in Builder

This branch has not been deployed

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant