description: "Task list for feature implementation"
Tasks: Interface graphique de tri et de consultation
Input: Design documents from /specs/006-gui-tri-consultation/
Prerequisites: plan.md, spec.md, research.md, data-model.md, contracts/, quickstart.md
Tests: Incluses — plan.md § Project Structure nomme explicitement les fichiers de test attendus, ce qui vaut demande explicite au sens des règles de génération.
Organisation: Tâches groupées par user story (spec.md) pour une implémentation et une validation indépendantes de chacune.
Contrainte structurante à respecter tout au long de l'implémentation
Principe CLI-first (constitution) : pour chaque user story, les tâches CLI (regine dossier ..., regine archive browse/restore) DOIVENT être implémentées et testées avant les écrans GUI équivalents — jamais l'inverse. L'ordre des tâches ci-dessous respecte déjà cette contrainte ; ne pas le réorganiser à l'implémentation.
Dépendances inter-specs (à lire avant de commencer)
| Dépendance | Fournit | État |
|---|---|---|
specs/001-import-photos |
regine_core.import_carte.copie.EXTENSIONS_MAITRES (réutilisée par selection.dossier_de_format, research.md § 4) |
Implémentée |
specs/003-config-contexte-travail |
regine_core.config.context/smb (contexte de travail, disponibilité et point de montage SMB, réutilisés par regine archive browse/restore) |
Implémentée |
specs/004-categorisation-dossiers |
regine_core.dossier.root — sémantique différente de regine_core.dossier.selection (cette spec), cf. research.md § 3, aucune modification requise |
Implémentée |
specs/005-checkout-reconciliation |
regine_core.archive.manifest/verrou, et comparer() qui détecte nativement un déplacement de promotion par hash (aucun code dédié requis ici) |
Implémentée |
Phase 1: Setup
- T001 [P] Ajouter la dépendance PySide6 à
packages/regine-gui/pyproject.toml(cf. research.md § 1) —uv add pyside6(6.11.2), description du paquet mise à jour (n'est plus « réservé ») - T002 [P] Créer
packages/regine-core/src/regine_core/dossier/selection.py(squelette, docstring de module uniquement — distinct deroot.py, cf. research.md § 3) - T003 [P] Créer
packages/regine-core/src/regine_core/archive/consultation.py(squelette, docstring de module uniquement) - T004 [P] Créer
packages/regine-cli/src/regine_cli/dossier_cmd.py(squelette, docstring de module uniquement) - T005 [P] Créer
packages/regine-gui/src/regine_gui/app.py(squelette, point d'entrée PySide6) - T006 [P] Vérifier que PySide6 s'installe et s'importe sur le poste de développement (
uv sync,python -c "import PySide6") — installé et importable (from PySide6.QtWidgets import QApplicationfonctionne), environnement graphique macOS disponible
Phase 2: Foundational (Blocking Prerequisites)
Objectif : la correspondance extension → dossier de format, prérequis de toute la logique de selection.py (US1).
⚠️ CRITIQUE : User Story 1 ne peut pas commencer avant la fin de cette phase. User Story 2 en est indépendante (cf. Independent Test de spec.md) et peut être menée en parallèle.
- T007 Implémenter
dossier_de_format(extension: str) -> str | Nonedansselection.py, en réutilisantregine_core.import_carte.copie.EXTENSIONS_MAITRES(research.md § 4) — jamais une nouvelle liste d'extensions dupliquée - T008 [P] Test unitaire de
dossier_de_formatdanspackages/regine-core/tests/unit/test_selection_grouping.py: extensions RAW →raw,tiff/tif→tiff,jpg/jpeg→jpeg, extension associée (sidecar) ou inconnue →None, format maître marginal (bmp) → son propre nom de dossier
Checkpoint : la correspondance de format est prête ; User Story 1 peut commencer.
Phase 3: User Story 1 - Trier une sortie photo fraîchement importée (Priority: P1) 🎯 MVP
Objectif : regroupement visuel RAW+JPEG, promotion/rétrogradation individuelle à la racine de sélection, vue combinant plusieurs dossiers de format et sous-dossiers — d'abord en CLI (Principe CLI-first), puis dans la GUI.
Test indépendant : ouvrir un dossier local avec des paires RAW+JPEG via regine dossier list, promouvoir un fichier, vérifier sur le système de fichiers.
Tests for User Story 1
- T009 [P] [US1] Test unitaire de
regrouper()danspackages/regine-core/tests/unit/test_selection_grouping.py: paire RAW+JPEG regroupée en une entrée, RAW seul, JPEG seul (mode JPEG seul), dossier sans structure reconnue → erreur dédiée (FR-013) — étendu pour couvrir un scan TIFF isolé (généralisationEntreeRegroupee.fichiers, cf. data-model.md) - T010 [P] [US1] Test unitaire de
promouvoir()/retrograder()danspackages/regine-core/tests/unit/test_selection_promotion.py: promotion idempotente (déjà à la racine → no-op), rétrogradation refuse si le fichier n'est pas à la racine, hash de contenu inchangé avant/après (Principe I/III) — écart de conception trouvé par ce test : la promotion « en deux temps » (étape puis dossier parent) ne peut pas être devinée à partir du seul chemin du fichier ;promouvoira été corrigée pour prendredossieren argument explicite, cf.contracts/regine-core-api.md - T011 [P] [US1] Test d'intégration de la vue combinée (dossier parent + sous-dossiers) dans
packages/regine-core/tests/integration/test_selection_combined_view.py—regrouperétant récursif (rglob), la vue du dossier parent inclut nativement les fichiers de ses étapes (planche-contact globale, section 10) ; aucune logique combinée dédiée nécessaire côtéregine-core - T012 [P] [US1] Test de contrat CLI dans
packages/regine-cli/tests/contract/test_cli_dossier.py:regine dossier list/promote/demote, codes de sortie, cf.contracts/cli-dossier-selection.md— 7/7 tests passent - T013 [US1] Test d'intégration bout-en-bout : une promotion via
regine dossier promoteest ensuite classéedeplacement(et nonanomalie) parregine_core.archive.reconciliation.comparerau moment de la réconciliation — preuve qu'aucun code dédié n'est nécessaire côté specs/005 (Principe III), danspackages/regine-core/tests/integration/test_selection_reconciliation.py, passe du premier coup
Implementation for User Story 1
- T014 [US1] Implémenter
regrouper(dossier: Path) -> list[EntreeRegroupee]dansselection.py - T015 [US1] Implémenter
promouvoir(dossier: Path, fichier: Path) -> Path/retrograder(fichier: Path) -> Pathdansselection.py— signature depromouvoircorrigée par rapport au contrat initial (cf. T010) - T016 [US1] Implémenter
regine dossier list/promote/demotedansdossier_cmd.py(cf.contracts/cli-dossier-selection.md) — CLI-first : cette tâche précède T017/T018, jamais l'inverse - T017 [US1] Implémenter l'écran de tri (
tri_view.py) dansregine-gui: appelle exclusivementregine_core.dossier.selection, aucune logique de regroupement/promotion propre à la GUI (Principe VI) — validé manuellement en mode headless (QT_QPA_PLATFORM=offscreen) : ouverture d'un dossier, regroupement, promotion via l'écran, fichier réellement déplacé sur le système de fichiers - T018 [US1] Implémenter la vue combinée (dossier parent + sous-dossiers) dans
tri_view.py— aucun code combiné dédié nécessaire :regrouper()étant récursif (T011), ouvrir un dossier parent affiche déjà ses fichiers et ceux de ses sous-dossiers dans la même liste. Décision de portée documentée : la promotion depuis cet écran cible toujours le dossier explicitement ouvert (self._dossier) — pour la promotion en deux temps d'un voyage, l'utilisateur ouvre l'étape puis, séparément, le dossier parent (deux gestes explicites distincts, cf. docstring detri_view.py), plutôt qu'un raccourci automatique
Checkpoint : User Story 1 fonctionnelle et testable indépendamment (MVP), CLI et GUI cohérentes entre elles.
Phase 4: User Story 2 - Retrouver une photo dans l'archive sans tout rapatrier (Priority: P2)
Objectif : consultation en lecture seule de l'archive avec recherche, restauration ciblée d'un fichier sans checkout complet — d'abord en CLI, puis dans la GUI. Indépendante de User Story 1 (aucune dépendance sur selection.py).
Test indépendant : regine archive browse --search ... puis regine archive restore vers un dossier local vide, sans checkout préalable.
Tests for User Story 2
- T019 [P] [US2] Test unitaire de
rechercher()danspackages/regine-core/tests/unit/test_consultation_search.py: filtre par nom de dossier/date/texte, aucun résultat → liste vide (pas une erreur) - T020 [P] [US2] Test unitaire de
restaurer()danspackages/regine-core/tests/unit/test_consultation_restore.py: copie vérifiée par somme de contrôle, refuse d'écraser un fichier local de contenu différent (FR-012), lève si la source est introuvable dans l'archive - T021 [P] [US2] Test de contrat CLI dans
packages/regine-cli/tests/contract/test_cli_archive_consultation.py:regine browse/regine restore(renommé, cf. écart documenté danscontracts/cli-archive-consultation.md), y compris le cas aucune archive configurée — 3/3 tests passent - T022 [US2] Test d'intégration : une restauration ciblée ne pose ni ne modifie aucun verrou du dossier d'origine (
regine_core.archive.verrou.verifierreste inchangé avant/après), danspackages/regine-core/tests/integration/test_consultation_restore_verrou.py(renommé : collision de nom de module avec le test unitaire T020, pytest exige des noms de fichiers de test uniques sans__init__.py)
Implementation for User Story 2
- T023 [US2] Implémenter
rechercher(archive_root_local: Path, requete: str | None) -> list[ResultatRecherche]dansconsultation.py - T024 [US2] Implémenter
restaurer(chemin_source: Path, chemin_destination: Path) -> Nonedansconsultation.py— réutiliseregine_core.archive.checkout.IntegriteCopieErrorplutôt qu'une exception dupliquée - T025 [US2] Étendre
packages/regine-cli/src/regine_cli/archive_cmd.pyavecregine browse/regine restore(charge le contexte,smb.assurer_disponibiliteavant tout parcours, cf.contracts/cli-archive-consultation.md) — écart trouvé à l'implémentation :checkout/reconcilesont déjà des sous-commandes de premier niveau (pasregine archive checkout),browse/restoresuivent la même convention plutôt que d'introduire un niveauarchiveinédit — CLI-first : précède T026 - T026 [US2] Implémenter l'écran de consultation (
consultation_view.py) dansregine-gui: lecture seule stricte, aucune action d'écriture proposée (FR-010) — bug réel trouvé par test manuel headless : la résolution du point de montage de l'archive était dupliquée entrearchive_cmd.pyetconsultation_view.pyavec un style d'import différent, rendant l'une des deux résolutions impossible à patcher en test et incohérente avec l'autre (violation du Principe VI). Corrigé en extrayantregine_core.config.smb.resoudre_archive_locale(conn), désormais seule source de vérité partagée par la CLI et la GUI ; validé de bout en bout en mode headless (QT_QPA_PLATFORM=offscreen) : recherche puis restauration réelles
Checkpoint : les deux user stories sont fonctionnelles indépendamment.
Phase 5: User Story 3 - Importer une carte mémoire depuis l'interface graphique (Priority: P3)
Objectif : onglet d'import GUI appelant directement regine_core.import_carte (research.md § 6) — jamais regine_cli.import_cmd — pour reprendre à l'identique les étapes déjà couvertes et testées par specs/001-import-photos (analyse, découpage, destination, titre, confirmation, désambiguïsation de collision). Aucune nouvelle fonction regine_core : regine import (CLI) existe déjà et n'est pas modifié.
Test indépendant : ouvrir l'onglet d'import, importer une carte à une seule journée jamais importée, vérifier que le dossier archivé obtenu est identique à celui que produirait regine import sur la même carte (Scénario 8 de quickstart.md).
Implementation for User Story 3
- T030 [US3] Créer
packages/regine-gui/src/regine_gui/import_view.py(squelette, docstring de module uniquement) - T031 [US3] Implémenter l'étape d'analyse dans
import_view.py: sélection de la carte (QFileDialog), appel àcopie.copier_cartepuisgroupage.decouper_en_groupes/jours_candidats_au_detachement, affichage de la répartition jour par jour (US3 Acceptance Scenario 1) - T032 [US3] Implémenter le détachement de jour dans
import_view.py: appel àgroupage.detacher_jourssur confirmation explicite de l'utilisateur, jamais automatique (US3 Acceptance Scenario 2, FR-006 despecs/001-import-photos) - T033 [US3] Implémenter le choix de destination et le titre dans
import_view.py: appel àdestination.resoudre_destination/resoudre_fusionetnommage.construire_nom_dossier*selon le type choisi, sans logique de nommage ou de collision propre à la GUI (US3 Acceptance Scenario 3) — écart trouvé à l'implémentation : le cas « nouveau sous-dossier » nécessitait la même reconstruction deRootLocationà partir d'un chemin relatif queregine_cli.import_cmd._root_location_depuis_chemin(privée) ; plutôt que de la dupliquer une seconde fois (même schéma quesmb.resoudre_archive_locale, déjà rencontré pour US1/US2), elle a été rendue publique dansregine_core.import_carte.destination.root_location_depuis_cheminetimport_cmd.pymis à jour pour l'appeler elle aussi (aucun changement de comportement CLI, 172/172 tests toujours au vert) — cf.contracts/regine-core-api.md - T034 [US3] Implémenter le résumé de confirmation avant écriture dans
import_view.py: appel àpush.preparer_resume, attente d'une confirmation explicite (QMessageBox) avant tout appel àpush.archiver(US3 Acceptance Scenario 4, FR-015) - T035 [US3] Implémenter la résolution de collision de boîtier dans
import_view.py: appel àcopie.resoudre_collisions_boitiers, présentation des mêmes options que la CLI (profil connu, numéro de série, étiquetage manuel) en cas de collision non résolue automatiquement (US3 Acceptance Scenario 5, FR-016,specs/002-profil-boitiers-optionnel) - T036 [US3] Implémenter la progression et le résultat dans
import_view.py: retour visuel (journal texte) pendantpush.archiver/push.publier_copie_locale, résumé final (US3 Acceptance Scenario 6, FR-017) — bug réel trouvé par test manuel (utilisateur) : la première version appelaitarchiver/publier_copie_localedirectement sur le thread principal Qt ; ces fonctions recalculent plusieurs sommes de contrôle par fichier (copie carte→local, local→archive, archive→copie locale, plus le hash image exiftool), ce qui gelait entièrement la fenêtre le temps de l'opération — violation de FR-017 malgré le journal déjà en place. Corrigé en déplaçantarchiver/publier_copie_localesur unQThreaddédié (_ArchivageWorker), la fenêtre restant utilisable pendant l'archivage. Second bug trouvé pendant cette correction : les signaux du worker étaient connectés à des lambdas plutôt qu'à de vraies méthodes liées deself(QMainWindow) — Qt ne détectait alors pas l'affinité de thread du récepteur et exécutait les slots en connexion directe dans le thread d'arrière-plan, provoquant unsqlite3.ProgrammingError(connexion utilisée hors de son thread de création) dès la fermeture de la connexion de contexte. Corrigé en connectant les signaux à de vraies méthodes deself(_sur_archivage_succes/_sur_archivage_echec), permettant à Qt d'utiliser une connexion en file d'attente vers le thread principal - T037 [US3] Câbler l'onglet « Import » dans
packages/regine-gui/src/regine_gui/app.py(FenetrePrincipale), aux côtés des onglets Tri/Consultation/Configuration déjà existants ; le dossier importé peut être ouvert directement dans l'onglet Tri via un signal Qtdossier_importe(US3 Acceptance Scenario 7) —tri_view.EcranTri._charger_dossierrenommée en méthode publiqueouvrir_dossierpour permettre cette connexion inter-onglets sans dupliquer le regroupement RAW+JPEG - T038 [US3] Validé manuellement en mode headless (
QT_QPA_PLATFORM=offscreen) : import complet du chemin « nouveau dossier » (analyse, titre, catégorie vide, confirmation, archivage en arrière-plan) déclenché par script simulant les réponses utilisateur (QInputDialog/QMessageBoxmonkeypatchés,resoudre_archive_localemonkeypatché pour éviter une dépendance à un vrai partage SMB monté, boucle d'événements Qt maintenue active jusqu'à la fin du thread d'archivage, même méthode quespecs/003T033/specs/006T017/T026) — dossier obtenu (AAAA-MM-JJ_Titre, fichiers renommésdate_titre_nomOrigine.ext) structurellement identique à ce que produitregine import(Scénario 8 dequickstart.md, SC-007). Complété par un test manuel réel (utilisateur, application non headless) sur une carte de test avec des fichiers plus volumineux, qui a mis en évidence les deux bugs de gel/threading documentés en T036 — tous deux corrigés et revalidés. Chemins « nouveau sous-dossier »/« fusion »/collision de boîtier non exercés par un script automatisé (aucune carte de test avec EXIF/voyage multi-étapes disponible dans cet environnement) : validés par lecture de code, chaque branche appelant exclusivement des fonctionsregine_core.import_carte/camera_profiledéjà testées unitairement parspecs/001/specs/002
Checkpoint : User Story 3 fonctionnelle et testable indépendamment ; CLI (déjà livrée par specs/001-import-photos) et GUI cohérentes entre elles.
Phase 6: Polish & Cross-Cutting Concerns
- T027 [P] Exécuter les 6 scénarios de
specs/006-gui-tri-consultation/quickstart.md— Scénario 1 (trier via CLI) : exécuté viauv run python -m regine_cli.dossier_cmd, réussi. Scénario 4 (consulter/restaurer) : validé via les fonctions core directement (le monkeypatch SMB inter-process n'est pas possible en subprocess, déjà couvert partest_cli_archive_consultation.py). Scénario 6 (archive non configurée) : réussi. Scénarios 2, 3, 5 (GUI) : validés manuellement en mode headless (QT_QPA_PLATFORM=offscreen), ouverture/regroupement/promotion et recherche/restauration réelles, sans interaction visuelle humaine (aucun poste avec écran physique disponible dans cet environnement de développement — limitation déjà documentée pourspecs/003scénario 4) - T028 [P]
ruff check --fix+ruff formatsurpackages/regine-core,packages/regine-clietpackages/regine-gui— 172/172 tests passent après coup - T029 Mettre à jour
contracts/regine-core-api.md/cli-dossier-selection.md/cli-archive-consultation.mdsi l'implémentation révèle un écart avec les signatures documentées (patron déjà établi dans les specs précédentes) — tous mis à jour au fil de l'implémentation (signaturepromouvoir, renommagebrowse/restore,resoudre_archive_locale) - T039 [P] Exécuté le Scénario 8 de
quickstart.md(cf. T038 ; Scénario 7 déjà couvert parspecs/001-import-photos, sans duplication) etruff check --fix/ruff formatsurpackages/regine-gui,packages/regine-cli,packages/regine-core(nouveau fichierimport_view.py, nouvelle fonction publiqueroot_location_depuis_chemin) — 172/172 testsregine-core/regine-clitoujours au vert, aucun nouveau test requis côté core (aucune nouvelle règle métier ajoutée par US3, seulement une fonction déjà testée indirectement viaimport_cmdrendue publique) - T040 [US3] Révision 2026-09-22 (cf. Clarifications de
specs/001-import-photos/spec.md) :import_view.pymis à jour pour suivre le nouveau pipelinespecs/001—classer_par_format/publier_localementremplacentarchiver/publier_copie_localedans_PublicationLocaleWorker(ex-_ArchivageWorker) ;_analysern'exige plus que le partage SMB soit monté (config.smb.chemin_local_du_partage, calcul pur, remplaceresoudre_archive_locale) ; dialogues/journal reformulés pour ne plus jamais mentionner un « archivage » à l'import (« Confirmer la copie de travail locale », message final indiquant la commanderegine reconcileà lancer plus tard). Revalidé manuellement en mode headless sans archive configurée/montée : structureraw/jpegobtenue, aucune écriture sous/Volumes/...— 176/176 testsregine-core/regine-cliau vert (172 + 4 issus de la révisionspecs/001, cf. sontasks.mdPhase 7) - T041 [US3] Révision 2026-09-22 (bis) — détection de doublons (FR-004 révisé, cf.
specs/001-import-photos/tasks.mdPhase 8) : couverte sans modification de spec grâce à FR-014 (« toute règle déjà vraie côté CLI reste vraie à l'identique depuis la GUI ») —import_view._analyserappellehistorique.rechercher_doublonsjuste aprèscopier_carte(nouvelle méthode_confirmer_malgre_doublons,QMessageBox.question), et_sur_publication_succesappellehistorique.enregistrer_importsur le thread principal (jamais dans_PublicationLocaleWorker, même précaution que pour la connexion SQLite déjà rencontrée en T036). Revalidé manuellement en mode headless : import de la même carte vers deux dossiers différents déclenche bien l'avertissement au second import, avec le chemin du premier dossier — 180/180 testsregine-core/regine-cliau vert
Dependencies & Execution Order
Phase Dependencies
- Setup (Phase 1) : aucune dépendance, tâches
[P]en parallèle. - Foundational (Phase 2) : dépend de Setup ; bloque User Story 1 uniquement (
dossier_de_formatn'est utilisée que parselection.py). - User Story 1 (Phase 3) : dépend de Foundational.
- User Story 2 (Phase 4) : dépend de Setup uniquement — indépendante de Foundational et de User Story 1, peut être menée en parallèle de la Phase 3.
- User Story 3 (Phase 5) : dépend de Setup (PySide6,
app.py) et despecs/001-import-photos(déjà livrée, fournitregine_core.import_cartesans modification) ; indépendante de Foundational, User Story 1 et User Story 2 — aucune dépendance CLI nouvelle puisqueregine importexiste déjà. - Polish (Phase 6) : dépend des user stories livrées (au minimum US1 ; T039 dépend spécifiquement de US3).
Au sein de chaque user story
CLI avant GUI, toujours (Principe CLI-first — cf. contrainte structurante en tête de fichier) : T016 avant T017/T018 ; T025 avant T026 ; pour US3, la CLI (regine import) est déjà livrée par specs/001-import-photos, donc aucune tâche CLI n'est requise avant T030-T037.
Implementation Strategy
MVP = Phase 1 + 2 + 3 (US1, CLI puis GUI). US2 est un incrément indépendant, livrable séparément dans n'importe quel ordre relatif à US1 une fois Setup terminé. US3 (ajoutée le 2026-09-21) est un incrément de confort pur, livrable à tout moment après Setup — sans lui, la CLI (regine import, specs/001-import-photos) reste pleinement fonctionnelle et suffisante pour importer une carte mémoire.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 |
|