diet-guard/app
Krzysztof kuhy Rudnicki d388f90ed4
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
Stop SIGKILLing the provisioning browser, and say why auto-sync failed
Two fixes found while verifying the desktop actually reaches Firebase rather
than trusting an exit code.

`--virtual-time-budget` makes headless Chrome SIGKILL itself when the budget
expires: the run reported exit -9 and could cut a sync short mid-flight. It
now gets a real wall-clock window and is closed cleanly, so the exit code
means what it says.

`_autoSync` caught `on Exception` and ignored it entirely — the exact
silent-failure shape screen-locker's CLAUDE.md bans ("an invisible log is a
silent failure with extra steps"). It now catches broadly and logs at error
level with the stack trace, so the next failure is diagnosable instead of
looking identical to "nothing to sync". `--enable-logging=stderr` on the
provisioning run surfaces that in the terminal that started it.

Verified end to end, which is what settled it: the desktop appears in the
RTDB as c891b813 with all four documents and a revs entry, and its food_log
holds 359 records — identical to the PC daemon's, 0 missing either way. An
earlier check read as "not pushing" because it ran before the push landed.

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:15:34 +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 Stop SIGKILLing the provisioning browser, and say why auto-sync failed 2026-08-09 22:15:34 +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 Stop SIGKILLing the provisioning browser, and say why auto-sync failed 2026-08-09 22:15:34 +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.