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". **Nachgezogen:** die Beschriftung heisst nur noch "Integration".
"Home-Assistant-Integration" brach auf Telefonbreite in zwei Zeilen um; auf "Home-Assistant-Integration" brach auf Telefonbreite in zwei Zeilen um; auf
Entscheidung des Eigentuemers gekuerzt. Nachgemessen: einzeilig, 20px hoch. 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. */ Eigentuemers, 04.09.2026). `null` heisst: nichts angefasst. */
const [pausenzeitEntwurf, setzePausenzeitEntwurf] = useState<number | null>(null) const [pausenzeitEntwurf, setzePausenzeitEntwurf] = useState<number | null>(null)
const [laeuft, setzeLaeuft] = useState(false) const [laeuft, setzeLaeuft] = useState(false)
const [gespeichert, setzeGespeichert] = useState(false)
const [importOffen, setzeImportOffen] = useState(false) const [importOffen, setzeImportOffen] = useState(false)
/* Abfrage "bis wann gilt der Rabatt", die das Einschalten des SmartDeals /* Abfrage "bis wann gilt der Rabatt", die das Einschalten des SmartDeals
ausloest (sdpopup im Panel). */ ausloest (sdpopup im Panel). */
@@ -236,18 +235,30 @@ export function Einstellungen({
eigenen Entwurf und uebergibt ihn erst beim Druck auf Speichern. Ohne den eigenen Entwurf und uebergibt ihn erst beim Druck auf Speichern. Ohne den
Umweg wuerde `entwurf` im selben Rendern noch den alten Wert tragen - Umweg wuerde `entwurf` im selben Rendern noch den alten Wert tragen -
setState wirkt erst beim naechsten Durchlauf. */ setState wirkt erst beim naechsten Durchlauf. */
/* `erstzulassung` fuehrt einen EIGENEN Entwurf, unabhaengig von /* Es gibt keine Speicherleiste mehr. Jedes Feld wirkt beim Verlassen,
`entwurf` - es gehoert zum Fahrzeug, nicht zu den Einstellungen. Diese Auswahlfelder sofort - wie im Panel, wo der `data-einrichten`-Handler
Bedingung hat es dabei vergessen: wer nur die Erstzulassung aenderte, seit jeher bei jeder Aenderung ins Profil schreibt.
kam hier nie vorbei, und weiter unten erschien auch die Speicherleiste
nicht (die haengt ebenfalls allein an `entwurf`). Der eingetippte Wert Ansage des Eigentuemers am 05.09.2026: "speichern: ersatzlos streichen".
war damit gar nicht speicherbar und beim Verlassen der Seite weg - Drei Gruende sprachen dafuer:
gemeldet vom Eigentuemer am 05.09.2026. Das Panel speichert an dieser
Stelle sofort bei jeder Aenderung und war deshalb nie betroffen. */ * 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>) => { const speichern = async (zusatz?: Partial<EinstellungenWerte>) => {
if (!entwurf && !zusatz && erstzulassung === null) return if (!entwurf && !zusatz && erstzulassung === null) return
setzeLaeuft(true) setzeLaeuft(true)
setzeGespeichert(false)
try { try {
await profilSpeichern({ await profilSpeichern({
einstellungen: { ...werte, ...(zusatz ?? {}) }, einstellungen: { ...werte, ...(zusatz ?? {}) },
@@ -256,12 +267,15 @@ export function Einstellungen({
setzeEntwurf(null) setzeEntwurf(null)
setzeErstzulassung(null) setzeErstzulassung(null)
setzePausenzeitEntwurf(null) setzePausenzeitEntwurf(null)
setzeGespeichert(true)
} finally { } finally {
setzeLaeuft(false) 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 /* Zugaenge: je Dienst ein Entwurf im Eingabefeld. Der gespeicherte Token
kommt NIE zurueck (siehe zugaenge.py), das Feld startet deshalb immer leer kommt NIE zurueck (siehe zugaenge.py), das Feld startet deshalb immer leer
und zeigt als Platzhalter, ob schon einer hinterlegt ist. */ und zeigt als Platzhalter, ob schon einer hinterlegt ist. */
@@ -394,6 +408,7 @@ export function Einstellungen({
className="dm-eingabe dm-eingabe--fahrzeug" className="dm-eingabe dm-eingabe--fahrzeug"
value={werte.fahrzeugtitel} value={werte.fahrzeugtitel}
onChange={(e) => aendern({ fahrzeugtitel: e.target.value })} onChange={(e) => aendern({ fahrzeugtitel: e.target.value })}
onBlur={() => sichern()}
/> />
</Feld> </Feld>
<Feld label="Kennzeichen"> <Feld label="Kennzeichen">
@@ -401,6 +416,7 @@ export function Einstellungen({
className="dm-eingabe dm-eingabe--fahrzeug" className="dm-eingabe dm-eingabe--fahrzeug"
value={werte.kennzeichen} value={werte.kennzeichen}
onChange={(e) => aendern({ kennzeichen: e.target.value })} onChange={(e) => aendern({ kennzeichen: e.target.value })}
onBlur={() => sichern()}
/> />
</Feld> </Feld>
<Feld label="Erstzulassung"> <Feld label="Erstzulassung">
@@ -409,6 +425,7 @@ export function Einstellungen({
placeholder="TT.MM.JJJJ" placeholder="TT.MM.JJJJ"
value={erstzulassungWert} value={erstzulassungWert}
onChange={(e) => setzeErstzulassung(e.target.value)} onChange={(e) => setzeErstzulassung(e.target.value)}
onBlur={() => sichern()}
/> />
</Feld> </Feld>
<Feld label="Ausführung" last> <Feld label="Ausführung" last>
@@ -416,6 +433,7 @@ export function Einstellungen({
className="dm-eingabe dm-eingabe--fahrzeug" className="dm-eingabe dm-eingabe--fahrzeug"
value={werte.ausfuehrung} value={werte.ausfuehrung}
onChange={(e) => aendern({ ausfuehrung: e.target.value })} onChange={(e) => aendern({ ausfuehrung: e.target.value })}
onBlur={() => sichern()}
/> />
</Feld> </Feld>
{/* „Setup — Sensoren zuordnen" bleibt dem Panel vorbehalten: die {/* „Setup — Sensoren zuordnen" bleibt dem Panel vorbehalten: die
@@ -700,7 +718,7 @@ export function Einstellungen({
<select <select
className="dm-auswahl" className="dm-auswahl"
value={werte.backupIntervall} value={werte.backupIntervall}
onChange={(e) => aendern({ backupIntervall: e.target.value })} onChange={(e) => sichern({ backupIntervall: e.target.value })}
> >
<option value="aus">Aus</option> <option value="aus">Aus</option>
<option value="taeglich">Täglich</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", "domain": "audi_dashboard",
"name": "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", "documentation": "https://gitea.nothaft.cloud/paul/audi-app/src/branch/main/README.md",
"issue_tracker": "https://gitea.nothaft.cloud/paul/audi-app/issues", "issue_tracker": "https://gitea.nothaft.cloud/paul/audi-app/issues",
"codeowners": [ "codeowners": [