The 'Navigation (PostIt)' rule was buried as a sub-item under
'Conventions de code', mixed with style rules. Lift it to a
top-level section between 'Tests' and 'Conventions de code' so
contributors looking for nav guidance find it without scrolling
through editorconfig preferences.
Add a pointer to doc/architecture/postit.md for the full
topology (NavRoot, SessionStatusViewModel, lifecycle signals
vs user-driven nav). Content of the rule itself is unchanged
from 12a71ada — only the placement and the cross-link.
11 KiB
Contribuer à Yavsc
Pré-requis
- .NET 10 SDK
- PostgreSQL (≥ 14) — local ou via
docker compose up(cf.docker-compose.yaml) - Node.js (uniquement pour la toolchain Avalonia Browser/WASM)
- Pour Android : Android SDK + workload
dotnet workload install android
Premier build
git clone https://github.com/pazof/yavsc.git
cd yavsc
dotnet restore
dotnet build
Pour exécuter Yavsc.Org en local :
cp src/Yavsc.Org/appsettings-org.json src/Yavsc.Org/appsettings-org.Development.json
$EDITOR src/Yavsc.Org/appsettings-org.Development.json # renseigner Site.Authority, ConnectionStrings.YavscConnection, …
ASPNETCORE_ENVIRONMENT=Development dotnet run --project src/Yavsc.Org
Cf. doc/Architecture.md section "Paramétrage" pour le détail des variables à renseigner.
Tests
dotnet test
Les tests sont répartis en :
src/Yavsc.Org.Tests/— tests d'intégration du front web (TestWebApplicationFactory<Program>+ EF InMemory). Comprend les smoke tests par BC sousSmoke/:AccountSmokeTests(GET /signin),BlogSmokeTests(GET /BlogSpot/Index). Chaque test couvre une bounded context DDD au sens dedoc/ddd-exploration-2026-06-14.md: il démarre le host en mémoire viaWebApplicationFactory<Program>et vérifie qu'une route publique de la BC répond en 2xx/3xx (ou 401/403 si elle exige une authentification). Cf. ROADMAP.md item « Tests d'intégration smoke par BC ».src/PostIt.Tests/— tests unitaires du client desktop PostIt.
Navigation (PostIt)
La navigation est centralisée dans
App.PushPageAsync(ViewModelBase vm) (src/PostIt/PostIt/App.axaml.cs).
Pour ouvrir un écran, un ViewModel (généralement dans une
commande [RelayCommand]) appelle
await ((App)App.Current!).PushPageAsync(targetVm).ConfigureAwait(true);.
PushPageAsync résout la Control correspondante via le
ViewLocator (un IDataTemplate enregistré dans
Application.DataTemplates au boot), l'identifie comme
Page, lui assigne le VM comme DataContext, et appelle
NavRoot.PushAsync(page). Une garde anti-empilement
compare par référence la nouvelle page au sommet courant
de la stack pour éviter un push doublon.
Pour qu'une nouvelle page soit navigable, il faut deux
enregistrements : la page dans le DI (AddTransient<TPage>
ou AddSingleton<TPage>) et une case dans le switch
de ViewLocator.Build. Si l'un manque, l'app affiche
"No view for X" sans crash.
Règles :
- On n'instancie jamais une
Viewà la main depuis un ViewModel, on ne récupère jamais uneViewdepuis la DI directement dans un ViewModel. - Le ViewModel qui déclenche la nav ne pousse pas lui-même
la page ; il appelle
App.PushPageAsync(vm)et laisseApporchestrer lePushAsyncphysique. - Le ViewModel qui déclenche la nav ne capture pas de
référence à
MainWindowouNavigationPage. Il passe parApp.Current(l'app Avalonia est un singleton).
Exemple canonique (depuis MainPageViewModel) :
[RelayCommand]
internal async Task OpenSettings()
{
var settingsVm = ((App)App.Current!).ServiceProvider
.GetRequiredService<Settings>();
await ((App)App.Current!).PushPageAsync(settingsVm)
.ConfigureAwait(true);
}
Cf. doc/architecture/postit.md
pour la topologie complète (host de navigation,
SessionStatusViewModel, signaux de cycle de vie vs nav
utilisateur).
Conventions de code
Le repo applique .editorconfig (UTF-8, LF, indent_size = 4 en
C#, 2 en Razor / XML / .axaml). Pas de formatter dédié — les EDI
(Visual Studio, Rider) appliquent automatiquement les règles Roslyn
- EditorConfig.
Quelques règles non capturées par .editorconfig :
usingtriés par groupe (System, puis packages, puis local). Pas de séparation visuelle entre groupes (cf.dotnet_separate_import_directive_groups = false).- Préférer les types BCL (
int,string) aux types framework (Int32,String). - Préférer les expressions de pattern matching aux casts explicites.
Branches & commits
-
Trunk-based sur
main. Pas de branche longue durée pour le moment ; on lande directement surmainvia PR (quand le projet attire de l'attention) ou push direct (mode solo). -
Un commit = un changement logique. Regrouper les fichiers qui touchent au même flux dans un seul commit (login + tests + doc = un commit), mais séparer les commits de reformulation d'historique (« refactor », « typo ») des commits de feature dans la mesure du possible.
-
Messages de commit en anglais, format :
<scope>: <imperative summary> <body — what changed and why, not how>Le scope est le nom du sous-projet (
postit,doc,readme,yavsc.org,yavsc.api, …). Pour les commits qui touchent plusieurs sous-projets, préférer un scope générique (readme:,doc:).
Découpage des projets
Cf. doc/architecture/decoupage-organisation.md.
qui couvre à la fois le build, la publication et les images
runtime, plus un second Dockerfile minimal (Dockerfile.backend)
utilisé uniquement par le workflow de publication de l'image
de production.
| Dockerfile | Stages | Construit par |
|---|---|---|
Dockerfile |
build-env (default), publish-org/api/blogs, web-runtime, api-runtime, blogs-runtime |
docker compose build + .github/workflows/docker-publish-android.yml |
Dockerfile.backend |
Idem limité à build-env + publish-org (suffisant pour publier l'image pazof/yavsc) |
.github/workflows/docker-publish-backend.yml |
L'image de base est construite depuis le dépôt sibling
dotnet-android-build-image (Debian 12 + .NET 10 SDK + Android
SDK 36 + workload .NET Android). Elle est poussée sur Docker Hub
sous le tag pazof/yavsc-build-env:debian12-dotnet10-android36-v1.
Le tag est déclaré comme ARG BUILD_ENV_TAG au début du
Dockerfile (et de Dockerfile.backend) — il faut le bumper en
lockstep dans les deux fichiers et dans docker-compose.yaml
(chaque bloc build.args.BUILD_ENV_TAG) quand l'image de base
est reconstruite.
docker compose up
sudo docker compose up --build
Démarre 4 services : db (PostgreSQL 16), web (Yavsc.Org),
api (Yavsc.Api), blogs (Yavsc.Blogs). Chaque service runtime
pointe sur le stage correspondant du Dockerfile multi-stage via
build.target. Les services runtime attendent le healthcheck
pg_isready de db avant de démarrer.
Sur une machine vierge, db, api et blogs démarrent. Le
service web (Yavsc.Org) échoue avec :
Production IdentityServer requires a signing certificate. Configure Kestrel:Endpoints:Https:Certificate:{Path,KeyPath}.
C'est attendu : IdentityServer8 en mode Production exige un cert
HTTPS pour signer les tokens (cf. src/Yavsc.Org/Extensions/HostingExtensions.cs:374).
Le critère de sortie Jalon 0 « docker compose up vert sur machine
vierge » n'est donc pas « tout démarre sans rien » — c'est
« tout démarre après la procédure d'installation qui monte
un cert HTTPS valide, cf. HTTPS en production ci-dessous ».
Le montage de /etc/letsencrypt (volume commenté par défaut
dans docker-compose.yaml) est l'étape qui distingue une
machine configurée d'une machine vierge.
appsettings-org.json
Le fichier de configuration de prod (src/Yavsc.Org/appsettings-org.json)
n'est pas commité. Il est injecté dans chaque image runtime
via un BuildKit secret mount — le fichier reste sur l'hôte,
n'apparaît dans aucun layer :
secrets:
yavsc_appsettings:
file: ./src/Yavsc.Org/appsettings-org.json
Compose le passe automatiquement à docker build via le bloc
build.secrets de chaque service runtime.
HTTPS en production
En dev local les services runtime sont forcés à HTTP seul par
un bloc environment explicite dans docker-compose.yaml :
environment:
ASPNETCORE_URLS: "http://+:5000"
ASPNETCORE_HTTPS_PORT: ""
C'est nécessaire parce que appsettings-org.Development.json
positionne Site.Authority = https://localhost:5001, ce qui
pousse Kestrel à essayer de binder HTTPS même sans certificat
disponible — et échoue proprement avec « Unable to configure
HTTPS endpoint. No server certificate was specified ».
En production, sur chaque service runtime de docker-compose.yaml :
-
Remplacer l'
environment.ASPNETCORE_URLSpar la forme double-bindhttp://+:5000;https://+:5001(ou équivalent pour Api / Blogs). -
Décommenter le port HTTPS correspondant (
5001pour Org,5003pour Api,5005pour Blogs). -
Décommenter le volume
/etc/letsencrypt:/etc/letsencrypt:ro— monter le répertoire Let's Encrypt de l'hôte en lecture seule pour que Kestrel accède aux fichiers.pem. -
Dans
appsettings-org.json, ajouter un blocKestrel:Endpointspointant vers les chemins du volume monté. Exemple :"Kestrel": { "Endpoints": { "Https": { "Url": "https://+:5001", "Certificate": { "Path": "/etc/letsencrypt/live/yavsc.example/fullchain.pem", "KeyPath": "/etc/letsencrypt/live/yavsc.example/privkey.pem" } } } } -
Régénérer l'image runtime (les
appsettingssont baked dans l'image via BuildKit secret mount — cf. section appsettings-org.json).
Bumper l'image de build
Quand on bumpe Debian, .NET SDK ou Android SDK, reconstruire l'image de base :
cd ../dotnet-android-build-image
docker build -t pazof/yavsc-build-env:debian12-dotnet10-android36-v2 .
docker push pazof/yavsc-build-env:debian12-dotnet10-android36-v2
Puis bumper en lockstep dans :
Dockerfile(ARGBUILD_ENV_TAGen tête de fichier)Dockerfile.backend(idem)docker-compose.yaml(chaque blocbuild.args.BUILD_ENV_TAG).
Vérifier un build isolé d'un stage runtime
docker build \
--secret id=yavsc_appsettings,src=src/Yavsc.Org/appsettings-org.json \
--target web-runtime \
-t yavsc-org:dev .
docker run --rm -p 5000:5000 yavsc-org:dev
et une roadmap à jour dans ROADMAP.md. Toute modification de modèle doit être précédée d'une note DDD ; les BC (Conciliation, etc.) listés dans ces docs sont les cibles de conception.
Sessions DDD
Le repo tient un journal de design DDD sous doc/ddd-exploration-*.md
et une roadmap à jour dans ROADMAP.md. Toute
modification de modèle doit être précédée d'une note DDD ; les BC
(Conciliation, etc.) listés dans ces docs sont les cibles de
conception.
Sécurité
Cf. la section "🔒 Hygiène transverse (à tous les jalons)" de
ROADMAP.md. En particulier : ne jamais committer
de secret, utiliser dotnet user-secrets en développement et les
variables d'environnement ASPNETCORE_* en production.
Questions
Ouvrir une issue GitHub, ou — pour les questions de design —
démarrer une session DDD et la consigner dans doc/.