The gate's pre-lock refresh ran a *full* sync tick -- peer logs, budget,
both food banks, a rebuild and a push -- in front of the fullscreen lock.
Measured against the live remote: ~18-27s, of which the curated-bank loop
alone was 17s spent fetching nothing (absent on Firebase for every peer, so
each of ~21 peers fell through to a GitHub round trip returning `{}`).
It was also failing: five `sync unavailable (network error trying to refresh
the session)` entries in the last five days, one of them a meal that logged
locally but never published.
The gate has one question -- "has a peer already logged this slot?" -- and
only the peer logs can answer it. `_sync_refresh` does that and nothing
else: one bulk revision-map read, a fetch of only the peers whose revision
moved, merge, re-sign, write. No budget, no banks, no push. The full tick
still runs, after the window closes, so nothing is permanently skipped --
the exhaustive pass just moves off the path the user waits on.
Three state invariants have their own regression tests, because each is
silent when broken: `pushed_rev` survives verbatim (this pass never pushes,
and overwriting it would make the device stop publishing forever),
`peer_revs` is merged rather than replaced (only changed peers are visited),
and the log is written before the state.
Separately, `gate --check` was importing an HTTP stack and a GUI it never
uses. `requests` (~78ms) arrived via `_cli_sync` -> `crdt_sync` and via
`_resolve` -> `_estimator_off`; both are now resolved through module-level
`__getattr__` (PEP 562) and `import_module`, which keeps every existing
`patch.object` target and needs no lint suppression.
Measured: `gate --check` 216ms -> ~110ms; narrow pull 92ms against the live
remote. Accepted trade, documented in `_sync_refresh`: a budget edited on
the phone is not visible in the lock dashboard until the post-window tick.
It cannot affect the lock decision, which is why it is safe to defer.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LJ5YnA3CERitQ57HjMu2Qz
|
||
|---|---|---|
| .. | ||
| .agents/skills | ||
| android | ||
| assets/icon | ||
| bin | ||
| desktop | ||
| lib | ||
| test | ||
| web | ||
| .gitignore | ||
| .metadata | ||
| analysis_options.yaml | ||
| install_arch.sh | ||
| pubspec.lock | ||
| pubspec.yaml | ||
| README.md | ||
| run.sh | ||
| skills-lock.json | ||
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/webonce 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 proxiesapi.github.complus 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:
imagePathwas 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.