diet-guard/app
Krzysztof kuhy Rudnicki 688bd7b1de
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
Finish the three sync bugs: gate, SIGKILL flag, and silent catches
Auditing the whole fleet for the bugs found on the desktop turned up three
that were still live here.

1. `--virtual-time-budget=45000` was still in the provisioning args. The
   previous commit added the comment explaining why it must not be there but
   left the flag itself, so headless Chrome kept SIGKILLing itself (exit -9)
   and could still truncate a sync. Actually removed now; a provisioning run
   exits 0.

2. `backgroundSyncPush` gated on `settings.isConfigured` -- "has a GitHub
   token" -- so the WorkManager background push was skipped entirely on a
   device connected only to Firebase. Same defect as _autoSync's, one layer
   down. Either backend now counts.

3. Three swallowed exceptions, all in the sync path: the in-isolate config
   read, the background push itself, and `loadAccount()`. The last one is the
   worst -- it hid *why* provisioning failed, which is indistinguishable from
   "no account set" until it says so, and cost real debugging time this
   session. All log at warning/error with the stack trace and stay non-fatal,
   per the repo's no-silent-failures rule.

Verified after the change, not just compiled: provisioning exits 0 and the
desktop (c891b813) still holds 359 food_log records, identical to the PC
daemon's, 0 missing.

522 tests, analyzer and pre-commit clean.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0177xwzcpfpWG2qca8Mr39q9
2026-08-09 22:28:18 +02:00
..
.agents/skills Fix pre-existing CI failures (deps, lint, whitespace) 2026-07-10 23:55:56 +02:00
android Build and publish a signed APK on every push to main 2026-08-04 20:55:14 +02:00
assets/icon app: warm-tint the shared palette, fix Snack chip + on-fill contrast 2026-07-21 21:16:26 +02:00
bin Finish the three sync bugs: gate, SIGKILL flag, and silent catches 2026-08-09 22:28:18 +02:00
desktop app: package the desktop app as the web build, and delete the Linux embedder 2026-07-20 16:32:24 +02:00
lib Finish the three sync bugs: gate, SIGKILL flag, and silent catches 2026-08-09 22:28:18 +02:00
test Make the sync-account route testable, and cover every branch 2026-08-09 21:49:28 +02:00
web app: run the companion app's desktop target as a web build 2026-07-20 16:31:32 +02:00
.gitignore Add Flutter companion app skeleton with local meal logging 2026-06-22 18:22:42 +02:00
.metadata app: run the companion app's desktop target as a web build 2026-07-20 16:31:32 +02:00
analysis_options.yaml Add Flutter companion app skeleton with local meal logging 2026-06-22 18:22:42 +02:00
install_arch.sh app: package the desktop app as the web build, and delete the Linux embedder 2026-07-20 16:32:24 +02:00
pubspec.lock Let the desktop provision its sync account from the wrapper 2026-08-09 21:41:08 +02:00
pubspec.yaml Let the desktop provision its sync account from the wrapper 2026-08-09 21:41:08 +02:00
README.md Gate the companion app in the push hook, and document it properly 2026-07-20 17:01:40 +02:00
run.sh app: package the desktop app as the web build, and delete the Linux embedder 2026-07-20 16:32:24 +02:00
skills-lock.json Add Flutter/Dart agent skills and AI rules for Claude Code 2026-06-22 22:53:06 +02:00

diet_guard_app

Companion app for diet_guard: log meals on the phone, and on the Linux desktop, sharing one food log through GitHub-backed sync.

Two targets, and only two

Target What it is
Android a normal Flutter app (flutter build apk)
Linux desktop the Flutter web build, served by a local wrapper and shown in a Chrome --app window

There is no linux/ directory. Flutter's GTK embedder manages ~20fps at 3840x2160 -- measured on a stock flutter create counter app, so it is the toolkit rather than this code -- where the same Dart rendered by Chrome sustains ~144fps (~/todo/docs/desktop-performance-findings.md). Running flutter create --platforms linux here would silently restore the slow path.

Commands

flutter test                     # the app's suite
flutter analyze
bash run.sh                      # build the web bundle and launch the desktop app
bash install_arch.sh             # build + install the pacman package and .desktop entry
flutter build apk --release      # the phone build
adb install -r build/app/outputs/flutter-apk/app-release.apk

Never adb uninstall or pm clear this package: both wipe the on-device log.

The desktop wrapper

bin/diet_guard_desktop.dart is the other half of the desktop app, because a browser cannot touch the filesystem. It:

  • serves the built web assets from build/web (or /opt/diet-guard-app/web once packaged);
  • keeps an on-disk copy of every document and photo under ~/.local/share/diet-guard-desktop/, which is what makes a wiped Chrome profile recoverable -- for photos it is the only copy, since they never sync;
  • holds the GitHub token (~/.config/diet_guard_app/sync_token, falling back to the PC gate's ~/.config/diet_guard/sync_token) and proxies api.github.com plus the device flow, whose endpoints send no CORS headers and so cannot be called from a page at all.

The port (8732) and the Chrome --user-data-dir must stay fixed. IndexedDB is keyed by origin and lives inside that profile, so changing either looks to the app like a device with no history.

Platform seams

dart:io does not fail a web compile; it becomes a stub that throws at runtime, so the symptom of a missed seam is a blank white window rather than a build error. Every platform edge is a conditional export -- document_store*, blob_store*, background_tasks*, notification_backend*, token_vault*, github_client_factory*, attached_image*. Branch on kIsWeb before any Platform.is…, which itself throws on web.

What the desktop genuinely loses, by design rather than omission:

  • Background reminders. A browser runs nothing once its window is closed, so the due-slot check runs in-page every 5 minutes while the window is open. On the PC the real backstop is diet-guard-gate.timer, which locks the screen.
  • Camera capture. The browser picker is a file input.
  • Photo sharing between devices -- unchanged: imagePath was always stripped before push.

Sync identity

Three devices push under diet-guard-sync/devices/: pc (the Python gate), phone, and desktop. Each must keep its own id -- two devices sharing one would overwrite each other's pushed log on every tick.