fix: web UI and src.common bugs (wifi wrong-password, plugin icon, starlark toggle, API caching, scroll/logo/font helpers) (#646)

- wifi: keep the "wrong_password:" prefix through the restore/AP fallback so
  the UI's incorrect-password prompt fires again.
- /plugins/installed returns the manifest's icon (string only).
- /starlark/apps/<id>/toggle coerces `enabled` and delegates to
  _toggle_starlark_app (disk before memory, no KeyError, "false" is false).
- /api/v3/ JSON GETs are sent Cache-Control: no-store; non-JSON keeps 5s.
- ScrollHelper.set_scrolling_image converts non-RGB input (alpha onto black);
  create/set_scrolling_image reset last_update_time like reset_scroll.
- LogoHelper backs off a failed download per path for
  MISSING_LOGO_RECHECK_SECONDS; cleared on invalidate/clear_cache.
- refresh_placeholder_timestamp saves atomically.
- FontManager.clear_cache / _clear_plugin_font_cache bump cache_generation.
- Odds manager: per-game logs to DEBUG; JSON decode error caught before
  RequestException (same cooldown).
- element_style mangled continuations; startup validator skips null plugin
  blocks and reuses the controller's discovery.
- src/common/README lists frame_timing, json_body, render_gate.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Chuck
2026-09-28 08:26:05 -04:00
committed by GitHub
co-authored by Claude Opus 5.5
parent b518c51679
commit 7eb7a58d0c
27 changed files with 764 additions and 64 deletions
+12
View File
@@ -356,6 +356,18 @@ class TestRefreshPlaceholderTimestamp:
def test_missing_file_is_not_an_error(self, tmp_path):
assert refresh_placeholder_timestamp(tmp_path / "nope.png") is False
def test_the_restamp_is_atomic(self, tmp_path):
# It used to save over the file in place, so a renderer opening the
# logo mid-write read a truncated PNG. A failed write must now leave
# the previous placeholder intact and no temp file behind.
assert LogoDownloader().create_placeholder_logo("COLL", str(tmp_path))
path = tmp_path / "COLL.png"
before = path.read_bytes()
with patch("src.logo_downloader.os.replace", side_effect=OSError("disk full")):
assert refresh_placeholder_timestamp(path) is False
assert path.read_bytes() == before
assert sorted(p.name for p in tmp_path.iterdir()) == ["COLL.png"]
class TestFailurePaths:
def test_a_team_without_logos_is_a_failed_download(self, tmp_path):