Avoir des listes de base à la création d'une famille #3

Open
opened 2026-08-28 21:57:25 +01:00 by maxime · 4 comments
Owner

Description

À la création d'une famille, l'espace est vide : on arrive sur un accueil sans rien et il faut deviner qu'il faut d'abord créer une liste. Provisionner une liste de courses et une liste de tâches, vides toutes les deux, donne quelque chose à ouvrir tout de suite.

Où ça se passe
services/family.ts, dans la création qui monte déjà l'équipe Circles et les trois agendas. Les deux listes sont des lignes SQLite et non des ressources Nextcloud : leur création ne peut pas échouer à moitié et n'a rien à compenser.

Ce qu'il faut trancher

  • Les noms. Ils sont visibles par toute la famille. L'i18n n'existe pas encore : un nom écrit en dur à la création restera figé ensuite, contrairement aux libellés d'interface qui, eux, se traduiront. À arbitrer avec la traduction de l'application.
  • created_by. La personne qui crée la famille est l'auteur naturel, mais elle n'a rien écrit. La colonne n'accepte pas NULL.
  • La suppression. Ce sont des listes ordinaires : les supprimer doit marcher et ne rien recréer. C'est un contenu de départ, pas un état à maintenir.
  • L'import. Une famille créée puis remplie par un import JSON aura les deux listes vides en plus de celles du fichier. C'est cohérent avec « l'import ajoute » (D36), mais mieux vaut le décider que le découvrir.

Plan d'action

Arbitrages et détail : voir le commentaire de conception.

  • Backend : createFamily reçoit la langue de la requête et crée, en dernière étape, « Liste de courses » (shopping) puis « Tâches » (todo) dans une transaction SQLite, created_by = le créateur
  • Backend : findListsByFamily départage deux listes créées dans la même seconde (ORDER BY created_at, rowid)
  • Catalogue : sources Shopping list / Tasks, traductions françaises, make l10n-build
  • Tests unitaires : les deux listes, leur type, leur nom en français et en anglais ; aucune liste après un échec compensé
  • E2E : families.spec ne suppose plus une famille neuve vide ; nouveau scénario (listes présentes, supprimées elles ne reviennent pas) ; i18n.spec vérifie les noms anglais
  • Nouvelle décision D41 dans Architecture (et renvoi depuis D36)
  • Hors plan : useActiveFamily invalide une ouverture dont la vue est démontée (course révélée par l'E2E, voir commentaire)

Recette

  • make lint typecheck test, make l10n-check
  • make build-frontend, puis cd e2e && npm test (suite complète : d'autres scénarios peuvent supposer une famille neuve sans liste)
  • /code-review, retours en commentaire
  • Revue technique personnelle
  • Mise à jour du wiki (décision, schéma, interface, périmètre)
  • Revue fonctionnelle

Plan de MEP

Aucune migration, aucun changement de schéma : livré avec la version 1.0.x. Les familles existantes ne sont pas touchées.

## Description À la création d'une famille, l'espace est vide : on arrive sur un accueil sans rien et il faut deviner qu'il faut d'abord créer une liste. Provisionner une liste de courses et une liste de tâches, vides toutes les deux, donne quelque chose à ouvrir tout de suite. **Où ça se passe** `services/family.ts`, dans la création qui monte déjà l'équipe Circles et les trois agendas. Les deux listes sont des lignes SQLite et non des ressources Nextcloud : leur création ne peut pas échouer à moitié et n'a rien à compenser. **Ce qu'il faut trancher** - **Les noms.** Ils sont visibles par toute la famille. L'i18n n'existe pas encore : un nom écrit en dur à la création restera figé ensuite, contrairement aux libellés d'interface qui, eux, se traduiront. À arbitrer avec la traduction de l'application. - **`created_by`.** La personne qui crée la famille est l'auteur naturel, mais elle n'a rien écrit. La colonne n'accepte pas NULL. - **La suppression.** Ce sont des listes ordinaires : les supprimer doit marcher et ne rien recréer. C'est un contenu de départ, pas un état à maintenir. - **L'import.** Une famille créée puis remplie par un import JSON aura les deux listes vides **en plus** de celles du fichier. C'est cohérent avec « l'import ajoute » (D36), mais mieux vaut le décider que le découvrir. ## Plan d'action Arbitrages et détail : voir le commentaire de conception. - [x] Backend : `createFamily` reçoit la langue de la requête et crée, en dernière étape, « Liste de courses » (`shopping`) puis « Tâches » (`todo`) dans une transaction SQLite, `created_by` = le créateur - [x] Backend : `findListsByFamily` départage deux listes créées dans la même seconde (`ORDER BY created_at, rowid`) - [x] Catalogue : sources `Shopping list` / `Tasks`, traductions françaises, `make l10n-build` - [x] Tests unitaires : les deux listes, leur type, leur nom en français et en anglais ; aucune liste après un échec compensé - [x] E2E : `families.spec` ne suppose plus une famille neuve vide ; nouveau scénario (listes présentes, supprimées elles ne reviennent pas) ; `i18n.spec` vérifie les noms anglais - [x] Nouvelle décision D41 dans `Architecture` (et renvoi depuis D36) - [x] Hors plan : `useActiveFamily` invalide une ouverture dont la vue est démontée (course révélée par l'E2E, voir commentaire) ## Recette - [x] `make lint typecheck test`, `make l10n-check` - [x] `make build-frontend`, puis `cd e2e && npm test` (suite complète : d'autres scénarios peuvent supposer une famille neuve sans liste) - [x] `/code-review`, retours en commentaire - [ ] Revue technique personnelle - [x] Mise à jour du wiki (décision, schéma, interface, périmètre) - [ ] Revue fonctionnelle ## Plan de MEP Aucune migration, aucun changement de schéma : livré avec la version 1.0.x. Les familles existantes ne sont pas touchées.
maxime added this to the 1.0.x milestone 2026-08-28 21:57:25 +01:00
claudeagent changed title from Avoir des liste de base à la création to Avoir des listes de base à la création d'une famille 2026-08-30 14:07:35 +01:00
Collaborator

Conception technique

Arbitrages (validés avec Maxime)

  • Noms figés dans la langue du créateur. L'i18n existe depuis D38, ce qui change la prémisse de la description. Le nom est traduit à la création avec la langue de la requête (req.language), puis stocké comme n'importe quel nom : c'est désormais celui de la famille, qu'elle peut renommer. Rejeté : un nom NULL rendu dans la langue de chaque lecteur — une migration, et chaque lecteur d'un nom de liste (export, page hors ligne, notifications, accueil) à adapter.
  • Libellés : « Liste de courses » / « Tâches » (sources Shopping list / Tasks).
  • Import : les deux listes vides s'ajoutent à celles du fichier, conformément à « l'import ajoute » (D36). Rien à coder, c'est écrit dans la décision.
  • Familles existantes : non concernées. Contenu de départ, pas un état à maintenir : pas de migration de données, et rien ne recrée une liste supprimée.

Choix de réalisation

  • created_by = le créateur. L'auteur d'une liste n'est affiché nulle part (il sort seulement dans le contrat d'API), donc pas d'enjeu visible.
  • Où : un createStarterLists(familyId, userId, language) dans services/list.ts, appelé par createFamily après provisionCalendars — la dernière étape, qui ne dépend pas du réseau. Les deux INSERT passent dans une transaction better-sqlite3 : les deux ou aucune.
  • Pas de compensation à ajouter : lists.family_id est en ON DELETE CASCADE, et la compensation « family row » existe déjà. Un échec après coup ne laisse donc aucune liste orpheline.
  • Pas de notification, pas de projection : le créateur est seul membre, et des listes sans élément n'ont rien à projeter sur l'agenda des tâches (D31).
  • Ordre d'affichage : findListsByFamily trie par created_at, à la seconde près. Deux listes créées dans la même seconde ont un ordre indéterminé ; ajout de rowid comme critère secondaire pour que « Liste de courses » soit toujours la première.
  • Frontend : rien à changer à priori. L'accueil et l'écran Listes chargent déjà les listes sur families.activeId. À vérifier en E2E.

Tests touchés

  • families.spec.ts:82-102 crée une famille et attend « Aucune liste » : l'intention (l'isolation entre familles) reste, l'assertion devient « les deux listes de départ, et pas la liste de l'autre famille ».
  • Nouveau scénario : une famille neuve montre les deux listes ; supprimées, elles ne reviennent pas après rechargement.
  • i18n.spec.ts : un compte anglais obtient Shopping list / Tasks.
  • Suite complète à relancer : useListSelection ouvre la première liste, et un scénario qui crée sa propre liste peut ne plus tomber dessus.
## Conception technique ### Arbitrages (validés avec Maxime) - **Noms figés dans la langue du créateur.** L'i18n existe depuis D38, ce qui change la prémisse de la description. Le nom est traduit à la création avec la langue de la requête (`req.language`), puis stocké comme n'importe quel nom : c'est désormais celui de la famille, qu'elle peut renommer. Rejeté : un nom NULL rendu dans la langue de chaque lecteur — une migration, et chaque lecteur d'un nom de liste (export, page hors ligne, notifications, accueil) à adapter. - **Libellés :** « Liste de courses » / « Tâches » (sources `Shopping list` / `Tasks`). - **Import :** les deux listes vides s'ajoutent à celles du fichier, conformément à « l'import ajoute » (D36). Rien à coder, c'est écrit dans la décision. - **Familles existantes :** non concernées. Contenu de départ, pas un état à maintenir : pas de migration de données, et rien ne recrée une liste supprimée. ### Choix de réalisation - **`created_by` = le créateur.** L'auteur d'une liste n'est affiché nulle part (il sort seulement dans le contrat d'API), donc pas d'enjeu visible. - **Où :** un `createStarterLists(familyId, userId, language)` dans `services/list.ts`, appelé par `createFamily` après `provisionCalendars` — la dernière étape, qui ne dépend pas du réseau. Les deux `INSERT` passent dans une transaction `better-sqlite3` : les deux ou aucune. - **Pas de compensation à ajouter :** `lists.family_id` est en `ON DELETE CASCADE`, et la compensation « family row » existe déjà. Un échec après coup ne laisse donc aucune liste orpheline. - **Pas de notification, pas de projection :** le créateur est seul membre, et des listes sans élément n'ont rien à projeter sur l'agenda des tâches (D31). - **Ordre d'affichage :** `findListsByFamily` trie par `created_at`, à la seconde près. Deux listes créées dans la même seconde ont un ordre indéterminé ; ajout de `rowid` comme critère secondaire pour que « Liste de courses » soit toujours la première. - **Frontend :** rien à changer à priori. L'accueil et l'écran Listes chargent déjà les listes sur `families.activeId`. À vérifier en E2E. ### Tests touchés - `families.spec.ts:82-102` crée une famille et attend « Aucune liste » : l'intention (l'isolation entre familles) reste, l'assertion devient « les deux listes de départ, et pas la liste de l'autre famille ». - Nouveau scénario : une famille neuve montre les deux listes ; supprimées, elles ne reviennent pas après rechargement. - `i18n.spec.ts` : un compte anglais obtient `Shopping list` / `Tasks`. - Suite complète à relancer : `useListSelection` ouvre la première liste, et un scénario qui crée sa propre liste peut ne plus tomber dessus.
Collaborator

Réalisation — branche feat/starter-lists (non poussée)

Trois commits :

  • fix(frontend) — une course existante, révélée par ce ticket. Après un changement de famille, l'écran Listes charge les listes puis route vers la première ; un clic sur Paramètres pendant ce chargement était annulé par ce router.replace. useActiveFamily ne considérait une ouverture comme périmée qu'au changement de famille suivant, pas au démontage de la vue. Invisible tant qu'une famille neuve n'avait aucune liste ; corrigé pour toutes les vues (onScopeDispose), avec un test unitaire qui échoue sans le correctif.
  • feat(lists) — createLists (une transaction) et ORDER BY created_at, rowid.
  • feat(families) — les deux listes, nommées avec translate(req.language, …). Les sources Shopping list / Tasks existaient déjà au catalogue, avec « Liste de courses » / « Tâches ».

E2E touchés. families.spec : scénario réécrit (listes de départ, pas celles de l'autre famille) + « une liste de départ supprimée ne revient pas après rechargement ». i18n.spec : un compte anglais obtient Shopping list / Tasks. transfer.spec : l'import annonce désormais 3 listes, celles de départ de la famille source voyageant avec l'export — conforme à D41.

Portes. make lint typecheck test et make l10n-check verts. Suite E2E complète : tout passe sauf member-colors.spec.ts:199, sans rapport : ses événements sont posés le mercredi de la semaine courante à 10 h/11 h UTC, donc absents de « À venir » quand la suite tourne un mercredi soir (c'était le cas). Expliqué par l’heure d’exécution, pas rejoué sur main ; tâche séparée proposée.

Wiki. D41 ajoutée à Architecture, renvoi depuis D36 ; paragraphe « A new family is not empty » dans UX.

Reste en recette : /code-review, revue technique personnelle, revue fonctionnelle.

## Réalisation — branche `feat/starter-lists` (non poussée) Trois commits : - `fix(frontend)` — **une course existante, révélée par ce ticket.** Après un changement de famille, l'écran Listes charge les listes puis route vers la première ; un clic sur Paramètres pendant ce chargement était annulé par ce `router.replace`. `useActiveFamily` ne considérait une ouverture comme périmée qu'au changement de famille suivant, pas au démontage de la vue. Invisible tant qu'une famille neuve n'avait aucune liste ; corrigé pour toutes les vues (`onScopeDispose`), avec un test unitaire qui échoue sans le correctif. - `feat(lists)` — `createLists` (une transaction) et `ORDER BY created_at, rowid`. - `feat(families)` — les deux listes, nommées avec `translate(req.language, …)`. Les sources `Shopping list` / `Tasks` existaient déjà au catalogue, avec « Liste de courses » / « Tâches ». **E2E touchés.** `families.spec` : scénario réécrit (listes de départ, pas celles de l'autre famille) + « une liste de départ supprimée ne revient pas après rechargement ». `i18n.spec` : un compte anglais obtient `Shopping list` / `Tasks`. `transfer.spec` : l'import annonce désormais 3 listes, celles de départ de la famille source voyageant avec l'export — conforme à D41. **Portes.** `make lint typecheck test` et `make l10n-check` verts. Suite E2E complète : tout passe sauf `member-colors.spec.ts:199`, **sans rapport** : ses événements sont posés le mercredi de la semaine courante à 10 h/11 h UTC, donc absents de « À venir » quand la suite tourne un mercredi soir (c'était le cas). Expliqué par l’heure d’exécution, pas rejoué sur `main` ; tâche séparée proposée. **Wiki.** D41 ajoutée à `Architecture`, renvoi depuis D36 ; paragraphe « A new family is not empty » dans `UX`. **Reste en recette** : `/code-review`, revue technique personnelle, revue fonctionnelle.
Collaborator

/code-review (low) — main..feat/starter-lists

Un seul retour, mineur :

  • db/lists.ts:126 — l’accueil montre les listes de départ dans l’ordre inverse. findRecentLists (« Listes en cours ») trie par updated_at DESC, rowid DESC ; les deux listes, écrites dans le même instant, y apparaissent « Tâches » puis « Liste de courses », alors que l’écran Listes montre « Liste de courses » en premier. Cosmétique, mais contraire à l’intention de D41. Correctif possible : écrire « Tâches » d’abord, ou trier l’accueil par rowid ASC à égalité — à trancher.

Vérifié sans retour : useActiveFamily — seul useListSelection lit isStillActive, les autres appelants l’ignorent, donc l’invalidation au démontage ne change rien pour eux.

## /code-review (low) — `main..feat/starter-lists` Un seul retour, mineur : - **`db/lists.ts:126` — l’accueil montre les listes de départ dans l’ordre inverse.** `findRecentLists` (« Listes en cours ») trie par `updated_at DESC, rowid DESC` ; les deux listes, écrites dans le même instant, y apparaissent « Tâches » puis « Liste de courses », alors que l’écran Listes montre « Liste de courses » en premier. Cosmétique, mais contraire à l’intention de D41. Correctif possible : écrire « Tâches » d’abord, ou trier l’accueil par `rowid ASC` à égalité — à trancher. Vérifié sans retour : `useActiveFamily` — seul `useListSelection` lit `isStillActive`, les autres appelants l’ignorent, donc l’invalidation au démontage ne change rien pour eux.
Collaborator

Retour de /code-review corrigé : findRecentLists départage désormais par rowid ASC, comme l’écran Listes ; test ajouté (il échoue sans le correctif). Intégré au commit feat(lists) par autosquash. Portes vertes, summary.spec et families.spec passent. D41 mise à jour.

Retour de `/code-review` corrigé : `findRecentLists` départage désormais par `rowid ASC`, comme l’écran Listes ; test ajouté (il échoue sans le correctif). Intégré au commit `feat(lists)` par autosquash. Portes vertes, `summary.spec` et `families.spec` passent. D41 mise à jour.
Sign in to join this conversation.
No description provided.