From a7006f9ef6337d5d5e119d01cb0f693536a852a9 Mon Sep 17 00:00:00 2001 From: cayossarian <23534755+cayossarian@users.noreply.github.com> Date: Thu, 30 Jul 2026 22:18:33 -0700 Subject: [PATCH 1/2] fix(build): drop the redundant wheel force-include that broke the add-on image MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The add-on Docker build failed on `pip install .`: ValueError: A second file is being added to the wheel archive at the same path: `span_panel_simulator/dashboard/static/dashboard.css` `packages = ["src/span_panel_simulator"]` already ships every file under the package, dashboard/templates and dashboard/static included. The `force-include` table re-added those two directories at the same destination paths, so hatchling was asked to write each of those 21 files into the wheel twice. Older hatchling tolerated it; the current one refuses. Nothing in this repo changed to trigger it — `requires = ["hatchling"]` is unpinned, and pip's build isolation resolves the backend fresh on every build, so the tolerant version this was authored against is gone for good. The duplicate was always wrong; the newer backend just stopped hiding it. Verified by building the wheel locally: it failed with the identical error before this change, succeeds after, and the built wheel still contains all 2 static assets and all 19 templates — matching the source tree exactly, so removing the force-include drops nothing. --- pyproject.toml | 11 +++++++---- span_panel_simulator/CHANGELOG.md | 2 ++ 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 95c7d7f..d2074cd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -39,12 +39,15 @@ dev = [ span-simulator = "span_panel_simulator.__main__:main" [tool.hatch.build.targets.wheel] +# `packages` already ships every file under src/span_panel_simulator, including +# dashboard/templates and dashboard/static. A `force-include` table re-adding +# those two directories at the same destination paths made hatchling write each +# file twice; newer hatchling rejects that outright ("A second file is being +# added to the wheel archive at the same path"), breaking `pip install .` in the +# add-on Docker build. Build isolation resolves `hatchling` fresh on every build, +# so the older, tolerant version this was authored against is not coming back. packages = ["src/span_panel_simulator"] -[tool.hatch.build.targets.wheel.force-include] -"src/span_panel_simulator/dashboard/templates" = "span_panel_simulator/dashboard/templates" -"src/span_panel_simulator/dashboard/static" = "span_panel_simulator/dashboard/static" - [tool.ruff] # Pinned to py313 because ruff 0.15.x has a formatter bug under py314 that # rewrites ``except (A, B):`` into the Python-2 ``except A, B:`` syntax. diff --git a/span_panel_simulator/CHANGELOG.md b/span_panel_simulator/CHANGELOG.md index 415967c..9b9f79d 100644 --- a/span_panel_simulator/CHANGELOG.md +++ b/span_panel_simulator/CHANGELOG.md @@ -20,6 +20,8 @@ - Panel startup no longer fails with `AttributeError: 'NoneType' object has no attribute 'get'`. The developer bootstrap script re-resolved the emitter's dependencies against PyPI instead of its lock file and pulled an `ebus-sdk` release incompatible with the `Device` API the emitter targets; setup now installs the emitter's locked dependency set +- Add-on image builds again. A redundant `force-include` in `pyproject.toml` re-added the dashboard templates and static assets that the package already + shipped, writing every one of them into the wheel twice; newer hatchling rejects the duplicate outright, failing `pip install .` in the Docker build - Stopping the simulator now clears each panel's retained MQTT topics on every stop path, not just Ctrl-C. `scripts/run-local.sh --stop`, Docker, and the Home Assistant supervisor all stop the process with SIGTERM, which was unhandled — the process died outright and its shutdown never ran, stranding a full retained topic tree on the broker with `$state` flipped to lost by the Last Will. A panel that was renamed or removed left those topics orphaned for good From 8de64a1af95bd13235d044f1f09a35a83894a8f9 Mon Sep 17 00:00:00 2001 From: cayossarian <23534755+cayossarian@users.noreply.github.com> Date: Thu, 30 Jul 2026 22:22:33 -0700 Subject: [PATCH 2/2] fix(build): move the add-on base image to Python 3.14 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Removing the duplicate wheel entry was necessary but not sufficient. The add-on base image is `base-python:3.13-alpine3.21`, while the project pinned `requires-python = ">=3.14"` (b195c86, dbd6ddd). The previous build never got far enough to report it — it died during metadata generation — but once the wheel builds, `pip install .` refuses it on 3.13: Because the current Python version (3.13.2) does not satisfy Python>=3.14 and span-panel-simulator==1.0.12 depends on Python>=3.14, we can conclude that span-panel-simulator==1.0.12 cannot be used. Reproduced against a real 3.13 interpreter, not inferred from metadata. Home Assistant publishes 3.14 base images for both build arches; confirmed `3.14-alpine3.23` exists for aarch64 and amd64 by querying the GHCR tag list. Bumping the base is the right side to change — the 3.14 pin is deliberate and recent, and relaxing it would undo that decision. Updated in all five places the old tag appeared: the build workflow matrix (both arches), the Dockerfile's BUILD_FROM default, build.yaml (both arches), and the DEVELOPER.md build example. Verified end to end: the wheel installs into a 3.14 venv, imports, reports version 1.0.12, and carries all 2 static assets and 19 templates. --- .github/workflows/build-addon.yaml | 4 ++-- DEVELOPER.md | 2 +- span_panel_simulator/CHANGELOG.md | 6 ++++-- span_panel_simulator/Dockerfile | 2 +- span_panel_simulator/build.yaml | 4 ++-- 5 files changed, 10 insertions(+), 8 deletions(-) diff --git a/.github/workflows/build-addon.yaml b/.github/workflows/build-addon.yaml index e767886..712734c 100644 --- a/.github/workflows/build-addon.yaml +++ b/.github/workflows/build-addon.yaml @@ -26,10 +26,10 @@ jobs: include: - arch: aarch64 platform: linux/arm64 - base: ghcr.io/home-assistant/aarch64-base-python:3.13-alpine3.21 + base: ghcr.io/home-assistant/aarch64-base-python:3.14-alpine3.23 - arch: amd64 platform: linux/amd64 - base: ghcr.io/home-assistant/amd64-base-python:3.13-alpine3.21 + base: ghcr.io/home-assistant/amd64-base-python:3.14-alpine3.23 steps: - uses: actions/checkout@v6 diff --git a/DEVELOPER.md b/DEVELOPER.md index 1bf77ff..75de245 100644 --- a/DEVELOPER.md +++ b/DEVELOPER.md @@ -95,7 +95,7 @@ networking), but the integration can connect by manual configuration. On Linux, ```bash # Build (use aarch64 base on Apple Silicon, amd64 on Intel/Linux) docker build -f span_panel_simulator/Dockerfile \ - --build-arg BUILD_FROM=ghcr.io/home-assistant/aarch64-base-python:3.13-alpine3.21 \ + --build-arg BUILD_FROM=ghcr.io/home-assistant/aarch64-base-python:3.14-alpine3.23 \ -t span-panel-simulator:local . # Run diff --git a/span_panel_simulator/CHANGELOG.md b/span_panel_simulator/CHANGELOG.md index 9b9f79d..1c265dc 100644 --- a/span_panel_simulator/CHANGELOG.md +++ b/span_panel_simulator/CHANGELOG.md @@ -20,8 +20,10 @@ - Panel startup no longer fails with `AttributeError: 'NoneType' object has no attribute 'get'`. The developer bootstrap script re-resolved the emitter's dependencies against PyPI instead of its lock file and pulled an `ebus-sdk` release incompatible with the `Device` API the emitter targets; setup now installs the emitter's locked dependency set -- Add-on image builds again. A redundant `force-include` in `pyproject.toml` re-added the dashboard templates and static assets that the package already - shipped, writing every one of them into the wheel twice; newer hatchling rejects the duplicate outright, failing `pip install .` in the Docker build +- Add-on image builds again. Two separate problems blocked it: a redundant `force-include` in `pyproject.toml` re-added the dashboard templates and static + assets the package already shipped, writing each into the wheel twice (newer hatchling rejects the duplicate outright); and the add-on base image was still + Python 3.13 while the project now requires 3.14, so the wheel could not install even once it built. The base image moves to + `base-python:3.14-alpine3.23` - Stopping the simulator now clears each panel's retained MQTT topics on every stop path, not just Ctrl-C. `scripts/run-local.sh --stop`, Docker, and the Home Assistant supervisor all stop the process with SIGTERM, which was unhandled — the process died outright and its shutdown never ran, stranding a full retained topic tree on the broker with `$state` flipped to lost by the Last Will. A panel that was renamed or removed left those topics orphaned for good diff --git a/span_panel_simulator/Dockerfile b/span_panel_simulator/Dockerfile index 26e9098..af5f165 100644 --- a/span_panel_simulator/Dockerfile +++ b/span_panel_simulator/Dockerfile @@ -1,4 +1,4 @@ -ARG BUILD_FROM=ghcr.io/home-assistant/aarch64-base-python:3.13-alpine3.21 +ARG BUILD_FROM=ghcr.io/home-assistant/aarch64-base-python:3.14-alpine3.23 FROM ${BUILD_FROM} # Install mosquitto, jq, and build tools needed for native Python extensions diff --git a/span_panel_simulator/build.yaml b/span_panel_simulator/build.yaml index d4e763a..badd1a2 100644 --- a/span_panel_simulator/build.yaml +++ b/span_panel_simulator/build.yaml @@ -1,3 +1,3 @@ build_from: - aarch64: ghcr.io/home-assistant/aarch64-base-python:3.13-alpine3.21 - amd64: ghcr.io/home-assistant/amd64-base-python:3.13-alpine3.21 + aarch64: ghcr.io/home-assistant/aarch64-base-python:3.14-alpine3.23 + amd64: ghcr.io/home-assistant/amd64-base-python:3.14-alpine3.23