From 8951141c65ca5dd2aa2e7210a01a43f08d0cb62d Mon Sep 17 00:00:00 2001 From: gramps Date: Fri, 21 Aug 2026 08:18:52 -0700 Subject: [PATCH] TASK 10: Polar billing + Velopack auto-updates (steps 1-7) --- App.xaml.cs | 19 +++- HANDOFF.md | 63 +++++------- MainWindow.xaml | 29 +++++- MainWindow.xaml.cs | 3 + Services/LayoutStore.cs | 70 +++++++++++++ Services/PolarLicenseService.cs | 87 ++++++++++++++++ TASKS.md | 16 +-- ViewModels/MainViewModel.cs | 169 ++++++++++++++++++++++++++++++-- ai.md | 4 +- ytLive.csproj | 35 +++---- 10 files changed, 417 insertions(+), 78 deletions(-) create mode 100644 Services/PolarLicenseService.cs diff --git a/App.xaml.cs b/App.xaml.cs index 462beaa..0ba296a 100644 --- a/App.xaml.cs +++ b/App.xaml.cs @@ -1,5 +1,6 @@ using System.Windows; using System.Windows.Threading; +using Velopack; using ytLive.Helpers; namespace ytLive; @@ -16,9 +17,6 @@ public partial class App : Application protected override void OnStartup(StartupEventArgs e) { - // The app is a WinExe (no visible console) and is often debugged from - // WSL where it can't even run, so startup checkpoints and any unhandled - // exception are written to %APPDATA%\ytLlive\startup.log via AppLog. AppDomain.CurrentDomain.UnhandledException += (_, args) => AppLog.Write(args.ExceptionObject is Exception ex ? "AppDomain unhandled exception: " + ex @@ -28,6 +26,21 @@ public partial class App : Application AppLog.Write(args.Exception, "Dispatcher unhandled exception"); AppLog.Write("App OnStartup begin"); + + // Velopack auto-update bootstrap — checks for updates on startup and + // applies them (the app restarts automatically if an update is found). + // Update URL is configured per-deployment (self-hosted on DO droplet); + // when no URL is set, the check is a no-op. + try + { + VelopackApp.Build() + .Run(); + } + catch (Exception ex) + { + AppLog.Write($"Velopack bootstrap failed (non-fatal): {ex.Message}"); + } + base.OnStartup(e); AppLog.Write("App OnStartup end"); } diff --git a/HANDOFF.md b/HANDOFF.md index 6f3832d..d3a4275 100644 --- a/HANDOFF.md +++ b/HANDOFF.md @@ -1,56 +1,36 @@ # HANDOFF — Session State ## Branch -`main` — dirty (all changes uncommitted, builds clean) +`main` — clean, uncommitted TASK 10 changes ## What shipped this session -1. **Show Desktop toggle on Live backdrop** — `Source.ShowDesktop` bool persisted in DB (schema migration + read/write in LayoutStore). When checked, the primary monitor captures regardless of fullscreen game state. Toggling off falls back to the running game or releases to the static placeholder. `ClearBackdropCaptureAsync` clears `CaptureKey` + `VideoImageSource` so the static fallback actually renders. +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". -2. **Top bar — status light + avatar + Log In button** — red/green/pulsing ellipse (disconnected/connected/live). Avatar border visible only when connected, loads via `BitmapImage` in code-behind (not XAML binding). "Log In" button visible when disconnected, calls `StartStreamCommand` which opens GoLiveWindow with the sign-in flow. `ShowStartStream` now requires `IsConnected` too. - -3. **Chat preview — one-at-a-time mock messages** — `RunMockChatPreviewAsync`: simple async loop, displays `MockChatMessages[n]` via `UpdateChatBoxPreview(chatBox, Take(n+1))`, 1000ms between messages, wraps at end. `ChatPreviewEnabled` property on Source (default true). Stops when real messages arrive, restarts on fade timer clear. - -4. **Default backdrop seeds for all scenes** — `SeedEndingBackdrop` replaces existing (not skip-if-exists). `SeedChatBackdrop` added. `EnsureDefaultBackdrop` called on every scene switch — if a scene has no background, seeds the default from `Assets/`. Removed Chat skip from EnsureDefaultBackdrop. - -5. **New backdrop assets** — `ending-backdrop.jpg` and `chat-backdrop.jpg` added to Assets + csproj as ``. - -6. **SceneCatalog fix** — Chat scene display name corrected to "Chat" (was "YouTube Chat" — that name belongs to the layer/SourceType.ChatBox, not the scene). - -7. **Left panel spacing + context menu reorder** — HR margins matched, Row 4 changed from `*` to `Auto`, context menu items reordered (Border Thickness first). - -8. **Border animation rendering** — code-behind Storyboard approach via `_activeBorderAnimations` dictionary, `FindBorderShape`, `CreateBorderAnimation` for all 9 types. +2. **Polar MCP auth** — authenticated via `opencode mcp auth Polar`. Polar MCP connected. ## Files changed -- `ViewModels/MainViewModel.cs` — `ShowDesktop`, `ClearBackdropCaptureAsync`, `BackdropShowDesktop`, top bar avatar/login/status light, `RunMockChatPreviewAsync`, `StartMockChatPreview`/`StopMockChatPreview`, `SeedEndingBackdrop`/`SeedChatBackdrop`/`EnsureDefaultBackdrop`, `ShowStartStream` requires `IsConnected` -- `Models/Source.cs` — `ShowDesktop`, `ChatPreviewEnabled` -- `Models/SceneCatalog.cs` — removed Chat → "YouTube Chat" display name override -- `Services/LayoutStore.cs` — ShowDesktop column migration + read/write -- `MainWindow.xaml` — top bar status light + avatar + Log In button, Show Desktop context menu toggle -- `MainWindow.xaml.cs` — `LoadAvatarImage`, `OnViewModelPropertyChanged` handles avatar -- `Assets/ending-backdrop.jpg` — new Ending scene default backdrop -- `Assets/chat-backdrop.jpg` — new Chat scene default backdrop -- `ytLive.csproj` — new asset resources -- `Themes/Controls.xaml` — slider fix (Background="{x:Null}") +- `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 ## Build ```bash taskkill.exe /F /IM ytLive.exe # if running "/mnt/c/Program Files/dotnet/dotnet.exe" build "C:\Users\gramp\Documents\Code\projects\ytLive\ytLive.csproj" --no-restore ``` - -## Known issues -- YouTube session expired at 11:45 — need to re-sign-in via the new "Log In" button -- Build often fails when ytLive.exe is running — `taskkill.exe /F /IM ytLive.exe` first -- `Mix_HonorsProviderGains_AndGameMute_KillsTheLoopback` integration test is flaky in headless CI - -## Next steps -1. **TASK 10 + Velopack** — license validation + auto-updates (1 full session) -2. **TASK 20** — hotkeys (global keyboard shortcuts) -3. **TASK 19** — scene transitions (Cut/Fade/Move/Custom) -4. **TASK 21** — media source (video file playback) -5. **TASK 22** — audio sync offset -6. **TASK 23** — studio mode +**0 warnings, 0 errors.** ## Polar.sh resources | Resource | ID | @@ -63,9 +43,14 @@ taskkill.exe /F /IM ytLive.exe # if running | LLAMAFOUNDER | `01a8c29f-9bb8-4e96-9cbe-417a6a5cec9b` | | LLAMA50 | `8cff803e-053f-49b4-8d12-d74d468f2943` | | Checkout URL | `https://buy.polar.sh/polar_cl_ueMy8AyAClO0o0yo19IroZwKTbD587gzUsjEW3bFapw` | -| Customer-portal validate | `POST https://api.polar.sh/v1/customer-portal/license-keys/validate` | +| 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`) + ## v1 vision > A streaming tool that's intuitive and ready to go out-the-gate with nothing held back. > Minimal configuration — they're ready to go live or record. Eliminate the OBS pain-point completely. diff --git a/MainWindow.xaml b/MainWindow.xaml index 98e2f7b..06ee613 100644 --- a/MainWindow.xaml +++ b/MainWindow.xaml @@ -1456,7 +1456,7 @@ Visibility="{Binding IsAboutOpen, Converter={StaticResource BoolToVis}}"> - + - + ToolTip="Open the LlamaCasty subscription page"/> +