From 8836453f8c0b7cf11185ccd81f3eba1fe67fa07e Mon Sep 17 00:00:00 2001 From: Krzysztof kuhy Rudnicki Date: Sun, 23 Aug 2026 09:08:54 +0200 Subject: [PATCH] fix: initialise the catering queue at startup `KuchniaQueueService` had no `init()` call in `main()`, so on a real device the whole feature would have done nothing: every entry point checks `isInitialized` first and returns quietly, which reads as "no delivery today" rather than as an error. All 694 tests passed throughout, because widget tests initialise the service directly. Caught by grepping main.dart before the phone deploy, not by the suite. So it is now a repo invariant: `repo_invariants_test.dart` asserts main() names both catering `init()` calls, and the assertion was checked against a main.dart with the line deleted. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01QQpCiPaSbyRpwsLkH3dgsX --- app/lib/main.dart | 2 ++ app/lib/services/kuchnia_queue.dart | 20 +++++++++++++++++++- app/pubspec.yaml | 2 +- app/test/repo_invariants_test.dart | 22 ++++++++++++++++++++++ 4 files changed, 44 insertions(+), 2 deletions(-) diff --git a/app/lib/main.dart b/app/lib/main.dart index e5967dd..85accc0 100644 --- a/app/lib/main.dart +++ b/app/lib/main.dart @@ -10,6 +10,7 @@ import 'package:diet_guard_app/services/budget_history_service.dart'; import 'package:diet_guard_app/services/foodbank_service.dart'; import 'package:diet_guard_app/services/frame_stats.dart'; import 'package:diet_guard_app/services/kuchnia_credential_service.dart'; +import 'package:diet_guard_app/services/kuchnia_queue.dart'; import 'package:diet_guard_app/services/log_storage_service.dart'; import 'package:diet_guard_app/services/meal_schedule_service.dart'; import 'package:diet_guard_app/services/notification_service.dart'; @@ -28,6 +29,7 @@ Future main() async { await MealScheduleService.init(); await FoodBankService.init(); await KuchniaCredentialService.init(); + await KuchniaQueueService.init(); final notifications = await NotificationService.init(); // Deliberately *not* awaited before the first frame: the browser's // `Notification.requestPermission()` does not complete until the user diff --git a/app/lib/services/kuchnia_queue.dart b/app/lib/services/kuchnia_queue.dart index be15cc0..197212b 100644 --- a/app/lib/services/kuchnia_queue.dart +++ b/app/lib/services/kuchnia_queue.dart @@ -23,6 +23,7 @@ library; import 'package:diet_guard_app/models/kuchnia_dish.dart'; import 'package:diet_guard_app/services/document_store.dart'; +import 'package:diet_guard_app/services/document_store_factory.dart'; import 'package:diet_guard_app/services/kuchnia_import.dart'; import 'package:diet_guard_app/services/kuchnia_orders.dart' show isoDay; import 'package:flutter/foundation.dart'; @@ -60,7 +61,24 @@ class KuchniaQueueService { /// How many dishes are still waiting to be logged. static int get remaining => _instance?._pending.length ?? 0; - /// Initialises from [store], for tests and for [init]. + /// Initialises the singleton against the platform document store. + /// + /// Without this the queue silently no-ops on a real device: every entry + /// point checks [isInitialized] first, so a missing call reads as "no + /// delivery" rather than as an error. + static Future init() async { + if (_instance != null) return _instance!; + // Resolving the platform store is a plugin call, not reachable from + // `flutter test`. + // coverage:ignore-start + final service = KuchniaQueueService._(await openDocumentStore()); + // coverage:ignore-end + await service._load(); + _instance = service; + return service; + } + + /// Initialises from [store], for tests. @visibleForTesting static Future initForTesting(DocumentStore store) async { final service = KuchniaQueueService._(store); diff --git a/app/pubspec.yaml b/app/pubspec.yaml index 5a58530..89fd722 100644 --- a/app/pubspec.yaml +++ b/app/pubspec.yaml @@ -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.64+1 +version: 1.0.65+1 environment: sdk: ^3.12.2 diff --git a/app/test/repo_invariants_test.dart b/app/test/repo_invariants_test.dart index e0c40e7..b025eb0 100644 --- a/app/test/repo_invariants_test.dart +++ b/app/test/repo_invariants_test.dart @@ -125,6 +125,28 @@ void main() { }); }); + group('singletons that silently no-op are initialised at startup', () { + // Every catering entry point checks `isInitialized` first and returns + // quietly when it is false, so a missing `init()` does not throw -- it + // reads as "no delivery" and "no credential" forever. That is invisible in + // widget tests, which initialise these directly, and was caught only by + // grepping main.dart before a phone deploy. + test('main.dart initialises the catering services', () { + final source = File('${_appDir.path}/lib/main.dart').readAsStringSync(); + for (final call in [ + 'KuchniaCredentialService.init()', + 'KuchniaQueueService.init()', + ]) { + expect( + source.contains(call), + isTrue, + reason: '$call is missing from main(). The feature will silently do ' + 'nothing on a real device while every test still passes.', + ); + } + }); + }); + group('the desktop wrapper origin is fixed', () { // IndexedDB is keyed by origin and lives in the Chrome profile. Changing // either the port or the --user-data-dir hides the entire local food log