# MEMORY ## 2026-08-07 - Completed runtime validation section from TODO. - Runtime video normalization tests executed in running `linearvoid-playout` container using ffmpeg/ffprobe. - Verified normalized outputs for MOV, MKV, VFR, and 5.1 inputs: `h264`, `yuv420p`, `30/1`, `aac`, `48000`, `2 channels`. - Concat continuity check passed with `CONCAT_OK`; output duration `25.344667` seconds. - Responsive viewport test on live viewer (`390x844`, `412x915`) detected 8px horizontal overflow from `100vw` usage in viewer container styles. - Fixed overflow in source by changing `.lv-body` and `.viewer-shell` width from `100vw` to `100%` in `frontend/src/style.css`. - Redis restart test: - Admin session endpoint returned 200 before and after Redis restart. - Presence key survived Redis restart in test-lab configuration. - Redis health returned to `healthy` post-restart. - Files changed: - `.github/instructions/TODO.md` - `.github/instructions/DONE.md` - `frontend/src/style.css` - `CHANGELOG.md` ## 2026-08-07 (continued) - Completed documentation half of recovery runbook TODO item. - Expanded `docs/incident-runbook.md` with quarterly DR drill: prerequisites, outage simulation, restore validation, pass/fail criteria, and evidence template. - Split TODO item into: - documented drill (`[x]`) - manual rehearsal execution (`[ ]`) - Updated `CHANGELOG.md` with incident runbook drill documentation entry. ## 2026-08-07 (continued 2) - Added manual operations checklist `docs/operations/cloudflare-uptimerobot-checklist.md` for Cloudflare + UptimeRobot task execution and evidence capture. - Linked checklist from `SETUP.md` Cloudflare section and TODO manual sections. - Did not mark manual dashboard items complete; checklist is preparation only. ## 2026-08-07 (continued 3) - Added `docs/operations/scaling-performance-playbook.md` covering manifest TTL tuning, TTFB baseline method, WebSocket/API benchmarking approach, cache ratio tracking, capacity triggers, and Phase 2/3 plans. - Updated TODO and BACKLOG to mark playbook creation done while keeping execution/measurement tasks pending. - Linked scaling playbook in `README.md` resources. ## 2026-08-09 - Added WebSocket benchmark summary helper `scripts/loadtest/summarize-artillery.ps1`. - Updated `scripts/loadtest/run-loadtests.ps1` to auto-generate `ws-chat-summary.txt` for `ws` and `all` scenarios. - Updated `scripts/loadtest/summarize-k6.ps1` to produce pipeline-friendly output and integrated automatic `admin-api-endpoint-summary.txt` generation. - Expanded `scripts/loadtest/README.md` with PowerShell-first artifact capture and helper usage for both ws and admin scenarios. - Updated `.gitignore` to ignore loadtest artifacts (`artifacts/` and `scripts/loadtest/artifacts/`). - Added artifact-to-worksheet field mapping section in `docs/operations/scaling-performance-playbook.md` so ws/admin worksheet entries map directly to generated summary files. - Updated `docs/DEVELOPMENT.md` and `.github/instructions/TODO.md` to mark ws/admin benchmark scaffolding complete while live benchmark execution remains pending. - Added `CHANGELOG.md` entry `1.0.10` summarizing loadtest helper automation and playbook mapping updates. ## 2026-08-09 (continued) - Completed Phase 1 (test fixtures): documented ffmpeg fixture-generation commands in `playlist_test.go` header comments (valid_720p30, invalid_h265, invalid_24fps). - Completed Phase 4 (R2 integration): - Enhanced `SyncToR2` in `playlist.go` to set CacheControl + ContentType based on file extension: - `.m4s`, `.ts`, `.mp4`, `.m4a` -> `public, max-age=31536000, immutable` + `video/MP2T` - `.m3u8` -> `public, max-age=2` + `application/vnd.apple.mpegurl` - unknown extension -> no cache headers - Added three tests: `TestSyncToR2` (segment upload with cache verification), `TestSyncToR2_ManifestCacheHeaders` (manifest upload), `TestSyncToR2_NoCacheHeadersForUnknownExt` (unknown extension). - All 17 playout tests pass (16 pass, 1 pre-existing skip). - Updated TODO.md: Phase 1 marked complete, Phase 4 partially complete (edge cache hit/miss remains manual). - Updated DEVELOPMENT.md: Test Fixtures marked complete, R2 Integration Test marked partially complete. ## 2026-08-09 (continued 2) - Completed Phase 2 chat tests in `frontend/src/main.test.ts`: - Presence updates: join/leave system messages add and remove users from presence list, sorted rendering. - Private DM flow: togglePrivateDM creates correct dm: room name from sorted usernames. - Message rendering: system messages, self messages (message-self), and other-user messages (message-other) all render correctly. - Total frontend tests: 27 passing (2 test files). - Marked Phase 4 "Document stubs/mocks" done (putObject injection pattern). - Updated TODO.md: Phase 2 chat tests checked, Phase 4 stubs checked. - Updated DEVELOPMENT.md: User Frontend Tests marked partially complete, R2 stubs checked. - Updated CHANGELOG.md with Phase 2 presence/DM/message tests. ## 2026-08-09 (continued 3) - Completed Phase 3 admin frontend tests in `frontend/src/admin.test.ts`: - Exported `savePlaylist` and `renderPlaylistSortable` from `admin.ts`. - Added playlist save test (verifies PUT /api/admin/playlist with JSON body). - Added playlist save error handling test (verifies failure status). - Added sortable rendering test (verifies index, paths, remove buttons, comment/blank line skip). - Total frontend tests: 31 passing (2 test files). - Updated TODO.md: Phase 3 playlist/dashboard/API integration all checked. - Updated DEVELOPMENT.md: Admin Frontend Tests marked partially complete. ## 2026-08-10 (continued 2) - Implemented STREAMING.md production-readiness gaps from plan `.hermes/plans/2026-08-10_164500-production-readiness.md`: - **Task 1-2**: Added `runID` (ISO timestamp format) and monotonic `segSequence` for globally unique segment naming (`r-{ISOTIME}/seg-{seq}.m4s`). Replaced `strftime` pattern with run ID-based path. - **Task 2**: Added `watchAndPublishSegments` using `fsnotify` for real-time per-segment R2 upload (~7s delay). Previously `syncLatestToR2()` only ran after FFmpeg exit. - **Task 3**: Added `publishManifest` with atomic `CopyObject` (temp key → final key) to prevent manifest referencing unconfirmed segments. - **Task 4**: Updated `manifestCacheControl` from `"public, max-age=2"` to `"public, max-age=1, s-maxage=2, must-revalidate"` per STREAMING.md. - **Task 5**: Added `hls.on(Hls.Events.ERROR)` handler in `main.ts` with bounded retries (5x), exponential backoff (2s→32s), reconnecting overlay (spinner + text). Added `showReconnecting`/`hideReconnecting` functions. - **Task 6**: Enriched `HealthHandler` with `run_id`, `active_item`, `playlist_revision`, `last_segment_seq`, `last_r2_pub`, `consecutive_failures`. Added "unhealthy" status when R2 publish is stale (>12s). - Files changed: `backend/playout/src/playlist.go`, `playlist_test.go`, `go.mod`, `frontend/src/main.ts`, `main.test.ts`, `style.css`, `index.html`. - Updated `.github/instructions/TODO.md`, `BACKLOG.md`, `MEMORY.md`, `DONE.md`. - Remaining: CORS on media.linearvoid.tv (Cloudflare R2 config, infrastructure). ## 2026-08-10 (continued 3) - Completed Phase 2 (viewer) + Phase 3 (admin) responsive/accessibility tests: - 5 viewer accessibility tests: video controls/attributes, chat panel aria-labels, chat toggle aria-expanded, message input label, reconnecting overlay aria-live/role. - 3 viewer responsive tests: chat-collapsed toggle, CSS grid layout detection, now-playing aria-live. - 3 admin accessibility tests: nav aria-label, active tab aria-current, nav button focusability. - 2 admin responsive tests: tab content visibility, essential tab presence. - Total: 45 frontend tests passing (2 test files). - Added coverage reporting: `vitest.config.ts` with v8 provider, thresholds at 20/15/15/20 (statements/branches/functions/lines). - Added `test:coverage` script to `package.json`. - Fixed CI: bumped Node from 20 to 22 in `ci.yml` and `pages-deploy.yml` (jsdom@30 requires Node 22+). Added `engines` to `package.json`. - Updated AGENTS.md: added Required tracking files table with 13 docs and update cadence. - All Go vet, Go tests, frontend test:coverage passing. - CORS on media.linearvoid.tv remains the only streaming pipeline gap (Cloudflare R2 config, not code). ## 2026-08-10 (continued 4) - Verified all test suites pass after streaming pipeline implementation: - Playout: `go mod tidy` (added fsnotify v1.8.0) → all tests pass - Chat: 7 packages pass (go vet + go test) - Frontend: 45 tests pass (2 files), test:coverage meets thresholds (22.92% / 18.44% / 19.01% / 25.42%) - Edge cache validation script `scripts/validate-edge-cache.sh` exists. - Remaining: CORS on media.linearvoid.tv (Cloudflare R2 bucket CORS config, not a code change). Phases 5-7 are manual/infrastructure only (Cloudflare dashboard, Hetzner provisioning, live benchmarks). ## 2026-08-11 - Consolidated all development tasks from `DEVELOPMENT.md` and `.github/instructions/TODO.md` into `.github/instructions/BACKLOG.md` as the single source of truth. `DEVELOPMENT.md` now obsolete and removed. `TODO.md` references `BACKLOG.md` and `DONE.md`. - Fixed R2 endpoint validation in playout service (`backend/playout/src/playlist.go`): - Added `isR2Configured()` checking all four required env vars (`R2_ENDPOINT`, `R2_ACCESS_KEY_ID`, `R2_SECRET_ACCESS_KEY`, `R2_BUCKET_NAME`). - `initR2Client()` now returns a clear error if R2 is not configured instead of creating an S3 client with an empty endpoint. - Segment watcher (`watchAndPublishSegments`), `syncLatestToR2()`, and `publishManifest()` are now guarded by `r2Client != nil` — no error spam when R2 is unavailable. - Playout gracefully degrades: local HLS via nginx works without R2. - Updated `.gitea/workflows/cd.yml` "Prepare environment" step to include all R2* variables sourced from Gitea secrets (`R2_ACCESS_KEY_ID`, `R2_SECRET_ACCESS_KEY`, `R2_BUCKET_NAME`, `R2_ENDPOINT`, `R2_ADMIN*\*`), Cloudflare variables (`CLOUDFLARE_API_TOKEN`, `CLOUDFLARE_ACCOUNT_ID`), and frontend public URL variables. - Updated `docs/DEPLOYMENT.md` "Environment File" section: replaced vague "Cloudflare/R2 variables" bullet with explicit variable lists and added a new "Gitea Secrets" table documenting all 15 required secrets. - All playout tests pass (20 tests), `go vet` clean. ## 2026-08-13 - Completed all remaining enhancement tasks (only manual/infrastructure phases left): - **Playout**: automatic media normalization on upload (`media.NormalizeVideo`, `*.normalized.mp4`, statuses `ready`/`needs_normalization`/`normalizing`/`normalization_failed`, `POST /api/admin/media/{id}/normalize`); R2 segment retention (`cleanupOldSegments`, `R2_SEGMENT_RETENTION_HOURS`); seamless looping (playlist SHA-256 fingerprint + `-start_number`); single `start-playout.sh` entrypoint + `-validate` mode; loop/cleanup metrics. - **Application configuration**: thread-safe JSON runtime config store (`backend/chat/src/config/runtime.go`) shared with playout via `/app/playlist/config.json`; `GET/PUT /api/admin/config`; admin UI config form. - **Matomo analytics**: public `GET /api/matomo` + viewer pageview snippet in `index.html`. - **Chat user accounts**: register/login/logout/session endpoints, bcrypt hashing, `linearvoid_user_session` cookie, viewer sign-in UI. - **DM history & search**: `GET /api/dm/history` (paginated) + `GET /api/dm/search` + DM search UI. - **Chat UI**: typing indicator, user-list toggle, media status badges, "Add to playlist" gated to `ready`. - **Prometheus TSDB retention**: `--storage.tsdb.retention.time=30d` + `--storage.tsdb.retention.size=15GB` in both compose files; alert rule count corrected 8 → 10. - Updated `docs/architecture/README.md` to reflect all `[Unreleased]` changes (normalization, looping, retention, runtime config, Matomo, user accounts, DM history/search, new API endpoints, module tables). - Released **1.0.13**: promoted changelog, bumped version, tagged and pushed. - Extended frontend test coverage: added `counter.test.ts`, reconnecting/typing/auth/websocket tests, admin failure + status-badge tests → 56 tests; statements 19.89% → 28.58% (CI coverage threshold passing). - Fixed Go test data race in `TestHandleAdminMediaByID_Normalize` (waits for async normalization goroutine) + shared in-memory SQLite via `SetMaxOpenConns(1)`. - Fixed viewer crash: `index.html` was missing the `#messages` chat container (stray ``); restored structure + made `addMessage` defensive. - Set `Doto` as the body font (`--font-body`) and added Google Fonts links to admin + login pages. - Files changed: across `backend/chat`, `backend/playout`, `frontend`, `docs`, `docker-compose*.yml`, `.github/instructions/*`.