Cross-cutting polish that surfaced while Phase 8 was being implemented:
* Invariant-culture formatting on every decimal ToString("0.00" / "0.##")
(OddsCell, OddsTimeline, SeverityBadge, AnomalyEvidence,
Pages/Events/Detail) — previously the comma/dot decimal separator
switched with the locale and broke tabular alignment + tests.
* LocaleState constructor no longer mutates process-wide ambient culture;
apply only happens through Set(...). Stops parallel bUnit test runs
leaking ru-RU into each other's threads.
* MainLayout: drawer-open state now offsets main content / footer / appbar
by the drawer width (248px) so the sidebar no longer overlaps content.
Mobile breakpoint (≤720px) keeps the original full-width layout.
* wwwroot/index.html (Marathon.UI RCL): switched from Plotly CDN to the
bundled "_content/Plotly.Blazor/plotly-2.35.3.min.js" — works offline
and matches the Plotly.Blazor 5.4.1 version pin.
* Marathon.Hosts.WpfBlazor/wwwroot/index.html: host-level page that
BlazorWebView's HostPage attribute resolves to. Same Plotly bundle,
no autostart="false" (BlazorWebView auto-starts).
Three fixes surfaced when launching the WPF host for the first time:
1. App.xaml.cs — call MarathonDbContextInitializer.InitializeAsync()
between Host.Build() and Host.Start() so EF migrations + WAL pragma
are applied BEFORE BackgroundServices race to query the DB. Without
this, all pollers crashed on 'no such table: Events'.
2. wwwroot/index.html — added <script src='https://cdn.plot.ly/plotly-2.35.2.min.js'>
before blazor.webview.js. Phase 6 reviewer flagged this for Phase 9,
but charts are unrenderable without it; better to ship now.
3. Migrations/20260505000000_InitialCreate.cs — added [DbContext] and
[Migration('20260505000000_InitialCreate')] attributes. Phase 2's
hand-written migration was missing both, so EF saw 'no migrations to
apply' even on a fresh DB. With the attributes, the migration runs
on first launch and creates all tables (Events, Snapshots, Bets,
EventResults, Anomalies, Sports, Leagues).
Verified: clean DB → migration applied → all 7 tables created → pollers
run with empty results (no data yet — UpcomingEventsPoller fires every 6h
by default; first scrape will populate the DB).