ENFRKOantony langlois
work

YouTube Channel Folders

visitchrome extensionSvelteBrowser ExtensionSupabase

Browser extension that injects a folder system into YouTube itself · Svelte in Shadow DOM riding a hostile SPA · uploads from public RSS, no API keys, no login · optional cross-device sync behind a bearer Sync Code

Problem to solve

Back in 2014 I used the 'subscriptions' page as my main source of videos to watch, but now it's just a giant mess of all the channels I've subscribed to.

I'm a heavy user of YouTube, and the way I use it has changed a lot through the years, due to a massive pile of subscriptions and the algorithm caring less about my subscriptions and more about viral videos surrounding my current centers of interest.

It's not all bad, and it's pretty good at making you very interested the second you open the homepage. But a problem coming with that is that I often miss videos from the channels I'm subscribed to, mostly because I got into a cycle of videos that weren't in their niche, and it feeds a snowball effect driving me apart from these channels.

And I wanted to revive this YouTube subscription page usage, while also allowing me to pick a specific niche of videos to watch without getting distracted by other videos. So I needed a way to categorize YouTube channels of the same niche inside a common feed to pick from. For example, if I wanted to keep up with tech news, or climbing news, or a specific video game's content.

My idea

Being able to create folders, to which you can add YouTube channels, and inside that folder a feed of videos exclusive to these channels. While also being able to filter out the shorts, which are really polluting the feed for the most part.

As for all my Chrome extensions, I don't want the user having to log in inside the extension. It adds a level of complexity and limitations, but for me it's important that it feels safe to the user and that it's also independent from the user's account, meaning that no matter which YouTube account he currently uses, the folders will remain the same.

Architecture

Svelte
Frontend · sidebar injection
Folder list
entrypoints/youtube.content
Injected into YouTube's left sidebar · unread dots · floating-launcher fallback
Svelte
Frontend · full-page view
Folder Feed
youtube.content · overlay host
Hides YouTube's content region · restored by Back / reload via URL hash
Svelte
Frontend · page button
Add-to-Folder button
beside the Subscribe · bell cluster
Re-injected by a MutationObserver when its anchor re-renders · opens the Folder Picker
chrome.alarms
Core · MV3 service worker
Background worker
entrypoints/background.ts
Sleeps between alarms · wakes for messages and the 30 min tick
@handle → UCid resolverpolite RSS poll · ETag · backoffunread computationsync push / pull
no auth
External · public RSS
YouTube RSS
/feeds/videos.xml?channel_id=…
≈15 newest entries per channel · ETag / 304 honored
browser
Storage · local-first
chrome.storage.local
folders · resolver cache · unread · link
Local source of truth · read by UI and worker alike
anon key
Storage · sync backend
Supabase Postgres
public.synced_folders
Rows keyed by SHA-256(Sync Code) · tombstones · last-write-wins

A click in the Folder list pushes a history entry and opens the Folder Feed over YouTube's content area, folder id in the URL hash so Back and reload restore it. The feed asks the Background worker via runtime message to fetch each channel's videos.xml from YouTube RSS (content scripts can't cross-origin fetch) and merges entries newest-first, capped near 100. Folders themselves live in chrome.storage.local, read and written directly by the UI. Every ~30 min a chrome.alarms tick wakes the worker to re-poll deduped channels and light unread dots; when a Sync Code is linked, changes push to Supabase Postgres keyed by the code's SHA-256.

Deployed: Extension → Chrome Web Store + Edge Add-ons · MV3, built with WXT · Sync backend → Supabase free tier · launchd keep-alive ping twice a week

Outcomes

  • 0 · API keys, quotas, or logins · uploads via public RSS
  • 30 min · unread poll cadence · worker sleeps between alarms
  • 256-bit · Sync Code · server stores only its SHA-256
  • 10 ADRs · every reversal recorded · 2 decisions superseded

Skills demonstrated: MV3 service-worker lifecycle (alarms, not timers) · conditional HTTP polling (ETag / 304, jitter, backoff) · union-merge + last-write-wins + tombstone sync · Shadow DOM style isolation inside a host page · MutationObserver-driven DOM injection · unit-testing DOM logic with Vitest + happy-dom

Problems and solutions

Everything follows from one constraint: live inside YouTube's page, on YouTube's data, without YouTube's permission or API.

█ No API, no login: RSS as the only data source

Problem: A folder feed needs upload data for every channel. The official Data API demands an API key, quotas, and a Google login, and burning quota per user kills a free extension.

Every channel exposes a public RSS feed (/feeds/videos.xml). The extension fetches it client-side, from the user's own browser, so there is no server, no key, and no login anywhere.

  • Rejected the YouTube Data API: quota math fails at even modest user counts and forces an account on everyone
  • Accepted RSS's limits knowingly: ~15 entries per channel, Shorts and live streams not reliably distinguishable
  • Bought a zero-cost, zero-credential product that no quota change can revoke

▤ Surviving YouTube's ever-rerendering DOM

Problem: YouTube is a SPA that rebuilds its header and content constantly; injected buttons vanish mid-session, and page CSS bleeds into anything mounted naively.

All UI is Svelte inside Shadow DOM (sealed style boundary), and the Add-to-Folder button is kept alive by a MutationObserver on the header subtree that re-injects the moment its anchor reappears.

  • Rejected fixed retry schedules: they either poll forever or give up right before the anchor mounts
  • Popovers render in a body-mounted overlay host, not the button's own shadow root, so YouTube's transformed header can't clip them
  • Trade-off: coupling to YouTube's DOM anchors, softened by a floating-launcher fallback when the sidebar anchor is missing

▒ Sync without accounts: a bearer Sync Code

Problem: Cross-device sync usually means auth: email flows, OAuth, password reset. That is enormous surface for syncing a folder list.

A 256-bit CSPRNG Sync Code is the whole identity. The server keys folders by SHA-256 of the code, so the database never holds the credential; first link union-merges devices, then last-write-wins per folder with tombstoned deletes.

  • Rejected real accounts for v1, but shaped the schema so a stronger identity can replace the code later
  • Tombstones instead of row deletes, so a delete on one device beats a stale copy instead of being silently resurrected
  • Trade-offs stated up front: lose the code, lose the cloud link; folders are plaintext server-side to keep future server features open

▓ Polite polling that won't get users rate-limited

Problem: Unread dots need background RSS checks from each user's own IP. Naive loops draw 429s from YouTube, and an MV3 service worker can't run a loop at all: Chrome kills it within seconds.

A single chrome.alarms tick every ~30 min wakes the worker; channels are deduped across folders and fetched with ETag conditional requests, capped concurrency, jitter, and 429 backoff.

  • Rejected a server-side poller: no server exists, and adding one would recreate the cost and credential problem
  • Rejected setInterval-style timers: alarms survive worker sleep, timers die with it
  • Accepted up to 30 min of unread lag as the honest price of politeness

▞ A full-page feed that respects the Back button

Problem: The feed started as a side drawer: cramped, and destroyed by any navigation, so pressing Back after watching a video dumped users on the page underneath instead of their feed.

The feed now occupies YouTube's content area like a native page; the folder id lives in the URL hash, a history entry is pushed on open, and Back / forward / reload restore the feed. The content region behind it is hidden and scroll-locked, not just covered.

  • Superseded my own earlier drawer decision (ADR 0004 → 0007 / 0010) once real use showed the drawer fought the site
  • Hiding the region rather than covering it stops background rendering and scroll bleed-through
  • Masthead and sidebar stay live, so it reads like YouTube's own Subscriptions page scoped to one folder

░ Matching YouTube's theme, not the OS's

Problem: prefers-color-scheme lies here: YouTube lets users pick a site theme independent of the OS, so an OS-driven extension renders light panels on a dark page.

The extension observes the dark attribute on YouTube's root element and mirrors whatever the page is actually showing, flipping live when the user toggles the site theme.

  • Rejected the standard media query precisely because the host page, not the OS, is the source of truth
  • One MutationObserver on a single attribute, so the cost is negligible
  • Bought pixel-consistent embedding however the user mixes OS and site themes