Refresh the phone weight from run_sync, where the PC's event-driven sync actually passes

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Krzysztof Rudnicki 2026-09-17 09:53:59 +02:00
parent 2ea5dcac52
commit 8e0a4bddab
6 changed files with 31 additions and 23 deletions

View File

@ -2,7 +2,7 @@ name: diet_guard_app
description: "Companion phone app for diet_guard: log meals on the go."
publish_to: 'none'
version: 1.0.84+1
version: 1.0.85+1
environment:
sdk: ^3.12.2

View File

@ -27,10 +27,6 @@ _LAZY_ATTRS = {
"run_sync": ("diet_guard._sync", "run_sync"),
"SyncError": ("diet_guard._sync", "SyncError"),
"RemoteSyncError": ("crdt_sync", "RemoteSyncError"),
"refresh_weight_from_phone": (
"diet_guard._phone_weight",
"refresh_weight_from_phone",
),
}
@ -82,7 +78,4 @@ def cmd_sync(emit: Callable[[str], None]) -> int:
return 1
total_entries = sum(len(entries) for entries in merged.values())
emit(f"synced: {total_entries} entries across {len(merged)} day(s).")
# After the merge, never before: the local record is then already the
# merged winner, so refreshing its ``t`` re-asserts nothing stale.
module.refresh_weight_from_phone(emit)
return 0

View File

@ -37,6 +37,7 @@ from diet_guard._constants import (
)
from diet_guard._device import device_identity
from diet_guard._foodbank_rebuild import rebuild_food_bank
from diet_guard._phone_weight import refresh_weight_from_phone
from diet_guard._state_sync import (
read_raw_log,
resign_entry,
@ -202,6 +203,11 @@ def run_sync() -> DayLog:
message="diet_guard sync: revision",
)
state_store.save(SyncState(pushed_rev=revision, peer_revs=seen_revs))
# Last, after the budget merge above: the local record is then already
# the merged winner, so the refreshed ``t`` re-asserts nothing stale.
# Here rather than in the ``sync`` subcommand because on the PC a full
# tick runs from ``publish_after_log``, never from that command.
refresh_weight_from_phone(lambda line: _logger.info("%s", line))
return resigned

View File

@ -162,6 +162,11 @@ def _isolate_state(tmp_path: Path) -> Iterator[None]:
"diet_guard._sync_client.CONFIG_FILE",
tmp_path / "nonexistent-firebase.json",
),
# Every full tick ends by reading the phone's weigh-in through
# `wake_alarm._weight`, which is installed here and reads the live
# Firebase database. `test_sync_budget` asserts the call; nothing
# else may pay for it.
patch("diet_guard._sync.refresh_weight_from_phone", return_value=None),
# Logging a meal now publishes immediately (`_sync_events`), so every
# test that logs one would otherwise open a real connection through a
# path that has nothing to do with what it is asserting. Patched at the

View File

@ -19,23 +19,9 @@ class TestCmdSync:
"2026-06-21": [{"id": "c"}],
}
lines: list[str] = []
with (
patch.object(_cli_sync, "run_sync", return_value=merged),
patch.object(_cli_sync, "refresh_weight_from_phone") as refresh,
):
with patch.object(_cli_sync, "run_sync", return_value=merged):
assert _cli_sync.cmd_sync(lines.append) == 0
assert lines == ["synced: 3 entries across 2 day(s)."]
# The phone's weigh-in is applied after the merge, through the same
# sink, so its one line lands under the sync summary.
refresh.assert_called_once_with(lines.append)
def test_a_failed_sync_does_not_touch_the_weight(self) -> None:
with (
patch.object(_cli_sync, "run_sync", side_effect=SyncError("no token")),
patch.object(_cli_sync, "refresh_weight_from_phone") as refresh,
):
assert _cli_sync.cmd_sync(lambda _line: None) == 1
refresh.assert_not_called()
def test_reports_sync_error_as_not_configured(self) -> None:
lines: list[str] = []

View File

@ -38,6 +38,24 @@ class TestSyncBudget:
pushed_paths = {call.args[0] for call in client.put_file_text.call_args_list}
assert f"diet-guard-sync/devices/{device_id()}/budget.json" in pushed_paths
def test_refreshes_the_weight_from_the_phone_after_the_merge(self) -> None:
"""The hook runs last, on every successful tick, into the sync log."""
_write_token()
write_budget(2000)
client = _mock_client(devices=())
with (
patch.object(_sync_client, "GitHubSyncClient", return_value=client),
patch.object(_sync, "refresh_weight_from_phone") as refresh,
):
_sync.run_sync()
refresh.assert_called_once()
(sink,) = refresh.call_args.args
with patch.object(_sync._logger, "info") as info:
sink("weight: 72.4 kg from the phone (2026-09-17).")
info.assert_called_once_with(
"%s", "weight: 72.4 kg from the phone (2026-09-17)."
)
def test_nothing_pushed_when_no_budget_ever_set(self) -> None:
"""An uninitialized device contributes nothing -- no push, no crash."""
_write_token()