fix: resolve comprehensive review findings (security, concurrency, perf, Android, UI)

Multi-dimension review of v0.8.2. Excludes the deliberately deferred
default_config.yaml weak-default-key item (C1).

Backend:
- calibration: create_default_calibration no longer exceeds led_count for small
  odd counts (bounded trim + regression test)
- game-integration: generic webhook now requires auth_token; constant-time
  compare_digest in all adapters; per-IP failed-auth rate limit on the ingest
  route; auth_token encrypted at rest via secret_box (migration-safe)
- playlist engine: serialize _state/_task under the lifecycle lock to close a
  delete-mid-play race (+ concurrency tests)
- main: stop the calibration session on shutdown (restore prior target)
- home_assistant: validate HA host via the LAN classifier on create/update
- perf: drop slow preview-WS clients instead of blocking the send loop; cache
  composite full-strip resize linspaces; effect_stream lava reuses scratch

Frontend:
- setup/auto-calibration wizard: guard _state after awaits (cancel-safe), await
  session teardown before output start, busy-gate skip-calibration, manual
  display input keeps focus, move focus on step change
- calibration: destroy EntitySelect on modal close
- color-strips test: dirty-flag-gated render + cached ctx/ImageData
- a11y/TV: focus-visible for new wizard/auto-cal/corner controls, aria-labels on
  the spatial corner/edge picker; theme-aware syntax tokens; dead/undefined CSS
  tokens removed; .modal-error styled; i18n titles (en/ru/zh)

Android:
- ApiKeyManager: EncryptedSharedPreferences with verified, data-safe legacy
  migration that never rotates an existing key
- CaptureService: validate MediaProjection token before promoting; satisfy the
  startForeground 5s contract on the bail path
- NotificationListener: connection-scoped executor with lazy fallback
- BLE: request BLUETOOTH_SCAN/CONNECT at runtime + guard handler-thread
  SecurityExceptions
- Root: cancellation-aware su grant probe

Adds 14 tests. Gate: ruff + tsc 5.9.3 + esbuild + pytest (2185 passed) +
compileDebugKotlin all green.
This commit is contained in:
2026-06-09 16:35:08 +03:00
parent 7a12f39f49
commit 17dd2e02ba
42 changed files with 1358 additions and 152 deletions
+16
View File
@@ -291,6 +291,22 @@ def test_create_default_calibration_small_count():
assert config.get_total_leds() == 4
@pytest.mark.parametrize("led_count", [4, 5, 6, 7, 9, 11, 13, 60, 144, 300])
def test_create_default_calibration_total_matches_count(led_count):
"""Total LED count must equal led_count exactly, with every edge >= 1.
Regression for small odd counts where the per-edge max(1, ...) floor used
to push the total above led_count (e.g. led_count=5 produced a total of 6).
"""
config = create_default_calibration(led_count)
assert config.get_total_leds() == led_count
assert config.leds_top >= 1
assert config.leds_right >= 1
assert config.leds_bottom >= 1
assert config.leds_left >= 1
def test_create_default_calibration_invalid():
"""Test default calibration with invalid LED count."""
with pytest.raises(ValueError):