Skip to content

Add release process: version, CHANGELOG, /whats-new page#68

Merged
StepKie merged 2 commits into
developfrom
feature/release-process-0.6.0
Jun 2, 2026
Merged

Add release process: version, CHANGELOG, /whats-new page#68
StepKie merged 2 commits into
developfrom
feature/release-process-0.6.0

Conversation

@StepKie
Copy link
Copy Markdown
Owner

@StepKie StepKie commented Jun 2, 2026

Summary

Sets up a release process modelled on MtgCsvHelper: a single-source-of-truth version, a user-facing CHANGELOG, and an in-app /whats-new page that renders it.

  • Directory.Build.props at repo root — <Version>0.6.0</Version> inherited by every .csproj. The .NET SDK auto-appends the git SHA to AssemblyInformationalVersionAttribute, which the toolbar reads at runtime.
  • CHANGELOG.md at repo root — Keep-a-Changelog, user-facing wording only (New Features / Bugfixes). 0.6.0 entry covers Bundesliga, the four European leagues, per-competition Elo set, "Restore historical competitions" button, and the historical-tournament restore fix.
  • /whats-new page — Markdig-rendered CHANGELOG behind a v0.6.0 · {commit} link in the toolbar. External links open in new tabs.
  • Release workflow documented (project + global CLAUDE.md): release-prep PR to develop bumps <Version> + dates a new CHANGELOG section (mandatory — never tag without it); then direct merge developmain (no PR), tag, gh release create.

Test plan

  • dotnet build clean (was clean locally).
  • /whats-new renders the 0.6.0 + prior version entries with proper spacing; PR links open in new tabs; the version-tag link goes to the GitHub releases page for the tag.
  • Toolbar shows v0.6.0 · {commit} between the brand and the help/dark-mode icons; hover shows the tooltip; click navigates to /whats-new.
  • gitignore correctly excludes the staged src/FantasyFootball.Web/wwwroot/CHANGELOG.md copy.

Single-source-of-truth version (<Version> in Directory.Build.props) inherited by all
.csproj files. .NET SDK auto-derives AssemblyInformationalVersionAttribute and appends
the git SHA, which the toolbar reads at runtime to render "v{version} . {commit}".

CHANGELOG.md follows Keep-a-Changelog; user-facing wording only (New Features /
Bugfixes), no internal refactor talk, no [Unreleased] placeholder. Version headings
link to GitHub releases pages.

CLAUDE.md (project + global) documents the release workflow: release-prep PR to
develop bumps Version and adds the dated CHANGELOG section before the develop -> main
direct merge + tag + gh release create. Updating the changelog is mandatory.

Initial version: 0.6.0 (Bundesliga + four European leagues + per-competition Elo set).
</MudLayout>

@code {
static readonly string _versionFull = Assembly.GetExecutingAssembly().GetCustomAttribute<AssemblyInformationalVersionAttribute>()!.InformationalVersion;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Null-forgiving ! in a static field initializer — unrecoverable crash if attribute is absent

GetCustomAttribute<AssemblyInformationalVersionAttribute>() returns null when the attribute isn't present on the assembly (e.g. <GenerateAssemblyInfo>false</GenerateAssemblyInfo>, a trimmer root that drops it, or the component being loaded in a test/mock host). Because this is a static readonly initializer the resulting NullReferenceException is wrapped in TypeInitializationException — a permanent failure that poisons the type for the lifetime of the WASM module. Every subsequent navigation attempt then throws the same TypeInitializationException with no error-boundary recovery possible.

Prefer a null-conditional + fallback so a missing attribute degrades gracefully instead of killing the whole layout:

Suggested change
static readonly string _versionFull = Assembly.GetExecutingAssembly().GetCustomAttribute<AssemblyInformationalVersionAttribute>()!.InformationalVersion;
static readonly string _versionFull = Assembly.GetExecutingAssembly().GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion ?? "0.0.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.

🟡 Partial — done in 175d24d: null-coalesce applied (?.InformationalVersion ?? "unknown"). Took the bot's instinct (static-init poisoning is severe), but used "unknown" instead of the suggested "0.0.0" — a missing attribute should be visibly anomalous rather than mimic a real version. Matches the fallback in MtgCsvHelper's AppInfo.

Comment on lines +37 to +38
// Rewrites every absolute http(s) anchor to open in a new tab. Without this, clicking a PR or
// compare link inside the rendered CHANGELOG would navigate the whole Blazor SPA away from the app.
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Convention: multi-line // comment block — cap at one line; the why belongs in the commit message.

Suggested change
// Rewrites every absolute http(s) anchor to open in a new tab. Without this, clicking a PR or
// compare link inside the rendered CHANGELOG would navigate the whole Blazor SPA away from the app.
// Rewrites external anchors to open in a new tab so SPA navigation isn't hijacked.

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.

✅ Done in 175d24d: collapsed to the one-liner you suggested. (My own rule, doubly so when responding to bot review comments — thanks for the catch.)

@claude
Copy link
Copy Markdown

claude Bot commented Jun 2, 2026

Review summary

Clean, purposeful PR — the release-process plumbing (single-source version, CHANGELOG, /whats-new page, toolbar link) all hangs together well. Two findings, one latent note.

Findings (inline above)

Severity Location Issue
Medium MainLayout.razor:45 Null-forgiving ! on GetCustomAttribute<AssemblyInformationalVersionAttribute>() in a static readonly initializer — null produces an unrecoverable TypeInitializationException that breaks every page for the session. Suggestion: ?.InformationalVersion ?? "0.0.0".
Convention WhatsNew.razor:37–38 Two-line // comment block — cap at one line; the why belongs in the commit message.

Latent note (no action required now)

WhatsNew.razor injects HttpClient via DI. The registration lives only in FantasyFootball.Web/Program.cs; MauiProgram.cs has none. The MAUI host doesn't reference FantasyFootball.UI yet so this is a latent gap rather than a live bug — but worth adding a HttpClient registration to MauiProgram.cs before Phase 5 (BlazorWebView migration) to avoid a silent DI crash on first navigation to /whats-new in the MAUI host.

Things investigated and found clean

  • StageRepoMarkdown AfterTargets="Build"dotnet publish for Blazor WASM does a filesystem scan of wwwroot/, so the file copied by the Build-afterhook is included in the publish output. The concern about manifest-gating does not apply here.
  • _externalLinkPattern regex attribute order — Markdig's UseAutoLinks() emits href as the first (and only) attribute on generated anchors, so all external links are correctly rewritten with target="_blank".
  • XSS via (MarkupString)_htmlDisableHtml() strips raw HTML from the markdown input; Markdig's own output from markdown syntax is structurally safe. The regex replacement does not introduce any injection surface given that URLs containing " would be percent-encoded by Markdig before the replacement runs.

StepKie added a commit that referenced this pull request Jun 2, 2026
- MainLayout.razor: null-coalesce InformationalVersion lookup; a missing attribute
  would otherwise throw TypeInitializationException in the static initializer and
  poison the layout for the session. Fallback "unknown" makes the failure visible
  rather than displaying a fake version.
- WhatsNew.razor: collapse two-line comment to one line per the inline-comments rule.
</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.

Toolbar shows "v{version} . {commit}" between the brand and the existing icons; the
caption links to a new /whats-new page that fetches CHANGELOG.md from wwwroot and
renders it through Markdig. External links open in a new tab.

CHANGELOG.md is staged into wwwroot at build time by FantasyFootball.Web.csproj
(StageRepoMarkdown AfterTargets=Build); the staged copy is gitignored. HttpClient
with HostEnvironment.BaseAddress is registered in Program.cs so the page can fetch
the file.

Styles for the rendered content live in app.css (.changelog-content rules) rather
than scoped CSS isolation, because the MudPaper wrapper's rendered <div> doesn't
pick up the scope attribute that ::deep selectors require.
@StepKie StepKie force-pushed the feature/release-process-0.6.0 branch from 175d24d to 013137e Compare June 2, 2026 13:57
@StepKie StepKie merged commit dbb952c into develop Jun 2, 2026
@StepKie StepKie deleted the feature/release-process-0.6.0 branch June 2, 2026 13:57
Comment thread Directory.Build.props

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant