docs: audit remediation plan (7 phases)
Checklist across security, performance/DOM, feeds/search/trending,
watch page, Shorts, channel/studio/live, and admin/a11y. Branch per
phase; backup tag backup/pre-audit-2026-10-21 at 5058eaf.
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
This commit is contained in:
1 parent
5058eaff2f
commit
56b6a83b34
1 file changed
+88
@@ -0,0 +1,88 @@
|
|||||||
|
# YouTube-parity audit remediation plan
|
||||||
|
|
||||||
|
Branch per phase. Feature tests per fix. STOP for approval between phases.
|
||||||
|
|
||||||
|
**Safety tag:** `backup/pre-audit-2026-10-21` at commit `5058eaf` on `full-screen-data`.
|
||||||
|
**Stashed WIP:** `stash@{0}` — pre-audit WIP on `full-screen-data` (recover with `git stash pop` on that branch).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 1 — Security & data integrity
|
||||||
|
Branch: `fix/phase-1-security`
|
||||||
|
|
||||||
|
- [ ] Wrap `sendEmailVerificationNotification` + `NewCommentNotification` mail in try/catch; move to a queued notification channel; log failures. Document `.env` for Gmail app password + Mailgun/Postmark fallback.
|
||||||
|
- [ ] `php artisan app:resend-verification {--user=}` command.
|
||||||
|
- [ ] Reserved-name blocklist (`admin`, `superadmin`, `takeone`, `support`, `root`, …) via `App\Rules\ReservedIdentifier` wired into `RegisteredUserController`.
|
||||||
|
- [ ] Harden login throttling to `5,1` with lockout message; log repeated failures and alert super admins.
|
||||||
|
- [ ] Confirm `email_verified_at` is not mass-assignable; only the signed-URL verification route flips it.
|
||||||
|
- [ ] Comment dedupe: raise 5s → 60s window, add client idempotency key header, server reject on duplicate. Disable submit button on first click.
|
||||||
|
- [ ] `php artisan comments:dedupe --dry-run` cleanup command.
|
||||||
|
- [ ] Owner-view exclusion in `VideoController@trackProgress`; dedupe by `device_hash` within 30 min.
|
||||||
|
- [ ] Feature tests for each item.
|
||||||
|
|
||||||
|
## Phase 2 — Performance & DOM
|
||||||
|
Branch: `fix/phase-2-perf`
|
||||||
|
|
||||||
|
- [ ] One shared `#globalEditModal` + `#globalCardMenu` per page; populated via `GET /videos/{id}/edit-payload`. Owner-only items gated server-side.
|
||||||
|
- [ ] Lazy-render reply/edit boxes (template clone on open).
|
||||||
|
- [ ] Match highlights panel rendered once.
|
||||||
|
- [ ] `href="javascript:void(0)"` → real `<button>` elements. Hide "Not interested" / "Don't recommend" / "Report" on own videos.
|
||||||
|
- [ ] Cursor pagination (`?cursor=`) across Home, `/videos` filters, channel Videos, search, history, liked, comments.
|
||||||
|
- [ ] IntersectionObserver infinite scroll + "Load more" fallback.
|
||||||
|
- [ ] `loading="lazy"`, explicit `width`/`height`, skeleton placeholders on thumbnails.
|
||||||
|
|
||||||
|
## Phase 3 — Feeds, search, trending
|
||||||
|
Branch: `fix/phase-3-feeds`
|
||||||
|
|
||||||
|
- [ ] Music + filter feeds switch to `latest()`.
|
||||||
|
- [ ] Trending windows: 24 / 168 / 720 h; rank by in-window views.
|
||||||
|
- [ ] Channel Popular: order by views, tiebreak by likes.
|
||||||
|
- [ ] Duration badge on card thumbnail. Scoreboard thumb → `<img>` or `aria-hidden`. Strip `CLUBLOGO` / `FLAG` placeholders.
|
||||||
|
- [ ] Filter tabs: fetch + `pushState`, sticky, `aria-current`.
|
||||||
|
- [ ] Normalize naming (DB stays `match`, display label "Sports" everywhere).
|
||||||
|
- [ ] "New to You" → "Recent" (or make personalized).
|
||||||
|
- [ ] New `SearchController` with `/search?q=` matching title/description/tags/language/channel; include channels + playlists. `/videos/search` becomes 302 redirect. Keep query in input. Merge the two duplicate search forms.
|
||||||
|
- [ ] `/feed/subscriptions` route backed by `subscriptions` table.
|
||||||
|
|
||||||
|
## Phase 4 — Watch page
|
||||||
|
Branch: `fix/phase-4-watch`
|
||||||
|
|
||||||
|
- [ ] Up Next: real `<a href>` cards. Recommender by type/tags/language, cross-channel. Tabs All / From this channel / Same type. Show more.
|
||||||
|
- [ ] Unified player wrapper for match/music/generic: theater (t), mini (i), PiP, volume slider, captions=lyrics (c), 2x speed, end screen, autoplay countdown. Autoplay-ON forces Loop-OFF.
|
||||||
|
- [ ] Like count visible, dislike button, Subscribe for guests, viewer `⋯` → Report. Channel row on match videos.
|
||||||
|
- [ ] `/videos/{id}/download/{format}` signed route; enforce per-video Downloads setting (Off/Everyone/Members/Subscribers) server-side for MP4 **and** MP3.
|
||||||
|
- [ ] Comments: `?lc=` permalink with scroll + highlight. Heart + pin (creator). "Read more" clamp. @handle prefill on nested reply. Singular/plural counts. Top-level vs reply counts separate.
|
||||||
|
- [ ] Markdown rendered (or stripped) in descriptions.
|
||||||
|
- [ ] Dedupe audio Language menu.
|
||||||
|
- [ ] Hide "Generating lyrics…" overlay unless job active.
|
||||||
|
- [ ] Edit form prefilled with real values (fix "Generic" / "Loading…" defaults).
|
||||||
|
- [ ] Streaming: HLS exists (`GenerateHlsJob`, `/videos/{video}/hls/{file}`). Switch player to hls.js when `hls_path` set. Verify `-movflags +faststart` on mp4 encode in `CompressVideoJob`. Add `Cache-Control` + Range on stream route.
|
||||||
|
|
||||||
|
## Phase 5 — Shorts
|
||||||
|
Branch: `fix/phase-5-shorts`
|
||||||
|
|
||||||
|
- [ ] Upload flag + auto-detect (vertical, ≤60s).
|
||||||
|
- [ ] `/shorts/{id}` route + branded 404 at `resources/views/errors/404.blade.php`.
|
||||||
|
- [ ] `/shorts` feed: 100dvh snap container, single reused `<video>` moved between slots, next 1–2 preloaded, URL via `history.replaceState`, wheel + arrows + swipe + ▲▼. Loop. Muted autoplay + "Tap to unmute"; unmuted state in `localStorage`.
|
||||||
|
- [ ] Right action column (like/dislike/comments/share). Bottom overlay (avatar/@handle/Subscribe/title). Comments side panel desktop / bottom sheet mobile.
|
||||||
|
- [ ] Home Shorts shelf. Channel Shorts tab (fix "My Shorts").
|
||||||
|
|
||||||
|
## Phase 6 — Channel / Studio / Live
|
||||||
|
Branch: `fix/phase-6-channel-studio-live`
|
||||||
|
|
||||||
|
- [ ] `users.handle` migration (unique, nullable → backfill → required). `/@handle` routes. Tabs `/@handle/{videos,shorts,playlists,posts,about}`. "Preview as visitor" toggle. Header shows description + links. Age/gender/horoscope private by default. Pluralize "1 video"; drop stray "· 0.0" on playlist cards.
|
||||||
|
- [ ] `/studio/*`: content table + bulk edit, per-video analytics, two-step upload (Details → Visibility: tags + playlist + schedule), file-size + duration + processing-status surfaced.
|
||||||
|
- [ ] Live: ended state → "Stream ended" + duration + recording (never "Waiting"). Live chat. Like / share / subscribe on viewer page. Channel name link. Private option on `/live/new` matching upload wording.
|
||||||
|
|
||||||
|
## Phase 7 — Admin, a11y, consistency
|
||||||
|
Branch: `fix/phase-7-admin-a11y`
|
||||||
|
|
||||||
|
- [ ] Rename "Live Videos" → "Ready". Storage counts NAS (via `NasSyncService`). Fix KPI WoW %. Strip HTML in description excerpts. Full titles with tooltip. Next/Last pagination on Audit Logs, Users, Videos. Error log viewer groups by stack trace in natural order.
|
||||||
|
- [ ] A11y: `aria-label` on every icon button (menu, search, voice, avatar, ⋯, Like-active, comment menus, seek slider with value text). Skip-to-content link. One `<h1>` per page. `label[for]` on all inputs. Remove duplicate "Prefer not to say". Birth year 13+ cap.
|
||||||
|
- [ ] Title helper `{page} | Play`. Brand: **Play** displayed; `TAKEONE` stays as `config('app.name')` per CLAUDE.md.
|
||||||
|
- [ ] Cleanup commands (all `--dry-run` first): `comments:dedupe`, `live:cleanup-test`, `tracks:dedupe`.
|
||||||
|
|
||||||
|
## Final deliverables
|
||||||
|
- Checklist: done / partial / skipped (with reason).
|
||||||
|
- `.env` keys the user must set.
|
||||||
|
- Deploy steps: migrations, queue restart, cache clear, asset build.
|
||||||
Reference in new issue
Block a user