feat(phase-6): event browsing UI — pre-match/live lists, detail page, +26 bUnit tests
Replaces PreMatch/Live placeholder pages with a shared EventListShell
(filter chips, date range, sortable virtualized-friendly table, debounced
search, live auto-refresh with odds-movement indicators) and adds a new
/events/{eventCode} detail page (asymmetric header lockup, dynamic
Match/Period tabs, Plotly.Blazor odds-over-time chart with accessible
data-table fallback, snapshot history, Excel export modal).
New primitives matching Phase 5's editorial-quant system:
- SportIcon: inline SVGs per sport (basketball=6, football=11,
tennis=22723, hockey=43658, generic fallback)
- OddsCell: tabular mono with ▲/▼/— delta + flash on change
(prefers-reduced-motion honored)
- OddsTimeline: Plotly.Blazor wrapper with theme-aware colors and
<details>/<summary> data-table screen-reader fallback
- ExportDialog: From/To pickers + ExportKind radio + Esc/Enter
keyboard, surfaces use-case errors inline
- EventListShell: shared section shell for PreMatch/Live cadence
State + service split keeps the RCL host-agnostic:
- IEventBrowsingService / EventBrowsingService — wraps repos, returns
view-model records (EventListItem, EventDetail, EventScopeBoard,
BetRow, OddsTimelinePoint, SnapshotHistoryEntry); pages never see
EF or domain entities directly.
- EventBrowsingState — singleton (per-circuit in BlazorWebView) holding
immutable PageFilter records for PreMatch and Live.
Plotly.Blazor 5.4.1 added (latest .NET 8 line; 7.x has breaking changes).
+59 RU/EN localization keys following the Phase 5 dot-segmented convention.
Tests: +26 bUnit tests (PreMatch/Live/Detail pages, OddsCell/SportIcon/
ExportDialog components, EventBrowsingState). Total 228/228 passing
(Domain 96 + Application 15 + Infrastructure 80 + UI 37; baseline 202).
Build clean (0/0).
PLAN.md: P2/P3/P5 top-level checkboxes ticked; P6 row marked Done.
This commit is contained in:
@@ -85,7 +85,7 @@ with scraping research, no implementation.
|
||||
| Phase 3 | phase-implementer | Sonnet 4.6 | ⏭️ Skipped (Big Bang) | ✅ With 2 + 5 | — |
|
||||
| Phase 4 | phase-implementer | Sonnet 4.6 | ⏭️ Skipped (Big Bang) | — | ✅ Done 2026-05-05. 4 use cases, 3 BackgroundService pollers, InfrastructureModule, ApplicationModule, reflection wiring removed. 202/202 tests green (+17 new). |
|
||||
| Phase 5 | phase-implementer-frontend | Opus | ⏭️ Skipped (Big Bang) | ✅ With 2 + 3 | Uses frontend-design skill |
|
||||
| Phase 6 | phase-implementer-frontend | Opus | ⏭️ Skipped (Big Bang) | — | Uses frontend-design skill |
|
||||
| Phase 6 | phase-implementer-frontend | Opus | ⏭️ Skipped (Big Bang) | — | ✅ Done 2026-05-05. PreMatch + Live + Events/Detail pages, EventListShell, SportIcon, OddsCell, OddsTimeline (Plotly.Blazor wrap), ExportDialog. EventBrowsingState + IEventBrowsingService facade. RU+EN strings under PreMatch.* / Live.* / Detail.* / Export.* / Sport.*. 228/228 tests green (+26 new bUnit). |
|
||||
| Phase 7 | phase-implementer (split if needed) | Sonnet/Opus | ⏭️ Skipped (Big Bang) | — | UI portion uses Opus |
|
||||
| Phase 8 | phase-implementer (split if needed) | Sonnet/Opus | ⏭️ Skipped (Big Bang) | — | UI portion uses Opus |
|
||||
| Phase 9 | phase-implementer | Sonnet 4.6 | ✅ Final phase tests | — | Full build + test enforced |
|
||||
@@ -101,6 +101,49 @@ with scraping research, no implementation.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
### Phase 6 (Event browsing UI, 2026-05-05)
|
||||
|
||||
- **Plotly.Blazor pinned to 5.4.1.** v7.x exists but introduces breaking changes;
|
||||
5.4.1 is the latest on the .NET 8 line and works with our existing MudBlazor
|
||||
7.15.0 / .NET 8.0.12 stack. The `Plotly.Blazor.LayoutLib.Margin` type clashes
|
||||
with `MudBlazor.Margin` — fully qualify the layout-side type.
|
||||
- **Razor source generator does NOT accept C# 11 raw string literals (`"""…"""`)**
|
||||
inside `@code` blocks. The parser sees the leading `"""` as the start of a
|
||||
normal string and never finds the close, producing an "Unterminated string
|
||||
literal" RZ1000. Use concatenated single-quoted attribute strings instead
|
||||
(see `SportIcon.razor` SVG constants).
|
||||
- **Razor reserves the identifier `code`.** A `@foreach (var code in ...)`
|
||||
loop is parsed as the `@code` directive, not as iteration. Use any other
|
||||
identifier (`var sportCode in ...`).
|
||||
- **`MudBlazor.DateRange` shadows `Marathon.Application.Storage.DateRange`**
|
||||
in any file whose `_Imports.razor` brings both namespaces in. Add
|
||||
`using AppDateRange = Marathon.Application.Storage.DateRange;` per-file
|
||||
where the application's `DateRange` is constructed (already done in
|
||||
`ExportDialog.razor` and `ExportDialogTests.cs`).
|
||||
- **EventBrowsingService is Scoped, EventBrowsingState is Singleton.** The
|
||||
service captures the per-circuit DI scope so EF Core's `DbContext` lifetime
|
||||
works correctly; the state object holds the per-page filter records and
|
||||
fires `OnChange` only when the new value !equals the old one. This split
|
||||
matches Phase 5's split between `ThemeState` (singleton) and per-circuit
|
||||
data services.
|
||||
- **View-models, not domain entities, cross the UI boundary.** Pages bind to
|
||||
`EventListItem` / `EventDetail` / `BetRow` / `OddsTimelinePoint`
|
||||
records (defined in `Marathon.UI.Services`). Repositories are not exposed
|
||||
to Razor components. This keeps the UI free of EF tracked graphs and
|
||||
preserves Phase 5's "RCL is host-agnostic" invariant.
|
||||
- **Live page reads polling cadence from `IOptionsMonitor<ScrapingSettingsForm>`.**
|
||||
Phase 4's `WorkerOptions.LivePollIntervalSeconds` (drives the poller) is a
|
||||
separate setting from the UI's display refresh; the latter intentionally
|
||||
follows `Scraping:PollingIntervalSeconds` per the Phase 6 subplan.
|
||||
- **Plotly chart memoization.** Computed signature = `(count, first ticks,
|
||||
last ticks, first/last rate triples)`. Sufficient to invalidate the trace
|
||||
list on any meaningful change while staying cheap during live polling.
|
||||
- **bUnit shared `MarathonTestContext` now registers the fake browsing service
|
||||
and the browsing state.** Phase 7 tests can extend it directly or follow the same pattern.
|
||||
`Support/TestData.MoscowToday(int hour)` produces correctly-offset
|
||||
`DateTimeOffset` values — domain `Event.ScheduledAt` will reject any other
|
||||
offset.
|
||||
|
||||
### Phase 1 (Solution skeleton + Domain model, 2026-05-05)
|
||||
|
||||
- **.NET 10 SDK creates `.slnx` by default.** `dotnet new sln` produces `Marathon.slnx`
|
||||
|
||||
Reference in New Issue
Block a user