companion-app: Datensatz sichern/laden portiert (Item 9 Paritaet)

- "Daten ausgeben" zu "Fahrzeugprofil" umgebaut, gleiche zwei Knoepfe wie
  im Panel: Datensatz sichern/laden, popup mit vier Aktionen
- CSV-Spaltennamen jetzt byte-identisch zum Panel-Export (gemeinsamer
  Backend-Parser, csv_import.py) - vorher eigene, abweichende Kopfzeilen
  ohne Reimport-Moeglichkeit
- neue api.csvImportieren(), Fahrzeugprofil-Import ueber das bestehende
  api.profilSchreiben() (volles Profil, nicht die teilweise
  zusammenfuehrende profilSpeichern()-Bequemlichkeitsfunktion)
- Blinden Fleck geprueft: importStatusLesen()/zustandLesen() lesen per
  REST, nicht aus lokalem Cache - dieselbe Racebedingung wie im Panel kann
  hier strukturell nicht auftreten
- Toten dateiwahl-Scaffolding entfernt (nie verdrahtet)

Beim Bauen echten, vorbestehenden Fehler gefunden: companion-app und Panel
verwenden unterschiedliche Feldnamen fuer Wartungsplan-Eintraege
(betrieb/notiz vs. werkstatt/kosten) - als eigene Aufgabe geflaggt statt
hier mitgefixt.

tsc --noEmit sauber, 146/146 Tests, vite build erfolgreich. Nicht live
getestet (kein laufender companion-app-Dev-Server mit Backend-Zugang in
dieser Sitzung).

Co-Authored-By: Claude Sonnet 5 <[email protected]>
This commit is contained in:
2026-08-28 10:31:10 +02:00
co-authored by Claude Sonnet 5
parent bfb7dec1e9
commit 2be6b99667
5 changed files with 368 additions and 43 deletions
+51
View File
@@ -4096,6 +4096,57 @@ bumped `2026.8.28.1` → `.2` (initial popup) → `.3` (the race-condition fix -
specifically to see this fix, since the panel JS is cache-busted by manifest version, not by content hash).
`audi_ha_test` restarted at each step, confirmed clean via log every time.
## AD. Item 9 ported to companion-app (2026.8.28)
companion-app's starting layout differed from the panel's: no standalone "Fahrzeugprofil" tile existed at
all (its "Sicherung" tile already bundled profile export together with the server-side backup trigger -
`profilExportieren()` there actually downloads the combined `{profil, fahrten, tankvorgaenge}` bundle, the
companion-app equivalent of the panel's separate, untouched "Backup" tile, not of "Fahrzeugprofil" - so
"Sicherung" was correctly left alone). "Daten ausgeben" only exported Fahrten/Tankvorgänge (no Wartungsplan,
no CSV import at all, no ID columns). Ported by turning "Daten ausgeben" into the same "Fahrzeugprofil" tile
+ "Datensatz sichern"/"laden" popup the panel now has, reaching the same end state as the panel through a
different starting layout - exactly what the parity rule asks for.
**The CSV column names had to be made byte-identical to the panel's** (`"ID"`, `"Start"`, `"Ende"`, `"km"`,
etc.), not merely similar - the backend CSV parser (`csv_import.py`) is shared by both frontends and looks
columns up by exact name. companion-app's own prior CSV export used different header text entirely
("Beginn"/"Strecke km"/"Dauer s") - harmless before, since nothing ever read it back, but would have broken
cross-frontend round-tripping (export from the panel, import into companion-app, or vice versa) had it been
left as-is under a shared backend parser.
**Found a second real, pre-existing bug while doing this**: companion-app's `ServicebuchEintrag` type
(`Service.tsx`) declares `betrieb`/`notiz`, but the actual backend list the panel writes to
(`profil["service"]["buch"]`) uses `werkstatt`/`kosten`/`arbeiten` - no translation layer exists anywhere
(`profilAdapter.ts` passes `service` through as an untyped `Record<string, unknown>`), so a Wartungsplan
entry created in one codebase genuinely doesn't display correctly in the other (shows "Betrieb: —", no
cost). Out of scope for this feature to fix properly - `DatensatzPopup.tsx`'s Wartungsplan CSV export reads
the real field names via a documented `as Record<string, unknown>` cast (with an inline comment explaining
why) rather than trusting the local, apparently-wrong type. Flagged as a separate background task
(`task_7105e44c`) for a dedicated fix - decide the canonical field names (almost certainly the panel's, the
older and more established convention), fix `ServicebuchEintrag` and `Service.tsx`'s read/write code, and
simplify the CSV export's workaround cast once the type itself is correct.
New `src/screens/DatensatzPopup.tsx` (own file, mirrors `HistorienImport.tsx`'s shape/idiom: `Popup
variant="form"`, own open/close/error/result state, an `aktiv` ref guarding against a state update after
unmount). New `api.csvImportieren(art, inhalt)` (`api/index.ts`) - not queued, same reasoning as
`historieImportieren()` (a deliberate one-off action, not a form field that should tolerate being offline).
Fahrzeugprofil import reuses the existing `api.profilSchreiben(profil)` directly (NOT the `DatenKontext`
convenience `profilSpeichern()`, which only merges partial `einstellungen`/`fahrzeug` fields - a loaded file
needs to replace the whole profile, matching the panel's `profil_schreiben` semantics exactly), followed by
`neuLaden()` to refresh derived state. `importStatusLesen()`/`zustandLesen()` fetch over REST, not a locally
cached reactive object - so companion-app was never exposed to the race-condition class of bug the panel
needed a dedicated fix for (see section AC) - confirmed structurally, not just assumed.
Also removed: a dead `dateiwahl` file-input ref in `Einstellungen.tsx` (declared, rendered, never wired to
an `onChange` or `.click()` anywhere) - pre-existing, unrelated scaffolding, but sitting directly next to
the new, functionally-equivalent file-input mechanism this change adds made it actively confusing to leave,
so removed rather than merely noted.
Verified: `tsc --noEmit` clean, full suite green at 146/146 (including the "every page renders with sample
data" smoke test, which covers `Einstellungen.tsx`), `vite build` succeeds. **Not live-verified** - no
running companion-app session with backend auth this round, the same structural limitation noted repeatedly
elsewhere in this file for companion-app-only changes.
---
## Working conventions (observed — keep them)