Skip to content

Commit 4403132

Browse files
sergeykadSergey
andauthored
refactor(settings-ui): HA-faithful redesign, auto-save, dedicated package (#1695)
* refactor(settings-ui): HA-faithful redesign, auto-save, dedicated package - Move settings UI into src/ha_mcp/settings_ui/ package; extract markup, CSS and JS into settings.html/settings.css/settings.js loaded at import - Restyle controls to match the HA frontend (ha-switch, ha-button, ha-alert, filled fields); replace persistent status pills with an HA-style toast - Auto-save Server Settings fields (drop the Save buttons); keep the Backups and Tool-Security-Policies form Save buttons - Add read-only/writes/deletes capability badges via a shared categorize_capability(), the same classifier that routes the read/write/delete proxies, so badge and routing never disagree - Fix mid-save data loss in advanced auto-save: preserve pending edits across the post-save reload and guard against re-entrancy * fix(packaging): point PyInstaller spec and parity comments at settings_ui/ The settings UI assets moved into src/ha_mcp/settings_ui/; the PyInstaller spec still referenced the old src/ha_mcp/settings.js|css paths, failing every binary Build. Point the spec at the settings_ui/ dir, add settings.html (now a third mandatory asset), and bundle into ha_mcp/settings_ui. Update the stale src/ha_mcp/settings.* / settings_ui.py cross-reference comments in the docs site to the new paths. * fix(settings-ui): address Gemini review (toast timer race, a11y) - Clear the 200ms leave-removal timer when a toast is reused so a replace-on-new toast is not yanked from the DOM by the prior removal - Add aria-live (polite/assertive) to the toast alongside role so every screen reader announces the update - Drop the redundant aria-label on the theme <select> (the wrapping <label> with visible text already supplies the accessible name) - Promote panel-server and panel-accessibility section titles from h3 to h2 so the heading hierarchy no longer skips a level (styling is class-controlled, no visual change) - Add a regression test for the toast removal-timer race and assert aria-live --------- Co-authored-by: Sergey <sergey@example.com>
1 parent 9a8e274 commit 4403132

23 files changed

Lines changed: 2119 additions & 1444 deletions

AGENTS.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -344,7 +344,8 @@ On merge, `hotfix-release.yml` runs semantic-release, creates GitHub release, sy
344344
### Setup
345345
```bash
346346
uv sync --group dev # Install with dev dependencies
347-
uv run ha-mcp # Run MCP server
347+
uv run ha-mcp # Run MCP server (stdio; needs interactive stdin)
348+
uv run ha-mcp-web # Run HTTP server; web settings UI at http://localhost:8086/mcp/settings (see src/ha_mcp/settings_ui/AGENTS.md)
348349
cp .env.example .env # Configure HA connection
349350
```
350351

@@ -379,6 +380,8 @@ Test token centralized in `tests/test_constants.py`.
379380
### Code Quality
380381
```bash
381382
uv run ruff check src/ tests/ --fix
383+
# Note: --fix removes unused imports from non-__init__ modules (lefthook runs it on commit with
384+
# stage_fixed). When adding an import, include its first use in the same change or it gets stripped.
382385
uv run mypy src/
383386
```
384387

MANIFEST.in

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
include README.md
22
include LICENSE
3-
include src/ha_mcp/settings.js
4-
include src/ha_mcp/settings.css
3+
include src/ha_mcp/settings_ui/settings.html
4+
include src/ha_mcp/settings_ui/settings.js
5+
include src/ha_mcp/settings_ui/settings.css
56
recursive-include src/ha_mcp/resources *.md *.json

packaging/binary/ha-mcp.spec

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -37,16 +37,15 @@ for dir_name in stdlib_dirs:
3737
if os.path.exists(dir_path):
3838
datas.append((dir_path, dir_name))
3939

40-
# settings_ui.py reads settings.js and settings.css via Path(__file__).parent
41-
# at import, so a missing file would build a working-looking but broken binary.
42-
# collect_all('ha_mcp') below also picks them up via package-data, but add them
43-
# explicitly and WITHOUT an existence guard: both are mandatory, so let the
44-
# build fail loudly if either is absent rather than ship a broken binary
40+
# settings_ui/__init__.py reads settings.html, settings.js and settings.css via
41+
# Path(__file__).parent at import, so a missing file would build a working-looking
42+
# but broken binary. collect_all('ha_mcp') below also picks them up via package-data,
43+
# but add them explicitly and WITHOUT an existence guard: all three are mandatory, so
44+
# let the build fail loudly if any is absent rather than ship a broken binary
4545
# (PyInstaller dedups the duplicate datas entries).
46-
_settings_js = os.path.join(PROJECT_ROOT, 'src', 'ha_mcp', 'settings.js')
47-
datas.append((_settings_js, 'ha_mcp'))
48-
_settings_css = os.path.join(PROJECT_ROOT, 'src', 'ha_mcp', 'settings.css')
49-
datas.append((_settings_css, 'ha_mcp'))
46+
_settings_ui_dir = os.path.join(PROJECT_ROOT, 'src', 'ha_mcp', 'settings_ui')
47+
for _asset in ('settings.html', 'settings.js', 'settings.css'):
48+
datas.append((os.path.join(_settings_ui_dir, _asset), 'ha_mcp/settings_ui'))
5049

5150
binaries = []
5251
hiddenimports = []

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ package-dir = {"" = "src", "tests" = "tests"}
5454
packages = { find = { where = ["src", "."], include = ["ha_mcp*", "tests"] } }
5555

5656
[tool.setuptools.package-data]
57-
ha_mcp = ["py.typed", "_pypi_marker", "settings.js", "settings.css", "resources/skills-vendor/**/*"]
57+
ha_mcp = ["py.typed", "_pypi_marker", "settings_ui/settings.html", "settings_ui/settings.js", "settings_ui/settings.css", "resources/skills-vendor/**/*"]
5858

5959
[tool.mypy]
6060
python_version = "3.13"

site/src/layouts/Layout.astro

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ const { title } = Astro.props;
2525
// from localStorage with safe defaults. Auto is the default (#1574
2626
// review: follow the OS preference out of the box); Dark stays one
2727
// click away as a preset.
28-
// Same logic as in src/ha_mcp/settings_ui.py head — both surfaces use
28+
// Same logic as in src/ha_mcp/settings_ui/__init__.py head — both surfaces use
2929
// the same localStorage key names interpreted identically, but storage
3030
// is per-origin, so each surface keeps its own saved values. Any change
3131
// here must be mirrored there (and vice versa) or the surfaces drift
@@ -111,7 +111,7 @@ const { title } = Astro.props;
111111
// attributes. The Layout head anti-FOUC script already set the initial
112112
// attributes; this module keeps them in sync for the rest of the
113113
// session and persists user changes. The block from `const PREFS` to
114-
// `const APPLY` must stay logically identical to src/ha_mcp/settings.js
114+
// `const APPLY` must stay logically identical to src/ha_mcp/settings_ui/settings.js
115115
// (comments and formatting may differ) — enforced by
116116
// tests/src/unit/test_anti_fouc_parity.py.
117117
(function bindAccessibilityPrefs() {

site/src/styles/global.css

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -323,7 +323,7 @@ fieldset.a11y-options { border: 0; padding: 0; margin: 0; min-inline-size: auto;
323323
/* Preset chips (#1574 review): one-click theme presets modeled on Firefox
324324
Reader View's Colors menu. Each chip shows a miniature swatch of its
325325
actual scheme — fixed preview colors by design. Mirrors the same chip
326-
palette in src/ha_mcp/settings.css. */
326+
palette in src/ha_mcp/settings_ui/settings.css. */
327327
.a11y-preset-chip {
328328
width: 20px; height: 20px; border-radius: 5px; display: inline-flex;
329329
align-items: center; justify-content: center; font-size: 0.65rem; font-weight: 600;
@@ -355,7 +355,7 @@ fieldset.a11y-options { border: 0; padding: 0; margin: 0; min-inline-size: auto;
355355
.a11y-reset:hover { background: rgb(var(--surface-2)); color: var(--text-primary); }
356356

357357
/* Light-mode background shade picker (#1572). Mirrors the same selectors
358-
used in src/ha_mcp/settings.css so the docs site and the settings UI
358+
used in src/ha_mcp/settings_ui/settings.css so the docs site and the settings UI
359359
honor the same localStorage shade key. Only the page background flips —
360360
surfaces (cards, glass-card) keep their tuned palette. */
361361
:root[data-theme="light"][data-shade="paper"] body.body-background { background-color: #f4ede0; }

0 commit comments

Comments
 (0)