- Champ de date de la fiche et du formulaire « Ajouter quelqu'un » : une annee seule
(1950) ou une date complete (12/03/1950), une aide si le texte n'en est pas une.
L'annee seule s'affiche « 1950 » et non plus « 1 janvier 1950 ».
- Statut « Decede(e) » sans date : persons.isDeceased (migration additive), « 1908–? »
sous le portrait, « Date inconnue » dans la fiche, en bas de la frise. Import de
« 1 DEAT Y », export en « 1 DEAT Y ». Une date de deces retire le drapeau.
- Parents : plus de « (null) » quand l'union n'a pas de type.
Closes#77
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Le zip est lu dans le navigateur, sans bibliotheque (table des matieres puis
DecompressionStream, fichier par fichier) : le .ged le plus haut s'importe, GEDZIP
compris, et les photos et actes passent directement a l'etape des visages, rapproches
comme un dossier et decompresses seulement a l'envoi. Messages clairs pour un zip sans
.ged, protege par mot de passe ou trop gros. Corrige aussi « citait 1 autres fichiers ».
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Un moment peut porter plusieurs souvenirs de tout type (photo, video, enregistrement,
acte en PDF) : media_items.lifeEventId, reprise de l'ancienne colonne mediaId.
- Naissance, mariage et deces se completent depuis la frise : documents, source, note
(la date et le lieu restent dans Infos).
- La famille propose un moment par le lien de partage : il attend la relecture, avec
ses fichiers dans la meme proposition, et apparait une fois accepte.
- Fiche : Ajouter un fichier, Choisir parmi ses souvenirs, prenom du contributeur,
textes qui expliquent ce qui se passe.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Un bouton « Ajouter un moment » pour les administrateurs, une fiche courte (quoi, quand,
environ, ou, une photo), date exacte, fin, source et note sous « Plus de details ».
La photo devient un souvenir de la personne, rattache au moment (life_events.mediaId,
migration additive). Toucher un moment l'ouvre pour le modifier ou le supprimer.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Naissance, unions et deces en grands reperes (direction A2 choisie par Patch le 16/10),
evenements importes plus petits, famille proche tiree de l'arbre en retrait, sources
repliees. Evenements charges a l'ouverture de l'onglet via GET /persons/:id/life,
donnees propres a la demo pour Robert.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Les arbres importés de Heredis avant le nettoyage à l'import gardent des
lieux bruts (« Ville,code INSEE,département,… »). La fiche les affiche avec
la même règle qu'à l'import, sans toucher à la valeur enregistrée ni au
champ d'édition.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>
‹ 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>
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>
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>
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>
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>
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>
- 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>
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>
- 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>
fast-jwt 6.3.4 (cache du vérificateur, jetons expirés sans iat), source-map-js 1.2.2 et
postcss-selector-parser 7.1.6 forcé sous Tailwind 3 (CSS produit identique octet pour octet).
Landing en Vite 8 et plugin-react 6, mêmes navigateurs cibles que le front, rendu comparé à
l'écran au build Vite 7 en 1920 et 390 px. Groupes mineurs front (#50) et landing (#38).
Reprend #38, #41, #46, #49, #50, #51. CHANGELOG 1.0.2.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
crypto.randomUUID n'est exposé par le navigateur qu'en contexte sécurisé
(HTTPS ou localhost). Servie en HTTP simple, l'app plantait dans
generateSecurePassword à l'étape de création de l'arbre.
Repli sur crypto.getRandomValues, disponible partout, qui produit le
même UUID v4 sans tirets.
Signed-off-by: Fabrice Coutadeur <coutadeurf@gmail.com>
- demo_opened (une fois par chargement, visiteur sans compte) et demo_interaction (personne, média, recherche, édition, création d'arbre ; une fois par type, sans contenu)
- Source d'arrivée gardée sur l'appareil (?source=hn) et jointe à account_screen_viewed et account_registered ; la landing transmet la vraie source au CTA et à l'iframe de démo au lieu de source=landing
- Plus de remise à zéro PostHog à chaque ouverture anonyme (effaçait le visiteur de la landing), seulement à la déconnexion
- Landing : un seul événement signup_cta_clicked (hero_cta_clicked retiré, plus utilisé par le funnel), envoyé avant la navigation sans sendBeacon (bloqué par Brave)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- text-wrap-style pretty sur les paragraphes, listes, légendes et chapôs, balance sur les titres
(landing, guides statiques, base pz.css de l'app). Longhand pour ne pas réactiver le retour
à la ligne là où white-space: nowrap est posé. Les noms de l'arbre sont sur le canvas, non touchés.
- Espaces insécables du français dans le texte visible : fine insécable avant ? ! ;,
insécable avant : et dans les guillemets, entre un nombre et son unité (Mo, Go, €, dpi).
Landing (i18n.js, bloc fr hors meta) et pages guides FR (corps de page, hors script).
- App : frenchTypography() appliquée par t() en français, avant le remplacement des variables ;
le dictionnaire anglais reste indexé sur le français tel qu'écrit. Tests mis à jour.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Le souvenir « Mariage Simone 1943 » ouvrait Big Buck Bunny sur YouTube.
Remplacé par 25 s des actualités Polygoon « Bruiloft te Giethoorn » (1947,
Beeld en Geluid via Open Beelden, CC BY-SA 3.0 NL), sans le son, en H.264
pour Safari, servi par le site (media-src 'self'). Crédit dans CREDITS.md
et sources.json. Les trois autres vidéos YouTube de remplacement de la démo
(Témoignage filmé 1960, Anniversaire 2020, Interview archive radio) sont
retirées.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Vidéo piégée : l'expression régulière de position était quadratique, un faux MP4 de 2 Mo figeait l'API (bornée, linéaire)
- Sessions émises avant le 01/10/2026 refusées (SESSIONS_NOT_BEFORE) : des sessions ont fuité dans les annotations et les journaux jusqu'à la 1.0
- Contributions : l'« avant » montré au propriétaire vient de la base ; une suppression approuvée part en cascade (unions, liens, souvenirs, portrait, fichiers)
- Mot de passe de partage : essai compté avant bcrypt, comparaisons une par une, changement et rattachement dans une transaction
- Quota mesuré sur le disque, 10 arbres par compte, corps à 1 Mo hors envois, bornes sur annotations, contributions et GEDCOM
- GIF réencodés ; anciennes propositions d'annotation sans adresse stockée ; clé d'exemple refusée sans NODE_ENV
- Déploiement du commit validé par la CI, clé d'hôte obligatoire ; nginx : X-Forwarded-For écrasé, version masquée, journal sans query string
- PostHog verrouillé dans le code, sans script externe ; CSP sans *.posthog.com ; sauvegardes hors serveur chiffrées par défaut
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Vite 8 relève sa cible par défaut à Safari 16.4 et Chrome 111. La famille ouvre souvent l'arbre sur un vieil iPad ou un ancien téléphone : la cible reste celle de Vite 7 (Chrome et Edge 107, Firefox 104, Safari 16.0), valeurs lues dans le guide de migration de Vite 8.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Vitest 5 demande Vite 6.4 ou plus et Node 22.12 ou plus (Vite 8.3 et Node 24 ici). Aucun test n'utilise vi.mock, bench, describe.sequential, toThrow('') ni d'assertion asynchrone non attendue, les points touchés par le guide de migration ; clearMocks activé par défaut est sans effet sans mock. 97 tests passent, comme avant.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
La PR montait @eslint/js seul, dont la config recommandée ajoute no-useless-assignment, no-unassigned-vars et preserve-caught-error. eslint passe aussi en 10 (configuration à plat déjà en place, aucun commentaire eslint-env, Node 24) ; eslint-plugin-react-hooks 7.1 et eslint-plugin-react-refresh 0.5 la déclarent prise en charge. Les trois signalements de no-useless-assignment étaient fondés : une valeur initiale jamais lue, chaque branche suivante l'écrase ou quitte la fonction (libellé de parenté, contenu JSON d'une photo ou d'un tracé posé sur l'arbre). Valeur initiale retirée, comportement inchangé.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>