The evidence this fixes: v2026.10.09 was published while master's manifest still
read 2026.10.08, so HACS compared every install against a version that never
moved and offered nobody that update. A second release in the same UTC day was
also a silent no-op: the date tag already existed, the run resolved to the same
version again, and it stopped at "already tagged".
* scripts/determine-release-version.sh owns the decision, so it is tested rather
than hoped for: the manifest version while it is untagged (bump it to force a
release); else today's UTC date; else YYYY.MM.DD.N with the LOWEST untagged
N >= 2 (a gap is reused, not burned). An already-released commit, and any
version whose tag exists, are an explicit RELEASE=no instead of a failure. The
script only ever reads the clock with `date -u`, and it emits
VERSION/TAG/SOURCE/BUMP/RELEASE on stdout and into $GITHUB_OUTPUT.
* the workflow writes the resolved version into the manifest whenever it differs,
commits it as "chore(release): v<VERSION> [skip ci]" with the existing bot
identity, pushes HEAD:master, and only then tags THAT commit — so the artifact,
its tag and the default branch agree. A branch-protection rejection fails the
job with an instruction to bump the manifest manually; nothing is tagged first.
* post-push verification reads the manifest out of origin/master (the copy HACS
fetches) and refuses to publish when it does not equal the released version.
* no self-trigger loop: the conventional [skip ci] marker on the bot commit plus a
job-level `if:` that re-checks github.event.head_commit.message. The guard is the
load-bearing part — it holds even where a Gitea version ignores the marker.
* preserved: manifest-wins precedence, the concurrency group without
cancel-in-progress, notes from the commit range, zip asset upload and the
409-tolerant publish path. RELEASE=no replaces the old `exists` output, so an
already-tagged version still ends as a clean no-op.
Also fixes the data: master's manifest moves 2026.10.08 -> 2026.10.09, the latest
released tag, so the repo stops contradicting its own releases immediately rather
than only after the next successful run (which resolves to v2026.10.11 either way).
tests/test_release_versioning.py drives all of it: the four scenarios, the
lowest-free-suffix rule, UTC determinism under three timezones, the error paths,
the $GITHUB_OUTPUT handoff, and the workflow's own contract — textual, because the
fast suite has no YAML parser; the verify command parses the file with pyyaml.
Verified: 356 passed / 2 skipped, 357 / 1 with androidtvremote2, ruff clean,
release.yml valid, `bash -n` clean, and the script resolves the real repo to
v2026.10.11 (date, bump=yes). The workflow itself cannot run here (no Gitea
runner): its run: steps were extracted from the YAML and executed against scratch
repos with a bare origin, covering the bump+verify+tag path (commit subject, tag
target and tag manifest all agree), the inconsistent-manifest failure and the
rejected-push failure.
HA requires domain, name, then alphabetical. Previous file had
issue_tracker before iot_class; alphabetically iot_class (i-o-t) sorts
before issue_tracker (i-s-s). Reordering unblocks hassfest.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
So ha-media-zone's card can interpolate the playhead smoothly between
server polls instead of snapping once every 10-30s.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Home Assistant custom integration for NVIDIA Shield TV that composes
the Android TV Remote, Jellyfin, smart plug, and receiver entities
into a single unified media player with ADB-based now-playing from
any app. Part of the Media Zone ecosystem.
Entities:
- media_player: power (with smart plug fallback), transport, input,
volume routed to receiver, rich stream info overlay when Jellyfin
is active
- sensor: DAG attributes and stream info
- button: Pair ADB
Config:
- Two-step flow: basic setup + optional ADB pairing on the Shield
- Configurable ADB port (default 5555)
- Pick an existing Jellyfin media_player entity — stream info is read
from the HA Jellyfin integration's own DataUpdateCoordinator, so
no server URL / API key / extra config needed here
- Privacy mode (adb_store_app_info=False) suppresses non-Jellyfin
media details, showing only the app name
Ecosystem:
- Exposes media_dag_* attributes for ha-media-zone orchestration
- Exposes media_dag_running_apps and async_bring_to_foreground for
the Media Zone card's app switcher
Shared ADB code lives in ltomes/ha-adb-common and is vendored at
custom_components/nvidia_shield/ha_adb_common/ for HACS installs.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>