TASKS.md 467 → 66 lines: one line per live item, detail stays in TASKS/ task-*.md. The old catalogue (63 rows of paragraph status prose plus the historical narration) is preserved verbatim as TASKS/archive.md — a 2026-10-29 snapshot, not the live queue. The "out of product" list and every retired decision moved to docs/ai/decisions.md with cause of death. HANDOFF.md 440 → 40 lines, and it is now OVERWRITTEN, not appended: branch, what's in flight, next action, live landmines, pending creator checks. History belongs to git log; the previous handoff is reachable at ee3c592:HANDOFF.md. docs/ai/decisions.md (new) collects what no longer governs: the paid tier and its pricing, Polar, Store IAP, the Store route and why it died, the deleted Velopack updater, signing economics, the retired monetization ideas, and the permanent out-of-product list. Facts that lived in the old ai.md "Current limitations" section were re-homed rather than dropped with the router rewrite: OAuth/DPAPI/session behaviour → docs/ai/youtube-api.md; paths, persistence and the Google project number → docs/ai/environment.md. verify.sh caps now cover all four always-read files (AGENTS 210, ai 100, HANDOFF 40, TASKS 120). Docs-only unit: scope-check run, no build.
157 lines
10 KiB
Markdown
157 lines
10 KiB
Markdown
# Environment, build & tests
|
||
|
||
Lookup material: LAN hosts, the Windows build commands, where the DB/auth/log live, and the
|
||
test suite map. Read when you are about to build, run, or add a test — not on every session.
|
||
|
||
> Paths and secrets belong with the runbook in [`../../HANDOFF.md`](../../HANDOFF.md) →
|
||
> "Secrets / paths" until the queue trim moves them here.
|
||
## LAN infrastructure (192.168.50.x)
|
||
|
||
All LAN credentials live in `CREDENTIALS.md` (gitignored). Server IPs and roles:
|
||
|
||
| Host | IP | Role |
|
||
|------|----|------|
|
||
| corsair | .132 | Gaming/streaming primary — RTX 5070 Ti (this machine) |
|
||
| llamavault | .87 | ODROID-HC4, 12TB RAID5, media storage (NFS). SSH: `ssh mshallop@192.168.50.87`. **DHCP-bound — it moved off `.86` after the 2026-10 power outage, so confirm with a ping before trusting any path.** SMB is reached by the **`llamavault` name**, pinned in the Windows hosts file, never by literal IP: `\\llamavault\llamaVault\...` |
|
||
| pivault | .158 | RPi5 8GB, 10.83TB RAID5 NAS |
|
||
| jarvis | .210 | AMD Ryzen 5 5600X, RX 6600 XT — AI inference, SearXNG, Docker registry, Immich |
|
||
| ultron | .108 | AMD Ryzen 7 7840HS — AI orchestrator, llama-server, Qdrant |
|
||
| gordito | .144 | Home Assistant, Alarmo, Zigbee |
|
||
| officerfriendly | .26 | Pi-hole DNS |
|
||
| octopi | .197 | OctoPrint (Prusa MK4) |
|
||
| BigBlinkyRouter | .1 | ASUS ROG GT-BE98 Pro, WiFi 7 |
|
||
|
||
Web infra (DO droplet): `llamacasty.com` + `llamachile.tube` at `143.244.176.131`.
|
||
|
||
**Pushing to the Gitea instance (`llgit.llamachile.tube`).** The repo is **`gramps/LlamaCasty`**
|
||
(renamed from `gramps/ytLlive`; the old path 301-redirects). **Push over HTTPS**, not SSH:
|
||
`origin = https://llgit.llamachile.tube/gramps/LlamaCasty.git`, with the credential helper
|
||
`/home/gramps/bin/gitea-credential` (registered as
|
||
`credential.https://llgit.llamachile.tube.helper`) reading the account password from the
|
||
gitignored `CREDENTIALS.md` on demand — nothing stored in plaintext, nothing in a command line.
|
||
**The Gitea account password IS the portal password** in `CREDENTIALS.md`, and the same password
|
||
authenticates the REST API (`curl --config …/api/v1/user`) — **that API is the fastest way to ask
|
||
the server questions** (the repo's real name and clone URL, the account's SSH keys, a token).
|
||
⛔ **The SSH endpoint on `llgit:2214` is not a usable push path**: the account's SSH keys ARE
|
||
registered (key id 6 "Corsair VSC" = this machine's `~/.ssh/id_ed25519`) yet the server refuses
|
||
the publickey, and the port later refused connections outright. Do not spend a session on key
|
||
archaeology — ask the API, then push over HTTPS.
|
||
|
||
## Run
|
||
|
||
**First time on a fresh clone (or if `ffmpeg/` was deleted) — this is REQUIRED, not optional:**
|
||
|
||
```bash
|
||
./scripts/fetch-ffmpeg.sh # ~65 MB once; verifies SHA-256, extracts 9 files into ./ffmpeg
|
||
```
|
||
|
||
FFmpeg is **bundled, never downloaded at runtime** (Windows Store policy 10.2.2 — the old runtime
|
||
download 404'd on 2026-09-01 and would fail certification). The binaries are gitignored because
|
||
135 MB of DLLs in git would tax every commit on this 9p tree; the pinned URL + SHA-256 in the script
|
||
are the audit trail. `ytLive.csproj`'s `GuardBundledFfmpeg` target **fails the build** with that
|
||
exact instruction if the folder is missing. `FFMPEG_ZIP=<path>` reuses a pre-downloaded archive.
|
||
|
||
```bash
|
||
# From WSL, ALWAYS use the Windows dotnet host — never Linux `dotnet` for this project:
|
||
"/mnt/c/Program Files/dotnet/dotnet.exe" build "C:\Users\gramp\Documents\Code\projects\ytLive\ytLive.csproj"
|
||
"/mnt/c/Program Files/dotnet/dotnet.exe" run -c Release # the launch command (creator, 2026-10-16): Release, not Debug — perf takes must read Release numbers
|
||
```
|
||
|
||
**Run-mode convention (creator directive 2026-10-16):** the creator launches with `-c Release`
|
||
by DEFAULT. Debug runs happen ONLY when the AI explicitly names the Debug command for a
|
||
specific reason — never implicitly, and never for reading performance numbers (Debug pixel
|
||
paths are several × slower; every take in the 141ms saga read Debug numbers where Release is
|
||
the real reading).
|
||
|
||
**Pre-commit gate (write it down — I forgot once):** ALWAYS run
|
||
`./scripts/verify.sh "<file1>" "<file2>" ...` (declared scope) before committing. It forces a
|
||
**clean** build (0 warnings), the full test suite (passes only the 2 known pre-existing `[FAIL]`s), and
|
||
the scope check in one shot. An **incremental** build can report `0 Warning(s)` while skipping
|
||
recompiles — that's how `CS8601` slipped past on 2026-08-29. Never claim 0 warnings from anything but
|
||
`verify.sh`'s clean build.
|
||
|
||
`EnableWindowsTargeting=true` in `ytLive.csproj` lets a cold restore work from WSL, but a Linux
|
||
`dotnet run`/`build` re-downloads 100M+ of `windowsdesktop.app.*` packs into the Linux NuGet cache
|
||
(which lacks them) over the slow 9p `/mnt/c` bridge — twice, because the WPF `_wpftmp` generated
|
||
project triggers a second restore (203s observed). The Windows cache has the SQLite packages and the
|
||
packs resolve from `C:\Program Files\dotnet\packs`, so the Windows host never re-downloads.
|
||
Never use `--no-restore` right after an interrupted restore — the stale `project.assets.json`
|
||
produces misleading `NETSDK1064` "package not found" errors. Running requires Windows anyway.
|
||
|
||
## Tests
|
||
|
||
xUnit in `ytLive.Tests` (net8.0-windows10.0.19041.0, matches the app TFM). Run on Windows —
|
||
WSL can't run net8.0-windows tests:
|
||
|
||
```bash
|
||
dotnet.exe vstest "C:\...\ytLive.Tests\bin\Debug\net8.0-windows10.0.19041.0\ytLive.Tests.dll"
|
||
```
|
||
|
||
Good Dog Rule: ONE integration test per change (no feature branches pre-1.0 — work lands on
|
||
`main`, see AGENTS.md). Current: TokenStore DPAPI
|
||
roundtrip/corrupt/missing/clear, OAuth exchange/refresh/ClearSession, CameraManager refcount +
|
||
frame pump + failure handling (fakes for the WinRT seams), real-`MainWindow` round-clip
|
||
interaction test, LayoutStore delete roundtrip, LayoutStore pre-round-rect-dims roundtrip,
|
||
LayoutStore background roundtrip, LayoutStore HasBackground roundtrip + v5→v6 non-Live backfill,
|
||
ScreenCaptureManager refcount + shared-bitmap + coalescing
|
||
(fake `IScreenCaptureSource` + a real background-STA `Dispatcher`), BackgroundTests, SceneCatalogTests,
|
||
the TASK 25 dirty-layout heal (BackgroundHealIntegrationTests), WebcamSafeguardTests,
|
||
SceneCompositorTests (full-scene composite + vertical tier), StretchMathTests, FfmpegLocatorTests,
|
||
FfmpegEncoderTests, FramePumpTests, the TASK 8/11/12 audio chain (AudioPipelineTests + MasterLimiter),
|
||
the Socials fediverse-heal roundtrip, AboutHubTests, NotificationAreaIntegrationTests (TASK 24),
|
||
GlobalHotkeyTests + HotkeyConfigTests (TASK 20), WebcamMenuGateTests (TASK 26), ChatLayerGateTests
|
||
(TASK 27), BroadcastPullOutTests (TASK 29), DefaultRecordFolder fallback (TASK 30), WebView2ManagerTests
|
||
(TASK 17), RecordingFileTests + OnAirSignTests (TASK 18), SessionTeardownTests +
|
||
WebcamOutputKeyTests + GameMeterHonestyTests (2026-09-01 recording-verification fixes) — **ZERO
|
||
known failures as of 2026-09-01. `AudioPipelineTests` 25/25 green in 26ms —
|
||
the "known failing" `Mix_HonorsProviderGains…` and the class's notorious STANDALONE HANG shared
|
||
one root cause: TASK 22's `_delayedMix` (nullable, never initialized) was dereferenced
|
||
(`delayed.Length` on null) every live-mix tick — a swallowed NRE starved the pipe the tests
|
||
read and flooded startup.log. Test was right, code drifted from the map's own contract
|
||
(loopbackGain = GameAudioVolume — now honored; the "unity because loopback scales with the
|
||
endpoint" assumption was disproven by the creator's 20%-volume meter observation). The former
|
||
RoundClip "known failure" (stale-test layers: namescoped `FindName` + `VisualTreeHelper.HitTest`
|
||
where `UIElement.InputHitTest` models input — see MyMistakes) was fixed the same day.
|
||
Per-class runs through the Windows dotnet.exe host execute the RealApp/MainWindow suites fine;
|
||
only the FULL-suite run still hangs (WASAPI teardown, pre-existing) — and the whole-suite
|
||
"247 total" era count is stale; trust per-class results.**
|
||
|
||
Reward-event capture (monetization awareness, see the Monetization section) will add its integration
|
||
tests here when it ships: one real chat-poll payload containing all seven reward event types →
|
||
assert the persisted canonical `RewardEvent` rows (type + amount/currency/tier/memberLevel/participant
|
||
ids) round-trip into SQLite.
|
||
|
||
### Real-MainWindow tests MUST be hermetic (DB pollution bug)
|
||
|
||
The integration test boots a real `MainWindow` → `MainViewModel` → real `LayoutStore`
|
||
(`%APPDATA%\ytLlive\ytLlive.db`). `Shutdown()` on close **saves the layout** (full rewrite:
|
||
DELETE all scenes/sources, re-insert), so any source a test adds would be persisted over the
|
||
user's real ones — this happened and wiped the real webcam source (DeviceId replaced by the
|
||
test's fake `test-camera`). Rule: a test that constructs `MainWindow` MUST first set
|
||
`MainViewModel.LayoutPathOverride` to a temp DB path and reset it (plus
|
||
`SqliteConnection.ClearAllPools()` + delete) in `finally`. The seam is
|
||
`internal static string? LayoutPathOverride` (line ~529 in `MainViewModel.cs`),
|
||
`ytLive.csproj` has `InternalsVisibleTo("ytLive.Tests")`.
|
||
|
||
The layout DB is a **full rewrite per save** (delete all, re-insert from memory), so
|
||
save/load round trips are exact: an element removed in the UI (`RemoveElement` →
|
||
`scene.Elements.Remove` → `OnElementsChanged` → debounced `ScheduleSave`, plus `Shutdown` on
|
||
close) does **not** come back after reload (`LayoutStorePersistenceTests` guards this).
|
||
|
||
|
||
## Persistence & external accounts (lookup)
|
||
|
||
- Layout: `%APPDATA%\ytLlive\ytLlive.db` (SQLite, schema v8 — scenes, sources, assets by SHA-256
|
||
content hash, socials, settings; **full rewrite per save**).
|
||
- Auth session: `%APPDATA%\ytLlive\ytLlive.auth` (DPAPI, CurrentUser scope).
|
||
- Startup log: `%APPDATA%\ytLlive\startup.log`, capped at 5 MB with one rolled copy
|
||
(`Helpers/LogRotation.cs`, TASK 55 slice 1).
|
||
- **Google Cloud project `198374416003`** — the OAuth ClientId prefix is the project number. It is
|
||
owned by **`llamachile.shop@gmail.com`** ("gramps"), which is **NOT** the Google account a creator
|
||
signs into YouTube with; console access and channel identity are separate logins. Direct link:
|
||
`https://console.cloud.google.com/apis/credentials?project=198374416003` — if the picker shows no
|
||
match it defaults to the **"Recent"** tab, so switch to **"All"**.
|
||
- LAN credentials live in the gitignored `CREDENTIALS.md`. ⚠️ It says Home Assistant is
|
||
`192.168.50.144`; the real HA is `192.168.50.145` (`.144` is `gordito`). Fix that file when next
|
||
editing it.
|