4 Commits
Author SHA1 Message Date
IliesAllaliandClaude Opus 5.5 0269b97efd 1.5.9 : changelog
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 15:12:39 +01:00
IliesAllaliandClaude Opus 5.5 cd6dfc6955 Enfant ajouté à une personne qui n'a qu'un conjoint : enfant du couple (#200)
Avant, « + Enfant » sur la fiche d'Isabelle (un seul conjoint, Robert) rattachait l'enfant à Isabelle seule :
il pendait sous elle et un parent « Inconnu », loin de ses frères et sœurs. Il passe maintenant par les liens
du couple (getCoupleParentLinks), comme un frère ou une sœur, pour la démo, la famille qui propose et le
propriétaire. Sans conjoint, avec plusieurs conjoints, ou en adoption : l'enfant reste au seul parent choisi.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 14:44:23 +01:00
IliesAllaliandClaude Opus 5.5 f595af581a Téléphone : plus de réglette de zoom, la loupe seule reste (on zoome à deux doigts) (#199)
La notification n'a plus à se décaler pour l'éviter.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 14:35:38 +01:00
IliesAllaliandClaude Opus 5.5 b803f6291d Fiche : « – 1995 » ne se coupe plus après le tiret ; bouton loupe nommé « Zoomer »
Une espace insécable seule ne suffit pas : Chrome coupe après un tiret demi-cadratin. Le gluon U+2060
devant l'espace insécable garde « – 1995 » d'un bloc (vu sur la fiche de Robert, démo, 1440 px).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 13:43:45 +01:00
9 changed files with 76 additions and 26 deletions

No files matched your search

+14
View File
@@ -2,6 +2,20 @@
Notable changes to Parenthèse. Dates are in YYYY-MM-DD format.
## 1.5.9 (2026-10-29)
### Tree
- A child added to someone who has a single partner becomes the couple's child, next to their brothers and sisters, instead of hanging under one parent and an "Unknown" one. With no partner, several partners, or an adoption, the child stays with the parent chosen (#200).
### Phone
- No more zoom slider: it opened by itself and covered a corner of the tree. Pinch to zoom, the magnifier zooms in one step. On a computer the slider still shows on hover (#199).
### Profile
- "– 1995" no longer breaks after the dash at the end of a line. The magnifier button is labelled "Zoom in" for screen readers.
## 1.5.8 (2026-10-29)
### Family tabs
+16 -3
View File
@@ -73,7 +73,7 @@ import { readGedcomBundle, zipErrorMessage } from './utils/gedcomBundle.js'
import { materializeFile } from './utils/zipReader.js'
import { loadMediaRefs, loadPhotoRefs, pickFirstFaces, prepareImageForUpload, saveMediaRefs, savePhotoRefs, sizeBucket } from './utils/photoImport'
import { fileToBase64, getAccountErrorMessage } from './utils/errorMessages'
import { getSiblingParentLinks } from './utils/familyLinks'
import { getCoupleParentLinks, getSiblingParentLinks } from './utils/familyLinks'
import { getUploadMimeType, inferMediaTypeFromMimeType, validateMediaFile } from './utils/mediaUpload'
import {
getSource,
@@ -1225,6 +1225,19 @@ function App() {
siblingParentLinks.push(...links)
}
}
// Un enfant ajouté à une personne qui n'a qu'un conjoint est l'enfant du couple : il passe par les liens
// du couple, comme un frère ou une sœur. Une adoption reste au seul parent choisi.
const relationCount = (formData.relations || []).length
formData = {
...formData,
relations: (formData.relations || []).filter((rel) => {
if (rel.type !== 'child' || rel.adopted) return true
const coupleLinks = getCoupleParentLinks(rel.personId, unions)
if (!coupleLinks.length) return true
siblingParentLinks.push(...coupleLinks)
return false
}),
}
if (!tree.canEditCurrentTree) {
if (isDemoMode) {
@@ -1331,7 +1344,7 @@ function App() {
},
]
editMode.updateDraft({ addedPersons })
trackAppEvent('person_added', { via: addPersonViaRef.current, role: 'family', relations: (formData.relations || []).length, tree_id: opaqueTreeId })
trackAppEvent('person_added', { via: addPersonViaRef.current, role: 'family', relations: relationCount, tree_id: opaqueTreeId })
// La personne apparaît en fantôme « Brouillon » : la famille peut en ajouter d'autres, puis envoyer une
// fois avec le bouton « Envoyer » de la barre (Patch, 25/10). Avant, la fenêtre d'envoi s'ouvrait d'office,
// seule façon de montrer qu'un ajout invisible comptait.
@@ -1409,7 +1422,7 @@ function App() {
})
await tree.refreshCurrentTreeGraph()
trackAppEvent('person_added', { via: addPersonViaRef.current, role: 'manager', relations: (formData.relations || []).length, tree_id: opaqueTreeId })
trackAppEvent('person_added', { via: addPersonViaRef.current, role: 'manager', relations: relationCount, tree_id: opaqueTreeId })
return newPerson
} catch (err) {
setAddPersonError(err.message || t("Erreur lors de l'ajout de la personne"))
+3 -1
View File
@@ -631,7 +631,9 @@ const PersonCard = ({
// <bdi> : un lieu en arabe ou en hébreu ne retourne pas l'ordre de la ligne (« 1980, الدار البيضاء · 46 ans »)
if (born) parts.push(person.birthPlace ? <>{born}, <bdi>{formatPlace(person.birthPlace)}</bdi></> : born)
if (!person.isAlive) parts.push(died || '?')
return parts.length ? parts.reduce((line, part) => <>{line} – {part}</>) : null
// « – 1995 » passe entier à la ligne : Chrome coupe après un tiret même suivi d'une espace insécable,
// le gluon U+2060 l'en empêche
return parts.length ? parts.reduce((line, part) => <>{line} –{'\u2060\u00a0'}{part}</>) : null
})()
const ageLabel = age === null ? null : person.isAlive ? t('{age} ans', { age }) : t('{age} ans au décès', { age })
// Une seule ligne sous le nom : surnom, repères de vie, âge, séparés par un point médian
@@ -293,11 +293,6 @@
.undo-toast-btn:hover { background: color-mix(in srgb, var(--pz-bg) 26%, transparent); }
.undo-toast-btn:focus-visible { outline: 2px solid var(--pz-bg); outline-offset: 2px; }
@media (max-width: 768px) { .contrib-success-toast { bottom: 78px; padding: 11px 16px; border-radius: 16px; font-size: 13px; } }
/* Sous 768 px, la réglette de zoom reste ouverte en bas à gauche (ZoomControl.css, de 8 à 60 px du bord) :
tant qu'elle se voit (ni panneau ni fiche par-dessus), la notification commence après elle au lieu de passer dessus */
@media (max-width: 768px) {
body:not(:has(.pz-overlay, .person-card)) .contrib-success-toast { left: 72px; right: 12px; width: auto; max-width: none; transform: none; }
}
@media (max-width: 560px) {
.contrib-invite-link { flex-wrap: wrap; border-radius: var(--pz-r-m); padding: 10px; }
-12
View File
@@ -185,16 +185,4 @@
width: 44px;
height: 44px;
}
/* Slider toujours visible sur mobile */
.zoom-slider-panel.visible {
max-height: 240px;
opacity: 1;
transform: translateX(-50%) translateY(0) scale(1);
pointer-events: auto;
}
.zoom-slider-track {
cursor: pointer;
}
}
+4 -3
View File
@@ -104,7 +104,8 @@ export default function ZoomControl({ zoomApiRef }) {
document.addEventListener('pointerup', onUp)
}, [zoomApiRef])
const panelVisible = isHovered || isMobile
// Sur téléphone, on zoome à deux doigts : la réglette n'apparaît jamais, la loupe seule reste
const panelVisible = isHovered && !isMobile
return (
<div
@@ -112,7 +113,7 @@ export default function ZoomControl({ zoomApiRef }) {
onMouseEnter={showSlider}
onMouseLeave={hideSlider}
>
{/* Panel slider — hover sur desktop, toujours visible sur mobile */}
{/* Panel slider — au survol sur ordinateur, jamais sur téléphone */}
<div
className={`zoom-slider-panel ${panelVisible ? 'visible' : ''}`}
onMouseEnter={showSlider}
@@ -139,7 +140,7 @@ export default function ZoomControl({ zoomApiRef }) {
<button
className="zoom-button"
onClick={stepZoom}
aria-label={t('Contrôle du zoom')}
aria-label={t('Zoomer')}
>
<ZoomIn size={20} strokeWidth={1.5} className="zoom-btn-icon" />
</button>
+1 -1
View File
@@ -23,7 +23,7 @@ export default {
'Lien': 'Link',
// Contrôles
'Contrôle du zoom': 'Zoom control',
'Zoomer': 'Zoom in',
'En cours': 'Loading',
// Annotations
+17
View File
@@ -36,3 +36,20 @@ export function getSiblingParentLinks(personId, filiations, unions) {
return links
}
/**
* Parents à rattacher à un nouvel enfant de `parentId` : quand cette personne n'a qu'une union,
* l'enfant est celui du couple (les deux partenaires, avec l'identifiant de l'union).
* Aucune union, ou plusieurs (on ne devine pas l'autre parent) : liste vide, l'enfant reste au seul parent.
*
* @param {string} parentId - Personne à qui l'on ajoute un enfant
* @param {Array} unions - Unions du graphe courant ({ id, partner1Id, partner2Id })
* @returns {Array<{ parentId: string, unionId: string }>}
*/
export function getCoupleParentLinks(parentId, unions) {
const key = String(parentId)
const own = (unions || []).filter((union) => String(union.partner1Id) === key || String(union.partner2Id) === key)
if (own.length !== 1 || !own[0].partner1Id || !own[0].partner2Id) return []
const unionId = String(own[0].id)
return [{ parentId: String(own[0].partner1Id), unionId }, { parentId: String(own[0].partner2Id), unionId }]
}
+21 -1
View File
@@ -1,5 +1,5 @@
import { describe, expect, it } from 'vitest'
import { getSiblingParentLinks } from './familyLinks'
import { getCoupleParentLinks, getSiblingParentLinks } from './familyLinks'
describe('getSiblingParentLinks', () => {
const unions = [{ id: 'u1', partner1Id: 'p1', partner2Id: 'p2' }]
@@ -31,3 +31,23 @@ describe('getSiblingParentLinks', () => {
expect(getSiblingParentLinks('c1', undefined, undefined)).toEqual([])
})
})
describe('getCoupleParentLinks', () => {
it('gives both partners when the parent has a single union', () => {
const unions = [{ id: 'u1', partner1Id: 'p1', partner2Id: 'p2' }]
expect(getCoupleParentLinks('p2', unions)).toEqual([
{ parentId: 'p1', unionId: 'u1' },
{ parentId: 'p2', unionId: 'u1' },
])
})
it('keeps the child with the parent alone when there is no union or several', () => {
expect(getCoupleParentLinks('p1', [])).toEqual([])
expect(getCoupleParentLinks('p1', undefined)).toEqual([])
const unions = [
{ id: 'u1', partner1Id: 'p1', partner2Id: 'p2' },
{ id: 'u2', partner1Id: 'p3', partner2Id: 'p1' },
]
expect(getCoupleParentLinks('p1', unions)).toEqual([])
})
})