fix(import): Stammbaum-/Eltern-/isResident-Tickets der Züchterin (18)

Import-Logik:
- Externe Gründer (Zooladen/„von Privat"/„from …, <Land>") bekommen keine
  erfundenen Chart-Eltern mehr → Bill, Cooky, Zadar haben korrekt unbekannte
  Eltern (is_external_origin). [#5,#13,#28]
- Hagrid: externe Zucht wird nicht mehr als Bestand markiert (isResident=false)
  + Leerhüllen-Dedup → ein Datensatz mit Eltern Snickers × Milka. [#17,#18,#20a]
- Gender-Index: eindeutiges Geschlecht schlägt unbekanntes Duplikat → Rollen-
  Auflösung repariert (Vance→Mutter Enya, Zac→Vater Vance/Mutter Dorie). [#33,#35]
- „Eltern: X + Y"-Wurfnotizen werden geparst (~46 Würfe); v.d.↔von-den-Namens-
  kanon (Theodore→BlackFire). [#9,#11,#30]

Daten-Overrides (conflict-decisions.json, jetzt auch Gender + exakte Eltern):
- Mozart→weiblich [#2], Yuki=Camaro×Izumi [#23], Gold-Mutter=Chelsea [#36],
  Arya=Vance×Sansa (Geschwisterverpaarung) [#16], Tony→Sammy [#12],
  Odelia [#15], Jamie→Danny [#31], Silver=Taro×Beatrice [#11].

Frontend: GerbilDetailPage blendet für isResident=false Würfe + Charakter aus
(Hinweis nonResidentNote). [#17,#20b]

Tests: test_extract/test_merge_resolve erweitert; vitest 135, playwright tiere 34 grün.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-22 21:16:53 +02:00
parent 9c6ca2118a
commit ce1704c3b7
9 changed files with 465 additions and 43 deletions

View File

@@ -137,6 +137,24 @@ test('Detailseite: Herkunft zeigt Kontaktlink wenn originContactId gesetzt (WURF
await expect(originLink).toHaveAttribute('href', '/kontakte/con-meier')
})
test('Detailseite: externer Vorfahre (isResident=false) blendet Würfe & Charakter aus (#17/#20)', async ({ page }) => {
skipUnlessMock()
// 'hilde' ist isResident=false (externer Ahne, nur für den Stammbaum erfasst).
await page.goto('/rennmaeuse/hilde')
await expect(page.getByRole('heading', { name: 'Hilde' })).toBeVisible()
// Extern-Badge + Hinweis sichtbar, Charakter- und Würfe-Abschnitt ausgeblendet.
await expect(page.getByText(t.detail.nonResidentNote)).toBeVisible()
await expect(page.getByText(de.character.sectionTitle)).toBeHidden()
await expect(page.getByRole('heading', { name: t.detail.parentLittersTitle })).toBeHidden()
// Gegenprobe: Bestandstier Krümel zeigt beide Abschnitte.
await page.goto('/rennmaeuse/kruemel')
await expect(page.getByRole('heading', { name: 'Krümel' })).toBeVisible()
await expect(page.getByText(de.character.sectionTitle)).toBeVisible()
await expect(page.getByRole('heading', { name: t.detail.parentLittersTitle })).toBeVisible()
await expect(page.getByText(t.detail.nonResidentNote)).toBeHidden()
})
test('Namenlose Tiere zeigen Platzhalter in der Liste (UI-POLISH-1)', async ({ page }) => {
skipUnlessMock()
await page.goto('/rennmaeuse')

View File

@@ -357,6 +357,15 @@ export default function GerbilDetailPage() {
)}
</section>
{/* Externe Vorfahren waren nie im eigenen Bestand → Würfe & Charakter
sind für sie nicht relevant; statt der Abschnitte ein kurzer Hinweis. */}
{g.isResident === false && (
<section className="ak-card">
<p className="ak-empty">{t.detail.nonResidentNote}</p>
</section>
)}
{g.isResident !== false && (
<section className="ak-card">
<button
type="button"
@@ -427,7 +436,9 @@ export default function GerbilDetailPage() {
</>
)}
</section>
)}
{g.isResident !== false && (
<section className="ak-card">
<h2 className="ak-h2">{t.detail.parentLittersTitle}</h2>
{parentLitters.loading && <p className="muted">{de.common.loading}</p>}
@@ -457,6 +468,7 @@ export default function GerbilDetailPage() {
</ul>
)}
</section>
)}
<section className="ak-card">
<h2 className="visually-hidden">{t.detail.moreData}</h2>

View File

@@ -126,6 +126,10 @@ export const de = {
siblingPairing: 'Geschwisterverpaarung',
siblingPairingTitle:
'Die Eltern dieses Tiers stammen aus demselben Wurf (Vollgeschwister).',
// Externe Tiere (nur als Vorfahre erfasst): Würfe & Charakter werden
// ausgeblendet, da das Tier nie im eigenen Bestand war.
nonResidentNote:
'Externes Tier — nur als Vorfahre erfasst. Würfe und Charakter werden nicht angezeigt, da es nie im eigenen Bestand war.',
},
// Formular (anlegen/bearbeiten)
form: {