You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Addresses all reviewer + idiot-checker findings on the previous
unified-restart-flow commit. The intent stays the same: save commits
the change, the global Restart Add-on button is the single restart
path, the page reloads automatically when the addon comes back. The
implementation is now actually robust enough to deliver that.
Server
- ``_save_tools`` now returns the same
``{success, applied, mode, restart_required}`` shape as
``_save_feature_flags`` and ``_save_backup_config``. The previous
``disabled``/``pinned`` count fields had no consumers (JS read
none; no test asserted on them) and pre-empted the unified
contract — replaced with ``applied`` (the states dict) and
``mode: "file"``.
- ``_save_feature_flags`` file/default branch now also returns the
unified shape including ``applied`` (was a bare ``{"success":
True}`` before). Standalone / Docker / Claude Desktop users get
the same restart banner the addon-mode users get.
- ``_save_feature_flags`` docstring no longer claims the addon
branch schedules a restart; describes the new pending-then-explicit
flow.
- ``_save_backup_config`` docstring same fix.
- File-mode feature-flag comment no longer makes a grep-falsifiable
claim about FEATURE_META text; describes the semantic instead.
- "reported by the user" rot-prone phrasing in
``_save_feature_flags`` replaced with the enduring tech reason.
- ``_schedule_supervisor_self_restart`` now catches ``RuntimeError``
from ``make_supervisor_httpx_client`` (raised when
``SUPERVISOR_TOKEN`` is unset). Mirrors the parity catch the two
supervisor options helpers already had. Without it, a race that
unsets the token between handler entry and the 300ms-later task
wakeup would surface only as asyncio's "Task exception was never
retrieved" at GC time.
JS — restart flow
- 10-second fixed timer replaced with poll-until-ready against
``./api/settings/info``. Initial 3-second grace lets supervisor
actually kill the addon before we start probing (so a too-eager
first probe doesn't return 200 from the OLD instance and reload
too early); then poll every 2 seconds for up to 60 seconds.
Reloads as soon as the new instance answers; falls back to
"did not come back online — reload manually" if the probe
times out. Obsoletes the magic-number constant.
- Concurrency guard: module-level ``restartInProgress`` boolean
checked at function entry. Guards against DevTools / programmatic
invocation that bypass the button's ``disabled`` attribute.
Cleared only on a 4xx genuine config error so the user can fix
the underlying cause and retry; otherwise stays true through the
full restart cycle.
- ``restartAddon`` flattened to early-return on the 4xx config-error
branch — removes the ``configError`` flag.
- Optional-chaining for ``err?.error?.message`` (drops the verbose
nullish chain).
- Empty ``catch (_e) {}`` for the network-drop path now logs a
``console.warn`` so DevTools shows what happened — useful when
debugging "I clicked Restart and nothing happened" reports.
JS — multi-tab sync (BroadcastChannel)
- New ``restartChannel`` on ``BroadcastChannel('ha-mcp-settings')``.
Falls back to ``null`` on browsers without the API; every channel
use is null-guarded so older browsers see no regression.
- ``saveConfig`` / ``saveFeatureFlag`` / ``saveBackupConfig`` post
``{type: 'restart-required'}`` when their response carries
``restart_required: true``. Other open tabs surface the same
banner.
- ``restartAddon`` posts ``{type: 'restart-initiated'}`` BEFORE
starting its poll cycle. The listener kicks off the same
poll-then-reload cycle in every other tab so they all come back
to the fresh addon instead of dangling on a stale connection.
JS — quality of life
- ``saveFeatureFlag`` JSON-parse fallback: on ``resp.ok`` with a
truncated / non-JSON body, default to ``{restart_required:
true}`` so the banner still shows. Silently hiding the banner
would make the user think the change took effect live.
- ``saveBackupConfig`` no longer calls ``loadBackupConfig()`` after
a ``restart_required: true`` save. In addon mode the GET reads
env-derived values that are still stale (Supervisor has the new
options but ``start.py`` doesn't re-derive env vars until the
next boot) — reloading the form snapped it back to old values
and wiped any in-flight edits the user wanted to bundle.
Tests
- ``test_schedule_self_restart_catches_runtime_error`` pins the
RuntimeError parity catch with a caplog assertion targeting the
specific "SUPERVISOR_TOKEN unset" message.
- ``TestSaveFeatureFlagsStandaloneMode::test_standalone_save_returns_unified_contract_shape``
pins the new file-mode response shape.
- ``TestSaveToolsResponseShape::test_save_returns_unified_contract_shape``
pins the new unified shape on ``_save_tools`` and asserts the
retired ``disabled``/``pinned`` count fields do not leak through.
Verified: ruff check, ruff format, mypy src/, 88 passing in
test_settings_ui.py (1 pre-existing Windows skip). ``node --check``
on the rendered ``<script>`` still passes — confirms the
BroadcastChannel + polling code is syntactically valid JS.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
0 commit comments