Commit Graph

8 Commits

Author SHA1 Message Date
0ca95eda5b fix: keep the catering dish queue alive across a submit
Some checks are pending
file-length / file length <= 250 lines (push) Waiting to run
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
Walking a real 5-dish delivery through the gate by hand showed the
"(N more to go)" the lock promises was a dead letter: `_prefill_next_dish`
had exactly one caller (the initial poll), so logging the first dish cleared
the form and stranded the rest. Reaching them meant clicking "Today's
delivery" again -- and the button's fetch is unguarded, so that was a fresh
login-plus-three-request walk per dish.

`_finish_slot` now prefills the next queued dish, carrying the
"Logged HH:00 ..." confirmation into the same status line so advancing the
queue does not swallow it.

The caterer plans five meals against four default slots, so a dish is still
queued when the last slot unlocks. It is already banked, so `_unlock` names
it rather than dropping it silently.

Also pins the phone conclusion that was previously reasoning only: a verbatim
capture of a real imported bank now round-trips through `FoodBankRecord` in
`app/test/kuchnia_bank_interop_test.dart`, covering the diacritics and the
`t` stamp, and the casefold/toLowerCase agreement the two devices depend on.

Verified against the live panel with state redirected to a scratch HOME
(real ~/.local/share/diet_guard byte-identical before and after): the
autoload prefills silently on window open, a failed autoload stays silent
while the button reports, the after-log warm-up runs off-thread (0.62s vs
0.28s guarded) and nothing is logged until "Log & Continue".

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QQpCiPaSbyRpwsLkH3dgsX
2026-08-22 23:36:53 +02:00
26f1770090 feat: import Kuchnia Wikinga catering deliveries into the food bank
Pulls the day's delivered dishes and their real per-portion macros out of
panel.kuchniavikinga.pl, so a catering meal is one keypress instead of five
retyped numbers.

A delivered meal is not an eaten meal, so nothing is ever logged unattended:
dishes land in the *curated* bank, and entries are written only after an
explicit confirmation. Auto-logging would let the gate satisfy its own
checkpoint from a delivery note, and would record what the courier dropped off
rather than what was eaten.

Three surfaces:
  - `diet-guard kuchnia [--log] [--yes]`
  - the lock screen's "Today's delivery" button, which prefills the meal form
    dish by dish (the user still clicks Log & Continue)
  - a warm-up after each logged meal, on the existing background thread

All event-driven; no new systemd timer. The two automatic paths go through
`refresh_delivery_once`, guarded to one fetch per day.

The panel is an undocumented white-labelled Dietly API; the endpoint map was
recovered from its JS bundles and confirmed against live responses. Notes,
including the traps, are in docs/kuchnia-wikinga.md.

Load-bearing details:

* Macros are per portion and `weight` is that portion in grams -- proven, not
  assumed: 4P+4C+9F reproduces the stated calories within ~1% on every meal of
  the captured day. `_energy_is_consistent` re-runs that check per dish and
  drops any that fails; a per-100g mix-up is invisible in a payload and would
  silently skew every logged meal by a factor of several.
* `bank_dishes` compares nutritional fields before calling `add_manual_entry`,
  which restamps `t` unconditionally. Without the compare, every refresh
  republishes the whole curated bank to every peer. Tests assert on call count.
* Duplicate log suppression reads today's synced log, not a local marker:
  `log_meal` mints a fresh uuid, so a re-import after marker loss would merge
  as duplicates on every peer.
* The gate hook is in `on_focus_ready`, not `_cli_gate._should_lock`:
  `gate_is_due()` never reads the food bank, so a refresh there could not
  change the lock decision while adding a third-party round trip to the ~105ms
  `gate --check` fast path.
* `requests` is lazy-imported so the gate's not-due tick does not pay for it.
* Credentials and the cached session cookie live in ~/.config/diet_guard at
  mode 600, outside the synced tree. The credentials file is written by hand,
  never by the package -- the same contract as sync_token.

`_GateDashboard` is merged into `_GateMealFlow`, and both "pull it in from
elsewhere" flows share one `_PullFlows` mixin: pylint's max-parents counts
every MRO entry, so the gate's controller chain had no room for a new class.

Also adds a side-by-side row for the two pull buttons -- stacking them cost
42px and pushed the tab past the viewport at 1366x768.

989 tests, 100% branch coverage, pre-commit clean.
2026-08-22 23:04:22 +02:00
d75ed9c083 docs: track AGENTS.md and split the sync-latency rules into docs/
The previous commit turned tracked `CLAUDE.md` into a symlink to an *untracked*
`AGENTS.md`, deleting 237 lines of documentation from the repo -- the content
survived only in my working tree, and a fresh clone would have got a dangling
symlink. The symlink predates this session (it was already a type-change in the
working tree at the start); committing it without tracking its target is what
broke it.

Tracking `AGENTS.md` then tripped the 250-line cap, so the sync-latency
material moves to `docs/sync-latency.md` behind a pointer -- the same shape the
repo already uses for `docs/meal-schedule.md` and the Flutter rules. That file
carries the measurements, the three narrow-pull state invariants, the Tk
threading rules, why the gate's publish must stay synchronous under
`Type=oneshot`, the timeout asymmetry, and the `revs/` marker trap.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LJ5YnA3CERitQ57HjMu2Qz
2026-08-21 13:42:11 +02:00
ed6d283ff8 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
2026-08-16 15:03:59 +02:00
17a2c62f26 Automate CLAUDE.md's structural rules, then cut it to 189 lines
CLAUDE.md was 1101 lines and loaded into every session in this repo. Three
passes, in order: automate what a gate can check, rewrite what is left as
instructions rather than rationale, extract what still does not fit.

Automated. Five prohibitions were enforced by nothing but a paragraph asking a
future reader not to do something; they are now repo_invariants_test.dart:

  - no app/linux/ (a GTK embedder target manages ~20fps at 4K)
  - dart:io only in *_io.dart seams and lib/desktop/ -- this one is why the
    test exists at all, since dart:io does not fail a web *compile*. It throws
    at runtime, so a bad import is a blank white window that the widget tests
    happily pass through.
  - no meal builder / repeat-last-meal / reward prompt
  - no image_picker, no CAMERA permission
  - wrapper port 8732 and the diet-guard-desktop Chrome profile, both pinned
    because IndexedDB is keyed by origin and moving either hides the whole
    local food log

Each guard was canary-tested by breaking the invariant and confirming the
suite goes red -- a guard that cannot fail is decoration.

Rules whose behaviour was already covered (budget epoch seeding, sync merge,
food bank) shrink to a pointer at the test that owns them.

Where a rule's entire value was its rationale ("the removal is the feature"),
the why moved into the name and reason string of the gate that now enforces
it, so it lives somewhere executable instead of somewhere quotable.

Extracted. The vendored flutter/flutter rules were 789 of the 1101 lines --
72% of the file, none of it about this repo. Split into five docs/ topic files
under the cap and linked. Verified content-complete: 664 non-blank lines in,
664 out, zero missing.

Python 692 tests / 100% coverage unchanged; Flutter 548 -> 555 (+7 new).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MK3HCZrM75sRHL1gL8usve
2026-08-15 16:39:57 +02:00
d631f8a141 Add change explanation: desktop-web-build
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
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 17:27:42 +02:00
ce68dcd82c Add change explanation: calendar-streaks-budget-sync
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
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dHac1iYQKTMZEtL7xDJvA
2026-07-13 21:45:11 +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