Plan: YT-Channel-Scanner (Multiuser PWA + Zero-Knowledge Local Storage)
This file is the single master checklist.
Conventions:
- = open
- = done
- Each task should be independently shippable or verifiable.
Progress notes:
- Updated checkmarks reflect actual implemented work + commits (not wishes).
0) Snapshot / What exists right now (audit)
Repo has two generations
- Legacy single-user (A): repo root (e.g.
server.js,sync.js,index.html, MongoDB archive + download endpoints). - Multiuser PWA (B):
app/app/backend/(Node/Express + MongoDB for users/licenses/payments)app/frontend/(Svelte + Vite PWA, IndexedDB local archive, WebCrypto vault)
Multiuser PWA: current status
- Backend: basic registration/login + admin endpoints exist.
- Frontend: basic screens exist (Auth, Onboarding, Scan, Library, Settings, Admin).
- Local archive: IndexedDB per-user DB name implemented (DB name includes userId).
- Vault: passphrase-derived AES-GCM key in memory; encrypted token storage in IndexedDB.
- OAuth PKCE: implemented fully in-browser.
Immediate blocker found (needs fixing before further UI work)
- Frontend dev server fails on linux/arm64:
- Error:
Cannot find module @rollup/rollup-linux-arm64-gnu(Rollup optional dependency / npm bug). - Fix plan in section 1.2.
- Error:
1) Make the project runnable (dev) + define “done”
1.1 Define MVP “done” for Multiuser
- Decide MVP scope:
- Auth + license gating (trial/paid/expired)
- Vault unlock + OAuth PKCE connect
- Scan subscriptions (OAuth) + store channels/videos in IndexedDB
- Library browse/search
- Export/Import backup
- Admin panel basic (users + licenses)
- Write a 5-line README section: “MVP = …”
1.2 Fix: frontend build/dev on this VM (linux/arm64)
Goal: pnpm dev / pnpm build in app/frontend works reliably.
- Choose package manager strategy:
- Option A: keep npm + lockfile
- Option B: switch to pnpm (recommended for reproducible optional deps)
- Switch to pnpm lockfiles for
app/,app/frontend,app/backend - Verify Vite dev + build works on linux/arm64 (Rollup native deps ok)
- Add a short note in docs: “If you hit @rollup/rollup-linux-arm64-gnu …”
1.3 Backend dev run
- Add
backend/.env.exampleand ensure backend reads it. - Make sure backend starts with:
-
PORT(default 5055) -
MONGODB_URI,MONGODB_DB -
JWT_SECRET(must not default tochange-mein production) -
ALLOWED_ORIGIN(frontend URL)
-
1.4 Frontend dev run
- Add
frontend/.env.examplecontainingVITE_BACKEND_URL. - Confirm Vite dev server port and correct API base.
1.5 One command to start everything
- Add root-level script(s) to start multiuser app:
-
npm --prefix app run dev
-
- Ensure ports do not collide with legacy (4000 vs 5055/5173).
2) Security + multiuser boundaries (must be correct)
2.1 Zero-knowledge acceptance criteria (write it down)
- Document threat model and explicit promises:
- Server never receives passphrase
- Server never stores OAuth tokens
- Tokens stored encrypted at rest in browser
- Loss of passphrase = loss of access (no recovery)
- Document KDF parameters:
- PBKDF2 iterations value (currently 150k) + rationale
- Salt length (currently 16 bytes) + stored location
2.2 Vault and encryption correctness
- Ensure per-user vault salt (currently localStorage key
ytcs:vault-saltis global)- Change to per-user keying or store salt in per-user IndexedDB settings.
- Ensure clearing tokens writes a valid value (currently
setSetting(..., null)may store{key,value:null})- Decide semantics: delete entry vs store null
- Implement consistently
- Ensure
toBase64/fromBase64can handle larger payloads safely (avoid stack issues)
2.3 Auth token handling
- Decide: token stored in localStorage vs sessionStorage
- Add expiry handling:
- Decode JWT exp or rely on API 401
- Auto-logout or refresh license on 401
2.4 Backend hardening (basic)
- Remove dangerous defaults:
-
JWT_SECRETmust be required in production -
ADMIN_PASSWORDdefault must not exist in prod -
ADMIN_TOKENdefault must not exist in prod
-
- Add rate limiting to auth endpoints
- Validate request bodies (basic schema validation)
- CORS: restrict allowed origin(s) + document configuration
3) Data model + IndexedDB schema (client)
3.1 Confirm required stores and indexes
- Stores exist: channels, videos, subscriptions, scans, settings
- Video indexes: byChannel, byPublishedAt, byTitle
- Add missing indexes required for UI search/sort performance:
- channels: bySubscribers/byViews/byVideoCount (if needed)
- videos: byViewCount (if needed)
3.2 Multiuser local DB strategy
- DB name includes active userId (
yt-channel-scanner-<userId>) - Add UX for switching users:
- Select known users on login screen
- Switching user should change DB name and refresh counts
3.3 Import/export compatibility (Mongo → IndexedDB)
- Define JSON schema v1 mapping explicitly:
-
_id(Mongo) →idstring - channel fields mapping list
- video fields mapping list
-
- Implement a “Mongo export importer” that accepts legacy exports
- Validate before writing
- Provide progress + cancel
- Resume option (optional)
4) YouTube OAuth + API scan correctness
4.1 OAuth redirect route correctness
- Fix redirect URI handling:
- Use
/oauth/callback(as planned) instead of root/ - Update
getRedirectUri()and router - Ensure OAuthCallback screen loads at that path
- Use
4.2 Token lifecycle
- PKCE code exchange
- Refresh token flow
- Handle missing refresh token cases:
- Detect and show user guidance (consent screen / prompt=consent)
4.3 Scan algorithm parity (from legacy)
- 1-day buffer diffing (avoid re-fetching everything every time)
- Quota handling:
- Capture quota errors (403/429)
- Store a quota log locally
- Resume strategy (store resume cursor)
- “Track only subs” vs “track all” mode implemented in PWA
5) Frontend UX / features to finish
5.0 Themes (4 presets + custom accent)
- Implement theme system using CSS variables (design tokens), not Tailwind-only classes
- Provide 4 preset themes + custom accent color override
- Store selection locally (IndexedDB
settings), per-user - Apply theme at app start (before render) to avoid flashes
- Add Settings UI:
- Preset dropdown
- Accent color picker + reset-to-preset
5.0a Internationalization (28 EU languages)
- Decide i18n approach (chosen:
svelte-i18n) - Define the target 28 languages list (Europe-wide languages) and fallback rules
- Create translation key structure + guidelines (no hardcoded strings)
- Add language selector (stored locally per-user)
- Extract all UI strings to i18n dictionaries
- Add build-time check for missing keys
- i18n scaffold created (de default + en fallback; partial UI migration started)
5.1 Auth + onboarding
- Auth screen:
- Register
- Login
- Magic link (simulated -> either remove or implement real email provider)
- Google login (simulated -> either remove or implement real OAuth)
- Onboarding:
- Enforce correct ordering: login → vault → OAuth → scan
- Show meaningful errors + next steps
5.2 Scan screen
- Progress bar should reflect real scan progress (currently static 45%)
- Display token status + quota info
- Store scan runs into
scansstore
5.3 Library
- Implement global search operators (AND/OR/EXCLUDE/phrases) on IndexedDB
- Top video per channel (local computation)
- Status badges (new videos since last scan)
5.4 Settings
-
BYO Keys (user provides their own YouTube credentials locally):
- Settings fields exist (stored locally, per-user):
- OAuth Client ID
- YouTube API Key (optional fallback)
- Default Channel ID (for API-key scan)
- Add Import/Export for settings (for browser resets):
- Export settings JSON (user-owned)
- Import settings JSON with validation + overwrite confirmation
- Settings fields exist (stored locally, per-user):
-
Local archive backup:
- Export JSON (channels/videos/subscriptions/scans)
- Import JSON with progress + validation
-
Reset local database button:
- Confirm dialog
- Clear all stores
- Clear tokens + per-user settings (and theme/language)
-
Optional user-side downloads (documentation only; no server downloads):
- Setting: “Show local download guide” (default off)
- Doc page: how to use
yt-dlplocally (copy/paste commands) - Optional: export selected video URLs/IDs as a text file
-
Manage subscription button:
- Placeholder → later Stripe portal
5.5 Admin
- Document how admin auth works:
- Admin via
role=adminJWT - Admin via
ADMIN_TOKEN
- Admin via
- Decide which mode to keep for production
6) Backend: licenses + payments + Stripe (planned)
6.1 License rules
- Trial license auto-created (14 days)
- Enforce trial end:
- If
trialEndsAt < now, mark inactive or plan=expired - Backend returns accurate
activeflag
- If
- Add plan lifecycle states: trial → paid → paused/canceled/expired
6.2 Stripe integration (if required)
- Products/prices:
- 1 EUR/month billed yearly (i.e. 12 EUR/year) – confirm pricing semantics
- Checkout session
- Webhook handling
- Customer portal
- Map Stripe subscription status → license.active
7) Docs + repo hygiene
7.1 Documentation set
- Create
app/README.mdwith:- Dev setup (Mongo, env vars)
- Run commands
- OAuth setup steps (Google Cloud)
- Troubleshooting
7.2 Remove/mark legacy
- Add a
LEGACY.mdexplaining root single-user mode - Mark legacy scripts as deprecated (do not delete yet)
8) Release / deployment
8.1 Hosting plan
- Pick backend hosting (VPS/Docker/Render/Fly/etc.)
- Frontend static hosting (Vercel/Netlify/S3)
- Set secure env vars and CORS origin
8.2 Compliance essentials (EU)
- Privacy policy text (zero-knowledge description)
- Terms + refund policy (if paid)
- Imprint (if applicable)
Next actions (recommended order)
- Fix frontend Rollup optional dependency (section 1.2)
- Run backend + frontend locally with clean env files
- Fix OAuth redirect path to
/oauth/callback - Implement scan diffing + quota/resume
- Finish paywall enforcement + trial expiry