feat: roadmap batch (2026-06-19) — solar/linear-light/dither/nanoleaf + integrations

Eight roadmap features from the 2026-06-19 review, each a full vertical
(backend + tests + frontend + i18n en/ru/zh); ~67 new unit tests:

- automations: SolarRule sunrise/sunset trigger (new utils/solar.py, shared
  with the daylight cycle; window logic mirrors TimeOfDayRule)
- ci: best-effort arm64 multi-arch Docker manifest via QEMU + docker manifest
  (release.yml; amd64 path untouched, continue-on-error)
- game-integration: wire the orphaned LoLPoller via a LoLPollManager + a shared
  runtime_state module (poll lifecycle on enable/CRUD/startup/shutdown)
- ui: color-harmony gradient generator (complementary/analogous/triadic/...)
- effects: audio-reactive palette modulation (new audio_energy_tap; brightness/
  saturation modulation across all 12 procedural effects)
- capture: linear-light blending + spatio-temporal dithering, opt-in per
  calibration (new utils/linear_light.py, utils/dither.py)
- devices: Nanoleaf extControl v2 per-panel UDP streaming (per_panel mode)

Also bundles the pending 2026-06-18 production-review fixes and other
in-progress work already in the working tree (manual-trigger rule, etc.),
since they share files and could not be cleanly separated.

Gate: ruff + tsc clean; pytest 2654 passed / 2 skipped. The single failing
test (automation manual_trigger handler coverage) is a separate in-progress
item owned elsewhere, intentionally left as-is.
This commit is contained in:
2026-06-22 23:21:24 +03:00
parent 126d8f2449
commit 6745e25b20
91 changed files with 4390 additions and 540 deletions
+52
View File
@@ -354,6 +354,58 @@ jobs:
docker push "$REGISTRY:latest"
fi
# Best-effort arm64 (Raspberry Pi / arm64 HAOS hosts). Runs AFTER the
# amd64 push so the amd64 image always ships even if this fails.
# Deliberately avoids `docker buildx` (its docker-container driver needs
# nested networking the TrueNAS runners lack — see contexts/ci-cd.md):
# instead it cross-builds a single arm64 image via QEMU binfmt and folds
# amd64 + arm64 into multi-arch manifest lists under the existing tags.
# `continue-on-error` keeps a runner that can't emulate arm64 from
# failing the release; the plain amd64 tags pushed above remain valid.
- name: Build + publish arm64 (multi-arch manifest, best-effort)
if: github.event_name == 'push' && steps.docker-login.outcome == 'success'
continue-on-error: true
run: |
set -e
export DOCKER_CLI_EXPERIMENTAL=enabled
TAG="${{ gitea.ref_name }}"
REGISTRY="${{ steps.meta.outputs.registry }}"
VERSION="${{ steps.meta.outputs.version }}"
# Register arm64 emulation. If the runner forbids privileged
# containers this fails and the whole step is skipped.
docker run --privileged --rm tonistiigi/binfmt --install arm64
# Cross-build the arm64 image (QEMU-emulated — slow but uses arm64
# manylinux wheels, so no source compilation). Stays in the local
# daemon alongside the amd64 image from the previous build step.
DOCKER_BUILDKIT=1 docker build \
--platform linux/arm64 \
--build-arg APP_VERSION="$VERSION" \
--label "org.opencontainers.image.version=$VERSION" \
--label "org.opencontainers.image.revision=${{ gitea.sha }}" \
-t "$REGISTRY:$VERSION-arm64" \
./server
# Fold amd64 + arm64 into a multi-arch manifest list under each
# user-facing tag. The arch-suffixed tags remain pullable directly.
publish_manifest() {
local t="$1"
docker tag "$REGISTRY:$t" "$REGISTRY:$t-amd64"
docker push "$REGISTRY:$t-amd64"
docker tag "$REGISTRY:$VERSION-arm64" "$REGISTRY:$t-arm64"
docker push "$REGISTRY:$t-arm64"
docker manifest create --amend "$REGISTRY:$t" \
"$REGISTRY:$t-amd64" "$REGISTRY:$t-arm64"
docker manifest push "$REGISTRY:$t"
}
publish_manifest "$TAG"
publish_manifest "$VERSION"
if ! echo "$TAG" | grep -qE '(alpha|beta|rc)'; then
publish_manifest "latest"
fi
# ── Publish the release (flip draft=false) ─────────────────
# Runs only after every build job succeeded so users never see a
# release that's missing artifacts or sha256 sidecars (the in-app