diet-guard/app
Krzysztof kuhy Rudnicki 55fade1e15
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
Let the desktop provision its sync account from the wrapper
Same change todo just took, for the same reason: app/'s desktop target is a
Flutter web build in a Chrome --app window, so FlutterSecureStorage resolves
to the web backend (AES-encrypted localStorage, keyed inside the browser
profile). Nothing outside the page can write that store — `secret-tool`
targets libsecret, which only a Linux embedder build would read, and a Dart
CLI cannot even import flutter_secure_storage, since it is a Flutter plugin
that fails to resolve on the VM. Both verified rather than assumed.

The wrapper is the one process that can read the filesystem and serve the
page, which is exactly why it already holds the GitHub token instead of the
browser (see github_proxy.dart). So the sync account now arrives the same
way: loadAccount() falls back to GET /sync-account when the store is empty
and persists the result, making the route a one-shot provisioning step
rather than a standing dependency.

It reads ~/.config/crdt-sync/{firebase.json,password} — the same pair
`diet_guard sync` already uses — so the machine keeps one source of truth.

Off unless CRDT_SYNC_SERVE_ACCOUNT is set, and 404 (not 403) when disabled so
a probe cannot tell the route exists. On Android there is no wrapper: the
request simply fails and loadAccount() returns null exactly as before.

clearAccount() writes an opt-out marker the fallback checks, so Disconnect
cannot be silently undone by the next launch re-adopting the account.

518 tests, analyzer clean, pre-commit clean. Pinned to crdt_sync_dart-v0.8.0.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0177xwzcpfpWG2qca8Mr39q9
2026-08-09 21:41:08 +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 app: add the desktop wrapper that serves the web build and fronts GitHub 2026-07-20 16:31:47 +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 Let the desktop provision its sync account from the wrapper 2026-08-09 21:41:08 +02:00
test Push under a persisted per-install uuid, not a fixed role id 2026-08-09 17:18:42 +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.