- playout: run-scoped R2 manifest URIs (candidate a rewrite via rewriteManifestToRun) - tests: playlist_rewrite_test.go (rewrite + publish integration + slate guard) - docs: STREAMING, cloudflare-workers-setup, architecture (incl. Level-2 diagram) - release housekeeping: TODO->DONE, CHANGELOG 1.1.0, version bump
6.5 KiB
6.5 KiB
DONE
Completed tasks from TODO. Product, feature, and release history lives in CHANGELOG; this file keeps only engineering and ops notes not captured there.
Manifest run-scoped rewrite — candidate (a) (2026-09-03)
- Playout now publishes the live R2 manifest with run-scoped media URIs.
rewriteManifestToRuninbackend/playout/src/playlist.goprefixes each bare relative URI (seg-*.m4s/init.mp4, plus#EXT-X-MAP:URI) withr-{runID}/beforepublishManifest's atomic.tmp→CopyObjectcommit; skipped in slate mode. Added a stubbablecopyObjecthook for tests (consistent withputObject/listObjects/deleteObjects). - Tests:
playlist_rewrite_test.go—TestRewriteManifestToRun,TestRewriteManifestToRun_AlreadyPrefixedUnchanged,TestRewriteManifestToRun_HandlesTSAndComments,TestRewriteManifestToRun_EmptyRunID,TestIsMediaURI,TestPublishManifest_RewritesToRunScopedURIs,TestPublishManifest_SlateNotRewritten. - Docs updated:
STREAMING.md,operations/cloudflare-workers-setup.md,architecture/README.md(incl. Level-2 diagram showing the R2-boundlinearvoid-mediaWorker and run-scoped layout). - Still pending (in TODO): live Validation — deploy the updated playout to the test lab and confirm manifest/segment URIs resolve via
media.linearvoid.tv.
Test infrastructure & refactors (2026-08-07)
- Chat service
main.gosplit into 8 packages (config, auth, db, handlers, ws, media, ratelimit, metrics);db.RedisClient+db.PubSubinterfaces added for mocking. - 78 tests total (Go + frontend), all passing. Coverage: auth 97.8%, config 100%, metrics 100%, ratelimit 90.3%, ws 79.6%, handlers 47.2%, media 36.8%, db 14.3%.
- Playout: 12 tests. Frontend: vitest + jsdom, 6 user + 5 admin smoke tests.
- CI:
go test -race -coverwith a 50% coverage threshold; SQLite switched to pure-Gogithub.com/glebarez/sqlite(CGO-free, works on Windows). - Frontend coverage enforcement: vitest v8 provider, 40% statements/lines + 30% branches/functions thresholds,
test:coveragescript + CI step.
Backups
scripts/backup-db.sh(daily pg_dump → R2, 14-day retention),scripts/backup-weekly.sh(weekly full dump + integrity check, 8-week retention),scripts/verify-backups.sh(download → temp Postgres → restore + row counts).
Gitea self-hosting migration
- Migrated repo + CI/CD from GitHub to Gitea Actions (
git.allucanget.biz/zwitschi/linearvoid): self-hosted runner, Container Registry conventiongit.allucanget.biz/zwitschi/linearvoid/<service>:<tag>, secrets renamedGITEA_*→CI_*.
Streaming pipeline gaps (2026-08-10)
- Continuous R2 sync via
fsnotify(watchAndPublishSegments, ~7s delay); run-ID + monotonic segment naming (r-{ISOTIME}/seg-{seq}.m4s); atomic manifest publication (upload.tmp→CopyObject); enriched/health(run_id, active_item, playlist_revision, last_segment_seq, last_r2_pub, consecutive_failures); player error recovery (5 retries, 2s→32s exponential backoff, reconnecting overlay). - CORS on
media.linearvoid.tvremains infrastructure-only (Cloudflare R2 config, not code).
Bug fixes not in CHANGELOG
- 2026-08-13 viewer crash:
index.htmlwas missing the#messagescontainer, soaddMessagethrewCannot read properties of null (reading 'appendChild')on WebSocket open/message. Restored the chat panel structure and madeaddMessagedefensive. - 2026-08-15 chat reconnect loop: after a registered user logged in, the chat panel repeatedly filled with old messages.
connectWebSocketleft the replaced socket'sonclosehandler attached, so each intentional reconnect scheduled another reconnect and every connection re-pulled the room's 50-message history on a 3s loop. Fixed by detaching the old socket'sonclosebefore closing and guarding the handler against stale sockets. Regression test added;npm --prefix frontend test→ 58 tests passing.
Chat improvements
- Manual smoke: viewer join/leave shows a 3s flash (not a chat row); reconnect <5s shows no duplicate; admin chat-log table has no system rows
Playout improvements
- Test in Firefox: live playback, rebuffer/recovery, chat sync; record quirks in
docs/STREAMING.md - Rebuild + run playout; confirm
playlist.m3u8lists ~12 segments at 2s each - Manual: verify viewer latency and cache-hit behavior after the change
Chat sidebar (2026-08-17)
- remove search bar from chat sidebar (not needed for single-room)
- remove "user" toggle button from chat sidebar (hide for now, add back when user interface is stable)
- make login/registration form a modal and only toggle it when user is not logged in and clicks "Login" or "Register"
- when user is logged in, do not show login/registration form
- make sure each message is only rendered once in the chat log (no duplicates on reconnect or initial load)
Plan
Files: frontend/index.html, frontend/src/main.ts, frontend/src/style.css.
-
Remove search bar (single-room)
index.html: delete the<div id="dm-search-row">block.main.ts: removesearchDM()and thedm-search-btnlistener; drop the#dm-search-rowshow/hide logic inswitchRoom()(thesearchRow.hidden = !isDMlines). KeepcurrentDMTargetonly if the "New DM" flow stays.
-
Remove "Users" toggle button
index.html: delete the<button id="users-toggle-btn">button.main.ts: removetoggleUserList()and theusers-toggle-btnlistener; drop the now-unusedusersVisiblevariable.
-
Login/registration form as a modal
index.html: wrap the#auth-panelform in a modal structure (.auth-modal+ backdrop) with a close button.style.css: add.auth-modal/.auth-modal-backdropstyles.main.ts: addopenAuthModal()/closeAuthModal(); change theauth-toggle-btnhandler to open the modal (only when not authenticated) instead of toggling#auth-panelinline; wire a close/backdrop-click tocloseAuthModal().
-
Hide form when logged in
main.tsupdateAuthUI(): whenauthenticated→ hide the "Sign in" button and callcloseAuthModal(); show the user label + "Sign out" button. When not authenticated → show "Sign in" and hide "Sign out".
-
No duplicate messages
main.ts: addclearMessages()(empties#messages) and call it at the top ofconnectWebSocket()so each (re)connect renders history fresh instead of appending.- Fix the initial double-connect: make the initial
connectWebSocket("main")wait forrefreshUserSession()(or connect once with the resolved username) so history isn't pushed twice on load.