166 lines
12 KiB
Markdown
166 lines
12 KiB
Markdown
# HANDOFF — session state
|
||
|
||
> Current operational state, read right after `TASKS.md`. Trust this file as the
|
||
> truth of what is in flight — do not re-derive from git/fs unless it points at
|
||
> a problem. Conventions: [`schema.md`](schema.md). Rewrite this file at session
|
||
> end, compaction, or any interruption.
|
||
|
||
## Session state (last updated: 2026-08-15)
|
||
|
||
- **Branch:** `main`, tracking `origin/main`. Working tree: the **TASK 24 polish batch** below is
|
||
committed + pushed. Local branches `social-bar`/`webcam-validation` untouched (no secrets).
|
||
- **TASK 24 — POST-PAUSE POLISH BATCH — SHIPPED + COMMITTED + PUSHED 2026-08-15.** All 8 creator
|
||
review issues fixed in one branch (details below). Build **0 warnings**, full suite **197 passing**
|
||
(ONE integration test for this branch: `AudioPipelineTests.Mix_HonorsProviderGains_AndGameMute_KillsTheLoopback`).
|
||
- **AUDIO MILESTONE (TASK 9) — SHIPPED + COMMITTED + PUSHED.** Real stream audio + voice filters +
|
||
auto-duck + free TRAX background music. Commits `6d71ace` (milestone) + `f2f6401` (secrets cleanup),
|
||
force-pushed (`725f5a7...f2f6401`).
|
||
- **MONETIZATION LOCKED (2026-08-14, creator) — committed `86fcd86`.** One paid line = **annual
|
||
subscription**: early access **$49.99/yr** → **$99/yr list at GA**, **grandfather-while-subscribed**,
|
||
lapse → list on renewal. Free tier unchanged (branding flash = the billboard + no Alerts; Alerts is
|
||
the paid feature). **Billing NOT itch-locked**; candidates Gumroad (native affiliates =
|
||
tiebreaker) / Lemon Squeezy. Support = in-app bug-report → git issues. Full policy in `ai.md` →
|
||
Monetization; scoped plan in `TASKS.md` **TASK 23**.
|
||
- **Secrets scrubbed from git history.** The DO token + passwords were purged via `git filter-branch`
|
||
+ `git gc --prune=now --aggressive` (all-refs scan = 0 hits); values still exist in chat — keep
|
||
treating as **compromised**; rotate the DO API token.
|
||
- **Shipped, all pushed:** TASK 24 polish batch, TASK 21 (creator-hub About, `3d92bd0`), TASK 8
|
||
(meter +10 dB), TASK 4 ship step 7 (one-click go-live + private-only), TASK 9 audio milestone
|
||
(`6d71ace`), secrets cleanup (`f2f6401`), monetization docs (`86fcd86`).
|
||
- **Resume point (next branch):** **TASK 5** — reusable stream → `_rtmpUrlProvider` (the last blocker
|
||
before go-live actually encodes + pushes). Then TASK 23 (billing + unlock + support).
|
||
Optional, not queued: rewriting the healed entry's `ProfileUrl` to
|
||
`https://mastodon.llamachile.tube/@gramps` (user must say the word).
|
||
|
||
## TASK 24 — the 8-issue polish batch — SHIPPED 2026-08-15 (what changed)
|
||
|
||
The creator's review of the TASK 9 build, all fixed in one branch (full record in `TASKS.md` TASK 24).
|
||
|
||
1. ✅ **Desktop/game audio volume slider had no effect** — the `AudioMixer` gain seams defaulted to
|
||
unity (the VM never passed them): the slider/mute were decorative, stream AND local. Fixed with a
|
||
new **`AudioGainProvider`** (`Services/Audio/`) wired into the mixer at construction, read live
|
||
each mix tick.
|
||
2. ✅ **Desktop/game mute had no effect** — same wiring; `GameMuted` now zeroes the loopback on the
|
||
stream AND silences the music locally via new `MusicPlayer.LocalGain` (scaled by the game bar on
|
||
every volume/mute change + on TRAX load) — the creator hears the control work in the headphones.
|
||
3. ✅ **TRAX played once then stopped** — `OnPlaybackStopped` only looped when `Position >= Length`
|
||
(unreliable for `MediaFoundationReader`); now any clean stop rewinds + replays. Dropped the unused
|
||
`using System.Runtime.InteropServices;` too.
|
||
4. ✅ **TRAX/MIC buttons swapped** — footer mic cluster is now MIC + meter + mute + volume (TRAX is out
|
||
of the mic cluster entirely).
|
||
5. ✅ **Mic source persists across restarts** — `LayoutStore` gained a `Settings` key/value table
|
||
(`SaveMicSourceName`/`LoadMicSourceName`); the VM saves on pick and restores before the mixer's
|
||
first `Start` → same already-vetted device reconnects green on restart, missing → yellow.
|
||
6. ✅ **TRAX moved right of Socials** — both centered under the scenes/sources listboxes (footer line
|
||
1, left cluster).
|
||
7. ✅ **Backdrop icons shifted right** — new `HiddenBoolToVisibilityConverter` keeps the trash column
|
||
reserved (`Hidden`, not `Collapsed`) so edit/eye stay in fixed columns for every source row.
|
||
8. ✅ **No separation between scenes and sources** — a 1px hairline with top/bottom padding now sits
|
||
between the two left-panel listboxes.
|
||
|
||
**THE ONE integration test:** `Mix_HonorsProviderGains_AndGameMute_KillsTheLoopback` — real mixer +
|
||
`AudioGainProvider` gains through the pipe harness: scaled loopback audible at 0.5, silence after mute.
|
||
|
||
## TASK 9 audio milestone — SHIPPED 2026-08-14 (what changed)
|
||
|
||
The creator's feature review settled this as the single next branch ("all the audio issues done and
|
||
tested — a huge milestone"). Final spec and full shipped-state records live in `TASKS.md` (TASK 9)
|
||
and `ai.md` ("Live audio capture"). Highlights:
|
||
|
||
- **Real audio into the encoder.** `FfmpegArgs` now builds `-f f32le -ar 48000 -ac 2 -i \\.\pipe\ytllive_audio`
|
||
+ explicit `-map 0:v -map 1:a` (replaces `anullsrc` silence). `MainViewModel.BeginGoLive` →
|
||
`_audioMixer.StartLive(EncoderOptions.DefaultAudioPipeName)`; `StopStream` → `_audioMixer.StopLive()`
|
||
before `_framePump.StopAsync()`.
|
||
- **2-input mix (mic + loopback)**, honest gains: `micGain = MicVolume` (mute = 0),
|
||
`loopbackGain = GameMuted ? 0 : GameAudioVolume` × duck. No third music channel.
|
||
- **Voice chain on the mic** (TASK 22), before meter AND mix: bass 120 Hz +4 dB → treble 8 kHz +3 dB →
|
||
gate (0.005 / hysteresis 0.5) → compressor (0.5, 4:1). Pure TDF2 DSP, per-sample, always on.
|
||
- **Auto-duck:** mic RMS > 0.02 → loopback ×0.25 (−12 dB), attack 0.05 / release 0.005.
|
||
- **TRAX (free BGM):** `MusicPlayer` = MediaFoundationReader → `VolumeWaveProvider16` at fixed **0.20** →
|
||
`WaveOutEvent`. Plays to the default device → rides the loopback into the stream (ducked with game).
|
||
Footer TRAX button (dot red/yellow/green + "TRAX"), left-click toggles/picks, right-click opens the
|
||
picker (`OpenFileDialog`), tooltip shows the track name. Track persists via **schema v9** single-row
|
||
`Music`. Sound-bar label "Game Audio Capture" → **"Desktop Audio"**;
|
||
`IsGameAudioBarVisible = gameDetectorProducingSound || IsMusicPlaying`.
|
||
- **Tests:** new `AudioPipelineTests.cs` (DSP/ring-buffer/ducker/resampler units + the ONE integration
|
||
test reading real pipe bytes via `NamedPipeClientStream`); `FfmpegEncoderTests` + layout persistence
|
||
updated for pipe args / Music roundtrip. Ring-buffer overwrite bug found by the unit test and fixed
|
||
(head must NOT advance on eviction — the write itself advances it). Integration test pre-fills the
|
||
ring buffers before `StartLive` so the first pipe tick already carries audio (deterministic — a
|
||
start-of-stream silence race was seen and eliminated).
|
||
|
||
**Out of scope this branch:** IP webcam, chat box, alt-key crop, credits, bg removal, music-off-VOD
|
||
track (YouTube mutes VODs with copyrighted music — future feature), `PremiumUrl` (TASK 23 seam).
|
||
|
||
- **Landmines:**
|
||
- Never add another test that constructs `new App()` — use `RealAppHost.Run(...)` (shared STA host
|
||
for the one WPF App per AppDomain; round-clip + source-naming tests).
|
||
- Never set a local `Canvas.SetTop` on the social bar — a local value permanently
|
||
overrides `{Binding SocialBarTop}` (the `ClearValue` lesson from 5.5).
|
||
- `AudioMixer` meter `Push` is unconditional **by design now**: `OnMicSample`/
|
||
`OnLoopbackSample` compute the level first, then raise the event — a
|
||
`?.Invoke(meter.Push(...))` short-circuit skipped the meter update when
|
||
nothing was subscribed (found by `RestartMic_ResetsLevel`, fixed).
|
||
- `MicConnected` comes from the source `Started` event, raised right after
|
||
`StartRecording()` succeeds — tests must `MarkStarted()` the fake source
|
||
before asserting connection state.
|
||
- The mic dot is red until a resource connects (see contract below) — green
|
||
only after `Started`, yellow on `Failed`.
|
||
- Zero mic devices at startup = red dot AND the mixer is never started, so
|
||
loopback + the game bar can't run either (no capture at all) — acceptable.
|
||
- The game detector is polled on the UI thread via a 250ms `DispatcherTimer`;
|
||
`OnGameAudioPollTick` wraps `Poll()` in try/catch + `AppLog`.
|
||
- The pump reads the active scene on a background thread while the UI can still
|
||
edit it — a concurrent-mutation exception is contained (logged + `Failed` +
|
||
the pump stops), not a crash.
|
||
- `StopAsync` must stop the encoder (closes stdin) **before** awaiting the pump
|
||
loop — closing stdin unblocks a write stuck on pipe backpressure; the reverse
|
||
order deadlocks. Same rule for audio: `StopLive()` (pipe EOF) before the pump
|
||
stop, so ffmpeg's two inputs end in order.
|
||
- Tests never instantiate `MainViewModel` directly except via a real `MainWindow`
|
||
on the `RealAppHost` STA thread (round-clip + naming), which never go live.
|
||
- Sandbox can't reach outbound HTTPS — `HttpSocialValidator` stub-handler tests
|
||
only, never the real instance.
|
||
- **Mic status contract (creator's rule, verified — do NOT "fix"):** the status dot is
|
||
**red until a mic resource is actually connected**. `MicStatus` starts `NotConnected`
|
||
(red); it goes green ONLY when the mixer's `MicConnected` fires, which comes strictly
|
||
from the mic source's `Started` event raised after `StartRecording()` succeeds. Zero
|
||
devices at startup → stays red and the mixer is never started; capture failure → yellow.
|
||
|
||
- **Secret/DB/port facts live:** OAuth client id/secret in `Helpers/OAuthCredentials.cs`;
|
||
OAuth session token in `Helpers/TokenStore.cs` (DPAPI → `%APPDATA%\ytLlive\ytLlive.auth`);
|
||
layout DB `%APPDATA%\ytLlive\ytLlive.db` (**schema v9** — single-row `Music`; the
|
||
`SocialEntry.Software` column is a column-presence migration like the others); OAuth callback
|
||
`http://localhost:8765/oauth2/callback`; crash log `%APPDATA%\ytLlive\startup.log`.
|
||
|
||
## Server recovery (llamachile.tube / YunoHost / DO) — 2026-08-14
|
||
|
||
**Facts (hunted this session — do not re-hunt):**
|
||
- Droplet **llamachile.tube**: DO droplet ID `473190301`, IP `143.244.176.131`, sfo3, 4GB/2vCPU/50GB.
|
||
**SSH port = 2214** (matches git remote `ssh://llgit.llamachile.tube:2214/gramps/ytLlive.git`);
|
||
22/2222/2200/etc all closed. SSH as `gramps` works with `~/.ssh/id_ed25519` (key auth, no password).
|
||
- **Root is YunoHost-locked**: `PermitRootLogin no` in `/etc/ssh/sshd_config` (there's a conflicting
|
||
cloud-init override section below it, but DO's "Reset root password" email does NOT work on this
|
||
box — cloud-init `set-passwords` only ran once at install). Root is reachable via `sudo -i` or the
|
||
DO web Recovery Console (Settings → Recovery console — VNC-based; the droplet page's Console button
|
||
is SSH-based and fails with "all configured authentication methods failed" when no account has a
|
||
working password).
|
||
- **gramps is a YunoHost LDAP account** (local `/etc/passwd` entry has `*`, auth via pam_ldap) — its
|
||
password is changed with `sudo yunohost user update gramps -p '<pw>'`, NOT `passwd`.
|
||
- **fail2ban bans the whole IP for 10-12 min** after repeated failed SSH/root logins (all ports
|
||
refuse; `ping` still works; droplet API still shows `active`). Wait it out — do NOT power-cycle.
|
||
- Password reset this session: `sudo yunohost tools rootpw -n '<pw>'` sets root (rootpw takes `-n`).
|
||
Verification: `getent shadow root` shows a `$y$` yescrypt hash + `passwd -S root` = `P`.
|
||
- Mastodon services run as systemd units `mastodon-web/sidekiq/streaming` (all were `active` after
|
||
the reboot); gitea/nginx/mariadb/postgres/redis/yunohost-api also systemd units. Disk was **93% full**
|
||
(3.6G free) — keep an eye on it before any big upgrade.
|
||
- The original outage was an interrupted Mastodon upgrade + a DO `password_reset` reboot; everything
|
||
came back on its own after boot. No code/schema damage observed.
|
||
|
||
**Secrets (removed 2026-08-14 — see git history if a value is ever needed again):** the DO API token,
|
||
the gramps/root passwords and the DO password-reset email password were recorded in this file and in
|
||
chat. They are treated as **compromised** — the DO API token and every listed password have been or
|
||
should be rotated/revoked, and **no new secrets belong in this repo or in chat**. Secret paths that
|
||
stay here are the file locations only (OAuth creds → `Helpers/OAuthCredentials.cs`, session token →
|
||
`Helpers/TokenStore.cs`).
|