From 3a1484b3c59382de78f0f117cf1a109736f62c54 Mon Sep 17 00:00:00 2001 From: gramps Date: Wed, 26 Aug 2026 06:48:14 -0700 Subject: [PATCH] TASK 20 step 2: hotkey config UI + modifier chord persistence + conflict detection --- HANDOFF.md | 25 ++-- HotkeyConfigDialog.xaml | 103 +++++++++++++ HotkeyConfigDialog.xaml.cs | 69 +++++++++ MainWindow.xaml.cs | 3 +- Services/GlobalHotkeys.cs | 75 +++++++--- Services/HotkeyChord.cs | 117 +++++++++++++++ Services/LayoutStore.cs | 51 +++++++ TASKS.md | 21 +-- ViewModels/HotkeyConfigViewModel.cs | 217 ++++++++++++++++++++++++++++ ViewModels/MainViewModel.cs | 29 ++++ ytLive.Tests/GlobalHotkeyTests.cs | 2 +- ytLive.Tests/HotkeyConfigTests.cs | 100 +++++++++++++ 12 files changed, 770 insertions(+), 42 deletions(-) create mode 100644 HotkeyConfigDialog.xaml create mode 100644 HotkeyConfigDialog.xaml.cs create mode 100644 Services/HotkeyChord.cs create mode 100644 ViewModels/HotkeyConfigViewModel.cs create mode 100644 ytLive.Tests/HotkeyConfigTests.cs diff --git a/HANDOFF.md b/HANDOFF.md index 5d2d52c..6ff5a7f 100644 --- a/HANDOFF.md +++ b/HANDOFF.md @@ -1,7 +1,7 @@ # HANDOFF — Session State ## Branch -**`main`** @ `1009800`, committed LOCALLY, working tree clean. **NOT pushed — user rule: never +**`main`** @ `89ab83b`, committed LOCALLY, working tree clean. **NOT pushed — user rule: never push without explicit instruction (2026-08-24).** No feature branches pre-1.0: all work lands on `main` per work unit. @@ -13,10 +13,18 @@ push without explicit instruction (2026-08-24).** No feature branches pre-1.0: a - ON-AIR sign right of REC — gray offline / red public / green private (`OnAirBrush`), shared `StatusSignText` style. Test `OnAirSignTests`. - **Broadcast metadata pull-out + launch geometry** (`b988e6d`) — white "Text" tab on preview's right edge slides out the broadcast form: Title, Description, Tags csv, Visibility, Made-for-Kids, read-only Scheduled Start. Edits persist to Settings `Broadcast.*` immediately; Update Broadcast PUTs `liveBroadcasts?part=snippet,status` via new `YouTubeStreamService.UpdateBroadcast`. Go Live prefills from the form + captures back what was inserted. Old Default Stream Title/Description removed from App Settings (properties deleted). Window: default 1920×1040, MinWidth 1366, MinHeight 768, Manual startup, size+position persisted on close and restored clamped. Test `BroadcastPullOutTests`. - **Bug-fix pass 1** (`e6baa55` + `d9e352f`): Text tab **always visible** (creator revised the Live-only gating same day); tab **toggles** the drawer; **click-away collapses it** (`Window_PreviewMouseLeftButtonDown` checks `TextPullOutHost` ancestry); avatar initial-letter fallback added. -- **Bug-fix pass 2** (`1009800`, UNPUSHED): real crash fix + avatar root cause: - - Logo/text-Run crash: first fix was WRONG (`parent ?? LogicalTreeHelper.GetParent` — unreachable, because `VisualTreeHelper.GetParent(Run)` throws before `??`). Real fix in `IsDescendantOf`: type-guard `child is Visual or Visual3D` BEFORE choosing visual vs logical helper. - - Avatar showed "L" because WPF's BitmapImage downloader failed with "This Freezable cannot be frozen". Now `HttpClient.GetByteArrayAsync` → `MemoryStream` → `StreamSource` + OnLoad → `Freeze()` (`MainWindow.LoadAvatarImage`, static `AvatarHttp`). Initial fallback only for empty URL / failed download; failures log to startup.log. -- Suite: 228 total, 227 pass — only failure is the known pre-existing `AudioPipelineTests.Mix_HonorsProviderGains_AndGameMute_KillsTheLoopback`. Build 0 warnings. +- **Bug-fix pass 2** (`1009800`): real crash fix + avatar root cause. +- **Update Broadcast button** (`89ab83b`): diff-before-push, offline=local only, live=API; Go Live empty-field prompt opens Text drawer. +- **TASK 20 step 2** (this session): hotkey config UI + persistence: + - `Services/HotkeyChord.cs` — modifier+key chord record with display/parse/FromWpf helpers + - `Services/GlobalHotkeys.cs` — modifier param on `IHotkeyRegistrar`, custom bindings dict, `Reattach()` for hot-swap + - `Services/LayoutStore.cs` — `LoadHotkeyBindings()` / `SaveHotkeyBindings()` (Settings table, no migration) + - `ViewModels/HotkeyConfigViewModel.cs` — working copy, capture mode, conflict detection, unbind, reset + - `HotkeyConfigDialog.xaml` + `.cs` — click-to-capture, Unbind per row, Reset/Save/Cancel + - `ViewModels/MainViewModel.cs` — loads bindings, `OpenHotkeyConfigCommand`, `HotkeyBindingsChanged` callback + - `MainWindow.xaml.cs` — passes bindings to manager, re-attaches on dialog save + - Tests: `HotkeyConfigTests` (round-trip, display string, storage serialization) +- Suite: 231 total, 230 pass — only failure is the known pre-existing `AudioPipelineTests.Mix_HonorsProviderGains_AndGameMute_KillsTheLoopback`. Build 0 warnings. ## ⚠️ Landmines @@ -38,10 +46,9 @@ push without explicit instruction (2026-08-24).** No feature branches pre-1.0: a - **Dead code flagged, not touched:** `ShowWebcamCommand` / `CanShowWebcamInStagedScene` wired but unbound — audit item. ## Next steps -1. **QUEUED (explicit, do this first next session): the red button at the bottom of the Text drawer ("Update Broadcast", YtButton style) must ALSO close the drawer when clicked** — so the drawer closes three ways: tab click (toggle), off-form click-away, and that bottom button. Creator confirmed meaning after I wasted time asking ("what else would you call the red button at the bottom of the form"). Implementation hint: extend `CloseDrawerCommand` usage or have the button's click set `IsDrawerOpen = false` after `UpdateRemoteAsync` completes (creator's wording: "put code behind the event button" = wire the dismiss behavior; keep Update's remote push as-is). Add/extend test in `BroadcastPullOutTests`. -2. Verify unpushed fixes on next launch: logo click opens About (no crash); avatar shows the real profile image (startup.log free of `Avatar load failed`); then PUSH pending creator go-ahead. -3. Continue queue: TASK 20 step 2 (hotkey config UI + persistence), TASK 17 web source, TASK 18 local recording, TASK 21 media source. -4. Loose ends: dead `ShowWebcamCommand`, AudioPipelineTests failure, Velopack update URL. +1. Verify unpushed fixes on next launch: logo click opens About (no crash); avatar shows the real profile image (startup.log free of `Avatar load failed`); then PUSH pending creator go-ahead. +2. Continue queue: TASK 17 web source, TASK 18 local recording, TASK 21 media source, TASK 22 audio sync offset. +3. Loose ends: dead `ShowWebcamCommand`, AudioPipelineTests failure, Velopack update URL. ## Build ```bash diff --git a/HotkeyConfigDialog.xaml b/HotkeyConfigDialog.xaml new file mode 100644 index 0000000..bb5bdc5 --- /dev/null +++ b/HotkeyConfigDialog.xaml @@ -0,0 +1,103 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +