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>
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
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
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
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
Address code-review feedback on the sync-before-lock change:
- pull_shared_log now catches (SyncError, GitHubSyncError, OSError) instead
of a blanket `except Exception`. crdt_sync wraps every requests transport
error in GitHubSyncError, so those three are the whole realistic failure
surface; the gate still fails closed on all of them, and a genuine bug now
surfaces instead of being hidden as "sync unavailable". Drops the
broad-exception pylint disable and the per-file BLE001 ignore in pyproject.
- Rename _cli_gate._is_due -> _should_lock: too easily confused with the pure
local predicate gate_is_due it wraps. The new name says what it decides.
- Rename the list `satisfied` -> `satisfied_slots` in _reconcile_after_fetch;
it's a list of slots, not a boolean.
No behaviour change; 100% coverage retained.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FtSmzACzJSGyEatgK9exkd
Mirrors the CI-green safeguards rolled out repo-wide (see screen-locker):
- scripts/check_requirements_sync.py: pre-commit hook that fails if a
package import isn't declared in requirements.txt. On rollout it caught
a real latent gap — diet_guard/_mcp.py imports pydantic but it was only
present transitively (via mcp); now declared explicitly.
- scripts/ci_mirror.sh: pre-push gate = requirements.txt-only venv
(hash-gated) running `pre-commit run --all-files` + `pytest`, replacing
the plain pytest-coverage pre-push hook.
Only the tooling files + the pydantic declaration are committed; unrelated
in-flight work in the tree is left untouched.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018PMRRVVkeRyrSaaqueMZKG
The PC gate forced a re-log when a meal was already logged on the phone
but the 15-min sync had not yet propagated it. Two ways to close that gap:
- The gate now pulls the shared log right before it would lock -- and only
when a lock is otherwise due, so the network cost is paid on the rare
tick that matters. A meal already pushed by the phone then unlocks
without re-entry. Fails closed: any sync error (no token, network, disk
write-back) falls through to the local lock decision, never skips it.
- A "Fetch from sync" button on the lock screen runs the same pull on
manual request: it auto-unlocks when the pull satisfies every pending
slot, advances to the next meal on a partial pull, or reports no change.
Disabled in --demo so the sandbox never triggers a real sync/push.
- Both paths share one fail-closed wrapper, _sync.pull_shared_log(), which
also keeps the single broad-except in one place.
- _write_log is now atomic (temp file + Path.replace): the gate and the
15-min sync timer can now both write the log, so a torn read must be
impossible.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FtSmzACzJSGyEatgK9exkd
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
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
Expose diet_guard's read surface and one gated write action as typed MCP
tools (diet_guard._mcp) so Claude Code and its subagents can query today's
intake and -- with explicit confirmation -- log a meal without shelling out
to the interactive gate CLI.
- _mcp.py: FastMCP stdio server. Read tools get_status, list_today,
get_slots expose today's CONSUMED kcal/macros and the qualitative
consumption band only -- never the raw daily budget, remaining number,
body weight, or protein target (those would back out the deliberately
hidden budget). Gated write log_meal previews unless confirm=True and
degrades gracefully on write failure. Nutrition is grouped into a Macros
model (kcal + optional protein/carbs/fat) to keep the tool under the
arg-count limits with no lint suppression, mirroring the CLI's grouping.
Logs to stderr; no secret (HMAC key / sync token) is ever returned.
- .mcp.json + scripts/setup_mcp.sh: registration via a dedicated
~/.venvs/diet-guard-mcp interpreter; mcp is an optional-dep, kept out of
the systemd/CLI system-python path.
- 100% coverage of the new module; verified end-to-end over stdio.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LQGQj8Ne6TriBExv91nG8v
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>
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
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
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
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
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
Adding the Flutter app/ directory made setuptools' auto-discovery see two
top-level packages and refuse to build, breaking install.sh's pip step.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RH2BHCKbDTiYJUMG3rb9nq
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
Pulls every other device's pushed log from GitHub-backed dumb storage,
merges it with the local log, and pushes this device's own merged copy
back -- the PC half of the diet-guard-app sync plan.
- _sync_merge.py: pure union-by-id merge, tombstone always wins, legacy
(time, desc) dedup for pre-id entries. Commutative and idempotent.
- _sync_github.py: minimal GitHub Contents API client (list/get/put),
distinguishing a 404 on an unused path from the repo itself being
unreachable.
- _sync.py: orchestration -- pull, merge, re-sign every persisted entry
regardless of origin, write, rebuild the food bank, push. Re-signing
unconditionally is load-bearing: an unsigned phone-origin entry would
otherwise be silently dropped on the very next read once a machine
holds the shared HMAC key.
- _foodbank.rebuild_food_bank(): the "replay a full log into a fresh
bank" entrypoint the Python side was missing (the Dart port already
had its equivalent). Backs sync's bank-rebuild step.
- New diet-guard-sync.service/.timer (15-minute cadence, headless, a
separate unit from the gate so a held lock can't stall sync) and a
new install.sh step to install them.
- Created the private kuhyx/diet-guard-sync GitHub repo for storage.
Incidental to this feature: adding the `sync` subcommand pushed _cli.py
past the repo's 500-line cap, so `gate`'s CLI glue moved out alongside
sync's into _cli_gate.py/_cli_sync.py -- same split pattern already used
for the gate window logic itself, not a sync-specific design choice.
338 tests, 100% branch coverage. Verified importing and running cleanly
under /usr/bin/python (the production interpreter), not just the dev
venv -- the gap that caused the earlier 3-day outage.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FU3f5KQ1GHXsbbSecfVEyF
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
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
Lays the Python-side groundwork for the phone companion app's eventual
log sync (Milestone 0 of the diet-app-as-wise-balloon plan):
- log_meal() stamps every entry with a UUID id for union-by-id merging
across devices.
- undo_last_today() now tombstones (deleted: true, re-signed) instead of
popping the entry, so a sync merge can't resurrect a stale copy of
something already undone on another device.
- log_meal() accepts components, the full per-item macros for a
composite meal, so a food bank rebuilt purely by replaying the log can
recover every component's standalone nutrition.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FU3f5KQ1GHXsbbSecfVEyF
A one-off `pip install "diet_guard @ git+..."` from a scratch
directory was used during the original cutover; that's a non-editable
snapshot that a later git push silently never reaches. Document the
correct deployment convention (install.sh from a durable ~/diet-guard
clone) so this doesn't drift again.
Rewrites python_pkg.diet_guard imports to diet_guard, vendors the
shared as_float coercion helper, drops the monorepo PYTHONPATH from
install.sh and the systemd unit (package is now pip-installed), and
scaffolds standalone lint/test config matching testsAndMisc's real
enforced bar (pylint --fail-under=10 with tests excluded and the
use-implicit-booleaness/consider-using-with disables, mypy's actual
disabled-error-code set, ruff ALL, bandit, 100% branch coverage).
test_demo_opens_window and test_bare_gate_due_opens_window never mocked
wait_for_display(), so they silently relied on a real, reachable X
display being present. On a dev machine this is always true, so they
passed; on a true headless CI runner (no X server at all) each test
blocked for the real 60s timeout before failing, explaining the CI
run's 131s duration. Mocking it to True (matching the existing
test_gate_due_but_display_not_ready_defers pattern for the False case)
restores hermeticity with no production code change.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A7vbgtFfZmfxJtN5DdtJky
MealGate now composes gatelock.GateRoot + gatelock.LockWindow instead of
inheriting the deleted _GateWindow/_GateRoot, and its HMAC signing goes
through gatelock.log_integrity. This is the first of three migrations
(diet_guard -> screen-locker -> wake_alarm) extracting the lock-window
mechanics that diet_guard's own _GateWindow proved out into a shared,
reusable package. Window-mechanics tests moved with the code; diet_guard's
suite now only tests its own wiring (LockConfig choice, hook delegation).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XCdT46zV8hESDvbgYMGDLt
Split diet_guard/_gatelock.py, wake_alarm/_alarm.py, and the
usage_report.py/_usage_report_parsing.py pair into focused
sub-modules so every Python file is <= 500 lines, satisfying
test_file_length.py. Install python-kasa into .venv (declared in
requirements but missing after the 3.13->3.14 venv upgrade),
fixing 8 failing smart_plug tests and restoring 100% coverage.
Also includes prior in-progress work from the working tree: the
wake_alarm Progress/View/Hardware field-grouping refactor,
brother_printer query module + tests, diet_guard foodbank/state/cli
updates, new shared coerce/logging_setup helpers, morning_routine
orchestrator tweaks, dwm window-manager config, gaming scripts, and
misc maintenance/digital-wellbeing script updates.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add the diet_guard package: a screen-locking meal-logging gate that fires
on 4-hour slots (08/12/16/20) and records calories/macros, persisting an
autocompleting food bank.
- Trigger fix: the systemd timer fires at session start (Persistent=true)
before lightdm has written ~/.Xauthority, so the gate crashed with a
TclError instead of locking the screen. Add wait_for_display() /
_display_is_ready() in _gatelock.py and wire it into _cli._cmd_gate so the
gate retries on the next tick instead of crashing; add
Environment=XAUTHORITY=%h/.Xauthority to the service as belt-and-suspenders.
- Food-bank hardening: a transiently corrupt food_bank.json was warned about
on every keystroke and then silently overwritten (data loss). _read_bank
now quarantines it via _quarantine_corrupt_bank() (warn-once + timestamped
backup) before starting fresh.
- Multi-item meals: new _meal.py (MealItem, meal_total, MEAL_SOURCE),
remember_meal() + _upsert() in _foodbank.py, and a "+ Add item" control in
the gate that logs both the individual items and the composite meal.
- Bundle resolve_nutrition's manual macros into a ManualMacros dataclass to
stay within the argument-count limit.
diet_guard at 100% branch coverage; full pre-commit suite passes.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>