Use the platform sync device id for HLC stamps, not a hardcoded 'phone'
Some checks are pending
pre-commit / pre-commit (push) Waiting to run
Tests / test (3.10) (push) Waiting to run
Tests / test (3.11) (push) Waiting to run
Tests / test (3.12) (push) Waiting to run
release-apk / release (APK) (push) Waiting to run

sync_merge.dart declared its own `const _syncDeviceId = 'phone'`, which was
unconditional -- so the desktop web build wrote records under
`diet-guard-sync/devices/desktop/` while every HLC string inside them ended
in `-phone`. The path segment came from the conditional-export constant, the
HLC did not.

Import `syncDeviceId` from github_client_factory.dart instead, so both halves
come from the same source: 'phone' on Android, 'desktop' on web, matching the
Python side's 'pc' in _constants.py.

Preventive, not a repair: there is no devices/desktop/ in the live RTDB yet,
so no already-written record has the mismatch. HLC tie-break is
nodeId.compareTo, so desktop stamps now sort below phone's rather than tying;
entryHlc is deterministic from entry.time and the bodies are identical for a
given entry id, so this changes which identical copy wins, never what data
survives.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GZH89WEqAjuJT8WZdqc7AP
This commit is contained in:
Krzysztof Rudnicki 2026-08-09 16:41:53 +02:00
parent fe28c8a395
commit 1328bece05

View File

@ -36,10 +36,9 @@ import 'package:crypto/crypto.dart';
import 'package:diet_guard_app/models/food_bank_record.dart';
import 'package:diet_guard_app/models/food_entry.dart';
import 'package:diet_guard_app/services/budget_schedule.dart';
import 'package:diet_guard_app/services/github_client_factory.dart';
import 'package:diet_guard_app/services/log_storage_service.dart';
const _syncDeviceId = 'phone';
/// Derives a deterministic [Hlc] for [entry] from its own `time` field.
///
/// The same entry always yields the same Hlc regardless of when this runs
@ -51,7 +50,7 @@ const _syncDeviceId = 'phone';
/// survives a merge.
Hlc entryHlc(FoodEntry entry) {
final wallTimeMs = DateTime.tryParse(entry.time)?.millisecondsSinceEpoch ?? 0;
return Hlc.newTick(_syncDeviceId, wallTimeMsOverride: wallTimeMs);
return Hlc.newTick(syncDeviceId, wallTimeMsOverride: wallTimeMs);
}
/// Deterministic id for a pre-`id` legacy entry, from `(time, desc)`.
@ -207,7 +206,7 @@ Log foodBankToLog(Map<String, FoodBankRecord> bank) {
'body': (
mapEntry.value.toJson(),
Hlc.newTick(
_syncDeviceId,
syncDeviceId,
wallTimeMsOverride: mapEntry.value.count.toInt(),
),
),
@ -271,7 +270,7 @@ Log manualBankToLog(Map<String, FoodBankRecord> bank) {
fields: {
'body': (
body,
Hlc.newTick(_syncDeviceId, wallTimeMsOverride: wallTimeMs),
Hlc.newTick(syncDeviceId, wallTimeMsOverride: wallTimeMs),
),
},
);
@ -338,7 +337,7 @@ Hlc budgetHlc(Map<String, dynamic> record) {
record['t'] as String? ?? '',
)?.toUtc().millisecondsSinceEpoch ??
0;
return Hlc.newTick(_syncDeviceId, wallTimeMsOverride: wallTimeMs);
return Hlc.newTick(syncDeviceId, wallTimeMsOverride: wallTimeMs);
}
/// Field-name prefix for the effective-from budget history, one field per
@ -356,7 +355,7 @@ const budgetHistoryFieldPrefix = 'hist:';
Hlc historyHlc(BudgetEntry entry) {
final wallTimeMs =
DateTime.tryParse(entry.editedAt)?.toUtc().millisecondsSinceEpoch ?? 0;
return Hlc.newTick(_syncDeviceId, wallTimeMsOverride: wallTimeMs);
return Hlc.newTick(syncDeviceId, wallTimeMsOverride: wallTimeMs);
}
/// Converts a raw local budget record plus its history into a [Log].