feat(settings): make slide-out alerts persist until dismissed

Add a persisted global App Settings switch. Off keeps the existing per-severity
lifetimes (errors remain sticky); on makes every alert non-expiring and closes
it when its body is clicked. The NotificationService supplies the current
setting to each toast request.

StickyNotificationIntegrationTests covers persistence and both request modes.
618 tests green, clean build with 0 warnings.

Reference: Notification.Wpf exposes a CloseOnClick request option:
https://github.com/G-Stas/Notification.Wpf
This commit is contained in:
2026-11-11 10:19:20 -08:00
parent ff5c005bd9
commit 9c0c40c688
11 changed files with 125 additions and 18 deletions
+3 -1
View File
@@ -159,7 +159,7 @@ The app is a **director's control surface**, not an editor. This replaces the OB
**Current layout (2026-10-26 redesign commits):** `MainWindow` has a top bar and health banner, then a two-column work area with a fixed 360px left column and preview column. Its top row holds stream readouts/resolution on the left and the five-scene strip above the preview; the lower row holds the left panel and central preview. The bottom bar is now only a height-preserving spacer.
- **Left panel:** `EditingActive` is an explicit `EDIT` toggle, not a mode inferred from `StreamStatus`. On = scene layers + properties; off = a two-tab panel with **Live Chat** first/default and **Logs** second. Logs is a read-only view of up to 500 `AppLog` entries captured in this process; it does not load older `startup.log` contents or change file logging/rotation. Entries wrap and use the theme's type colors. Explicit Class 1/2 severity is independent of message type/source: Class 1 sorts above Class 2 in the pinned group; acknowledging unpins the entry but retains it in the chronological list. The staged scene is the preview/edit target; `LiveScene` is the program output.
- **Settings is a right-edge drawer, not a modal (2026-11-01):** the gear rail tab opens `Controls/SettingsDrawer` in the same animated-width stack as Stream Settings / YPP / Socials / TEST (`SettingsDrawerOpen`, exclusive with the other four). Categories are section headings with their controls indented under them — Zoom holds *Zoom view shape* and *Zoom box follows*. TASK 37 adds **Restore staged scene** and **Restore all scenes** under Layout defaults; both are disabled while live or recording, and restoring a layout preserves the selected webcam. App Settings also controls slide-out toast placement (six left/right × top/center/bottom positions; bottom-right default). The app menu (Report Bug / Feature Request / About) hangs off a **right-click on the LlamaCasty wordmark**; left-click still opens About. The modal shell keeps only Bug / Feature / About, which is why it no longer needs a scroll viewer.
- **Settings is a right-edge drawer, not a modal (2026-11-01):** the gear rail tab opens `Controls/SettingsDrawer` in the same animated-width stack as Stream Settings / YPP / Socials / TEST (`SettingsDrawerOpen`, exclusive with the other four). Categories are section headings with their controls indented under them — Zoom holds *Zoom view shape* and *Zoom box follows*. TASK 37 adds **Restore staged scene** and **Restore all scenes** under Layout defaults; both are disabled while live or recording, and restoring a layout preserves the selected webcam. App Settings controls slide-out toast placement (six left/right × top/center/bottom positions; bottom-right default) and has a sticky mode that keeps all notifications until clicked or closed. The app menu (Report Bug / Feature Request / About) hangs off a **right-click on the LlamaCasty wordmark**; left-click still opens About. The modal shell keeps only Bug / Feature / About, which is why it no longer needs a scroll viewer.
- **Top bar:** the `EDIT` toggle sits beside the REC/ON-AIR session controls; the brand and account surfaces remain at the sides. Its background uses the idle surface color when stopped. Normal ON-AIR stays `Brush.Primary` red while connecting and turns `Brush.Positive` green only after YouTube reports the broadcast `live`; stopping restores the idle color. TEST stays gold for the entire test session. Its **right edge aligns with the layers panel below** (`TopBar` column 0 is a fixed **352** = work-area margin 8 + 360px column − the bar's own 16px padding), so the button and the panel share an edge. The **settings gear moved to the preview rail** (2026-11-01): first tab, orange (`Brush.Attention`), gear icon only — left-click opens the app menu (Settings / Bug / Feature / About), right-click goes straight to App Settings.
- **Preview gestures (2026-11-01):** *edit mode* — left selects/drags/resizes, right opens the context menu, **Ctrl+right toggles the zoom box**. *Preview mode* — **left toggles the zoom box**, right opens the context menu; **Ctrl+right does nothing at all** (no chord while live or recording — one hand, always). ⚠️ **Both the Down and the Up event must be handled for a Ctrl+right-click**: WPF's `PopupControlService` opens a context menu on right-button-**up**, so suppressing only the down event leaves the menu appearing under the gesture (found 2026-11-01 in edit mode). Menus exist on the webcam layer and the backdrop; every other layer right-clicks through to the backdrop menu. Left-dragging with the zoom box open moves it, in both modes. **Ctrl-down closes it**: pressing Ctrl while the box is open dismisses it, so the same hand that opened it can put it away (`MainWindow_PreviewKeyDown`; the box cannot cancel itself on creation because Ctrl is already down before it exists). **Follow mode** is a Settings pill, default **off = Anchor**: with Follow on, dragging re-anchors the capture to the box centre (`ZoomFollowsBox`, persisted as the `Zoom.FollowBox` setting). **The gesture activates the box at the cursor**, not merely dismisses it: `ActivateZoom` no longer refuses when idle (it used to return unless `IsLive || IsRecording`, and `ToggleZoom` only ever dismissed — so the gesture did nothing at all outside a session until 2026-11-01). The social bar consumes its own click (toggle top ⇄ bottom), and the preview's tunnelling handler steps aside for it — otherwise the bar is unclickable in preview mode and drags the layer beneath it in edit mode.
- **Preview undo/redo (2026-11-01):** `Ctrl+Z` / `Ctrl+Shift+Z`, window-level bindings, live only while `EditingActive` — undo with the preview showing program output would rewrite a scene nobody is looking at. `Models/PreviewHistory` snapshots element **references** plus geometry, visibility, order and membership, so move / resize / reorder / add / remove / hide all undo in one step per gesture (`PushPreviewUndo` is called once at drag or resize start, never inside the move loop). Property edits (colour, opacity, border, font) are **not** captured yet — stated in the type's doc comment so nobody assumes otherwise. The stack clears on layout load. Guard: `ytLive.Tests/PreviewHistoryTests.cs`.
@@ -212,6 +212,8 @@ MIT — notice #10 in `THIRD-PARTY-NOTICES.txt`). Facts that cost a hunt:
- Lifetimes: Info/Success ~4s, Warning ~8s auto-dismiss, Error sticky until dismissed (the pure mapping
lives in `NotificationService.BuildRequest` + the `Cards` tint table: slate info `#3a3b52`, green
success `#1d5c38`, amber warning `#b8860b`, dark-red error `#8f1f1f` — matching the health-banner language).
- Global App Settings `StickyAlertsUntilDismissed` changes every severity to no-expiration + body-click-to-close;
off preserves those defaults. The preference persists in the profile `Settings` table.
- Calls marshal to the captured Dispatcher (`_dispatcher.Invoke`) — encoder/pump events fire off-thread.
Migrated MessageBoxes: both webcam-acquire warnings, image-read warning, sign-in unsuccessful/failed.