Skip to content

Commit c96709a

Browse files
committed
fix: Address remaining PR #262 review comments
Fix instance index desync in app_detail_page by deriving instance_index from the actual instance object instead of hardcoding 0. Add text color to .ht-detail-panel for proper contrast on dark background. Strengthen weak test assertions and add regression test for non-zero instance index.
1 parent 498e54a commit c96709a

File tree

4 files changed

+46
-5
lines changed

4 files changed

+46
-5
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1616
- Replaced hardcoded CSS fallback colors in alerts and detail panels with proper design tokens (`--ht-surface-inset`, `--ht-surface-code`, `--ht-warning-*`, `--ht-danger-*`)
1717
- Toggle buttons now show fallback text before Alpine.js initializes and expose `aria-expanded` for accessibility (#262)
1818

19+
### Fixed
20+
- App detail page now uses the actual instance index instead of hardcoded 0, fixing data/URL desync for non-zero instances (#262)
21+
- Detail panel labels now have proper text contrast on dark `--ht-surface-code` background (#262)
22+
1923
### Added
2024
- Global alert banner showing HA disconnect warnings and failed app errors with expandable tracebacks (#262)
2125
- `ht-btn--ghost` and `ht-btn--xs` button modifier classes (#262)

src/hassette/web/static/css/style.css

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1048,6 +1048,7 @@ html.ht-sidebar-closed .ht-sidebar:not(.is-open) {
10481048
.ht-detail-panel {
10491049
padding: var(--ht-sp-2) var(--ht-sp-4);
10501050
background: var(--ht-surface-code);
1051+
color: var(--ht-text-code);
10511052
border-radius: var(--ht-radius-sm);
10521053
margin: 0 var(--ht-sp-2) var(--ht-sp-2);
10531054
display: flex;
@@ -1075,6 +1076,11 @@ html.ht-sidebar-closed .ht-sidebar:not(.is-open) {
10751076
font-weight: 500;
10761077
}
10771078

1079+
.ht-detail-panel .ht-detail-label {
1080+
color: var(--ht-text-code);
1081+
opacity: 0.65;
1082+
}
1083+
10781084
/* — Responsive: small phones (max-width: 480px) ———————— */
10791085
@media screen and (max-width: 480px) {
10801086
.ht-section {

src/hassette/web/ui/router.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -97,17 +97,18 @@ async def app_detail_page(app_key: str, request: Request, data_sync: DataSyncDep
9797
raise HTTPException(status_code=404, detail=f"App '{app_key}' not found")
9898

9999
instance = manifest.instances[0] if manifest.instances else None
100+
instance_index = instance.index if instance else 0
100101
owner_id = instance.owner_id if instance else None
101-
listeners = data_sync.get_listener_metrics_for_instance(app_key, 0) if owner_id else []
102-
jobs = await data_sync.get_scheduled_jobs_for_instance(app_key, 0) if owner_id else []
102+
listeners = data_sync.get_listener_metrics_for_instance(app_key, instance_index) if owner_id else []
103+
jobs = await data_sync.get_scheduled_jobs_for_instance(app_key, instance_index) if owner_id else []
103104
logs = data_sync.get_recent_logs(app_key=app_key, limit=50)
104105
ctx = {
105106
**base_context("apps"),
106107
**alert_context(data_sync),
107108
"manifest": manifest,
108109
"instance": instance,
109110
"app_key": app_key,
110-
"instance_index": 0,
111+
"instance_index": instance_index,
111112
"owner_id": owner_id,
112113
"listeners": listeners,
113114
"jobs": jobs,

tests/integration/test_web_ui.py

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -671,6 +671,36 @@ async def test_instance_detail_404_bad_app(self, client: "AsyncClient") -> None:
671671
response = await client.get("/ui/apps/nonexistent/0")
672672
assert response.status_code == 404
673673

674+
async def test_default_page_uses_actual_instance_index(self, client: "AsyncClient", mock_hassette) -> None:
675+
"""GET /apps/{app_key} should use the actual instance index, not hardcoded 0."""
676+
setup_registry(
677+
mock_hassette,
678+
[
679+
make_manifest(
680+
app_key="offset_app",
681+
instance_count=1,
682+
instances=[
683+
AppInstanceInfo(
684+
app_key="offset_app",
685+
index=3,
686+
instance_name="OffsetApp[3]",
687+
class_name="OffsetApp",
688+
status=ResourceStatus.RUNNING,
689+
owner_id="OffsetApp.OffsetApp[3]",
690+
)
691+
],
692+
),
693+
],
694+
)
695+
response = await client.get("/ui/apps/offset_app")
696+
assert response.status_code == 200
697+
body = response.text
698+
# Partial URLs must reference the real instance index (3), not hardcoded 0
699+
assert "instance-listeners/offset_app/3" in body
700+
assert "instance-jobs/offset_app/3" in body
701+
assert "instance-listeners/offset_app/0" not in body
702+
assert "instance-jobs/offset_app/0" not in body
703+
674704
async def test_instance_listeners_partial(self, client: "AsyncClient") -> None:
675705
"""Instance-scoped listeners partial returns HTML."""
676706
response = await client.get("/ui/partials/instance-listeners/my_app/0")
@@ -933,7 +963,7 @@ async def test_alert_no_failed_apps(self, client: "AsyncClient") -> None:
933963
"""No failed apps means the partial renders empty (no alert)."""
934964
response = await client.get("/ui/partials/alert-failed-apps")
935965
assert response.status_code == 200
936-
assert "failed" not in response.text.lower() or "app(s) failed" not in response.text
966+
assert response.text.strip() == ""
937967

938968
async def test_alert_with_failed_app(self, client: "AsyncClient", mock_hassette) -> None:
939969
"""A failed app renders the danger alert with app key and error message."""
@@ -1064,7 +1094,7 @@ async def test_sidebar_brand_link(self, client: "AsyncClient") -> None:
10641094
async def test_sidebar_no_close_button(self, client: "AsyncClient") -> None:
10651095
response = await client.get("/ui/")
10661096
html = response.text
1067-
assert 'class="ht-sidebar-close"' not in html
1097+
assert not re.search(r"\bht-sidebar-close\b", html)
10681098
assert "fa-xmark" not in html
10691099

10701100
async def test_sidebar_toggle_button_exists(self, client: "AsyncClient") -> None:

0 commit comments

Comments
 (0)