Kein Rueckschritt mehr als Update, Version-Kachel schlanker

aktualisierung.py pruefte mit `remote_version != eigene_version` - reine
Ungleichheit ohne Richtung. Lief die Instanz einer Veroeffentlichung voraus,
bot die Integration an, sich auf die aeltere Fassung zu aktualisieren. Vom
Eigentuemer gemeldet. ist_neuer() ist jetzt das Gegenstueck zu
versionOrdnung() in appVersion.ts, wo derselbe Fehler am 04.09. behoben wurde:
fehlende Stellen zaehlen als 0, kein int() mit Vorabschnitt, und was sich
nicht in eine Reihenfolge bringen laesst, gilt als verfuegbar statt
verschwiegen. Sechs neue Testfaelle, 79 Python-Tests gruen.

Dazu auf Vorgabe des Eigentuemers: die Zeile benennt jetzt selbst, was sie
zeigt - "Home-Assistant-Integration | 2026.9.5.19" statt "Installiert" mit dem
Gegenstand klein darunter. Damit entfallen die Abschnitts-Ueberschriften aus
dem vorigen Commit, sie sagten dasselbe doppelt.

Live in beiden Oberflaechen gegengeprueft: Gitea auf .18, Instanz auf .19 -
gemeldet wird gruenes "aktuell" (--ok) mit Pruefzeitpunkt statt eines
Rueckschritts. 0 Tracebacks.

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
2026-09-05 11:33:56 +02:00
co-authored by Claude Opus 5
parent f693a41692
commit 5a65ec9590
8 changed files with 158 additions and 24 deletions
+57
View File
@@ -11797,3 +11797,60 @@ Die Gestaltung des zweiten Abschnitts gegengeprueft, indem einer eingesetzt
wurde: Haarlinie 0.8px, 16px Abstand und 16px Polster darueber, Abschnittskopf wurde: Haarlinie 0.8px, 16px Abstand und 16px Polster darueber, Abschnittskopf
14px ueber seinem Inhalt. Der Abschnitt selbst ist nur in der nativen Huelle zu 14px ueber seinem Inhalt. Der Abschnitt selbst ist nur in der nativen Huelle zu
sehen - im Browser ist `otaMoeglich()` falsch. sehen - im Browser ist `otaMoeglich()` falsch.
## CX. Die Integration bot Rueckschritte als Update an (2026.9.5.19)
Gemeldet: *"Warum wird eine aeltere Version angeboten? .16 ist aelter als
.18"*. In `aktualisierung.py` stand:
"verfuegbar": remote_version != eigene_version
Reine Ungleichheit, ohne Richtung. Lief die Instanz einer Veroeffentlichung
voraus - waehrend der Entwicklung der Normalfall -, bot die Integration an,
sich auf die AELTERE Fassung zu "aktualisieren".
Es ist derselbe Fehler, der am 04.09.2026 fuer die App behoben wurde
(`appVersion.ts`: *"erkannt werden soll eine NEUERE Fassung und keine
andere"*), nur auf der anderen Seite - und er blieb dort stehen, weil damals
nur die App angesehen wurde.
`ist_neuer()` ist jetzt das Gegenstueck zu `versionOrdnung()`, mit denselben
drei Eigenschaften:
* Fehlende Stellen zaehlen als 0, `2026.9.4` und `2026.9.4.0` sind derselbe
Stand.
* Kein `int()` mit Vorabschnitt - "4b" darf nicht klaglos als 4 gelesen werden,
sonst entsteht genau die scheingenaue Reihenfolge, die vermieden werden soll.
* Laesst sich keine Reihenfolge bilden (Formatwechsel), gilt die Fassung als
verfuegbar: die Oberflaeche sagt dann, dass etwas abweicht, aber nicht in
welche Richtung - besser als eine Aenderung zu verschweigen.
Sechs neue Faelle in `tests/aktualisierung/test_aktualisierung.py`, 79 Tests
gruen. Live gegengeprueft: Gitea auf .18, Instanz auf .19 - gemeldet wird
"aktuell" statt eines Rueckschritts.
### Die Kachel wurde dabei schlanker
Vorgabe des Eigentuemers: *"Statt Installiert 'Home-Assistant-Integration' zu
schreiben. Dann haette der User die Sicht: Was ist in welcher Version vorhanden
auf seinem Geraet."* Die Zeile benennt jetzt selbst, was sie zeigt:
Home-Assistant-Integration 2026.9.5.19
aktuell geprueft 05.09.2026, 11:32 Uhr
Damit entfallen auch die Abschnitts-Ueberschriften "Integration"/"App" aus
CW - sie sagten dasselbe ein zweites Mal. Die beiden Bloecke trennt weiterhin
eine Haarlinie, und benannt sind sie durch ihre eigene Zeile.
**"aktuell" erscheint nur nach einer Pruefung.** Vorher steht dort gar nichts:
gruen "aktuell" ohne Pruefung waere eine Behauptung ohne Grundlage, und der
Knopf "Auf Update pruefen" sagt den Zustand ohnehin. Es gibt keinen
automatischen Takt - geprueft wird nur auf Knopfdruck (nachgesehen, kein
`async_track_time_interval` dafuer).
Gemessen in beiden: Farbe `rgb(52,199,89)` bzw. `rgb(48,209,88)` - das ist
`--ok` im jeweiligen Thema.
**Offen, zur Entscheidung:** "Home-Assistant-Integration" bricht als Beschriftung
auf Telefonbreite in zwei Zeilen um. Lesbar, aber unruhig. "Integration" allein
wuerde einzeilig passen.
+7 -18
View File
@@ -748,20 +748,14 @@ export function Einstellungen({
<Tile> <Tile>
<span className="ads-eyebrow">Version</span> <span className="ads-eyebrow">Version</span>
{/* Die Zeile benennt selbst, WAS in welcher Fassung auf dem Geraet
liegt - vorher hiess das Label "Installiert" und der Gegenstand
stand klein darunter. Damit braucht der Abschnitt auch keine
Ueberschrift mehr: sie saegte dasselbe ein zweites Mal (Vorgabe
des Eigentuemers, 05.09.2026: "damit wird es nochmal schlanker"). */}
<div className="dm-kachelabschnitt"> <div className="dm-kachelabschnitt">
{/* Nur wenn darunter wirklich ein zweiter Abschnitt folgt. Ohne den
staende "Integration" allein unter "Version" - eine Unterteilung
ohne zweiten Teil, genau wie im Panel, das den App-Abschnitt gar
nicht kennt. */}
{otaMoeglich() && <span className="ads-eyebrow">Integration</span>}
<Werteliste <Werteliste
kinder={ kinder={<Wertzeile label="Home-Assistant-Integration" wert={serverVersion ?? "unbekannt"} />}
<Wertzeile
label="Installiert"
wert={serverVersion ?? "unbekannt"}
zusatz="Home-Assistant-Integration"
/>
}
/> />
{/* HIG "Loading": Netzwerk zu Gitea plus Entpacken dauert unbestimmt {/* HIG "Loading": Netzwerk zu Gitea plus Entpacken dauert unbestimmt
lang, ohne verlässliche Fortschrittsangabe - ein laufender lang, ohne verlässliche Fortschrittsangabe - ein laufender
@@ -865,12 +859,7 @@ export function Einstellungen({
{otaMoeglich() && ( {otaMoeglich() && (
<div className="dm-kachelabschnitt"> <div className="dm-kachelabschnitt">
<span className="ads-eyebrow">App</span> <Werteliste kinder={<Wertzeile label="App" wert={eigeneVersion() ?? "unbekannt"} />} />
<Werteliste
kinder={
<Wertzeile label="Installiert" wert={eigeneVersion() ?? "unbekannt"} zusatz="Diese App" />
}
/>
{otaUpdateVerfuegbar && otaBuendel ? ( {otaUpdateVerfuegbar && otaBuendel ? (
<> <>
<Werteliste <Werteliste
@@ -84,6 +84,54 @@ def _header(token: str) -> dict[str, str]:
return {"Authorization": f"token {token}"} return {"Authorization": f"token {token}"}
def _versionsteile(version: str) -> list[int] | None:
"""Zerlegt `2026.9.4.17` in [2026, 9, 4, 17].
`None`, sobald ein Teil keine reine Ziffernfolge ist - dann wird nicht
sortiert. Bewusst kein `int()` mit Vorabschnitt: das läse "4b" klaglos als
4 und erzeugte genau die scheingenaue Reihenfolge, die hier vermieden
werden soll. Wortgleich mit `teile()` in
companion-app/src/daten/appVersion.ts."""
if not version:
return None
zahlen: list[int] = []
for stueck in version.split("."):
if not stueck.isdigit():
return None
zahlen.append(int(stueck))
return zahlen
def ist_neuer(kandidat: str, bisher: str) -> bool | None:
"""Ob `kandidat` nachweislich neuer ist als `bisher`.
`None` heißt "nicht zu entscheiden" - verschieden, aber nicht in eine
Reihenfolge zu bringen (Formatwechsel). Fehlende Stellen zählen als 0,
damit `2026.9.4` und `2026.9.4.0` denselben Stand bezeichnen.
Gegenstück zu `versionOrdnung()` in appVersion.ts. Der Grund ist derselbe
und dort ausführlich vermerkt: bis zum 05.09.2026 stand hier
`remote_version != eigene_version`, also reine Ungleichheit ohne Richtung.
Lief die Instanz einer Veröffentlichung voraus - während der Entwicklung
der Normalfall -, bot die Integration an, sich auf die ÄLTERE Fassung zu
"aktualisieren". Vom Eigentümer am 05.09.2026 gemeldet: „Warum wird eine
ältere Version angeboten? .16 ist älter als .18"."""
if not kandidat or not bisher:
return None
if kandidat == bisher:
return False
links = _versionsteile(kandidat)
rechts = _versionsteile(bisher)
if links is None or rechts is None:
return None
for i in range(max(len(links), len(rechts))):
l = links[i] if i < len(links) else 0
r = rechts[i] if i < len(rechts) else 0
if l != r:
return l > r
return False
async def version_pruefen(hass: HomeAssistant, token: str, eigene_version: str) -> dict[str, Any]: async def version_pruefen(hass: HomeAssistant, token: str, eigene_version: str) -> dict[str, Any]:
"""Fragt nur die manifest.json von Gitea ab (ein kleiner Request, kein """Fragt nur die manifest.json von Gitea ab (ein kleiner Request, kein
Repo-Download) und vergleicht die Version gegen die installierte. Repo-Download) und vergleicht die Version gegen die installierte.
@@ -120,7 +168,11 @@ async def version_pruefen(hass: HomeAssistant, token: str, eigene_version: str)
raise AktualisierungsFehler(f"Antwort von Gitea nicht lesbar: {fehler}") from fehler raise AktualisierungsFehler(f"Antwort von Gitea nicht lesbar: {fehler}") from fehler
return { return {
"verfuegbar": remote_version != eigene_version, # Nur eine NACHWEISLICH neuere Fassung ist ein Update. Ist sie nicht
# in eine Reihenfolge zu bringen (None), gilt sie als verfuegbar -
# dann sagt die Oberflaeche, dass etwas abweicht, aber nicht in
# welche Richtung, statt eine Aenderung zu verschweigen.
"verfuegbar": ist_neuer(remote_version, eigene_version) is not False,
"version": remote_version, "version": remote_version,
"geprueft_am": datetime.datetime.now(datetime.timezone.utc).isoformat(), "geprueft_am": datetime.datetime.now(datetime.timezone.utc).isoformat(),
} }
@@ -1 +1 @@
{"version":"2026.9.5.18","sha256":"8b03fe0a871e5fc88b421146910832fbd73999ee9844000efbaf1b41a52ab603","bytes":324620,"gebaut":"2026-09-05T09:27:15Z"} {"version":"2026.9.5.19","sha256":"5490ca4a341da15dd210a13f84fb6941620d2974683b3c4280ea31ed87f0fb7c","bytes":324597,"gebaut":"2026-09-05T09:31:37Z"}
@@ -3719,7 +3719,7 @@ function vEinst() {
(Vorgabe des Eigentuemers, 04.09.2026). --> (Vorgabe des Eigentuemers, 04.09.2026). -->
<div class="tile"><span class="label">Version</span> <div class="tile"><span class="label">Version</span>
<dl class="rows"> <dl class="rows">
<div class="row" style="border-bottom:0"><dt>Installiert</dt><dd>${esc(APP_VERSION || "unbekannt")}<small>Home-Assistant-Integration</small></dd></div> <div class="row" style="border-bottom:0"><dt>Home-Assistant-Integration</dt><dd>${esc(APP_VERSION || "unbekannt")}</dd></div>
</dl> </dl>
${(() => { ${(() => {
const u = INTEGRATION_UPDATE; const u = INTEGRATION_UPDATE;
@@ -1,7 +1,7 @@
{ {
"domain": "audi_dashboard", "domain": "audi_dashboard",
"name": "Audi Dashboard", "name": "Audi Dashboard",
"version": "2026.9.5.18", "version": "2026.9.5.19",
"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": [
@@ -188,5 +188,41 @@ class AbgelehnteFaelle(unittest.TestCase):
self._unangetastet() self._unangetastet()
class VersionsOrdnung(unittest.TestCase):
"""Nur eine NACHWEISLICH neuere Fassung ist ein Update.
Bis zum 05.09.2026 stand in version_pruefen() `remote_version !=
eigene_version` - reine Ungleichheit ohne Richtung. Lief die Instanz einer
Veroeffentlichung voraus, bot die Integration an, sich auf die AELTERE
Fassung zu aktualisieren (vom Eigentuemer gemeldet: .16 wurde angeboten,
waehrend .18 lief)."""
def test_neuere_fassung_ist_ein_update(self):
self.assertIs(a.ist_neuer("2026.9.5.18", "2026.9.5.16"), True)
self.assertIs(a.ist_neuer("2026.10.1.1", "2026.9.5.99"), True)
self.assertIs(a.ist_neuer("2027.1.1.1", "2026.12.31.9"), True)
def test_aeltere_fassung_ist_keins(self):
self.assertIs(a.ist_neuer("2026.9.5.16", "2026.9.5.18"), False)
self.assertIs(a.ist_neuer("2026.9.4.26", "2026.9.5.1"), False)
def test_gleichstand_ist_keins(self):
self.assertIs(a.ist_neuer("2026.9.5.18", "2026.9.5.18"), False)
def test_fehlende_stellen_zaehlen_als_null(self):
self.assertIs(a.ist_neuer("2026.9.4", "2026.9.4.0"), False)
self.assertIs(a.ist_neuer("2026.9.4.1", "2026.9.4"), True)
def test_nicht_sortierbar_bleibt_offen(self):
# Formatwechsel: lieber "nicht entscheidbar" als eine erfundene
# Reihenfolge. "4b" darf nicht klaglos als 4 gelesen werden.
self.assertIsNone(a.ist_neuer("2026.9.4b", "2026.9.4.1"))
self.assertIsNone(a.ist_neuer("v2", "2026.9.5.1"))
self.assertIsNone(a.ist_neuer("", "2026.9.5.1"))
def test_zeichengleich_ist_gleich_auch_bei_fremdem_format(self):
self.assertIs(a.ist_neuer("beliebig", "beliebig"), False)
if __name__ == "__main__": if __name__ == "__main__":
unittest.main() unittest.main()