Files
zwitschi d8cf853842
Release / release (push) Successful in 1m15s
CI / test (push) Successful in 2m53s
CI / docker (push) Successful in 2m0s
chore(release): 1.1.0
- 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
2026-09-03 09:27:38 +02:00

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. rewriteManifestToRun in backend/playout/src/playlist.go prefixes each bare relative URI (seg-*.m4s / init.mp4, plus #EXT-X-MAP:URI) with r-{runID}/ before publishManifest's atomic .tmp → CopyObject commit; skipped in slate mode. Added a stubbable copyObject hook for tests (consistent with putObject/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-bound linearvoid-media Worker 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.go split into 8 packages (config, auth, db, handlers, ws, media, ratelimit, metrics); db.RedisClient + db.PubSub interfaces 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 -cover with a 50% coverage threshold; SQLite switched to pure-Go github.com/glebarez/sqlite (CGO-free, works on Windows).
  • Frontend coverage enforcement: vitest v8 provider, 40% statements/lines + 30% branches/functions thresholds, test:coverage script + 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 convention git.allucanget.biz/zwitschi/linearvoid/<service>:<tag>, secrets renamed GITEA_* → 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.tv remains infrastructure-only (Cloudflare R2 config, not code).

Bug fixes not in CHANGELOG

  • 2026-08-13 viewer crash: index.html was missing the #messages container, so addMessage threw Cannot read properties of null (reading 'appendChild') on WebSocket open/message. Restored the chat panel structure and made addMessage defensive.
  • 2026-08-15 chat reconnect loop: after a registered user logged in, the chat panel repeatedly filled with old messages. connectWebSocket left the replaced socket's onclose handler 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's onclose before 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.m3u8 lists ~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.

  1. Remove search bar (single-room)

    • index.html: delete the <div id="dm-search-row"> block.
    • main.ts: remove searchDM() and the dm-search-btn listener; drop the #dm-search-row show/hide logic in switchRoom() (the searchRow.hidden = !isDM lines). Keep currentDMTarget only if the "New DM" flow stays.
  2. Remove "Users" toggle button

    • index.html: delete the <button id="users-toggle-btn"> button.
    • main.ts: remove toggleUserList() and the users-toggle-btn listener; drop the now-unused usersVisible variable.
  3. Login/registration form as a modal

    • index.html: wrap the #auth-panel form in a modal structure (.auth-modal + backdrop) with a close button.
    • style.css: add .auth-modal / .auth-modal-backdrop styles.
    • main.ts: add openAuthModal() / closeAuthModal(); change the auth-toggle-btn handler to open the modal (only when not authenticated) instead of toggling #auth-panel inline; wire a close/backdrop-click to closeAuthModal().
  4. Hide form when logged in

    • main.ts updateAuthUI(): when authenticated → hide the "Sign in" button and call closeAuthModal(); show the user label + "Sign out" button. When not authenticated → show "Sign in" and hide "Sign out".
  5. No duplicate messages

    • main.ts: add clearMessages() (empties #messages) and call it at the top of connectWebSocket() so each (re)connect renders history fresh instead of appending.
    • Fix the initial double-connect: make the initial connectWebSocket("main") wait for refreshUserSession() (or connect once with the resolved username) so history isn't pushed twice on load.