Commit Graph

27 Commits

Author SHA1 Message Date
a106970b4f theme.dart: define secondary/tertiary ColorScheme roles
Some checks failed
pre-commit / pre-commit (push) Has been cancelled
Tests / test (3.10) (push) Has been cancelled
Tests / test (3.11) (push) Has been cancelled
Tests / test (3.12) (push) Has been cancelled
Found via testing todo's app, which shares this theme.dart template and
has a SegmentedButton: neither ColorScheme.dark() constructor call
defined secondary/tertiary (or their container/on- variants), so any M3
widget reaching for secondaryContainer silently falls back to Flutter's
stock Material teal instead of erroring. Confirmed this app has the same
live bug — ChoiceChip (slot selector) uses secondaryContainer implicitly,
and FilterChip's "Any"/"With photo"/"Without photo" chips in the History
filter sheet were rendering in stock teal in production right now.

The shared palette has one accent, not a separate secondary hue, so these
mirror primary; secondaryContainer/tertiaryContainer use line-dark as a
distinguishable-but-subtle selected-state fill.

Verified: flutter analyze clean, 436/436 tests, re-verified identical in
an isolated worktree, and confirmed live: rebuilt debug + release APKs,
installed via adb install -r (no data wipe), screenshotted the real
device before/after (the History filter chips went from teal to warm
gold/tan), then rebuilt and redeployed the desktop package
(install_arch.sh -> pacman -U) and confirmed the running desktop window
renders identically.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-22 19:46:07 +02:00
c935a202cc Make AppStatusColors lookup null-safe; fix tests that never had it
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
Theme.of(context).extension<AppStatusColors>()! crashed in any context
that doesn't build its theme from buildAppTheme() -- which is most widget
tests, since they wrap in a bare MaterialApp(home: ...) rather than the
real app theme. That crash was latent since AppStatusColors was
introduced but only became reachable once main.dart actually started
using buildAppTheme() (previous commit), which is when 27-47 tests
started failing.

Fixed at both call sites (slot_selector_row.dart, day_status_calendar.dart)
by falling back to AppStatusColors.dark instead of force-unwrapping --
production code should never trust every context to fully replicate the
real theme. Also fixed day_status_calendar_test.dart, which asserted
against stale hardcoded Colors.* values from before the theme rewrite;
it now wraps in the real buildAppTheme() and asserts against the actual
tokens, so the assertions mean something again.

Verified: flutter analyze clean, 436/436 tests pass (up from 389/47).
2026-07-22 16:52:13 +02:00
0e9acb9e18 Wire theme.dart into the app; replace remaining raw Colors.* literals
main.dart now builds MaterialApp's theme from buildAppTheme() instead of
ColorScheme.fromSeed(Colors.teal) -- theme.dart existed but was never
actually used until this commit. photo_viewer_screen.dart,
settings_screen.dart, and day_status_calendar.dart's raw Colors.*
literals (Colors.black, Colors.red, Colors.white38, Colors.grey.shade800,
...) now read from Theme.of(context) instead.

Also rewrites CLAUDE.md's Visual Design & Theming section, which
previously contradicted the unified-design-system skill (noise texture,
multi-layered shadows, ColorScheme.fromSeed -- all things that skill
explicitly forbids).

This was the other prerequisite the on-fill/warm-palette fixes actually
needed: theme.dart existing but unwired meant the app was still running
Flutter's default seeded theme.
2026-07-21 21:50:16 +02:00
26f95d42f5 Fix stale adaptive_icon_background left over from the warm-palette icon regen
pubspec.yaml's adaptive_icon_background is a separate literal from
python_pkg/app_icons' own style.BACKGROUND -- the icon regen updated
every generated PNG but missed this one, so the actual on-device home
screen icon (Android's adaptive icon, not the legacy mipmap) stayed on
the old cool charcoal. Caught on the physical device, not by file checks.

testsAndMisc's generator now syncs this automatically going forward.
2026-07-21 21:42:32 +02:00
2a772d624f app: warm-tint the shared palette, fix Snack chip + on-fill contrast
- theme.dart: warm unified-design-system palette, on-fill for
  onPrimary/onError, and inputDecorationTheme/dividerTheme (previously
  unset -> every TextField/Divider fell back to Material defaults).
- slot_selector_row.dart: the Snack ChoiceChip had no explicit
  backgroundColor/side/labelStyle like the hour chips, so it fell through
  to Flutter's own default -- a literal pure-white border.
- Regenerated launcher icon assets (warm charcoal + warm gold accent) via
  python_pkg/app_icons.
- Bump gatelock pin to the released v0.1.1 carrying this same palette.

Verified: flutter analyze clean, diet_guard pytest 443/443 @ 100% coverage,
phone-installed APK pixel-checked (Snack chip border, on-fill contrast).
2026-07-21 21:16:26 +02:00
50f311b6b8 Gate the companion app in the push hook, and document it properly
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
`ci_mirror.sh` -- the pre-push gate -- ran only the Python side: a clean-venv
pytest and `pre-commit --all-files`. Nothing ran the app's suite, and
`.pre-commit-config.yaml` has no Dart hooks at all, so a red `app/` could be
pushed with a fully green gate. It now runs `flutter analyze` and
`flutter test` too, and *fails* rather than skips when `app/` exists but
flutter is missing: a gate that quietly passes without its toolchain is the
same as no gate. Verified by breaking a Dart file on purpose and watching the
push gate refuse it.

app/README.md was still the stock "A new Flutter project" template. It now
says what the two targets are, why there is no `linux/`, what the wrapper
owns, why the port and Chrome profile are fixed, and which behaviours the
desktop genuinely gives up.

Drops MIGRATION_PROMPT.md, whose content now lives in CLAUDE.md and the
migration commits.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 17:01:40 +02:00
2231850bfc app: test the IndexedDB stores instead of shipping them unverified
The IndexedDB stores are the desktop device's primary copy of the food log and
the *only* copy of its photos -- photos never sync -- and their wrapper-mirror
fallback is what makes a wiped Chrome profile recoverable. All of that was
invisible to the suite: the files import `idb_browser.dart`, which cannot be
imported outside a web compile, so they were simply absent from the VM test
run rather than covered.

Splitting the store classes from the browser-factory `open…()` functions lets
them run against `idb_shim`'s in-memory backend on the plain VM, which is what
the flutter-desktop-to-chrome skill recommends the shim for. Twelve tests now
pin the round-trip, the mirror write, the recover-from-mirror path, and the
no-wrapper-running case (a plain browser tab, where the app must still work).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 16:56:15 +02:00
c12faa3920 app: package the desktop app as the web build, and delete the Linux embedder
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
Completes the move: `linux/` is gone, replaced by the web build served by the
wrapper in a Chrome `--app` window. The icons and the .desktop installer move
out of `linux/` first -- they are not Flutter scaffolding and are still
needed; the entry now points at the packaged launcher and matches the
`--class=diet_guard_app` the wrapper passes, so the taskbar shows the app's
icon rather than a browser's.

install_arch.sh carries over the three traps each of which produced an
install that looked fine until launched: `dart build cli` rather than
`dart compile exe` (which refuses any package whose dependency graph has
native build hooks), `options=('!strip')` (makepkg would otherwise discard
the AOT snapshot, leaving a binary that runs as the bare Dart VM printing a
usage message), and no browser in `depends` (this machine's policy uninstalls
`chromium` seconds later, failing dependency resolution).

Also adds the frame-cadence instrument the performance claim is measured
with, armed by `--dart-define=DIET_GUARD_FRAME_STATS=1` and read with
`String.fromEnvironment` -- `bool.fromEnvironment` only accepts the literal
`true`, so `=1` would evaluate to false and a whole run would silently
produce no data. It reports frame-to-frame cadence rather than build/raster
durations, since work that fits the budget says nothing about the frame rate
actually achieved.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 16:32:24 +02:00
aeb9376391 app: add the desktop wrapper that serves the web build and fronts GitHub
A browser cannot touch the filesystem, so the desktop app needs a second
half. This local process serves the built web assets, owns an on-disk copy of
every document and photo blob the app stores, and performs the GitHub I/O the
page cannot.

Both GitHub responsibilities are load-bearing, not convenience:

- the device-flow endpoints (github.com/login/device/*) send no CORS headers,
  so a page cannot call them at all;
- proxying api.github.com server-side is what keeps the token out of the
  browser -- the page is told `{"status":"ok"}`, never the token itself.

The wrapper falls back to the PC gate's existing
`~/.config/diet_guard/sync_token`, so this machine needs no second setup.

Details learned the hard way in the two apps migrated before this one, kept
here deliberately: loopback-only binding (the endpoints overwrite files in
$HOME with no auth), `application/wasm` for .wasm or CanvasKit renders
nothing at all, a fixed port and Chrome profile because IndexedDB is keyed by
origin, and treating a browser exit inside ~5s as a handoff to an existing
window rather than a reason to stop serving. Mirror names are rejected unless
they are plain filenames -- a `..` segment would turn a mirror write into an
arbitrary-file overwrite.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 16:31:47 +02:00
802ee0fe6d app: run the companion app's desktop target as a web build
Flutter's Linux 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 (evidence in
~/todo/docs/desktop-performance-findings.md). The desktop target therefore
becomes a Flutter *web* build; Android is untouched.

`dart:io` does not fail a web *compile* -- it resolves to a stub that throws
at runtime -- so every platform edge moves behind a conditional export rather
than being discovered later as a blank white window:

- storage: `document_store*.dart`, files on Android and IndexedDB in the
  browser (mirrored to the desktop wrapper, which is what makes a wiped
  Chrome profile recoverable). Document names stay the exact pre-web
  filenames so an installed phone keeps reading its existing log.
- photos: `blob_store*.dart` plus `attached_image*.dart`, since `Image.file`
  cannot resolve an IndexedDB key. The picker itself has web support; what
  changed is the storage and display model. Camera *capture* is genuinely
  lost on desktop -- and photos never crossed devices anyway, `imagePath`
  being stripped before push.
- scheduling: `background_tasks*.dart`. A browser has no WorkManager
  equivalent -- a closed tab runs nothing -- so the desktop runs an in-page
  5-minute check while its window is open. That loses nothing on the PC,
  where `diet-guard-gate.timer` already locks the screen on an unlogged
  slot, which is strictly stronger than a notification.
- notifications: `notification_backend*.dart`, the browser Notifications API
  in place of flutter_local_notifications.
- the GitHub token: `token_vault*.dart`. On web it is *not* held by the
  browser at all -- flutter_secure_storage's web backend is WebCrypto-wrapped
  localStorage with the wrap key beside the ciphertext, which is obfuscation,
  not encryption at rest -- so the desktop wrapper owns it and proxies the
  API (`github_client_factory*.dart`).

`Platform.isAndroid` itself throws on web, so every platform branch now tests
`kIsWeb` first; the battery-optimization row (permission_handler, no web
implementation) is hidden off Android.

The desktop pushes under device id `desktop`, not `phone`: sharing an id
would have made the two devices overwrite each other's pushed log on every
tick. The Python side enumerates device directories generically, so it needs
no change.

Tests move onto the same seams -- the file-backed stores keep the atomicity
and unreadable-file guarantees where they belong.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 16:31:32 +02:00
10364dc1ff app: one-tap repeat-last-meal FAB with optional temptation-bundling reward
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
Applies Atomic Habits' two-minute rule: a FAB on the home screen repeats
the most recent logged meal in a single tap, mirroring _onLogMeal's
existing post-write sync sequence. An optional device-local reward
(label + URL, configured in Settings) surfaces after a repeat and opens
externally via url_launcher.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-20 10:09:36 +02:00
f6abf802c9 app: replace the stock Flutter launcher icon, add a Linux desktop icon
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
Adopt the shared icon family (python_pkg/app_icons in testsAndMisc): a
charcoal #1B1D21 field carrying a teal shield-and-cutlery glyph, teal being
this app's own colorSchemeSeed. The fork is paired with a knife on purpose -
a lone three-tine fork reads as a trident at launcher size.

Android gains the adaptive-icon layers the project never had, including an
Android 13 monochrome layer.

Linux needs separate handling: Flutter's GTK runner never sets a window icon,
so my_application.cc now calls gtk_window_set_icon_name and
install_desktop_entry.sh populates the hicolor theme from the pre-rendered
PNGs in linux/icons (committed, so the installer needs no image tooling).
StartupWMClass is the APPLICATION_ID rather than the binary name - verified
with xprop against a running window, where WM_CLASS is the GApplication id.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-19 17:00:37 +02:00
957769ff0e Add budget-adherence calendar, streaks, and freely-editable synced budget
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
Both platforms gain a calendar tab/screen (green/yellow/red/not-logged
day coloring, logging/adherence streaks, year-to-date tally), and
tapping a day drills into that day's logged entries. This requires the
daily budget to become a normal, freely-editable value synced between
devices like the food log — replaces the previous chattr +i seal
(deliberately, per user decision) with plain last-edit-wins sync via a
new budget.json alongside food_log.json. The budget field defaults to
2200 kcal, starts read-only, and unlocks via an Edit/Save toggle on
both the PC gate's History tab and the phone app's Calendar screen.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dHac1iYQKTMZEtL7xDJvA
2026-07-13 21:31:45 +02:00
09cf5b726a app: use crdt_sync's shared GitHubClient; drop the local copy
Some checks failed
pre-commit / pre-commit (push) Has been cancelled
Tests / test (3.10) (push) Has been cancelled
Tests / test (3.11) (push) Has been cancelled
Tests / test (3.12) (push) Has been cancelled
crdt_sync v0.3.0 adds canAccessRepo(), so the settings 'Test connection'
button uses the shared client and the local lib/services/github_client.dart
(+ its test) is deleted. flutter analyze clean, 299 tests pass. On-device
foreground check deferred: the active night curfew disables the app (a
phone-policy thing, not a code issue); build + install -r succeeded.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019KwUCPESvE5CjCtmUFz8xQ
2026-07-11 23:54:03 +02:00
67bc98c7e0 app: offline backstop — connectivity-gated push on reconnect
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
Immediate push on log only helps when the device is online. If a meal is
logged while offline, the in-process auto-sync fails silently and the meal
waited for the next app lifecycle event. Now each log also enqueues a one-off
WorkManager task constrained to NetworkType.connected, so the log uploads as
soon as connectivity returns, without the app being reopened.

- background_sync_service.dart: backgroundSyncPush() loads SyncSettings and
  runs runSync, returning WorkManager's success/retry flag (true when nothing
  to push or the push succeeded; false to retry after a transient failure).
  Unit-testable half; the dispatcher branch and the one-off registration are
  integration-only (real background isolate) and coverage-ignored, matching
  the existing periodic check.
- log_meal_screen.dart: _enqueueSyncBackstop() registers the task (replace
  policy coalesces a burst of logs; linear backoff retries) from both log
  paths. No-op off mobile.
- background_check_service.dart: dispatcher routes the new task name.
- log_storage_service.dart: writeLog is now atomic (temp file + rename) — the
  background push's runSync can write the log in a separate isolate while the
  foreground app also writes, so a torn read must be impossible.

Verified on device (BL9000EEA0000102): airplane mode on -> log -> background;
airplane mode off -> the phone's remote food_log.json commit advanced within
~15s, app never reopened (also confirms the token loads in the background
isolate).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FtSmzACzJSGyEatgK9exkd
2026-07-11 19:11:54 +02:00
16ca5227f6 app: push immediately when a meal is logged
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
The phone only pushed its log on screen-init, app resume/pause, or manual
"Sync now" -- never on the log action itself -- so a meal could sit locally
for many minutes before reaching GitHub (and thus the PC gate). Fire the
existing best-effort _autoSync right after a meal is logged, from both the
quick-log path (_onLogMeal) and the meal-builder return (_onBuildMeal), so an
online device propagates a new meal within seconds.

_autoSync is already single-flight and swallows offline/transient failures, so
this is a fire-and-forget unawaited call with no new machinery. Offline logging
still waits for reconnect + a lifecycle event (a WorkManager backstop is a
future follow-up).

Verified on device (BL9000EEA0000102): logging a meal advanced the phone's
remote food_log.json commit within seconds.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FtSmzACzJSGyEatgK9exkd
2026-07-11 18:55:06 +02:00
e6812f7b4f Fix pre-existing CI failures (deps, lint, whitespace)
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
CI on main was already red before the crdt_sync migration. Make the
pre-commit --all-files run and the test job green:

- requirements.txt: add crdt-sync (now a runtime dep) and mcp so CI's
  `pip install -r requirements.txt` can import diet_guard._sync and
  collect test_mcp.py (both were ModuleNotFoundError in CI).
- .pre-commit-config.yaml: add mcp + pydantic to the pylint hook deps so
  it can resolve diet_guard._mcp's imports (was E0401).
- pyproject.toml: raise pylint min-similarity-lines to 8 — the shared
  nutrition-field keys in _foodbank/_mcp mirror one domain schema, not
  real duplicate code.
- scripts/setup_mcp.sh: split declare/assign for SCRIPT_DIR/REPO_DIR
  (shellcheck SC2155).
- codespell: fix "packge"/"unparseable" typos; trailing-whitespace
  auto-fixes across app/.agents/skills docs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017RRCdR4D8SaVrN2xraMQxM
2026-07-10 23:55:56 +02:00
59d1c29dd2 Migrate sync to shared crdt_sync library (Python + Dart)
Replace the bespoke GitHub-Gist sync with the shared crdt-sync /
crdt_sync_dart libraries so diet-guard uses the same CRDT transport as
the other apps.

- Python: delete _sync_github.py (+ its test); rewrite _sync.py,
  _cli_sync.py, _sync_merge.py against crdt-sync. Add the crdt-sync git
  dependency (pyproject + pylint deps) and S107 to the test ignore list.
- Dart: rewrite app sync_service.dart / sync_merge.dart against
  crdt_sync_dart (new git dep + crypto); update log_meal and settings
  screens and their tests.

Verification: pytest 347 passed at 100% coverage; flutter analyze clean;
flutter test 306 passed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017RRCdR4D8SaVrN2xraMQxM
2026-07-10 23:49:43 +02:00
a121da211d Point sync at the consolidated utils/syncs monorepos
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
gatelock and diet-guard-sync were folded into shared monorepos:
gatelock -> github.com/kuhyx/utils (subdirectory), diet-guard-sync ->
github.com/kuhyx/syncs (subdirectory). Update the gatelock pip URL,
SYNC_REPO_NAME, the devices-dir path prefix (PC and phone), and add a
persisted-value migration so an already-configured phone picks up
'syncs' on next load.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-10 14:12:16 +02:00
c43e37b09d Compact LogMealScreen so it fits without scrolling on-screen keyboard
Merge the slot-status bar, slot-selector chips, and "Logging for
HH:00" caption into one selectable, status-colored SlotSelectorRow.
Add an opt-in compact mode to MacroInputRow (single row, abbreviated
labels), AutocompleteSuggestionList (top-3 + "N more" bottom sheet),
and PhotoAttachField (icon-only + badge thumbnail), used only by
LogMealScreen so MealBuilderScreen/EditEntryScreen keep their default
rendering. Verified on-device (BL-9000) that all fields stay visible
with the keyboard open.

Also fixes an unrelated time-bomb in history_screen_test.dart's date
range picker test, which hardcoded an expected "2026-06-01" label
assuming "today" was in June; the picker's displayed month and
selectable range depend on the real current date, so the assertion
now computes its expectation from DateTime.now() instead.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018UorgLvWJ4huH55tmXoUAZ
2026-07-04 05:19:23 +02:00
4c6083b768 Add food bank, edit-entry, and app settings screens; history redesign
Work-in-progress feature set accumulated ahead of the log-meal compact
layout change: a food bank browser, a shared entry-edit screen, an
app-wide settings service, and a substantially reworked history screen
with filtering/sorting.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018UorgLvWJ4huH55tmXoUAZ
2026-07-04 05:18:32 +02:00
7d421c1d8b Add R8 keep rules and re-enable release minification
The previous commit disabled isMinifyEnabled/isShrinkResources as a crash
workaround (AGP 9 default minification stripped WorkDatabase_Impl's no-arg
constructor). This commit adds the proper fix:

- new android/app/proguard-rules.pro: keeps the no-arg constructor on all
  RoomDatabase subclasses (the gap the Room AAR's own consumer rule misses
  — it keeps the class name but not the reflectively-called constructor),
  plus WorkManager worker/InputMerger rules re-stated from the work-runtime
  AAR in case transitive consumer-rule propagation is incomplete through the
  Flutter plugin wrapper.
- build.gradle.kts: re-enables isMinifyEnabled=true + isShrinkResources=true
  with proguard-android-optimize.txt + proguard-rules.pro.

Verified on the real phone (BL9000): minified release APK launches without
crash, MainActivity foreground, food-bank suggestions and slot chips render
correctly. APK size 52.1 MB (vs 56.9 MB unminified).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SWPUBzE24Ls9i9GMRwXnnn
2026-06-25 17:34:15 +02:00
adbfb20e9a Add OAuth device flow, background notifications, and fix AGP9 release crash (Milestones 3–4)
M3 – GitHub OAuth device flow: replace PAT-paste with a guided "Connect
GitHub" button that runs the device-code flow; tapping with no client id
now opens a setup dialog (instructions + inline paste field) rather than
a buried inline hint. Bakes in the app's own OAuth App client id so fresh
installs work with zero manual config. Auto-syncs immediately after
connect. Verified end-to-end on the real phone: OAuth flow → token saved
→ PC's 48-entry log merged in (confirmed via food-bank vs manual source
labels in History).

M4 – Background meal-slot notifications: WorkManager periodic task (15 min
floor) checks for overdue slots and posts/cancels notifications via
flutter_local_notifications. New permissions: POST_NOTIFICATIONS,
WAKE_LOCK, RECEIVE_BOOT_COMPLETED, REQUEST_IGNORE_BATTERY_OPTIMIZATIONS,
INTERNET (was missing — latent sync bug). "Disable battery optimization"
button in Settings. Verified on real phone: WorkManager registered, forced
run posted a real notification ("Meal not logged / You haven't logged your
16:00 meal yet."), isolated to background path (only caller is the
WorkManager dispatcher, not any foreground lifecycle hook).

AGP9 release crash fix: AGP 9 defaults isMinifyEnabled/isShrinkResources
to true for release even with no proguard config; R8 stripped
WorkDatabase_Impl's reflection-only constructor, crashing every launch
with NoSuchMethodException. Explicitly disabled both flags in
build.gradle.kts. Verified via dexdump (constructor present) and on-device
launch (no crash). Proper R8 keep rules are the long-term fix; tracked.

177 tests, flutter analyze clean.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SWPUBzE24Ls9i9GMRwXnnn
2026-06-25 17:29:23 +02:00
0ddad00ab9 Add Flutter/Dart agent skills and AI rules for Claude Code
Installs the official flutter/skills and dart-lang/skills packs into
app/.agents/skills/ and appends Flutter's AI rules.md to CLAUDE.md, so
Claude Code has task-specific playbooks alongside the dart MCP server
(registered separately at user scope).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013NqCvrbFnoNNmqCwZKntBK
2026-06-22 22:53:06 +02:00
a82047502f Add Flutter half of cross-device sync (Milestone 3)
Ports github_client.dart and sync_settings.dart from ~/todo (PAT-paste
instead of OAuth device flow), and writes a new (non-CRDT) sync_merge.dart
and sync_service.dart matching diet_guard's Python _sync_merge.py/_sync.py
algorithm exactly. Adds a settings screen for the PAT plus manual "Sync
now", and wires lifecycle-triggered auto-sync (launch + resumed/paused)
into the main logging screen, silent on failure per plan decision 4.

Also adds Linux desktop platform scaffolding so this and future UI changes
can be visually verified without a connected phone.

Verified end-to-end against the real kuhyx/diet-guard-sync GitHub API on a
Linux desktop build: Test connection and Sync now both round-trip to GitHub
and surface real auth errors correctly via SnackBar.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RH2BHCKbDTiYJUMG3rb9nq
2026-06-22 22:42:27 +02:00
feef5984f8 Add photo attach, full-size viewer, and a minimal history screen
Milestone 2 of the diet-app-as-wise-balloon plan, plus feedback from
manually testing it on-device:

- PhotoAttachService wraps image_picker and copies the picked photo into
  <app documents>/images/<uuid>.<ext>, so the file survives after the
  picker's own (possibly cache-cleared) temp copy is gone. Phone-local
  only, per the sync plan: imagePath is never synced.
- PhotoAttachField is a shared attach/preview/remove widget, used
  identically by both the single-item and composite-meal logging
  screens, so logging a multi-item meal can now carry a photo too.
- PhotoViewerScreen gives a full-screen, pinch-to-zoom view of an
  attached photo -- the 64x64 inline thumbnail was too small to
  actually check the photo.
- HistoryScreen lists every logged entry across all days, newest first,
  with a thumbnail when one is attached. There was previously no way to
  confirm what got logged (or whether a photo actually attached) short
  of inspecting food_log.json directly.

Verified on a physical device (BL9000): built, installed, and the user
confirmed the photo-attach flow logs a real entry with a real photo,
visible afterward in the new history list. 88 Flutter tests passing,
`flutter analyze` clean against very_good_analysis.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FU3f5KQ1GHXsbbSecfVEyF
2026-06-22 18:57:58 +02:00
ee5a7660cb Add Flutter companion app skeleton with local meal logging
Milestone 1 of the diet-app-as-wise-balloon plan: a phone-native way to
log meals away from the PC, sharing the exact on-disk JSON shape
diet_guard already uses (same field names, no translation layer).

- lib/models/: 1:1 Dart mirrors of the Python dataclasses (Nutrition,
  FoodEntry, MealItem, FoodBankRecord, Slot), including the per-100g/
  amount-eaten portion scaling that matches _resolve.resolve_nutrition's
  semantics exactly.
- lib/services/log_storage_service.dart: plain-JSON persistence to
  food_log.json's exact shape (no sqflite -- the canonical format
  already is this JSON).
- lib/services/foodbank_service.dart: ports _foodbank.py's upsert/fuzzy
  search logic for autocomplete.
- lib/screens/: log_meal_screen.dart (single-item logging) and
  meal_builder_screen.dart (composite multi-item meals, logging full
  per-component macros via the new components field).

Verified end-to-end on a physical device (BL9000): built, installed,
logged a real meal through the UI. 77 Flutter tests passing, `flutter
analyze` clean against very_good_analysis.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FU3f5KQ1GHXsbbSecfVEyF
2026-06-22 18:22:42 +02:00