Commit Graph

532 Commits

Author SHA1 Message Date
bf074696b4 docs(triage): Triage-Lauf 2026-08-19 — 16 offene Tickets in 13 Buendeln
Je Bundel ein Triage-Agent gegen Prod-API + lokale Quellen (Stammbaum-xlsx,
Wurfchronik, _rpro3.db, resolved_import.json). Ergebnis: alle 13 umsetzbar,
keine Rueckfrage noetig. Uebergreifend: Prod ist auf dem aktuellen
Importer-Stand, die beklagten Dubletten stecken schon in resolved_import.json
— die Juli-Entscheidungen greifen nach dem Chart-Import vom 2026-08-18 nicht
mehr (Wurfchronik-Elternstubs ohne Geburtsdatum, one-decision-per-animal).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-19 23:01:27 +02:00
fdad1633cd fix(akte): Komma-Trenner in Listen + Elternnamen in der Wurf-Liste
- Zuchtpartner/Nachzucht: .ak-kv-value ist ein Flex-Container mit wrap, jeder
  Eintrag ein eigenes Item. Das vorangestellte „, " landete beim Umbruch am
  Zeilenanfang („, Zora"). Das Komma klebt jetzt hinten am vorherigen Namen.
  Regressionstest KV-COMMA (schlaegt gegen den alten Code fehl).
- Wurf-Liste/Zuchtpaare: Elternnamen wurden nur aus den ersten 1000 Tieren
  aufgeloest (Prod hat >2400) → Eltern dahinter erschienen als „Unbekannt".
  Jetzt werden alle Tiere geladen.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-19 22:42:59 +02:00
54e873c115 feat(tiere): Eltern direkt in der Tier-Akte bearbeiten (QOL)
All checks were successful
CI / Backend Tests (.NET) (push) Successful in 1m15s
CI / Frontend Tests (Node/Vite) (push) Successful in 9m37s
CI / Docker Build & Push (push) Successful in 7m36s
CI / Deploy auf TrueNAS (Custom App) (push) Successful in 8s
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>
2026-08-19 20:43:28 +02:00
7a2feac04d fix(ci): Deploy-Job repariert — kein rsync im Runner, Transfer per tar over ssh
All checks were successful
CI / Backend Tests (.NET) (push) Successful in 1m17s
CI / Frontend Tests (Node/Vite) (push) Successful in 9m37s
CI / Docker Build & Push (push) Successful in 4m32s
CI / Deploy auf TrueNAS (Custom App) (push) Successful in 9s
Der Deploy-Job war seit seiner Einführung rot (Runs 99–107). Zwei Ursachen:

1. Die Repo-Secrets NAS_SSH_HOST/NAS_SSH_KEY waren in Gitea nie angelegt, der
   Job baute also "ssh root@" mit leerer Key-Datei -> "Could not resolve
   hostname". Secrets sind jetzt gesetzt (neuer Deploy-Key
   gitea-ci-deploy@gerbilmanager, Pubkey in /root/.ssh/authorized_keys auf der
   NAS) — das ist Server-Konfiguration, hier ändert sich dadurch nichts.
2. Danach scheiterte der Transfer an "rsync: command not found": der Guard
   prüft nur `ssh`, und ssh ist im Runner-Image vorhanden, also lief der
   apt-get-Zweig nie und rsync fehlte. Der Transfer nutzt jetzt `tar | ssh`
   (tar existiert auf beiden Seiten, `-xof` erhält die Modi, die Deploy-Skripte
   bleiben ausführbar); rsync ist aus dem apt-Aufruf entfernt.

Transfer und truenas-deploy.sh vorab per SSH gegen die NAS verifiziert: Dateien
identisch übertragen, Redeploy erfolgreich, /api/health grün, Daten unverändert
(2451 Tiere, 965 Würfe). CLAUDE.md hält beide Stolperfallen fest.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 00:13:01 +02:00
0b99cfd2bd feat(import): neue Stammbäume (12 Charts) + litterChildren.add + EMF-Fotos überspringen
Some checks failed
CI / Backend Tests (.NET) (push) Successful in 1m16s
CI / Frontend Tests (Node/Vite) (push) Successful in 9m39s
CI / Docker Build & Push (push) Successful in 14m0s
CI / Deploy auf TrueNAS (Custom App) (push) Failing after 3s
Die Züchterin hat 24 neue/aktualisierte Stammbaum-xlsx geliefert (Ordner
"neuestammbäume"); sie liegen jetzt im kanonischen Quellverzeichnis
Sttammbäume (12 neue Charts, 7 aktualisierte, 3 identisch, das inhaltsgleiche
"Picus Son (2)" ausgelassen). Prod ist per Upload-Ingest aktualisiert:
2372 -> 2451 Tiere, 916 -> 965 Würfe, 432 -> 507 Fotos, 2198 -> 2275 Tiere
mit Geburtsdatum. Overrides/verified-Zeilen, manuelle Tiere und Tickets
haben den Ingest unverändert überlebt.

Zwei Datenfehler, die die neuen Charts aufgedeckt haben — datengetrieben und
re-ingest-stabil gefixt statt an der globalen Heuristik zu drehen:

- litterChildren kennt jetzt `add` [Name | {name, dob}] als Gegenstück zu
  `keep`: hängt ein Jungtier an DIESEN Wurf und entfernt den alten Wurf, wenn
  er dadurch kinderlos UND virtuell ist. Nötig, weil "Pukas Kids" Akanes
  Eltern komplett UNTER ihren Block setzt (N80 Roni = Vater, N81 Fumi =
  Mutter) — _reconstruct_parents griff eine Zeile zu hoch, paarte Irish
  Coffee (Bonapartes Mutter) mit Roni und riss Akane aus dem Z21-Wurf in
  einen Phantom-Wurf, der in der Wurfchronik auftauchte (Ticket 88389f8e).
- Merle: durch das neue Geburtsdatum (18.06.2023) mergt der addAnimals-Stub
  in den Chart-Datensatz und verliert dabei sein isResident -> expliziter
  resolutions-Override (Ticket 36a3fcde/a8f11ac0, Züchterin: Zuchttier).

Außerdem: Excel legt neben Fotos teils EMF/WMF-Vektorvorschauen ab, die
Browser nicht darstellen können (kaputte Bildkachel in der Tier-Akte) ->
extract._attach_photos überspringt .emf/.wmf (5 Fotos betroffen).

Regressionstests für alle drei Punkte; alle Python-Suites grün.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 22:27:18 +02:00
1cb9e52117 fix(frontend): nginx client_max_body_size 50m für große /api-Uploads
Some checks failed
CI / Backend Tests (.NET) (push) Successful in 1m9s
CI / Frontend Tests (Node/Vite) (push) Successful in 9m34s
CI / Docker Build & Push (push) Successful in 1m25s
CI / Deploy auf TrueNAS (Custom App) (push) Failing after 3s
Der Reverse-Proxy des Frontend-Containers hatte kein client_max_body_size (Default 1 MB)
→ 413 bei größeren Uploads (RennmausPro-Backups, Foto-Anhänge, und v. a.
resolved_import.json ~6,6 MB für /api/import/ingest-resolved/upload). Limit auf 50m
angehoben, damit der Prod-Re-Ingest per HTTP-Upload ohne SSH funktioniert.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 01:02:53 +02:00
2e7911074f feat(triage): Ticket-Fixes (Daten + Code) + prod-fähige Triage
Some checks failed
CI / Backend Tests (.NET) (push) Successful in 1m36s
CI / Frontend Tests (Node/Vite) (push) Successful in 9m35s
CI / Docker Build & Push (push) Successful in 1m28s
CI / Deploy auf TrueNAS (Custom App) (push) Failing after 3s
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>
2026-07-21 00:41:43 +02:00
0a4bcabd98 feat(deploy): publicsite-Container intern (Port 8081) + app.update im Deploy-Skript
Some checks failed
CI / Backend Tests (.NET) (push) Successful in 1m9s
CI / Frontend Tests (Node/Vite) (push) Successful in 9m34s
CI / Docker Build & Push (push) Successful in 1m29s
CI / Deploy auf TrueNAS (Custom App) (push) Failing after 3s
- custom-app.compose.yaml: publicsite (nginx) ergaenzt, LAN-only, statisches HTML aus
  publicsite-Volume (POST /api/publish); PUBLICSITE_PORT-Platzhalter
- truenas-deploy.sh: bei existierender App app.update (statt nur redeploy), damit
  Compose-Aenderungen (neue Dienste) uebernommen werden
- CLAUDE.md: 5. Dienst dokumentiert

Verifiziert: publicsite Up auf 8081, publish -> HTTP 200 (inkl. abgabetiere-Seite).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-19 14:24:54 +02:00
1484d04917 docs(claude): Produktion/Deployment-Sektion (TrueNAS Custom-App, wie deployen)
Some checks failed
CI / Backend Tests (.NET) (push) Successful in 1m13s
CI / Docker Build & Push (push) Has been cancelled
CI / Deploy auf TrueNAS (Custom App) (push) Has been cancelled
CI / Frontend Tests (Node/Vite) (push) Has been cancelled
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-19 14:19:25 +02:00
854da1a000 fix(deploy): postgres:18-Mount /var/lib/postgresql + backup ohne cron
Some checks failed
CI / Backend Tests (.NET) (push) Successful in 1m8s
CI / Frontend Tests (Node/Vite) (push) Successful in 9m35s
CI / Docker Build & Push (push) Successful in 1m28s
CI / Deploy auf TrueNAS (Custom App) (push) Failing after 3s
- db-Volume auf /var/lib/postgresql (v18 legt PGDATA in .../18/docker; Mount auf
  .../data laesst v18 nicht starten -> db unhealthy -> App-Rollback)
- Backup-Sidecar: Shell-Scheduler statt Alpine-crond (/etc/crontabs fehlt im
  Debian-postgres:18-Image -> Crash-Loop)

Verifiziert auf TrueNAS: Custom-App gerbilmanager RUNNING, 2370 Tiere, Port 8090.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-19 10:13:01 +02:00
60214a7c92 fix(web): zwei tsc-Buildfehler (useMutation-Rueckgabe void, Uint8Array<ArrayBuffer>)
Some checks failed
CI / Backend Tests (.NET) (push) Successful in 1m18s
CI / Frontend Tests (Node/Vite) (push) Successful in 9m35s
CI / Docker Build & Push (push) Successful in 1m46s
CI / Deploy auf TrueNAS (Custom App) (push) Failing after 4s
Blockierten den CI-Frontend-Build (npm run build) und damit build-and-push.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-19 09:47:03 +02:00
45673bb066 fix(deploy): App-Home auf Pool statt read-only /opt (TrueNAS Goldeye)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-19 09:25:26 +02:00
45b8533f18 feat(deploy): TrueNAS Custom-App + Auto-Deploy, plus aufgelaufene Arbeit
Some checks failed
CI / Backend Tests (.NET) (push) Successful in 1m11s
CI / Frontend Tests (Node/Vite) (push) Failing after 4m59s
CI / Docker Build & Push (push) Has been skipped
CI / Deploy auf TrueNAS (Custom App) (push) Has been skipped
Deployment:
- custom-app.compose.yaml: self-contained Compose fuer TrueNAS "Custom App"
  (absolute Host-Bind-Pfade, postgres:18, pull_policy always, Port 8090)
- scripts/truenas-deploy.sh: Host-Skript create/redeploy via midclt (App
  bleibt unter Apps sichtbar) inkl. Image-Pull + Health-Check
- ci.yml Deploy-Job: laeuft auf ubuntu-latest-Runner, kopiert Deploy-Dateien
  per SSH auf den NAS-Host und triggert truenas-deploy.sh (statt runs-on goldeye)
- compose.yaml/.env.example: postgres:18 (Locale-Match zur Quell-DB), Port 8090
- .gitignore: .agents/, tools/rag/, deploy/truenas/.env (Secrets/Scratch)

Aufgelaufene Feature-Arbeit (verified/Freeze, Migrationen, Import-Triage):
- GerbilOverride/VerifiedGerbil-Endpoints + GerbilSnapshotService + Tests
- EF-Migrationen (ShowInChronicle, Stillborn, BirthOrder, ManualFlag, DSGVO)
- Frontend VerifizierteTierePage + verified-API + e2e-Spec
- diverse Import-/Triage-Skripte und -Tests

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-19 09:19:11 +02:00
84365bba7f feat(import): 6 weitere RPRO3-Dubletten-Entscheidungen (Runde 2)
Some checks failed
CI / Backend Tests (.NET) (push) Successful in 1m6s
CI / Frontend Tests (Node/Vite) (push) Failing after 4m58s
CI / Docker Build & Push (push) Has been skipped
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>
2026-06-26 11:35:57 +02:00
a333bce1b3 feat(import): RPRO3-Dubletten-Entscheidungen der Züchterin als Override
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>
2026-06-26 11:26:50 +02:00
bbd305861f feat(import): Hiros Eltern aus RennmausPro ergänzt (Ticket f3ad5ec9)
Hiro of Golden Lights erschien fälschlich als Gründertier (keine Eltern im Import).
Aus dem RennmausPro-Backup sicher zugeordnet (gleiches Geburtsdatum 13.02.2012):
Eltern Wynn × Amidala — als nicht-residente Ahnen-Stubs angelegt und verknüpft.

Bewusst nur EINE Generation: tiefere Ahnen würden über addAnimals-Stubs nicht
verkettet (der Eltern-Override greift nur auf echte Stammbaum-/Wurfchronik-Tiere,
nicht auf Stubs) — und mehrere Großeltern-Namen (Harry/Shy/Jack/Speedy) kollidieren
mit bestehenden, anderen Tieren. Voller mehrstufiger RennmausPro-Ahnen-Import =
eigenes Feature (Import-Code-Änderung + robustes Matching), nicht blind machbar.

python test_merge_resolve.py + test_extract.py grün.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 15:51:06 +02:00
8aaddea4ef fix(tickets): Tab + Scrollposition überleben Browser-Zurück
Zwei Ursachen, warum es auf der Tickets-Seite nicht ging (am echten Firefox/Chromium
gegen die Live-App verifiziert):

1) Tab ging verloren: der gewählte Filter-Tab (z. B. „Geschlossen") war reiner
   Component-State und sprang nach „Zurück" (Remount) auf „Offen" zurück — also war man
   in der falschen, kurzen Liste. Jetzt wird der Tab in der Session gemerkt und
   wiederhergestellt (userPicked startet weiterhin false, damit ?focus=-Deep-Links
   weiter funktionieren).

2) Scroll-Save-Heuristik zu unzuverlässig: Beim Wegnavigieren schrumpft der Inhalt von
   .app-main → der Container klemmt auf eine kleinere Position → dieses scroll-Event
   überschrieb die gemerkte Stelle. Erkennung präzisiert: nur verwerfen, wenn Position
   UND scrollHeight gleichzeitig SINKEN (= Inhalt geschrumpft/Seitenwechsel). Eine echte
   Nutzer-Scrollung verkleinert die scrollHeight nie — damit kein fälschliches Verwerfen
   echter Positionen mehr (vorherige „Höhe geändert"-Heuristik schluckte zu viel).

Tests: scroll-restore.spec erweitert (realistisches 2-Schritt-Scrollen + Tickets-Tab
überlebt Zurück), desktop+phone grün; vitest 149 grün.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 14:54:55 +02:00
54cc493230 test(e2e): Scroll-Wiederherstellung der Rennmausliste (desktop + phone)
Deckt die Regression ab, die zuletzt durchrutschte:
- Liste scrollen → Tier öffnen → Browser-Zurück → Position bleibt erhalten.
- Position überlebt App-Hintergrund/Wiederanzeige (visibilitychange).
Läuft im phone-Projekt (390x844, Touch) UND desktop, am echten Scroll-Container .app-main.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 14:33:17 +02:00
bb1b8f8c76 fix(ux): Scrollposition nicht durch Seitenwechsel-Kollaps auf 0 überschreiben
Echte Ursache (per Firefox/Chromium gegen die Live-App verifiziert): Beim Wegklicken
eines Listeneintrags wird die Seite ausgehängt → der Inhalt von .app-main kollabiert →
der Container springt auf scrollTop 0 → das löste ein scroll-Event aus, das 0 als neue
Position speicherte und die gemerkte Stelle ZERSTÖRTE, kurz bevor wir wegnavigierten.

Fix:
- scroll-Events ignorieren, wenn der Container gerade NICHT scrollbar ist
  (scrollHeight ~ clientHeight = Inhalt kollabiert/Seitenwechsel) → kein „unechter" 0-Save.
- Wiederherstellung hält den Schutz über die gesamte Settling-Phase: bei jeder DOM-Änderung
  Position erneut setzen, erst nach ~350 ms Ruhe freigeben (gegen Reflow-Races beim Nachladen).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 14:28:32 +02:00
00a3919c9f fix(ux): Scroll-Wiederherstellung am echten Scroll-Container (.app-main), nicht am Fenster
Die App scrollt NICHT das Fenster: .app-shell ist height:100dvh/overflow:hidden, der
Inhaltsbereich .app-main hat overflow-y:auto. window.scrollY/scrollTo waren daher
wirkungslos (am Desktop sichtbar). Der Hook arbeitet jetzt auf .app-main (scrollTop/
scrollTo, Scroll-Listener am Container) und beobachtet den Inhalt per MutationObserver,
um nach dem Nachladen exakt zur gemerkten Position zu springen. Fallback auf window,
falls der Container fehlt.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 14:06:56 +02:00
b0e92c363a fix(ux): zuverlässige Scroll-Wiederherstellung für Infinite-Scroll-Listen
Zwei Probleme bei der großen Rennmausliste (und allen Infinite-Scroll-Listen) nach
Zurück-Navigation behoben:

1) Event- statt Timer-basiert: Die Scroll-Wiederherstellung hängt sich jetzt per
   ResizeObserver an die Höhenänderung der Seite (Liste ist wieder da) und springt dann
   zur gemerkten Position — statt auf einen festen Timer zu warten. Bricht bei eigener
   Scroll-Eingabe ab; Sicherheitsnetz nach 15 s.

2) Infinite-Scroll baut Höhe wieder auf: useInfiniteList merkt sich (optionaler
   restoreKey) die geladene Seitenzahl je Query und lädt sie nach einer Zurück-
   Navigation wieder nach — sonst war die Liste nur 1 Seite hoch und die Position
   unerreichbar. Aktiviert für Rennmäuse, Kontakte, Würfe, Becken, Anfragen, Verträge.

tsc/eslint/vitest grün; e2e (Listen + Navigation, 41) grün.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 14:01:28 +02:00
7262b34ae9 fix(ux): Scrollposition übersteht auch Dev-Server-Hot-Reload (Vite HMR)
Bei einem HMR-/Fast-Refresh-Update rendert die Seite kurz neu (Liste kollabiert →
Sprung nach oben), ohne Navigation/pagehide. Jetzt vor `vite:beforeUpdate`/
`vite:beforeFullReload` sichern und nach `vite:afterUpdate` wiederherstellen (nur Dev;
import.meta.hot ist in Produktion undefiniert). Restore-Fenster auf 4 s erhöht, damit
auch langsam nachladende Listen die Position noch erreichen.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 13:52:16 +02:00
ebc70cc0b8 feat(ux): globale Scroll-Wiederherstellung für ALLE Listen/Seiten
Neuer Hook useScrollRestoration (einmal im AppShell gemountet) erhält die
Scrollposition je Route über:
- Zurück/Vorwärts-Navigation (POP restauriert, PUSH startet oben),
- Bildschirm-Sperre / App-Hintergrund (visibilitychange, pagehide/pageshow) —
  damit das Handy nach dem Entsperren nicht mehr oben landet.

Robust gegen nachladende Listen (rAF-Nachjustierung bis ~2,5 s, während die Liste
wächst), bricht bei eigener Scroll-Eingabe ab, Deep-Links (?focus=, #anchor) haben
Vorrang. Die bisher nur in der Tickets-Seite eingebaute Logik wurde durch den
globalen Hook ersetzt.

tsc/eslint/vitest grün; e2e (navigation/listen/tickets, 42) grün.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 13:48:02 +02:00
f9e672c707 fix(tickets): Scrollposition übersteht Handy-Sperre/Hintergrund (nicht mehr Sprung nach oben)
Bisher wurde die Scrollposition nur einmal beim Mount wiederhergestellt — sperrte das
Handy und kam zurück, war die Seite oben. Jetzt:
- Position wird beim Ausblenden sicher gesichert (visibilitychange→hidden, pagehide),
  nicht nur bei Scroll-Events.
- Wiederherstellung beim Wiederanzeigen (visibilitychange→visible, pageshow) UND nach
  dem Laden — mit mehreren rAF-Versuchen gegen nachwachsende Inhalte.
- Schutz gegen den Aufwach-Sprung nach oben: während des Wiederherstellens werden
  Scroll-Speicherungen blockiert, damit ein vom Browser ausgelöster 0-Sprung die
  gemerkte Position nicht überschreibt.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 13:41:05 +02:00
fe79be2eee feat(import): isResident-Override + Vorfahren Bentley/Alexandria/Bugatti
- Neuer isResident-Override im Import (merge_and_resolve, NACH dem Dedup, damit die
  Zusammenführung ihn nicht überschreibt). Matcht über externalRef (präzise, auch für
  namenlose Tiere) oder normalize(call-name)+dob. Ein Nicht-Bestandstier verliert den
  eigenen Zucht-Breeder.
- conflict-decisions.json: Bentley, Alexandria, Bugatti = nur Vorfahren (isResident=false),
  bestätigt von der Züchterin (Tickets 09bcac78 / 45cc501b).
- Regressionstests ergänzt/aktualisiert (Bentley/Alexandria/Bugatti isResident; Danielle
  Vater Makoto; Catelyn Eddard×Milena; Gaida Zhuāngzǐ×Zaibunissa). Alle python test_*.py grün.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 12:29:25 +02:00
41ee4ee3ab fix(import): Eltern für Danielle, Catelyn, Gaida (Triage-Antworten der Züchterin)
- Danielle: Vater Makoto ergänzt (Mutter Ella war schon gesetzt) — Geschwisterverpaarung.
- Catelyn: Eltern Eddard Stark of Sunset Glow × Milena.
- Gaida: Eltern über Geburtsdatum gefunden — Zhuāngzǐ × Zaibunissa (beide *21.02.2020,
  Geschwisterverpaarung, Kinder von Nisha × Zenon).

Per conflict-decisions.json (re-ingest verifiziert: Eltern live korrekt). Tickets
4692fd5c / 7bbc045c / ba63325a geschlossen.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 12:18:06 +02:00
f7a1a7724b feat(tickets): Web-Push-Benachrichtigungen (PWA)
- 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>
2026-06-23 11:32:26 +02:00
750619d3d7 feat(tickets): Foto-/Datei-Anhänge an Tickets
- 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>
2026-06-23 11:24:02 +02:00
48b138bbfd feat(tickets): Volltextsuche + Kategorien, 👍/👎-Rückmeldung & ähnliche gelöste Tickets
- 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>
2026-06-23 11:14:07 +02:00
c0a7b1a5ee feat(tickets): Wiederöffnen→Rückfrage mit Zeitstempel + Soft-Delete-Papierkorb + Scroll-Erhalt
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>
2026-06-23 11:02:04 +02:00
a0fa33da42 feat(tickets): Erst-Text nach Antwort sperren, nur neueste Antwort editierbar
Sobald ein Ticket eine Antwort hat, ist der ursprüngliche Ticket-Text nicht
mehr bearbeitbar. Editierbar ist dann nur noch die neueste Antwort — und auch
die nur, bis die KI darauf reagiert hat (Status verlässt „Answered"). Ältere
Antworten im Verlauf (thread) bleiben wie bisher schreibgeschützt.

Regressionstest + Answered-Mock-Ticket ergänzt.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 10:32:04 +02:00
6b264a9b71 fix(import): Farbschlag aus Genotyp ableiten + Mamta-Eltern — Ticket-Triage
genotype.py: Python-Port von genotypeToFarbschlag (0 Abw. über 3402 Genotypen).
resolve_color_and_genotype: bei vorhandenem Genotyp gewinnt der berechnete Farbschlag
(Goldfuchs≠Gold, Dilute Agouti/Anthrazit, Blaufuchs statt -schimmel bei (schimmel),
spsp statt Schecke). Mamta Mini: Ee + Eltern Geely×Gaida am Wurf. Regressionstests je Fall.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 10:08:13 +02:00
26977d73ff docs(mempalace): Subagenten müssen MemPalace zu Aufgabenbeginn abfragen
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 09:43:12 +02:00
12f5de00a0 docs: Genetik-Engine immer voll mit Tests absichern (Konvention) + E-Locus-Regel
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 09:36:12 +02:00
0540498e85 Merge branch 'worktree-agent-a7281df1f2cc3f1f1'
# Conflicts:
#	gerbil-manager-web/src/genetics/catalog.ts
#	gerbil-manager-web/src/genetics/genotype.ts
#	gerbil-manager-web/src/genetics/punnett.ts
2026-06-23 09:29:42 +02:00
a87d1ec865 fix(api): API-Host im Dev-Modus aus window.location ableiten (LAN/Handy-Zugriff)
Statt fest http://localhost:5179 (auf dem Handy = das Handy selbst) nutzt der
Client im Dev-Modus den Host, von dem die Seite geladen wurde → 192.168.x.y:5179.
e2e/Rechner bleiben bei localhost.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 09:18:55 +02:00
8c1ee75b81 fix(genetics): Engine-Fixes Ticket-Triage (Goldfuchsschimmel, Dilute CP-Blaufuchs, Eltern-Inferenz, Punnett, uw[d])
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 09:05:35 +02:00
aa4552ff84 docs(triage): Genetik-Vorlauf-Notizen als Referenz für Engine-Fixes
Some checks failed
CI / Backend Tests (.NET) (push) Successful in 1m8s
CI / Frontend Tests (Node/Vite) (push) Failing after 4m55s
CI / Docker Build & Push (push) Has been skipped
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 08:34:54 +02:00
18d74ff3f3 docs(triage): Ticket-Triage-Matrix (Lauf 56 Tickets)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 08:29:12 +02:00
72ad706ca6 fix(import): Eltern-/Herkunft-/Dubletten-Korrekturen — Ticket-Triage Cluster
Akane (Roni=Vater male + Mutter Fumi), Sunny (Bill+Melly), Danielle (Mutter Ella,
motherDob-Disambiguierung), Cherry Berry (female), Black-Forest-Label vereinheitlicht,
namenloser Bock entdoppelt (mergeExternalRefs).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 08:25:43 +02:00
a66739e84d feat(import): „Was wird (nicht) importiert"-Hinweis auf der RPRO3-Import-Seite
Klarstellung für die Züchterin: übernommen werden Tiere/Stammbäume/Genotypen/
Würfe/Kontakte/Gehege+Reinigung/Erwerb/Reservierung/Rücknahmen/Warteliste/
Ausstellungen; (noch) nicht: Finanzen, Termine, Tierarzt-Stammdaten, Tagebuch.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 07:24:59 +02:00
00dba91a5c feat(tickets): Kurz-Verweise (Shortlinks) im Ticket-Text auflösen
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>
2026-06-23 07:23:03 +02:00
c7b054e909 feat(import): RPRO3-Importer um 6 Feature-Tabellen erweitern
becken→Gehege-Reinigung, herktier→Erwerb, abgeben/abstat→Reservierung,
getback→Rücknahmen, nachfrage→Warteliste, ausz→Ausstellungen.
Deterministische GUIDs, idempotenter Re-Import.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 07:05:01 +02:00
00dc724605 test(e2e): Gewicht-Tab-Button im Tabpanel ansprechen (Erwerb-Sektion-Kollision)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 23:15:23 +02:00
156ede120e Merge branch 'worktree-agent-ae59040b377a324cb'
# Conflicts:
#	GerbilManagerWebAPI/ApplicationContext.cs
#	GerbilManagerWebAPI/Program.cs
#	gerbil-manager-web/e2e/mock-data.ts
#	gerbil-manager-web/src/pages/GerbilDetailPage.tsx
#	gerbil-manager-web/src/strings/de.ts
2026-06-22 23:10:18 +02:00
db2c29769e Merge branch 'worktree-agent-a02f88e8490625846'
# Conflicts:
#	GerbilManagerWebAPI/ApplicationContext.cs
#	GerbilManagerWebAPI/Program.cs
#	gerbil-manager-web/e2e/mock-data.ts
#	gerbil-manager-web/src/App.tsx
#	gerbil-manager-web/src/components/AppShell.tsx
#	gerbil-manager-web/src/strings/de.ts
2026-06-22 23:07:22 +02:00
ae7d171131 Merge branch 'worktree-agent-a0e6f31173772c798'
# Conflicts:
#	GerbilManagerWebAPI/ApplicationContext.cs
#	GerbilManagerWebAPI/Program.cs
#	gerbil-manager-web/e2e/mock-data.ts
#	gerbil-manager-web/src/strings/de.ts
2026-06-22 23:02:17 +02:00
6a64698601 Merge branch 'worktree-agent-a36d202be311b9289'
# Conflicts:
#	GerbilManagerWebAPI/ApplicationContext.cs
#	GerbilManagerWebAPI/Program.cs
#	gerbil-manager-web/e2e/mock-data.ts
2026-06-22 22:57:16 +02:00
1469a7f3dc Merge branch 'worktree-agent-a5f48334696288159'
# Conflicts:
#	gerbil-manager-web/e2e/mock-data.ts
2026-06-22 22:54:50 +02:00
6123b8ef9e Merge branch 'worktree-agent-a9d8edbd439109a45' 2026-06-22 22:53:33 +02:00