The creator (2026-10-09): 'we need some sort of log-rotation so that we don't inadvertently fill up a user's drive.' startup.log was uncapped - 23MB and growing every few seconds, because FfmpegEncoder logs every non-progress ffmpeg stderr line. The policy lives in a new Helpers/LogRotation.cs as a pure function over a path (RollIfNeeded(logPath, maxBytes, retainedFiles)), so AppLog's 156 call sites keep their surface and the rule is testable on its own. At 5MB the live file rolls to startup.1.log and one rolled copy is kept - the FILE SET is bounded, not just the live file, so a long stream cannot grow the log without limit. A failed rotation degrades to the old behaviour instead of taking the app down. Pattern reference (cited): Serilog's file sink pairs fileSizeLimitBytes (the cap) with retainedFileCountLimit (how many rolled files to keep) - https://github.com/serilog/serilog-sinks-file Guard: ytLive.Tests/AppLogRotationTests.cs - the ONE test drives the real AppLog.Write against a temp dir (two internal seams: PathOverride, MaxBytes) and asserts the roll, the fresh live file, and that after a SECOND roll the directory still holds exactly startup.log + startup.1.log. Also fixed a stale catalog row: TASK 12 (master limiter) shipped 2026-08-15 and was still marked 'Queued'.
3.4 KiB
3.4 KiB
Helpers — index
Cross-cutting utilities. See schema.md for the memory-map conventions.
| File | Purpose |
|---|---|
ViewModelBase.cs |
INotifyPropertyChanged + SetProperty<T>() — base for all ViewModels |
RelayCommand.cs |
ICommand implementation for all button actions |
AppLog.cs |
File logger to %APPDATA%\ytLlive\startup.log; startup checkpoints + unhandled-exception capture (the crash this map is named for). Size-capped: rolls to startup.1.log at 5 MB (TASK 55) |
LogRotation.cs |
The size-capped-rotation policy as a pure function over a path: roll startup.log → startup.1.log when it passes the cap, keeping N rolled copies. Separate from AppLog so the logger's 156-call-site surface never changes and the rule is testable alone (TASK 55) |
FocusPreservingListBox.cs |
ListBox subclass that keeps selection/focus coherent when the selected item is deleted; IsSelectable predicate skips hidden scenes; drag-reorder guard |
OAuthCredentials.cs |
Baked-in Google OAuth client ID/secret (desktop app; loopback callback) |
TokenStore.cs |
DPAPI-protected OAuth session persistence (%APPDATA%\ytLlive\ytLlive.auth, CurrentUser scope); Save/Load/Clear — sign-in survives restarts |
ImageCache.cs |
Image byte caching (assets live in the DB) |
InverseBoolToVisibilityConverter.cs / NotNullToVisibilityConverter.cs |
XAML value converters for visibility bindings |
AllTrueToVisibilityConverter.cs |
Multi-value AND-gate → Visible; gates the layer-row capture menu items on row-IsBackground + staged-scene-is-Live (TASK 25) |
CameraConflictProbe.cs |
Best-effort diagnostic: when a camera won't start, enumerates running processes known to hold cameras (OBS, Zoom, Teams, NVIDIA Broadcast, browsers, etc.) — Windows doesn't expose "which process has this device" via any public API, so this is a suspect list, not a verdict |
StripPool.cs |
Four DEDICATED background threads at BelowNormal priority that split one composite into horizontal row strips (the caller runs a strip too); started lazily, runs inline below 64 rows. UNIT 5 — do NOT demote the ThreadPool from Parallel.For, it leaks into unrelated work (see ai.md "Render budget") |
FrameCadence.cs |
Pure arrival-cadence counter (FrameCadence + FrameCadenceSummary.Describe): frames, gaps, hitches, longest/avg gap for a window of a timestamped stream. Fed by MediaCaptureFrameSource (camera stamps vs our arrivals) and FramePump (distinct webcam Epochs that reached the output) — UNIT 0, see ai.md "Frame cadence" |
HexToBrushConverter.cs |
A "#RRGGBB" hex string → SolidColorBrush (transparent on bad input), so a template can paint a colour swatch straight from the palette. Registered in the theme as HexToBrush — the theme paints the colour, never the UI code (TASK 63) |
ClosedBoxTemplateSelector.cs |
An ItemTemplateSelector that gives a ComboBox's CLOSED box a different template from its drop-down rows: WPF calls it with the ContentPresenter's templated parent — the ComboBox for the closed box, the ComboBoxItem for a row. The only way to tell them apart (SelectionBoxItemTemplate is read-only and mirrors ItemTemplate) — TASK 63 |
Related: Themes/Controls.xaml styles the lists this
class backs; Models/index.md and
ViewModels/index.md for the objects it works on.