mirror of
https://github.com/kuhyx/diet-guard.git
synced 2026-11-10 19:27:14 +01:00
Document the configurable meal schedule
The rules that are expensive to rediscover: integer-only arithmetic and why (Python and Dart round differently), the count clamp and why a duplicate slot hour is worse than it looks, the last+2h cutoff and its midnight clamp, and the required-argument decision in _slots.py / slot.dart. Split into docs/meal-schedule.md rather than inlined -- CLAUDE.md hit 276 lines against the 250 cap, the same split the Flutter rules already use. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159b4E5hh512jYUeaJVNxfM
This commit is contained in:
parent
c0564c05a2
commit
ed6d283ff8
25
CLAUDE.md
25
CLAUDE.md
@ -1,7 +1,8 @@
|
||||
# CLAUDE.md — diet_guard
|
||||
|
||||
A log-to-unlock gate. Every ~30 min `diet-guard-gate.timer` checks whether a
|
||||
meal slot (08:00, 12:00, 16:00, 20:00) elapsed unlogged; if so it opens a
|
||||
meal slot (08:00, 12:00, 16:00, 20:00 by default — the schedule is
|
||||
user-configurable, see below) elapsed unlogged; if so it opens a
|
||||
fullscreen Tk window that blocks the desktop until the user logs what they
|
||||
ate. It also tracks a daily calorie/macro budget, seeded from biometrics at
|
||||
`init` and freely editable afterward on either device. A History tab
|
||||
@ -166,6 +167,16 @@ changing either hides the entire local food log).
|
||||
is `diet-guard-gate.timer`.
|
||||
- Never run `flutter create --platforms linux` in `app/`.
|
||||
|
||||
## The meal schedule
|
||||
|
||||
The slot hours are user-configurable: a first meal, a last meal, and how
|
||||
many meals fall between them, evenly divided. Default `(8, 20, 4)` is the
|
||||
old hardcoded 08/12/16/20. Four load-bearing rules (integer-only
|
||||
arithmetic, whole-hour slots, the count clamp, the `last + 2h` cutoff), the
|
||||
forward-only history, and the two edit surfaces are documented in
|
||||
[docs/meal-schedule.md](docs/meal-schedule.md) — read it before touching
|
||||
`_slots.py` / `slot.dart`.
|
||||
|
||||
## Averages (`_averages.py` / `average_service.dart`)
|
||||
|
||||
Three load-bearing rules; changing any silently changes what "under budget"
|
||||
@ -187,9 +198,15 @@ phone show both.
|
||||
## Do NOT
|
||||
|
||||
- Don't relax the meal-slot logic without re-reading `docs/design.md`. The
|
||||
off-hours clamp in `slot_for_log`/`slotForLog` (before 08:00 → 08:00, after
|
||||
22:00 → 20:00) must stay byte-identical across Python and Dart; do NOT widen
|
||||
`elapsed_slots`, which would make every slot fall due at 23:00.
|
||||
off-hours clamp in `slot_for_log`/`slotForLog` (before the first slot → the
|
||||
first slot, after the enforcement window → the last slot) must stay
|
||||
byte-identical across Python and Dart; do NOT widen `elapsed_slots`, which
|
||||
would make every slot fall due at the end of the day.
|
||||
- Don't reintroduce floating point into the slot derivation. Python's `round`
|
||||
is banker's (`round(2.5) == 2`), Dart's is half-away-from-zero
|
||||
(`2.5.round() == 3`), so a float path silently desyncs the two devices —
|
||||
and a slot one device offers while the other does not is a checkpoint that
|
||||
can never be satisfied, i.e. a permanent lock. Integer `//` and `~/` only.
|
||||
- Don't re-add the meal builder, "repeat last meal", the reward prompt, or
|
||||
**meal photos** — all removed deliberately, enforced by
|
||||
`app/test/repo_invariants_test.dart`. Photos also took `image_picker`, the
|
||||
|
||||
@ -2,7 +2,7 @@ name: diet_guard_app
|
||||
description: "Companion phone app for diet_guard: log meals on the go."
|
||||
publish_to: 'none'
|
||||
|
||||
version: 1.0.36+1
|
||||
version: 1.0.37+1
|
||||
|
||||
environment:
|
||||
sdk: ^3.12.2
|
||||
|
||||
51
docs/meal-schedule.md
Normal file
51
docs/meal-schedule.md
Normal file
@ -0,0 +1,51 @@
|
||||
# The meal schedule (`_meal_schedule.py` / `meal_schedule.dart`)
|
||||
|
||||
_Split out of `CLAUDE.md` to keep it under the repo's 250-line cap._
|
||||
|
||||
The user sets a first meal hour, a last meal hour, and how many meals fall
|
||||
between them; the intermediate checkpoints are derived by even division.
|
||||
`MealSchedule(8, 20, 5)` → `08:00, 11:00, 14:00, 17:00, 20:00`. The default
|
||||
`MealSchedule(8, 20, 4)` reproduces the hours that used to be hardcoded, so an
|
||||
existing install sees no change until it is edited.
|
||||
|
||||
Four rules, each load-bearing:
|
||||
|
||||
- **Integer arithmetic only** — see the `Do NOT` entry below. The shared
|
||||
test-vector table plus an identical 0..23 × 0..23 × 2..6 sweep in
|
||||
`test_meal_schedule.py` and `meal_schedule_test.dart` is what catches a
|
||||
cross-language divergence before it reaches a device.
|
||||
- **Slots stay whole hours.** Changing that changes the on-disk log's `slot`
|
||||
tag and the sync wire format, on both devices.
|
||||
- **Counts are clamped to the window width** (`last - first + 1`), because a
|
||||
narrow window would otherwise round two meals onto the same hour, and slot
|
||||
hours are set members, dict keys *and* notification ids — a duplicate
|
||||
silently drops a checkpoint. The two edit surfaces reject such input with a
|
||||
message; `normalized()` clamps it, which is the defence against a peer's
|
||||
corrupt sync data rather than the input contract.
|
||||
- **The enforcement cutoff is `last + 2h`, clamped to midnight.** It is a
|
||||
grace period for a late dinner, deliberately independent of meal count.
|
||||
Unclamped, a 23:00 last meal gives 25 and `hour < cutoff` is vacuously
|
||||
true — the window never closes.
|
||||
|
||||
`_slots.py` / `slot.dart` take the schedule as a **required** argument. A
|
||||
default there would let a call site missed in a refactor keep deriving the old
|
||||
fixed hours on one device only, which is the split brain that can lock the
|
||||
user out; making it required lets mypy and `flutter analyze` enumerate the
|
||||
sites. Callers resolve it at the impure edge through
|
||||
`_meal_schedule_store.current_schedule()` / `MealScheduleService.current`.
|
||||
|
||||
The history is forward-only (`_meal_schedule_store.py` /
|
||||
`meal_schedule_history.dart`), so switching from four meals to five does not
|
||||
retroactively mark past days as having missed a checkpoint. It seeds the
|
||||
default at `1970-01-01` **before** recording today's value — same ordering
|
||||
requirement as `_budget_history.py`. It syncs as `sched:<YYYY-MM-DD>` fields
|
||||
on the existing `budget` CRDT record, so devices predating the feature relay
|
||||
them untouched.
|
||||
|
||||
Editable on both surfaces: the app's Settings screen
|
||||
(`settings_meal_schedule.dart`) and the gate's History tab
|
||||
(`_gatelock_scheduleedit.py`).
|
||||
|
||||
**Notification ids are slot hours.** `syncToSlots` must sweep the whole 0..23
|
||||
id space, not the current schedule's slots — otherwise a schedule change
|
||||
orphans the ids it no longer contains and they nag forever.
|
||||
Loading…
Reference in New Issue
Block a user