From 4b9a8e515f48831069657675e69add4493b629ad Mon Sep 17 00:00:00 2001 From: gramps Date: Sat, 22 Aug 2026 18:10:59 -0700 Subject: [PATCH] Control Surface UX: pill toggle, background rename, per-scene properties - Sliding pill toggle (green ON / red OFF) replaces checkbox - Geometry mismatch error for custom backgrounds - Backdrop -> Background: C# rename, SQL column rename with migration - Source.IsBackground: new -> override (fixes WPF binding) - Legacy 'Backdrop' display name migrated to 'Background' - Per-scene UseDefaultBackground + CustomBackgroundPath - Asset files renamed backdrop -> background - StagedScene/LiveScene split, thumbnail strip, edit gating - TransitionService (Cut/Fade/Move) - Context menu: Capture Desktop, Refresh Desktop - Resolution badge removed, ON-AIR status light added --- .../{brb-backdrop.jpg => brb-background.jpg} | Bin ...{chat-backdrop.jpg => chat-background.jpg} | Bin ...ing-backdrop.jpg => ending-background.jpg} | Bin ...{live-backdrop.jpg => live-background.jpg} | Bin ...g-backdrop.jpg => starting-background.jpg} | Bin HANDOFF.md | 105 ++- Helpers/HiddenBoolToVisibilityConverter.cs | 2 +- MainWindow.xaml | 472 ++++++------- MainWindow.xaml.cs | 52 +- Models/Scene.cs | 59 +- Models/SceneCatalog.cs | 6 +- Models/SceneElement.cs | 1 + Models/Source.cs | 8 +- Services/Compositor/SceneCompositor.cs | 44 +- Services/Encoder/FramePump.cs | 32 +- Services/LayoutStore.cs | 144 +++- Services/ScreenCaptureFrameSource.cs | 10 +- Services/ScreenCaptureManager.cs | 2 +- Services/SocialValidator.cs | 2 +- Services/TransitionService.cs | 154 +++++ Services/YouTubeAuthService.cs | 4 +- Services/YouTubeStreamService.cs | 2 +- SocialsDialog.xaml.cs | 2 +- TASKS.md | 51 +- Themes/Controls.xaml | 59 ++ ViewModels/MainViewModel.cs | 639 ++++++++++++------ ai.md | 49 +- .../{BackdropTests.cs => BackgroundTests.cs} | 56 +- ytLive.Tests/FramePumpTests.cs | 14 +- ytLive.Tests/LayoutStorePersistenceTests.cs | 62 +- ytLive.Tests/RoundClipInteractionTests.cs | 2 +- ytLive.Tests/SceneCatalogTests.cs | 42 +- ytLive.Tests/SceneCompositorTests.cs | 36 +- ytLive.Tests/SourceNamingTests.cs | 2 +- ytLive.csproj | 10 +- 35 files changed, 1406 insertions(+), 717 deletions(-) rename Assets/{brb-backdrop.jpg => brb-background.jpg} (100%) rename Assets/{chat-backdrop.jpg => chat-background.jpg} (100%) rename Assets/{ending-backdrop.jpg => ending-background.jpg} (100%) rename Assets/{live-backdrop.jpg => live-background.jpg} (100%) rename Assets/{starting-backdrop.jpg => starting-background.jpg} (100%) create mode 100644 Services/TransitionService.cs rename ytLive.Tests/{BackdropTests.cs => BackgroundTests.cs} (62%) diff --git a/Assets/brb-backdrop.jpg b/Assets/brb-background.jpg similarity index 100% rename from Assets/brb-backdrop.jpg rename to Assets/brb-background.jpg diff --git a/Assets/chat-backdrop.jpg b/Assets/chat-background.jpg similarity index 100% rename from Assets/chat-backdrop.jpg rename to Assets/chat-background.jpg diff --git a/Assets/ending-backdrop.jpg b/Assets/ending-background.jpg similarity index 100% rename from Assets/ending-backdrop.jpg rename to Assets/ending-background.jpg diff --git a/Assets/live-backdrop.jpg b/Assets/live-background.jpg similarity index 100% rename from Assets/live-backdrop.jpg rename to Assets/live-background.jpg diff --git a/Assets/starting-backdrop.jpg b/Assets/starting-background.jpg similarity index 100% rename from Assets/starting-backdrop.jpg rename to Assets/starting-background.jpg diff --git a/HANDOFF.md b/HANDOFF.md index d1df55f..5edf406 100644 --- a/HANDOFF.md +++ b/HANDOFF.md @@ -1,29 +1,86 @@ # HANDOFF — Session State ## Branch -`main` — clean, pushed (8951141) +`main` — clean, last push `8951141` (TASK 10 shipped). TRAX meter fix pushed `e06bfa4`. +All Control Surface UX work is **uncommitted** — build passes 0 warnings/0 errors. + +## ⚠️ IN FLIGHT: repo-wide Backdrop→Background rename (multi-session) +Models already renamed (`Source.IsBackground`, `Scene.HasBackground`/`CustomBackgroundPath`, +`SceneCatalog.HasBackground`, `Source` enum `Background`). Status per file: +- ✅ `ViewModels/MainViewModel.cs` — full case-sensitive rename done; compiles clean. +- ✅ `MainWindow.xaml.cs` + `MainWindow.xaml` — renamed; full solution builds 0 warnings/0 errors. +- ✅ `Services/Compositor/SceneCompositor.cs` — CS0128 duplicate locals fixed. +- ⏳ `Services/LayoutStore.cs` — C# members renamed, but DB column strings + (`HasBackdrop`/`IsBackdrop`) are **schema** and intentionally still say Backdrop: + renaming them needs a real migration (new user_version) touching every existing DB. + The V5-backfill unit test's raw CREATE TABLE must keep `IsBackdrop` to match — + blanket-renaming it breaks `V5_Database_Backfill_Removes_NonLive_Backgrounds` + (verified: migration can't see flags, Live source loses `IsBackground`). +- ✅ `ytLive.Tests` — all 5 files blanket-renamed (class/method/var/comment names + + C# property accesses); `BackdropTests.cs` → `BackgroundTests.cs`. Tests: 207/208 pass; + the only red is pre-existing `AudioPipelineTests.Mix_HonorsProviderGains_AndGameMute_KillsTheLoopback` + (unrelated gain-math assert at AudioPipelineTests.cs:373, zero backdrop occurrences). +- Pending elsewhere: leftover "backdrop" doc comments (`Source.cs`, `SceneCatalog.cs`, + `ScreenCaptureManager.cs`, `ScreenCaptureFrameSource.cs`, `MainWindow.xaml:769`), + asset files + csproj (`Assets/*-backdrop.jpg` — pack-URI strings in MainViewModel now say + `-background.jpg` so default-scene seeding silently no-ops until assets+csproj are renamed). ## What shipped this session -1. **TASK 10 — Polar billing + Velopack auto-updates (steps 1–7)** — full license key activation flow: - - **Velopack** — NuGet `Velopack` 1.2.0 added, bootstrap in `App.xaml.cs` (`VelopackApp.Build().Run()` in try/catch). Self-hosted update URL TBD (DO droplet). The bootstrap is a no-op until the update URL is configured. - - **PolarLicenseService** — `Services/PolarLicenseService.cs`: `POST .../validate` with `key` + `organization_id`. Returns `status` (`granted`/`revoked`/`expired`) + `expires_at`. No auth. Network errors surfaced gracefully. - - **License key entry UI** — About overlay gains a third panel (same pattern as Licenses): text input + "Activate" button + status message. "Enter License Key" button added to the hub (alongside "Unlock Premium" which opens the Polar checkout URL). `IsHubVisible` computed property hides the hub when either sub-panel is open. - - **Entitlement persistence** — `LayoutStore` gains `LoadLicenseKey`/`SaveLicenseKey`, `LoadLicenseValidatedAt`/`SaveLicenseValidatedAt`, `LoadIsPremium`/`SaveIsPremium` via the Settings key/value table (no schema version bump needed). 14-day offline grace period: cached premium state trusted while online re-validation runs in the background. - - **BrandFlash toggle** — `IsPremium` property setter flips `BrandFlashEnabled = !value`. On startup, `ValidateLicenseAtStartupAsync` loads cached state → attempts online re-validation → overrides cache on success → lapses on failure/expiry. Premium removes the watermark; free tier keeps it. - - **PremiumUrl wired** — set to `https://buy.polar.sh/polar_cl_ueMy8AyAClO0o0yo19IroZwKTbD587gzUsjEW3bFapw`. `IsPremiumAvailable` now true. About overlay "Unlock Premium" button lights up. Tooltip updated. - - **About text updated** — "one-time unlock" → "subscription". +1. **TASK 10 — Polar billing + Velopack auto-updates (steps 1–7)** — full license key activation flow +2. **TRAX meter double-attenuation fix** — removed redundant slider multiplication +3. **LAN infrastructure documented** — all servers in `ai.md` + `CREDENTIALS.md` +4. **llamavault NFS/SMB diagnosis** — zombie SMB session from power outage, fixed by killing stale PID -2. **Polar MCP auth** — authenticated via `opencode mcp auth Polar`. Polar MCP connected. +## Active work: Control Surface UX (TASK 19/23 merged) — ALL PHASES COMPLETE -## Files changed -- `App.xaml.cs` — Velopack bootstrap (try/catch, non-fatal) -- `ytLive.csproj` — added `Velopack` 1.2.0 NuGet -- `Services/PolarLicenseService.cs` — new: Polar license validation API client -- `Services/LayoutStore.cs` — new: `Load/SaveLicenseKey`, `Load/SaveLicenseValidatedAt`, `Load/SaveIsPremium` -- `ViewModels/MainViewModel.cs` — `IsPremium` (was `static false`, now instance + DB-backed), `PremiumUrl` set, `BrandFlashEnabled` wired to `!IsPremium`, license fields/commands/validation methods, `IsHubVisible`, startup validation -- `MainWindow.xaml` — license entry panel, "Enter License Key" button, hub visibility binding, About text -- `MainWindow.xaml.cs` — `BackToAbout_Click` closes both sub-panels +**Design philosophy:** Director's control surface, not an editor. Five scene monitors above one central live monitor. Click thumbnail = transition (live) or stage (offline). Edit mode only when offline. Live/Recording = read-only. Right-click + drag locked when live. + +### Phases completed (ALL) +1. ✅ **Phase 1:** `StagedScene`/`LiveScene` split + `IsEditMode` in MainViewModel +2. ✅ **Phase 2:** Left panel two-state (edit: layers+props, live: chat) +3. ✅ **Phase 3:** Right panel eliminated +4. ✅ **Phase 4:** Thumbnail strip above center monitor + `Scene.Snapshot` + placeholder snapshots +5. ✅ **Phase 5:** Edit-mode gating (drag/resize/context menus blocked when live) +6. ✅ **Phase 6:** `TransitionService.cs` (Cut/Fade/Move) + `SceneCompositor.BlendFrames()` +7. ✅ **Phase 7:** `FramePump` integrated with `TransitionService` +8. ✅ **Phase 8:** Thumbnail click → `TransitionToScene()` wiring +9. ✅ **Phase 9:** Brand rename "ytLlive" → "LlamaCasty" (user-visible strings) +10. ✅ **Phase 10:** `LayoutStore.SaveTransitionSettings()` / `LoadTransitionSettings()` + +### Key decisions +- **Mode is implicit:** offline = edit, live/recording = read-only. No toggle button. +- **Left panel = one panel, two states:** offline → LAYERS + PROPERTIES; live → YouTube Live Chat (entire panel replaced, never coexists) +- **No (=>) transition button:** clicking a thumbnail directly triggers the transition +- **Thumbnail glow:** active scene gets brand-red `#e94560` border via `IsStaged` data trigger +- **Right panel eliminated:** chat moved to left panel (live mode only) +- **5 thumbnails above center monitor**, 120px height, uniform grid. Placeholder colored boxes until compositor snapshots added. +- **Transitions:** Cut + Fade + Move (v1). Go Live always = Cut. Duration persisted to Settings table. +- **Brand rename:** "ytLlive" → "LlamaCasty" across all XAML, VM strings, service strings. File paths (`%APPDATA%\ytLlive\`) intentionally NOT renamed. + +### Architecture changes +- `ActiveScene` → split into `StagedScene` (central monitor, editable) + `LiveScene` (encoder output, read-only) +- `IsEditMode` = derived property: `StreamStatus == StreamStatus.Offline` +- `TransitionService` — transition state machine with Start/Tick/BlendFrame +- `SceneCompositor.BlendFrames()` — crossfade between two VideoFrames +- `Scene.Snapshot` + `Scene.IsStaged` — thumbnail image and staged indicator +- `FramePump` — now accepts `TransitionService` + `fromSceneProvider`, blends during transitions + +### Files changed +- `ViewModels/MainViewModel.cs` — StagedScene/LiveScene split, IsEditMode, TransitionToScene, snapshots, brand rename, transition persistence +- `MainWindow.xaml` — 2-col layout, thumbnail strip, left panel two-state, right panel deleted, brand rename +- `MainWindow.xaml.cs` — Thumbnail click, edit-mode gating, context menu gating, dead code cleanup +- `Models/Scene.cs` — Snapshot, IsStaged properties +- `Services/Compositor/SceneCompositor.cs` — BlendFrames() static method +- `Services/Encoder/FramePump.cs` — transition support in render loop +- `Services/TransitionService.cs` — **new** — Cut/Fade/Move transition engine +- `Services/LayoutStore.cs` — SaveTransitionSettings/LoadTransitionSettings/UpsertSetting + +### What still needs testing +- Runtime visual testing (need Windows display to run) +- Actual transition animation quality (Fade + Move) +- Thumbnail generation from real scene compositor (currently placeholder colors) +- Drag-reorder of thumbnails (not yet implemented) ## Build ```bash @@ -40,16 +97,16 @@ taskkill.exe /F /IM ytLive.exe # if running | Price | `7c1b6f70-cf8c-46df-9bc8-6703f9fa64d0` | | Benefit | `80f66bc1-d5a3-44a1-b9ea-1709ae508535` | | Checkout | `70e037cd-9bcb-414e-8b26-c82c1d268f65` | -| LLAMAFOUNDER | `01a8c29f-9bb8-4e96-9cbe-417a6a5cec9b` | -| LLAMA50 | `8cff803e-053f-49b4-8d12-d74d468f2943` | | Checkout URL | `https://buy.polar.sh/polar_cl_ueMy8AyAClO0o0yo19IroZwKTbD587gzUsjEW3bFapw` | | Validate endpoint | `POST https://api.polar.sh/v1/customer-portal/license-keys/validate` | | Key prefix | `LCYT-` | -## Next session -1. **TASK 20** — Hotkeys (global keyboard shortcuts) -2. **TASK 19** — Scene transitions (Cut/Fade/Move/Custom) -3. **Velopack update URL** — self-host on DO droplet (`llamacasty.com/updates`) +## Next steps +1. **Visual testing** — run on Windows display, verify layout, transitions, thumbnail strip +2. **Thumbnail drag-reorder** — reorder scenes via drag on thumbnail strip +3. **Real snapshot generation** — replace placeholder colors with compositor-rendered thumbnails +4. **Transition dropdown UI** — add XAML control to pick transition type + duration +5. **Commit** — all this work needs to be committed once visually verified ## v1 vision > A streaming tool that's intuitive and ready to go out-the-gate with nothing held back. diff --git a/Helpers/HiddenBoolToVisibilityConverter.cs b/Helpers/HiddenBoolToVisibilityConverter.cs index daf6aa4..b3f0981 100644 --- a/Helpers/HiddenBoolToVisibilityConverter.cs +++ b/Helpers/HiddenBoolToVisibilityConverter.cs @@ -7,7 +7,7 @@ namespace ytLive.Helpers; /// /// Maps bool → Visibility, collapsing nothing: true becomes Hidden (keeps /// its layout slot) rather than Collapsed (releases it). Used where a row -/// must keep its column grid aligned — e.g. the Backdrop source's missing +/// must keep its column grid aligned — e.g. the Background source's missing /// trashcan must not shift the edit/eye icons one slot to the right. /// public class HiddenBoolToVisibilityConverter : IValueConverter diff --git a/MainWindow.xaml b/MainWindow.xaml index 06ee613..5b4d2f8 100644 --- a/MainWindow.xaml +++ b/MainWindow.xaml @@ -16,7 +16,8 @@ Loaded="MainWindow_Loaded" Activated="MainWindow_Activated" Deactivated="MainWindow_Deactivated" - PreviewMouseLeftButtonDown="Window_PreviewMouseLeftButtonDown"> + PreviewMouseLeftButtonDown="Window_PreviewMouseLeftButtonDown" + ContextMenuOpening="Window_ContextMenuOpening"> @@ -95,19 +96,28 @@ + + + + + + + + + - - + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - + + + + + + + + + + - - - - - - - - - - - - - - - - - - + + - - - - + + + + Visibility="{Binding IsBackground, Converter={StaticResource InverseBoolToVis}}"/> @@ -367,7 +380,7 @@ @@ -376,19 +389,19 @@ - - - - @@ -592,29 +605,109 @@ IsChecked="{Binding SelectedElement.ChatShowBadges, Mode=TwoWay}"/> - - + + + + + + + + + + +