Files
linearvoid/.github/instructions/DONE.md
T
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

79 lines
6.5 KiB
Markdown

# DONE
> Completed tasks from [TODO](./TODO.md). Product, feature, and release history lives in [CHANGELOG](./CHANGELOG.md); 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)
- [x] remove search bar from chat sidebar (not needed for single-room)
- [x] remove "user" toggle button from chat sidebar (hide for now, add back when user interface is stable)
- [x] make login/registration form a modal and only toggle it when user is not logged in and clicks "Login" or "Register"
- [x] when user is logged in, do not show login/registration form
- [x] 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.