Skip to content

feat(charges): CadenceField unified cadence picker (THI-301) - #228

Merged
thierryvm merged 4 commits into
mainfrom
feat/thi-301-cadencefield
Jul 18, 2026
Merged

feat(charges): CadenceField unified cadence picker (THI-301)#228
thierryvm merged 4 commits into
mainfrom
feat/thi-301-cadencefield

Conversation

@thierryvm

@thierryvm thierryvm commented Jul 18, 2026

Copy link
Copy Markdown
Owner

PR-D — CadenceField : saisie de cadence unifiée (THI-301)

Le dernier gros irritant UX des charges : la cadence se saisissait via 3 champs séparés (fréquence / « Mois de référence » toujours affiché même en mensuel / jour en input libre) — et tu avais dit le trouver déroutant. Remplacé par un seul cluster clair, dans les deux points de saisie (form création + drawer édition).

Ce que ça donne

  • Mensuel → « Prélevé le [15 ▾] de chaque mois » — le mois-ancre disparaît (il n'avait aucun sens).
  • Trimestriel/Semestriel/Annuel → « Prélevé le [15 ▾] à partir de [mars ▾] » — tout reste éditable à tout moment.
  • Ligne de résumé humaine : « Prélevé le 15 : mars, juin, sept., déc. » — l'arithmétique des fréquences expliquée en mots, plus besoin de la comprendre.
  • Option « Dernier jour du mois » (= jour 31 sous le capot ; le clamp domaine année-conscient en fait 30/29/28 automatiquement — fin de mois par design, zéro piège bissextile).
  • Selects natifs (décision verrouillée : a11y iOS imbattable, garde anti auto-zoom Safari).

Pour corriger TES dates réelles

C'est l'outil qui manquait : ouvre une charge (crayon), ajuste fréquence/ancre/jour avec l'aperçu des mois sous les yeux, sauve — payment_months est recalculé proprement. (Ex. S.W.D.E : trimestriel à partir de mai → « le 10 : févr., mai, août, nov. ».)

Gouvernance

Design + plan plan-reviewer APPROVED (2026-06-02), exécutés avec re-vérification du code actuel (mandat Fable 5). Zéro modif schéma/domaine/Server Action — voie légère. La démo design-playground du plan est délibérément omise (nice-to-have QA locale, à ajouter si tu la veux).

Tests

9 CadenceField (résumés, Dernier jour, a11y labels, émissions onChange) + 2 tests existants adaptés. Suite complète 1484 verte, typecheck + lint + build clean.

Smoke @Thierry (desktop + mobile)

/app/charges → « + Ajouter une charge » : mensuel = pas de mois visible ; passe en trimestriel → « À partir de » apparaît + le résumé liste les 4 mois ; choisis « Dernier jour du mois » ; édite une charge existante (crayon) → même cluster pré-rempli.

🤖 Generated with Claude Code

Summary by Sourcery

Introduire un composant unifié CadenceField pour éditer la cadence des frais récurrents dans les flux de création et de modification, en remplaçant les champs séparés précédents (fréquence, mois d’ancrage et jour de paiement) tout en préservant les contrats de domaine existants et les actions serveur.

New Features:

  • Ajouter un composant d’interface réutilisable CadenceField qui capture la fréquence, le mois d’ancrage et le jour de paiement, avec un résumé lisible de la cadence et une option « dernier jour du mois ».
  • Connecter CadenceField au formulaire de création de frais et au tiroir d’édition afin de fournir une expérience unifiée et cohérente pour la modification de la cadence sur les deux points d’entrée.
  • Ajouter de la documentation de conception et d’implémentation pour le comportement et l’UX de CadenceField dans les différentes locales.

Enhancements:

  • Affiner les messages i18n dans toutes les locales prises en charge afin de gérer les nouveaux libellés et résumés de cadence sans modifier les sémantiques de domaine existantes.

Tests:

  • Ajouter des tests unitaires ciblés pour le composant CadenceField et mettre à jour les tests existants de ChargesClient afin de couvrir la nouvelle interface et le nouveau comportement de cadence.
Original summary in English

Summary by Sourcery

Introduce a unified CadenceField component for editing recurring charge cadence in both creation and edit flows, replacing the previous separate frequency, anchor month, and payment day inputs while preserving existing domain contracts and server actions.

New Features:

  • Add a reusable CadenceField UI component that captures frequency, anchor month, and payment day with a human-readable cadence summary and a 'last day of month' option.
  • Wire CadenceField into the charges create form and edit drawer to provide a single, consistent cadence editing experience across both entry points.
  • Add design and implementation documentation for the CadenceField behavior and UX across locales.

Enhancements:

  • Refine i18n messages across all supported locales to support the new cadence labels and summaries without changing existing domain semantics.

Tests:

  • Add focused unit tests for the CadenceField component and update existing ChargesClient tests to cover the new cadence UI and behavior.

…dit drawer (THI-301)

Replaces the 3 separate fields (frequency select / always-visible anchor
month / day number input) with one controlled cluster, in BOTH entry points:

- Native <select>s (iOS a11y locked decision; ankora-form-control-16 guards
  the Safari auto-zoom), 1..30 + explicit 'Dernier jour du mois' option
  mapping to paymentDay=31 (the domain year-aware clamp already turns 31
  into 30/29/28 — end-of-month by design, no leap-year trap).
- Anchor month HIDDEN for monthly charges (pure noise removed) and shown as
  'À partir de [mars]' otherwise; everything stays editable at all times.
- Human summary line: 'Prélevé le 15 : mars, juin, sept., déc.' derived via
  paymentMonthsFromFrequency — the confusing frequency arithmetic explained
  in plain words (@Thierry verbatim: frequencies are confusing).
- Zero schema/domain change; Server Actions consume the same
  {frequency, dueMonth, paymentDay} as before.

i18n: app.charges.cadence.* x5 locales (labels reuse the existing wording).
Design + plan: docs/plans/THI-301-cadencefield-{design,plan}.md (plan-reviewer
APPROVED 2026-06-02, executed per @Thierry's Fable-5 mandate with reality
re-verification). Tests: 9 CadenceField + 2 adapted; full suite 1484 green.
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@vercel

vercel Bot commented Jul 18, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
ankora Ready Ready Preview, Comment Jul 18, 2026 10:01pm

@github-actions github-actions Bot added status:review-needed Ready for review type:feat New user-facing feature labels Jul 18, 2026
@sourcery-ai

sourcery-ai Bot commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

Guide du relecteur

Introduit un nouveau composant unifié CadenceField pour la sélection de la cadence de facturation et l’intègre à la fois dans le formulaire de création et le tiroir d’édition, avec les mises à jour i18n, tests et documentation, tout en laissant inchangées la logique métier et les actions côté serveur.

Diagramme de séquence pour la mise à jour de l’état du formulaire parent par CadenceField via onChange

sequenceDiagram
  actor User
  participant CadenceField
  participant ChargesClient

  User->>CadenceField: change frequency/day/month selects
  CadenceField->>ChargesClient: onChange(next)
  ChargesClient->>ChargesClient: setFrequency(next.frequency)
  ChargesClient->>ChargesClient: setDueMonth(String(next.dueMonth))
  ChargesClient->>ChargesClient: setPaymentDay(String(next.paymentDay))
Loading

Modifications au niveau des fichiers

Changement Détails Fichiers
Ajouter un composant contrôlé CadenceField qui encapsule la sélection de la fréquence, du mois d’ancrage, du jour de paiement, ainsi qu’un récapitulatif lisible de la cadence en utilisant des listes déroulantes natives.
  • Définir les types CadenceValue et CadenceFieldProps basés sur le type de domaine Charge
  • Afficher trois contrôles natifs étiquetés pour la fréquence, le jour (y compris une option spéciale pour le dernier jour) et, de manière conditionnelle, le mois d’ancrageCalculer une chaîne de résumé en utilisant les nouvelles clés i18n app.charges.cadence et paymentMonthsFromFrequency, sans effectuer de calculs de dates dans l’UIExposer des data-testid stables basés sur un idPrefix pour permettre la réutilisation dans les formulaires de création/édition et les tests
src/app/[locale]/app/charges/CadenceField.tsx
Remplacer les trois champs de cadence séparés dans le formulaire de création de charge par le nouveau CadenceField, en préservant la logique de soumission existante et la forme de l’état au niveau de la frontière du composant.
  • Importer CadenceField dans ChargesClient et supprimer les imports inutilisés relatifs à Select/aux mois dans i18n
  • Regrouper les contrôles de cadence dans une seule instance de CadenceField avec l’idPrefix create-charge
  • Convertir l’état de formulaire de type chaîne pour dueMonth et paymentDay en nombres lors du passage à CadenceField, puis les reconvertir en chaînes dans le gestionnaire onChange
  • Laisser inchangés le payload de createChargeAction et l’utilisation de paymentMonthsFromFrequency
src/app/[locale]/app/charges/ChargesClient.tsx
Remplacer les trois champs de cadence séparés dans le tiroir d’édition de charge par CadenceField, en conservant le comportement d’interaction avec le serveur et de normalisation.
  • Importer CadenceField dans ChargeEditDrawer et supprimer les anciens champs de cadence basés sur Select
  • Passer la fréquence, dueMonth et paymentDay du tiroir dans CadenceField avec l’idPrefix edit-charge, en les convertissant en nombres et en définissant par défaut paymentDay à 1
  • Mettre à jour l’état local en réponse au onChange de CadenceField tout en conservant l’utilisation de Number(dueMonth)/Number(paymentDay) et paymentMonthsFromFrequency dans submit()
  • Conserver la constante FREQUENCIES pour normalizeFrequency tout en supprimant la constante de clé de mois désormais inutilisée et les hooks useId des anciens champs
src/app/[locale]/app/charges/ChargeEditDrawer.tsx
Ajouter des tests unitaires ciblés pour CadenceField et adapter les tests existants de ChargesClient à la nouvelle UI de cadence et aux nouveaux testids.
  • Créer des tests pour CadenceField couvrant le comportement mensuel vs non mensuel, l’option de dernier jour, le texte de résumé, les émissions onChange et les associations de labels pour l’accessibilité (a11y) en utilisant les messages fr-BE
  • Mettre à jour les tests de layout de ChargesClient pour vérifier la présence du résumé create-charge à la place du champ d’ancrage mensuel supprimé
  • Ajuster les tests du tiroir d’édition pour lire la nouvelle valeur du select edit-charge-day comme une chaîne plutôt que l’ancien champ numérique
  • S’assurer que les tests utilisent NextIntlClientProvider avec la locale et le fuseau horaire appropriés
src/app/[locale]/app/charges/__tests__/CadenceField.test.tsx
src/app/[locale]/app/charges/__tests__/ChargesClient.test.tsx
Étendre les messages i18n et la documentation pour prendre en charge la nouvelle UI de cadence et fournir un plan de conception et d’implémentation pour CadenceField.
  • Ajouter des clés app.charges.cadence pour les labels, la formulation du dernier jour et les modèles de résumé dans toutes les locales prises en charge (fr-BE, en, nl-BE, de-DE, es-ES)
  • Introduire les documents de plan d’implémentation et de spécification de conception THI-301 cadencefield décrivant l’architecture, le comportement, les frontières de domaine et les attentes en matière de tests/QA
  • Aligner la formulation afin que les sélecteurs existants getByLabelText restent pour la plupart valides en réutilisant les labels « Fréquence » et « Jour du mois » lorsque c’est approprié
messages/de-DE.json
messages/en.json
messages/es-ES.json
messages/fr-BE.json
messages/nl-BE.json
docs/plans/THI-301-cadencefield-plan.md
docs/plans/THI-301-cadencefield-design.md

Conseils et commandes

Interagir avec Sourcery

  • Déclencher une nouvelle revue : Commentez @sourcery-ai review sur la pull request.
  • Poursuivre les discussions : Répondez directement aux commentaires de revue de Sourcery.
  • Générer une issue GitHub à partir d’un commentaire de revue : Demandez à Sourcery de créer une issue à partir d’un commentaire de revue en y répondant. Vous pouvez aussi répondre à un commentaire de revue avec @sourcery-ai issue pour créer une issue à partir de celui-ci.
  • Générer un titre de pull request : Écrivez @sourcery-ai n’importe où dans le titre de la pull request pour générer un titre à tout moment. Vous pouvez également commenter
    @sourcery-ai title sur la pull request pour (re)générer le titre à tout moment.
  • Générer un résumé de pull request : Écrivez @sourcery-ai summary n’importe où dans le corps de la pull request pour générer un résumé de PR à tout moment exactement à l’endroit souhaité. Vous pouvez également commenter
    @sourcery-ai summary sur la pull request pour (re)générer le résumé à tout moment.
  • Générer le guide du relecteur : Commentez @sourcery-ai guide sur la pull request pour (re)générer le guide du relecteur à tout moment.
  • Résoudre tous les commentaires Sourcery : Commentez @sourcery-ai resolve sur la pull request pour résoudre tous les commentaires Sourcery. Utile si vous avez déjà traité tous les commentaires et ne voulez plus les voir.
  • Ignorer toutes les revues Sourcery : Commentez @sourcery-ai dismiss sur la pull request pour ignorer toutes les revues Sourcery existantes. Particulièrement utile si vous voulez repartir de zéro avec une nouvelle revue — n’oubliez pas de commenter
    @sourcery-ai review pour déclencher une nouvelle revue !

Personnaliser votre expérience

Accédez à votre dashboard pour :

  • Activer ou désactiver des fonctionnalités de revue telles que le résumé de pull request généré par Sourcery, le guide du relecteur, et d’autres.
  • Changer la langue de revue.
  • Ajouter, supprimer ou modifier des instructions de revue personnalisées.
  • Ajuster d’autres paramètres de revue.

Obtenir de l’aide

Original review guide in English

Reviewer's Guide

Introduces a new unified CadenceField component for charge cadence selection and wires it into both the create form and edit drawer, along with i18n, tests, and documentation updates, while keeping domain logic and server actions unchanged.

Sequence diagram for CadenceField onChange updating parent form state

sequenceDiagram
  actor User
  participant CadenceField
  participant ChargesClient

  User->>CadenceField: change frequency/day/month selects
  CadenceField->>ChargesClient: onChange(next)
  ChargesClient->>ChargesClient: setFrequency(next.frequency)
  ChargesClient->>ChargesClient: setDueMonth(String(next.dueMonth))
  ChargesClient->>ChargesClient: setPaymentDay(String(next.paymentDay))
Loading

File-Level Changes

Change Details Files
Add a controlled CadenceField component that encapsulates frequency, anchor month, payment day selection, and a human-readable cadence summary using native selects.
  • Define CadenceValue and CadenceFieldProps types keyed off the Charge domain type
  • Render three labelled native controls for frequency, day (including a special last-day option), and conditional anchor monthCompute a summary string using new app.charges.cadence i18n keys and paymentMonthsFromFrequency, without performing date calculations in the UIExpose stable data-testids based on an idPrefix to support reuse in create/edit forms and tests
src/app/[locale]/app/charges/CadenceField.tsx
Replace the three separate cadence inputs in the charge creation form with the new CadenceField, preserving existing submit logic and state shape at the component boundary.
  • Import CadenceField into ChargesClient and remove unused Select/month-related i18n imports
  • Wrap the cadence controls in a single CadenceField instance with idPrefix create-charge
  • Convert string form state for dueMonth and paymentDay to numbers when passing into CadenceField and back to strings in the onChange handler
  • Keep createChargeAction payload and paymentMonthsFromFrequency usage unchanged
src/app/[locale]/app/charges/ChargesClient.tsx
Replace the three separate cadence inputs in the charge edit drawer with CadenceField, keeping the server interaction and normalization behavior intact.
  • Import CadenceField into ChargeEditDrawer and remove legacy Select-based cadence inputs
  • Pass the drawer’s frequency, dueMonth, and paymentDay into CadenceField with idPrefix edit-charge, coercing to numbers and defaulting paymentDay to 1
  • Update the local state in response to CadenceField onChange while keeping submit()’s use of Number(dueMonth)/Number(paymentDay) and paymentMonthsFromFrequency
  • Retain FREQUENCIES constant for normalizeFrequency while removing now-unused month key constant and useId hooks for the old fields
src/app/[locale]/app/charges/ChargeEditDrawer.tsx
Add focused unit tests for CadenceField and adapt existing ChargesClient tests to the new cadence UI and testids.
  • Create CadenceField tests that cover monthly vs non-monthly behavior, last-day option, summary text, onChange emissions, and a11y label associations using fr-BE messages
  • Update ChargesClient layout tests to assert presence of the create-charge summary instead of the removed monthly anchor-month field
  • Adjust edit drawer tests to read the new edit-charge-day select value as a string instead of the removed numeric input
  • Ensure tests use NextIntlClientProvider with appropriate locale and timezone
src/app/[locale]/app/charges/__tests__/CadenceField.test.tsx
src/app/[locale]/app/charges/__tests__/ChargesClient.test.tsx
Extend i18n messages and documentation to support the new cadence UI and provide a design and implementation plan for CadenceField.
  • Add app.charges.cadence keys for labels, last-day wording, and summary templates in all supported locales (fr-BE, en, nl-BE, de-DE, es-ES)
  • Introduce THI-301 cadencefield implementation plan and design spec documents describing architecture, behavior, domain boundaries, and testing/QA expectations
  • Align wording so existing getByLabelText selectors remain mostly valid by reusing "Fréquence" and "Jour du mois" labels where appropriate
messages/de-DE.json
messages/en.json
messages/es-ES.json
messages/fr-BE.json
messages/nl-BE.json
docs/plans/THI-301-cadencefield-plan.md
docs/plans/THI-301-cadencefield-design.md

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - j’ai identifié 6 points, et laissé quelques retours plus globaux :

  • La liste des fréquences prises en charge est maintenant écrite en dur à la fois dans CadenceField et dans les écrans de charges (FREQUENCIES dans ChargesClient/ChargeEditDrawer), ce qui risque de diverger si une nouvelle fréquence est ajoutée. Il vaudrait mieux centraliser cet enum dans un domaine/module partagé pour que tous les callsites dérivent d’une source unique.
  • La chaîne Tailwind selectClass dans CadenceField encode tout le contrat de stylage d’un form-control ; si d’autres inputs/selects utilisent le même pattern, vous pourriez extraire une constante ou utilitaire partagé afin de garder les changements de style cohérents dans toute l’app.
  • Dans les callsites de CadenceField, vous forcez paymentDay avec Number(paymentDay) || 1, ce qui retombe silencieusement sur le jour 1 quand le champ est vide. Si ce n’est pas un choix UX intentionnel, il serait sans doute plus sûr soit de le laisser à undefined tant que l’utilisateur n’a pas choisi de valeur, soit de gérer l’erreur à la validation plutôt que de mettre une valeur par défaut.
Prompt pour les agents IA
Veuillez traiter les commentaires de cette revue de code :

## Commentaires généraux
- La liste des fréquences prises en charge est maintenant écrite en dur à la fois dans `CadenceField` et dans les écrans de charges (`FREQUENCIES` dans `ChargesClient`/`ChargeEditDrawer`), ce qui risque de diverger si une nouvelle fréquence est ajoutée. Il vaudrait mieux centraliser cet enum dans un domaine/module partagé pour que tous les callsites dérivent d’une source unique.
- La chaîne Tailwind `selectClass` dans `CadenceField` encode tout le contrat de stylage d’un form-control ; si d’autres inputs/selects utilisent le même pattern, vous pourriez extraire une constante ou utilitaire partagé afin de garder les changements de style cohérents dans toute l’app.
- Dans les callsites de `CadenceField`, vous forcez `paymentDay` avec `Number(paymentDay) || 1`, ce qui retombe silencieusement sur le jour 1 quand le champ est vide. Si ce n’est pas un choix UX intentionnel, il serait sans doute plus sûr soit de le laisser à `undefined` tant que l’utilisateur n’a pas choisi de valeur, soit de gérer l’erreur à la validation plutôt que de mettre une valeur par défaut.

## Commentaires individuels

### Commentaire 1
<location path="src/app/[locale]/app/charges/__tests__/CadenceField.test.tsx" line_range="20" />
<code_context>
+const monthly: CadenceValue = { frequency: 'monthly', dueMonth: 1, paymentDay: 15 };
+const quarterly: CadenceValue = { frequency: 'quarterly', dueMonth: 3, paymentDay: 15 };
+
+describe('<CadenceField />', () => {
+  it('hides the anchor-month select when monthly', () => {
+    renderField(monthly);
</code_context>
<issue_to_address>
**suggestion (testing):** Ajoutez une couverture de test pour la prop `disabled` afin de vérifier que tous les contrôles sont bien désactivés quand on la passe.

Comme `disabled` est propagé à chaque `<select>`, merci d’ajouter un test qui rend `CadenceField` avec `disabled={true}` et vérifie que `t-frequency`, `t-day` et `t-month` (pour une configuration non mensuelle) ont tous l’attribut `disabled`. Cela permettra de détecter les régressions lorsqu’un contrôle arrête de propager l’état `disabled`.

Suggested implementation:

```typescript
    <NextIntlClientProvider locale="fr-BE" messages={messages} timeZone="Europe/Brussels">
      <CadenceField idPrefix="t" value={value} onChange={onChange} />
    </NextIntlClientProvider>,
  );
  return onChange;
}

function renderDisabledField(value: CadenceValue) {
  const onChange = jest.fn();
  render(
    <NextIntlClientProvider locale="fr-BE" messages={messages} timeZone="Europe/Brussels">
      <CadenceField idPrefix="t" value={value} onChange={onChange} disabled />
    </NextIntlClientProvider>,
  );
  return onChange;
}

const monthly: CadenceValue = { frequency: 'monthly', dueMonth: 1, paymentDay: 15 };

```

```typescript
  it('shows an editable anchor-month select when non-monthly', () => {
    renderField(quarterly);
    expect(screen.getByTestId('t-month')).toBeInTheDocument();
  });

  it('disables all selects when disabled is true for a non-monthly cadence', () => {
    renderDisabledField(quarterly);

    expect(screen.getByTestId('t-frequency')).toBeDisabled();
    expect(screen.getByTestId('t-day')).toBeDisabled();
    expect(screen.getByTestId('t-month')).toBeDisabled();
  });


```
</issue_to_address>

### Commentaire 2
<location path="src/app/[locale]/app/charges/__tests__/CadenceField.test.tsx" line_range="38-46" />
<code_context>
+    expect(screen.getByTestId('t-summary')).toHaveTextContent('Prélevé le 15 de chaque mois');
+  });
+
+  it('renders the recurring summary with computed months (quarterly anchored March)', () => {
+    renderField(quarterly);
+    // paymentMonthsFromFrequency('quarterly', 3) → [3,6,9,12] → mars, juin, sept., déc.
</code_context>
<issue_to_address>
**suggestion (testing):** Envisagez un test pour le résumé "dernier jour" aussi dans le cas récurrent non mensuel.

Actuellement, on vérifie seulement le libellé "dernier jour" pour le mensuel (`paymentDay = 31`) et le résumé récurrent générique pour le trimestriel, mais pas la combinaison `frequency !== 'monthly'` avec `paymentDay === 31`. Merci d’ajouter un cas trimestriel (par exemple `frequency: 'quarterly', dueMonth: 3, paymentDay: 31`) qui vérifie à la fois la formulation "dernier jour" et la liste de mois attendue, pour valider que `daySummary` est bien réutilisé dans la branche récurrente.

```suggestion
  it('renders the recurring summary with computed months (quarterly anchored March)', () => {
    renderField(quarterly);
    // paymentMonthsFromFrequency('quarterly', 3) → [3,6,9,12] → mars, juin, sept., déc.
    expect(screen.getByTestId('t-summary')).toHaveTextContent(/mars/i);
    expect(screen.getByTestId('t-summary')).toHaveTextContent(/juin/i);
    expect(screen.getByTestId('t-summary')).toHaveTextContent(/déc/i);
  });

  it('renders the recurring summary with "dernier jour" wording for non-monthly last-day payments', () => {
    renderField({ frequency: 'quarterly', dueMonth: 3, paymentDay: 31 });

    const summary = screen.getByTestId('t-summary');

    // Reuses the same "dernier jour" daySummary wording in the recurring branch
    expect(summary).toHaveTextContent(/dernier jour/i);

    // paymentMonthsFromFrequency('quarterly', 3) → [3,6,9,12] → mars, juin, sept., déc.
    expect(summary).toHaveTextContent(/mars/i);
    expect(summary).toHaveTextContent(/juin/i);
    expect(summary).toHaveTextContent(/sept/i);
    expect(summary).toHaveTextContent(/déc/i);
  });

  it('exposes a "Dernier jour du mois" option that emits paymentDay=31', () => {
```
</issue_to_address>

### Commentaire 3
<location path="src/app/[locale]/app/charges/__tests__/ChargesClient.test.tsx" line_range="196-205" />
<code_context>
     expect(screen.getByLabelText(/Montant/)).toBeInTheDocument();
     expect(screen.getByLabelText('Fréquence')).toBeInTheDocument();
-    expect(screen.getByLabelText('Mois de référence')).toBeInTheDocument();
+    // THI-301: the anchor-month select is intentionally HIDDEN for monthly
+    // (the default) — the CadenceField summary line proves the cluster is
+    // mounted instead.
+    expect(screen.getByTestId('create-charge-summary')).toBeInTheDocument();
     expect(screen.getByLabelText(/jour du mois/i)).toBeInTheDocument();
     expect(screen.getByRole('button', { name: /^ajouter$/i })).toBeInTheDocument();
</code_context>
<issue_to_address>
**suggestion (testing):** Renforcez le test de flux de création en vérifiant le contenu du résumé CadenceField, pas seulement sa présence.

Cette modification vérifie seulement que `create-charge-summary` est rendu, pas que la sémantique de cadence est correcte. Pour que ce test continue à couvrir le scénario de création par défaut, merci de vérifier aussi que le texte du résumé reflète la copie par défaut attendue (par exemple, mensuel au jour N) et que le select du mois d’ancrage n’est pas présent dans le DOM. De cette façon, le test prouve toujours que le formulaire est câblé sur la bonne cadence par défaut, et pas seulement que le composant est monté.

```suggestion
    expect(screen.getByLabelText('Libellé')).toBeInTheDocument();
    expect(screen.getByLabelText(/Montant/)).toBeInTheDocument();
    expect(screen.getByLabelText('Fréquence')).toBeInTheDocument();
    // THI-301: the anchor-month select is intentionally HIDDEN for monthly
    // (the default) — the CadenceField summary line proves the cluster is
    // mounted instead.
    const createChargeSummary = screen.getByTestId('create-charge-summary');
    expect(createChargeSummary).toBeInTheDocument();
    // Verify that the default cadence semantics are correctly wired:
    // monthly collection on a day-of-month (default create scenario).
    expect(createChargeSummary).toHaveTextContent(/mensuel(le)?/i);
    expect(createChargeSummary).toHaveTextContent(/mois/i);
    // The anchor-month select should not be rendered for the default monthly cadence.
    expect(screen.queryByLabelText('Mois de référence')).not.toBeInTheDocument();

    expect(screen.getByLabelText(/jour du mois/i)).toBeInTheDocument();
    expect(screen.getByRole('button', { name: /^ajouter$/i })).toBeInTheDocument();
  });
```
</issue_to_address>

### Commentaire 4
<location path="src/app/[locale]/app/charges/__tests__/ChargesClient.test.tsx" line_range="280-281" />
<code_context>
     expect(screen.getByTestId('charge-edit-label')).toHaveValue('Loyer appartement');
     expect(screen.getByTestId('charge-edit-amount')).toHaveValue(1200);
-    expect(screen.getByTestId('charge-edit-payment-day')).toHaveValue(5);
+    // THI-301: native <select> in CadenceField → string value.
+    expect(screen.getByTestId('edit-charge-day')).toHaveValue('5');
   });

</code_context>
<issue_to_address>
**suggestion (testing):** Envisagez d’ajouter un test de flux d’édition qui couvre une cadence non mensuelle pour s’assurer que le jour et le mois d’ancrage sont correctement préremplis.

Ce test ne couvre toujours que le cas mensuel et vérifie maintenant la valeur chaîne issue du `<select>`. Avec le nouveau `CadenceField` dépendant de `frequency`, `dueMonth` et `paymentDay` de la charge existante, merci d’ajouter un test d’intégration pour une charge non mensuelle (par exemple trimestrielle/semestrielle/annuelle) qui ouvre le drawer d’édition et vérifie que `edit-charge-day` et `edit-charge-month` (ou équivalents) sont pré-remplis avec les valeurs attendues. Cela permettra de vérifier que les données de cadence héritées sont correctement mappées dans le nouveau picker pour toutes les cadences.

Suggested implementation:

```typescript
    await screen.findByTestId('charge-edit-drawer');
    expect(screen.getByTestId('charge-edit-label')).toHaveValue('Loyer appartement');
    expect(screen.getByTestId('charge-edit-amount')).toHaveValue(1200);
    // THI-301: native <select> in CadenceField → string value.
    expect(screen.getByTestId('edit-charge-day')).toHaveValue('5');
  });

  it('pre-fills cadence fields when editing a non-monthly charge', async () => {
    /**
     * THI-301:
     * Ensure legacy non-monthly cadence values (frequency, dueMonth, paymentDay)
     * are correctly mapped into CadenceField when opening the edit drawer.
     *
     * This test uses an annual charge as a representative non-monthly cadence.
     * Adjust the seeded charge below if your fixtures use different labels
     * or cadence shapes.
     */
    const charges = [
      {
        id: 'annual-charge-id',
        label: 'Assurance habitation',
        amount: 35000,
        // Legacy cadence fields feeding CadenceField:
        frequency: 'ANNUAL',
        paymentDay: 15,
        dueMonth: 3, // e.g. March
      },
    ];

    renderChargesClientWithCharges(charges);

    // Open the edit drawer for the non-monthly charge.
    // Reuse the same interaction pattern as the monthly edit-flow test.
    await userEvent.click(
      screen.getByRole('button', { name: /modifier\s+assurance habitation/i }),
    );

    await screen.findByTestId('charge-edit-drawer');

    // Label / amount sanity check.
    expect(screen.getByTestId('charge-edit-label')).toHaveValue('Assurance habitation');
    expect(screen.getByTestId('charge-edit-amount')).toHaveValue(350);

    // CadenceField should be pre-populated from paymentDay / dueMonth.
    expect(screen.getByTestId('edit-charge-day')).toHaveValue('15');
    expect(screen.getByTestId('edit-charge-month')).toHaveValue('3');
  });

  it('calls updateChargeAction with the modified amount on Save', async () => {

```

1. Assurez-vous que `renderChargesClientWithCharges` existe et accepte une liste de charges comme ci‑dessus. Si votre suite de tests utilise un autre helper ou un wrapper de provider, adaptez l’appel en conséquence (par exemple `renderChargesClient({ charges })` ou similaire).
2. Alignez la forme de la charge initialisée avec votre véritable API/fixtures :
   - Si votre domaine utilise `due_month` / `payment_day` ou des objets `cadence` au lieu de propriétés de haut niveau `frequency`, `paymentDay`, `dueMonth`, adaptez les noms de propriétés dans le tableau `charges`.
   - Si l’UI affiche les montants en euros (par exemple 350 au lieu de 35000 centimes), ajustez l’assertion sur `amount` pour correspondre à vos conventions existantes.
3. Remplacez le sélecteur `getByRole('button', { name: /modifier\s+assurance habitation/i })` par le sélecteur exact du bouton d’édition utilisé dans le test de flux d’édition mensuelle existant (pour rester cohérent avec la manière dont vous ouvrez le drawer d’édition).
4. Vérifiez que les test IDs `edit-charge-day` et `edit-charge-month` correspondent bien aux attributs `data-testid` réels dans le formulaire d’édition CadenceField ; si ce n’est pas le cas, mettez à jour les sélecteurs `getByTestId` en conséquence.
</issue_to_address>

### Commentaire 5
<location path="docs/plans/THI-301-cadencefield-plan.md" line_range="543" />
<code_context>
+
+### Task 7 : QA agents + DoD final
+
+- [ ] **Step 1 : Agents QA ciblés (voie lourde, par ce que le diff touche)**
+
+- `ui-auditor` (nouveaux selects natifs, labels, contraste tokens, mobile-first)
</code_context>
<issue_to_address>
**issue (typo):** Corriger "par ce que" en "parce que".

Formulation correcte en français : « parce que » en un seul mot.

```suggestion
- [ ] **Step 1 : Agents QA ciblés (voie lourde, parce que le diff touche)**
```
</issue_to_address>

### Commentaire 6
<location path="docs/plans/THI-301-cadencefield-design.md" line_range="65-72" />
<code_context>
+
+```
+MENSUEL                              TRIMESTRIEL
+ Frequence ( Mensuel   v )            Frequence ( Trimestriel v )
+ Preleve le [ 15 v ] de chaque mois   Preleve le [15 v] a partir de [mars v]
+   (... 1..28, 'Dernier jour')          (jour: 1..28, 'Dernier jour')
+ -> 'le 15 de chaque mois'            -> 'le 15 : mars, juin, sept, dec'
</code_context>
<issue_to_address>
**nitpick (typo):** Ajouter les accents manquants dans lexemple ASCII (Fréquence, Prélevé, à partir de, déc.).

Dans lexemple, ces mots devraient être accentués pour rester cohérents avec le reste de la doc : « Frequence » → « Fréquence », « Preleve » → « Prélevé », « a partir de » → « à partir de », et « dec » → « déc. ».

```suggestion
```
MENSUEL                              TRIMESTRIEL
 Fréquence ( Mensuel   v )            Fréquence ( Trimestriel v )
 Prélevé le [ 15 v ] de chaque mois   Prélevé le [15 v] à partir de [mars v]
   (... 1..28, 'Dernier jour')          (jour: 1..28, 'Dernier jour')
 -> 'le 15 de chaque mois'            -> 'le 15 : mars, juin, sept, déc.'
                                        (changer ancre/freq = tout bouge)
```
```
</issue_to_address>

Sourcery est gratuit pour l’open source – si nos revues vous sont utiles, pensez à les partager ✨
Aidez‑moi à devenir plus utile ! Merci de cliquer sur 👍 ou 👎 sur chaque commentaire : vos retours m’aident à améliorer la qualité de mes revues.
Original comment in English

Hey - I've found 6 issues, and left some high level feedback:

  • The list of supported frequencies is now hard-coded in both CadenceField and the charges screens (FREQUENCIES in ChargesClient/ChargeEditDrawer), which risks drift if a new frequency is added; consider centralizing this enum in a shared domain/module so all callsites derive from a single source.
  • The selectClass Tailwind string in CadenceField encodes a full form-control styling contract; if other inputs/selects use the same pattern, you might want to extract a shared constant or utility to keep styling changes consistent across the app.
  • In the CadenceField callsites you coerce paymentDay with Number(paymentDay) || 1, which silently falls back to day 1 when the field is empty; if this is not intentional UX, it might be safer to either keep it undefined until the user chooses a value or handle the error at validation time instead of defaulting.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The list of supported frequencies is now hard-coded in both `CadenceField` and the charges screens (`FREQUENCIES` in `ChargesClient`/`ChargeEditDrawer`), which risks drift if a new frequency is added; consider centralizing this enum in a shared domain/module so all callsites derive from a single source.
- The `selectClass` Tailwind string in `CadenceField` encodes a full form-control styling contract; if other inputs/selects use the same pattern, you might want to extract a shared constant or utility to keep styling changes consistent across the app.
- In the `CadenceField` callsites you coerce `paymentDay` with `Number(paymentDay) || 1`, which silently falls back to day 1 when the field is empty; if this is not intentional UX, it might be safer to either keep it `undefined` until the user chooses a value or handle the error at validation time instead of defaulting.

## Individual Comments

### Comment 1
<location path="src/app/[locale]/app/charges/__tests__/CadenceField.test.tsx" line_range="20" />
<code_context>
+const monthly: CadenceValue = { frequency: 'monthly', dueMonth: 1, paymentDay: 15 };
+const quarterly: CadenceValue = { frequency: 'quarterly', dueMonth: 3, paymentDay: 15 };
+
+describe('<CadenceField />', () => {
+  it('hides the anchor-month select when monthly', () => {
+    renderField(monthly);
</code_context>
<issue_to_address>
**suggestion (testing):** Add coverage for the `disabled` prop to ensure all controls are properly disabled when requested.

Since `disabled` is passed through to each `<select>`, please add a test that renders `CadenceField` with `disabled={true}` and asserts that `t-frequency`, `t-day`, and `t-month` (for a non-monthly config) all have the `disabled` attribute. This will help catch regressions where any control stops forwarding the `disabled` state.

Suggested implementation:

```typescript
    <NextIntlClientProvider locale="fr-BE" messages={messages} timeZone="Europe/Brussels">
      <CadenceField idPrefix="t" value={value} onChange={onChange} />
    </NextIntlClientProvider>,
  );
  return onChange;
}

function renderDisabledField(value: CadenceValue) {
  const onChange = jest.fn();
  render(
    <NextIntlClientProvider locale="fr-BE" messages={messages} timeZone="Europe/Brussels">
      <CadenceField idPrefix="t" value={value} onChange={onChange} disabled />
    </NextIntlClientProvider>,
  );
  return onChange;
}

const monthly: CadenceValue = { frequency: 'monthly', dueMonth: 1, paymentDay: 15 };

```

```typescript
  it('shows an editable anchor-month select when non-monthly', () => {
    renderField(quarterly);
    expect(screen.getByTestId('t-month')).toBeInTheDocument();
  });

  it('disables all selects when disabled is true for a non-monthly cadence', () => {
    renderDisabledField(quarterly);

    expect(screen.getByTestId('t-frequency')).toBeDisabled();
    expect(screen.getByTestId('t-day')).toBeDisabled();
    expect(screen.getByTestId('t-month')).toBeDisabled();
  });


```
</issue_to_address>

### Comment 2
<location path="src/app/[locale]/app/charges/__tests__/CadenceField.test.tsx" line_range="38-46" />
<code_context>
+    expect(screen.getByTestId('t-summary')).toHaveTextContent('Prélevé le 15 de chaque mois');
+  });
+
+  it('renders the recurring summary with computed months (quarterly anchored March)', () => {
+    renderField(quarterly);
+    // paymentMonthsFromFrequency('quarterly', 3) → [3,6,9,12] → mars, juin, sept., déc.
</code_context>
<issue_to_address>
**suggestion (testing):** Consider a test for the "dernier jour" summary in the non-monthly (recurring) case as well.

Right now we only assert the "dernier jour" wording for monthly (`paymentDay = 31`) and the generic recurring summary for quarterly, but not the combination of `frequency !== 'monthly'` with `paymentDay === 31`. Please add a quarterly case (e.g. `frequency: 'quarterly', dueMonth: 3, paymentDay: 31`) that checks both the "dernier jour" phrasing and the expected months list, to validate that `daySummary` is correctly reused in the recurring branch.

```suggestion
  it('renders the recurring summary with computed months (quarterly anchored March)', () => {
    renderField(quarterly);
    // paymentMonthsFromFrequency('quarterly', 3) → [3,6,9,12] → mars, juin, sept., déc.
    expect(screen.getByTestId('t-summary')).toHaveTextContent(/mars/i);
    expect(screen.getByTestId('t-summary')).toHaveTextContent(/juin/i);
    expect(screen.getByTestId('t-summary')).toHaveTextContent(/déc/i);
  });

  it('renders the recurring summary with "dernier jour" wording for non-monthly last-day payments', () => {
    renderField({ frequency: 'quarterly', dueMonth: 3, paymentDay: 31 });

    const summary = screen.getByTestId('t-summary');

    // Reuses the same "dernier jour" daySummary wording in the recurring branch
    expect(summary).toHaveTextContent(/dernier jour/i);

    // paymentMonthsFromFrequency('quarterly', 3) → [3,6,9,12] → mars, juin, sept., déc.
    expect(summary).toHaveTextContent(/mars/i);
    expect(summary).toHaveTextContent(/juin/i);
    expect(summary).toHaveTextContent(/sept/i);
    expect(summary).toHaveTextContent(/déc/i);
  });

  it('exposes a "Dernier jour du mois" option that emits paymentDay=31', () => {
```
</issue_to_address>

### Comment 3
<location path="src/app/[locale]/app/charges/__tests__/ChargesClient.test.tsx" line_range="196-205" />
<code_context>
     expect(screen.getByLabelText(/Montant/)).toBeInTheDocument();
     expect(screen.getByLabelText('Fréquence')).toBeInTheDocument();
-    expect(screen.getByLabelText('Mois de référence')).toBeInTheDocument();
+    // THI-301: the anchor-month select is intentionally HIDDEN for monthly
+    // (the default) — the CadenceField summary line proves the cluster is
+    // mounted instead.
+    expect(screen.getByTestId('create-charge-summary')).toBeInTheDocument();
     expect(screen.getByLabelText(/jour du mois/i)).toBeInTheDocument();
     expect(screen.getByRole('button', { name: /^ajouter$/i })).toBeInTheDocument();
</code_context>
<issue_to_address>
**suggestion (testing):** Strengthen the create-flow test by asserting the CadenceField summary content, not just its presence.

This change only verifies that `create-charge-summary` renders, not that the cadence semantics are correct. To keep this test covering the default create scenario, please also assert that the summary text reflects the expected default copy (e.g. monthly at day N) and that the anchor-month select is not present in the DOM. That way the test still proves the form is wired to the correct default cadence, not just that the component mounts.

```suggestion
    expect(screen.getByLabelText('Libellé')).toBeInTheDocument();
    expect(screen.getByLabelText(/Montant/)).toBeInTheDocument();
    expect(screen.getByLabelText('Fréquence')).toBeInTheDocument();
    // THI-301: the anchor-month select is intentionally HIDDEN for monthly
    // (the default) — the CadenceField summary line proves the cluster is
    // mounted instead.
    const createChargeSummary = screen.getByTestId('create-charge-summary');
    expect(createChargeSummary).toBeInTheDocument();
    // Verify that the default cadence semantics are correctly wired:
    // monthly collection on a day-of-month (default create scenario).
    expect(createChargeSummary).toHaveTextContent(/mensuel(le)?/i);
    expect(createChargeSummary).toHaveTextContent(/mois/i);
    // The anchor-month select should not be rendered for the default monthly cadence.
    expect(screen.queryByLabelText('Mois de référence')).not.toBeInTheDocument();

    expect(screen.getByLabelText(/jour du mois/i)).toBeInTheDocument();
    expect(screen.getByRole('button', { name: /^ajouter$/i })).toBeInTheDocument();
  });
```
</issue_to_address>

### Comment 4
<location path="src/app/[locale]/app/charges/__tests__/ChargesClient.test.tsx" line_range="280-281" />
<code_context>
     expect(screen.getByTestId('charge-edit-label')).toHaveValue('Loyer appartement');
     expect(screen.getByTestId('charge-edit-amount')).toHaveValue(1200);
-    expect(screen.getByTestId('charge-edit-payment-day')).toHaveValue(5);
+    // THI-301: native <select> in CadenceField → string value.
+    expect(screen.getByTestId('edit-charge-day')).toHaveValue('5');
   });

</code_context>
<issue_to_address>
**suggestion (testing):** Consider adding an edit-flow test that covers a non-monthly cadence to ensure day and anchor month are correctly pre-filled.

This still only covers the monthly case and now asserts the string value from the `<select>`. With the new `CadenceField` depending on `frequency`, `dueMonth`, and `paymentDay` from the existing charge, please add an integration test for a non-monthly charge (e.g. quarterly/semiannual/annual) that opens the edit drawer and asserts that `edit-charge-day` and `edit-charge-month` (or equivalent) are pre-populated with the expected values. That will verify the legacy charge data is correctly mapped into the new picker for all cadences.

Suggested implementation:

```typescript
    await screen.findByTestId('charge-edit-drawer');
    expect(screen.getByTestId('charge-edit-label')).toHaveValue('Loyer appartement');
    expect(screen.getByTestId('charge-edit-amount')).toHaveValue(1200);
    // THI-301: native <select> in CadenceField → string value.
    expect(screen.getByTestId('edit-charge-day')).toHaveValue('5');
  });

  it('pre-fills cadence fields when editing a non-monthly charge', async () => {
    /**
     * THI-301:
     * Ensure legacy non-monthly cadence values (frequency, dueMonth, paymentDay)
     * are correctly mapped into CadenceField when opening the edit drawer.
     *
     * This test uses an annual charge as a representative non-monthly cadence.
     * Adjust the seeded charge below if your fixtures use different labels
     * or cadence shapes.
     */
    const charges = [
      {
        id: 'annual-charge-id',
        label: 'Assurance habitation',
        amount: 35000,
        // Legacy cadence fields feeding CadenceField:
        frequency: 'ANNUAL',
        paymentDay: 15,
        dueMonth: 3, // e.g. March
      },
    ];

    renderChargesClientWithCharges(charges);

    // Open the edit drawer for the non-monthly charge.
    // Reuse the same interaction pattern as the monthly edit-flow test.
    await userEvent.click(
      screen.getByRole('button', { name: /modifier\s+assurance habitation/i }),
    );

    await screen.findByTestId('charge-edit-drawer');

    // Label / amount sanity check.
    expect(screen.getByTestId('charge-edit-label')).toHaveValue('Assurance habitation');
    expect(screen.getByTestId('charge-edit-amount')).toHaveValue(350);

    // CadenceField should be pre-populated from paymentDay / dueMonth.
    expect(screen.getByTestId('edit-charge-day')).toHaveValue('15');
    expect(screen.getByTestId('edit-charge-month')).toHaveValue('3');
  });

  it('calls updateChargeAction with the modified amount on Save', async () => {

```

1. Ensure that `renderChargesClientWithCharges` exists and accepts a list of charges as used above. If your test suite uses a different helper or a provider wrapper, adapt the call accordingly (e.g. `renderChargesClient({ charges })` or similar).
2. Align the seeded charge shape with your real API/fixture shape:
   - If your domain uses `due_month` / `payment_day` or `cadence` objects instead of top-level `frequency`, `paymentDay`, `dueMonth`, adjust the property names in the seeded `charges` array.
   - If the UI displays amounts in euros (e.g. 350 instead of 35000 cents), adjust the `amount` assertion to match your existing conventions.
3. Replace the `getByRole('button', { name: /modifier\s+assurance habitation/i })` selector with the exact edit-button selector used in the existing monthly edit-flow test (for consistency with how you open the edit drawer).
4. Confirm that the test IDs `edit-charge-day` and `edit-charge-month` match the actual `data-testid` attributes in the CadenceField edit form; if they differ, update the `getByTestId` selectors accordingly.
</issue_to_address>

### Comment 5
<location path="docs/plans/THI-301-cadencefield-plan.md" line_range="543" />
<code_context>
+
+### Task 7 : QA agents + DoD final
+
+- [ ] **Step 1 : Agents QA ciblés (voie lourde, par ce que le diff touche)**
+
+- `ui-auditor` (nouveaux selects natifs, labels, contraste tokens, mobile-first)
</code_context>
<issue_to_address>
**issue (typo):** Corriger "par ce que" en "parce que".

Formulation correcte en français : « parce que » en un seul mot.

```suggestion
- [ ] **Step 1 : Agents QA ciblés (voie lourde, parce que le diff touche)**
```
</issue_to_address>

### Comment 6
<location path="docs/plans/THI-301-cadencefield-design.md" line_range="65-72" />
<code_context>
+
+```
+MENSUEL                              TRIMESTRIEL
+ Frequence ( Mensuel   v )            Frequence ( Trimestriel v )
+ Preleve le [ 15 v ] de chaque mois   Preleve le [15 v] a partir de [mars v]
+   (... 1..28, 'Dernier jour')          (jour: 1..28, 'Dernier jour')
+ -> 'le 15 de chaque mois'            -> 'le 15 : mars, juin, sept, dec'
</code_context>
<issue_to_address>
**nitpick (typo):** Ajouter les accents manquants dans lexemple ASCII (Fréquence, Prélevé, à partir de, déc.).

Dans lexemple, ces mots devraient être accentués pour rester cohérents avec le reste de la doc : « Frequence » → « Fréquence », « Preleve » → « Prélevé », « a partir de » → « à partir de », et « dec » → « déc. ».

```suggestion
```
MENSUEL                              TRIMESTRIEL
 Fréquence ( Mensuel   v )            Fréquence ( Trimestriel v )
 Prélevé le [ 15 v ] de chaque mois   Prélevé le [15 v] à partir de [mars v]
   (... 1..28, 'Dernier jour')          (jour: 1..28, 'Dernier jour')
 -> 'le 15 de chaque mois'            -> 'le 15 : mars, juin, sept, déc.'
                                        (changer ancre/freq = tout bouge)
```
```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread src/app/[locale]/app/charges/__tests__/CadenceField.test.tsx
Comment thread src/app/[locale]/app/charges/__tests__/CadenceField.test.tsx
Comment thread src/app/[locale]/app/charges/__tests__/ChargesClient.test.tsx
Comment thread src/app/[locale]/app/charges/__tests__/ChargesClient.test.tsx
Comment thread docs/plans/THI-301-cadencefield-plan.md Outdated
Comment thread docs/plans/THI-301-cadencefield-design.md
…t cadence flows (Sourcery #228)

Four testing suggestions applied: disabled prop covers all three selects;
'dernier jour' asserted in the recurring (non-monthly) summary; the create
flow asserts the summary TEXT (default 'Prélevé le 1 de chaque mois'); a new
edit-flow test opens a NON-monthly charge and checks the pre-filled cluster
(frequency/anchor/day + summary). Plus two doc typos (parce que, accents).
58 charges-page tests green.
…ery #228)

The supported-cadence list was hard-coded in 3 places (CadenceField +
ChargesClient + ChargeEditDrawer). It now lives once in the domain
(src/lib/domain/types.ts) with ChargeFrequency inferred from it — adding a
frequency updates every call-site at once.
@thierryvm

Copy link
Copy Markdown
Owner Author

Retours globaux Sourcery traités :

  1. FREQUENCIES dupliqué ×3 → appliqué : CHARGE_FREQUENCIES vit désormais une seule fois dans le domaine (src/lib/domain/types.ts), ChargeFrequency en est inféré, les 3 call-sites en dérivent.
  2. selectClass partagé → déféré (rationale) : CadenceField est aujourd'hui le SEUL select natif de l'app — extraire un contrat partagé pour un consommateur unique serait de l'abstraction spéculative. À extraire au 2e select natif (le commentaire du composant référence déjà le contrat input.tsx 1:1).
  3. Number(paymentDay) || 1 → intentionnel : filet défensif jamais actif en pratique — le state est seedé '1' (create) ou depuis la DB smallint NOT NULL 1-31 (edit), et la saisie passe par des <select> qui ne peuvent pas être vides. Le fallback évite un NaN UI si un seed legacy inattendu apparaissait ; la validation Zod serveur reste la gate de vérité.

@thierryvm
thierryvm merged commit e498015 into main Jul 18, 2026
9 checks passed
@thierryvm
thierryvm deleted the feat/thi-301-cadencefield branch July 18, 2026 22:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

status:review-needed Ready for review type:feat New user-facing feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant