diet-guard/app
Krzysztof kuhy Rudnicki 6258b35f88
Some checks are pending
file-length / file length <= 250 lines (push) Waiting to run
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
perf(sync): hoist the device listing and stop the curated-bank 404 storm
Two independent costs in the background tick, both pure waste.

`list_directory` was called four times per tick -- once by the log pull and
once by each of the three bank syncs. It is the single most expensive call
in a tick at ~445ms mirrored (the GitHub half alone is ~360ms), so that was
~1.3s spent asking the same question four times. It is now listed once in
`run_sync` and passed down.

The curated-bank loop was worse: 17s and 62 requests to fetch *zero* useful
bytes. Firebase returns None for every peer's `food_bank_manual.json`, so
`MirrorSyncClient.get_file_text` treated each miss as "this device has not
migrated yet" and fell through to GitHub, which answers `{}` after ~320ms.
`_primary_only_text` skips that per-peer fallthrough for this one artifact.

Checked before narrowing rather than assumed, because CLAUDE.md requires
both halves of the food bank to sync: reading every peer's curated bank
straight off the mirror found 23 absent, 2 empty, and zero non-empty. No
curated food exists on the mirror alone. The probe is written into the
docstring so it can be re-run before anyone widens this again.

Measured end to end: manual-bank loop 17.0s -> 3.4s, full tick ~27s -> 15.2s,
log/bank/peer state unchanged (441 entries, 117 foods, 22 peers).

`_pull_remote_logs` now returns the revisions it saw instead of mutating a
caller-supplied dict, which keeps it under the argument limit and makes the
data flow visible at the call site.

Deliberately NOT done: revision-skipping the derived food bank. `run_sync`
rebuilds that bank from the merged log immediately before syncing it, so
unlike the log the peer records are not durably held locally -- skipping a
peer would drop its counts from the pushed bank and oscillate under
max-count-wins. Verified empirically: the on-disk bank and a pure log-derived
rebuild are identical (117 foods, 0 differing counts), and the 2 foods peers
know that the local bank lacks are discarded by the existing `if name in
local` filter anyway. The loop is off every interactive path now, so the
remaining cost is background-only.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LJ5YnA3CERitQ57HjMu2Qz
2026-08-21 13:16: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 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 Rebuild the slot row when settings changes the schedule 2026-08-16 15:12:57 +02:00
test Rebuild the slot row when settings changes the schedule 2026-08-16 15:12:57 +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 Persist the CI-mirror's analyzer excludes and dependency upgrades 2026-08-14 16:13:46 +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 Split six Dart test files; share the sync fixtures via a support sibling 2026-08-15 20:49:54 +02:00
pubspec.yaml perf(sync): hoist the device listing and stop the curated-bank 404 storm 2026-08-21 13:16: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.