Speicherleiste ersatzlos gestrichen, Felder speichern beim Verlassen

Auf Entscheidung des Eigentuemers nach der HIG-Pruefung. Gegen die Leiste
sprach viererlei: iOS kennt kein solches Muster auf einer geschobenen
Einstellungsseite; sie schwebte ueber der Tab-Leiste und deckte einen Knopf
der Kachel dahinter zu (gemessen); sie war die Ursache des Datenverlusts bei
der Erstzulassung; und das Panel hatte sie nie - dort schreibt jede Aenderung
sofort ins Profil.

Jetzt speichern Textfelder beim Verlassen, Auswahlfelder sofort. Die Meldung
"Gespeichert." entfaellt mit. Der Regler "Fahrt beenden" behaelt seinen
eigenen Bestaetigungsschritt - ihn bei jedem Zwischenschritt zu sichern hiesse
ein Dutzend Schreibvorgaenge je Bedienung.

Mit echten Eingabeereignissen am laufenden System geprueft: getippt, ins
naechste Feld geklickt, Wert steht in fahrzeugprofil.json; danach der
urspruengliche Wert wiederhergestellt. 299 Tests gruen, 0 Tracebacks.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-05 12:42:50 +02:00
parent b22a0eca5b
commit 71403efafe
5 changed files with 80 additions and 33 deletions
+48
View File
@@ -11854,3 +11854,51 @@ Gemessen in beiden: Farbe `rgb(52,199,89)` bzw. `rgb(48,209,88)` - das ist
**Nachgezogen:** die Beschriftung heisst nur noch "Integration".
"Home-Assistant-Integration" brach auf Telefonbreite in zwei Zeilen um; auf
Entscheidung des Eigentuemers gekuerzt. Nachgemessen: einzeilig, 20px hoch.
## CY. Die Speicherleiste ist ersatzlos entfallen (2026.9.5.21)
Frage des Eigentuemers: *"entspricht Speichern/verwerfen anzeige bei Fahrzeug
einrichten der Apple HIG?"* - Antwort: nur teilweise. Entscheidung danach:
*"speichern: ersatzlos streichen"*.
### Was dagegen sprach
* **Das Muster.** iOS kennt keine dauerhafte Speichern/Verwerfen-Leiste auf
einer *geschobenen* Einstellungsseite. Apples zwei Muster sind: Aenderungen
wirken sofort (Einstellungen-App) oder das Bearbeiten laeuft in einem
**modalen Blatt** mit Abbrechen oben links und Fertig oben rechts. Eine
klebende Aktionsleiste unten ist ein Web-/Android-Idiom.
* **Sie verdeckte Inhalt.** Gemessen: Leiste bei y 577-670, Tab-Leiste ab 700;
`elementsFromPoint` direkt dahinter ein `BUTTON.ads-action--primary` in der
Kachel. Dazu Seitengrund `rgb(242,242,247)` auf weisser Kachel - ein grauer
Balken quer durch die Karte.
* **Sie war die Ursache eines Datenverlusts** (Abschnitt CU): wer nur die
Erstzulassung aenderte, bekam gar keinen Speichern-Knopf zu sehen. Eine
Leiste, die eine Eingabe erst gueltig macht, kann sie auch verschlucken.
* **Sie war eine Paritaetsabweichung.** Das Panel hat sie nie gehabt - dort
schreibt der `data-einrichten`-Handler bei jeder Aenderung sofort ins Profil.
### Was jetzt gilt
Textfelder (Modell, Kennzeichen, Erstzulassung, Ausfuehrung) speichern **beim
Verlassen des Feldes**, Auswahlfelder sofort. Genau wie im Panel. Die
Erfolgsmeldung "Gespeichert." entfaellt mit - eine sofort wirkende Aenderung
braucht keine Bestaetigung, und das Panel zeigt auch keine.
**`speichern()` bleibt fuer den Regler "Fahrt beenden".** Ein Schieberegler bei
jedem Zwischenschritt zu sichern hiesse ein Dutzend Schreibvorgaenge je
Bedienung; er behaelt deshalb seinen eigenen Bestaetigungsschritt mit
Verwerfen/Speichern in der Kachel (`dm-speicherleiste--kachel`). Das ist die
einzige verbliebene Stelle mit dieser Klasse.
### Zweimal am eigenen Testfehler vorbeigeschrammt
Der erste Nachweis schlug fehl - Wert nicht gespeichert. Ursache war nicht der
Code, sondern mein Test: ein programmatisches `e.focus()` + Wertsetzer +
`e.blur()` loest React's `onBlur` hier nicht verlaesslich aus. Mit echten
Eingabeereignissen (Dreifachklick, Tippen, Klick ins naechste Feld) steht der
Wert sofort in `fahrzeugprofil.json`.
Dasselbe Muster wie beim Koordinatenfehler in Abschnitt CS: **ein fehlgeschlagener
synthetischer Test ist kein Fehlerbeweis.** Erst mit echten Ereignissen pruefen,
dann urteilen.
+30 -31
View File
@@ -80,7 +80,6 @@ export function Einstellungen({
Eigentuemers, 04.09.2026). `null` heisst: nichts angefasst. */
const [pausenzeitEntwurf, setzePausenzeitEntwurf] = useState<number | null>(null)
const [laeuft, setzeLaeuft] = useState(false)
const [gespeichert, setzeGespeichert] = useState(false)
const [importOffen, setzeImportOffen] = useState(false)
/* Abfrage "bis wann gilt der Rabatt", die das Einschalten des SmartDeals
ausloest (sdpopup im Panel). */
@@ -236,18 +235,30 @@ export function Einstellungen({
eigenen Entwurf und uebergibt ihn erst beim Druck auf Speichern. Ohne den
Umweg wuerde `entwurf` im selben Rendern noch den alten Wert tragen -
setState wirkt erst beim naechsten Durchlauf. */
/* `erstzulassung` fuehrt einen EIGENEN Entwurf, unabhaengig von
`entwurf` - es gehoert zum Fahrzeug, nicht zu den Einstellungen. Diese
Bedingung hat es dabei vergessen: wer nur die Erstzulassung aenderte,
kam hier nie vorbei, und weiter unten erschien auch die Speicherleiste
nicht (die haengt ebenfalls allein an `entwurf`). Der eingetippte Wert
war damit gar nicht speicherbar und beim Verlassen der Seite weg -
gemeldet vom Eigentuemer am 05.09.2026. Das Panel speichert an dieser
Stelle sofort bei jeder Aenderung und war deshalb nie betroffen. */
/* Es gibt keine Speicherleiste mehr. Jedes Feld wirkt beim Verlassen,
Auswahlfelder sofort - wie im Panel, wo der `data-einrichten`-Handler
seit jeher bei jeder Aenderung ins Profil schreibt.
Ansage des Eigentuemers am 05.09.2026: "speichern: ersatzlos streichen".
Drei Gruende sprachen dafuer:
* iOS kennt keine dauerhafte Speichern/Verwerfen-Leiste auf einer
geschobenen Einstellungsseite. Apples Muster sind "wirkt sofort"
(Einstellungen-App) oder ein modales Blatt mit Abbrechen/Fertig in
der Navigationsleiste.
* Die Leiste schwebte ueber der Tab-Leiste und deckte dabei einen Knopf
der Kachel dahinter zu (gemessen 05.09.2026).
* Sie war die Ursache eines echten Datenverlusts: wer nur die
Erstzulassung aenderte, bekam gar keinen Speichern-Knopf zu sehen.
Eine Leiste, die eine Eingabe erst gueltig macht, kann sie auch
verschlucken.
`speichern()` bleibt fuer den Regler "Fahrt beenden": ein Schieberegler
bei jedem Zwischenschritt zu sichern hiesse ein Dutzend Schreibvorgaenge
je Bedienung, deshalb behaelt er seinen eigenen Bestaetigungsschritt. */
const speichern = async (zusatz?: Partial<EinstellungenWerte>) => {
if (!entwurf && !zusatz && erstzulassung === null) return
setzeLaeuft(true)
setzeGespeichert(false)
try {
await profilSpeichern({
einstellungen: { ...werte, ...(zusatz ?? {}) },
@@ -256,12 +267,15 @@ export function Einstellungen({
setzeEntwurf(null)
setzeErstzulassung(null)
setzePausenzeitEntwurf(null)
setzeGespeichert(true)
} finally {
setzeLaeuft(false)
}
}
/** Uebernimmt den aktuellen Stand des Bildschirms - beim Verlassen eines
Feldes oder sofort bei einem Auswahlfeld. Ohne Zusatz und ohne Knopf. */
const sichern = (zusatz?: Partial<EinstellungenWerte>) => void speichern(zusatz)
/* Zugaenge: je Dienst ein Entwurf im Eingabefeld. Der gespeicherte Token
kommt NIE zurueck (siehe zugaenge.py), das Feld startet deshalb immer leer
und zeigt als Platzhalter, ob schon einer hinterlegt ist. */
@@ -394,6 +408,7 @@ export function Einstellungen({
className="dm-eingabe dm-eingabe--fahrzeug"
value={werte.fahrzeugtitel}
onChange={(e) => aendern({ fahrzeugtitel: e.target.value })}
onBlur={() => sichern()}
/>
</Feld>
<Feld label="Kennzeichen">
@@ -401,6 +416,7 @@ export function Einstellungen({
className="dm-eingabe dm-eingabe--fahrzeug"
value={werte.kennzeichen}
onChange={(e) => aendern({ kennzeichen: e.target.value })}
onBlur={() => sichern()}
/>
</Feld>
<Feld label="Erstzulassung">
@@ -409,6 +425,7 @@ export function Einstellungen({
placeholder="TT.MM.JJJJ"
value={erstzulassungWert}
onChange={(e) => setzeErstzulassung(e.target.value)}
onBlur={() => sichern()}
/>
</Feld>
<Feld label="Ausführung" last>
@@ -416,6 +433,7 @@ export function Einstellungen({
className="dm-eingabe dm-eingabe--fahrzeug"
value={werte.ausfuehrung}
onChange={(e) => aendern({ ausfuehrung: e.target.value })}
onBlur={() => sichern()}
/>
</Feld>
{/* „Setup — Sensoren zuordnen" bleibt dem Panel vorbehalten: die
@@ -700,7 +718,7 @@ export function Einstellungen({
<select
className="dm-auswahl"
value={werte.backupIntervall}
onChange={(e) => aendern({ backupIntervall: e.target.value })}
onChange={(e) => sichern({ backupIntervall: e.target.value })}
>
<option value="aus">Aus</option>
<option value="taeglich">Täglich</option>
@@ -1031,25 +1049,6 @@ export function Einstellungen({
</>
)}
{gespeichert && <p className="dm-erfolg">Gespeichert.</p>}
{(entwurf || erstzulassung !== null) && (
<div className="dm-speicherleiste">
<ActionButton
onClick={() => {
setzeEntwurf(null)
// Sonst bliebe die Leiste nach "Verwerfen" stehen, weil der
// Erstzulassungs-Entwurf sie weiter offen haelt.
setzeErstzulassung(null)
}}
disabled={laeuft}
>
Verwerfen
</ActionButton>
<ActionButton variant="primary" onClick={() => void speichern()} disabled={laeuft}>
{laeuft ? "Speichere " : "Speichern"}
</ActionButton>
</div>
)}
</>
)
}
@@ -1 +1 @@
{"version":"2026.9.5.20","sha256":"65eb2b8e3c4fafbc3ef3aeaeb2daca86c136a209067d3f779b5eb65f4cc637ec","bytes":324590,"gebaut":"2026-09-05T09:38:57Z"}
{"version":"2026.9.5.21","sha256":"db8589425bb50d359c7711d845844f41285c48bc0cebd016c54e6b717cf39787","bytes":324552,"gebaut":"2026-09-05T10:40:06Z"}
@@ -1,7 +1,7 @@
{
"domain": "audi_dashboard",
"name": "Audi Dashboard",
"version": "2026.9.5.20",
"version": "2026.9.5.21",
"documentation": "https://gitea.nothaft.cloud/paul/audi-app/src/branch/main/README.md",
"issue_tracker": "https://gitea.nothaft.cloud/paul/audi-app/issues",
"codeowners": [