Combobox de catégorie avec saisie : AdjustmentForm, LinkTransfersModal, et liste rognée dans SplitAdjustmentModal #429

Open
opened 2026-10-12 22:48:25 +00:00 by maximus · 1 comment
Owner

Contexte

#423 (PR #428, mergée le 2026-10-12) a remplacé par CategoryCombobox les deux <select> de catégories du formulaire de catégorie et du dialogue « Nouveau mot-clé », et a sorti les champs de ce dialogue de son bloc défilant parce que la liste absolute du combobox était rognée par le scrollport. Deux autres <select> de catégories restent, et le rognage existe dans deux autres conteneurs.

Analyse du 2026-10-12 (/analyze, lu sur main 92e9dd5). Prémisse vérifiée fichier par fichier.

Constats

  • src/components/adjustments/AdjustmentForm.tsx:123-133 — une ligne d'ajustement choisit sa catégorie dans un <select> natif, nom brut (c.name, pas d'i18n_key). La liste vient de useAdjustments.ts:130 : SELECT * FROM categories WHERE is_active = 1, saisissables ou non, alors que Transactions, le mot-clé (#423, D2) et le filtre des transferts ne montrent que is_inputable = 1. Une nouvelle ligne prend la première catégorie de la liste (AdjustmentForm.tsx:44), la valeur n'est jamais null : pas d'option « aucune » à prévoir.
  • src/components/balance/LinkTransfersModal.tsx:259-272 — c'est le filtre par catégorie du sélecteur de transactions à lier (« Toutes les catégories », balance.transfers.modal.anyCategory), remise à null par l'option vide. La liste vient de BalancePage.tsx:69 (transactionService.getAllCategories(), saisissables). Modèle à suivre : TransactionFilterBar.tsx:121-135 (combobox + extraOptions « Toutes les catégories »). Ce modal est lui-même rendu par createPortal.
  • src/components/transactions/SplitAdjustmentModal.tsx:156 — le corps est max-h-[60vh] overflow-y-auto et les lignes de ventilation (chacune avec son CategoryCombobox, lignes 180-190) sont le contenu défilant : impossible de les sortir du bloc comme pour le dialogue mot-clé. La liste ouverte est rognée par le scrollport ; avec une seule ligne, elle n'est visible qu'en faisant défiler le corps.
  • src/components/transactions/TransactionTable.tsx:139 — le tableau vit dans un wrapper overflow-x-auto, qui rogne aussi verticalement (overflow-x: auto force overflow-y à auto) : la liste ouverte depuis les dernières lignes est coupée au bas du tableau. Même défaut, non signalé jusqu'ici.
  • Aucun test n'existe sur les trois composants. TransactionTable.render.test.tsx épingle le markup exact du combobox fermé (<div class="relative"><input ... role="combobox" ...></div>, regex ringedCategory) : le markup fermé ne doit pas changer, ou ce test est mis à jour en conséquence.
  • Pas de jsdom dans le projet (vitest en environnement node, renderToStaticMarkup) : un portail et sa position ne sont pas testables unitairement. renderToStaticMarkup ne supporte pas createPortal : un test de rendu de LinkTransfersModal doit remplacer createPortal par l'identité (vi.mock("react-dom", ...)).

Décisions (Max, 2026-10-12)

  • D1 — Portail dans CategoryCombobox : la liste ouverte est rendue dans document.body par createPortal, en position fixe calculée depuis le champ. Règle le rognage pour tous les conteneurs (modal de ventilation, dernières lignes du tableau Transactions, conteneurs futurs) au lieu d'une restructuration modal par modal.
  • D2 — Les lignes d'ajustement listent les catégories à saisie autorisée (is_active = 1 AND is_inputable = 1), comme partout ailleurs. Une ligne existante qui vise une catégorie non saisissable s'affiche vide dans le combobox tant qu'on ne la change pas ; la valeur enregistrée reste.

Travail à faire

  • CategoryCombobox : liste en portail. Quand open && totalItems > 0, rendre le <ul role="listbox"> par createPortal(..., document.body) avec position: fixed, top/left/width pris sur inputRef.current.getBoundingClientRect() (top = bottom + 4px), recalculés sur scroll (écouteur en capture sur window, pour attraper les conteneurs défilants) et resize, et à l'ouverture. Si l'espace sous le champ est inférieur à la hauteur maximale de la liste (max-h-60 = 240 px) et l'espace au-dessus plus grand, ouvrir vers le haut. z-index au-dessus de tous les modals de l'app (AddKeywordDialog est z-[200]) : z-[300]. Le clic extérieur (mousedown sur document) doit considérer le nœud de la liste comme intérieur (listRef), sinon un clic dans la liste la ferme avant la sélection ; onMouseDown={e => e.preventDefault()} sur les options reste. aria-controls/aria-activedescendant restent valides, les ids sont globaux. Markup fermé inchangé (<div class="relative"><input .../></div>).
  • Extraire le calcul de position en fonction pure testée, par exemple placeListbox({ anchor: DOMRect-like, viewportHeight, maxHeight }) → { top, left, width, openUpward }, pour que la règle de retournement soit épinglée sans DOM.
  • AdjustmentForm : remplacer le <select> par <CategoryCombobox categories={categories} value={entry.category_id} onChange={id => updateEntryField(index, "category_id", id)} compact ariaLabel={t("adjustments.category")} /> dans le conteneur flex-1 min-w-0 ; la catégorie par défaut d'une nouvelle ligne reste la première de la liste.
  • useAdjustments.ts:130 (D2) : AND is_inputable = 1 dans la requête des catégories, ou appel de transactionService.getAllCategories() qui porte déjà ce filtre. Vérifier que rien d'autre dans le hook ne lit cette liste pour afficher le nom d'une catégorie d'une ligne existante (un ajustement sur une catégorie non saisissable perdrait son libellé dans le détail) ; si c'est le cas, garder une liste complète pour l'affichage et la liste filtrée pour le choix.
  • LinkTransfersModal : remplacer le <select> par CategoryCombobox avec extraOptions=[{ value: "", label: t("balance.transfers.modal.anyCategory") }], activeExtra={categoryId === null ? "" : null}, onExtraSelect={() => setCategoryId(null)}, ariaLabel={t("balance.transfers.modal.category")} ; même rendu compact que les autres champs du filtre.
  • SplitAdjustmentModal : rien à changer dans le composant si le portail est en place ; vérifier dans l'app lancée que la liste s'ouvre par-dessus le pied du modal, et que le défilement du corps la repositionne ou la ferme.
  • Tests :
    • placeListbox.test.ts (ou dans CategoryCombobox.test.ts) : position sous le champ, largeur du champ, retournement vers le haut quand l'espace manque en bas et suffit en haut, pas de retournement quand il manque des deux côtés.
    • AdjustmentForm.render.test.tsx : role="combobox" par ligne, <select> absent, valeur affichée = nom (ou i18n_key[defaultValue=…] avec le faux t au format k[...], #389) de la catégorie de la ligne.
    • LinkTransfersModal.render.test.tsx : createPortal remplacé par l'identité ; role="combobox" présent, <select> absent, valeur affichée balance.transfers.modal.anyCategory quand categoryId est null.
    • TransactionTable.render.test.tsx : reste vert sans modification (markup fermé inchangé), ou mis à jour si la structure fermée change.
    • useAdjustments : si un test de hook existe sur la requête, l'adapter ; sinon un test sur le texte SQL n'apporte rien, la preuve est la lecture.
    • Mutations à passer par /fix-issue (déduites à la lecture) : retournement vers le haut retiré ; is_inputable retiré de la requête (observable seulement si le hook est testé, sinon le dire) ; onExtraSelect du filtre des transferts ne remet pas null ; compact ou le combobox retiré d'une ligne d'ajustement.
    • Vérification manuelle dans l'app lancée (Linux), listée dans la PR : ventilation avec 1 et 6 lignes, dernière ligne du tableau Transactions, filtre des transferts, dialogue mot-clé, clavier (flèches, Entrée, Échap) et clic dans la liste en portail.
  • CHANGELOG : entrée Changed/Modifié dans les deux langues : saisie dans les ajustements et le filtre des transferts, liste des ajustements limitée aux catégories saisissables, liste de catégories qui n'est plus coupée par le bord d'un modal ou du tableau.
  • Docs : docs/architecture.md si la section composants décrit CategoryCombobox ; pas d'ADR.

Fichiers concernés

  • src/components/shared/CategoryCombobox.tsx — portail, position, clic extérieur
  • src/components/shared/placeListbox.ts (nouveau) — calcul de position pur
  • src/components/adjustments/AdjustmentForm.tsx — combobox par ligne
  • src/hooks/useAdjustments.ts — requête des catégories (D2)
  • src/components/balance/LinkTransfersModal.tsx — combobox du filtre
  • CHANGELOG.md, CHANGELOG.fr.md
  • Tests : placeListbox.test.ts, AdjustmentForm.render.test.tsx, LinkTransfersModal.render.test.tsx (nouveaux)

Surface de test

  • Tests existants : CategoryCombobox.test.ts (7 cas, sortHierarchical), TransactionTable.render.test.tsx (markup fermé du combobox), CategoryForm.render.test.tsx, AddKeywordDialog.render.test.tsx, CategoryDetailPanel.render.test.tsx (#423). Aucun sur les trois composants de cette issue.
  • Tests manquants : ci-dessus.
  • Test de régression requis : non (type:feature), mais le rognage est un défaut existant : la preuve est manuelle, faute de jsdom.

Options écartées

  • Structurel, modal par modal (retirer max-h-[60vh] overflow-y-auto du corps de SplitAdjustmentModal) : le pied du modal sort de l'écran avec beaucoup de lignes, et le tableau Transactions reste rogné. Écartée par D1.
  • jsdom pour tester le portail : ajouter une dépendance de test pour un seul composant ; non retenu, la vérification est manuelle.

Critères d'acceptation

  • Ligne d'ajustement : taper filtre la liste (catégories actives et saisissables, ordre hiérarchique, noms du seed traduits) ; une nouvelle ligne garde la première catégorie par défaut ; sauvegarde inchangée.
  • Filtre des transferts : taper filtre la liste ; « Toutes les catégories » en tête remet le filtre à vide et reste la valeur par défaut ; la liste des transactions se recharge comme avant.
  • La liste ouverte d'un CategoryCombobox n'est plus coupée par le bord d'un conteneur défilant : modal de ventilation (1 et 6 lignes), dernières lignes du tableau Transactions, dialogue mot-clé, filtre des transferts. Elle s'ouvre vers le haut quand l'espace manque en bas.
  • Clavier et souris inchangés : flèches, Entrée, Échap, clic sur une option, clic extérieur qui ferme sans sélectionner.
  • Markup fermé du combobox inchangé (TransactionTable.render.test.tsx vert).
  • Aucune chaîne en dur ; npm run build et npm test verts ; tests listés présents et mutations rouges (ou déclarées non observables, avec la raison).
  • Entrées Changed/Modifié dans les deux changelogs.

Complexité estimée

Medium : le portail est le seul morceau non trivial (position, écouteurs, clic extérieur, retournement), les deux remplacements de <select> suivent #423. Aucune migration, aucun changement Rust.

Hors portée

Validation de cycle côté service : #393.


Plan d'exécution — milestone lot-v018 (planifié le 2026-10-16)

Les sections ci-dessous viennent de la séance /plan-run du 2026-10-16 (revue /review-spec Sécurité, Architecture, Technique comprise). En cas de conflit avec le corps au-dessus, elles priment.

Consignes communes

  • Préflight : #369, #419 et #421 sont fermées et le tag v0.17.2 existe (git ls-remote --tags origin v0.17.2). Sinon, arrêt sans rien écrire.
  • Les numéros de ligne du corps ont été lus sur main 065f580 ; #369, #419 et #421 les ont déplacés depuis. Repérer chaque site par son contenu (symbole, chaîne), jamais par son numéro.
  • Toute clé i18n ajoutée l'est en FR et en EN (garde de #453, en tête de pile).
  • Les vérifications manuelles (app lancée) vont dans le corps de la PR et le DAILY-REPORT, pas dans STATE.md : le report dans STATE.md se fait au /merge-stack.

Depends on

Note : dépendance d'ordre de la pile, pas de dépendance de code.

Décisions prises en planification

  • Défilement d'un conteneur parent, liste ouverte (Max) : la liste se repositionne (recalcul sur scroll en capture et sur resize), et suit le champ même s'il sort de la zone visible de son conteneur. Elle ne se ferme pas. Le « repositionne ou la ferme » de la tâche SplitAdjustmentModal devient « se repositionne ».
  • Les écouteurs scroll et resize ne vivent que pendant open, avec un retrait dans le retour du useEffect, comme le mousedown actuel.
  • #369 a modifié TransactionTable.tsx (bouton de répartition verrouillé) : relire le markup fermé épinglé par TransactionTable.render.test.tsx sur la base du maillon avant d'y toucher.
  • Vérifications manuelles (corps de PR, DAILY-REPORT) : celles du corps, plus « modal fermé pendant que la liste est ouverte : la liste disparaît ».

Review caveats

  • (Sécurité, suggestion retenue) Écouteurs orphelins si plusieurs combobox coexistent : traité par la décision sur les écouteurs.

Spec source

spec-plan-lot-v018.md

## Contexte #423 (PR #428, mergée le 2026-10-12) a remplacé par `CategoryCombobox` les deux `<select>` de catégories du formulaire de catégorie et du dialogue « Nouveau mot-clé », et a sorti les champs de ce dialogue de son bloc défilant parce que la liste `absolute` du combobox était rognée par le scrollport. Deux autres `<select>` de catégories restent, et le rognage existe dans deux autres conteneurs. Analyse du 2026-10-12 (`/analyze`, lu sur `main` `92e9dd5`). Prémisse vérifiée fichier par fichier. ### Constats - `src/components/adjustments/AdjustmentForm.tsx:123-133` — une ligne d'ajustement choisit sa catégorie dans un `<select>` natif, nom brut (`c.name`, pas d'`i18n_key`). La liste vient de `useAdjustments.ts:130` : `SELECT * FROM categories WHERE is_active = 1`, **saisissables ou non**, alors que Transactions, le mot-clé (#423, D2) et le filtre des transferts ne montrent que `is_inputable = 1`. Une nouvelle ligne prend la première catégorie de la liste (`AdjustmentForm.tsx:44`), la valeur n'est jamais `null` : pas d'option « aucune » à prévoir. - `src/components/balance/LinkTransfersModal.tsx:259-272` — c'est le **filtre par catégorie** du sélecteur de transactions à lier (« Toutes les catégories », `balance.transfers.modal.anyCategory`), remise à `null` par l'option vide. La liste vient de `BalancePage.tsx:69` (`transactionService.getAllCategories()`, saisissables). Modèle à suivre : `TransactionFilterBar.tsx:121-135` (combobox + `extraOptions` « Toutes les catégories »). Ce modal est lui-même rendu par `createPortal`. - `src/components/transactions/SplitAdjustmentModal.tsx:156` — le corps est `max-h-[60vh] overflow-y-auto` et les lignes de ventilation (chacune avec son `CategoryCombobox`, lignes 180-190) **sont** le contenu défilant : impossible de les sortir du bloc comme pour le dialogue mot-clé. La liste ouverte est rognée par le scrollport ; avec une seule ligne, elle n'est visible qu'en faisant défiler le corps. - `src/components/transactions/TransactionTable.tsx:139` — le tableau vit dans un wrapper `overflow-x-auto`, qui rogne aussi verticalement (`overflow-x: auto` force `overflow-y` à `auto`) : la liste ouverte depuis les dernières lignes est coupée au bas du tableau. Même défaut, non signalé jusqu'ici. - Aucun test n'existe sur les trois composants. `TransactionTable.render.test.tsx` épingle le markup exact du combobox fermé (`<div class="relative"><input ... role="combobox" ...></div>`, regex `ringedCategory`) : le markup fermé ne doit pas changer, ou ce test est mis à jour en conséquence. - Pas de jsdom dans le projet (`vitest` en environnement node, `renderToStaticMarkup`) : un portail et sa position ne sont pas testables unitairement. `renderToStaticMarkup` ne supporte pas `createPortal` : un test de rendu de `LinkTransfersModal` doit remplacer `createPortal` par l'identité (`vi.mock("react-dom", ...)`). ## Décisions (Max, 2026-10-12) - **D1 — Portail dans `CategoryCombobox`** : la liste ouverte est rendue dans `document.body` par `createPortal`, en position fixe calculée depuis le champ. Règle le rognage pour tous les conteneurs (modal de ventilation, dernières lignes du tableau Transactions, conteneurs futurs) au lieu d'une restructuration modal par modal. - **D2 — Les lignes d'ajustement listent les catégories à saisie autorisée** (`is_active = 1 AND is_inputable = 1`), comme partout ailleurs. Une ligne existante qui vise une catégorie non saisissable s'affiche vide dans le combobox tant qu'on ne la change pas ; la valeur enregistrée reste. ## Travail à faire - [ ] **`CategoryCombobox` : liste en portail.** Quand `open && totalItems > 0`, rendre le `<ul role="listbox">` par `createPortal(..., document.body)` avec `position: fixed`, `top`/`left`/`width` pris sur `inputRef.current.getBoundingClientRect()` (`top = bottom + 4px`), recalculés sur `scroll` (écouteur en capture sur `window`, pour attraper les conteneurs défilants) et `resize`, et à l'ouverture. Si l'espace sous le champ est inférieur à la hauteur maximale de la liste (`max-h-60` = 240 px) et l'espace au-dessus plus grand, ouvrir vers le haut. `z-index` au-dessus de tous les modals de l'app (`AddKeywordDialog` est `z-[200]`) : `z-[300]`. Le clic extérieur (`mousedown` sur `document`) doit considérer le nœud de la liste comme intérieur (`listRef`), sinon un clic dans la liste la ferme avant la sélection ; `onMouseDown={e => e.preventDefault()}` sur les options reste. `aria-controls`/`aria-activedescendant` restent valides, les ids sont globaux. Markup fermé inchangé (`<div class="relative"><input .../></div>`). - [ ] **Extraire le calcul de position** en fonction pure testée, par exemple `placeListbox({ anchor: DOMRect-like, viewportHeight, maxHeight })` → `{ top, left, width, openUpward }`, pour que la règle de retournement soit épinglée sans DOM. - [ ] **`AdjustmentForm`** : remplacer le `<select>` par `<CategoryCombobox categories={categories} value={entry.category_id} onChange={id => updateEntryField(index, "category_id", id)} compact ariaLabel={t("adjustments.category")} />` dans le conteneur `flex-1 min-w-0` ; la catégorie par défaut d'une nouvelle ligne reste la première de la liste. - [ ] **`useAdjustments.ts:130`** (D2) : `AND is_inputable = 1` dans la requête des catégories, ou appel de `transactionService.getAllCategories()` qui porte déjà ce filtre. Vérifier que rien d'autre dans le hook ne lit cette liste pour afficher le nom d'une catégorie d'une ligne existante (un ajustement sur une catégorie non saisissable perdrait son libellé dans le détail) ; si c'est le cas, garder une liste complète pour l'affichage et la liste filtrée pour le choix. - [ ] **`LinkTransfersModal`** : remplacer le `<select>` par `CategoryCombobox` avec `extraOptions=[{ value: "", label: t("balance.transfers.modal.anyCategory") }]`, `activeExtra={categoryId === null ? "" : null}`, `onExtraSelect={() => setCategoryId(null)}`, `ariaLabel={t("balance.transfers.modal.category")}` ; même rendu `compact` que les autres champs du filtre. - [ ] **`SplitAdjustmentModal`** : rien à changer dans le composant si le portail est en place ; vérifier dans l'app lancée que la liste s'ouvre par-dessus le pied du modal, et que le défilement du corps la repositionne ou la ferme. - [ ] **Tests** : - `placeListbox.test.ts` (ou dans `CategoryCombobox.test.ts`) : position sous le champ, largeur du champ, retournement vers le haut quand l'espace manque en bas et suffit en haut, pas de retournement quand il manque des deux côtés. - `AdjustmentForm.render.test.tsx` : `role="combobox"` par ligne, `<select>` absent, valeur affichée = nom (ou `i18n_key[defaultValue=…]` avec le faux `t` au format `k[...]`, #389) de la catégorie de la ligne. - `LinkTransfersModal.render.test.tsx` : `createPortal` remplacé par l'identité ; `role="combobox"` présent, `<select>` absent, valeur affichée `balance.transfers.modal.anyCategory` quand `categoryId` est `null`. - `TransactionTable.render.test.tsx` : reste vert sans modification (markup fermé inchangé), ou mis à jour si la structure fermée change. - `useAdjustments` : si un test de hook existe sur la requête, l'adapter ; sinon un test sur le texte SQL n'apporte rien, la preuve est la lecture. - Mutations à passer par `/fix-issue` (déduites à la lecture) : retournement vers le haut retiré ; `is_inputable` retiré de la requête (observable seulement si le hook est testé, sinon le dire) ; `onExtraSelect` du filtre des transferts ne remet pas `null` ; `compact` ou le combobox retiré d'une ligne d'ajustement. - Vérification manuelle dans l'app lancée (Linux), listée dans la PR : ventilation avec 1 et 6 lignes, dernière ligne du tableau Transactions, filtre des transferts, dialogue mot-clé, clavier (flèches, Entrée, Échap) et clic dans la liste en portail. - [ ] **CHANGELOG** : entrée Changed/Modifié dans les deux langues : saisie dans les ajustements et le filtre des transferts, liste des ajustements limitée aux catégories saisissables, liste de catégories qui n'est plus coupée par le bord d'un modal ou du tableau. - [ ] **Docs** : `docs/architecture.md` si la section composants décrit `CategoryCombobox` ; pas d'ADR. ## Fichiers concernés - `src/components/shared/CategoryCombobox.tsx` — portail, position, clic extérieur - `src/components/shared/placeListbox.ts` (nouveau) — calcul de position pur - `src/components/adjustments/AdjustmentForm.tsx` — combobox par ligne - `src/hooks/useAdjustments.ts` — requête des catégories (D2) - `src/components/balance/LinkTransfersModal.tsx` — combobox du filtre - `CHANGELOG.md`, `CHANGELOG.fr.md` - Tests : `placeListbox.test.ts`, `AdjustmentForm.render.test.tsx`, `LinkTransfersModal.render.test.tsx` (nouveaux) ## Surface de test - Tests existants : `CategoryCombobox.test.ts` (7 cas, `sortHierarchical`), `TransactionTable.render.test.tsx` (markup fermé du combobox), `CategoryForm.render.test.tsx`, `AddKeywordDialog.render.test.tsx`, `CategoryDetailPanel.render.test.tsx` (#423). Aucun sur les trois composants de cette issue. - Tests manquants : ci-dessus. - Test de régression requis : non (`type:feature`), mais le rognage est un défaut existant : la preuve est manuelle, faute de jsdom. ## Options écartées - **Structurel, modal par modal** (retirer `max-h-[60vh] overflow-y-auto` du corps de `SplitAdjustmentModal`) : le pied du modal sort de l'écran avec beaucoup de lignes, et le tableau Transactions reste rogné. Écartée par D1. - **jsdom pour tester le portail** : ajouter une dépendance de test pour un seul composant ; non retenu, la vérification est manuelle. ## Critères d'acceptation - [ ] Ligne d'ajustement : taper filtre la liste (catégories actives et saisissables, ordre hiérarchique, noms du seed traduits) ; une nouvelle ligne garde la première catégorie par défaut ; sauvegarde inchangée. - [ ] Filtre des transferts : taper filtre la liste ; « Toutes les catégories » en tête remet le filtre à vide et reste la valeur par défaut ; la liste des transactions se recharge comme avant. - [ ] La liste ouverte d'un `CategoryCombobox` n'est plus coupée par le bord d'un conteneur défilant : modal de ventilation (1 et 6 lignes), dernières lignes du tableau Transactions, dialogue mot-clé, filtre des transferts. Elle s'ouvre vers le haut quand l'espace manque en bas. - [ ] Clavier et souris inchangés : flèches, Entrée, Échap, clic sur une option, clic extérieur qui ferme sans sélectionner. - [ ] Markup fermé du combobox inchangé (`TransactionTable.render.test.tsx` vert). - [ ] Aucune chaîne en dur ; `npm run build` et `npm test` verts ; tests listés présents et mutations rouges (ou déclarées non observables, avec la raison). - [ ] Entrées Changed/Modifié dans les deux changelogs. ## Complexité estimée Medium : le portail est le seul morceau non trivial (position, écouteurs, clic extérieur, retournement), les deux remplacements de `<select>` suivent #423. Aucune migration, aucun changement Rust. ## Hors portée Validation de cycle côté service : #393. --- # Plan d'exécution — milestone lot-v018 (planifié le 2026-10-16) Les sections ci-dessous viennent de la séance `/plan-run` du 2026-10-16 (revue `/review-spec` Sécurité, Architecture, Technique comprise). **En cas de conflit avec le corps au-dessus, elles priment.** ## Consignes communes - Préflight : #369, #419 et #421 sont fermées et le tag `v0.17.2` existe (`git ls-remote --tags origin v0.17.2`). Sinon, arrêt sans rien écrire. - Les numéros de ligne du corps ont été lus sur `main` `065f580` ; #369, #419 et #421 les ont déplacés depuis. Repérer chaque site par son contenu (symbole, chaîne), jamais par son numéro. - Toute clé i18n ajoutée l'est en FR et en EN (garde de #453, en tête de pile). - Les vérifications manuelles (app lancée) vont dans le corps de la PR et le DAILY-REPORT, pas dans `STATE.md` : le report dans `STATE.md` se fait au `/merge-stack`. ## Depends on - #372 Note : dépendance d'ordre de la pile, pas de dépendance de code. ## Décisions prises en planification - **Défilement d'un conteneur parent, liste ouverte** (Max) : la liste se repositionne (recalcul sur `scroll` en capture et sur `resize`), et suit le champ même s'il sort de la zone visible de son conteneur. Elle ne se ferme pas. Le « repositionne ou la ferme » de la tâche `SplitAdjustmentModal` devient « se repositionne ». - Les écouteurs `scroll` et `resize` ne vivent que pendant `open`, avec un retrait dans le retour du `useEffect`, comme le `mousedown` actuel. - #369 a modifié `TransactionTable.tsx` (bouton de répartition verrouillé) : relire le markup fermé épinglé par `TransactionTable.render.test.tsx` sur la base du maillon avant d'y toucher. - Vérifications manuelles (corps de PR, DAILY-REPORT) : celles du corps, plus « modal fermé pendant que la liste est ouverte : la liste disparaît ». ## Review caveats - (Sécurité, suggestion retenue) Écouteurs orphelins si plusieurs combobox coexistent : traité par la décision sur les écouteurs. ## Spec source spec-plan-lot-v018.md
maximus added the
status:triage
type:feature
source:human
labels 2026-10-12 22:48:25 +00:00
Author
Owner

Corps d'origine (écrit en fin de run de #423, avant lecture des fichiers), archivé par le /analyze du 2026-10-12. Une imprécision corrigée : le <select> de LinkTransfersModal est le filtre du sélecteur de transactions (« Toutes les catégories »), pas « la catégorie du transfert ».

Corps d'origine

Contexte

#423 (PR #428, mergée le 2026-10-12) a remplacé par CategoryCombobox les deux <select> de catégories du formulaire de catégorie et du dialogue « Nouveau mot-clé ». Deux autres <select> de catégories restent dans l'app, et un rognage de liste identique à celui corrigé dans AddKeywordDialog existe ailleurs.

Constats

  • src/components/adjustments/AdjustmentForm.tsx:124 — choix de catégorie par ligne d'ajustement, <select> natif sans saisie.
  • src/components/balance/LinkTransfersModal.tsx:260 — choix de catégorie du transfert, <select> natif, remise à null par l'option vide.
  • src/components/transactions/SplitAdjustmentModal.tsx:156 — le combobox vit dans un bloc max-h-[60vh] overflow-y-auto : sa liste absolute est rognée par le scrollport, le même défaut que celui corrigé dans AddKeywordDialog par #428 (champs sortis du bloc défilant). Relevé par le plan check de #423.

Travail à faire

  • AdjustmentForm : CategoryCombobox par ligne (compact), liste des catégories saisissables comme dans Transactions ; vérifier la remise à null si elle existe.
  • LinkTransfersModal : CategoryCombobox avec option extra pour « aucune » (modèle TransactionTable.tsx:214-222) ; liste limitée aux catégories de type transfer si c'est le filtre actuel.
  • SplitAdjustmentModal : sortir les lignes de ventilation du bloc défilant, ou rendre la liste du combobox hors du scrollport.
  • Tests de rendu statique (renderToStaticMarkup, faux t au format k[...]) : role="combobox" présent, <select> absent, position du combobox avant tout overflow-y-auto pour SplitAdjustmentModal.
  • CHANGELOG Changed/Modifié dans les deux langues.

Hors portée

Validation de cycle côté service : #393.

Corps d'origine (écrit en fin de run de #423, avant lecture des fichiers), archivé par le `/analyze` du 2026-10-12. Une imprécision corrigée : le `<select>` de `LinkTransfersModal` est le **filtre** du sélecteur de transactions (« Toutes les catégories »), pas « la catégorie du transfert ». <details><summary>Corps d'origine</summary> ## Contexte #423 (PR #428, mergée le 2026-10-12) a remplacé par `CategoryCombobox` les deux `<select>` de catégories du formulaire de catégorie et du dialogue « Nouveau mot-clé ». Deux autres `<select>` de catégories restent dans l'app, et un rognage de liste identique à celui corrigé dans `AddKeywordDialog` existe ailleurs. ## Constats - `src/components/adjustments/AdjustmentForm.tsx:124` — choix de catégorie par ligne d'ajustement, `<select>` natif sans saisie. - `src/components/balance/LinkTransfersModal.tsx:260` — choix de catégorie du transfert, `<select>` natif, remise à `null` par l'option vide. - `src/components/transactions/SplitAdjustmentModal.tsx:156` — le combobox vit dans un bloc `max-h-[60vh] overflow-y-auto` : sa liste `absolute` est rognée par le scrollport, le même défaut que celui corrigé dans `AddKeywordDialog` par #428 (champs sortis du bloc défilant). Relevé par le plan check de #423. ## Travail à faire - [ ] `AdjustmentForm` : `CategoryCombobox` par ligne (`compact`), liste des catégories saisissables comme dans Transactions ; vérifier la remise à `null` si elle existe. - [ ] `LinkTransfersModal` : `CategoryCombobox` avec option extra pour « aucune » (modèle `TransactionTable.tsx:214-222`) ; liste limitée aux catégories de type `transfer` si c'est le filtre actuel. - [ ] `SplitAdjustmentModal` : sortir les lignes de ventilation du bloc défilant, ou rendre la liste du combobox hors du scrollport. - [ ] Tests de rendu statique (`renderToStaticMarkup`, faux `t` au format `k[...]`) : `role="combobox"` présent, `<select>` absent, position du combobox avant tout `overflow-y-auto` pour `SplitAdjustmentModal`. - [ ] CHANGELOG Changed/Modifié dans les deux langues. ## Hors portée Validation de cycle côté service : #393. </details>
maximus added
status:ready
and removed
status:triage
labels 2026-10-12 22:58:34 +00:00
maximus added this to the planned-2026-10-16-lot-v018 milestone 2026-10-17 00:14:24 +00:00
maximus added
status:in-progress
and removed
status:ready
labels 2026-10-18 22:17:54 +00:00
maximus added
status:review
and removed
status:in-progress
labels 2026-10-18 22:46:02 +00:00
maximus added
status:approved
and removed
status:review
labels 2026-10-19 01:59:30 +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#429
No description provided.