80 lines
5.8 KiB
Markdown
80 lines
5.8 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-14)
|
||
|
||
- **Branch:** `main`, in sync with `origin/main`.
|
||
- **TASK 8 — meter scaling amplification:** SHIPPED + pushed (`0b71b03`). `AudioLevelMeter.ToDisplay`
|
||
adds **+10 dB input amplification** before the −60..0 dBFS log map: speech peaks (~0.2 RMS) read
|
||
~0.93 (red) and normal speech (~0.05) ~0.73 (yellow) at maxed volume; ≤0.001 linear still reads 0
|
||
(never idles on background noise). One knob shared by the mic + game bars; `× MicVolume` untouched.
|
||
- **TASK 4 ship step 7 — one-click go live + private-only enforcement: SHIPPED** (this session; the
|
||
final open TASK 4 requirement). Double-enforced privacy:
|
||
1. **Dialog locked to Private** — `GoLiveViewModel.Visibility` is a get-only `"Private"`, the
|
||
visibility ComboBox is gone (replaced by a "Streams always start Private" note); settings' dead
|
||
"Default Visibility" dropdown + `MainViewModel.Visibilities`/`DefaultStreamVisibility` removed.
|
||
2. **Service forces private** — `YouTubeStreamService.CreateBroadcast` always sends
|
||
`privacyStatus="private"`; ctor gained `HttpClient? http = null` seam for tests.
|
||
3. **Wired into go-live** — `BeginGoLive` fires `CreateBroadcastAsync` (stores `_currentBroadcastId`
|
||
for TASK 5's bind/transition; failure → `StreamStatus.Error` via AppLog, never a crash) +
|
||
`_framePump.StartAsync()`; `StopStream` clears `_currentBroadcastId`.
|
||
4. **PRIVATE badge** — dark-red bordered, next to REC in the top bar, `Visibility="{Binding
|
||
IsLivePrivate}"` (MainWindow.xaml ~110).
|
||
5. **Integration test** — `ytLive.Tests/YouTubeStreamServiceTests.cs`: the broadcast-insert request
|
||
must carry `"privacyStatus":"private"` (RecordingHandler seam) + a no-session → null guard test.
|
||
- **Verified:** build 0 warnings / 0 errors; **173/173 tests pass** (171 + the 2 stream-service tests).
|
||
- **Uncommitted (this session's ship step 7 batch):** `GoLiveWindow.xaml`, `ViewModels/GoLiveViewModel.cs`,
|
||
`ViewModels/MainViewModel.cs`, `Services/YouTubeStreamService.cs`, `MainWindow.xaml`,
|
||
`ytLive.Tests/YouTubeStreamServiceTests.cs` (new), `TASKS.md`, `ai.md`, `ViewModels/index.md`,
|
||
`Services/index.md`, `HANDOFF.md`. **Commit + push before continuing.**
|
||
- **Next step:** commit + push ship step 7, then **TASK 5 — reusable `variable` stream** (create once
|
||
per channel, cache ingestion URL, bind to broadcast, feed `_rtmpUrlProvider` so the pump actually
|
||
pushes — `_currentBroadcastId` is already stashed for it). Also queued: task 21 (logo + About hub),
|
||
task 22 (voice filters). Nothing else queued — do not expand the task queue on your own. Optional,
|
||
not queued: rewriting the healed entry's `ProfileUrl` to `https://mastodon.llamachile.tube/@gramps`
|
||
(user must say the word).
|
||
|
||
- **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.
|
||
- 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 v8; `SocialEntry.Software`
|
||
column is a column-presence migration like the others, no version bump); OAuth callback
|
||
`http://localhost:8765/oauth2/callback`; crash log `%APPDATA%\ytLlive\startup.log`.
|