Compare commits

...

1 Commits

Author SHA1 Message Date
ca253d1e6c Fix dashboard text overflow and close v1 docs gaps
Some checks failed
quality / test (3.11) (push) Has been cancelled
quality / test (3.13) (push) Has been cancelled
2026-06-17 11:53:25 +02:00
8 changed files with 64 additions and 14 deletions

View File

@@ -1,5 +1,13 @@
# Changelog # 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 ## 1.0.4 - 2026-06-17
- Sensor-Relevanz ist in der Aktor-Detailansicht sichtbar: automatische - Sensor-Relevanz ist in der Aktor-Detailansicht sichtbar: automatische
Relevanz, aktive Gewichtung und Score werden pro verwendetem Sensor/Zustand Relevanz, aktive Gewichtung und Score werden pro verwendetem Sensor/Zustand

View File

@@ -1,5 +1,5 @@
name: SillyHome Next name: SillyHome Next
version: "1.0.4" version: "1.0.5"
slug: sillyhome_next slug: sillyhome_next
description: Lernt automatisch aus deinem Verhalten und steuert freigegebene Aktoren description: Lernt automatisch aus deinem Verhalten und steuert freigegebene Aktoren
url: http://192.168.6.31:3000/pino/sillyhome-next url: http://192.168.6.31:3000/pino/sillyhome-next

View File

@@ -105,7 +105,7 @@ async def lifespan(app: FastAPI) -> AsyncIterator[None]:
app = FastAPI( app = FastAPI(
title="SillyHome Next API", title="SillyHome Next API",
description="Lokales Smart-Home-Intelligenzsystem für Home Assistant.", description="Lokales Smart-Home-Intelligenzsystem für Home Assistant.",
version="1.0.4", version="1.0.5",
lifespan=lifespan, lifespan=lifespan,
) )
app.state.settings = load_settings() app.state.settings = load_settings()

View File

@@ -25,6 +25,7 @@
--bad:#8fb8ff; --bad:#8fb8ff;
} }
* { box-sizing:border-box; } * { box-sizing:border-box; }
html, body { max-width:100%; overflow-x:hidden; }
body { margin:0; font-size:15px; background:var(--panel-quiet); } body { margin:0; font-size:15px; background:var(--panel-quiet); }
h1,h2,h3 { margin:0 0 10px; letter-spacing:0; } h1,h2,h3 { margin:0 0 10px; letter-spacing:0; }
p { margin:6px 0; } p { margin:6px 0; }
@@ -76,26 +77,29 @@
button.secondary { background:var(--complement-soft); color:#dff6ff; border:1px solid #22607c; } button.secondary { background:var(--complement-soft); color:#dff6ff; border:1px solid #22607c; }
button.danger { background:#2a3441; color:#f2f6fb; border:1px solid #536273; } 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; } 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; } 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; } 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; } ul { margin: 8px 0; padding-left: 18px; }
.notice { border-left:4px solid var(--complement); padding-left:10px; } .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; } .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; } .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); } .muted { color:var(--text-soft); }
.card-list { display:grid; grid-template-columns:repeat(auto-fit,minmax(250px,1fr)); gap:10px; } .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 { 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); } .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; } .card-title { display:flex; flex-wrap:wrap; justify-content:space-between; gap:10px; align-items:flex-start; margin-bottom:8px; min-width:0; }
.entity-id { overflow-wrap:anywhere; font-weight:800; } .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-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; } .metric strong { display:block; margin-bottom:4px; color:#cfe0ec; font-size:.84rem; }
.actions { display:flex; flex-wrap:wrap; gap:8px; margin-top:10px; } .actions { display:flex; flex-wrap:wrap; gap:8px; margin-top:10px; }
.actions button { flex:1 1 180px; margin-top:0; } .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; } .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; } .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; } .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; } textarea { width:100%; border-radius:8px; border:1px solid #3b4b5b; padding:12px; background:#101820; color:#fff; font:inherit; }

View File

@@ -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 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 ## Rollback
Der stabile Vor-1.0-Stand ist `v0.7.21`. Vor dem 1.0.0-Umbau wurde ein Der stabile Vor-1.0-Stand ist `v0.7.21`. Vor dem 1.0.0-Umbau wurde ein

View File

@@ -44,6 +44,12 @@ expliziter Freigabe.
- `ruff check .` - `ruff check .`
- `mypy app backend tests` - `mypy app backend tests`
- `git diff --check` - `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 ## Teilweise Erfuellt
@@ -62,14 +68,10 @@ expliziter Freigabe.
## Offen Fuer v1.0.x ## 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 - Dashboard-Jobstatus fuer Reconciliation, Training, Discovery und
Automation-Refresh. Automation-Refresh.
- Mehr Entscheidungsstatistik pro Aktor: welche Sensoren wie stark - Mehr Entscheidungsstatistik pro Aktor: welche Sensoren wie stark
beigetragen haben, wie sich Confidence und Sample Count entwickeln. 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 ## Rollback

View File

@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project] [project]
name = "sillyhome-next" name = "sillyhome-next"
version = "1.0.4" version = "1.0.5"
description = "Lokales Smart-Home-Intelligenzsystem für Home Assistant" description = "Lokales Smart-Home-Intelligenzsystem für Home Assistant"
requires-python = ">=3.11" requires-python = ">=3.11"
dependencies = [ dependencies = [

View File

@@ -1,5 +1,6 @@
from __future__ import annotations from __future__ import annotations
from time import perf_counter
from datetime import datetime, timedelta from datetime import datetime, timedelta
from pathlib import Path 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"] 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: def test_discovery_reads_entities_once_and_reuses_them(tmp_path: Path) -> None:
with TestClient(app) as client: with TestClient(app) as client:
_install_service(tmp_path) _install_service(tmp_path)