Use this guide for Vercel previews, SSR metadata, runtime checks, and rollback. Earlier provider comparisons and PR verification reports are collected in the historical section.
README | Development | Player | Mobile Safari | Deployment
Production runs on the Git-connected team-leftium/youloop Vercel project
at youloop.leftium.com. Pushes to main trigger
Vercel builds using vercel.json and the SvelteKit 3 Vercel adapter. The owner
moved the custom-domain DNS from GitHub Pages to Vercel on 2026-10-12,
before PR #25 merged. All routes serve
from the domain root; no SvelteKit base path is configured.
The former GitHub Pages publish workflow has been retired. The GitHub Pages site/custom-domain configuration may still exist in repository settings; deleting the workflow does not disable that site or change its settings. The static rollback build was retired when video-specific server metadata was added. A prerendered page cannot resolve a video from request query parameters.
The Git-connected team-leftium/youloop
project deploys main to production. Pushes to other branches and pull requests
receive Preview deployments in the same project. Review the commit-specific
Preview URL in the GitHub PR or Vercel dashboard, and confirm its commit SHA and
environment before merging. vercel.json keeps automatic Git deployments enabled.
PR #25 used a temporary, Git-disconnected team-leftium/youloop-staging
project while the original project was paused. That separate project is no longer
needed and can be deleted manually from the Vercel dashboard. Deleting it also
removes its historical deployment URLs and project-specific configuration; it
does not remove the separately configured production youloop project.
The app uses Node 24, the sveltekit framework, and the committed install/build
commands in vercel.json. No application secrets are currently required. Local
.vercel/ and OIDC environment files must remain untracked.
DEPLOY_TARGET accepts only vercel (the default). Other targets, including
static, fail the build. The Vercel build renders the player shell and social
metadata per request. Video source creation, player URL parsing, and playback
remain in browser mount handlers.
For a future dual Vercel/Workers deployment, add a cloudflare adapter branch
to vite.config.ts, a separate Wrangler config and CI build, then repeat the
HTTP/browser suite in Workers. Keep shared hooks on Web APIs and put any
provider bindings behind server-only boundaries. Current shared code has no
Vercel SDK calls, ISR configuration, or route-level vendor settings. Workers
deployment is an extension point, not a verified target in this change.
Direct requests and reloads of /?v=VIDEO_ID&a=31&b=38 and
/s?v=VIDEO_ID&a=31&b=38 render Open Graph and Twitter Card tags in the initial
HTML. A shared server layout calls src/lib/server/video-metadata.ts once and
passes plain data to SocialHead.svelte; Svelte escapes it during rendering.
The Video.js custom element and watch controls still load inside browser mount.
Canonical /<videoId>/A~B watch links are supported; see the
player URL contract.
YouTube's fixed HTTPS oEmbed endpoint supplies the title and fallback thumbnail without an API key. The resolver optionally discovers YouTube's own social image from a bounded crawler-style watch-page prefix and validates its JPEG dimensions. oEmbed normally has no video description, so the preview uses title and creator attribution plus YouLoop's segment-repeat description. Upstream embed HTML is ignored. IDs must contain exactly 11 ASCII letters, digits, hyphens, or underscores; duplicate IDs and malformed values cause no lookup. Thumbnail URLs must use a known YouTube image host and the requested video path. Missing or unavailable metadata uses the YouLoop title and PNG icon.
og:url uses https://youloop.leftium.com and the shared URL codec's canonical
video path and clip bounds. Playback/framing flags and tracking parameters do not
change preview identity. Invalid input keeps safe generic metadata. Preview
deployments also emit the production origin.
Each warm server instance keeps at most 1,000 cached videos: successful lookups last one hour and failures one minute. Concurrent requests for the same video share a pending lookup. One two-second deadline covers oEmbed, optional native image discovery, and JPEG-header validation; redirects are disabled throughout. There are at most three outbound requests per cache miss (180/minute per instance at the lookup budget), rather than three separate timeout windows. Native discovery parses at most 32 KiB of HTML and 32 KiB of JPEG headers and cancels the remaining streams. At most eight lookups run at once and 60 new lookups start per minute per instance; excess requests render defaults without queueing. Cold starts and multiple instances have independent caches and budgets. These limits reduce upstream traffic but are not a distributed rate limiter.
Normal SvelteKit SSR keeps the metadata in the usual route/head pipeline. The player shell is small and media initialization already happens in the browser, so a custom metadata-injection hook has no demonstrated cost benefit. Vercel Fluid Compute meters active CPU, provisioned memory, and invocations (pricing). Caching reduces repeat upstream work; it does not eliminate function invocations or measure savings. HTML CDN caching is deferred: query-dependent clip identity, default responses during upstream failures, and SvelteKit data-navigation variants need a deliberate cache policy before enabling it. Static assets keep their normal hosting cache behavior.
Run pnpm test:metadata for deterministic cache, timeout, limits, URL safety,
fallback, and server-head escaping checks. With a built server running:
HOSTING_URL=http://127.0.0.1:4173 pnpm test:social
# Require successful live YouTube metadata rather than accepting safe defaults:
HOSTING_URL=https://YOUR-PUBLIC-PREVIEW.vercel.app REQUIRE_VIDEO_METADATA=1 pnpm test:socialThe social smoke suite sends crawler-style HTTP requests without executing JS.
CI permits upstream fallback because YouTube availability is external; release
verification should also use REQUIRE_VIDEO_METADATA=1 on the public preview.
Real social platforms cache unfurls independently; a successful HTTP check
does not prove that a platform has refreshed its preview.
YOULOOP_RUNTIME_PROOF=1 opts into fresh UUID/time response headers and
Cache-Control: private, no-store on ordinary server routes. It is off by
default. There is no diagnostic endpoint. Omit
the flag and rebuild for a normal deployment; changing runtime env alone does
not remove a compiled-in proof flag.
YOULOOP_RUNTIME_PROOF=1 pnpm build
pnpm preview --host 127.0.0.1 --port 4173
# In another terminal:
HOSTING_URL=http://127.0.0.1:4173 pnpm test:hosting
# Requires a Vercel deployment built with YOULOOP_RUNTIME_PROOF=1:
HOSTING_URL=https://YOUR-PROOF-ENABLED-DEPLOYMENT.vercel.app pnpm test:hostingThe smoke suite requests / and /s?v=dt-SqNL4z3w&a=31&b=38 twice, checks route-specific HTML and distinct server-generated headers, and
checks an SVG asset and a missing-route 404. This distinguishes runtime SSR
from a static SPA fallback. vite preview runs the production-built Kit server
locally; hosted checks exercise the actual Vercel function. Browser reload and
YouTube interaction checks are separate from HTTP tests. The
hosting CI workflow checks the server build,
types, lint, URL codec, metadata resolver, and production-mode HTTP.
It does not publish to GitHub Pages or change Vercel domain settings.
The owner switched the custom domain to the original Vercel youloop project
on 2026-10-12. Vercel reported successful deployment of merged PR #25 at
e937e7e.
Normal releases follow the connected main branch. The same Vercel project
creates Preview deployments for pull requests and other branches.
- For a release, confirm the production deployment's SHA, branch, domain
assignment, and HTTPS certificate in the Vercel dashboard. Check direct loads
and reloads of
/and/s?v=dt-SqNL4z3w&a=31&b=38, along with assets, playback, and mobile scrolling. - For an application regression, promote or redeploy a known-good version in
the original
youloopVercel project, then recheck the domain and routes. This rollback normally needs no DNS change. - An emergency GitHub Pages rollback requires checking out a pre-metadata version (for example, PR #26), restoring its static build and retired publishing workflow, and changing Pages settings and DNS. It loses video-specific server previews. Prefer a known-good Vercel deployment.
- If GitHub Pages remains enabled in repository settings, disable the unused site and remove its old domain association separately when no longer needed. Removing the workflow alone does not modify Pages settings.
For preview comparisons, open a commit-specific Vercel Preview deployment. To
roll back production, restore a known-good deployment in the original youloop
project; do not promote an unverified Preview build.
The following comparison and verification reports were recorded for PRs #25 and #27 on 2026-10-12. They describe those commits, deployments, commands, and limits. They are not current verification results or standing merge requirements; retired commands, removed routes, and temporary preview URLs may no longer work. Provider prices and quotas are historical and require a fresh check before use.
Vercel was selected for staging and then production, using the existing account and first-party Kit 3 adapter. Cloudflare Workers was compared but not deployed. The old migration ce364f2 was reviewed for adapter/prerender history, without reverting its client URL handling or subsequent Video.js and mobile viewport work.
| Concern | Vercel | Cloudflare Workers |
|---|---|---|
| Compatibility | @sveltejs/adapter-vercel 7 supports Kit 3; Node 24 functions built with Vite 8 verified here. |
First-party adapter-cloudflare 8 supports Kit 3. Workers use a different runtime; this project has not been built or tested there. |
| Previews | Automatic Preview deployments for pull requests and non-main branches; production deploys from main. |
Wrangler deploys and version URLs; requires a Cloudflare account, Wrangler configuration, and Workers runtime verification. |
| Execution | Regional Node functions, CDN static assets; cold requests can add startup/network latency. | Distributed isolates and static assets; CPU limits matter for SSR, and Node compatibility is not identical to Node. |
| Cost | Hobby is free for personal non-commercial use, with hard usage caps; Pro adds paid usage. Monitor actual function CPU/memory, requests, transfer, build and protection usage. | Free: 100,000 dynamic requests/day and 10ms CPU/invocation. Paid starts at $5/month with 10M requests and 30M CPU-ms included; overages apply. Static asset requests are free. |
| Cache | CDN assets and optional ISR; proof-enabled builds use private/no-store HTML responses. No HTML cache policy added. | Cache API/CDN policies need explicit design for dynamic responses and query keys. |
| Domain/TLS | The custom domain now points to Vercel; confirm HTTPS/TLS and the assigned deployment in the Vercel dashboard. | workers.dev HTTPS for staging; Workers Custom Domains require an active Cloudflare zone. |
| Rollback | Restore a known-good deployment in the original youloop project without a DNS change. |
Wrangler version rollback; bindings/storage changes can prevent safe rollback. |
Sources checked 2026-10-12: Kit 3 migration, Vercel adapter, Cloudflare adapter, Vercel Hobby, Vercel pricing, Workers pricing, Workers version URLs, and Workers rollback. Provider quotas and prices can change; confirm the account's actual plan.
On 2026-10-12, pnpm check, pnpm lint, 20 URL tests, nine metadata tests,
and the Vercel build passed. Four hosting smoke tests and four social smoke
tests passed against the production-built local server, with live YouTube
metadata required for the social checks. GitHub hosting CI passed too.
The public Vercel preview
for implementation commit 1bdfe3cbca8b57f4bf6e1a8fe4d3a145c04eb85a passed all
four social smoke tests with REQUIRE_VIDEO_METADATA=1. Both legacy routes
returned the requested video's title, description, thumbnail, and clip URL in
initial HTML on direct fetch and reload. About, SVG assets, and missing-route
404 behavior also passed on that deployment.
OpenGraph.xyz's external validator retrieved the video title and creator description, loaded the YouTube thumbnail, and recognized the Twitter large-image card. It flagged the sample's 73-character title and 480x360 thumbnail as possible truncation/crop issues. These are display recommendations; custom social artwork remains a follow-up. Actual Facebook, X, LinkedIn, Discord, and messaging-app unfurls were not tested.
T3 Chromium loaded the public watch and editor routes. The watch player created the real YouTube iframe, reported the 65-second duration, sought to 31 seconds, and played after a gesture. At 390px width the watch document had no horizontal overflow and retained its native scroll runway. Physical-device Safari and platform cache refresh behavior remain unverified.
The placeholder /about route and its navigation link were removed at the
owner's request during review. Existing smoke checks no longer request it;
no regression tests were added for its removal.
On 2026-10-12, direct YouTube watch-page fetches with Facebook crawler, Twitter
crawler, and desktop user agents exposed the same social image for each sample.
The native og:image and twitter:image agree. Downloaded JPEG pixels explain
the reported differences without requiring messaging-app-specific processing:
| Video | Plain hqdefault |
Native social image | Other variants |
|---|---|---|---|
dt-SqNL4z3w |
480x360 with black top/bottom strips | Signed maxresdefault, 1280x720, no strips |
mqdefault 320x180 and plain maxresdefault 1280x720 also lack strips; sddefault 640x480 has strips; oar2 returns 404 and a 120x90 placeholder |
IF_7SLHdreQ |
480x360 with dark enlarged side imagery | Signed hqdefault, 480x360, with brown side fill already in the pixels |
mqdefault 320x180 keeps dark side imagery; sddefault 640x480 has black side bars; maxresdefault/hq720 return 404 and a 120x90 placeholder; oar2 is 1080x1920 portrait |
PVVA-_wvKks |
480x360 with top/bottom padding in the source file | Plain maxresdefault, 1280x720, same native artwork at higher resolution |
mqdefault 320x180 and maxresdefault/hq720 1280x720 are unpadded; sddefault 640x480 is padded; oar2 returns 404 and a 120x90 placeholder |
For BFX, the top tenth of plain hqdefault is effectively black, while the
native image contains the room scene. For Grace & Nick, the native image's
left-side average RGB is approximately (112, 69, 52), versus (35, 25, 20) for
plain hqdefault and (0, 0, 0) for sddefault. The acceptable video's native
image and plain maxresdefault are byte-identical. These are image observations,
not proof of how any messaging app renders its cards.
Native-image discovery preserves YouTube's signed sqp/rs query exactly;
these parameters affect composition, not merely size. It requests only a
fixed YouTube watch URL and an allowlisted HTTPS, same-video JPEG variant.
It rejects redirects, missing variants, incorrect MIME, unparseable JPEG
headers, and dimensions below 320x180 (including 120x90 placeholders). The
oEmbed thumbnail remains the fallback if this optional step fails. No native URLs
are guessed, signed transforms synthesized, video-specific exceptions added,
or images generated by our server.
YouTube's HTML is not a supported metadata API contract and can change or be blocked. That trade-off buys a close match to native portrait fill as well as landscape framing; blindly preferring max resolution or medium resolution does not solve both cases. Supported oEmbed still owns video metadata. The selected image, including an oEmbed fallback after failed discovery, shares the existing one-hour successful-metadata cache. A transient discovery failure can therefore keep the old image until that entry expires. YouTube's own thumbnail documentation also cautions that variants and resolutions vary and fitting may add bars.
For physical messaging-app verification, send fresh links from the latest public
PR preview for /?v=ID and /s?v=ID for each of these three IDs, next to
https://youtu.be/ID, in the same conversation. If the app caches cards, use
a fresh preview hostname or add an ignored preview_check query value and
confirm the app actually refetched it; og:url stays on the production origin.
Check that BFX has no source top/bottom strips, Grace & Nick retains the whole
portrait with brown side fill, and the chess image retains its composition and
readable text. Open each YouLoop link to confirm the player still works.
Record the app/version, device, screenshots, and whether a new unfurl occurred.
A reused cached card is inconclusive. Actual cards and physical Safari remain
unverified until this check is performed.
Implementation commit d8f664f1cedd7aab05cf6e11e7f2cef8b25d220b passed types,
lint, 20 URL tests, 15 metadata/thumbnail tests, the server build, and GitHub CI.
The production-built local server passed three hosting and five social smoke
tests with live video metadata required. The
public Vercel preview passed
all five social smoke tests, including direct loads/reloads and sentinel
normalization. Additional crawler requests to both legacy routes for all three
comparison videos returned exactly the native YouTube image URLs recorded
in the investigation above.
OpenGraph.xyz loaded BFX's signed 1280x720 image and Grace & Nick's signed 480x360 image, plus the chess video's native 1280x720 image, from this public preview, and recognized their video titles, descriptions, and Twitter cards. Its recommendations about card aspect ratios and title length still apply; matching native pixels does not control an app's card cropping. Actual messaging-app unfurls remain unverified.
Hosted Chromium playback passed for Grace & Nick (86-second duration, seek/play
at 10 seconds), and BFX in the editor (65-second duration, seek/play at 31 seconds).
The watch view had no horizontal overflow at 390px width. The editor preserved
portrait/contain settings and removed b=99999 from its URL, matching the
server's OG normalization. Physical Safari behavior was not tested.
On 2026-10-12, pnpm check, pnpm lint, pnpm test:share-url (20 tests),
pnpm build, and pnpm build:static passed. The four HTTP smoke tests passed
against both the production-built local server and public Vercel staging.
PR #25 records the historical manual staging URL and remote SHA. That URL will
stop working if the temporary project is deleted. All three pages were loaded
and reloaded in T3's Chromium preview.
Browser regressions passed 39 orientation checks, eight provider-focus checks,
and 35 pure timeline checks. Hosted /s bootstrapped the real YouTube iframe,
reported a 65-second duration, sought to 31 seconds, and responded to Play.
An explicit orientation=portrait&fit=contain URL survived navigation and
produced a centered portrait canvas. At 390px width, the watch document had no
horizontal overflow; the shared viewport declaration remained unchanged.
The T3 tab remained hidden even when asked to open visibly. A combined
animation-frame geometry run timed out, and the native-scroll viewport test
failed its Theater transition assertion at scrollY=80. These are unresolved
browser-verification limits, not passing regressions. The watch-controls run
with hidden-tab scroll notifications also failed A=0 shows only excluded end tail; its full playback/gesture suite did not pass. Sustained playback,
loop completion, native scroll/chrome behavior, full Fit/Fill geometry,
fullscreen, and the physical iPhone Safari matrix still need visible/device
review. No player or mobile geometry code was changed. Cloudflare execution
and rollback were not tested during PR #25; after merge, the owner changed DNS
and Vercel reported a successful production deployment. The live domain and TLS
remain separate operational checks.