From 78e5189bf00437b9f6f5bacb22cd9dd84f073ad8 Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Tue, 18 Aug 2026 16:40:52 +0100 Subject: [PATCH 001/274] links --- README.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 9747c9afb..f3ca0e46d 100644 --- a/README.md +++ b/README.md @@ -6,9 +6,14 @@ C'est une application mettant en oeuvre une prise de contact entre un demandeur # Statut actuel des actions Forgejo -![Build and test](https://forgejo.pschneider.fr/notazof/yavsc/badges/workflows/buildAndTest.yml/badge.svg) +[![Build and test](https://forgejo.pschneider.fr/notazof/yavsc/badges/workflows/buildAndTest.yml/badge.svg)](https://forgejo.pschneider.fr/notazof/yavsc/actions?workflow=buildAndTest.yml) + +[![Release](https://forgejo.pschneider.fr/notazof/yavsc/badges/workflows/release.yml/badge.svg)]( +https://forgejo.pschneider.fr/notazof/yavsc/actions?workflow=release.yml +) + +[![The latest release made in the repository](https://forgejo.pschneider.fr/notazof/yavsc/badges/release.svg)](https://forgejo.pschneider.fr/notazof/yavsc/releases/latest) -![Release](https://forgejo.pschneider.fr/notazof/yavsc/badges/workflows/release.yml/badge.svg) # Statut actuel des actions GitHub From 37816a8b7d35e5ab917da5a420e3f02c049b745c Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Tue, 18 Aug 2026 17:20:23 +0100 Subject: [PATCH 002/274] build(makefile): add 'release' target to automate release branch creation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Paul wanted a Makefile target that: - takes the target version as an argument (V=1.0.7-rc1), - creates a release/ branch from main, - runs 'dotnet-gitversion /updateprojectfiles' to bump across all .csproj from git history, - commits the bump on the release branch (not on main, so main stays clean), - pushes the new branch to origin. The /src/**/*.csproj paths and CHANGELOG.md are excluded from Forgejo's protected-branch rule so the bump commit goes through on the release branch. The target refuses to run unless the operator is already on main with a clean working tree — no automatic checkout to main, so the bump never lands on the wrong branch by accident. The version in the branch name (V=...) is an intent label. The version GitVersion writes into the .csproj is whatever GitVersion computes from git history (last tag + commit count), so assembly versions stay truthful even when the branch name is aspirational. --- Makefile | 74 +++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 73 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 2683c542b..fa9d4ecf1 100644 --- a/Makefile +++ b/Makefile @@ -48,5 +48,77 @@ docker-build: docker-run: docker run -d -p 5000:5000 --name yavsc yavsc +# Crée une branche release/ depuis main, met à jour les +# `` des .csproj via dotnet-gitversion, et la +# pousse sur origin. +# +# Usage : make release V=1.0.7-rc1 +# +# Pré-requis : être sur main, working tree clean. La cible +# vérifie les deux et refuse sinon — elle ne fait JAMAIS +# de checkout automatique, c'est à l'opérateur de s'être +# positionné sur la bonne branche au préalable (sinon le +# bump pourrait partir sur une branche tierce par accident). +# +# Notes : +# - Le nom de branche vient de l'argument V (ex: 1.0.7-rc1 +# donne release/1.0.7-rc1). C'est une étiquette d'intention, +# pas la version assembly. +# - La version dans les .csproj vient de GitVersion qui la +# calcule depuis l'historique git (tag le plus proche + +# nombre de commits). C'est la version assembly réelle. +# - L'ordre (fetch → branche → bump → push) garantit qu'on +# part d'un main synchro et qu'on ne pollue pas main avec +# le bump (qui vit sur la branche release). +# - Fail-fast si la branche existe déjà en local ou sur origin. +release: + @if [ -z "$(V)" ]; then \ + echo "Usage: make release V="; \ + echo " V : version semver (ex. 1.0.7-rc1) — sert à nommer la branche."; \ + exit 1; \ + fi + @CURRENT=$$(git branch --show-current); \ + if [ "$$CURRENT" != "main" ]; then \ + echo "Refus : la cible doit être lancée depuis main."; \ + echo " Branche courante : $$CURRENT"; \ + echo " Fais : git checkout main && git pull --ff-only origin main"; \ + exit 1; \ + fi + @if [ -n "$$(git status --porcelain)" ]; then \ + echo "Working tree sale, refus de créer une branche release."; \ + git status --short; \ + exit 1; \ + fi + @BRANCH="release/$(V)"; \ + if git show-ref --verify --quiet "refs/heads/$$BRANCH"; then \ + echo "La branche $$BRANCH existe déjà en local."; \ + echo " Pour la supprimer : git branch -D $$BRANCH"; \ + exit 1; \ + fi; \ + if git ls-remote --exit-code --heads origin "$$BRANCH" >/dev/null 2>&1; then \ + echo "La branche $$BRANCH existe déjà sur origin."; \ + exit 1; \ + fi; \ + echo "==> Fetch + vérification synchro main"; \ + git fetch origin main; \ + if ! git merge-base --is-ancestor origin/main HEAD; then \ + echo "main a avancé plus loin que HEAD. Fais :"; \ + echo " git pull --ff-only origin main"; \ + exit 1; \ + fi; \ + echo "==> Création de $$BRANCH depuis main"; \ + git checkout -b "$$BRANCH"; \ + echo "==> dotnet-gitversion /updateprojectfiles"; \ + dotnet-gitversion /updateprojectfiles; \ + echo "==> Commit du bump"; \ + git add .; \ + if git diff --cached --quiet; then \ + echo "Pas de changements à committer (gitversion n'a produit aucune diff)."; \ + else \ + git commit -m "chore(release): bump version via gitversion for $(V)"; \ + fi; \ + echo "==> Push de $$BRANCH sur origin"; \ + git push -u origin "$$BRANCH"; \ + echo "==> Terminé. Branche $$BRANCH live sur origin." -.PHONY: test +.PHONY: test release From ae1ae471d7f9bd20969c083f014e09a33f0aeae9 Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Tue, 18 Aug 2026 17:20:59 +0100 Subject: [PATCH 003/274] chore(release): bump version via gitversion for 1.0.7-rc1 --- src/PostIt.Tests/PostIt.Tests.csproj | 8 ++++---- src/PostIt/PostIt.Android/PostIt.Android.csproj | 8 ++++---- src/PostIt/PostIt.Browser/PostIt.Browser.csproj | 8 ++++---- src/PostIt/PostIt.Desktop/PostIt.Desktop.csproj | 8 ++++---- src/PostIt/PostIt/PostIt.csproj | 8 ++++---- src/Yavsc.Abstract/Yavsc.Abstract.csproj | 8 ++++---- src/Yavsc.Api/Yavsc.Api.csproj | 8 ++++---- src/Yavsc.Blogs.Tests/Yavsc.Blogs.Tests.csproj | 8 ++++---- src/Yavsc.Blogs/Yavsc.Blogs.csproj | 8 ++++---- src/Yavsc.Org.Tests/Yavsc.Org.Tests.csproj | 8 ++++---- src/Yavsc.Org/Yavsc.Org.csproj | 8 ++++---- src/Yavsc.Server/Yavsc.Server.csproj | 8 ++++---- src/Yavsc.Tests.Shared/Yavsc.Tests.Shared.csproj | 4 ++++ src/cli/cli.csproj | 8 ++++---- 14 files changed, 56 insertions(+), 52 deletions(-) diff --git a/src/PostIt.Tests/PostIt.Tests.csproj b/src/PostIt.Tests/PostIt.Tests.csproj index 3d35d8277..54c40e8c2 100644 --- a/src/PostIt.Tests/PostIt.Tests.csproj +++ b/src/PostIt.Tests/PostIt.Tests.csproj @@ -6,10 +6,10 @@ false PostIt.Tests true - 1.0.1.0 - 1.0.1.0 - 1.0.1-5+Branch.main.Sha.0617fc6bda7151c70559d87177e2dcfb1b60995f - 1.0.1-5 + 1.1.0.0 + 1.1.0.0 + 1.1.0-beta.1+2.Branch.release-1.0.7-rc1.Sha.6e50967702ba9d310017c86a2d7ee636a9e94ada + 1.1.0-beta.1 diff --git a/src/PostIt/PostIt.Android/PostIt.Android.csproj b/src/PostIt/PostIt.Android/PostIt.Android.csproj index 3820bf49f..de4e3801c 100644 --- a/src/PostIt/PostIt.Android/PostIt.Android.csproj +++ b/src/PostIt/PostIt.Android/PostIt.Android.csproj @@ -12,10 +12,10 @@ apk false android-arm;android-arm64;android-x86;android-x64 - 1.0.1.0 - 1.0.1.0 - 1.0.1-5+Branch.main.Sha.0617fc6bda7151c70559d87177e2dcfb1b60995f - 1.0.1-5 + 1.1.0.0 + 1.1.0.0 + 1.1.0-beta.1+2.Branch.release-1.0.7-rc1.Sha.6e50967702ba9d310017c86a2d7ee636a9e94ada + 1.1.0-beta.1 diff --git a/src/PostIt/PostIt.Browser/PostIt.Browser.csproj b/src/PostIt/PostIt.Browser/PostIt.Browser.csproj index a339b9f01..7a0a9ba31 100644 --- a/src/PostIt/PostIt.Browser/PostIt.Browser.csproj +++ b/src/PostIt/PostIt.Browser/PostIt.Browser.csproj @@ -4,10 +4,10 @@ Exe true enable - 1.0.1.0 - 1.0.1.0 - 1.0.1-5+Branch.main.Sha.0617fc6bda7151c70559d87177e2dcfb1b60995f - 1.0.1-5 + 1.1.0.0 + 1.1.0.0 + 1.1.0-beta.1+2.Branch.release-1.0.7-rc1.Sha.6e50967702ba9d310017c86a2d7ee636a9e94ada + 1.1.0-beta.1 diff --git a/src/PostIt/PostIt.Desktop/PostIt.Desktop.csproj b/src/PostIt/PostIt.Desktop/PostIt.Desktop.csproj index c543c5509..6ab6fdac4 100644 --- a/src/PostIt/PostIt.Desktop/PostIt.Desktop.csproj +++ b/src/PostIt/PostIt.Desktop/PostIt.Desktop.csproj @@ -5,10 +5,10 @@ See https://docs.avaloniaui.net/docs/guides/platforms/platform-specific-code/dotnet for more details.--> net10.0 enable - 1.0.1.0 - 1.0.1.0 - 1.0.1-5+Branch.main.Sha.0617fc6bda7151c70559d87177e2dcfb1b60995f - 1.0.1-5 + 1.1.0.0 + 1.1.0.0 + 1.1.0-beta.1+2.Branch.release-1.0.7-rc1.Sha.6e50967702ba9d310017c86a2d7ee636a9e94ada + 1.1.0-beta.1 app.manifest diff --git a/src/PostIt/PostIt/PostIt.csproj b/src/PostIt/PostIt/PostIt.csproj index d9cf96d38..c1e3c3fad 100644 --- a/src/PostIt/PostIt/PostIt.csproj +++ b/src/PostIt/PostIt/PostIt.csproj @@ -4,10 +4,10 @@ enable latest true - 1.0.1.0 - 1.0.1.0 - 1.0.1-5+Branch.main.Sha.0617fc6bda7151c70559d87177e2dcfb1b60995f - 1.0.1-5 + 1.1.0.0 + 1.1.0.0 + 1.1.0-beta.1+2.Branch.release-1.0.7-rc1.Sha.6e50967702ba9d310017c86a2d7ee636a9e94ada + 1.1.0-beta.1 diff --git a/src/Yavsc.Abstract/Yavsc.Abstract.csproj b/src/Yavsc.Abstract/Yavsc.Abstract.csproj index 06174cf84..2b6d261a7 100644 --- a/src/Yavsc.Abstract/Yavsc.Abstract.csproj +++ b/src/Yavsc.Abstract/Yavsc.Abstract.csproj @@ -9,10 +9,10 @@ true true latest - 1.0.1.0 - 1.0.1.0 - 1.0.1-5+Branch.main.Sha.0617fc6bda7151c70559d87177e2dcfb1b60995f - 1.0.1-5 + 1.1.0.0 + 1.1.0.0 + 1.1.0-beta.1+2.Branch.release-1.0.7-rc1.Sha.6e50967702ba9d310017c86a2d7ee636a9e94ada + 1.1.0-beta.1 diff --git a/src/Yavsc.Api/Yavsc.Api.csproj b/src/Yavsc.Api/Yavsc.Api.csproj index d6cceba50..63019232c 100644 --- a/src/Yavsc.Api/Yavsc.Api.csproj +++ b/src/Yavsc.Api/Yavsc.Api.csproj @@ -5,10 +5,10 @@ 1c73094f-959f-4211-b1a1-6a69b236c283 Yavsc.Api true - 1.0.1.0 - 1.0.1.0 - 1.0.1-5+Branch.main.Sha.0617fc6bda7151c70559d87177e2dcfb1b60995f - 1.0.1-5 + 1.1.0.0 + 1.1.0.0 + 1.1.0-beta.1+2.Branch.release-1.0.7-rc1.Sha.6e50967702ba9d310017c86a2d7ee636a9e94ada + 1.1.0-beta.1 diff --git a/src/Yavsc.Blogs.Tests/Yavsc.Blogs.Tests.csproj b/src/Yavsc.Blogs.Tests/Yavsc.Blogs.Tests.csproj index ec1f7f0a0..393a520c3 100644 --- a/src/Yavsc.Blogs.Tests/Yavsc.Blogs.Tests.csproj +++ b/src/Yavsc.Blogs.Tests/Yavsc.Blogs.Tests.csproj @@ -7,10 +7,10 @@ Yavsc.Blogs.Tests b1a9d0d6-3f5e-4a07-9f0a-7e4d5b6c1a82 true - 1.0.1.0 - 1.0.1.0 - 1.0.1-5+Branch.main.Sha.0617fc6bda7151c70559d87177e2dcfb1b60995f - 1.0.1-5 + 1.1.0.0 + 1.1.0.0 + 1.1.0-beta.1+2.Branch.release-1.0.7-rc1.Sha.6e50967702ba9d310017c86a2d7ee636a9e94ada + 1.1.0-beta.1 diff --git a/src/Yavsc.Blogs/Yavsc.Blogs.csproj b/src/Yavsc.Blogs/Yavsc.Blogs.csproj index 3c4bdc687..a7f4bea99 100644 --- a/src/Yavsc.Blogs/Yavsc.Blogs.csproj +++ b/src/Yavsc.Blogs/Yavsc.Blogs.csproj @@ -6,10 +6,10 @@ Yavsc.Blogs https://github.com/pazof/yavsc true - 1.0.1.0 - 1.0.1.0 - 1.0.1-5+Branch.main.Sha.0617fc6bda7151c70559d87177e2dcfb1b60995f - 1.0.1-5 + 1.1.0.0 + 1.1.0.0 + 1.1.0-beta.1+2.Branch.release-1.0.7-rc1.Sha.6e50967702ba9d310017c86a2d7ee636a9e94ada + 1.1.0-beta.1 diff --git a/src/Yavsc.Org.Tests/Yavsc.Org.Tests.csproj b/src/Yavsc.Org.Tests/Yavsc.Org.Tests.csproj index 79a6bae17..8943e8696 100644 --- a/src/Yavsc.Org.Tests/Yavsc.Org.Tests.csproj +++ b/src/Yavsc.Org.Tests/Yavsc.Org.Tests.csproj @@ -9,10 +9,10 @@ true exe $(MSBuildProjectDirectory)\test.runsettings - 1.0.1.0 - 1.0.1.0 - 1.0.1-5+Branch.main.Sha.0617fc6bda7151c70559d87177e2dcfb1b60995f - 1.0.1-5 + 1.1.0.0 + 1.1.0.0 + 1.1.0-beta.1+2.Branch.release-1.0.7-rc1.Sha.6e50967702ba9d310017c86a2d7ee636a9e94ada + 1.1.0-beta.1 diff --git a/src/Yavsc.Org/Yavsc.Org.csproj b/src/Yavsc.Org/Yavsc.Org.csproj index adb21103e..80caabbb6 100644 --- a/src/Yavsc.Org/Yavsc.Org.csproj +++ b/src/Yavsc.Org/Yavsc.Org.csproj @@ -7,10 +7,10 @@ Yavsc true https://github.com/pazof/yavsc - 1.0.1.0 - 1.0.1.0 - 1.0.1-5+Branch.main.Sha.0617fc6bda7151c70559d87177e2dcfb1b60995f - 1.0.1-5 + 1.1.0.0 + 1.1.0.0 + 1.1.0-beta.1+2.Branch.release-1.0.7-rc1.Sha.6e50967702ba9d310017c86a2d7ee636a9e94ada + 1.1.0-beta.1 diff --git a/src/Yavsc.Server/Yavsc.Server.csproj b/src/Yavsc.Server/Yavsc.Server.csproj index c12210a68..d8f0a6834 100644 --- a/src/Yavsc.Server/Yavsc.Server.csproj +++ b/src/Yavsc.Server/Yavsc.Server.csproj @@ -7,10 +7,10 @@ true https://github.com/pazof/yavsc true - 1.0.1.0 - 1.0.1.0 - 1.0.1-5+Branch.main.Sha.0617fc6bda7151c70559d87177e2dcfb1b60995f - 1.0.1-5 + 1.1.0.0 + 1.1.0.0 + 1.1.0-beta.1+2.Branch.release-1.0.7-rc1.Sha.6e50967702ba9d310017c86a2d7ee636a9e94ada + 1.1.0-beta.1 diff --git a/src/Yavsc.Tests.Shared/Yavsc.Tests.Shared.csproj b/src/Yavsc.Tests.Shared/Yavsc.Tests.Shared.csproj index b8fa9e646..81404c206 100644 --- a/src/Yavsc.Tests.Shared/Yavsc.Tests.Shared.csproj +++ b/src/Yavsc.Tests.Shared/Yavsc.Tests.Shared.csproj @@ -12,6 +12,10 @@ TestSdk. The consumer projects own the test execution and inherit from the shared base classes. --> + 1.1.0.0 + 1.1.0.0 + 1.1.0-beta.1+2.Branch.release-1.0.7-rc1.Sha.6e50967702ba9d310017c86a2d7ee636a9e94ada + 1.1.0-beta.1 diff --git a/src/cli/cli.csproj b/src/cli/cli.csproj index fe627d9d7..305ec0afc 100644 --- a/src/cli/cli.csproj +++ b/src/cli/cli.csproj @@ -5,10 +5,10 @@ enable Yavsc.cli true - 1.0.1.0 - 1.0.1.0 - 1.0.1-5+Branch.main.Sha.0617fc6bda7151c70559d87177e2dcfb1b60995f - 1.0.1-5 + 1.1.0.0 + 1.1.0.0 + 1.1.0-beta.1+2.Branch.release-1.0.7-rc1.Sha.6e50967702ba9d310017c86a2d7ee636a9e94ada + 1.1.0-beta.1 From d8bb6f94e3b797cc0094d04d03b6d05224ddca3c Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Tue, 18 Aug 2026 18:08:38 +0100 Subject: [PATCH 004/274] chore(release): bump version via gitversion for 1.0.8-rc1 --- src/PostIt.Tests/PostIt.Tests.csproj | 8 ++++---- src/PostIt/PostIt.Android/PostIt.Android.csproj | 8 ++++---- src/PostIt/PostIt.Browser/PostIt.Browser.csproj | 8 ++++---- src/PostIt/PostIt.Desktop/PostIt.Desktop.csproj | 8 ++++---- src/PostIt/PostIt/PostIt.csproj | 8 ++++---- src/Yavsc.Abstract/Yavsc.Abstract.csproj | 8 ++++---- src/Yavsc.Api/Yavsc.Api.csproj | 8 ++++---- src/Yavsc.Blogs.Tests/Yavsc.Blogs.Tests.csproj | 8 ++++---- src/Yavsc.Blogs/Yavsc.Blogs.csproj | 8 ++++---- src/Yavsc.Org.Tests/Yavsc.Org.Tests.csproj | 8 ++++---- src/Yavsc.Org/Yavsc.Org.csproj | 8 ++++---- src/Yavsc.Server/Yavsc.Server.csproj | 8 ++++---- src/Yavsc.Tests.Shared/Yavsc.Tests.Shared.csproj | 4 ++++ src/cli/cli.csproj | 8 ++++---- 14 files changed, 56 insertions(+), 52 deletions(-) diff --git a/src/PostIt.Tests/PostIt.Tests.csproj b/src/PostIt.Tests/PostIt.Tests.csproj index 3d35d8277..049ce9f29 100644 --- a/src/PostIt.Tests/PostIt.Tests.csproj +++ b/src/PostIt.Tests/PostIt.Tests.csproj @@ -6,10 +6,10 @@ false PostIt.Tests true - 1.0.1.0 - 1.0.1.0 - 1.0.1-5+Branch.main.Sha.0617fc6bda7151c70559d87177e2dcfb1b60995f - 1.0.1-5 + 1.1.0.0 + 1.1.0.0 + 1.1.0-beta.1+2.Branch.release-1.0.8-rc1.Sha.6e50967702ba9d310017c86a2d7ee636a9e94ada + 1.1.0-beta.1 diff --git a/src/PostIt/PostIt.Android/PostIt.Android.csproj b/src/PostIt/PostIt.Android/PostIt.Android.csproj index 3820bf49f..7a78afb8b 100644 --- a/src/PostIt/PostIt.Android/PostIt.Android.csproj +++ b/src/PostIt/PostIt.Android/PostIt.Android.csproj @@ -12,10 +12,10 @@ apk false android-arm;android-arm64;android-x86;android-x64 - 1.0.1.0 - 1.0.1.0 - 1.0.1-5+Branch.main.Sha.0617fc6bda7151c70559d87177e2dcfb1b60995f - 1.0.1-5 + 1.1.0.0 + 1.1.0.0 + 1.1.0-beta.1+2.Branch.release-1.0.8-rc1.Sha.6e50967702ba9d310017c86a2d7ee636a9e94ada + 1.1.0-beta.1 diff --git a/src/PostIt/PostIt.Browser/PostIt.Browser.csproj b/src/PostIt/PostIt.Browser/PostIt.Browser.csproj index a339b9f01..150daaffa 100644 --- a/src/PostIt/PostIt.Browser/PostIt.Browser.csproj +++ b/src/PostIt/PostIt.Browser/PostIt.Browser.csproj @@ -4,10 +4,10 @@ Exe true enable - 1.0.1.0 - 1.0.1.0 - 1.0.1-5+Branch.main.Sha.0617fc6bda7151c70559d87177e2dcfb1b60995f - 1.0.1-5 + 1.1.0.0 + 1.1.0.0 + 1.1.0-beta.1+2.Branch.release-1.0.8-rc1.Sha.6e50967702ba9d310017c86a2d7ee636a9e94ada + 1.1.0-beta.1 diff --git a/src/PostIt/PostIt.Desktop/PostIt.Desktop.csproj b/src/PostIt/PostIt.Desktop/PostIt.Desktop.csproj index c543c5509..faef4e28f 100644 --- a/src/PostIt/PostIt.Desktop/PostIt.Desktop.csproj +++ b/src/PostIt/PostIt.Desktop/PostIt.Desktop.csproj @@ -5,10 +5,10 @@ See https://docs.avaloniaui.net/docs/guides/platforms/platform-specific-code/dotnet for more details.--> net10.0 enable - 1.0.1.0 - 1.0.1.0 - 1.0.1-5+Branch.main.Sha.0617fc6bda7151c70559d87177e2dcfb1b60995f - 1.0.1-5 + 1.1.0.0 + 1.1.0.0 + 1.1.0-beta.1+2.Branch.release-1.0.8-rc1.Sha.6e50967702ba9d310017c86a2d7ee636a9e94ada + 1.1.0-beta.1 app.manifest diff --git a/src/PostIt/PostIt/PostIt.csproj b/src/PostIt/PostIt/PostIt.csproj index d9cf96d38..bcd3b7e67 100644 --- a/src/PostIt/PostIt/PostIt.csproj +++ b/src/PostIt/PostIt/PostIt.csproj @@ -4,10 +4,10 @@ enable latest true - 1.0.1.0 - 1.0.1.0 - 1.0.1-5+Branch.main.Sha.0617fc6bda7151c70559d87177e2dcfb1b60995f - 1.0.1-5 + 1.1.0.0 + 1.1.0.0 + 1.1.0-beta.1+2.Branch.release-1.0.8-rc1.Sha.6e50967702ba9d310017c86a2d7ee636a9e94ada + 1.1.0-beta.1 diff --git a/src/Yavsc.Abstract/Yavsc.Abstract.csproj b/src/Yavsc.Abstract/Yavsc.Abstract.csproj index 06174cf84..f22b95037 100644 --- a/src/Yavsc.Abstract/Yavsc.Abstract.csproj +++ b/src/Yavsc.Abstract/Yavsc.Abstract.csproj @@ -9,10 +9,10 @@ true true latest - 1.0.1.0 - 1.0.1.0 - 1.0.1-5+Branch.main.Sha.0617fc6bda7151c70559d87177e2dcfb1b60995f - 1.0.1-5 + 1.1.0.0 + 1.1.0.0 + 1.1.0-beta.1+2.Branch.release-1.0.8-rc1.Sha.6e50967702ba9d310017c86a2d7ee636a9e94ada + 1.1.0-beta.1 diff --git a/src/Yavsc.Api/Yavsc.Api.csproj b/src/Yavsc.Api/Yavsc.Api.csproj index d6cceba50..19e22fa40 100644 --- a/src/Yavsc.Api/Yavsc.Api.csproj +++ b/src/Yavsc.Api/Yavsc.Api.csproj @@ -5,10 +5,10 @@ 1c73094f-959f-4211-b1a1-6a69b236c283 Yavsc.Api true - 1.0.1.0 - 1.0.1.0 - 1.0.1-5+Branch.main.Sha.0617fc6bda7151c70559d87177e2dcfb1b60995f - 1.0.1-5 + 1.1.0.0 + 1.1.0.0 + 1.1.0-beta.1+2.Branch.release-1.0.8-rc1.Sha.6e50967702ba9d310017c86a2d7ee636a9e94ada + 1.1.0-beta.1 diff --git a/src/Yavsc.Blogs.Tests/Yavsc.Blogs.Tests.csproj b/src/Yavsc.Blogs.Tests/Yavsc.Blogs.Tests.csproj index ec1f7f0a0..9ea7b8936 100644 --- a/src/Yavsc.Blogs.Tests/Yavsc.Blogs.Tests.csproj +++ b/src/Yavsc.Blogs.Tests/Yavsc.Blogs.Tests.csproj @@ -7,10 +7,10 @@ Yavsc.Blogs.Tests b1a9d0d6-3f5e-4a07-9f0a-7e4d5b6c1a82 true - 1.0.1.0 - 1.0.1.0 - 1.0.1-5+Branch.main.Sha.0617fc6bda7151c70559d87177e2dcfb1b60995f - 1.0.1-5 + 1.1.0.0 + 1.1.0.0 + 1.1.0-beta.1+2.Branch.release-1.0.8-rc1.Sha.6e50967702ba9d310017c86a2d7ee636a9e94ada + 1.1.0-beta.1 diff --git a/src/Yavsc.Blogs/Yavsc.Blogs.csproj b/src/Yavsc.Blogs/Yavsc.Blogs.csproj index 3c4bdc687..86aa173ee 100644 --- a/src/Yavsc.Blogs/Yavsc.Blogs.csproj +++ b/src/Yavsc.Blogs/Yavsc.Blogs.csproj @@ -6,10 +6,10 @@ Yavsc.Blogs https://github.com/pazof/yavsc true - 1.0.1.0 - 1.0.1.0 - 1.0.1-5+Branch.main.Sha.0617fc6bda7151c70559d87177e2dcfb1b60995f - 1.0.1-5 + 1.1.0.0 + 1.1.0.0 + 1.1.0-beta.1+2.Branch.release-1.0.8-rc1.Sha.6e50967702ba9d310017c86a2d7ee636a9e94ada + 1.1.0-beta.1 diff --git a/src/Yavsc.Org.Tests/Yavsc.Org.Tests.csproj b/src/Yavsc.Org.Tests/Yavsc.Org.Tests.csproj index 79a6bae17..d2a184514 100644 --- a/src/Yavsc.Org.Tests/Yavsc.Org.Tests.csproj +++ b/src/Yavsc.Org.Tests/Yavsc.Org.Tests.csproj @@ -9,10 +9,10 @@ true exe $(MSBuildProjectDirectory)\test.runsettings - 1.0.1.0 - 1.0.1.0 - 1.0.1-5+Branch.main.Sha.0617fc6bda7151c70559d87177e2dcfb1b60995f - 1.0.1-5 + 1.1.0.0 + 1.1.0.0 + 1.1.0-beta.1+2.Branch.release-1.0.8-rc1.Sha.6e50967702ba9d310017c86a2d7ee636a9e94ada + 1.1.0-beta.1 diff --git a/src/Yavsc.Org/Yavsc.Org.csproj b/src/Yavsc.Org/Yavsc.Org.csproj index adb21103e..060d7933a 100644 --- a/src/Yavsc.Org/Yavsc.Org.csproj +++ b/src/Yavsc.Org/Yavsc.Org.csproj @@ -7,10 +7,10 @@ Yavsc true https://github.com/pazof/yavsc - 1.0.1.0 - 1.0.1.0 - 1.0.1-5+Branch.main.Sha.0617fc6bda7151c70559d87177e2dcfb1b60995f - 1.0.1-5 + 1.1.0.0 + 1.1.0.0 + 1.1.0-beta.1+2.Branch.release-1.0.8-rc1.Sha.6e50967702ba9d310017c86a2d7ee636a9e94ada + 1.1.0-beta.1 diff --git a/src/Yavsc.Server/Yavsc.Server.csproj b/src/Yavsc.Server/Yavsc.Server.csproj index c12210a68..9ebddba8c 100644 --- a/src/Yavsc.Server/Yavsc.Server.csproj +++ b/src/Yavsc.Server/Yavsc.Server.csproj @@ -7,10 +7,10 @@ true https://github.com/pazof/yavsc true - 1.0.1.0 - 1.0.1.0 - 1.0.1-5+Branch.main.Sha.0617fc6bda7151c70559d87177e2dcfb1b60995f - 1.0.1-5 + 1.1.0.0 + 1.1.0.0 + 1.1.0-beta.1+2.Branch.release-1.0.8-rc1.Sha.6e50967702ba9d310017c86a2d7ee636a9e94ada + 1.1.0-beta.1 diff --git a/src/Yavsc.Tests.Shared/Yavsc.Tests.Shared.csproj b/src/Yavsc.Tests.Shared/Yavsc.Tests.Shared.csproj index b8fa9e646..5f9193108 100644 --- a/src/Yavsc.Tests.Shared/Yavsc.Tests.Shared.csproj +++ b/src/Yavsc.Tests.Shared/Yavsc.Tests.Shared.csproj @@ -12,6 +12,10 @@ TestSdk. The consumer projects own the test execution and inherit from the shared base classes. --> + 1.1.0.0 + 1.1.0.0 + 1.1.0-beta.1+2.Branch.release-1.0.8-rc1.Sha.6e50967702ba9d310017c86a2d7ee636a9e94ada + 1.1.0-beta.1 diff --git a/src/cli/cli.csproj b/src/cli/cli.csproj index fe627d9d7..b53ea9f24 100644 --- a/src/cli/cli.csproj +++ b/src/cli/cli.csproj @@ -5,10 +5,10 @@ enable Yavsc.cli true - 1.0.1.0 - 1.0.1.0 - 1.0.1-5+Branch.main.Sha.0617fc6bda7151c70559d87177e2dcfb1b60995f - 1.0.1-5 + 1.1.0.0 + 1.1.0.0 + 1.1.0-beta.1+2.Branch.release-1.0.8-rc1.Sha.6e50967702ba9d310017c86a2d7ee636a9e94ada + 1.1.0-beta.1 From 7bd93ddfe9bd33362c2dab61a8da3996a4104a34 Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Tue, 18 Aug 2026 18:09:30 +0100 Subject: [PATCH 005/274] MEF --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index d1ed912ac..c84cade7b 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,19 @@ # Yavsc + [![The latest release made in the repository](https://forgejo.pschneider.fr/notazof/yavsc/badges/release.svg)](https://forgejo.pschneider.fr/notazof/yavsc/releases/latest) C'est une application mettant en oeuvre une prise de contact entre un demandeur de services et son éventuel prestataire associé. +# Statut actuel des actions Forgejo + +* [![Build and test](https://forgejo.pschneider.fr/notazof/yavsc/badges/workflows/buildAndTest.yml/badge.svg)](https://forgejo.pschneider.fr/notazof/yavsc/actions?workflow=buildAndTest.yml) + +* [![Release](https://forgejo.pschneider.fr/notazof/yavsc/badges/workflows/release.yml/badge.svg)]( +https://forgejo.pschneider.fr/notazof/yavsc/actions?workflow=release.yml +) + + # Statut actuel des actions GitHub * [![Build and Push Yavsc Apk](https://github.com/pazof/yavsc/actions/workflows/docker-publish-android.yml/badge.svg)](https://github.com/pazof/yavsc/actions/workflows/docker-publish-android.yml) From c952826424f27bb0e10363fd86282b7b875149da Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Tue, 18 Aug 2026 18:33:40 +0100 Subject: [PATCH 006/274] chore(release): add 1.0.7 preview section to CHANGELOG The release workflow's validate-release job requires a '## [TAG] - channel' section in CHANGELOG.md before allowing the tag to ship. Without this entry, the 1.0.7 tag push fails the workflow with: ::error::No section matching '## [1.0.7]' found in CHANGELOG.md. Add a '## [1.0.7] - preview' section before tagging. The section collects the 35 commits shipped between 1.0.6 and 1.0.7: ACL feature (per-post grants + circle membership), the Publish toggle that replaces the abandoned Visibility enum, the make release target, the IYavscApiClient abstraction, the IContactService/IUserDirectory split, and the Forgejo Actions release workflow rewrite (bash + jq, runner-provided GITHUB_TOKEN, .csproj projects built directly inside the runner container). The '## [Unreleased]' block is consumed by this section, and the trailing link reference is updated to point at 1.0.6...1.0.7 for the standard Keep-a-Changelog compare URL. --- CHANGELOG.md | 99 ++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 97 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c855b2490..ac258ff6e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,15 +16,111 @@ Cette convention est partagée avec le dépôt [`postit-debian`](https://forgejo.pschneider.fr/notazof/postit-debian) pour la production des paquets `.deb`. -## [Unreleased] +## [1.0.7] - preview ### Added +- Per-post ACL in PostIt: a new “Manage ACL” page, opened from the ACL + button on a selected post, lets the post author grant or revoke + grants for individuals or circles. The server scopes each grant + operation to `caller == post.AuthorId` and returns `404` (not `403`) + for posts the caller does not own, so the existence of another + user's post is not leaked. +- Circle membership API + UI: three new REST endpoints under + `/api/circle/{id}/members` (`GET` list, `POST` add, `DELETE` + remove) and a new “Members” column on the *My Circles* page with an + “Add a member” button that opens a search modal. The search modal + reuses `IUserDirectory` (introduced by the `IContactService` split + in this same release) — exactly the use case the abstraction was + carved out for. +- Publish toggle for blog posts: a new `PUT /api/BlogApi/{id}/publish` + endpoint, and a `Published` checkbox in the post toolbar that + toggles a `BlogSpotPublication` row for the post. The publish + signal flows through the pre-existing `PermissionHandler.IsPublic` + path, so no new column was needed and the server-side authorisation + logic is unchanged. +- `UserSearchApiController` in `Yavsc.Blogs`: + `GET /api/user-search?q=...&e=...&take=...`. Any-authenticated- + caller endpoint that exposes the user's email under a closed- + community assumption (documented in the controller's XML doc). + Wired to the PostIt Desktop address book so the user search modal + picks it up. +- `IYavscApiClient` abstraction in `Yavsc.Api.Client`. The transport + for the blog/circle/blog-acl/user-search clients is now accessed + through this interface, so `PostIt.Tests` can stub the HTTP layer + without spinning up a real WebAPI host. +- Forgejo Actions release workflow: a `.forgejo/workflows/release.yml` + pipeline that builds and publishes a release with the PostIt APK + on tag push. Written in pure bash (the runner image has no Node), + uses `jq` for JSON body construction and response parsing, uses the + runner-provided `GITHUB_TOKEN` (no repo-level secret needed), + validates the CHANGELOG section heading before allowing the tag + to ship. +- `make release V=` target: creates a `release/` branch + from `main`, bumps the `` property in every `.csproj` via + `dotnet-gitversion /updateprojectfiles`, commits the bump on the + release branch, and pushes to `origin`. Fails fast if the working + tree is dirty or if `HEAD` is not on `main`. +- Forgejo status badges in the README. ### Changed +- The new Publish toggle replaces the “Visibility enum” approach + originally drafted in this branch: the existing `BlogSpotPublication` + table already carried enough information to expose a publish + switch, so no schema change was needed. The original `feat(blog): + add Visibility { Private, Public }` commit and its EF migration + were reverted in favour of the endpoint-only toggle. +- `BlogPost` DTO and `IBlogPost` moved from `PostIt.Models` to + `Yavsc.Abstract.Blogspot`, the shared assembly where the server-side + entity and the wire DTO both live. Renamed `Yavsc.Blogspot.BlogPost` + to `BlogPostDto` to make the wire/entity distinction explicit. +- `BlogAclApiController` and `CircleApiController` moved from + `Yavsc.Api` (not yet enabled in production) to `Yavsc.Blogs`, where + they belong next to the `BlogSpotService` they depend on. +- `IContactService` split from `IUserDirectory`: the two interfaces + previously conflated the local address-book access (mobile-only, + via `Contacts.Default`) and the Yavsc user-search access + (Desktop-only, via `/api/user-search`) behind a single facade. The + split restores the `ContactDto.Emails` multi-value shape that was + being silently flattened to a single string before. +- CI: the Forgejo Actions build now compiles `.csproj` projects + directly inside the runner container (which ships the .NET SDK + + Android workload), instead of relying on a separate Docker build + step. Node-based third-party actions were replaced with bash + curl + + `jq`. The validate-release job parses the CHANGELOG section + heading to derive the channel (`stable` / `preview` / `unstable`) + rather than the patch-version parity alone. ### Fixed +- `CircleApiController` used to read the caller's user id via + `FindFirstValue(ClaimTypes.NameIdentifier)`, which does not match + when JWT Bearer middleware has `MapInboundClaims = false`. Switched + to `User.GetUserId()` (tries `sub` first, then + `ClaimTypes.NameIdentifier`, then `nameid`). This was a latent + bug visible in tests but easy to ship to production if a host + ever disabled the remap. +- `CircleApiController` and `BlogAclApiController` reads and writes + were not always scoped to the caller's own data. Tightened the + authorisation checks: cross-user reads now return `404`, not the + raw record. +- `validate-release` CHANGELOG channel check used to parse the + patch-version parity only, which disagreed with the channel + suffix in the section heading (e.g. `## [1.0.7] - preview` + would be flagged as `stable` from the parity alone). The job now + inspects the heading line and trusts the suffix when present. +- `.forgejo/workflows/release.yml`: the asset-upload URL now carries + the asset name as a query-string parameter instead of a `curl` + positional argument. The previous shape triggered Forgejo's + “Missing `name` parameter” 400 in some cases. ### Removed +- The `## [Unreleased]` block has been moved into this section. +- The abandoned `Visibility { Private, Public }` enum and its EF + migration, reverted in this release. The publish toggle covers + the same user-visible switch without a schema change. + +[Unreleased]: https://github.com/pazof/yavsc/compare/HEAD +[1.0.7]: https://github.com/pazof/yavsc/compare/1.0.6...1.0.7 +[1.0.6]: https://github.com/pazof/yavsc/compare/1.0.5...1.0.6 ## [1.0.6] - stable @@ -58,5 +154,4 @@ pour la production des paquets `.deb`. actual release id. Switched to `jq` for both body construction and field extraction. -[Unreleased]: https://github.com/pazof/yavsc/compare/HEAD [1.0.6]: https://github.com/pazof/yavsc/compare/1.0.5...1.0.6 From ed058c2e421b597bb0b2f6bcd114c57002de8438 Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Tue, 18 Aug 2026 19:18:36 +0100 Subject: [PATCH 007/274] chore(release): bump version via gitversion for 1.0.8-rc1 --- src/PostIt.Tests/PostIt.Tests.csproj | 2 +- src/PostIt/PostIt.Android/PostIt.Android.csproj | 2 +- src/PostIt/PostIt.Browser/PostIt.Browser.csproj | 2 +- src/PostIt/PostIt.Desktop/PostIt.Desktop.csproj | 2 +- src/PostIt/PostIt/PostIt.csproj | 2 +- src/Yavsc.Abstract/Yavsc.Abstract.csproj | 2 +- src/Yavsc.Api.Client/Yavsc.Api.Client.csproj | 10 +++++----- src/Yavsc.Api/Yavsc.Api.csproj | 2 +- src/Yavsc.Blogs.Tests/Yavsc.Blogs.Tests.csproj | 2 +- src/Yavsc.Blogs/Yavsc.Blogs.csproj | 2 +- src/Yavsc.Org.Tests/Yavsc.Org.Tests.csproj | 2 +- src/Yavsc.Org/Yavsc.Org.csproj | 2 +- src/Yavsc.Server/Yavsc.Server.csproj | 2 +- src/Yavsc.Tests.Shared/Yavsc.Tests.Shared.csproj | 2 +- src/cli/cli.csproj | 2 +- 15 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/PostIt.Tests/PostIt.Tests.csproj b/src/PostIt.Tests/PostIt.Tests.csproj index 54c40e8c2..b12c536e1 100644 --- a/src/PostIt.Tests/PostIt.Tests.csproj +++ b/src/PostIt.Tests/PostIt.Tests.csproj @@ -8,7 +8,7 @@ true 1.1.0.0 1.1.0.0 - 1.1.0-beta.1+2.Branch.release-1.0.7-rc1.Sha.6e50967702ba9d310017c86a2d7ee636a9e94ada + 1.1.0-beta.1+1.Branch.release-1.0.8-rc1.Sha.1167169aa89e1bf25290e9a152d27b357a500ab3 1.1.0-beta.1 diff --git a/src/PostIt/PostIt.Android/PostIt.Android.csproj b/src/PostIt/PostIt.Android/PostIt.Android.csproj index de4e3801c..b08143b4b 100644 --- a/src/PostIt/PostIt.Android/PostIt.Android.csproj +++ b/src/PostIt/PostIt.Android/PostIt.Android.csproj @@ -14,7 +14,7 @@ android-arm;android-arm64;android-x86;android-x64 1.1.0.0 1.1.0.0 - 1.1.0-beta.1+2.Branch.release-1.0.7-rc1.Sha.6e50967702ba9d310017c86a2d7ee636a9e94ada + 1.1.0-beta.1+1.Branch.release-1.0.8-rc1.Sha.1167169aa89e1bf25290e9a152d27b357a500ab3 1.1.0-beta.1 diff --git a/src/PostIt/PostIt.Browser/PostIt.Browser.csproj b/src/PostIt/PostIt.Browser/PostIt.Browser.csproj index 7a0a9ba31..8643fcc6c 100644 --- a/src/PostIt/PostIt.Browser/PostIt.Browser.csproj +++ b/src/PostIt/PostIt.Browser/PostIt.Browser.csproj @@ -6,7 +6,7 @@ enable 1.1.0.0 1.1.0.0 - 1.1.0-beta.1+2.Branch.release-1.0.7-rc1.Sha.6e50967702ba9d310017c86a2d7ee636a9e94ada + 1.1.0-beta.1+1.Branch.release-1.0.8-rc1.Sha.1167169aa89e1bf25290e9a152d27b357a500ab3 1.1.0-beta.1 diff --git a/src/PostIt/PostIt.Desktop/PostIt.Desktop.csproj b/src/PostIt/PostIt.Desktop/PostIt.Desktop.csproj index 6ab6fdac4..5043da6e5 100644 --- a/src/PostIt/PostIt.Desktop/PostIt.Desktop.csproj +++ b/src/PostIt/PostIt.Desktop/PostIt.Desktop.csproj @@ -7,7 +7,7 @@ enable 1.1.0.0 1.1.0.0 - 1.1.0-beta.1+2.Branch.release-1.0.7-rc1.Sha.6e50967702ba9d310017c86a2d7ee636a9e94ada + 1.1.0-beta.1+1.Branch.release-1.0.8-rc1.Sha.1167169aa89e1bf25290e9a152d27b357a500ab3 1.1.0-beta.1 diff --git a/src/PostIt/PostIt/PostIt.csproj b/src/PostIt/PostIt/PostIt.csproj index c63771b87..163a6a774 100644 --- a/src/PostIt/PostIt/PostIt.csproj +++ b/src/PostIt/PostIt/PostIt.csproj @@ -6,7 +6,7 @@ true 1.1.0.0 1.1.0.0 - 1.1.0-beta.1+2.Branch.release-1.0.7-rc1.Sha.6e50967702ba9d310017c86a2d7ee636a9e94ada + 1.1.0-beta.1+1.Branch.release-1.0.8-rc1.Sha.1167169aa89e1bf25290e9a152d27b357a500ab3 1.1.0-beta.1 diff --git a/src/Yavsc.Abstract/Yavsc.Abstract.csproj b/src/Yavsc.Abstract/Yavsc.Abstract.csproj index 2b6d261a7..7a4f83ae6 100644 --- a/src/Yavsc.Abstract/Yavsc.Abstract.csproj +++ b/src/Yavsc.Abstract/Yavsc.Abstract.csproj @@ -11,7 +11,7 @@ latest 1.1.0.0 1.1.0.0 - 1.1.0-beta.1+2.Branch.release-1.0.7-rc1.Sha.6e50967702ba9d310017c86a2d7ee636a9e94ada + 1.1.0-beta.1+1.Branch.release-1.0.8-rc1.Sha.1167169aa89e1bf25290e9a152d27b357a500ab3 1.1.0-beta.1 diff --git a/src/Yavsc.Api.Client/Yavsc.Api.Client.csproj b/src/Yavsc.Api.Client/Yavsc.Api.Client.csproj index 5376856dd..ade7ca17c 100644 --- a/src/Yavsc.Api.Client/Yavsc.Api.Client.csproj +++ b/src/Yavsc.Api.Client/Yavsc.Api.Client.csproj @@ -15,10 +15,10 @@ https://github.com/pazof/yavsc true - 1.0.1.0 - 1.0.1.0 - 1.0.1-5+Branch.main.Sha.0617fc6bda7151c70559d87177e2dcfb1b60995f - 1.0.1-5 + 1.1.0.0 + 1.1.0.0 + 1.1.0-beta.1+1.Branch.release-1.0.8-rc1.Sha.1167169aa89e1bf25290e9a152d27b357a500ab3 + 1.1.0-beta.1 @@ -26,4 +26,4 @@ - + \ No newline at end of file diff --git a/src/Yavsc.Api/Yavsc.Api.csproj b/src/Yavsc.Api/Yavsc.Api.csproj index 63019232c..5672ab7a9 100644 --- a/src/Yavsc.Api/Yavsc.Api.csproj +++ b/src/Yavsc.Api/Yavsc.Api.csproj @@ -7,7 +7,7 @@ true 1.1.0.0 1.1.0.0 - 1.1.0-beta.1+2.Branch.release-1.0.7-rc1.Sha.6e50967702ba9d310017c86a2d7ee636a9e94ada + 1.1.0-beta.1+1.Branch.release-1.0.8-rc1.Sha.1167169aa89e1bf25290e9a152d27b357a500ab3 1.1.0-beta.1 diff --git a/src/Yavsc.Blogs.Tests/Yavsc.Blogs.Tests.csproj b/src/Yavsc.Blogs.Tests/Yavsc.Blogs.Tests.csproj index 393a520c3..256bdc4de 100644 --- a/src/Yavsc.Blogs.Tests/Yavsc.Blogs.Tests.csproj +++ b/src/Yavsc.Blogs.Tests/Yavsc.Blogs.Tests.csproj @@ -9,7 +9,7 @@ true 1.1.0.0 1.1.0.0 - 1.1.0-beta.1+2.Branch.release-1.0.7-rc1.Sha.6e50967702ba9d310017c86a2d7ee636a9e94ada + 1.1.0-beta.1+1.Branch.release-1.0.8-rc1.Sha.1167169aa89e1bf25290e9a152d27b357a500ab3 1.1.0-beta.1 diff --git a/src/Yavsc.Blogs/Yavsc.Blogs.csproj b/src/Yavsc.Blogs/Yavsc.Blogs.csproj index a7f4bea99..5c175cb54 100644 --- a/src/Yavsc.Blogs/Yavsc.Blogs.csproj +++ b/src/Yavsc.Blogs/Yavsc.Blogs.csproj @@ -8,7 +8,7 @@ true 1.1.0.0 1.1.0.0 - 1.1.0-beta.1+2.Branch.release-1.0.7-rc1.Sha.6e50967702ba9d310017c86a2d7ee636a9e94ada + 1.1.0-beta.1+1.Branch.release-1.0.8-rc1.Sha.1167169aa89e1bf25290e9a152d27b357a500ab3 1.1.0-beta.1 diff --git a/src/Yavsc.Org.Tests/Yavsc.Org.Tests.csproj b/src/Yavsc.Org.Tests/Yavsc.Org.Tests.csproj index 8943e8696..88842cb45 100644 --- a/src/Yavsc.Org.Tests/Yavsc.Org.Tests.csproj +++ b/src/Yavsc.Org.Tests/Yavsc.Org.Tests.csproj @@ -11,7 +11,7 @@ $(MSBuildProjectDirectory)\test.runsettings 1.1.0.0 1.1.0.0 - 1.1.0-beta.1+2.Branch.release-1.0.7-rc1.Sha.6e50967702ba9d310017c86a2d7ee636a9e94ada + 1.1.0-beta.1+1.Branch.release-1.0.8-rc1.Sha.1167169aa89e1bf25290e9a152d27b357a500ab3 1.1.0-beta.1 diff --git a/src/Yavsc.Org/Yavsc.Org.csproj b/src/Yavsc.Org/Yavsc.Org.csproj index 80caabbb6..ccdb7f245 100644 --- a/src/Yavsc.Org/Yavsc.Org.csproj +++ b/src/Yavsc.Org/Yavsc.Org.csproj @@ -9,7 +9,7 @@ https://github.com/pazof/yavsc 1.1.0.0 1.1.0.0 - 1.1.0-beta.1+2.Branch.release-1.0.7-rc1.Sha.6e50967702ba9d310017c86a2d7ee636a9e94ada + 1.1.0-beta.1+1.Branch.release-1.0.8-rc1.Sha.1167169aa89e1bf25290e9a152d27b357a500ab3 1.1.0-beta.1 diff --git a/src/Yavsc.Server/Yavsc.Server.csproj b/src/Yavsc.Server/Yavsc.Server.csproj index d8f0a6834..4eaf6a831 100644 --- a/src/Yavsc.Server/Yavsc.Server.csproj +++ b/src/Yavsc.Server/Yavsc.Server.csproj @@ -9,7 +9,7 @@ true 1.1.0.0 1.1.0.0 - 1.1.0-beta.1+2.Branch.release-1.0.7-rc1.Sha.6e50967702ba9d310017c86a2d7ee636a9e94ada + 1.1.0-beta.1+1.Branch.release-1.0.8-rc1.Sha.1167169aa89e1bf25290e9a152d27b357a500ab3 1.1.0-beta.1 diff --git a/src/Yavsc.Tests.Shared/Yavsc.Tests.Shared.csproj b/src/Yavsc.Tests.Shared/Yavsc.Tests.Shared.csproj index 81404c206..52effc72e 100644 --- a/src/Yavsc.Tests.Shared/Yavsc.Tests.Shared.csproj +++ b/src/Yavsc.Tests.Shared/Yavsc.Tests.Shared.csproj @@ -14,7 +14,7 @@ --> 1.1.0.0 1.1.0.0 - 1.1.0-beta.1+2.Branch.release-1.0.7-rc1.Sha.6e50967702ba9d310017c86a2d7ee636a9e94ada + 1.1.0-beta.1+1.Branch.release-1.0.8-rc1.Sha.1167169aa89e1bf25290e9a152d27b357a500ab3 1.1.0-beta.1 diff --git a/src/cli/cli.csproj b/src/cli/cli.csproj index 305ec0afc..db5e93142 100644 --- a/src/cli/cli.csproj +++ b/src/cli/cli.csproj @@ -7,7 +7,7 @@ true 1.1.0.0 1.1.0.0 - 1.1.0-beta.1+2.Branch.release-1.0.7-rc1.Sha.6e50967702ba9d310017c86a2d7ee636a9e94ada + 1.1.0-beta.1+1.Branch.release-1.0.8-rc1.Sha.1167169aa89e1bf25290e9a152d27b357a500ab3 1.1.0-beta.1 From 924ccccca4277bd4e0f43a7cb3ae0c33848a6a77 Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Tue, 18 Aug 2026 21:14:52 +0100 Subject: [PATCH 008/274] cleanup --- .forgejo/workflows/buildAndTest.yml | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/.forgejo/workflows/buildAndTest.yml b/.forgejo/workflows/buildAndTest.yml index 9b3403dba..0c48451a3 100644 --- a/.forgejo/workflows/buildAndTest.yml +++ b/.forgejo/workflows/buildAndTest.yml @@ -24,17 +24,6 @@ on: branches: [ "main" ] jobs: - log-the-inputs: - runs-on: debian-latest - steps: - - run: | - echo "Log level: $LEVEL" - echo "Tags: $TAGS" - echo "Environment: $ENVIRONMENT" - env: - LEVEL: ${{ inputs.logLevel }} - TAGS: ${{ inputs.tags }} - build: runs-on: docker From 218d9ef6a8e6c43a7ec4a8b06354b86cfe82521d Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Tue, 18 Aug 2026 21:25:06 +0100 Subject: [PATCH 009/274] build and test release/* --- .forgejo/workflows/buildAndTest.yml | 2 +- .forgejo/workflows/release.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.forgejo/workflows/buildAndTest.yml b/.forgejo/workflows/buildAndTest.yml index 0c48451a3..a0f3a375b 100644 --- a/.forgejo/workflows/buildAndTest.yml +++ b/.forgejo/workflows/buildAndTest.yml @@ -21,7 +21,7 @@ on: push: branches: [ "main" ] pull_request: - branches: [ "main" ] + branches: [ "main", "release/*" ] jobs: build: diff --git a/.forgejo/workflows/release.yml b/.forgejo/workflows/release.yml index ef518037a..f7173b3a0 100644 --- a/.forgejo/workflows/release.yml +++ b/.forgejo/workflows/release.yml @@ -328,4 +328,4 @@ jobs: exit 1 fi - echo "Release publiée: $API_BASE/$GITHUB_REPOSITORY/releases/tag/$TAG" \ No newline at end of file + echo "Release publiée: $API_BASE/$GITHUB_REPOSITORY/releases/tag/$TAG" From 26ba29d060caa3f69835a5b8b7ee5d612491a919 Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Tue, 18 Aug 2026 21:26:40 +0100 Subject: [PATCH 010/274] ci: retrigger Forgejo Actions on PR #37 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PR #37 (feat/postit-fs -> release/1.0.8-rc1) was opened before the buildAndTest.yml workflow trigger was widened to include release/*. Forgejo Actions does not re-evaluate the workflow file on its own — a push event on the PR is needed to re-trigger the CI. This empty commit is the push. From 7d3b2e6a0b5fb722bf821a7b40be814450331fbf Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Tue, 18 Aug 2026 22:01:09 +0100 Subject: [PATCH 011/274] fix(blog): replace IApplicationUser Author with concrete BlogPostAuthorDto MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit System.Text.Json cannot materialise an interface without a polymorphic converter. Until this commit, BlogPostDto.Author was typed as the abstract interface IApplicationUser, which crashed the "load posts" call in PostIt whenever the server returned a post with a populated Author object (the common case — GET /api/BlogApi). Fix: * Introduce a minimum-viable wire DTO BlogPostAuthorDto in Yavsc.Abstract.Blogspot (record: Id, UserName, Avatar). These are the only fields the client UI actually needs; the server-side ApplicationUser navigation is preserved for permission checks and authorisation. * Change IBlogPost.Author and BlogPostDto.Author from IApplicationUser to BlogPostAuthorDto? (interface change, breaking). The EF entity BlogPost keeps its full ApplicationUser navigation property and exposes IBlogPost.Author via an explicit interface implementation that projects to BlogPostAuthorDto on demand (so EF can still lazy-load the navigation without forcing an eager join on every read). * Restore the using directive that was accidentally removed when the BlogPostDto property was rewritten (needed for ICircleAuthorization in GetACL()). Regression coverage (the missing test Paul flagged): * Add BlogPostAuthorDtoTests in PostIt.Tests with four scenarios that exercise the wire shape on the client side: - A BlogPostDto JSON with a populated Author round-trips through JsonSerializer without throwing and the three fields (Id, UserName, Avatar) survive intact. - A BlogPostDto JSON with explicit "author": null deserialises with Author == null. - A BlogPostDto JSON without any Author field at all deserialises with Author == null (forward compat). - The serialised shape of BlogPostAuthorDto uses camelCase property names (matching the server's Web defaults), so the field names on the wire don't drift without a test catching it. Tests: 55/55 PostIt.Tests (+4 new), 24/24 Yavsc.Blogs.Tests, 44/44 Yavsc.Org.Tests. No regressions. Side note: yavsc.sln picks up Yavsc.Api.Client (added by 'feat/postit-acl' in 1.0.7 but never registered in the solution file until now — probably auto-added by a recent 'dotnet build' that discovered the .csproj). --- src/PostIt.Tests/BlogPostAuthorDtoTests.cs | 169 ++++++++++++++++++ src/Yavsc.Abstract/Blogspot/BlogPost.cs | 3 +- .../Blogspot/BlogPostAuthorDto.cs | 33 ++++ src/Yavsc.Abstract/Blogspot/IBlogPost.cs | 8 +- src/Yavsc.Server/Models/Blog/BlogPost.cs | 28 ++- yavsc.sln | 15 ++ 6 files changed, 251 insertions(+), 5 deletions(-) create mode 100644 src/PostIt.Tests/BlogPostAuthorDtoTests.cs create mode 100644 src/Yavsc.Abstract/Blogspot/BlogPostAuthorDto.cs diff --git a/src/PostIt.Tests/BlogPostAuthorDtoTests.cs b/src/PostIt.Tests/BlogPostAuthorDtoTests.cs new file mode 100644 index 000000000..895f220ec --- /dev/null +++ b/src/PostIt.Tests/BlogPostAuthorDtoTests.cs @@ -0,0 +1,169 @@ +using System.Text.Json; +using Yavsc.Blogspot; + +namespace PostIt.Tests; + +/// +/// Round-trip tests for the wire shape of a blog post as +/// serialised by Yavsc.Blogs and consumed by PostIt. +/// +/// +/// Background: in 1.0.7, BlogPostDto.Author was typed as +/// the abstract interface IApplicationUser. System.Text.Json +/// cannot materialise an interface without a polymorphic +/// converter, so the "load posts" call from PostIt crashed when +/// the server returned a post with a populated Author +/// object. The fix replaced IApplicationUser with a thin +/// concrete DTO, BlogPostAuthorDto, embedded directly in +/// BlogPostDto.Author. +/// +/// +/// +/// These tests pin the wire shape: a JSON document with an +/// Author object must deserialise without throwing and +/// must round-trip the three fields PostIt exposes in the UI +/// (Id, UserName, Avatar). They are intentionally placed in +/// PostIt.Tests — the client-side assembly — so the +/// regression is caught at the deserialisation boundary, where +/// it actually manifested in production. +/// +/// +public class BlogPostAuthorDtoTests +{ + private static readonly JsonSerializerOptions CaseInsensitiveJson + = new() { PropertyNameCaseInsensitive = true }; + + [Fact] + public void BlogPostDto_deserialises_with_populated_author() + { + // A representative JSON shape the server would emit for + // GET /api/BlogApi. The Author object is fully populated + // — that's the shape that used to break deserialisation + // when Author was typed as the abstract IApplicationUser + // interface. + var json = """ + { + "id": 42, + "title": "Premier billet", + "article": "Contenu", + "photo": null, + "dateCreated": "2026-08-01T12:00:00Z", + "dateModified": "2026-08-02T12:00:00Z", + "userCreated": "alice", + "userModified": "alice", + "authorId": "u-alice", + "isPublished": true, + "author": { + "id": "u-alice", + "userName": "alice", + "avatar": "/avatars/alice.png" + } + } + """; + + var post = JsonSerializer.Deserialize(json, CaseInsensitiveJson); + + Assert.NotNull(post); + Assert.Equal(42, post!.Id); + Assert.Equal("Premier billet", post.Title); + Assert.Equal("u-alice", post.AuthorId); + Assert.True(post.IsPublished); + + // The actual regression coverage: Author must + // materialise as a concrete DTO, not be left null because + // of a JsonException on IApplicationUser. + Assert.NotNull(post.Author); + Assert.Equal("u-alice", post.Author!.Id); + Assert.Equal("alice", post.Author.UserName); + Assert.Equal("/avatars/alice.png", post.Author.Avatar); + } + + [Fact] + public void BlogPostDto_deserialises_when_author_is_null() + { + // The server is allowed to omit Author (the field is + // nullable on the wire — it maps to a navigation + // property that may not have been Included). The client + // must accept that shape without throwing. + var json = """ + { + "id": 7, + "title": "Sans auteur", + "article": null, + "photo": null, + "dateCreated": "2026-08-01T12:00:00Z", + "dateModified": "2026-08-01T12:00:00Z", + "userCreated": "system", + "userModified": "system", + "authorId": "system", + "isPublished": false, + "author": null + } + """; + + var post = JsonSerializer.Deserialize(json, CaseInsensitiveJson); + + Assert.NotNull(post); + Assert.Null(post!.Author); + Assert.Equal("system", post.AuthorId); + } + + [Fact] + public void BlogPostDto_deserialises_when_author_field_is_missing() + { + // Forward-compatibility: an older server that doesn't + // emit the Author field at all. Should not throw. + var json = """ + { + "id": 9, + "title": "Ancien format", + "article": "Pas d'auteur dans la charge utile", + "photo": null, + "dateCreated": "2026-07-01T12:00:00Z", + "dateModified": "2026-07-01T12:00:00Z", + "userCreated": "bob", + "userModified": "bob", + "authorId": "u-bob", + "isPublished": true + } + """; + + var post = JsonSerializer.Deserialize(json, CaseInsensitiveJson); + + Assert.NotNull(post); + Assert.Null(post!.Author); + } + + [Fact] + public void BlogPostAuthorDto_serialises_back_to_expected_json_shape() + { + // Pin the wire shape on the way out too. The server + // builds BlogPostAuthorDto from an ApplicationUser and + // PostIt receives it as JSON; if the field names + // change (e.g. case) the round-trip on the client side + // is what would silently break. + // + // The server emits camelCase (ASP.NET Core's Web + // defaults — PropertyNamingPolicy = CamelCase). We + // mirror that here so the test reflects what the wire + // actually looks like. PropertyNameCaseInsensitive on + // the client deserialiser means we don't have to + // hardcode the casing for the inbound assertions. + var author = new BlogPostAuthorDto + { + Id = "u-alice", + UserName = "alice", + Avatar = "/avatars/alice.png" + }; + + var json = JsonSerializer.Serialize(author, + new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }); + + using var doc = JsonDocument.Parse(json); + var root = doc.RootElement; + + Assert.True(root.TryGetProperty("id", out _)); + Assert.True(root.TryGetProperty("userName", out _)); + Assert.True(root.TryGetProperty("avatar", out _)); + } +} diff --git a/src/Yavsc.Abstract/Blogspot/BlogPost.cs b/src/Yavsc.Abstract/Blogspot/BlogPost.cs index 0f88fdf81..2406fb2a5 100644 --- a/src/Yavsc.Abstract/Blogspot/BlogPost.cs +++ b/src/Yavsc.Abstract/Blogspot/BlogPost.cs @@ -1,5 +1,4 @@ using System; -using Yavsc.Abstract.Identity; using Yavsc.Abstract.Identity.Security; namespace Yavsc.Blogspot; @@ -8,7 +7,7 @@ public class BlogPostDto : IBlogPost { public string AuthorId { get; set; } - public IApplicationUser Author { get; set; } + public BlogPostAuthorDto? Author { get; set; } public string Article { get; set ; } public string Photo { get; set ; } diff --git a/src/Yavsc.Abstract/Blogspot/BlogPostAuthorDto.cs b/src/Yavsc.Abstract/Blogspot/BlogPostAuthorDto.cs new file mode 100644 index 000000000..e332822e1 --- /dev/null +++ b/src/Yavsc.Abstract/Blogspot/BlogPostAuthorDto.cs @@ -0,0 +1,33 @@ +namespace Yavsc.Blogspot; + +/// +/// Minimum-viable author payload embedded in . +/// +/// +/// Before this record existed, BlogPostDto.Author was typed +/// as the abstract interface IApplicationUser. The +/// interface is fine for server-side contract (we have a concrete +/// entity that implements it) but System.Text.Json cannot +/// materialise an interface without a polymorphic converter +/// configured on both ends. PostIt would crash on load-posts +/// because the JSON contained an Author object that the +/// client could not deserialise. +/// +/// +/// +/// This record is the wire shape: Id for "go to author +/// profile", UserName for "by @username", Avatar +/// for the round badge next to the title. The server-side +/// BlogPost entity (Yavsc.Server.Models.Blog) keeps +/// its full ApplicationUser navigation property for +/// permission checks and authorisation; the DTO is built on +/// demand by the controller / service layer when the post is +/// served to the wire. +/// +/// +public sealed record BlogPostAuthorDto +{ + public string Id { get; init; } = string.Empty; + public string? UserName { get; init; } + public string? Avatar { get; init; } +} diff --git a/src/Yavsc.Abstract/Blogspot/IBlogPost.cs b/src/Yavsc.Abstract/Blogspot/IBlogPost.cs index 5287685df..6090fca22 100644 --- a/src/Yavsc.Abstract/Blogspot/IBlogPost.cs +++ b/src/Yavsc.Abstract/Blogspot/IBlogPost.cs @@ -1,7 +1,6 @@ -using Yavsc.Abstract.Identity; using Yavsc.Abstract.Identity.Security; using Yavsc.Interfaces; @@ -9,6 +8,11 @@ namespace Yavsc.Blogspot { public interface IBlogPost : IBlogPostPayLoad, ICircleAuthorized, ITrackedEntity, ITitle { - IApplicationUser Author { get; } + // Typed as a concrete wire DTO (not the IApplicationUser + // interface) so System.Text.Json can materialise it on the + // client without a polymorphic converter. The server-side + // BlogPost entity implements this getter by mapping its + // ApplicationUser navigation to a BlogPostAuthorDto. + BlogPostAuthorDto? Author { get; } } } diff --git a/src/Yavsc.Server/Models/Blog/BlogPost.cs b/src/Yavsc.Server/Models/Blog/BlogPost.cs index 213910cd4..be9fe7faa 100644 --- a/src/Yavsc.Server/Models/Blog/BlogPost.cs +++ b/src/Yavsc.Server/Models/Blog/BlogPost.cs @@ -107,6 +107,32 @@ namespace Yavsc.Models.Blog [NotMapped] public bool IsPublished { get; set; } - IApplicationUser IBlogPost.Author => Author; + /// + /// Explicit interface implementation of + /// . The underlying + /// navigation property is + /// (an ApplicationUser entity), but the wire + /// DTO is a thin with + /// only the fields the client UI consumes. We project + /// on demand so EF can lazy-load the navigation + /// without forcing an eager join on every read. + /// Returns null when the navigation hasn't been + /// loaded (caller should pre-Include Author if + /// they need it). + /// + BlogPostAuthorDto? IBlogPost.Author + { + get + { + var a = Author; + if (a == null) return null; + return new BlogPostAuthorDto + { + Id = a.Id, + UserName = a.UserName, + Avatar = a.Avatar + }; + } + } } } diff --git a/yavsc.sln b/yavsc.sln index fafdff895..7d972ade4 100644 --- a/yavsc.sln +++ b/yavsc.sln @@ -37,6 +37,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Yavsc.Blogs.Tests", "src\Ya EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Yavsc.Tests.Shared", "src\Yavsc.Tests.Shared\Yavsc.Tests.Shared.csproj", "{34D1F73D-BF74-47CC-9358-9F4F221C75D7}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Yavsc.Api.Client", "src\Yavsc.Api.Client\Yavsc.Api.Client.csproj", "{59AF5DEA-D349-495A-BC44-FC7BD4E55099}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -215,6 +217,18 @@ Global {34D1F73D-BF74-47CC-9358-9F4F221C75D7}.Release|x64.Build.0 = Release|Any CPU {34D1F73D-BF74-47CC-9358-9F4F221C75D7}.Release|x86.ActiveCfg = Release|Any CPU {34D1F73D-BF74-47CC-9358-9F4F221C75D7}.Release|x86.Build.0 = Release|Any CPU + {59AF5DEA-D349-495A-BC44-FC7BD4E55099}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {59AF5DEA-D349-495A-BC44-FC7BD4E55099}.Debug|Any CPU.Build.0 = Debug|Any CPU + {59AF5DEA-D349-495A-BC44-FC7BD4E55099}.Debug|x64.ActiveCfg = Debug|Any CPU + {59AF5DEA-D349-495A-BC44-FC7BD4E55099}.Debug|x64.Build.0 = Debug|Any CPU + {59AF5DEA-D349-495A-BC44-FC7BD4E55099}.Debug|x86.ActiveCfg = Debug|Any CPU + {59AF5DEA-D349-495A-BC44-FC7BD4E55099}.Debug|x86.Build.0 = Debug|Any CPU + {59AF5DEA-D349-495A-BC44-FC7BD4E55099}.Release|Any CPU.ActiveCfg = Release|Any CPU + {59AF5DEA-D349-495A-BC44-FC7BD4E55099}.Release|Any CPU.Build.0 = Release|Any CPU + {59AF5DEA-D349-495A-BC44-FC7BD4E55099}.Release|x64.ActiveCfg = Release|Any CPU + {59AF5DEA-D349-495A-BC44-FC7BD4E55099}.Release|x64.Build.0 = Release|Any CPU + {59AF5DEA-D349-495A-BC44-FC7BD4E55099}.Release|x86.ActiveCfg = Release|Any CPU + {59AF5DEA-D349-495A-BC44-FC7BD4E55099}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -235,5 +249,6 @@ Global {4D283324-6DD3-4CD1-9893-8C317772C6B5} = {CDB1BDB5-53F9-4B43-864F-60F2E74F44E2} {0E471075-DABF-40E9-98B7-1630BEF19145} = {CDB1BDB5-53F9-4B43-864F-60F2E74F44E2} {34D1F73D-BF74-47CC-9358-9F4F221C75D7} = {CDB1BDB5-53F9-4B43-864F-60F2E74F44E2} + {59AF5DEA-D349-495A-BC44-FC7BD4E55099} = {CDB1BDB5-53F9-4B43-864F-60F2E74F44E2} EndGlobalSection EndGlobal From bb180acc0bc2b54467e35cb7546174daa89c9675 Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Tue, 18 Aug 2026 23:24:08 +0100 Subject: [PATCH 012/274] test(postit): pin inoperative toolbar buttons (ACL, Mes cercles, [DEV] Signature) with headless UI tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three buttons on MainPage's toolbar are reported as inoperative in the running app: ACL, Mes cercles, and [DEV] Signature. They click but no dialog / page opens. This commit adds headless UI tests that drive each button via the Avalonia headless harness (KeyPressQwerty(Enter) on a focused, x:Name'd button, per the CalculatorTests pattern in Avalonia.Samples) and asserts the post-click top of NavRoot.NavigationStack is a non-null Page. The tests fail today on every button (stack size before == after == 1): the click does not push anything. The bug is the user's real complaint — the test is now wired to catch it. To make the buttons reachable by the harness without walking the visual tree (which does not see buttons hosted inside a NavigationPage), name the two unnamed buttons: - ACL -> ManageAclButton - Mes cercles -> OpenCirclesButton ([DEV] Signature was already named OpenSignatureDevButton.) The XAML change is cosmetic; bindings and commands are untouched. The test pattern follows SessionStatusBannerTests: new MainWindow().Show(), PushAsync(MainPage), drive controls via their generated x:Name fields. --- src/PostIt.Tests/MainPageButtonsTests.cs | 207 +++++++++++++++++++++++ src/PostIt/PostIt/Views/MainPage.axaml | 8 +- 2 files changed, 213 insertions(+), 2 deletions(-) create mode 100644 src/PostIt.Tests/MainPageButtonsTests.cs diff --git a/src/PostIt.Tests/MainPageButtonsTests.cs b/src/PostIt.Tests/MainPageButtonsTests.cs new file mode 100644 index 000000000..26636635a --- /dev/null +++ b/src/PostIt.Tests/MainPageButtonsTests.cs @@ -0,0 +1,207 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Headless; +using Avalonia.Headless.XUnit; +using Avalonia.Input; +using Avalonia.Interactivity; +using CommunityToolkit.Mvvm.Input; +using Yavsc.Api.Client; +using Yavsc.Blogspot; +using PostIt.Services; +using PostIt.ViewModels; +using PostIt.Views; + +namespace PostIt.Tests; + +/// +/// Regression coverage for the three toolbar buttons on +/// that the user reported as inoperative: +/// "ACL", "Mes cercles", and "[DEV] Signature". +/// +/// Pattern (per the Avalonia headless testing docs — +/// TestableApp.Headless.XUnit/CalculatorTests): name every +/// interactive control in the XAML with x:Name="...", then +/// in the test focus the named control and raise the click via +/// window.KeyPressQwerty(PhysicalKey.Enter, ...). This is +/// the supported path — searching the visual tree via +/// GetVisualDescendants().OfType<Button>() for a +/// button by Content text is brittle and was tried first; it does +/// not work reliably when the page is hosted inside an +/// , which wraps the +/// pushed page in an internal container that the visual-tree walk +/// does not always expose under headless. +/// +/// The assertion is on the post-click top of +/// : +/// the user's bug is "I click and the dialog / page never opens", +/// so the test fails when the click doesn't push anything onto the +/// stack. We pin γ + sniff léger — the new top must be a non-null +/// , but we do not yet assert the concrete type +/// (that would require a fully stubbed App.ServiceProvider, +/// which is the next iteration of this suite). +/// +/// Each test exercises the bit that would silently break if +/// the wiring was reverted: +/// +/// "ACL" — click with a selected post pushes a page onto +/// the stack. +/// "Mes cercles" — click pushes a page onto the stack. +/// "[DEV] Signature" — click pushes a page onto the +/// stack. +/// +/// +public class MainPageButtonsTests +{ + /// + /// Fake that throws on any + /// wire call. These tests never invoke a command that hits + /// the API — only the click → nav side of the pipeline is + /// asserted. + /// + private sealed class ThrowingApi : YavscApiClient + { + public ThrowingApi() : base( + new Settings + { + Authentication = new AuthenticationSettings + { + Authority = "https://stub.invalid", + ClientId = "stub", + Scopes = new[] { "openid" }, + }, + }, + new TokenStore(System.IO.Path.GetTempFileName())) + { } + } + + private static MainPageViewModel MakeViewModel(BlogPostDto? selectedPost = null) + { + var api = new ThrowingApi(); + var blog = new BlogApiClient(api, "http://localhost/"); + var vm = new MainPageViewModel(blog); + if (selectedPost is not null) vm.SelectedPost = selectedPost; + return vm; + } + + /// + /// Mount a real (as + /// SessionStatusBannerTests does), push a + /// with the given VM onto + /// NavRoot. PushAsync is awaited (via + /// GetAwaiter().GetResult()) so the page is on the + /// nav stack before the test tries to interact with its + /// named buttons. The window is shown so the visual tree is + /// realised and KeyPressQwerty has a real + /// to dispatch against. + /// + private static (MainWindow window, MainPage page) MountMainPage(MainPageViewModel vm) + { + var window = new MainWindow(); + var page = new MainPage { DataContext = vm }; + window.Show(); + window.NavRoot.PushAsync(page).GetAwaiter().GetResult(); + return (window, page); + } + + /// + /// Click a button by focusing it and pressing Enter — the + /// supported headless pattern (cf. CalculatorTests in the + /// Avalonia.Samples repo). Returns the nav-stack count + /// before the click so the caller can assert on the delta. + /// KeyPressQwerty is dispatched on the + /// itself — it is the that owns the + /// headless implementation, and routing the key through any + /// descendant TopLevel (e.g. one obtained via + /// TopLevel.GetTopLevel(button)) fails with a + /// NullReferenceException from the headless impl + /// because the descendant does not carry the + /// PlatformHandle the harness expects. + /// + private static int ClickAndCapture(MainWindow window, Button button) + { + var stackBefore = window.NavRoot.NavigationStack.Count; + button.Focus(); + window.KeyPressQwerty(PhysicalKey.Enter, RawInputModifiers.None); + return stackBefore; + } + + [AvaloniaFact] + public void Acl_button_click_pushes_a_page_onto_nav_stack() + { + // Arrange: a VM whose SelectedPost is non-null so + // CanManageAcl evaluates to true and the button is + // armed. + var post = new BlogPostDto + { + Id = 42, + Title = "An existing post", + AuthorId = "u-alice" + }; + var vm = MakeViewModel(post); + var (window, page) = MountMainPage(vm); + + // Sanity: the button's command is bound and CanExecute + // is true. If this fails, the bug is upstream (XAML + // binding) and the rest of the test is moot. + var aclButton = page.ManageAclButton; + Assert.NotNull(aclButton.Command); + Assert.True(aclButton.Command.CanExecute(null)); + + // Act + var stackBefore = ClickAndCapture(window, aclButton); + + // Assert γ + sniff léger: stack grew, new top is a Page. + Assert.True(window.NavRoot.NavigationStack.Count > stackBefore, + $"Click on ACL must push a new page onto the nav stack. Stack size before: {stackBefore}, after: {window.NavRoot.NavigationStack.Count}."); + var pushed = window.NavRoot.NavigationStack.Last(); + Assert.NotNull(pushed); + Assert.IsAssignableFrom(pushed); + } + + [AvaloniaFact] + public void Circles_button_click_pushes_a_page_onto_nav_stack() + { + // Arrange: OpenCircles has no CanExecute guard today — + // any click should fire it and push the page. + var vm = MakeViewModel(); + var (window, page) = MountMainPage(vm); + + var circlesButton = page.OpenCirclesButton; + Assert.NotNull(circlesButton.Command); + + // Act + var stackBefore = ClickAndCapture(window, circlesButton); + + // Assert + Assert.True(window.NavRoot.NavigationStack.Count > stackBefore, + "Click on 'Mes cercles' must push a new page onto the nav stack."); + var pushed = window.NavRoot.NavigationStack.Last(); + Assert.NotNull(pushed); + Assert.IsAssignableFrom(pushed); + } + + [AvaloniaFact] + public void Signature_dev_button_click_pushes_a_page_onto_nav_stack() + { + // Arrange: the "[DEV] Signature" button uses XAML's + // Click="OpenSignatureDev" attribute, so we don't bind + // a Command here — we drive the click directly. The + // handler resolves App.ServiceProvider, which is null + // in a unit test, and early-returns; that is the + // failure mode the test pins. + var vm = MakeViewModel(); + var (window, page) = MountMainPage(vm); + + var signatureButton = page.OpenSignatureDevButton; + + // Act + var stackBefore = ClickAndCapture(window, signatureButton); + + // Assert + Assert.True(window.NavRoot.NavigationStack.Count > stackBefore, + "Click on '[DEV] Signature' must push a new page onto the nav stack."); + var pushed = window.NavRoot.NavigationStack.Last(); + Assert.NotNull(pushed); + Assert.IsAssignableFrom(pushed); + } +} diff --git a/src/PostIt/PostIt/Views/MainPage.axaml b/src/PostIt/PostIt/Views/MainPage.axaml index 0246857e6..86bcf7dde 100644 --- a/src/PostIt/PostIt/Views/MainPage.axaml +++ b/src/PostIt/PostIt/Views/MainPage.axaml @@ -33,8 +33,12 @@