diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md index f2c184d6..a5f2f863 100644 --- a/.claude/CLAUDE.md +++ b/.claude/CLAUDE.md @@ -179,13 +179,39 @@ also happen to be accessible) remain in scope under their non-a11y framing. - **`develop`** is the default branch. Feature branches branch off `develop` and PR back to `develop`. -- **`main`** is the release branch. When `develop` is release-ready, open a PR - from `develop` → `main`, bump version, tag the merge commit (e.g. `0.3.0`). - The `develop` → `main` PR is a **pure formality** — everything substantial - has already been reviewed on its way into `develop`. Auto-review is - **disabled** at the workflow level on PRs targeting `main` (see - `claude-auto-review` in `.github/workflows/claude.yml`); open, then merge as - soon as the .NET build is green (the build is real CI, not review). +- **`main`** is the release branch. When `develop` is release-ready, merge + `develop` → `main` **directly** (no PR — everything was already reviewed on + its way into `develop`), then tag the merge commit (e.g. `0.6.0`). Auto-review + is **disabled** at the workflow level on PRs targeting `main` (see + `claude-auto-review` in `.github/workflows/claude.yml`) — left in place as + belt-and-braces in case a PR is ever opened. +- **Version bump** lives in `Directory.Build.props` at the repo root (single + source of truth — every `.csproj` inherits it). The .NET SDK auto-derives + `AssemblyInformationalVersionAttribute` from `` and appends + `+` when a git working copy is present. The toolbar's "v{version} · + {commit}" link reads this attribute at runtime. +- **`CHANGELOG.md`** at the repo root tracks releases in user-facing language + (see `[[feedback-changelog-user-facing]]`). The Web project stages it into + `wwwroot/` via a build target so the `/whats-new` page can fetch + render it. + +### Release workflow + +Cutting `X.Y.Z` from `develop`: + +1. **Release-prep PR to `develop`** (mandatory — never tag without it): + - Bump `` in `Directory.Build.props` to `X.Y.Z`. + - Add a new `## [X.Y.Z](https://github.com/StepKie/FantasyFootball/releases/tag/X.Y.Z) — YYYY-MM-DD` + section at the top of `CHANGELOG.md` with user-facing notes (New Features / + Bugfixes only; see the feedback memory). + - Open as a small PR to `develop` so the changelog wording gets one round of + review before it ships. +2. **Cut release**: after the prep PR merges, locally + `git checkout main && git merge --no-ff develop && git push origin main`, + then `git tag X.Y.Z` on the merge commit and `git push origin X.Y.Z`. +3. **GitHub release**: `gh release create X.Y.Z --title "X.Y.Z" --notes "..."` + pasting the matching `CHANGELOG.md` section as the notes — the `/whats-new` + page's `[X.Y.Z](.../releases/tag/X.Y.Z)` link expects a real release page, + not a bare tag. - **GitHub Pages deployment** is handled by `.github/workflows/github-pages.yml`, which auto-deploys on push to `main`. It uses the modern Pages-from-Actions artifact pattern (`actions/configure-pages` + `upload-pages-artifact` + diff --git a/.gitignore b/.gitignore index 09d852a2..50179465 100644 --- a/.gitignore +++ b/.gitignore @@ -68,3 +68,7 @@ mono_crash* # Claude Code per-user settings .claude/settings.local.json + +# CHANGELOG.md is staged into the Blazor wwwroot at build time (see Web csproj StageRepoMarkdown target). +# The canonical copy lives at the repo root; the staged copy must not be committed. +src/FantasyFootball.Web/wwwroot/CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..19d7b02c --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,29 @@ +## [0.6.0](https://github.com/StepKie/FantasyFootball/releases/tag/0.6.0) — 2026-06-02 + +### New Features + +- **Bundesliga 2025-26** ([PR #65](https://github.com/StepKie/FantasyFootball/pull/65)) — simulate the German top flight with real fixtures, real results so far, and club crests for all 18 teams. Domestic-league competitions are a new format: season-long round-robin, no group stage, no knockout. League standings show qualification bands (Champions League, Europa League, Conference League, relegation playoff, relegation) per position. +- **Premier League, Serie A, LaLiga, Ligue 1 (2025-26)** ([PR #66](https://github.com/StepKie/FantasyFootball/pull/66)) — the same treatment for four more top-flight leagues. Real fixtures and results, 78 additional club crests, 96 club Elo ratings from a single snapshot. +- **Pick the Elo set per competition** ([PR #65](https://github.com/StepKie/FantasyFootball/pull/65)) — the rating set used to simulate a competition is now chosen at competition setup instead of being driven by a single global "active" set. National-team and club rating snapshots are kept separate, so the picker only shows compatible options. +- **Per-team rating history on the Teams page** ([PR #65](https://github.com/StepKie/FantasyFootball/pull/65)) — opening a team now shows a table of every stored rating snapshot that covers it (date, rank, Elo), instead of a single value. +- **"Restore historical competitions" button in Settings** ([PR #65](https://github.com/StepKie/FantasyFootball/pull/65)) — explicit, idempotent restore of bundled World Cup / Euro tournaments after a database reset. Replaces the previous behaviour of implicitly seeding them on first visit to the Competitions page. + +### Bugfixes + +- **Bundled historical competitions simulate correctly after a reset** ([PR #65](https://github.com/StepKie/FantasyFootball/pull/65)) — restoring the historical World Cup / Euro tournaments used to leave them without a rating set assigned, which made any sim refuse to run. Each bundled tournament now ships with its rating set pinned to the matching year. + +## [0.5.0](https://github.com/StepKie/FantasyFootball/releases/tag/0.5.0) — 2026-05-30 + +See the [compare view](https://github.com/StepKie/FantasyFootball/compare/0.4.0...0.5.0). + +## [0.4.0](https://github.com/StepKie/FantasyFootball/releases/tag/0.4.0) — 2026-05-24 + +See the [compare view](https://github.com/StepKie/FantasyFootball/compare/0.3.1...0.4.0). + +## [0.3.1](https://github.com/StepKie/FantasyFootball/releases/tag/0.3.1) — 2026-05-20 + +See the [compare view](https://github.com/StepKie/FantasyFootball/compare/0.3.0...0.3.1). + +## [0.3.0](https://github.com/StepKie/FantasyFootball/releases/tag/0.3.0) — 2026-05-20 + +First tagged release. diff --git a/Directory.Build.props b/Directory.Build.props new file mode 100644 index 00000000..bcc553ad --- /dev/null +++ b/Directory.Build.props @@ -0,0 +1,16 @@ + + + + + 0.6.0 + + + diff --git a/src/FantasyFootball.UI/FantasyFootball.UI.csproj b/src/FantasyFootball.UI/FantasyFootball.UI.csproj index fff9b77a..2bfbb0d8 100644 --- a/src/FantasyFootball.UI/FantasyFootball.UI.csproj +++ b/src/FantasyFootball.UI/FantasyFootball.UI.csproj @@ -14,6 +14,7 @@ + diff --git a/src/FantasyFootball.UI/Layout/MainLayout.razor b/src/FantasyFootball.UI/Layout/MainLayout.razor index 8eb6cf55..a2b433f3 100644 --- a/src/FantasyFootball.UI/Layout/MainLayout.razor +++ b/src/FantasyFootball.UI/Layout/MainLayout.razor @@ -1,4 +1,5 @@ @inherits LayoutComponentBase +@using System.Reflection @@ -17,6 +18,11 @@ FantasyFootball + + + v@_version · @_commit + + @code { + static readonly string _versionFull = Assembly.GetExecutingAssembly().GetCustomAttribute()?.InformationalVersion ?? "unknown"; + static readonly string[] _parts = _versionFull.Split("+"); + readonly string _version = _parts[0]; + // CI-published builds suffix the commit hash with '+abc1234…'; local debug builds may not. + readonly string _commit = _parts.Length > 1 ? _parts[1][..Math.Min(7, _parts[1].Length)] : "dev"; + bool _drawerOpen = true; bool _isDarkMode = true; diff --git a/src/FantasyFootball.UI/Pages/WhatsNew.razor b/src/FantasyFootball.UI/Pages/WhatsNew.razor new file mode 100644 index 00000000..2e33f914 --- /dev/null +++ b/src/FantasyFootball.UI/Pages/WhatsNew.razor @@ -0,0 +1,65 @@ +@page "/whats-new" +@inject HttpClient Http +@implements IDisposable +@using System.Text.RegularExpressions +@using Markdig + +What's new — Fantasy Football + +What's new + + Release history for Fantasy Football. The full source of this page is + CHANGELOG.md + on GitHub. + + +@if (_error is not null) +{ + + Couldn't load release notes: @_error + +} +else if (_html is null) +{ + +} +else +{ + + @((MarkupString)_html) + +} + +@code { + static readonly Markdig.MarkdownPipeline _pipeline = + new Markdig.MarkdownPipelineBuilder().UseAutoLinks().DisableHtml().Build(); + + // Rewrites external anchors to open in a new tab so SPA navigation isn't hijacked. + static readonly Regex _externalLinkPattern = new(@" + + + + + diff --git a/src/FantasyFootball.Web/Program.cs b/src/FantasyFootball.Web/Program.cs index c6237615..191e20d5 100644 --- a/src/FantasyFootball.Web/Program.cs +++ b/src/FantasyFootball.Web/Program.cs @@ -29,6 +29,7 @@ builder.Services.AddMudServices(); builder.Services.AddBlazoredLocalStorage(); +builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); diff --git a/src/FantasyFootball.Web/wwwroot/app.css b/src/FantasyFootball.Web/wwwroot/app.css index e8925c08..f3194a3f 100644 --- a/src/FantasyFootball.Web/wwwroot/app.css +++ b/src/FantasyFootball.Web/wwwroot/app.css @@ -502,3 +502,38 @@ body { font-variant-numeric: tabular-nums; font-weight: 400; } + +/* What's new / CHANGELOG.md rendering. Global rather than scoped because the MudPaper wrapper's + root
doesn't pick up Blazor CSS isolation's scope attribute, so ::deep selectors miss + the Markdig-generated descendants. */ +.changelog-content h2 { + margin-top: 2.25rem; + margin-bottom: 0.5rem; + font-size: 1.5rem; + font-weight: 600; +} +.changelog-content h2:first-of-type { margin-top: 0; } +.changelog-content h3 { + margin-top: 1.5rem; + margin-bottom: 0.5rem; + font-size: 1.1rem; + font-weight: 600; + opacity: 0.85; +} +.changelog-content p { margin: 0.5rem 0; } +.changelog-content ul { + padding-left: 1.5rem; + margin-top: 0.25rem; + margin-bottom: 0.75rem; +} +.changelog-content li { margin: 0.5rem 0; } +.changelog-content a { + color: var(--mud-palette-primary); + text-decoration: none; +} +.changelog-content a:hover { text-decoration: underline; } +.changelog-content code { + padding: 0 0.25rem; + background: rgba(127, 127, 127, 0.15); + border-radius: 3px; +}