Sharing
Share links, passwords, unfurls, the MP4 note, and exactly what a stranger can and cannot learn from a link.
Em dashes in quoted shipped strings are shown as plain hyphens; the originals use em dashes.
A link is live before the bytes are
A share link works the moment the row is registered, before any bytes arrive. The viewer says so itself: "Still processing. This page will show it as soon as it is ready - the link already works, so you can send it now." (apps/web/src/lib/share-access.ts:186, em dash shown as hyphen; joined-to-code: apps/web/src/app/s/[token]/share-viewer.test.ts).
The other two notice sentences, verbatim from apps/web/src/lib/share-access.ts:181-188:
- Failed transcode: "This one could not be processed, so there is nothing to play. Whoever shared it will need to send it again."
- Every other no-player state: "There is nothing here to show. Whoever shared it will need to send it again."
Deny-by-default: only a row explicitly in processing is told to wait; everything else gets the
dead-end sentence (shareNotice, apps/web/src/lib/share-access.ts; joined-to-code).
Password links
The gate is a plain form that works with JavaScript off and inside email preview panes. Wrong password: "That password is not right." Too many tries: "Too many attempts. Wait a few minutes and try again." (both in apps/web/src/app/s/[token]/page.tsx:146-147; the attempt limit is ten per link per ten minutes, apps/web/src/lib/share-password.ts:152). The unlock cookie is minted per link: the token is inside the signed message, so a cookie for link A proves nothing about link B (apps/web/src/lib/share-password.ts; joined-to-code: its unit tests and apps/web/e2e/share-password.spec.ts).
A gated link gives up nothing to a channel: unfurls and the tab title read "Password protected", never the real name, because the title is frequently the whole secret (apps/web/src/lib/share-access.ts:157; joined-to-code: oembed.test.ts and share-viewer.test.ts).
What a stranger learns from a dead or guessed link: nothing
No such token, expired, non-public, deleted and missing-asset all collapse into one identical page titled "This page isn't available" (apps/web/src/app/not-found.tsx:42), on purpose. The error page does not distinguish "never existed" from "existed and was deleted", so probing tokens teaches nothing.
Trash kills the link
Deleting a capture kills its share link immediately and restoring revives it. Pinned live by apps/web/e2e/library.spec.ts: 200, then 404 in trash, then 200 after restore (joined-to-code). See library-and-trash.md.
Downloads
The MP4 download note reads "MP4 re-encoded by Cloudflare - not the original file."
(DOWNLOAD_NOTE, apps/web/src/lib/media.ts:85, em dash shown as hyphen; one shared constant
rendered on both the share viewer and the dashboard asset page; joined-to-code:
share-viewer.test.ts and player.test.ts).
The Download link deliberately still appears when the player area shows a notice, because the file can be finished while the player is not, and in that state the link is the only thing on the page with the capture behind it (apps/web/src/app/s/[token]/page.tsx; joined-to-code: share-viewer.test.ts download gating).
allowDownload is an affordance, not protection: it decides whether the link is rendered, and
the file URL is world-readable either way. Do not describe it as access control.
No analytics on the viewer
The share viewer carries no analytics. PostHog mounts only inside the signed-in dashboard layout (apps/web/src/app/dashboard/layout.tsx), and scripts/check-promises.mjs fails the build if it ever moves back to the root layout (joined-to-code).
The sentence a support agent can paste
"The link starts working as soon as it is created; if the page says it is still processing, the recording just has not finished uploading or converting yet. Send the link whenever you like."