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:
@@ -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.
|
||||||
|
|||||||
@@ -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"}
|
||||||
Binary file not shown.
@@ -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": [
|
||||||
|
|||||||
Reference in New Issue
Block a user