Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 33 additions & 7 deletions .claude/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 `<Version>` and appends
`+<git-sha>` 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 `<Version>` 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` +
Expand Down
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -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
29 changes: 29 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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.
16 changes: 16 additions & 0 deletions Directory.Build.props
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<Project>

<PropertyGroup>
<!--
Single source of truth for the project version. All .csproj files in the solution
inherit this. MSBuild auto-derives AssemblyVersion / FileVersion / InformationalVersion
from <Version>, so reading via Assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>()
gives the same string back (with a +<git-sha> suffix appended automatically when the
build sees a git working copy).

Bump this on each release; do not put <Version> in individual .csproj files.
-->
<Version>0.6.0</Version>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FantasyFootball.Maui.csproj still has <Version>$(ApplicationDisplayVersion)</Version> (line 23), which evaluates to 0.5.0 — it will override this inherited value because a property set inside the project file takes precedence over Directory.Build.props. Both the MAUI assembly stamp and ApplicationDisplayVersion stay at 0.5.0 while every other project picks up 0.6.0. Needs to be updated for each release along with Directory.Build.props.

</PropertyGroup>

</Project>
1 change: 1 addition & 0 deletions src/FantasyFootball.UI/FantasyFootball.UI.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="Markdig" Version="1.0.0" />

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Build-breaking: Markdig 1.0.0 does not exist on NuGet.

Markdig (by xoofx) uses 0.x.y versioning — the latest stable release is in the 0.40.x range. There is no 1.0.0 on NuGet. dotnet restore will fail to resolve the package, blocking every build of this project (and Web/MAUI which reference it).

Suggested change
<PackageReference Include="Markdig" Version="1.0.0" />
<PackageReference Include="Markdig" Version="0.40.0" />

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❌ Skipped — the claim is wrong. NuGet's flatcontainer API for markdig lists 1.0.0-preview.1, 1.0.0, 1.0.1, 1.1.0-1.1.3, 1.2.0 as stable releases (https://api.nuget.org/v3-flatcontainer/markdig/index.json). Markdig did spend years on 0.x versioning but went 1.0 in mid-2025. Our build resolves the package against nuget.org without overrides — staying on 1.0.0 for parity with MtgCsvHelper.

<PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="10.0.8" />
<PackageReference Include="MudBlazor" Version="9.4.0" />
<PackageReference Include="Serilog" Version="4.3.0" />
Expand Down
12 changes: 12 additions & 0 deletions src/FantasyFootball.UI/Layout/MainLayout.razor
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
@inherits LayoutComponentBase
@using System.Reflection

<MudThemeProvider Theme="_theme" IsDarkMode="_isDarkMode" />
<MudPopoverProvider />
Expand All @@ -17,6 +18,11 @@
FantasyFootball
</span>
<MudSpacer />
<MudTooltip Text="What's new — release notes for this version and earlier">
<MudLink Href="whats-new" Color="Color.Inherit" Underline="Underline.None" Class="d-none d-sm-flex mx-3">
<MudText Typo="Typo.caption">v@_version · @_commit</MudText>
</MudLink>
</MudTooltip>
<MudIconButton Icon="@Icons.Material.Filled.HelpOutline"
Color="Color.Inherit"
Href="about"
Expand All @@ -36,6 +42,12 @@
</MudLayout>

@code {
static readonly string _versionFull = Assembly.GetExecutingAssembly().GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.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;

Expand Down
65 changes: 65 additions & 0 deletions src/FantasyFootball.UI/Pages/WhatsNew.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
@page "/whats-new"
@inject HttpClient Http
@implements IDisposable
@using System.Text.RegularExpressions
@using Markdig

<PageTitle>What's new — Fantasy Football</PageTitle>

<MudText Typo="Typo.h4" Class="mb-2">What's new</MudText>
<MudText Typo="Typo.body2" Class="mud-text-secondary mb-6">
Release history for Fantasy Football. The full source of this page is
<MudLink Href="https://github.com/StepKie/FantasyFootball/blob/main/CHANGELOG.md" Target="_blank" Color="Color.Primary">CHANGELOG.md</MudLink>
on GitHub.
</MudText>

@if (_error is not null)
{
<MudAlert Severity="Severity.Error" Variant="Variant.Outlined">
Couldn't load release notes: @_error
</MudAlert>
}
else if (_html is null)
{
<MudProgressCircular Indeterminate="true" Color="Color.Primary" />
}
else
{
<MudPaper Elevation="1" Class="pa-5 changelog-content">
@((MarkupString)_html)
</MudPaper>
}

@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(@"<a href=""(https?://[^""]+)""", RegexOptions.Compiled);

string? _html;
string? _error;

readonly CancellationTokenSource _cts = new();

protected override async Task OnInitializedAsync()
{
try
{
var markdown = await Http.GetStringAsync("CHANGELOG.md", _cts.Token);
var html = Markdig.Markdown.ToHtml(markdown, _pipeline);
_html = _externalLinkPattern.Replace(html, @"<a target=""_blank"" rel=""noopener noreferrer"" href=""$1""");
}
catch (OperationCanceledException) { /* user navigated away mid-fetch */ }
catch (Exception ex)
{
_error = ex.Message;
}
}

public void Dispose()
{
_cts.Cancel();
_cts.Dispose();
}
}
8 changes: 8 additions & 0 deletions src/FantasyFootball.Web/FantasyFootball.Web.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,12 @@
<ProjectReference Include="..\FantasyFootball.UI\FantasyFootball.UI.csproj" />
</ItemGroup>

<!-- Stage CHANGELOG.md from the repo root into wwwroot so the /whats-new page can fetch it
at runtime via HttpClient. AfterTargets="Build" runs after the static-web-assets pipeline
has enumerated wwwroot, so the file isn't tracked in git and the page links to the
GitHub-hosted original as a fallback if rendering ever fails. -->
<Target Name="StageRepoMarkdown" AfterTargets="Build">
<Copy SourceFiles="..\..\CHANGELOG.md" DestinationFolder="wwwroot" />
</Target>

</Project>
1 change: 1 addition & 0 deletions src/FantasyFootball.Web/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<IRepository, LocalStorageRepository>();
builder.Services.AddScoped<ISettingsService, LocalStorageSettingsService>();
builder.Services.AddScoped<IDataService, JsonDataService>();
Expand Down
35 changes: 35 additions & 0 deletions src/FantasyFootball.Web/wwwroot/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -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 <div> 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;
}
Loading