From 4cda942fb4090719faf4a2ed0d2395b75b4d198c Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Wed, 19 Aug 2026 17:19:43 +0100 Subject: [PATCH] doc: lift PostIt navigation rule to top-level section MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- CONTRIBUTING.md | 94 +++++++++++++++++++++++++++---------------------- 1 file changed, 51 insertions(+), 43 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 847b2954..7a045bbd 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -49,6 +49,57 @@ Les tests sont répartis en : 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` +ou `AddSingleton`) **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 une `View` depuis 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 laisse + `App` orchestrer le `PushAsync` physique. +- Le ViewModel qui déclenche la nav ne capture pas de + référence à `MainWindow` ou `NavigationPage`. Il passe + par `App.Current` (l'app Avalonia est un singleton). + +Exemple canonique (depuis `MainPageViewModel`) : + +```csharp +[RelayCommand] +internal async Task OpenSettings() +{ + var settingsVm = ((App)App.Current!).ServiceProvider + .GetRequiredService(); + await ((App)App.Current!).PushPageAsync(settingsVm) + .ConfigureAwait(true); +} +``` + +Cf. [doc/architecture/postit.md](./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 @@ -64,49 +115,6 @@ Quelques règles non capturées par `.editorconfig` : - Préférer les types BCL (`int`, `string`) aux types framework (`Int32`, `String`). - Préférer les expressions de pattern matching aux casts explicites. -- **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` - ou `AddSingleton`) **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 une `View` depuis 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 laisse - `App` orchestrer le `PushAsync` physique. - - Le ViewModel qui déclenche la nav ne capture pas de - référence à `MainWindow` ou `NavigationPage`. Il passe - par `App.Current` (l'app Avalonia est un singleton). - - Exemple canonique (depuis `MainPageViewModel`) : - - ```csharp - [RelayCommand] - internal async Task OpenSettings() - { - var settingsVm = ((App)App.Current!).ServiceProvider - .GetRequiredService(); - await ((App)App.Current!).PushPageAsync(settingsVm) - .ConfigureAwait(true); - } - ``` ## Branches & commits