From ca253d1e6cdcf1299bb495dcb7103d873af9926c Mon Sep 17 00:00:00 2001 From: Otto Date: Wed, 17 Jun 2026 11:53:25 +0200 Subject: [PATCH] Fix dashboard text overflow and close v1 docs gaps --- CHANGELOG.md | 8 ++++++++ addon/config.yaml | 2 +- app/main.py | 2 +- app/static/index.html | 18 +++++++++++------- docs/V1_0_0_OPERATING_GUIDE.md | 15 +++++++++++++++ docs/V1_0_ACCEPTANCE.md | 10 ++++++---- pyproject.toml | 2 +- tests/api/test_actuators.py | 21 +++++++++++++++++++++ 8 files changed, 64 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 844637e..c5bf971 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## 1.0.5 - 2026-06-17 +- Lange Friendly Names, Entity-IDs, Chips, Tabellenwerte und Metriken brechen + im Dashboard responsiv um und laufen nicht mehr aus Karten oder Boxen. +- Automatisierter Performance-Budget-Test fuer Root-HTML und + `/v1/actuators/dashboard` gegen das 5-Sekunden-Limit ergaenzt. +- HA-/Ingress-Verifikation mit Supervisor-Status, Backup, Watchdog, + Hard-Reload und Rollback im Operating Guide dokumentiert. + ## 1.0.4 - 2026-06-17 - Sensor-Relevanz ist in der Aktor-Detailansicht sichtbar: automatische Relevanz, aktive Gewichtung und Score werden pro verwendetem Sensor/Zustand diff --git a/addon/config.yaml b/addon/config.yaml index ada1e9e..f545320 100644 --- a/addon/config.yaml +++ b/addon/config.yaml @@ -1,5 +1,5 @@ name: SillyHome Next -version: "1.0.4" +version: "1.0.5" slug: sillyhome_next description: Lernt automatisch aus deinem Verhalten und steuert freigegebene Aktoren url: http://192.168.6.31:3000/pino/sillyhome-next diff --git a/app/main.py b/app/main.py index 2d1d3d0..1680a39 100644 --- a/app/main.py +++ b/app/main.py @@ -105,7 +105,7 @@ async def lifespan(app: FastAPI) -> AsyncIterator[None]: app = FastAPI( title="SillyHome Next API", description="Lokales Smart-Home-Intelligenzsystem für Home Assistant.", - version="1.0.4", + version="1.0.5", lifespan=lifespan, ) app.state.settings = load_settings() diff --git a/app/static/index.html b/app/static/index.html index 0237e15..d84db09 100644 --- a/app/static/index.html +++ b/app/static/index.html @@ -25,6 +25,7 @@ --bad:#8fb8ff; } * { box-sizing:border-box; } + html, body { max-width:100%; overflow-x:hidden; } body { margin:0; font-size:15px; background:var(--panel-quiet); } h1,h2,h3 { margin:0 0 10px; letter-spacing:0; } p { margin:6px 0; } @@ -76,26 +77,29 @@ button.secondary { background:var(--complement-soft); color:#dff6ff; border:1px solid #22607c; } button.danger { background:#2a3441; color:#f2f6fb; border:1px solid #536273; } button.compact { width:auto; min-width:112px; margin-right:8px; padding:8px 10px; min-height:36px; } - table { width: 100%; border-collapse: collapse; font-size: .92rem; } - td,th { padding: 8px; border-bottom: 1px solid #2d3a47; text-align: left; vertical-align: top; } + table { width: 100%; border-collapse: collapse; table-layout:fixed; font-size: .92rem; } + td,th { padding: 8px; border-bottom: 1px solid #2d3a47; text-align: left; vertical-align: top; overflow-wrap:anywhere; word-break:break-word; } ul { margin: 8px 0; padding-left: 18px; } .notice { border-left:4px solid var(--complement); padding-left:10px; } .grid-two { display:grid; grid-template-columns:repeat(auto-fit,minmax(240px,1fr)); gap:12px; } .chips { display:flex; flex-wrap:wrap; gap:6px; margin-top:8px; } - .chip { padding:4px 8px; border-radius:8px; background:#222b36; border:1px solid var(--border); font-size:.85rem; } + .chip { padding:4px 8px; border-radius:8px; background:#222b36; border:1px solid var(--border); font-size:.85rem; max-width:100%; overflow-wrap:anywhere; word-break:break-word; } .muted { color:var(--text-soft); } .card-list { display:grid; grid-template-columns:repeat(auto-fit,minmax(250px,1fr)); gap:10px; } .actuator-card { background:#121922; border:1px solid var(--border); border-radius:8px; padding:10px; min-width:0; } .actuator-card.selected { border-color:var(--complement); box-shadow:0 0 0 1px rgba(28,199,255,.35); } - .card-title { display:flex; justify-content:space-between; gap:10px; align-items:flex-start; margin-bottom:8px; } - .entity-id { overflow-wrap:anywhere; font-weight:800; } + .card-title { display:flex; flex-wrap:wrap; justify-content:space-between; gap:10px; align-items:flex-start; margin-bottom:8px; min-width:0; } + .card-title > div { min-width:0; flex:1 1 160px; overflow-wrap:anywhere; word-break:break-word; } + .card-title .chip { flex:0 1 auto; white-space:normal; text-align:center; } + .actuator-card strong { overflow-wrap:anywhere; word-break:break-word; } + .entity-id { overflow-wrap:anywhere; word-break:break-word; font-weight:800; } .metric-grid { display:grid; grid-template-columns:repeat(auto-fit,minmax(120px,1fr)); gap:6px; margin:8px 0; } - .metric { background:var(--panel-soft); border:1px solid var(--border); border-radius:8px; padding:8px; min-width:0; } + .metric { background:var(--panel-soft); border:1px solid var(--border); border-radius:8px; padding:8px; min-width:0; overflow-wrap:anywhere; word-break:break-word; } .metric strong { display:block; margin-bottom:4px; color:#cfe0ec; font-size:.84rem; } .actions { display:flex; flex-wrap:wrap; gap:8px; margin-top:10px; } .actions button { flex:1 1 180px; margin-top:0; } .detail-header { display:flex; justify-content:space-between; gap:12px; align-items:flex-start; flex-wrap:wrap; } - .manual-context { margin-top:12px; background:#121922; border:1px solid var(--border); border-radius:8px; padding:10px; } + .manual-context { margin-top:12px; background:#121922; border:1px solid var(--border); border-radius:8px; padding:10px; min-width:0; overflow-wrap:anywhere; word-break:break-word; } .inline-controls { display:grid; grid-template-columns:repeat(auto-fit,minmax(160px,1fr)); gap:8px; margin:8px 0; } .manual-entry { min-height:80px; resize:vertical; } textarea { width:100%; border-radius:8px; border:1px solid #3b4b5b; padding:12px; background:#101820; color:#fff; font:inherit; } diff --git a/docs/V1_0_0_OPERATING_GUIDE.md b/docs/V1_0_0_OPERATING_GUIDE.md index 28503f4..bfea854 100644 --- a/docs/V1_0_0_OPERATING_GUIDE.md +++ b/docs/V1_0_0_OPERATING_GUIDE.md @@ -101,6 +101,21 @@ wget -qO /tmp/summary.json http://58adbe1e-sillyhome-next:8000/v1/actuators/summ wget -qO /tmp/dashboard.json http://58adbe1e-sillyhome-next:8000/v1/actuators/dashboard ``` +Wenn der Add-on-Container aus dem Agent-Host nicht direkt routbar ist, gilt der +Home-Assistant-Supervisor als Verifikationsquelle: + +- Add-on-Info pruefen: Version, `version_latest`, `update_available`, `state`, + `boot` und `watchdog`. +- Vor Updates eine Home-Assistant-Teil-Sicherung fuer **SillyHome Next** + erstellen. +- Nach einem Store-Reload und Update muss `version == version_latest`, + `update_available == false`, `state == started`, `boot == auto` und + `watchdog == true` gelten. +- Den HA-/Ingress-Tab nach jedem Update hart neu laden, weil Home Assistant + sonst alte HTML-/JavaScript-Ressourcen aus dem bestehenden Tab verwenden kann. +- Rollback erfolgt ueber die vorherige Add-on-Teil-Sicherung oder den letzten + Git-Tag; beide Referenzen im Release-/Abnahmeprotokoll notieren. + ## Rollback Der stabile Vor-1.0-Stand ist `v0.7.21`. Vor dem 1.0.0-Umbau wurde ein diff --git a/docs/V1_0_ACCEPTANCE.md b/docs/V1_0_ACCEPTANCE.md index 5065287..02250dd 100644 --- a/docs/V1_0_ACCEPTANCE.md +++ b/docs/V1_0_ACCEPTANCE.md @@ -44,6 +44,12 @@ expliziter Freigabe. - `ruff check .` - `mypy app backend tests` - `git diff --check` +- Performance-Budget: + - Automatisierter Test prueft Root-HTML und `/v1/actuators/dashboard` gegen + das 5-Sekunden-Budget mit kontrollierten Fake-HA-/Cache-Daten. +- HA-/Ingress-Verifikation: + - Supervisor-Update, Add-on-Status, Watchdog, Backup, Ingress-Hard-Reload + und Rollback sind im Operating Guide dokumentiert. ## Teilweise Erfuellt @@ -62,14 +68,10 @@ expliziter Freigabe. ## Offen Fuer v1.0.x -- Echte Dashboard-Performance-Budget-Tests, die Start-HTML und - `/v1/actuators/dashboard` gegen ein 5-Sekunden-Limit messen. - Dashboard-Jobstatus fuer Reconciliation, Training, Discovery und Automation-Refresh. - Mehr Entscheidungsstatistik pro Aktor: welche Sensoren wie stark beigetragen haben, wie sich Confidence und Sample Count entwickeln. -- Dokumentierte HA-Installationspruefung mit Supervisor-/Ingress-Hinweisen, - weil direkte Container-HTTP-Pruefung ausserhalb HA nicht immer routbar ist. ## Rollback diff --git a/pyproject.toml b/pyproject.toml index 3fd7721..78500c9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "sillyhome-next" -version = "1.0.4" +version = "1.0.5" description = "Lokales Smart-Home-Intelligenzsystem für Home Assistant" requires-python = ">=3.11" dependencies = [ diff --git a/tests/api/test_actuators.py b/tests/api/test_actuators.py index 2458720..2c2668f 100644 --- a/tests/api/test_actuators.py +++ b/tests/api/test_actuators.py @@ -1,5 +1,6 @@ from __future__ import annotations +from time import perf_counter from datetime import datetime, timedelta from pathlib import Path @@ -299,6 +300,26 @@ def test_dashboard_overview_uses_cache_without_ha_roundtrip(tmp_path: Path) -> N assert payload["discovery_groups"] +def test_dashboard_start_path_stays_within_five_second_budget(tmp_path: Path) -> None: + with TestClient(app) as client: + _install_service(tmp_path) + client.get("/v1/actuators/discovery") + client.post("/v1/actuators", json={"actuator_entity_id": "light.abstellkammer"}) + + root_started_at = perf_counter() + root_response = client.get("/") + root_elapsed = perf_counter() - root_started_at + + dashboard_started_at = perf_counter() + dashboard_response = client.get("/v1/actuators/dashboard") + dashboard_elapsed = perf_counter() - dashboard_started_at + + assert root_response.status_code == 200 + assert dashboard_response.status_code == 200 + assert root_elapsed < 5.0 + assert dashboard_elapsed < 5.0 + + def test_discovery_reads_entities_once_and_reuses_them(tmp_path: Path) -> None: with TestClient(app) as client: _install_service(tmp_path)