From f3b007152af21341fd11b3b65302c8ef280f35c7 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 5 Oct 2026 19:09:43 +0000 Subject: [PATCH 1/4] Move build and release to GitHub Actions and update packages Replace the Azure Pipelines definitions with GitHub Actions workflows that mirror the MessageBus and DSoft.DataPrivacy repositories: - ci.yml builds Release for pull requests into main or development. - release.yml runs on pushes to main, builds Release as 3.6.yyMM. (plus RELEASE_SUFFIX), uploads the packages as the drop artifact, pushes them to nuget.org with Trusted Publishing (OIDC, NUGET_USER secret, nuget environment), then tags the commit and creates a GitHub release. Remove the stale hard-coded 2.2.2102.142-prerelease versions from the library projects; the release workflow injects the version. Set ContinuousIntegrationBuild under GitHub Actions as well as Azure Pipelines. Update packages: Microsoft.Extensions.* 10.0.12, Microsoft.Maui.Controls 10.0.110, Microsoft.WindowsAppSDK 2.5.1, Microsoft.Windows.SDK.BuildTools 10.0.28000.2705, Nerdbank.Streams 2.14.354, Microsoft.SourceLink.GitHub 10.0.401. Document the pipelines in README.md and CLAUDE.md. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_012tsggoGoEogfFmpET9YiqJ --- .github/workflows/ci.yml | 34 +++++++ .github/workflows/release.yml | 97 +++++++++++++++++++ CLAUDE.md | 6 +- .../DSoft.System.Mvvm.Hosting.csproj | 5 +- .../DSoft.System.Mvvm.SourceGenerators.csproj | 3 - .../DSoft.System.Mvvm.UI.MAUI.csproj | 9 +- Directory.Build.props | 4 +- MauiSample/MauiSample.csproj | 6 +- README.md | 9 ++ .../DSoft.System.Mvvm.UI.WinUI.csproj | 9 +- .../DSoft.System.Mvvm.UI.WPF.csproj | 5 +- System.Mvvm.Ui/DSoft.System.Mvvm.Ui.csproj | 5 +- ....Mvvm.VisualStudio.Extensibility.UI.csproj | 4 +- System.Mvvm.slnx | 4 +- System.Mvvm/DSoft.System.Mvvm.csproj | 3 - azure-pipelines-mergetest.yml | 51 ---------- azure-pipelines-release.yml | 75 -------------- 17 files changed, 160 insertions(+), 169 deletions(-) create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/release.yml delete mode 100644 azure-pipelines-mergetest.yml delete mode 100644 azure-pipelines-release.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..e1bd3ce --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,34 @@ +# Pull request validation: builds Release. Publishes nothing. +# There is no unit test project; MVVMSample and MauiSample are manual samples. +name: CI + +on: + pull_request: + branches: [main, development] + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: ci-${{ github.ref }} + cancel-in-progress: true + +jobs: + build: + runs-on: windows-latest + steps: + - uses: actions/checkout@v7 + + - uses: actions/setup-dotnet@v6 + with: + dotnet-version: '10.x' + + - name: Restore workloads + run: dotnet workload restore System.Mvvm.slnx + + - name: Restore + run: dotnet restore System.Mvvm.slnx + + - name: Build + run: dotnet build System.Mvvm.slnx --configuration Release --no-restore diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..05cac47 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,97 @@ +# Release: on every push to main, builds Release and pushes the DSoft.*.nupkg +# packages to nuget.org as 3.6.yyMM., then tags the commit and +# creates a GitHub release with the packages attached. +# RELEASE_SUFFIX is empty for a stable version; set it to a suffix such as +# '-prerelease' to publish a prerelease. +# Signs in to nuget.org with Trusted Publishing (OIDC), so no API key is stored. +name: Release + +on: + push: + branches: [main] + paths-ignore: + - '**.md' + - '.github/workflows/**' + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: release + cancel-in-progress: false + +env: + RELEASE_SUFFIX: '' + +jobs: + release: + runs-on: windows-latest + environment: nuget + permissions: + contents: write # GitHub release and tag + id-token: write # Trusted Publishing + steps: + - uses: actions/checkout@v7 + + - uses: actions/setup-dotnet@v6 + with: + dotnet-version: '10.x' + + - name: Version + id: version + shell: bash + run: | + version="3.6.$(date -u +%y%m).${{ github.run_number }}" + echo "version=$version" >> "$GITHUB_OUTPUT" + echo "package=$version$RELEASE_SUFFIX" >> "$GITHUB_OUTPUT" + echo "Version: $version$RELEASE_SUFFIX" + + - name: Restore workloads + run: dotnet workload restore System.Mvvm.slnx + + - name: Restore + run: dotnet restore System.Mvvm.slnx + + - name: Build + run: > + dotnet build System.Mvvm.slnx --configuration Release --no-restore + /p:Version=${{ steps.version.outputs.package }} + /p:AssemblyVersion=${{ steps.version.outputs.version }} + /p:FileVersion=${{ steps.version.outputs.version }} + + - name: Collect packages + shell: bash + run: | + mkdir -p artifacts + find . -path '*/bin/Release/*' -name 'DSoft.*.nupkg' -exec cp {} artifacts/ \; + ls artifacts + + - uses: actions/upload-artifact@v7 + with: + name: drop + path: artifacts/*.nupkg + + - name: NuGet login + id: nuget + uses: NuGet/login@v1 + with: + user: ${{ secrets.NUGET_USER }} + + - name: Push to nuget.org + shell: bash + run: | + for package in artifacts/*.nupkg; do + dotnet nuget push "$package" --api-key "${{ steps.nuget.outputs.NUGET_API_KEY }}" --source https://api.nuget.org/v3/index.json --skip-duplicate + done + + - name: GitHub release + shell: bash + env: + GH_TOKEN: ${{ github.token }} + run: | + gh release create "v${{ steps.version.outputs.package }}" artifacts/*.nupkg \ + --target "${{ github.sha }}" \ + --title "${{ steps.version.outputs.package }}" \ + --generate-notes \ + ${RELEASE_SUFFIX:+--prerelease} diff --git a/CLAUDE.md b/CLAUDE.md index 42f9db3..73222a5 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -17,13 +17,13 @@ dotnet build System.Mvvm.slnx -c Release - No unit test project exists; `MVVMSample` (WPF) and `MauiSample` are the manual runnable samples. - Multi-targeted builds (especially MAUI/WinUI) require workloads: `dotnet workload restore`. -- Azure pipelines (`azure-pipelines-*.yml`) build via the `solution: 'System.Mvvm.slnx'` variable. +- CI is GitHub Actions (`.github/workflows/`): `ci.yml` builds Release for pull requests into `main` or `development` and publishes nothing; `release.yml` runs on every push to `main` (changes only to Markdown or workflow files are skipped; run it by hand to test a workflow change), builds Release as `3.6..` plus `RELEASE_SUFFIX` (empty for a stable version, set it to `-prerelease` in the workflow to publish a prerelease), uploads the packages as the `drop` artifact, pushes them to nuget.org with Trusted Publishing (OIDC, `NUGET_USER` secret, `nuget` environment), then tags the commit `v` and creates a GitHub release with the packages attached. - The core library targets `netstandard2.0;net10.0`. Platform UI libs add windows-specific TFMs (e.g. `net462;net10.0-windows7.0;net10.0-windows10.0.18362.0;...`). - Assemblies are strong-named/signed (`DSoft.snk`); `Release` builds enable SourceLink and pack symbols. `GeneratePackageOnBuild` is on, so building produces `.nupkg` files. ## Shared build configuration -`Directory.Build.props` (root) centralizes versioning, signing, license, SourceLink, and `NoWarn` for **all** projects. Per-project `.csproj` files only set package id/description/TFMs. Change version/copyright/signing here, not per-project. +`Directory.Build.props` (root) centralizes signing, license, SourceLink, and `NoWarn` for **all** projects. Per-project `.csproj` files only set package id/description/TFMs. Change copyright/signing here, not per-project. No project sets a version: the release workflow injects `/p:Version`, `/p:AssemblyVersion` and `/p:FileVersion`, so a local build produces `1.0.0` packages. ## Architecture @@ -68,4 +68,4 @@ Platform projects use `` (e.g. `WPF`) and `.shared.cs` filename - Root namespace is `System.Mvvm` across most projects regardless of folder name (note folder/file names like `DSoft.System.Mvvm.UI.WPF.csproj` differ from assembly name `System.Mvvm.UI.WPF`). - DI registration extensions live in namespace `Microsoft.Extensions.DependencyInjection` so `AddCoreUI()` surfaces on `IServiceCollection` without extra usings. -- Version numbers are duplicated across `.csproj` files and `Directory.Build.props` — keep them in sync when bumping. +- Do not add `Version`/`AssemblyVersion`/`FileVersion` to a `.csproj`; the version comes from `.github/workflows/release.yml`. Bump the `3.6` prefix there. diff --git a/DSoft.System.Mvvm.Hosting/DSoft.System.Mvvm.Hosting.csproj b/DSoft.System.Mvvm.Hosting/DSoft.System.Mvvm.Hosting.csproj index f0a0dac..fce808a 100644 --- a/DSoft.System.Mvvm.Hosting/DSoft.System.Mvvm.Hosting.csproj +++ b/DSoft.System.Mvvm.Hosting/DSoft.System.Mvvm.Hosting.csproj @@ -10,9 +10,6 @@ System.Mvvm $(AssemblyName) ($(TargetFramework)) false - 2.2.2102.142 - 2.2.2102.142-prerelease - 2.2.2102.142 readme.md @@ -22,7 +19,7 @@ - + diff --git a/DSoft.System.Mvvm.SourceGenerators/DSoft.System.Mvvm.SourceGenerators.csproj b/DSoft.System.Mvvm.SourceGenerators/DSoft.System.Mvvm.SourceGenerators.csproj index 420a148..0d0675e 100644 --- a/DSoft.System.Mvvm.SourceGenerators/DSoft.System.Mvvm.SourceGenerators.csproj +++ b/DSoft.System.Mvvm.SourceGenerators/DSoft.System.Mvvm.SourceGenerators.csproj @@ -10,9 +10,6 @@ Updated to .NET 10 and latest packages. Removed .NET 8 and .NET 9 support. Performance improvements. System.Mvvm.SourceGenerators $(AssemblyName) ($(TargetFramework)) - 2.2.2102.142 - 2.2.2102.142-prerelease - 2.2.2102.142 readme.md true diff --git a/DSoft.System.Mvvm.UI.MAUI/DSoft.System.Mvvm.UI.MAUI.csproj b/DSoft.System.Mvvm.UI.MAUI/DSoft.System.Mvvm.UI.MAUI.csproj index 2d70bfa..32f9f56 100644 --- a/DSoft.System.Mvvm.UI.MAUI/DSoft.System.Mvvm.UI.MAUI.csproj +++ b/DSoft.System.Mvvm.UI.MAUI/DSoft.System.Mvvm.UI.MAUI.csproj @@ -10,14 +10,11 @@ enable true DSoft.System.Mvvm.UI.MAUI - 2.2.2102.142-prerelease MAUI platform support for System.Mvvm.UI Updated to .NET 10 and latest packages. Removed .NET 8 and .NET 9 support. Performance improvements. MAUI NET System.Mvvm.UI.MAUI $(AssemblyName) ($(TargetFramework)) - 2.2.2102.142 - 2.2.2102.142 System.Mvvm 16.0 15.0 @@ -31,9 +28,9 @@ - - - + + + diff --git a/Directory.Build.props b/Directory.Build.props index bcbcd78..8d3c33c 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -40,10 +40,10 @@ - + - + true diff --git a/MauiSample/MauiSample.csproj b/MauiSample/MauiSample.csproj index eef0552..064326b 100644 --- a/MauiSample/MauiSample.csproj +++ b/MauiSample/MauiSample.csproj @@ -64,9 +64,9 @@ - - - + + + diff --git a/README.md b/README.md index 5a2f9d2..ac498b2 100644 --- a/README.md +++ b/README.md @@ -93,3 +93,12 @@ To create a notifiable property create a property with a backing field using the - .NET 10 (net10.0, Android, iOS, MacCatalyst and Windows 10.0.19041.0) - [Visual Studio Extensibility](https://www.nuget.org/packages/DSoft.System.Mvvm.VisualStudio.Extensibility.UI) - VisualStudio.Extensibility SDK on .NET 8 (Windows) + +# Building and releasing + +Builds run on GitHub Actions: + +* `.github/workflows/ci.yml` validates every pull request into `main` or `development`: it builds Release and publishes nothing. +* `.github/workflows/release.yml` runs on every push to `main` (or manually from the Actions tab). It builds and pushes the packages to nuget.org as `3.6..`, then tags the commit and creates a GitHub release with the packages attached. Set `RELEASE_SUFFIX` (e.g. `-prerelease`) to publish a prerelease. + +Publishing uses [NuGet trusted publishing](https://learn.microsoft.com/nuget/nuget-org/trusted-publishing), so no API key is stored: the nuget.org policy trusts workflow `release.yml` in environment `nuget`, and the `NUGET_USER` secret holds the nuget.org profile name. diff --git a/System.Mvvm.UI.WinUI/DSoft.System.Mvvm.UI.WinUI.csproj b/System.Mvvm.UI.WinUI/DSoft.System.Mvvm.UI.WinUI.csproj index 1b76353..9f0f4cf 100644 --- a/System.Mvvm.UI.WinUI/DSoft.System.Mvvm.UI.WinUI.csproj +++ b/System.Mvvm.UI.WinUI/DSoft.System.Mvvm.UI.WinUI.csproj @@ -2,7 +2,6 @@ net10.0-windows10.0.19041; DSoft.System.Mvvm.UI.WinUI - 2.2.2102.142-prerelease WinUI 3 platform support for System.Mvvm.UI for .NET 10+ Updated to .NET 10 and latest packages. Removed .NET 8 and .NET 9 support. Performance improvements. WinUI .NET @@ -10,8 +9,6 @@ $(AssemblyName) ($(TargetFramework)) false false - 2.2.2102.142 - 2.2.2102.142 readme.md System.Mvvm true @@ -36,8 +33,8 @@ - - + + @@ -50,6 +47,6 @@ - + \ No newline at end of file diff --git a/System.Mvvm.UI.Wpf/DSoft.System.Mvvm.UI.WPF.csproj b/System.Mvvm.UI.Wpf/DSoft.System.Mvvm.UI.WPF.csproj index 85bc6e9..219c0ff 100644 --- a/System.Mvvm.UI.Wpf/DSoft.System.Mvvm.UI.WPF.csproj +++ b/System.Mvvm.UI.Wpf/DSoft.System.Mvvm.UI.WPF.csproj @@ -3,14 +3,11 @@ net462;net10.0-windows7.0;net10.0-windows10.0.18362.0; true DSoft.System.Mvvm.UI.WPF - 2.2.2102.142-prerelease WPF platform support for DSoft.System.Mvvm.UI for .NET Framework 4.6.2 and .NET 10+ Updated to .NET 10 and latest packages. Removed .NET 8 and .NET 9 support. Performance improvements. WPF MVVM NetCore System.Mvvm.UI.WPF $(AssemblyName) ($(TargetFramework)) - 2.2.2102.142 - 2.2.2102.142 readme.md System.Mvvm true @@ -20,6 +17,6 @@ - + \ No newline at end of file diff --git a/System.Mvvm.Ui/DSoft.System.Mvvm.Ui.csproj b/System.Mvvm.Ui/DSoft.System.Mvvm.Ui.csproj index 276558d..cc3ae3f 100644 --- a/System.Mvvm.Ui/DSoft.System.Mvvm.Ui.csproj +++ b/System.Mvvm.Ui/DSoft.System.Mvvm.Ui.csproj @@ -2,21 +2,18 @@ true DSoft.System.Mvvm.Ui - 2.2.2102.142-prerelease Core UI library for System.Mvvm.UI Updated to .NET 10 and latest packages. Removed .NET 8 and .NET 9 support. Performance improvements. UI MVVM System.Mvvm.Ui $(AssemblyName) ($(TargetFramework)) - 2.2.2102.142 - 2.2.2102.142 readme.md System.Mvvm - + diff --git a/System.Mvvm.VisualStudio.Extensibility.UI/DSoft.System.Mvvm.VisualStudio.Extensibility.UI.csproj b/System.Mvvm.VisualStudio.Extensibility.UI/DSoft.System.Mvvm.VisualStudio.Extensibility.UI.csproj index 8bee175..9e05a8e 100644 --- a/System.Mvvm.VisualStudio.Extensibility.UI/DSoft.System.Mvvm.VisualStudio.Extensibility.UI.csproj +++ b/System.Mvvm.VisualStudio.Extensibility.UI/DSoft.System.Mvvm.VisualStudio.Extensibility.UI.csproj @@ -9,8 +9,6 @@ MVVM classes for Visual Studio Extensibility Initial release System.Mvvm - 2.2.2102.142-prerelease - 2.2.2102.142 readme.md @@ -20,7 +18,7 @@ - + diff --git a/System.Mvvm.slnx b/System.Mvvm.slnx index 54223f7..25073ef 100644 --- a/System.Mvvm.slnx +++ b/System.Mvvm.slnx @@ -7,8 +7,8 @@ - - + + diff --git a/System.Mvvm/DSoft.System.Mvvm.csproj b/System.Mvvm/DSoft.System.Mvvm.csproj index 639396b..cd8dfa4 100644 --- a/System.Mvvm/DSoft.System.Mvvm.csproj +++ b/System.Mvvm/DSoft.System.Mvvm.csproj @@ -8,9 +8,6 @@ System.Mvvm System.Mvvm $(AssemblyName) ($(TargetFramework)) - 2.2.2102.142 - 2.2.2102.142-prerelease - 2.2.2102.142 readme.md diff --git a/azure-pipelines-mergetest.yml b/azure-pipelines-mergetest.yml deleted file mode 100644 index 2ac842d..0000000 --- a/azure-pipelines-mergetest.yml +++ /dev/null @@ -1,51 +0,0 @@ -# .NET Desktop -# Build and run tests for .NET Desktop or Windows classic desktop solutions. -# Add steps that publish symbols, save build artifacts, and more: -# https://docs.microsoft.com/azure/devops/pipelines/apps/windows/dot-net - -trigger: none - -pool: - vmImage: 'windows-latest' - -variables: - solution: '**/*.slnx' - buildPlatform: 'Any CPU' - buildConfiguration: 'Release' - netVersion: '10.x' - -steps: -- task: NuGetToolInstaller@1 - displayName: Install Latest Nuget - -- task: UseDotNet@2 - displayName: 'Install .NET sdk' - inputs: - packageType: 'sdk' - version: $(netVersion) - -- task: Bash@3 - displayName: Restore Workloads - inputs: - targetType: 'inline' - script: - dotnet workload restore - -# - task: Bash@3 -# displayName: Install MAUI -# inputs: -# targetType: 'inline' -# script: -# dotnet workload install android ios maccatalyst tvos macos maui wasm-tools - -- task: DotNetCoreCLI@2 - displayName: dotnet restore - inputs: - command: 'restore' - projects: '$(solution)' - -- task: DotNetCoreCLI@2 - displayName: dotnet build - inputs: - projects: '$(solution)' - arguments: '--configuration=$(buildConfiguration) /p:Platform="$(buildPlatform)"' \ No newline at end of file diff --git a/azure-pipelines-release.yml b/azure-pipelines-release.yml deleted file mode 100644 index 50f25db..0000000 --- a/azure-pipelines-release.yml +++ /dev/null @@ -1,75 +0,0 @@ -# .NET Desktop -# Build and run tests for .NET Desktop or Windows classic desktop solutions. -# Add steps that publish symbols, save build artifacts, and more: -# https://docs.microsoft.com/azure/devops/pipelines/apps/windows/dot-net - -trigger: - batch: true - branches: - include: - - master - paths: - exclude: - - '*.yml' - -pool: - vmImage: 'windows-latest' - -variables: - solution: '**/*.slnx' - buildPlatform: 'Any CPU' - buildConfiguration: 'Release' - netVersion: '10.x' - releaseSuffix: '' - -name: 3.6.$(date:yyMM).$(date:dd)$(rev:r) -steps: -- task: NuGetToolInstaller@1 - displayName: Install Latest Nuget - -- task: UseDotNet@2 - displayName: 'Install .NET sdk' - inputs: - packageType: 'sdk' - version: $(netVersion) - -- task: Bash@3 - displayName: Restore Workloads - inputs: - targetType: 'inline' - script: - dotnet workload restore - -# - task: Bash@3 -# displayName: Install MAUI -# inputs: -# targetType: 'inline' -# script: -# dotnet workload install android ios maccatalyst tvos macos maui wasm-tools - -- task: DotNetCoreCLI@2 - displayName: dotnet restore - inputs: - command: 'restore' - projects: '$(solution)' - -- task: DotNetCoreCLI@2 - displayName: dotnet build - inputs: - projects: '$(solution)' - arguments: '--configuration=$(buildConfiguration) /p:Platform="$(buildPlatform)" /p:Version=$(Build.BuildNumber) /p:AssemblyVersion=$(Build.BuildNumber) /p:FileVersion=$(Build.BuildNumber) ' - -- task: CopyFiles@2 - displayName: Copy Files to $(build.artifactstagingdirectory) - inputs: - SourceFolder: '$(system.defaultworkingdirectory)' - Contents: '**/Dsoft.*.nupkg' - TargetFolder: '$(build.artifactstagingdirectory)' - flattenFolders: true - -- task: PublishBuildArtifacts@1 - displayName: Publish Artifacts drop - inputs: - PathtoPublish: '$(Build.ArtifactStagingDirectory)' - ArtifactName: 'drop' - publishLocation: 'Container' From 83ada2c543d117d0230a32b4b495e438625552ca Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 5 Oct 2026 19:24:42 +0000 Subject: [PATCH 2/4] Convert CLAUDE.md to AGENTS.md and document the branching rule Rename the agent guidance to the tool-neutral AGENTS.md, as in DSoft.DataPrivacy, and keep a one-line CLAUDE.md that imports it so Claude Code still loads it. Add a Branching section: features and bug fixes go to development; main is the release branch and every push to it publishes. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_012tsggoGoEogfFmpET9YiqJ --- AGENTS.md | 78 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ CLAUDE.md | 72 +------------------------------------------------- 2 files changed, 79 insertions(+), 71 deletions(-) create mode 100644 AGENTS.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..eb4d9bc --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,78 @@ +# AGENTS.md + +This file provides guidance to AI coding agents (Claude Code, Codex, Copilot and others) when working with code in this repository. + +## Branching + +- `development` is the working branch. **Every feature and bug fix goes to `development`**: branch from it and open pull requests into it. Never commit or open a pull request directly against `main`. +- `main` is the release branch. Every push to `main` runs `release.yml`, which publishes the packages to nuget.org and creates a GitHub release, so only merge `development` into `main` when a release is intended, and only when asked to release. + +## Overview + +DSoft.System.Mvvm is a cross-platform MVVM class library for .NET, published as a family of NuGet packages (`DSoft.System.Mvvm.*`). Core MVVM lives in one base library; UI dialog/threading support is layered on top per-platform (WPF, WinUI/UWP, MAUI, Xamarin.Forms). Built-in DI is provided via a `Microsoft.Extensions.Hosting` wrapper. + +## Build & Test + +The solution uses the newer `.slnx` format (the old `.sln` was removed): + +```powershell +dotnet restore System.Mvvm.slnx +dotnet build System.Mvvm.slnx -c Release +``` + +- No unit test project exists; `MVVMSample` (WPF) and `MauiSample` are the manual runnable samples. +- Multi-targeted builds (especially MAUI/WinUI) require workloads: `dotnet workload restore`. +- CI is GitHub Actions (`.github/workflows/`): `ci.yml` builds Release for pull requests into `main` or `development` and publishes nothing; `release.yml` runs on every push to `main` (changes only to Markdown or workflow files are skipped; run it by hand to test a workflow change), builds Release as `3.6..` plus `RELEASE_SUFFIX` (empty for a stable version, set it to `-prerelease` in the workflow to publish a prerelease), uploads the packages as the `drop` artifact, pushes them to nuget.org with Trusted Publishing (OIDC, `NUGET_USER` secret, `nuget` environment), then tags the commit `v` and creates a GitHub release with the packages attached. +- The core library targets `netstandard2.0;net10.0`. Platform UI libs add windows-specific TFMs (e.g. `net462;net10.0-windows7.0;net10.0-windows10.0.18362.0;...`). +- Assemblies are strong-named/signed (`DSoft.snk`); `Release` builds enable SourceLink and pack symbols. `GeneratePackageOnBuild` is on, so building produces `.nupkg` files. + +## Shared build configuration + +`Directory.Build.props` (root) centralizes signing, license, SourceLink, and `NoWarn` for **all** projects. Per-project `.csproj` files only set package id/description/TFMs. Change copyright/signing here, not per-project. No project sets a version: the release workflow injects `/p:Version`, `/p:AssemblyVersion` and `/p:FileVersion`, so a local build produces `1.0.0` packages. + +## Architecture + +### Core MVVM (`System.Mvvm/`, namespace `System.Mvvm`, assembly `System.Mvvm`) + +ViewModel inheritance hierarchy — pick the closest base: +- `ViewModel` — base. Implements `INotifyPropertyChanged` + `INotifyDataErrorInfo`. Provides `IsBusy`/`IsLoaded`/`IsEditable`/`IsValid` state, validation, and the central `static ViewModel.OnErrorOccurred` error event. +- `ListViewModel` → adds list management. +- `SearchViewModel` → adds search/filtering. +- `SearchTreeViewModel` → adds tree-path prep. + +Key patterns inside `ViewModel`: +- Property change: call `NotifyPropertyChanged()` (uses `[CallerMemberName]`), `SetProperty(ref field, value)`, or `UpdateValueAndNotify(...)`. `WhenPropertyChanged(name, action)` registers a side-effect action instead of overriding the setter. +- Validation: register via `AddValidator(...)` or `[Validated]` attributes; `Validator` (see `Validator.cs`) holds rules and drives `INotifyDataErrorInfo`. +- Commands: `DelegateCommand` (`ICommand`). Static toggles control auto-requery on property change: `DelegateCommand.RequeryCommandsOnChange` (default true, scans `ICommand` *properties*) vs `UpdateICommandFields` (scans `ICommand` *fields*). These reflection scans run on every notify — relevant for perf. + +### Source generator (`DSoft.System.Mvvm.SourceGenerators/`) + +`ViewModelGenerator` is an `ISourceGenerator` that injects the `[MVVMViewModel]` attribute and generates boilerplate for classes marked with it. Targets `netstandard2.0` (required for analyzers). Referenced as an analyzer, not a normal dependency. + +### DI / Hosting (`DSoft.System.Mvvm.Hosting/`) + +`ServiceHost` is a **static** wrapper around `Microsoft.Extensions.Hosting`. Set `ServiceHost.Host = builtHost` once, then resolve anywhere via `ServiceHost.GetRequiredService()`. Supports a dual Primary/Secondary host (`HostMode`) and reusable fixed scopes. The `Initialize(...)` overloads are `[Obsolete]` — build the `IHost` yourself and assign it. `ViewModel.GetRequiredService()` extension pulls from `ServiceHost`. + +### UI abstraction (`System.Mvvm.Ui/`, package `DSoft.System.Mvvm.Ui`) + +This is the platform-agnostic contract for calling UI from shared/non-UI code: +- `IPlatformCoreUIProvider` — `ShowAlertAsync`, `ShowConfirmationDialogAsync`, `InvokeOnUIThread(Async)`. +- `UI` — static facade that forwards to the registered provider (`UI.ShowAlertAsync(...)`, etc.). +- Uses `InternalsVisibleTo` (with the strong-name public key) to expose `UI.PlatformProvider` internally to each platform UI assembly. + +### Platform UI implementations (`System.Mvvm.UI.Wpf/`, `System.Mvvm.UI.WinUI/`, `DSoft.System.Mvvm.UI.MAUI/`) + +Each platform package implements the abstraction the same way — when adding/maintaining a platform, mirror this structure: +1. `PlatformUIProvider` — internal singleton implementing the platform-specific provider interface (e.g. `IWPFPlatformUIProvider`) plus `IPlatformCoreUIProvider`. +2. `MvvmManager.Init()` — wires everything: sets `UI.PlatformProvider`, subscribes to `ViewModel.OnErrorOccurred` to show alerts (special-casing `TitledException`), and sets `DelegateCommand.RunOnUiThreadAction`. **App startup must call `MvvmManager.Init()`.** +3. `ServiceCollectionExtensions.AddCoreUI()` — registers the provider into DI via `TryAddSingleton`. + +Platform projects use `` (e.g. `WPF`) and `.shared.cs` filename suffixes for cross-platform code sharing within a multi-targeted project. + +## Conventions + +- Target `development` for all changes; `main` is only ever updated by merging `development` for a release (see Branching). + +- Root namespace is `System.Mvvm` across most projects regardless of folder name (note folder/file names like `DSoft.System.Mvvm.UI.WPF.csproj` differ from assembly name `System.Mvvm.UI.WPF`). +- DI registration extensions live in namespace `Microsoft.Extensions.DependencyInjection` so `AddCoreUI()` surfaces on `IServiceCollection` without extra usings. +- Do not add `Version`/`AssemblyVersion`/`FileVersion` to a `.csproj`; the version comes from `.github/workflows/release.yml`. Bump the `3.6` prefix there. diff --git a/CLAUDE.md b/CLAUDE.md index 73222a5..43c994c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,71 +1 @@ -# CLAUDE.md - -This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. - -## Overview - -DSoft.System.Mvvm is a cross-platform MVVM class library for .NET, published as a family of NuGet packages (`DSoft.System.Mvvm.*`). Core MVVM lives in one base library; UI dialog/threading support is layered on top per-platform (WPF, WinUI/UWP, MAUI, Xamarin.Forms). Built-in DI is provided via a `Microsoft.Extensions.Hosting` wrapper. - -## Build & Test - -The solution uses the newer `.slnx` format (the old `.sln` was removed): - -```powershell -dotnet restore System.Mvvm.slnx -dotnet build System.Mvvm.slnx -c Release -``` - -- No unit test project exists; `MVVMSample` (WPF) and `MauiSample` are the manual runnable samples. -- Multi-targeted builds (especially MAUI/WinUI) require workloads: `dotnet workload restore`. -- CI is GitHub Actions (`.github/workflows/`): `ci.yml` builds Release for pull requests into `main` or `development` and publishes nothing; `release.yml` runs on every push to `main` (changes only to Markdown or workflow files are skipped; run it by hand to test a workflow change), builds Release as `3.6..` plus `RELEASE_SUFFIX` (empty for a stable version, set it to `-prerelease` in the workflow to publish a prerelease), uploads the packages as the `drop` artifact, pushes them to nuget.org with Trusted Publishing (OIDC, `NUGET_USER` secret, `nuget` environment), then tags the commit `v` and creates a GitHub release with the packages attached. -- The core library targets `netstandard2.0;net10.0`. Platform UI libs add windows-specific TFMs (e.g. `net462;net10.0-windows7.0;net10.0-windows10.0.18362.0;...`). -- Assemblies are strong-named/signed (`DSoft.snk`); `Release` builds enable SourceLink and pack symbols. `GeneratePackageOnBuild` is on, so building produces `.nupkg` files. - -## Shared build configuration - -`Directory.Build.props` (root) centralizes signing, license, SourceLink, and `NoWarn` for **all** projects. Per-project `.csproj` files only set package id/description/TFMs. Change copyright/signing here, not per-project. No project sets a version: the release workflow injects `/p:Version`, `/p:AssemblyVersion` and `/p:FileVersion`, so a local build produces `1.0.0` packages. - -## Architecture - -### Core MVVM (`System.Mvvm/`, namespace `System.Mvvm`, assembly `System.Mvvm`) - -ViewModel inheritance hierarchy — pick the closest base: -- `ViewModel` — base. Implements `INotifyPropertyChanged` + `INotifyDataErrorInfo`. Provides `IsBusy`/`IsLoaded`/`IsEditable`/`IsValid` state, validation, and the central `static ViewModel.OnErrorOccurred` error event. -- `ListViewModel` → adds list management. -- `SearchViewModel` → adds search/filtering. -- `SearchTreeViewModel` → adds tree-path prep. - -Key patterns inside `ViewModel`: -- Property change: call `NotifyPropertyChanged()` (uses `[CallerMemberName]`), `SetProperty(ref field, value)`, or `UpdateValueAndNotify(...)`. `WhenPropertyChanged(name, action)` registers a side-effect action instead of overriding the setter. -- Validation: register via `AddValidator(...)` or `[Validated]` attributes; `Validator` (see `Validator.cs`) holds rules and drives `INotifyDataErrorInfo`. -- Commands: `DelegateCommand` (`ICommand`). Static toggles control auto-requery on property change: `DelegateCommand.RequeryCommandsOnChange` (default true, scans `ICommand` *properties*) vs `UpdateICommandFields` (scans `ICommand` *fields*). These reflection scans run on every notify — relevant for perf. - -### Source generator (`DSoft.System.Mvvm.SourceGenerators/`) - -`ViewModelGenerator` is an `ISourceGenerator` that injects the `[MVVMViewModel]` attribute and generates boilerplate for classes marked with it. Targets `netstandard2.0` (required for analyzers). Referenced as an analyzer, not a normal dependency. - -### DI / Hosting (`DSoft.System.Mvvm.Hosting/`) - -`ServiceHost` is a **static** wrapper around `Microsoft.Extensions.Hosting`. Set `ServiceHost.Host = builtHost` once, then resolve anywhere via `ServiceHost.GetRequiredService()`. Supports a dual Primary/Secondary host (`HostMode`) and reusable fixed scopes. The `Initialize(...)` overloads are `[Obsolete]` — build the `IHost` yourself and assign it. `ViewModel.GetRequiredService()` extension pulls from `ServiceHost`. - -### UI abstraction (`System.Mvvm.Ui/`, package `DSoft.System.Mvvm.Ui`) - -This is the platform-agnostic contract for calling UI from shared/non-UI code: -- `IPlatformCoreUIProvider` — `ShowAlertAsync`, `ShowConfirmationDialogAsync`, `InvokeOnUIThread(Async)`. -- `UI` — static facade that forwards to the registered provider (`UI.ShowAlertAsync(...)`, etc.). -- Uses `InternalsVisibleTo` (with the strong-name public key) to expose `UI.PlatformProvider` internally to each platform UI assembly. - -### Platform UI implementations (`System.Mvvm.UI.Wpf/`, `System.Mvvm.UI.WinUI/`, `DSoft.System.Mvvm.UI.MAUI/`) - -Each platform package implements the abstraction the same way — when adding/maintaining a platform, mirror this structure: -1. `PlatformUIProvider` — internal singleton implementing the platform-specific provider interface (e.g. `IWPFPlatformUIProvider`) plus `IPlatformCoreUIProvider`. -2. `MvvmManager.Init()` — wires everything: sets `UI.PlatformProvider`, subscribes to `ViewModel.OnErrorOccurred` to show alerts (special-casing `TitledException`), and sets `DelegateCommand.RunOnUiThreadAction`. **App startup must call `MvvmManager.Init()`.** -3. `ServiceCollectionExtensions.AddCoreUI()` — registers the provider into DI via `TryAddSingleton`. - -Platform projects use `` (e.g. `WPF`) and `.shared.cs` filename suffixes for cross-platform code sharing within a multi-targeted project. - -## Conventions - -- Root namespace is `System.Mvvm` across most projects regardless of folder name (note folder/file names like `DSoft.System.Mvvm.UI.WPF.csproj` differ from assembly name `System.Mvvm.UI.WPF`). -- DI registration extensions live in namespace `Microsoft.Extensions.DependencyInjection` so `AddCoreUI()` surfaces on `IServiceCollection` without extra usings. -- Do not add `Version`/`AssemblyVersion`/`FileVersion` to a `.csproj`; the version comes from `.github/workflows/release.yml`. Bump the `3.6` prefix there. +@AGENTS.md From a1ce83dae671270241e31b7bf2f48d76e36b9567 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 5 Oct 2026 19:31:58 +0000 Subject: [PATCH 3/4] Add the main branch ruleset definition Ruleset JSON for import into the repository's Rules settings: changes to main must come through a pull request, and main cannot be deleted or force-pushed. The session could not create the ruleset through the API, so the definition is kept here for import. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_012tsggoGoEogfFmpET9YiqJ --- .github/rulesets/protect-main.json | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 .github/rulesets/protect-main.json diff --git a/.github/rulesets/protect-main.json b/.github/rulesets/protect-main.json new file mode 100644 index 0000000..aa9aff2 --- /dev/null +++ b/.github/rulesets/protect-main.json @@ -0,0 +1,24 @@ +{ + "name": "Protect main", + "target": "branch", + "enforcement": "active", + "bypass_actors": [], + "conditions": { + "ref_name": { "include": ["refs/heads/main"], "exclude": [] } + }, + "rules": [ + { "type": "deletion" }, + { "type": "non_fast_forward" }, + { + "type": "pull_request", + "parameters": { + "required_approving_review_count": 0, + "dismiss_stale_reviews_on_push": false, + "require_code_owner_review": false, + "require_last_push_approval": false, + "required_review_thread_resolution": false, + "allowed_merge_methods": ["merge", "squash"] + } + } + ] +} From 6bd34141ffff974418c6a08f46cb2a4ef88905fd Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 5 Oct 2026 20:02:17 +0000 Subject: [PATCH 4/4] Restore under Release in the workflows The Mac Catalyst SDK picks different default runtime identifiers for an app project in Debug and Release. Restoring with the default configuration and then building Release with --no-restore left MauiSample without a net10.0-maccatalyst/maccatalyst-arm64 target in its assets file (NETSDK1047). Restore with Configuration=Release so both steps agree. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_012tsggoGoEogfFmpET9YiqJ --- .github/workflows/ci.yml | 2 +- .github/workflows/release.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e1bd3ce..dc8dd3d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,7 +28,7 @@ jobs: run: dotnet workload restore System.Mvvm.slnx - name: Restore - run: dotnet restore System.Mvvm.slnx + run: dotnet restore System.Mvvm.slnx -p:Configuration=Release - name: Build run: dotnet build System.Mvvm.slnx --configuration Release --no-restore diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 05cac47..782fdbf 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -51,7 +51,7 @@ jobs: run: dotnet workload restore System.Mvvm.slnx - name: Restore - run: dotnet restore System.Mvvm.slnx + run: dotnet restore System.Mvvm.slnx -p:Configuration=Release - name: Build run: >