Onboarding : carte d'étape, mise en évidence du contrôle, et carte de la page Import #398

Closed
opened 2026-10-07 23:50:07 +00:00 by maximus · 0 comments
Owner

Milestone planned-2026-10-06-onboarding-parcours-guide, issue 3/6. Phase 2 de l'onboarding (#391) : bandeau de démarrage sur chaque page, et cartes d'étape avec mise en évidence. La spec (spec-decisions-onboarding-parcours-guide.md et spec-plan-onboarding-parcours-guide.md) est locale et ignorée par git : ce corps est auto-suffisant.

Sur la page de l'étape en cours, une carte remplace le bandeau. Elle dit quoi faire, met en évidence le contrôle à utiliser, voit la réussite en direct et mène à l'étape suivante. Cette issue livre la carte générique, la mise en évidence et la carte de la page Import, qui suit l'assistant pour les étapes 2, 3 et 4.

À faire

  1. src/components/onboarding/StepCard.tsx, piloté par props :

    • titre h2 avec tabIndex={-1} : onboarding.card.stepOf puis le libellé de l'étape. Dans « Étape n sur N », N est le nombre de lignes déverrouillées et n le rang (à partir de 1, dans l'ordre de la liste) de l'étape que montre la carte. Sur /import, c'est la première non faite parmi importFolder, source et import, ou import quand les trois sont faites ;
    • le corps du modèle ;
    • une zone role="status" toujours rendue, d'abord vide, qui reçoit onboarding.card.done quand les étapes de la page sont faites ;
    • « Passer cette étape » (onboarding.card.skip) seulement tant que l'étape n'est pas faite ;
    • « Étape suivante : <étape> → » (onboarding.card.next) seulement une fois les étapes de la page faites, en lien vers nextStep.to avec l'état de focus ;
    • sans étape suivante, le mode fin (onboarding.banner.complete, « Ouvrir le guide », « Fermer »).
  2. src/components/onboarding/OnboardingStepCard.tsx, la carte connectée :

    • lit useOnboarding() ;
    • ne se rend que si mode === "card" et que cardPage vaut la route courante ;
    • donne le focus au titre au montage si readOnboardingNavState(location.state).focus est vrai, puis consomme l'état (navigate(pathname, { replace: true, state: null }), une seule fois) ;
    • câble skip, hide et le lien suivant ;
    • la page ne passe que son modèle de carte.

    Chaque page de carte rend OnboardingStepCard en premier enfant, avant son en-tête.

  3. Mise en évidence, dans src/styles.css :

    • [data-onboarding-highlight="true"] { outline: 3px solid var(--accent); outline-offset: 2px; }, distinct de l'anneau de focus ring-[var(--primary)] ;
    • une pulsation par box-shadow, animation-iteration-count: 3 ;
    • @media (prefers-reduced-motion: reduce) : aucune animation.

    Un test lit styles.css, extrait --accent, --background et --card des deux thèmes (:root et .dark) et calcule le contraste WCAG, qui doit être d'au moins 3:1. Si l'accent échoue, créer une variable --onboarding-ring qui passe et la tester.

  4. Propriété onboardingTarget?: boolean. Le composant rend alors data-onboarding-target="<clé>" et data-onboarding-highlight="true". Elle s'ajoute sur :

    • l'action principale d'EmptyState (par EmptyStateAction, EmptyState.tsx:8-10), qu'ImportEmptyState passe à « Choisir un dossier » ou à « Créer un dossier d'exemple » ;
    • SourceList, qui la passe à la première carte de sa boucle (SourceList.tsx:41-52, SourceCard) ;
    • le bouton suivant de WizardNavigation (:52-61).

    ImportFolderConfig ne reçoit pas de cible : sans dossier, l'état vide et son bouton sont toujours affichés (ImportPage.tsx:51, :109).

  5. ImportCardInput dans shared/types (wizardStep, folderLoaded, hasFolder, sourceCount, importing, et importedCount, nombre ou null hors de report). importCardModel(input, checklist) dans onboardingPath.ts lit la table ci-dessous dans l'ordre et s'arrête à la première ligne qui s'applique. importedCount vient de state.importReport.importedCount (ImportReport, shared/types/index.ts:315-323).

  6. ImportPage construit l'entrée, rend OnboardingStepCard et passe la cible du modèle. CARD_PAGES reçoit /import.

  7. #384, fin du flash « aucun dossier » : useImportWizard expose importFolderLoaded.

    • Il est posé dans un finally de la lecture du dossier au montage (:320-332, qui avale aujourd'hui ses erreurs).
    • Il est porté à travers RESET (:290-298, qui reconstruit depuis initialState), avec un test du reducer.
    • L'état vide et la carte l'attendent.
  8. CHANGELOG FR et EN (Added).

Modèle de la carte Import (dans cet ordre)

importing passe avant les compteurs, parce que la ligne imported_files est écrite completed avant les insertions (useImportWizard.ts:1065, puis :1106).

Condition Texte Cible
folderLoaded faux aucune carte —
importing card.import.importing aucune
report avec importedCount === 0 (insertion en échec ou doublons seulement) card.import.failed aucune
report avec importedCount > 0 card.done et « Étape suivante » ou fin aucune
Aucun dossier card.import.noFolder action principale de l'état vide (« Choisir un dossier »)
Dossier, aucune source scannée card.import.noSources « Créer un dossier d'exemple »
source-list avec sources card.import.sourceList première carte de SourceList
source-config card.import.sourceConfig bouton suivant (Aperçu)
file-preview card.import.filePreview bouton suivant (Vérifier les doublons)
duplicate-check card.import.duplicateCheck bouton suivant (Confirmer)
confirm card.import.confirm bouton suivant (Importer)
Étapes 3 et 4 déjà cochées, hors assistant card.done et « Étape suivante » ou fin aucune

Textes (FR / EN)

Clé FR EN
onboarding.card.stepOf Étape {{n}} sur {{total}} Step {{n}} of {{total}}
onboarding.card.skip Passer cette étape Skip this step
onboarding.card.done Fait. Done.
onboarding.card.next Étape suivante : {{step}} → Next step: {{step}} →
onboarding.card.import.noFolder Choisissez le dossier où vous déposerez vos relevés bancaires, avec le bouton {{button}}. Simpl'Résultat y cherchera un sous-dossier par banque. Choose the folder where you will drop your bank statements, with the {{button}} button. Simpl'Résultat will look for one sub-folder per bank in it.
onboarding.card.import.noSources Dans le dossier {{folder}}, créez un sous-dossier par banque et déposez-y vos relevés CSV, puis cliquez {{refresh}}. Pour essayer d'abord, {{sample}} dépose deux banques fictives. In the {{folder}} folder, create one sub-folder per bank and drop your CSV statements in it, then click {{refresh}}. To try first, {{sample}} adds two fictitious banks.
onboarding.card.import.sourceList Cliquez sur la carte de votre banque. Simpl'Résultat reconnaît le format de ses relevés et vous montre ce qu'il a compris. Click your bank's card. Simpl'Résultat recognises the format of its statements and shows you what it understood.
onboarding.card.import.sourceConfig Vérifiez le nom de la source et les fichiers cochés, puis cliquez {{button}}. Check the source name and the selected files, then click {{button}}.
onboarding.card.import.filePreview Vérifiez que les dates et les montants sont bien lus, puis cliquez {{button}}. Check that dates and amounts are read correctly, then click {{button}}.
onboarding.card.import.duplicateCheck Choisissez quoi faire des transactions déjà importées, puis cliquez {{button}}. Choose what to do with transactions already imported, then click {{button}}.
onboarding.card.import.confirm Cliquez {{button}} : la source est enregistrée et vos transactions arrivent. Click {{button}}: the source is saved and your transactions come in.
onboarding.card.import.importing Import en cours… Importing…
onboarding.card.import.failed L'import n'a ajouté aucune transaction ; le rapport ci-dessous dit pourquoi. The import added no transaction; the report below says why.

Interpolations : les libellés de boutons ne sont jamais recopiés.

  • {{button}} :
    • import.empty.noFolder.cta pour noFolder ;
    • import.wizard.preview, import.wizard.checkDuplicates, import.wizard.confirm ou import.wizard.import selon l'état.
  • {{refresh}} : import.empty.refresh.
  • {{sample}} : import.empty.createSample.
  • {{folder}} : folderNameFromPath.
  • {{step}} : onboarding.checklist.items.<clé>.

Fichiers concernés

  • src/components/onboarding/StepCard.tsx (+ StepCard.render.test.tsx), src/components/onboarding/OnboardingStepCard.tsx
  • src/styles.css (+ test de contraste, par exemple src/styles.contrast.test.ts)
  • src/components/shared/EmptyState.tsx (+ EmptyState.test.tsx), src/components/import/ImportEmptyState.tsx (+ test), SourceList.tsx, SourceCard.tsx, WizardNavigation.tsx (+ tests)
  • src/shared/types/index.ts (ImportCardInput), src/utils/onboardingPath.ts (+ test : importCardModel, CARD_PAGES)
  • src/hooks/useImportWizard.ts (+ useImportWizard.test.ts : importFolderLoaded, RESET)
  • src/pages/ImportPage.tsx
  • src/i18n/locales/fr.json, en.json ; CHANGELOG.md, CHANGELOG.fr.md

Depends on

Critères d'acceptation

  • StepCard (tests de rendu) :
    • role="status" présent avant et après la réussite ;
    • h2 avec tabIndex=-1 et « Étape n sur N » ;
    • « Passer » seulement avant la réussite ; « Étape suivante » seulement après ;
    • mode fin.
  • Chaque composant cible : avec onboardingTarget, exactement un élément porte data-onboarding-highlight="true" ; sans, aucun (tests).
  • importCardModel couvre chaque ligne de la table, ordre compris : importing passe avant « étapes cochées », et report avec importedCount === 0 donne failed.
  • Test de contraste à au moins 3:1 dans les deux thèmes. La pulsation est bornée à 3 cycles et absente sous prefers-reduced-motion.
  • Sur /import, dans un profil neuf : la carte remplace le bandeau, suit l'assistant, reste affichée quand les étapes 3 et 4 se cochent, annonce « Fait. » et propose « Étape suivante ». Elle ne dit pas « Fait. » pour un import qui n'ajoute rien.
  • Plus de flash « aucun dossier », y compris après « Annuler » ou « Terminé » (test du reducer sur RESET).
  • Focus sur le titre après une arrivée par « Étape suivante » ; une visite ordinaire, sans état de navigation, ne plante pas.
  • FR et EN ; CHANGELOG FR et EN ; npm run build et npx vitest run verts.

Review caveats

  • La carte doit survivre à sa propre réussite : c'est le verrou de carte de l'issue 1 (cardPage), pas nextStep, qui décide de son affichage.
  • Une cible manquante laisse la carte sans anneau ; aucune requête dans le DOM, la cible est rendue par props.
  • Les composants cibles restent purs (props seulement) pour les tests sans jsdom ; seule OnboardingStepCard lit le contexte.

Décisions prises en planification

  • D2 : carte d'étape avec mise en évidence, réussite en direct et « Étape suivante → ».
  • D10 : une seule carte sur /import qui suit l'assistant ; les étapes 3 et 4 se cochent ensemble.
  • D3 : « Passer » marque l'étape comme passée (icône distincte, comptée), et « Reprendre » l'annule.
  • Mise en évidence : outline en --accent, 3 pulsations au plus, rien sous prefers-reduced-motion ; le focus va sur le titre de la carte, pas sur la cible (WCAG 2.2.2, 1.4.11, 1.4.1, 4.1.3).
  • D8 : un seul état masqué, la préférence onboarding_checklist_hidden par profil ; masquer le bandeau masque la liste, et le réaffichage depuis Paramètres → Utilisateurs rétablit tout.
  • Le fournisseur d'onboarding est le seul écrivain des préférences d'onboarding (hidden, étapes passées, initialisation).

Spec source

spec-plan-onboarding-parcours-guide.md (et spec-decisions-onboarding-parcours-guide.md)

Milestone `planned-2026-10-06-onboarding-parcours-guide`, issue 3/6. Phase 2 de l'onboarding (#391) : bandeau de démarrage sur chaque page, et cartes d'étape avec mise en évidence. La spec (`spec-decisions-onboarding-parcours-guide.md` et `spec-plan-onboarding-parcours-guide.md`) est locale et ignorée par git : ce corps est auto-suffisant. Sur la page de l'étape en cours, une carte remplace le bandeau. Elle dit quoi faire, met en évidence le contrôle à utiliser, voit la réussite en direct et mène à l'étape suivante. Cette issue livre la carte générique, la mise en évidence et la carte de la page Import, qui suit l'assistant pour les étapes 2, 3 et 4. ## À faire 1. **`src/components/onboarding/StepCard.tsx`**, piloté par props : - titre `h2` avec `tabIndex={-1}` : `onboarding.card.stepOf` puis le libellé de l'étape. Dans « Étape n sur N », N est le nombre de lignes déverrouillées et n le rang (à partir de 1, dans l'ordre de la liste) de l'étape que montre la carte. Sur `/import`, c'est la première non faite parmi `importFolder`, `source` et `import`, ou `import` quand les trois sont faites ; - le corps du modèle ; - une zone `role="status"` toujours rendue, d'abord vide, qui reçoit `onboarding.card.done` quand les étapes de la page sont faites ; - « Passer cette étape » (`onboarding.card.skip`) seulement tant que l'étape n'est pas faite ; - « Étape suivante : <étape> → » (`onboarding.card.next`) seulement une fois les étapes de la page faites, en lien vers `nextStep.to` avec l'état de focus ; - sans étape suivante, le mode fin (`onboarding.banner.complete`, « Ouvrir le guide », « Fermer »). 2. **`src/components/onboarding/OnboardingStepCard.tsx`**, la carte connectée : - lit `useOnboarding()` ; - ne se rend que si `mode === "card"` et que `cardPage` vaut la route courante ; - donne le focus au titre au montage si `readOnboardingNavState(location.state).focus` est vrai, puis consomme l'état (`navigate(pathname, { replace: true, state: null })`, une seule fois) ; - câble `skip`, `hide` et le lien suivant ; - la page ne passe que son modèle de carte. Chaque page de carte rend `OnboardingStepCard` en premier enfant, avant son en-tête. 3. **Mise en évidence**, dans `src/styles.css` : - `[data-onboarding-highlight="true"] { outline: 3px solid var(--accent); outline-offset: 2px; }`, distinct de l'anneau de focus `ring-[var(--primary)]` ; - une pulsation par `box-shadow`, `animation-iteration-count: 3` ; - `@media (prefers-reduced-motion: reduce)` : aucune animation. Un test lit `styles.css`, extrait `--accent`, `--background` et `--card` des deux thèmes (`:root` et `.dark`) et calcule le contraste WCAG, qui doit être d'au moins 3:1. Si l'accent échoue, créer une variable `--onboarding-ring` qui passe et la tester. 4. **Propriété `onboardingTarget?: boolean`**. Le composant rend alors `data-onboarding-target="<clé>"` et `data-onboarding-highlight="true"`. Elle s'ajoute sur : - l'action principale d'`EmptyState` (par `EmptyStateAction`, `EmptyState.tsx:8-10`), qu'`ImportEmptyState` passe à « Choisir un dossier » ou à « Créer un dossier d'exemple » ; - `SourceList`, qui la passe à la **première** carte de sa boucle (`SourceList.tsx:41-52`, `SourceCard`) ; - le bouton suivant de `WizardNavigation` (`:52-61`). `ImportFolderConfig` ne reçoit **pas** de cible : sans dossier, l'état vide et son bouton sont toujours affichés (`ImportPage.tsx:51`, `:109`). 5. **`ImportCardInput`** dans `shared/types` (`wizardStep`, `folderLoaded`, `hasFolder`, `sourceCount`, `importing`, et `importedCount`, nombre ou `null` hors de `report`). `importCardModel(input, checklist)` dans `onboardingPath.ts` lit la table ci-dessous dans l'ordre et s'arrête à la première ligne qui s'applique. `importedCount` vient de `state.importReport.importedCount` (`ImportReport`, `shared/types/index.ts:315-323`). 6. **`ImportPage`** construit l'entrée, rend `OnboardingStepCard` et passe la cible du modèle. `CARD_PAGES` reçoit `/import`. 7. **#384, fin du flash « aucun dossier »** : `useImportWizard` expose `importFolderLoaded`. - Il est posé dans un `finally` de la lecture du dossier au montage (`:320-332`, qui avale aujourd'hui ses erreurs). - Il est porté à travers `RESET` (`:290-298`, qui reconstruit depuis `initialState`), avec un test du reducer. - L'état vide et la carte l'attendent. 8. **CHANGELOG** FR et EN (Added). ## Modèle de la carte Import (dans cet ordre) `importing` passe avant les compteurs, parce que la ligne `imported_files` est écrite `completed` avant les insertions (`useImportWizard.ts:1065`, puis `:1106`). | Condition | Texte | Cible | |---|---|---| | `folderLoaded` faux | aucune carte | — | | `importing` | `card.import.importing` | aucune | | `report` avec `importedCount === 0` (insertion en échec ou doublons seulement) | `card.import.failed` | aucune | | `report` avec `importedCount > 0` | `card.done` et « Étape suivante » ou fin | aucune | | Aucun dossier | `card.import.noFolder` | action principale de l'état vide (« Choisir un dossier ») | | Dossier, aucune source scannée | `card.import.noSources` | « Créer un dossier d'exemple » | | `source-list` avec sources | `card.import.sourceList` | première carte de `SourceList` | | `source-config` | `card.import.sourceConfig` | bouton suivant (Aperçu) | | `file-preview` | `card.import.filePreview` | bouton suivant (Vérifier les doublons) | | `duplicate-check` | `card.import.duplicateCheck` | bouton suivant (Confirmer) | | `confirm` | `card.import.confirm` | bouton suivant (Importer) | | Étapes 3 et 4 déjà cochées, hors assistant | `card.done` et « Étape suivante » ou fin | aucune | ## Textes (FR / EN) | Clé | FR | EN | |---|---|---| | `onboarding.card.stepOf` | Étape {{n}} sur {{total}} | Step {{n}} of {{total}} | | `onboarding.card.skip` | Passer cette étape | Skip this step | | `onboarding.card.done` | Fait. | Done. | | `onboarding.card.next` | Étape suivante : {{step}} → | Next step: {{step}} → | | `onboarding.card.import.noFolder` | Choisissez le dossier où vous déposerez vos relevés bancaires, avec le bouton {{button}}. Simpl'Résultat y cherchera un sous-dossier par banque. | Choose the folder where you will drop your bank statements, with the {{button}} button. Simpl'Résultat will look for one sub-folder per bank in it. | | `onboarding.card.import.noSources` | Dans le dossier {{folder}}, créez un sous-dossier par banque et déposez-y vos relevés CSV, puis cliquez {{refresh}}. Pour essayer d'abord, {{sample}} dépose deux banques fictives. | In the {{folder}} folder, create one sub-folder per bank and drop your CSV statements in it, then click {{refresh}}. To try first, {{sample}} adds two fictitious banks. | | `onboarding.card.import.sourceList` | Cliquez sur la carte de votre banque. Simpl'Résultat reconnaît le format de ses relevés et vous montre ce qu'il a compris. | Click your bank's card. Simpl'Résultat recognises the format of its statements and shows you what it understood. | | `onboarding.card.import.sourceConfig` | Vérifiez le nom de la source et les fichiers cochés, puis cliquez {{button}}. | Check the source name and the selected files, then click {{button}}. | | `onboarding.card.import.filePreview` | Vérifiez que les dates et les montants sont bien lus, puis cliquez {{button}}. | Check that dates and amounts are read correctly, then click {{button}}. | | `onboarding.card.import.duplicateCheck` | Choisissez quoi faire des transactions déjà importées, puis cliquez {{button}}. | Choose what to do with transactions already imported, then click {{button}}. | | `onboarding.card.import.confirm` | Cliquez {{button}} : la source est enregistrée et vos transactions arrivent. | Click {{button}}: the source is saved and your transactions come in. | | `onboarding.card.import.importing` | Import en cours… | Importing… | | `onboarding.card.import.failed` | L'import n'a ajouté aucune transaction ; le rapport ci-dessous dit pourquoi. | The import added no transaction; the report below says why. | Interpolations : les libellés de boutons ne sont jamais recopiés. - `{{button}}` : - `import.empty.noFolder.cta` pour `noFolder` ; - `import.wizard.preview`, `import.wizard.checkDuplicates`, `import.wizard.confirm` ou `import.wizard.import` selon l'état. - `{{refresh}}` : `import.empty.refresh`. - `{{sample}}` : `import.empty.createSample`. - `{{folder}}` : `folderNameFromPath`. - `{{step}}` : `onboarding.checklist.items.<clé>`. ## Fichiers concernés - src/components/onboarding/StepCard.tsx (+ StepCard.render.test.tsx), src/components/onboarding/OnboardingStepCard.tsx - src/styles.css (+ test de contraste, par exemple src/styles.contrast.test.ts) - src/components/shared/EmptyState.tsx (+ EmptyState.test.tsx), src/components/import/ImportEmptyState.tsx (+ test), SourceList.tsx, SourceCard.tsx, WizardNavigation.tsx (+ tests) - src/shared/types/index.ts (`ImportCardInput`), src/utils/onboardingPath.ts (+ test : `importCardModel`, `CARD_PAGES`) - src/hooks/useImportWizard.ts (+ useImportWizard.test.ts : `importFolderLoaded`, `RESET`) - src/pages/ImportPage.tsx - src/i18n/locales/fr.json, en.json ; CHANGELOG.md, CHANGELOG.fr.md ## Depends on - #397 ## Critères d'acceptation - [ ] `StepCard` (tests de rendu) : - `role="status"` présent avant et après la réussite ; - `h2` avec `tabIndex=-1` et « Étape n sur N » ; - « Passer » seulement avant la réussite ; « Étape suivante » seulement après ; - mode fin. - [ ] Chaque composant cible : avec `onboardingTarget`, exactement un élément porte `data-onboarding-highlight="true"` ; sans, aucun (tests). - [ ] `importCardModel` couvre chaque ligne de la table, ordre compris : `importing` passe avant « étapes cochées », et `report` avec `importedCount === 0` donne `failed`. - [ ] Test de contraste à au moins 3:1 dans les deux thèmes. La pulsation est bornée à 3 cycles et absente sous `prefers-reduced-motion`. - [ ] Sur /import, dans un profil neuf : la carte remplace le bandeau, suit l'assistant, reste affichée quand les étapes 3 et 4 se cochent, annonce « Fait. » et propose « Étape suivante ». Elle ne dit pas « Fait. » pour un import qui n'ajoute rien. - [ ] Plus de flash « aucun dossier », y compris après « Annuler » ou « Terminé » (test du reducer sur `RESET`). - [ ] Focus sur le titre après une arrivée par « Étape suivante » ; une visite ordinaire, sans état de navigation, ne plante pas. - [ ] FR et EN ; CHANGELOG FR et EN ; `npm run build` et `npx vitest run` verts. ## Review caveats - La carte doit survivre à sa propre réussite : c'est le verrou de carte de l'issue 1 (`cardPage`), pas `nextStep`, qui décide de son affichage. - Une cible manquante laisse la carte sans anneau ; aucune requête dans le DOM, la cible est rendue par props. - Les composants cibles restent purs (props seulement) pour les tests sans jsdom ; seule `OnboardingStepCard` lit le contexte. ## Décisions prises en planification - D2 : carte d'étape avec mise en évidence, réussite en direct et « Étape suivante → ». - D10 : une seule carte sur /import qui suit l'assistant ; les étapes 3 et 4 se cochent ensemble. - D3 : « Passer » marque l'étape comme passée (icône distincte, comptée), et « Reprendre » l'annule. - Mise en évidence : `outline` en `--accent`, 3 pulsations au plus, rien sous `prefers-reduced-motion` ; le focus va sur le titre de la carte, pas sur la cible (WCAG 2.2.2, 1.4.11, 1.4.1, 4.1.3). - D8 : un seul état masqué, la préférence `onboarding_checklist_hidden` par profil ; masquer le bandeau masque la liste, et le réaffichage depuis Paramètres → Utilisateurs rétablit tout. - Le fournisseur d'onboarding est le seul écrivain des préférences d'onboarding (`hidden`, étapes passées, initialisation). ## Spec source spec-plan-onboarding-parcours-guide.md (et spec-decisions-onboarding-parcours-guide.md)
maximus added this to the planned-2026-10-06-onboarding-parcours-guide milestone 2026-10-07 23:50:07 +00:00
maximus added the
status:ready
type:feature
source:human
labels 2026-10-07 23:50:07 +00:00
maximus added
status:review
and removed
status:ready
labels 2026-10-08 12:37:21 +00:00
maximus added
status:approved
and removed
status:review
labels 2026-10-08 23:48:23 +00:00
maximus removed the
status:approved
label 2026-10-09 01:11:27 +00:00
Sign in to join this conversation.
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: maximus/Simpl-Resultat#398
No description provided.