Les correspondances gardent le chemin cité par le logiciel (sourcePath) :
plusieurs fichiers d'une même personne ne se confondent plus, le décompte
des fichiers ajoutés et restants est juste, et le pied du panneau parle de
fichiers quand des documents ont été ajoutés.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
À l'étape du dossier de photos, les fichiers cités par le logiciel en plus
des portraits (actes, registres, photos de famille) sont retrouvés dans le
même dossier et ajoutés aux souvenirs de chaque personne, avec le titre et
la source venus du GEDCOM. Un seul bouton « Tout ajouter » ; les fichiers
restants sont gardés pour rouvrir l'étape depuis les paramètres. Pour #59.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
En plus du portrait, l'import renvoie mediaRefs : les fichiers cités pour
chaque personne créée, où qu'ils soient accrochés (sous la personne, un
événement, une source citée, la fiche de la source, ou la famille pour les
actes de mariage), avec leur titre et leur source. Comme Gramps : adresses
web écartées, un fichier cité deux fois gardé une fois. Particularités
gérées : titre dans la note (Heredis, RTF compris), _PHOTO pour le portrait
(Family Tree Maker), chemins file:// (GEDCOM 7). Pour #59.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
‹ 2/4 › à côté de la pastille Édition au lieu d'une ligne à part ; la
place complète reste annoncée aux lecteurs d'écran.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
En mode édition, un administrateur glisse une personne à l'horizontale
parmi ses frères et soeurs pour changer leur ordre. Pour chaque place visée,
le vrai placement est recalculé : la personne soulevée suit le pointeur,
les autres branches glissent vers leur place avec un ressort, et rien ne
saute au lâcher. Appui long au doigt, Échap ou un deuxième doigt pour
annuler, mouvement réduit respecté. La fiche propose aussi deux flèches
(Déplacer à gauche, Déplacer à droite) avec la même animation. L'ordre est
appliqué tout de suite puis enregistré ; en cas d'échec l'arbre revient
en arrière et un message le dit.
Idée de départ : PR #60 de py5932 (glisser-déposer des personnes).
Co-Authored-By: py5932 <py.dequidt@gmail.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
L'arbre ne montre qu'un lien par enfant et par union ; le lien vers l'autre
parent de la même union reçoit le même ordre, pour que les deux restent
d'accord.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Le placement par blocs rangeait les enfants dans l'ordre des identifiants,
displayOrder n'avait donc aucun effet. Les enfants d'une union sont
maintenant triés sur displayOrder, avec un tri stable : un arbre jamais
réordonné garde exactement son placement. Dans l'arbre de démo, Sophie
(ordre 1) repasse devant Thomas (ordre 2).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sans ordre fourni, un lien parent-enfant créé (route, contribution, import
GEDCOM) prend l'ordre suivant celui de la fratrie, pour qu'un enfant ajouté
après un réordonnancement ne passe pas devant les autres.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PUT /trees/:id/links/order réécrit le displayOrder des liens parent-enfant
d'une fratrie, dans l'ordre reçu. Administrateurs seulement ; chaque lien
doit appartenir à l'arbre et à son enfant, et les enfants partager au moins
un parent. Journalisé (children_reordered).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Le mainteneur préfère un réordonnancement des frères et soeurs au placement
libre (coordonnées enregistrées). Ce commit annule le glisser libre, ses
corrections, la migration des coordonnées et le bouton de remise à zéro ;
le réordonnancement arrive dans les commits suivants.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
L'onglet Immich des paramètres de l'arbre est supprimé. Sa section passe dans
l'onglet Avancé, au-dessus de la zone de suppression, et n'apparaît que si le
serveur déclare au moins une instance. La config Immich n'est lue que pour les
administrateurs, et le panneau attend cette réponse avant de s'afficher pour
que la zone de suppression ne bouge pas.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Les visages affichent leur miniature ronde au lieu de l'icône. Chargées
quand elles entrent dans la liste (IntersectionObserver), trois requêtes
au plus en parallèle, une requête par visage, cache le temps du panneau.
Les URL blob sont révoquées au démontage. En attente ou sans vignette : un
rond neutre.
- La personne déjà associée à la fiche est marquée « Associée » (lecture
réservée aux administrateurs, via la nouvelle route de lien).
- Le cadre pointillé vide disparaît : l'aperçu n'apparaît qu'une fois un
visage choisi, avec la photo carrée arrondie des fiches. Le bouton principal
reste désactivé tant que rien n'est choisi.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Au passage à deux doigts (ou en cas d'interruption tactile), le glisser en
cours est annulé et la personne revient à sa place : plus d'enregistrement
pendant un pincement. Un doigt qui reste après un pincement ne saisit plus
de personne, il ne fait que déplacer l'arbre.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Onglet Avancé des paramètres de l'arbre (admin) : une action « Rétablir la
disposition automatique », visible seulement s'il existe des positions
manuelles, avec confirmation. Elle appelle la route admin
/trees/:id/layout/reset puis recharge le graphe.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Contributeur : après l'envoi, la place proposée reste à l'écran comme un
nom modifié, au lieu de revenir à l'ancienne place jusqu'à l'approbation.
- Admin : un échec d'enregistrement d'une personne est signalé par un toast
d'erreur, et le brouillon est conservé au lieu d'être vidé.
- Gestionnaire handleResetTreeLayout (le bouton arrive dans le commit suivant).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Le sélecteur doit marquer la personne Immich déjà associée. L'identifiant ne
figure pas dans le graphe public : GET /trees/:id/immich/links/:personId le
renvoie aux seuls administrateurs de l'arbre.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Une contribution qui déplace une personne affiche désormais la ligne
« Position dans l'arbre » (Déplacée, Replacée automatiquement), sans
coordonnées brutes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
La liste des positions manuelles était mise en cache sur un hasDraft et
une fonction qui ne changent plus après le premier glisser : une seule
personne (la première) restait placée. Le brouillon expose maintenant un
draftRevision incrémenté à chaque écriture, et le calcul des positions
passe par buildManualPositionOverrides (testé).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Dans la grille de la ligne, la pastille « Configurée » s'étirait sur toute la
largeur. Elle s'aligne désormais au début, comme les autres pastilles.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Un contributeur qui modifiait une fiche avec un portrait venu d'Immich voyait
le menu de source, alors que l'association et le retrait sont réservés aux
administrateurs côté serveur. La connexion Immich n'est aussi interrogée que
pour un administrateur.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Vignette lue en flux : au-delà de 5 Mo (ou sans taille annoncée), la lecture
s'arrête sans charger le reste.
- Vignette servie seulement si les octets sont une image, avec
X-Content-Type-Options: nosniff (plus de repli sur image/jpeg).
- Liste des personnes : délai global de 30 s sur toutes les pages.
- Corps JSON illisible ou flux interrompu : 503 immich_unreachable, au lieu
d'une erreur serveur.
- Retirer le portrait à la main (DELETE avatar) ou supprimer une personne
efface aussi son identifiant Immich, sinon l'association reste invisible.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Le résumé ne lisait que l'adresse : après un changement de JWT_SECRET, l'onglet
affichait « Configurée » alors que chaque appel échouait. Le résumé déchiffre
désormais la clé (configured: false, keyNeedsReentry: true, instance gardée pour
préremplir le menu). L'onglet Immich affiche « Clé à ressaisir », garde le
bouton de suppression, et un PUT sans clé est refusé tant que la clé en place
ne se lit pas. Tests : résumé avec clé illisible, refus de conserver la clé.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Route admin POST /trees/:id/layout/reset : remet layoutX/layoutY à null
pour toutes les personnes de l'arbre, dans une transaction, avec une
entrée de journal (tree_layout_reset). Documentée dans l'OpenAPI.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
L'API ne renvoie plus les adresses brutes d'Immich à un administrateur
d'arbre : la liste IMMICH_ALLOWED_API_URLS devient des instances nommées
(« Libellé=https://adresse », libellé facultatif, nom d'hôte par défaut).
Chaque instance a un identifiant opaque dérivé de son adresse, donc stable
même si l'opérateur réordonne la liste. Le PUT accepte cet identifiant et
stocke l'adresse canonique de la liste. Deux instances au même nom d'hôte
sans libellé sont refusées au démarrage avec un avertissement.
Les variables d'exemple documentent le format, la liste reste compatible
avec les adresses seules.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Les coordonnées de disposition sont bornées à ±1 000 000 (PATCH personne,
modification en session de contribution, OpenAPI).
- Un déplacement par le propriétaire ne met plus en conflit les
contributions en attente sur la même personne : layoutX/layoutY sont
exclus de la comparaison de conflit.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Le test « aucune instance autorisée » héritait de IMMICH_ALLOWED_API_URLS du
backend/.env de développement et échouait sur une machine qui le renseigne.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
new URL(chemin, base) effaçait le sous-chemin d'une instance derrière un
proxy (https://hote/immich devenait https://hote/api/...). L'adresse se
concatène désormais. Une adresse avec identifiants, requête ou fragment est
refusée, et le client reçoit l'adresse autorisée normalisée, jamais la
valeur brute de la base. Tests : sous-chemin, domaine préfixe, userinfo.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
fetch suivait les redirections par défaut : une instance autorisée qui
répond 3xx pouvait recevoir x-api-key sur une autre adresse. Les appels
passent en redirect: 'error' (remontée en immich_unreachable), avec un test
qui le prouve.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
POST /trees/:id/persons/:personId/avatar lit immichPersonId de la personne et le
remet à null quand une association existe ; l'entrée d'audit person_avatar_updated
trace le retrait via payloadJson immichLinkRemoved. Sans cela, l'association
survivait invisible en base après remplacement du portrait par un fichier.
Test : l'envoi d'un fichier sur un portrait Immich efface l'association (échouait
avant ce fix : immichPersonId absent de l'update) ; sans association, le champ
n'est pas touché.
Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
- PersonCard : le lien « Depuis Immich » empilé sous « Changer »/« Retirer » disparaît ;
« Changer »/« Ajouter une photo » ouvre un menu contextuel (portail, pz-menu) proposant
« Depuis un fichier » ou « Depuis Immich », seulement quand l'arbre a une connexion
Immich ou que la personne garde un portrait venu d'Immich (sinon sélecteur direct).
La connexion se lit via GET /trees/:id/immich/config à l'ouverture en édition.
- « Retirer » transmet l'origine du portrait ; App route vers unassignImmichPerson quand
il vient d'Immich (portrait + association retirés ensemble) au lieu de la suppression
seule qui laissait une association pendante. Erreurs du chemin Immich mappées
par getImmichErrorMessage.
- i18n en : « Depuis un fichier », « Source du portrait ».
Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
- avatarFromImmich (fichier préfixé « immich- ») remplace immichPersonId
côté front : le panneau ne propose le retrait que si le portrait vient
d'Immich, et le bouton de la fiche retrouve son libellé distinctif
- helper isImmichAvatarPath partagé entre le payload du graphe et la route
unassign, qui testaient le préfixe chacun de son côté
- tests : origine du portrait dans le payload (vrai/faux/null)
Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
- buildGraphPayload ne mappe plus immichPersonId ; test qu'il n'apparaît pas
- le front ne propage plus le champ (graphAdapter, mockData et son test)
- le panneau Immich propose le retrait d'association en permanence, sans lire
le champ ; unassign sans association ne fait rien : pas d'écriture, pas
d'entrée d'audit (test du no-op), et le bouton de la fiche garde un
libellé fixe
Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
L'association remplaçait et supprimait le portrait en place sans rien
demander. Le serveur refuse maintenant le remplacement d'un portrait
existant (409 avatar_exists), quelle que soit son origine, tant que la
requête ne porte pas replaceAvatar ; le panneau Immich transforme ce
refus en demande de confirmation avant de rejouer la requête. Sans
vignette Immich, l'association ne touche pas au portrait et aucune
confirmation n'est demandée.
Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
- Nouvelle table tree_immich_configs (adresse + clé d'API chiffrée AES-256-GCM,
clé dérivée de JWT_SECRET) et routes GET/PUT/DELETE /trees/:id/immich/config
réservées aux administrateurs de l'arbre ; journal d'audit sans secret
(adresse et « clé changée » uniquement).
- Fin des variables d'env IMMICH_ENABLED / IMMICH_API_URL / IMMICH_API_KEY :
l'adresse se choisit parmi IMMICH_ALLOWED_API_URLS, la liste établie par
l'opérateur du serveur. Elle filtre l'enregistrement ET chaque requête :
retirer une adresse coupe aussitôt les connexions qui l'utilisent.
- Changer d'instance exige une nouvelle clé : l'ancienne est écrasée et ne
voyage jamais vers le nouveau serveur.
- Le désassignement reste possible sans connexion : purement local, il nettoie
association et portrait stocké même après suppression de la connexion ; le
panneau affiche « Retirer l'association » dans tous les états.
- Onglet Immich dans Paramètres de l'arbre : menu des instances autorisées,
test de connexion, suppression avec confirmation ; i18n anglais des nouveaux
messages.
Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
Le 404 de la vignette ne distingue pas une personne inexistante d'une
vignette manquante : l'association pouvait donc être enregistrée pour
une personne Immich qui n'existe pas.
- ImmichClient.getPerson() : GET /api/people/{id} (200 | 404), qui
établit l'existence de la personne et annonce sa vignette.
- La route d'association rejette une personne inexistante (404
immich_person_not_found) ; un refus d'authentification (502) ou une
erreur serveur (503) n'est jamais pris pour une absence.
- La vignette n'est demandée que si Immich en annonce une ; une vignette
manquante chez une personne existante associe toujours sans portrait.
- Tests : personne inexistante refusée sans toucher base, audit ni
fichiers ; vignette disparue entre les deux appels ; échec
d'authentification pendant l'association sans changement en base.
Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
Le .env.example racine (docker compose, env_file de l'API) ne
mentionnait pas IMMICH_ENABLED, IMMICH_API_URL et IMMICH_API_KEY ;
seul le .env.example du backend les documentait.
Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
Reprise de upstream (IliesAllali/main) dans la branche Immich. Seul
conflit : l'import des icônes dans PersonCard.jsx — les deux lignes
sont gardées (ImagePlus pour le portrait vide de « Premières photos »,
Sparkles pour le bouton « Depuis Immich »). Tout le reste fusionne
automatiquement ; l'intégration Immich (routes, exports de media.ts,
mapping immichPersonId, panneau) est inchangée.
Vérifié après la fusion : tests Immich (16), tests backend focalisés
gedcom-photos (4), build tsc, eslint et les 107 tests du front. Seul
« rate-limits login endpoint » dépasse son délai de 5 s, déjà le cas
sur main avant la fusion (bcrypt, machine lente).
Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
- Colonne persons.immichPersonId (schéma + migration 20261012_001) et
exposition dans le graphe pour l'afficher côté fiche.
- Client Immich (src/lib/immich.ts) sur le contrat OpenAPI v3.1.0 :
ping, /api/people paginé, vignette par personne ; erreurs distinctes
entre injoignable, clé refusée et introuvable, identifiants validés
comme UUID v4.
- Routes /trees/:id/immich/* (status, people, thumbnail, assign,
unassign), réservées aux administrateurs de l'arbre ; l'adresse et la
clé d'Immich ne quittent jamais le backend.
- La vignette passe par le pipeline des portraits (type lu dans les
octets, métadonnées retirées, quota de l'arbre) : le fichier est écrit
avant la transaction (personne + journal immich_assign) et l'ancien
portrait est supprimé après ; un échec du nettoyage est journalisé
sans faire échouer la requête. Le désassignement retire le portrait
seulement s'il vient d'Immich (préfixe immich-) et préserve un
portrait envoyé à la main.
- Côté front : bouton « Depuis Immich » sur la fiche en édition, panneau
de sélection avec aperçu et retrait de l'association, traductions
anglaises, messages d'erreur distincts.
- Tests : 16 tests backend (succès, 400/401/403/404/413/502/503, quota,
échec de transaction, préservation du portrait manuel, serveur
désactivé) et le mapping immichPersonId côté front.
Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
Les migrations Prisma passent avant que l'API réponde. Avec 30 s, le conteneur
web, qui attend une API saine, abandonnait au premier docker compose up
(signalé sur r/GenealogieFR le 11/10).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Création d'arbre : « J'ai déjà un arbre » importe le .ged sans fiche « vous » en double.
- Import GEDCOM : lit les photos citées (OBJE / FILE, en ligne ou par renvoi, _PRIM d'abord)
et renvoie leur chemin ; rien n'est gardé côté serveur.
- Fenêtre « Il manque les visages » après un import : dossier des photos apparié par nom de
fichier, cinq visages à demander (famille de « Moi », sinon les plus jeunes).
- Portrait vide chez le propriétaire : cadre cliquable sur la fiche hors édition, « + photo »
au survol sur l'arbre.
- Photos de plus de 5 Mo réduites à 2000 px avant l'envoi ; portraits à 120 envois par minute.
- PostHog : gedcom_imported, portraits_bulk_placed, portrait_added (effectifs arrondis).
- Corrigé : une union à un seul parent connu relie ses enfants à ce parent, sous un seul
« Inconnu », au lieu de les laisser sans lien.
- Corrigé : réimporter un GEDCOM reprend les personnes, unions et liens déjà présents au lieu
de tout doubler.
Recette locale (vraie API, vraie base) : 29/29 sur le parcours photos, 47/47 sur la recette
de non-régression des parcours. Tests 123 serveur, 107 app, lint et build propres.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>