fonzarely/regine-photos-archiverpublic Fork
⑂ main
Commits
⬇ Clone ▾
git clone https://git.rickub.com/fonzarely/regine-photos-archiver.git
git clone ssh://git@rickub.com/fonzarely/regine-photos-archiver.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

Download ZIP
duplicates a1d9142 · on main · Fabien Champigny · Sep 22, 2026
tasks.md · 152 lines · 23.1 KBmarkdown
Blame HistoryOpen raw

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 de root.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 QApplication fonctionne), 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 | None dans selection.py, en réutilisant regine_core.import_carte.copie.EXTENSIONS_MAITRES (research.md § 4) — jamais une nouvelle liste d'extensions dupliquée
  • T008 [P] Test unitaire de dossier_de_format dans packages/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() dans packages/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éralisation EntreeRegroupee.fichiers, cf. data-model.md)
  • T010 [P] [US1] Test unitaire de promouvoir()/retrograder() dans packages/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 ; promouvoir a été corrigée pour prendre dossier en 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 promote est ensuite classée deplacement (et non anomalie) par regine_core.archive.reconciliation.comparer au moment de la réconciliation — preuve qu'aucun code dédié n'est nécessaire côté specs/005 (Principe III), dans packages/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] dans selection.py
  • T015 [US1] Implémenter promouvoir(dossier: Path, fichier: Path) -> Path / retrograder(fichier: Path) -> Path dans selection.py — signature de promouvoir corrigée par rapport au contrat initial (cf. T010)
  • T016 [US1] Implémenter regine dossier list/promote/demote dans dossier_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) dans regine-gui : appelle exclusivement regine_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 de tri_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() dans packages/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() dans packages/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é dans contracts/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.verifier reste inchangé avant/après), dans packages/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] dans consultation.py
  • T024 [US2] Implémenter restaurer(chemin_source: Path, chemin_destination: Path) -> None dans consultation.py — réutilise regine_core.archive.checkout.IntegriteCopieError plutôt qu'une exception dupliquée
  • T025 [US2] Étendre packages/regine-cli/src/regine_cli/archive_cmd.py avec regine browse/regine restore (charge le contexte, smb.assurer_disponibilite avant tout parcours, cf. contracts/cli-archive-consultation.md) — écart trouvé à l'implémentation : checkout/reconcile sont déjà des sous-commandes de premier niveau (pas regine archive checkout), browse/restore suivent la même convention plutôt que d'introduire un niveau archive inédit — CLI-first : précède T026
  • T026 [US2] Implémenter l'écran de consultation (consultation_view.py) dans regine-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 entre archive_cmd.py et consultation_view.py avec 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 extrayant regine_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_carte puis groupage.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_jours sur confirmation explicite de l'utilisateur, jamais automatique (US3 Acceptance Scenario 2, FR-006 de specs/001-import-photos)
  • T033 [US3] Implémenter le choix de destination et le titre dans import_view.py : appel à destination.resoudre_destination/resoudre_fusion et nommage.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 de RootLocation à partir d'un chemin relatif que regine_cli.import_cmd._root_location_depuis_chemin (privée) ; plutôt que de la dupliquer une seconde fois (même schéma que smb.resoudre_archive_locale, déjà rencontré pour US1/US2), elle a été rendue publique dans regine_core.import_carte.destination.root_location_depuis_chemin et import_cmd.py mis à 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) pendant push.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 appelait archiver/publier_copie_locale directement 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çant archiver/publier_copie_locale sur un QThread dé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 de self (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 un sqlite3.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 de self (_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 Qt dossier_importe (US3 Acceptance Scenario 7) — tri_view.EcranTri._charger_dossier renommée en méthode publique ouvrir_dossier pour 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/QMessageBox monkeypatchés, resoudre_archive_locale monkeypatché 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 que specs/003 T033/specs/006 T017/T026) — dossier obtenu (AAAA-MM-JJ_Titre, fichiers renommés date_titre_nomOrigine.ext) structurellement identique à ce que produit regine import (Scénario 8 de quickstart.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 fonctions regine_core.import_carte/camera_profile déjà testées unitairement par specs/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é via uv 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 par test_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 pour specs/003 scénario 4)
  • T028 [P] ruff check --fix + ruff format sur packages/regine-core, packages/regine-cli et packages/regine-gui — 172/172 tests passent après coup
  • T029 Mettre à jour contracts/regine-core-api.md/cli-dossier-selection.md/cli-archive-consultation.md si 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 (signature promouvoir, renommage browse/restore, resoudre_archive_locale)
  • T039 [P] Exécuté le Scénario 8 de quickstart.md (cf. T038 ; Scénario 7 déjà couvert par specs/001-import-photos, sans duplication) et ruff check --fix/ruff format sur packages/regine-gui, packages/regine-cli, packages/regine-core (nouveau fichier import_view.py, nouvelle fonction publique root_location_depuis_chemin) — 172/172 tests regine-core/regine-cli toujours 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 via import_cmd rendue publique)
  • T040 [US3] Révision 2026-09-22 (cf. Clarifications de specs/001-import-photos/spec.md) : import_view.py mis à jour pour suivre le nouveau pipeline specs/001 — classer_par_format/publier_localement remplacent archiver/publier_copie_locale dans _PublicationLocaleWorker (ex-_ArchivageWorker) ; _analyser n'exige plus que le partage SMB soit monté (config.smb.chemin_local_du_partage, calcul pur, remplace resoudre_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 commande regine reconcile à lancer plus tard). Revalidé manuellement en mode headless sans archive configurée/montée : structure raw/jpeg obtenue, aucune écriture sous /Volumes/... — 176/176 tests regine-core/regine-cli au vert (172 + 4 issus de la révision specs/001, cf. son tasks.md Phase 7)
  • T041 [US3] Révision 2026-09-22 (bis) — détection de doublons (FR-004 révisé, cf. specs/001-import-photos/tasks.md Phase 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._analyser appelle historique.rechercher_doublons juste après copier_carte (nouvelle méthode _confirmer_malgre_doublons, QMessageBox.question), et _sur_publication_succes appelle historique.enregistrer_import sur 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 tests regine-core/regine-cli au 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_format n'est utilisée que par selection.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 de specs/001-import-photos (déjà livrée, fournit regine_core.import_carte sans modification) ; indépendante de Foundational, User Story 1 et User Story 2 — aucune dépendance CLI nouvelle puisque regine import existe 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
---

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

- [X] 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é »)
- [X] T002 [P] Créer `packages/regine-core/src/regine_core/dossier/selection.py` (squelette, docstring de module uniquement — distinct de `root.py`, cf. research.md § 3)
- [X] T003 [P] Créer `packages/regine-core/src/regine_core/archive/consultation.py` (squelette, docstring de module uniquement)
- [X] T004 [P] Créer `packages/regine-cli/src/regine_cli/dossier_cmd.py` (squelette, docstring de module uniquement)
- [X] T005 [P] Créer `packages/regine-gui/src/regine_gui/app.py` (squelette, point d'entrée PySide6)
- [X] 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 QApplication` fonctionne), 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.

- [X] T007 Implémenter `dossier_de_format(extension: str) -> str | None` dans `selection.py`, en réutilisant `regine_core.import_carte.copie.EXTENSIONS_MAITRES` (research.md § 4) — jamais une nouvelle liste d'extensions dupliquée
- [X] T008 [P] Test unitaire de `dossier_de_format` dans `packages/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

- [X] T009 [P] [US1] Test unitaire de `regrouper()` dans `packages/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éralisation `EntreeRegroupee.fichiers`, cf. data-model.md)
- [X] T010 [P] [US1] Test unitaire de `promouvoir()`/`retrograder()` dans `packages/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 ; `promouvoir` a été corrigée pour prendre `dossier` en argument explicite, cf. `contracts/regine-core-api.md`
- [X] 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`
- [X] 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
- [X] T013 [US1] Test d'intégration bout-en-bout : une promotion via `regine dossier promote` est ensuite classée `deplacement` (et non `anomalie`) par `regine_core.archive.reconciliation.comparer` au moment de la réconciliation — preuve qu'aucun code dédié n'est nécessaire côté specs/005 (Principe III), dans `packages/regine-core/tests/integration/test_selection_reconciliation.py`, passe du premier coup

### Implementation for User Story 1

- [X] T014 [US1] Implémenter `regrouper(dossier: Path) -> list[EntreeRegroupee]` dans `selection.py`
- [X] T015 [US1] Implémenter `promouvoir(dossier: Path, fichier: Path) -> Path` / `retrograder(fichier: Path) -> Path` dans `selection.py` — signature de `promouvoir` corrigée par rapport au contrat initial (cf. T010)
- [X] T016 [US1] Implémenter `regine dossier list/promote/demote` dans `dossier_cmd.py` (cf. `contracts/cli-dossier-selection.md`) — **CLI-first : cette tâche précède T017/T018, jamais l'inverse**
- [X] T017 [US1] Implémenter l'écran de tri (`tri_view.py`) dans `regine-gui` : appelle exclusivement `regine_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
- [X] 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 de `tri_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

- [X] T019 [P] [US2] Test unitaire de `rechercher()` dans `packages/regine-core/tests/unit/test_consultation_search.py` : filtre par nom de dossier/date/texte, aucun résultat → liste vide (pas une erreur)
- [X] T020 [P] [US2] Test unitaire de `restaurer()` dans `packages/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
- [X] 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é dans `contracts/cli-archive-consultation.md`), y compris le cas aucune archive configurée — 3/3 tests passent
- [X] 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.verifier` reste inchangé avant/après), dans `packages/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

- [X] T023 [US2] Implémenter `rechercher(archive_root_local: Path, requete: str | None) -> list[ResultatRecherche]` dans `consultation.py`
- [X] T024 [US2] Implémenter `restaurer(chemin_source: Path, chemin_destination: Path) -> None` dans `consultation.py` — réutilise `regine_core.archive.checkout.IntegriteCopieError` plutôt qu'une exception dupliquée
- [X] T025 [US2] Étendre `packages/regine-cli/src/regine_cli/archive_cmd.py` avec `regine browse`/`regine restore` (charge le contexte, `smb.assurer_disponibilite` avant tout parcours, cf. `contracts/cli-archive-consultation.md`) — **écart trouvé à l'implémentation** : `checkout`/`reconcile` sont déjà des sous-commandes de premier niveau (pas `regine archive checkout`), `browse`/`restore` suivent la même convention plutôt que d'introduire un niveau `archive` inédit — **CLI-first : précède T026**
- [X] T026 [US2] Implémenter l'écran de consultation (`consultation_view.py`) dans `regine-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 entre `archive_cmd.py` et `consultation_view.py` avec 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 extrayant `regine_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

- [X] T030 [US3] Créer `packages/regine-gui/src/regine_gui/import_view.py` (squelette, docstring de module uniquement)
- [X] T031 [US3] Implémenter l'étape d'analyse dans `import_view.py` : sélection de la carte (`QFileDialog`), appel à `copie.copier_carte` puis `groupage.decouper_en_groupes`/`jours_candidats_au_detachement`, affichage de la répartition jour par jour (US3 Acceptance Scenario 1)
- [X] T032 [US3] Implémenter le détachement de jour dans `import_view.py` : appel à `groupage.detacher_jours` sur confirmation explicite de l'utilisateur, jamais automatique (US3 Acceptance Scenario 2, FR-006 de `specs/001-import-photos`)
- [X] T033 [US3] Implémenter le choix de destination et le titre dans `import_view.py` : appel à `destination.resoudre_destination`/`resoudre_fusion` et `nommage.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 de `RootLocation` à partir d'un chemin relatif que `regine_cli.import_cmd._root_location_depuis_chemin` (privée) ; plutôt que de la dupliquer une seconde fois (même schéma que `smb.resoudre_archive_locale`, déjà rencontré pour US1/US2), elle a été rendue publique dans `regine_core.import_carte.destination.root_location_depuis_chemin` et `import_cmd.py` mis à jour pour l'appeler elle aussi (aucun changement de comportement CLI, 172/172 tests toujours au vert) — cf. `contracts/regine-core-api.md`
- [X] 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)
- [X] 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`)
- [X] T036 [US3] Implémenter la progression et le résultat dans `import_view.py` : retour visuel (journal texte) pendant `push.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 appelait `archiver`/`publier_copie_locale` directement 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çant `archiver`/`publier_copie_locale` sur un `QThread` dé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 de `self` (`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 un `sqlite3.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 de `self` (`_sur_archivage_succes`/`_sur_archivage_echec`), permettant à Qt d'utiliser une connexion en file d'attente vers le thread principal
- [X] 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 Qt `dossier_importe` (US3 Acceptance Scenario 7) — `tri_view.EcranTri._charger_dossier` renommée en méthode publique `ouvrir_dossier` pour permettre cette connexion inter-onglets sans dupliquer le regroupement RAW+JPEG
- [X] 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`/`QMessageBox` monkeypatchés, `resoudre_archive_locale` monkeypatché 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 que `specs/003` T033/`specs/006` T017/T026) — dossier obtenu (`AAAA-MM-JJ_Titre`, fichiers renommés `date_titre_nomOrigine.ext`) structurellement identique à ce que produit `regine import` (Scénario 8 de `quickstart.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 fonctions `regine_core.import_carte`/`camera_profile` déjà testées unitairement par `specs/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

- [X] T027 [P] Exécuter les 6 scénarios de `specs/006-gui-tri-consultation/quickstart.md` — Scénario 1 (trier via CLI) : exécuté via `uv 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 par `test_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 pour `specs/003` scénario 4)
- [X] T028 [P] `ruff check --fix` + `ruff format` sur `packages/regine-core`, `packages/regine-cli` et `packages/regine-gui` — 172/172 tests passent après coup
- [X] T029 Mettre à jour `contracts/regine-core-api.md`/`cli-dossier-selection.md`/`cli-archive-consultation.md` si 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 (signature `promouvoir`, renommage `browse`/`restore`, `resoudre_archive_locale`)
- [X] T039 [P] Exécuté le Scénario 8 de `quickstart.md` (cf. T038 ; Scénario 7 déjà couvert par `specs/001-import-photos`, sans duplication) et `ruff check --fix`/`ruff format` sur `packages/regine-gui`, `packages/regine-cli`, `packages/regine-core` (nouveau fichier `import_view.py`, nouvelle fonction publique `root_location_depuis_chemin`) — 172/172 tests `regine-core`/`regine-cli` toujours 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 via `import_cmd` rendue publique)
- [X] T040 [US3] Révision 2026-09-22 (cf. Clarifications de `specs/001-import-photos/spec.md`) : `import_view.py` mis à jour pour suivre le nouveau pipeline `specs/001` — `classer_par_format`/`publier_localement` remplacent `archiver`/`publier_copie_locale` dans `_PublicationLocaleWorker` (ex-`_ArchivageWorker`) ; `_analyser` n'exige plus que le partage SMB soit monté (`config.smb.chemin_local_du_partage`, calcul pur, remplace `resoudre_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 commande `regine reconcile` à lancer plus tard). Revalidé manuellement en mode headless sans archive configurée/montée : structure `raw`/`jpeg` obtenue, aucune écriture sous `/Volumes/...` — 176/176 tests `regine-core`/`regine-cli` au vert (172 + 4 issus de la révision `specs/001`, cf. son `tasks.md` Phase 7)
- [X] T041 [US3] Révision 2026-09-22 (bis) — détection de doublons (FR-004 révisé, cf. `specs/001-import-photos/tasks.md` Phase 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._analyser` appelle `historique.rechercher_doublons` juste après `copier_carte` (nouvelle méthode `_confirmer_malgre_doublons`, `QMessageBox.question`), et `_sur_publication_succes` appelle `historique.enregistrer_import` sur 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 tests `regine-core`/`regine-cli` au 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_format` n'est utilisée que par `selection.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 de `specs/001-import-photos` (déjà livrée, fournit `regine_core.import_carte` sans modification) ; indépendante de Foundational, User Story 1 et User Story 2 — aucune dépendance CLI nouvelle puisque `regine import` existe 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.