Setup: Erwartungswert je Listenposition, gruen auch dort (2026.9.1.15)

Tuer- und Fenstersensoren haben vier Positionen, aber nur einen Beispielnamen
(front_left_door). Der beschreibt die erste; die anderen drei koennen ihn nie
enthalten. Folge: nur die erste Position konnte gruen werden, und im Kopf
stand ein Erwartungswert, der fuer drei von vier Zeilen nicht galt.

Neu "beispiele" je Position im Feld-Katalog und feldBeispiel(feld, idx) im
Panel. Der Erwartungswert steht jetzt an jeder Position statt im Kopf -
rechtsbuendig, in der kleinen grauen Schrift des Positionslabels. Auch die
Sortierung der Vorschlagsliste nutzt ihn, sonst stuende in allen vier Zeilen
dieselbe Entitaet oben.

Typografie: .setup-feld-hinweis ist fuer die Kopfzeile gebaut (14px, --fg,
rechtsbuendig, max-width 55%). Im 12,5px-Positionslabel wirkt das falsch herum.
Eine Regel im Kontext .setup-unterfeld-label .setup-feld-hinweis laesst Groesse
und Farbe erben; die Rechtsbuendigkeit kommt aus justify-content: space-between
am Label, wie eine Ebene darueber bei .setup-feld-kopf.

Verifiziert: alle acht Positionen tragen ihren eigenen Beispielnamen,
py_compile und Panel als Modul sauber, audi_ha_test ohne Fehler gestartet.
Die Darstellung selbst habe ich nicht im Browser gesehen - das Panel liess
sich in der Testinstanz nicht ansteuern.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-01 19:00:16 +02:00
parent 09c7461892
commit 7e2854fe8d
7 changed files with 115 additions and 19 deletions
+41 -1
View File
@@ -1,6 +1,7 @@
# AGENTS.md — Project state, review findings, open items, and working rules
**Last updated: 2026-09-01** (Rueckblick ohne Nullfahrten, Streckenwahl gemeinsam in `verlauf.py`,
**Last updated: 2026-09-01** (Setup: gruene Bestaetigung und Erwartungswert je Listenposition,
`2026.9.1.15`, Abschnitt BG. Davor: Rueckblick ohne Nullfahrten, Streckenwahl gemeinsam in `verlauf.py`,
`2026.9.1.14`, Abschnitt BF. Davor: Fahrtstrecke auf 100 m: CAN als Anker, GNSS als Nachkommastelle,
`2026.9.1.10`, Abschnitt BE. Davor: Nachlauf-Abzug nur mit zugeordnetem `TRIP_SENSOR`, `2026.9.1.9`;
davor „502" beim Neustart entschärft, OTA-Bündel auf `2026.9.1.8`
@@ -6879,6 +6880,45 @@ weglaesst.
innerhalb einer einzigen langen `trip_status`-Phase. Nachweisbar bleibt es an den 13 Faellen; live
zeigt es sich bei der naechsten kurzen Fahrt.
## BG. Setup: gruene Bestaetigung, Erwartungswert je Position (2026.9.1.11.15)
Zwei Wuensche des Eigentuemers vom 01.09.2026, die zusammengehoeren.
### Gruen, wenn die Zuordnung zum Beispielnamen passt
Enthaelt die zugeordnete Entitaet den Beispielnamen der Rolle, faerbt sich das Feld leicht gruen.
Dieselbe Pruefung, die `entitaetScore()` ohnehin mit 8 gewichtet - hier nur fuers Auge, damit man
eine richtig belegte Rolle sieht, ohne den langen Entity-Namen Zeichen fuer Zeichen mit dem Beispiel
daneben zu vergleichen.
Bewusst **nur ein Hinweis, keine Bedingung**: Installationen mit anderer Datenquelle haben andere
Namen und sind trotzdem richtig zugeordnet. Deshalb gruen als Bestaetigung, aber kein Rot und keine
Warnung, wenn es fehlt. Und nicht waehrend gesucht wird - dann steht die Sucheingabe im Feld, nicht
die Zuordnung. Die Farbe kommt per `color-mix` aus dem vorhandenen `--ok`-Ton und dem Feldgrund
statt aus einem eigenen Gruen, damit sie in beiden Themen ruhig bleibt.
`„Auf Standard zuruecksetzen"` ist im selben Zug entfallen - Knopf, Symbol, Zuhoerer, CSS und der
nur dafuer vorhandene Helfer `setupStandardwert()`.
### Listenfelder brauchen vier Beispiele, nicht eines
Tuer- und Fenstersensoren haben vier Positionen, aber nur **einen** Beispielnamen
(`front_left_door`). Der beschreibt die erste; die anderen drei koennen ihn nie enthalten. Folge:
nur die erste Position konnte gruen werden, und im Kopf stand ein Erwartungswert, der fuer drei von
vier Zeilen nicht galt.
Neu: `"beispiele"` je Position im Feld-Katalog, `feldBeispiel(feld, idx)` im Panel. Der
Erwartungswert steht jetzt **an jeder Position** statt im Kopf - rechtsbuendig, in der kleinen grauen
Schrift des Positionslabels. Auch die Sortierung der Vorschlagsliste nutzt ihn, sonst stuende in
allen vier Zeilen dieselbe Entitaet oben.
**Merkposten zur Typografie:** `.setup-feld-hinweis` ist fuer die Kopfzeile gebaut (14px, `--fg`,
rechtsbuendig, `max-width: 55%`). Im 12,5px-Positionslabel wirkt das falsch herum - groesser und
dunkler als die Zeile, in der es steht. Eine eigene Regel im Kontext
`.setup-unterfeld-label .setup-feld-hinweis` laesst es Groesse und Farbe erben; die Rechtsbuendigkeit
kommt aus `justify-content: space-between` am Label, wie eine Ebene darueber bei
`.setup-feld-kopf`.
## Working conventions (observed — keep them)
- German is the project language: identifiers, comments, commits, UI texts. Exceptions:
@@ -293,11 +293,26 @@ FELDER: list[dict] = [
"hinweis": "\"aus\"/off = zu. Für \"Sicher abgestellt\" wichtig.",
"domains": ["binary_sensor"], "device_classes": ["door"], "units": [], "liste": True, "positionen": POSITIONEN,
"pflicht": False, "beispiel": "front_left_door",
# Ein Beispielname je Position. "beispiel" allein beschreibt nur die
# erste; die anderen drei koennen "front_left_door" nie enthalten und
# standen deshalb ohne Erwartungswert da.
"beispiele": [
"front_left_door",
"front_right_door",
"rear_left_door",
"rear_right_door",
],
"stichworte": ["tür", "door"]},
{"key": "FENSTER_SENSOREN", "label": "Fenster", "gruppe": "sicherheit",
"hinweis": "\"aus\"/off = zu.",
"domains": ["binary_sensor"], "device_classes": ["window"], "units": [], "liste": True, "positionen": POSITIONEN,
"pflicht": False, "beispiel": "front_left_window",
"beispiele": [
"front_left_window",
"front_right_window",
"rear_left_window",
"rear_right_window",
],
"stichworte": ["fenster", "window"]},
{"key": "HECKKLAPPE_SENSOR", "label": "Heckklappe", "gruppe": "sicherheit",
"hinweis": "\"aus\"/off = zu.",
@@ -1 +1 @@
{"version":"2026.9.1.14","sha256":"f1fa3a91435bfc80f3f4245e52c488e3487fe12e5031349e1d7d20ca757d4b7d","bytes":265727,"gebaut":"2026-09-01T16:47:50Z"}
{"version":"2026.9.1.15","sha256":"c7eda6e1a85b2d4295b0bd21383b66895dfb217a5a788a6ccf92acee467cad4c","bytes":265741,"gebaut":"2026-09-01T16:59:16Z"}
@@ -3044,7 +3044,7 @@ function entitaetName(id) {
Domain/Einheit/Device-Class sind Zusatzpunkte fürs Sortieren, wenn der
Schalter "Nur passende Sensoren" aus ist (dann filtern sie nicht hart,
siehe entitaetKandidaten()). */
function entitaetScore(feld, e, suchtext) {
function entitaetScore(feld, e, suchtext, idx) {
let score = 0;
const hay = (e.id + " " + e.name).toLowerCase();
for (const wort of feld.stichworte || []) if (hay.includes(wort)) score += 5;
@@ -3054,12 +3054,15 @@ function entitaetScore(feld, e, suchtext) {
// Der Signalname aus dem Katalog ("z. B. can_fuel_volume") ist der
// treffsicherste Hinweis, den es gibt - deutlich mehr wert als ein
// einzelnes Stichwort.
if (feld.beispiel && hay.includes(feld.beispiel.toLowerCase())) score += 8;
// Bei Listenfeldern der Name der jeweiligen Position, nicht der der ersten -
// sonst stuende in allen vier Zeilen dieselbe Entitaet oben.
const beispiel = feldBeispiel(feld, idx);
if (beispiel && hay.includes(beispiel.toLowerCase())) score += 8;
if (suchtext && hay.includes(suchtext.trim().toLowerCase())) score += 10;
return score;
}
function entitaetKandidaten(feldKey, suchtext, aktuellerWert) {
function entitaetKandidaten(feldKey, suchtext, aktuellerWert, idx) {
const feld = setupKatalog().find((f) => f.key === feldKey);
if (!feld) return [];
const text = (suchtext || "").trim().toLowerCase();
@@ -3101,7 +3104,7 @@ function entitaetKandidaten(feldKey, suchtext, aktuellerWert) {
);
if (genau.length) liste = genau;
}
liste.sort((a, b) => entitaetScore(feld, b, suchtext) - entitaetScore(feld, a, suchtext));
liste.sort((a, b) => entitaetScore(feld, b, suchtext, idx) - entitaetScore(feld, a, suchtext, idx));
return liste.slice(0, 60);
}
@@ -3192,11 +3195,34 @@ function setupSchliessen() {
anderer Datenquelle, deren Namen nicht passen und die trotzdem richtig
zugeordnet sind. Deshalb grün als Bestätigung, aber kein Rot und keine
Warnung, wenn es fehlt. */
function entitaetPasstZumBeispiel(key, wert) {
function entitaetPasstZumBeispiel(key, idx, wert) {
if (!wert) return false;
const feld = setupKatalog().find((f) => f.key === key);
if (!feld || !feld.beispiel) return false;
return String(wert).toLowerCase().includes(String(feld.beispiel).toLowerCase());
const beispiel = feldBeispiel(setupKatalog().find((f) => f.key === key), idx);
if (!beispiel) return false;
return String(wert).toLowerCase().includes(String(beispiel).toLowerCase());
}
/* Der Beispielname für eine bestimmte Position einer Rolle.
Listenfelder (Tür- und Fenstersensoren, vier Positionen) haben je Position
einen eigenen Namen: die zweite Position kann "front_left_door" nie
enthalten. Ohne diese Unterscheidung stand nur bei der ersten Position ein
Erwartungswert, und nur sie konnte grün werden - vom Eigentümer beanstandet
am 01.09.2026. */
function feldBeispiel(feld, idx) {
if (!feld) return null;
if (idx != null && Array.isArray(feld.beispiele) && feld.beispiele[idx]) {
return feld.beispiele[idx];
}
return feld.beispiel || null;
}
/* Der Klammertext "(z. B. )" - im Kopf einer Rolle wie an einer
Listenposition derselbe Bau, damit beide gleich aussehen. */
function beispielHinweis(name) {
return name
? `<span class="setup-feld-hinweis">(z. B. <span class="setup-feld-einheit">${esc(name)}</span>)</span>`
: "";
}
/* Entity-IDs, die im Arbeitsstand mehr als einer Rolle zugeordnet sind. */
@@ -4513,7 +4539,11 @@ function feldEinheitAnzeige(feld) {
}
function entitaetZeilenMarkup(feldId, key, suchtext, aktuellerWert) {
const kandidaten = entitaetKandidaten(key, suchtext, aktuellerWert);
// Die Position steckt in der Feld-Kennung ("KEY:idx") - sie entscheidet,
// welcher Beispielname die Vorschlagsliste anfuehrt.
const teile = String(feldId).split(":");
const idx = teile.length === 2 ? Number(teile[1]) : null;
const kandidaten = entitaetKandidaten(key, suchtext, aktuellerWert, idx);
if (!kandidaten.length) return `<div class="entitaet-leer">Keine passende Entität gefunden</div>`;
return kandidaten.map((e) => {
const zustand = HASS.states[e.id] ? HASS.states[e.id].state : null;
@@ -4536,7 +4566,7 @@ function entitaetCombo(key, idx) {
// Waehrend gesucht wird nicht gruen faerben - der Text im Feld ist dann
// die Sucheingabe und nicht die Zuordnung, die Faerbung wuerde ihm
// widersprechen.
const treffer = !offen && entitaetPasstZumBeispiel(key, wert);
const treffer = !offen && entitaetPasstZumBeispiel(key, idx, wert);
return `<div class="entitaet-combo">
<span class="entitaet-combo-icon">${LUPE_SVG}</span>
<input type="text" class="entitaet-combo-input${treffer ? " passend" : ""}" autocomplete="off" spellcheck="false"
@@ -4570,12 +4600,13 @@ function setupFeldzeile(feld) {
Listenfelder bekommen es ebenfalls: dort ist der Name der ersten
Position das Muster, nach dem sich die anderen drei richten. */
const erwartet = feld.beispiel || "";
// Bei Listenfeldern steht das Beispiel NICHT im Kopf, sondern an jeder
// Position - siehe unten. Ein einzelner Name im Kopf beschriebe nur die
// erste der vier und liesse die anderen drei ohne Erwartungswert dastehen.
const erwartet = feld.liste ? "" : feld.beispiel || "";
// Der Klammertext in Label-Größe/-Farbe, der Sensorname darin in derselben
// kleinen/grauen Schrift wie die Einheit ("[on/off]").
const hinweis = erwartet
? `<span class="setup-feld-hinweis">(z. B. <span class="setup-feld-einheit">${esc(erwartet)}</span>)</span>`
: "";
const hinweis = beispielHinweis(erwartet);
const kopf = `<div class="setup-feld-kopf">
<span class="setup-feld-label">${esc(feld.label)}${einheit ? ` <span class="setup-feld-einheit">[${esc(einheit)}]</span>` : ""}</span>
${hinweis}
@@ -4585,7 +4616,7 @@ function setupFeldzeile(feld) {
<div class="setup-unterfelder">
${feld.positionen.map((pos, i) => `
<div class="setup-unterfeld">
<span class="setup-unterfeld-label">${esc(pos)}</span>
<span class="setup-unterfeld-label">${esc(pos)}${beispielHinweis(feldBeispiel(feld, i))}</span>
${entitaetCombo(feld.key, i)}
${entitaetStatusWarnung((setupZuordnung[feld.key] || [])[i])}
</div>`).join("")}
@@ -1378,7 +1378,17 @@ img.carfix.klein { aspect-ratio: auto; height: auto; }
}
.setup-unterfelder { display: flex; flex-direction: column; gap: 12px; }
.setup-unterfeld { display: flex; flex-direction: column; gap: 4px; }
.setup-unterfeld-label { font-size: 12.5px; color: var(--fg3); }
/* Positionsname links, Erwartungswert rechts - derselbe Bau wie
.setup-feld-kopf eine Ebene darueber, damit beide Zeilen dieselbe Kante
haben. Der Hinweis erbt hier die kleine graue Schrift des Labels; die
14px/--fg-Form von .setup-feld-hinweis passt nur in die Kopfzeile. */
.setup-unterfeld-label {
font-size: 12.5px; color: var(--fg3);
display: flex; align-items: baseline; justify-content: space-between; gap: 10px;
}
.setup-unterfeld-label .setup-feld-hinweis {
font-size: inherit; color: inherit; max-width: none; flex: 0 0 auto;
}
.entitaet-combo { position: relative; }
.entitaet-combo-icon {
@@ -1,7 +1,7 @@
{
"domain": "audi_dashboard",
"name": "Audi Dashboard",
"version": "2026.9.1.14",
"version": "2026.9.1.15",
"documentation": "https://gitea.nothaft.cloud/paul/audi-app/src/branch/main/README.md",
"issue_tracker": "https://gitea.nothaft.cloud/paul/audi-app/issues",
"codeowners": ["@paul"],