fix: tolerate UTF-8 BOM in manifest; sync docs to app/app.old model

#1 BOM: PowerShell Set-Content -Encoding UTF8 writes a BOM that made the in-app
json.loads silently fail (banner never showed even with an update available).
- update_service: decode manifest with utf-8-sig (HTTP + local)
- build.ps1: write manifest.json without BOM (UTF8Encoding $false)
- tests: +2 covering BOM manifests (HTTP + local)

#2 docs: docs/10 §16 status synced to the implemented app/app.old/version.txt +
SHA-256 model (stages 2/3 done + e2e verified); tasks 17.18 updated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-18 10:19:03 +08:00
co-authored by Claude Opus 4.8
parent ba8daaccdd
commit 54e42670f7
5 changed files with 34 additions and 7 deletions
+17
View File
@@ -98,6 +98,14 @@ class TestCheckForUpdate(unittest.TestCase):
self._write_manifest({"notes": "no version here"})
self.assertIsNone(check_for_update(str(self.tmp), "1.0.0"))
def test_local_manifest_with_utf8_bom(self):
# PowerShell Set-Content -Encoding UTF8 emits a BOM; must still parse.
with open(str(self.tmp / "manifest.json"), "w", encoding="utf-8-sig") as f:
json.dump({"version": "1.1.0"}, f)
info = check_for_update(str(self.tmp), "1.0.0")
self.assertIsInstance(info, UpdateInfo)
self.assertEqual(info.version, "1.1.0")
def test_source_falls_back_to_update_source(self):
self._write_manifest({"version": "2.0.0"}) # no "source" field
info = check_for_update(str(self.tmp), "1.0.0")
@@ -168,6 +176,15 @@ class TestCheckForUpdate(unittest.TestCase):
with patch("services.update_service.request.urlopen", fake_urlopen):
self.assertIsNone(check_for_update("https://example.test", "1.0.0"))
def test_http_manifest_with_utf8_bom(self):
def fake_urlopen(req, timeout=0):
return _FakeResponse('{"version": "2.0.0"}', raw=True)
with patch("services.update_service.request.urlopen", fake_urlopen):
info = check_for_update("https://example.test", "1.0.0")
self.assertIsInstance(info, UpdateInfo)
self.assertEqual(info.version, "2.0.0")
class _FakeResponse:
def __init__(self, data, raw=False):