food_bank_screen.dart (712) carried a near-clone of the filter sheet batch 2
had just factored out of history: the same name-search field, the same slider
captions, and four more copies of the same min/max macro range block. Splitting
it in isolation would have baked that duplication in permanently, so the shared
widgets moved to a neutral `lib/widgets/filters/` first and both screens now
import them.
food_bank_screen.dart 712 -> 238
lib/widgets/food_bank/
fb_filter.dart 156 FbFilter, FbSortField, applyFbFilter
fb_filter_sheet.dart 244 the sheet itself
fb_add_entry_dialog.dart 103 the manual-entry dialog
lib/widgets/history/history_filter_fields.dart
-> lib/widgets/filters/filter_fields.dart
FilterRangeRow gained `showEndpointLabels`, defaulting to true. History showed
endpoint captions and a selected-range readout; the food-bank sheet never did,
and switching them on there would have been a UI change smuggled in under a
refactor. The food-bank call sites pass false, so both screens render exactly
what they rendered before.
FbSortField and HistorySortField stay separate on purpose -- they are not the
same enum (`name` vs `date`/`description`), so the sort dropdown is not shared.
The test file mirrors the source split, three files under test/screens/
food_bank/ plus fb_test_support.dart for the setUp/tearDown/settle prologue,
same shape as batch 2's history_test_support.dart.
Verified: 555 flutter tests (unchanged count -- no test lost, and food bank's
own RangeSlider/sort-toggle tests still pass against the shared widget),
flutter analyze clean over lib/ and test/, live ~/.local/share/diet_guard
byte-identical. Over-cap files: 40 -> 38.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RA9ynjqXPnexRQnR5oAuCC
|
||
|---|---|---|
| .. | ||
| .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.