Commit Graph

10 Commits

Author SHA1 Message Date
af461d3157 Split _state.py and _tk_fakes.py; make the food-log redirect fail closed
Eighth batch of the 250-line sweep (over-cap count 28 -> 25).

  _state.py       366 -> 197  (+ _state_today.py, _state_sync.py)
  test_state.py   400 -> 221  (+ test_state_today.py, test_state_sync.py)
  _tk_fakes.py    368 -> 167  (+ _tk_fakes_containers.py, _tk_fakes_canvas.py)

_state.py is the file conftest patches FOOD_LOG_FILE on, so the split is
constrained rather than free: `FOOD_LOG_FILE` and the two functions that open
it stay put, and the derived read model (_state_today) and the sync/undo
helpers (_state_sync) reach the file only through _state's private
_read_raw_log/_write_log. Callers were rewired to import from the new modules
directly rather than via a circular re-export.

That constraint is now enforced instead of merely documented. Moving a reader
of FOOD_LOG_FILE into a sibling would leave conftest's patch pointing at a
name nothing reads: check_patch_targets.py still exits 0 (the path resolves)
and the runtime is unchanged (a file write, not a network call), so the suite
would go green while writing to the real ~/.local/share/diet_guard and the
next sync tick would publish it. test_state_redirect.py asserts both halves --
only _constants and _state may name the constant, and the redirect
demonstrably moves a real write off the live path. Verified it fails closed by
adding a FOOD_LOG_FILE reader to _state_today: it named that module exactly.

_tk_fakes.py keeps _FAKE_TK/_FAKE_TTK and gains an explicit __all__, since the
re-exports are load-bearing and ruff would otherwise strip them as unused. The
identity canary from the previous commit covers the split: breaking one entry
in the assembly (Canvas=object) fails 8 gate tests loudly rather than silently
building real widgets.

Verified: 706 Python tests / 100% branch coverage, check_patch_targets 0,
pre-commit clean, ~/.local/share/diet_guard byte-identical.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RA9ynjqXPnexRQnR5oAuCC
2026-08-15 20:21:26 +02:00
372e8a19f6 Remove the Snack chip from the phone's slot selector
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
release-apk / release (APK) (push) Waiting to run
The chip was the only control that logged a meal against no slot at all
(`slot: null`). Removing it leaves the row with just the four hour chips,
so every logged meal now satisfies a real slot.

Storage keeps its null-slot support on purpose. Entries already on disk
and arriving over sync carry no `slot` key, and the `slot != null` filters
in log_storage_service.loggedSlotsToday / _state.logged_slots_today are
what stop them from retroactively satisfying meal slots -- so both are
documented as load-bearing rather than deleted as dead code.

Also corrects the MCP tool's docstring, which claimed its slot argument
"may be None early in the day"; it falls back to slot_for_log, which
clamps to a real hour and never returns None.

Adds the widget test the chip row never had -- being untested is why the
chip read as unused.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MPheLM86xUN9TGf34Ygtfz
2026-08-14 22:31:09 +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
be94b4e147 Sync before locking the gate + manual "Fetch from sync" button
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
2026-07-11 17:54:38 +02:00
e5b80fd610 Add cross-device log sync (Python half of Milestone 3)
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
2026-06-22 19:36:27 +02:00
888c877048 Add sync-ready log schema: stable id, tombstone undo, composite components
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
2026-06-22 18:22:24 +02:00
843f5e0221 Extract diet_guard from testsAndMisc as a standalone repo
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).
2026-06-22 12:18:39 +02:00
205b214d78 Migrate diet_guard to the shared gatelock backend
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
2026-06-21 18:16:45 +02:00
84898c0272 feat: split oversized modules for 500-line limit, fix kasa coverage gap
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>
2026-06-14 07:19:37 +02:00
400f89b469 feat(diet_guard): add meal-logging screen-lock gate with trigger fix
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>
2026-06-10 22:32:39 +02:00