Genetik-Engine (bde4ec70, f89e95ad, 2322c2a8):
- Saphir/Platin/Platin-Hell unterscheiden sich NUR in der C-Zygotie und waren im
Katalog identisch (locusToken reduziert auf das dominante Allel) → Saphir war
unerreichbar. Token darf jetzt ein exaktes, ungeordnetes Allelpaar "x/y"
verlangen: Platin C/C, Saphir C/cchm, Platin-Hell C/ch. TS- und Python-Mirror
identisch, Seeds regeneriert, Migration ReseedColorVarietiesGen6Saphir.
- c[hm] wird als Alias auf c[chm] normalisiert (Tippfehler in zwei Charts) —
vorher war Jays Gencode unparsebar und ergab "Zobel-Hell".
- 6 Tiere wechseln den errechneten Farbschlag (5x Platin→Saphir, Jay→Zobel).
Importer-Logik:
- renameTo-Notiz-Sweep: der alte Name blieb in Wurf-Notizen stehen ("Blacky +
Kruke") — jetzt wortgenau ersetzt, mit vier Guards gegen Kollateralschaden.
- Neuer Resolution-Schlüssel goHomeDate (autoritativ, zieht GivenAway nach) und
spottingType; Gencode-Overrides gewinnen jetzt bei präzisem Match (vorher
wurden Entscheidungen stumm verschluckt, z. B. Eliza und Chris).
- parse_date verwirft implausible Jahre (JackJack hatte 1310-05-13).
- extract.py erkennt Stammbaum-Blöcke auch ohne Stern vor dem Geburtsdatum
(Bijou bekam dadurch den Nachbar-Ast als Mutter).
- Backend-Ingest überträgt SpottingType (nur bei Payload-Wert).
Daten (conflict-decisions.json, re-ingest-stabil): Kuke-Merge + Zuchtname,
Jamie- und Sakura-Dubletten, Phantom-Tier "Unbekannt", Eltern von Kathlin,
Fast Boy/Ziwa und dem Q4-/TS-Wurf, Eliza-Gencode, Merle-Abgabe, JackJack.
Frontend (24522f5f): Abgabedatum steht in der Tier-Akte jetzt direkt unter dem
Abnehmer und hängt nicht mehr am Status.
Tests: +25 Checks test_merge_resolve, +7 test_genotype, +8 genetics.test.ts,
neuer Extract- und Ingest-Test, e2e GOHOME-ROW. Alles grün.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Im Tier-Formular gibt es jetzt den Block „Abstammung" mit Wurf-Auswahl und
Vater-/Mutter-Picker — die Eltern müssen nicht mehr über die Wurf-Seite
gesucht und dort editiert werden. Das Datenmodell bleibt unverändert: Eltern
hängen weiterhin am Geburtswurf.
- Backend: PUT /gerbils/{id}/parents schreibt Litter.FatherId/MotherId des
Geburtswurfs. Ohne Wurf wird ein bestehender mit gleichem Elternpaar +
gleichem Datum verknüpft, sonst ein Träger-Wurf angelegt
("Wurf von X + Y", ShowInChronicle=false, IsManual=true).
Geschlechts-Regel wiederverwendet LitterEndpoints.ValidateParents,
Selbstbezug (Tier als eigener Elternteil) wird abgewiesen.
- UI: Vorbelegung aus dem gewählten Wurf, Warnung mit Anzahl der Geschwister
(Eltern gehören dem Wurf → Änderung gilt für alle), Hinweis wenn ein
Wurf-Eintrag angelegt wird. Texte in de.ts.
- Nebenbei: Speichern nutzt im Edit-Modus die Route-Id (PUT /gerbils/{id}
antwortet 204 ohne Body) und der vorher schon rote Spec-Locator
„Würfe als Elternteil" ist auf den Abschnitt eingegrenzt.
- Tests: GerbilParentsTests (9), e2e tiere.spec (2 neu) + Mock-Route.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Daten-Fixes (conflict-decisions.json, re-ingest-stabil) für ~30 Tickets:
Merges (Jamie/Hiro/Mino/Jana/Blacky/Sakura/Malou/Socke→Marty), Eltern-Korrekturen
(Jacky/Idefix/Ichika/Roni/Ethan), Kruke→Kuke (+ Todesdatum), Targa-Wurf R14 + Druna,
Stacy/Merle/Domi/Eliza; Joghurt-Phantomwurf entfernt.
Code-Fixes:
- Gaida & alle Verstorbenen: Status wird aus Todesdatum/Abgabe abgeleitet
(Program.cs Startup-Sweep heilt Altfälle; IngestResolved re-derived nach Freeze).
- CoCo: Scheckungsart wird bei jeder Schecke angezeigt (Platzhalter wenn leer).
- M-Wurf/Gale: über-gemergte Fremdtiere via neuem litterChildren-Override entfernt.
- renameTo eltern-verknüpfungssicher (Quell-Name im Index); dateOfDeath als Override.
Prod-fähige Triage (API):
- GET /feedback/{id} + GET /feedback?status= (kein 2-MB-Dump).
- POST /import/ingest-resolved/upload (multipart) → Ingest gegen Prod ohne SSH.
Tests: 280 Backend, 149 Frontend, alle Python, betroffene Playwright grün.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Bali, Aladin, Lexi, Pinto, Jasper, Nestor — aus der zweiten Ticket-Runde.
rpro3-decisions.json: 14 → 20 Einträge.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Manuelle "gleich/verschieden?"-Entscheidungen aus den rpro3-import-Tickets
fließen jetzt in den RennmausPro-III-Import ein:
- Rpro3Decisions (Modell + JSON-Loader): same (Force-Merge), different
(Force-Split), fields (Farbe/DOB/Herkunft/resident/Notiz). Schlüssel = rid.
- Rpro3Dedup.Run(animals, decisions): honoriert die Overrides nach dem
automatischen Dedup (eine rid zieht ihren Cluster mit).
- Rpro3ImportService: lädt Import/Rpro3/rpro3-decisions.json, wendet
Feld-Overrides im Plan-Builder an.
- rpro3-decisions.json: 14 bestätigte Entscheidungen (Eiji, Momo, Samuel,
Female, Kennedy, Fegur, Tuli, Bura, Mister X, Zoey, Max, Akiro, Merlin,
Snickers).
- tools/import/rpro3_lookup.py: Triage-Helfer (Farbe/Gencode/Eltern/
Nachzucht+Partner aus _rpro3.db) für die Rückfragen an die Züchterin.
- 4 neue Tests (Force-Merge/Force-Split/Loader); Suite 268 grün.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Die Züchterin kann auf der Tickets-Seite Push-Benachrichtigungen aktivieren
(🔔-Schalter). Service Worker ist BEWUSST push-only (kein fetch/Caching), damit das
Laden der App nie beeinträchtigt wird.
- Backend: WebPush-Bibliothek + VAPID; PushNotifier sendet an alle Abos, räumt veraltete
(404/410) auf. Endpoints: GET /push/vapid-public-key, POST /push/subscribe|unsubscribe.
- Ausgelöst über ein notify-Flag auf PUT /feedback: NUR die KI setzt es (z. B. neue
Rückfrage / Ticket gelöst) → keine Selbst-Pushes durch Aktionen der Züchterin. Text wird
aus dem Status abgeleitet, Klick öffnet das Ticket (?focus=).
- Schalter/Logik blenden sich aus, wenn das Gerät kein Push kann oder der Server keine
VAPID-Schlüssel hat (z. B. e2e-Mock).
Migration WebPushSubscriptions. VAPID-Schlüssel in appsettings.json (lokale Single-User-App
im Heimnetz — bewusste, vertretbare Vereinfachung). Tests: 264 Backend grün (+Push-Endpoints),
e2e Tickets Desktop grün, vitest 149.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Anhänge im Melde-Fenster (beim Erstellen) und direkt an bestehenden Tickets:
Vorschaubilder, Öffnen im neuen Tab, Entfernen.
- Bytes liegen in eigener Tabelle (FeedbackAttachment, lose FeedbackId ohne FK →
übersteht den Ingest-Wipe); GET /feedback liefert nur Metadaten (id/Name/Typ/Größe),
die Bytes über /feedback/attachments/{id}. Größenlimit 10 MB.
- Endpoints: POST /feedback/{id}/attachments (base64), GET /feedback/attachments/{id}
(Bytes), DELETE /feedback/attachments/{id}.
Migration FeedbackAttachments. Tests: 262 Backend grün (+Upload/Serve/Delete +Validierung),
e2e Tickets Desktop+Phone grün (+Foto-Upload), vitest 149.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Suche: Volltext über Nachricht/Frage/Antwort/Changelog/Name/Kategorie, plus
Kategorie-Filter (KI-gesetzte Kategorie als Chip + Auswahlmenü), je innerhalb der
aktiven Ansicht.
- 👍/👎 auf gelösten Tickets: 👍 speichert die Rückmeldung, 👎 öffnet das Ticket
wieder (zurück in die Rückfragen) und bittet um die fehlende Info.
- Melde-Fenster: schlägt beim Tippen ähnliche bereits gelöste Tickets vor
(Wort-Überlappung + Bonus bei gleichem Tier/Wurf/Kontakt) — Self-Service.
Backend: Feedback.Category + Feedback.Helpful (+ PUT-Handling), Migration
FeedbackCategoryAndHelpful (beide nullable). Tests: 260 Backend grün, e2e Tickets
Desktop +4 (Suche/Kategorie/👍/👎), vitest 149.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Wiederöffnen eines gelösten Tickets:
- landet jetzt in „Rückfragen" (NeedsInfo) statt „Offen".
- War es ein echt gelöstes Ticket OHNE Rückfrage, wird um mehr Infos gebeten und
„Wieder geöffnet am" (ReopenedAt) gestempelt.
- Hatte es bereits eine offene Rückfrage (z. B. manuell gelöste Rückfrage), kehrt es
ohne neuen Hinweis und ohne Zeitstempel in genau diese Frage zurück.
Löschen über die UI = Soft-Delete:
- Tickets wandern in die neue Kategorie „Gelöscht" (Papierkorb) statt entfernt zu werden.
- Von dort wiederherstellbar (POST /feedback/{id}/restore → DeletedAt = null).
- Countdown „Wird in N Tagen endgültig gelöscht"; nach 30 Tagen endgültige Löschung
(lazy beim Abruf in GET /feedback).
Außerdem: Scrollposition bleibt beim Zurück-Navigieren (z. B. nach Klick auf einen
Tier-Link) erhalten, statt an den Seitenanfang zu springen.
EF-Migration FeedbackReopenedAndSoftDelete (ReopenedAt, DeletedAt — beide nullable).
Tests: 260 Backend grün (+2 Reopen-Fälle, CRUD auf Soft-Delete umgestellt), e2e Tickets
Desktop+Phone grün (+Soft-Delete/Restore/Reopen), vitest 149.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Die KI kann im Text `tier:<8hex>` / `kontakt:` / `wurf:` / `gehege:` schreiben;
Frontend löst per POST /refs/resolve (Präfix-Match, DB-agnostisch) zu Name +
internem Link auf. Reine KI-Schreibhilfe — kein Nutzer-Button.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Tickets können jetzt eine Rückfrage (Question) tragen und die Züchterin kann
in-app antworten. Feedback um FK-freie Felder Question/Answer/AnsweredAt erweitert
(überlebt Ingest-Wipe); Status-Lebenszyklus Open → NeedsInfo (Rückfrage gestellt)
→ Answered (beantwortet) → Resolved. Migration AddFeedbackQuestionAnswer.
PUT /feedback/{id}: question → NeedsInfo, answer → Answered+AnsweredAt; DTO gibt
die Felder zurück. Tickets-Seite (/hilfe/tickets) zeigt die Rückfrage hervorgehoben
und bietet ein Antwort-Feld + „Antworten"; Status-Badges Offen/Rückfrage offen/
Beantwortet/Gelöst.
Tests: FeedbackEndpointTests (Frage→NeedsInfo, Antwort→Answered, übersteht Ingest),
e2e tickets.spec.ts. dotnet/vitest/playwright grün.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Die ~1095 geparsten Verträge werden jetzt als echte SaleContract-Records erzeugt,
sodass die Verträge-Seite gefüllt ist (statt 0). enrich_from_contracts() liefert
zusätzlich eine saleContracts-Liste (deterministische Id aus Dateiname, Käufer-
ContactId, Preis, Abgabe-/Vertragsdatum, FileName, gematchte Tier-Ids); main()
schreibt sie als Top-Level-Key in resolved_import.json. IngestResolvedService legt
SaleContract + SaleContractAnimal an (FK-sicher nach Kontakten/Tieren, unbekannte
Links übersprungen) — Tabelle wird wie gehabt gewischt und aus dem Payload neu
befüllt (idempotent).
Ergebnis: 1033 Verträge (611 mit ≥1 Tier, alle datiert; 54 Dateinamen-Dubletten
zusammengefasst, 7 datumlose + 1 ohne Käufer übersprungen). Kein Schema-Change
(datumlose übersprungen statt Spalten nullable → keine Migration).
DOCX-Download: importierte Verträge haben keine Word-Datei im contract-storage →
DTO.HasFile=false, Frontend blendet den Word-Button aus (Hinweis „Keine Word-
Datei"), PDF wird weiterhin generiert. Download-Endpoint liefert sauber 404 statt
500 bei fehlender Datei.
Tests: dotnet 213, vitest 129, playwright 16 (neuer vertraege.spec.ts), Python grün.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Neuer „Datenherkunft"-Button in der Rennmausakte öffnet einen Dialog, der zeigt,
aus welchen Quellen der Eintrag erzeugt wurde: Quelldateien (Stammbäume +
Wurfchronik), Anzahl zusammengeführter Datensätze, Eltern-Herleitung
(Methode/Konfidenz) und Hinweise (z. B. „per manueller Entscheidung zugeordnet",
„Konflikt gelöst", „aus N Datensätzen zusammengeführt").
Import: build_provenance() in merge_and_resolve.py sammelt die Herkunft über alle
deduplizierten Datensätze und schreibt sie als Provenance-JSON je Tier in
resolved_import.json. Backend: Gerbil.Provenance (nullable text) + Migration
AddGerbilProvenance, gemappt im Ingest und im GerbilDto zurückgegeben. Frontend:
ProvenanceDialog + Typen + Strings.
Tests: Ingest-Round-trip (vorhanden/abwesend), e2e provenance.spec.ts.
dotnet(212)/vitest(129)/playwright/tsc/eslint grün.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Rechtsklick auf eine Maus im Stammbaum öffnet ein Kontextmenü mit „ID kopieren"
(Clipboard + Toast) und „Fehler melden". Zusätzlich „Fehler melden"-Buttons in
der Rennmausakte und der Wurf-Ansicht. Der Dialog erfasst eine Beschreibung und
sendet sie samt Debug-Kontext (Ansicht, Tier-/Wurf-ID + Name, URL, Zeitstempel,
User-Agent) an POST /feedback; gespeichert in einer neuen Feedback-Tabelle.
Backend: Feedback-Entity (lose nullable GerbilId/LitterId ohne FK), Endpoints
POST/GET /feedback, EF-Migration AddFeedback. Die Tabelle wird vom Import-Ingest
NICHT geleert — Feedback überlebt Re-Ingests (Test deckt das ab).
Tests: FeedbackEndpointTests (persistiert, 400 bei leer, übersteht Ingest-Wipe);
e2e feedback.spec.ts. tsc/eslint/vitest(129)/playwright(6)/dotnet(212) grün.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Detailseite (Rennmausakte):
- Neugestaltung: Hero-Foto + Overlay, Schnellfakten-Pills, Karten-Sektionen,
Charakter als Chips, getabte Fotos/Gesundheit/Gewicht.
- Eltern (Vater/Mutter) als Links; Charakter-Karte klappt bei Status
Abgabe/Verstorben/Abgegeben ein (nur Zucht/Liebhaber offen).
- Gehege wird bei abgegebenen/verstorbenen Tieren ausgeblendet (Detail + Formular).
Listen:
- Infinite Scroll auf allen Listen (Rennmäuse, Würfe, Gehege, Verträge,
Anfragen, Kontakte) via useInfiniteList/useInfiniteSentinel; stabile
Sortierung mit id-Tiebreaker (keine doppelten Keys), Back-to-top-Button.
- Kontakte: Rolle-Filter (Züchter/Abnehmer) als Quick-Chips + Sticky-Header.
Zucht-Nachname:
- Namens-Anhängsel der Zucht in den Einstellungen + je Züchter-Kontakt
(Backend-Spalten + Migration); eigene Tiere zeigen „Name + Suffix".
- „Eigene Zucht" ist die Standard-Herkunft neuer Tiere.
Weiteres:
- Gehege-Bilder: Upload/Galerie auf der Gehege-Detailseite (Backend
EnclosurePhoto + Endpoints + Migration, geteilte Dateiablage).
- Toast-Rückmeldungen für alle Speichern-Aktionen.
- Checkboxen durch mobile-freundliche Toggle-Schalter ersetzt.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
During import execution, the migration 20260607012436_StatusModel failed because it
referenced a non-existent table "SaleContractAnimals" (plural). The actual table name
is "SaleContractAnimal" (singular). Fixed the reference in the SQL raw string to use
the correct table name so the migration can apply cleanly on fresh deployments.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
GET /gerbils/{id}: loads first photo by sortOrder (2 queries, no N+1).
GET /gerbils list: batch-loads first photos for the page in one IN query.
profilePhotoUrl = /photos/files/{fileName} or null. No migration.
3 new tests: no-photo=null, sortOrder priority, list batch.
UsageMap bildet alle Codes auf deutsche Beschreibungen ab; thematische Kategorien
landen unter "Themen-Kategorien" im Prompt (kein Etymologie-Zwang, origin=Kategoriename).
22 neue Tests, 179/179 gruen.
Single UpdateData migration for ID 00000000-...-0001 (sortOrder 0).
Name-only rename, genotype/ID/FK unchanged — no drift risk.
Matches colorVarietySeed.backend.json after GEN-4c (main 3aa9ac9).
165/165 tests, ef has-pending=No.
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
NpgsqlRetryingExecutionStrategy rejects user-initiated transactions: SaveChanges
inside a BeginTransactionAsync block triggers OnFirstExecution which throws
InvalidOperationException. Fix: CreateExecutionStrategy().ExecuteAsync wraps the
entire tx block; mutable state (counters, contactByNorm, change tracker) reset at
lambda top for idempotent retry. Logic extracted to RunLoopAsync local function
shared by dry-run and execute paths.
Regression test (Test 7): FakeRetryingStrategy with MaxRetryCount=1 reproduces
the OnFirstExecution check in CI without a live Npgsql instance.
165/165 tests, ef has-pending=No, no schema change.
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>