Skip to content

Save favorites with the measure they were built with - #290

Merged
d3mocide merged 2 commits into
mainfrom
claude/food-log-favorite-units-w1mbkn
Sep 16, 2026
Merged

d3mocide merged 2 commits into
mainfrom
claude/food-log-favorite-units-w1mbkn

Conversation

@d3mocide

Copy link
Copy Markdown
Owner

Building a food in cups and saving it as a favorite collapsed the portion to grams, so reopening the favorite to edit it showed 237 g instead of the 1 cup that was picked. Favorites now remember the measure.

What changed

Schema — favorite_items gains quantity + unit (migration 0034_favorite_item_portion). Both nullable: favorites saved before this, and items entered in grams, keep displaying in grams exactly as they do today. quantity_g is still the resolved weight everything computes from — the measure only rides along so the builder can show it back.

Draft items gain unit_grams (grams in one of that unit). That's what lets the portion row render and edit the amount in cups while still committing grams downstream. The saved quantity is re-derived from the item's current weight, so a portion changed after it was added (½× chips, a typed value) saves as the matching count of its own unit rather than drifting out of sync.

Portion row (DraftItemList) now reads [ 2 ] cup · 480g when a measure survived, and [ 150 ] g when it didn't. Editing the quantity converts back to grams on the way out. A small draft-string state keeps a half-typed value (0., an empty field) from snapping back mid-keystroke.

Household-measure labels — USDA ships these with the count baked in ("0.5 cup"), which would have read as 3 × 1 cup. draftPortion splits the count out, so 3 × "0.5 cup" reads as 1.5 cup — same grams either way.

Consolidation — one favoriteItemFromDraft / draftFromFavoriteItem pair now serves every surface that saves or opens a favorite (log sheet, favorites route, calculator tab, quick tab, ingredient search), replacing five divergent inline copies of the same mapping. The shared-favorite copy in family.py carries the measure through too.

Favorites list — the expanded ingredients drawer shows the measure as a small badge next to the item name; the numeric Weight column is unchanged.

Behavior notes

  • Opening an existing favorite in the builder now anchors the ½×/2× chips to the saved weight (base_weight_g), matching every other builder surface. Previously the favorites edit view let them drift as the weight changed.
  • NutritionCalculatorCard has no unit concept in its builder, so favorites saved from there continue to store grams only.

Testing

  • pnpm type-check, pnpm lint (1 pre-existing warning in IngredientBuilder, unchanged), pnpm test — 162 passed, 32 new
  • mypy luma --ignore-missing-imports — clean, 78 files
  • pytest — 201 passed
  • Alembic chain verified: single head at 0034_favorite_item_portion

New tests cover the portion helpers, the build → save → reopen round trip (including the legacy grams fallback), and the portion row's unit rendering and gram conversion.

🤖 Generated with Claude Code

https://claude.ai/code/session_01KqXjgMRArAxubgd5f5UYgC


Generated by Claude Code

Building a food in cups and saving it as a favorite collapsed the portion
to grams, so reopening the favorite to edit it showed "237 g" instead of
the "1 cup" that was picked.

favorite_items now carries the chosen measure alongside quantity_g:
`quantity` + `unit` (both nullable — favorites saved before this, and
items entered in grams, keep displaying in grams). quantity_g stays the
resolved weight everything computes from; the measure only rides along so
the builder can show it back.

Draft items gain `unit_grams` (grams in one of that unit), which lets the
portion row show and edit the amount in cups while still committing grams.
The quantity is re-derived from the current weight, so a portion changed
after it was added (½× chips, a typed value) saves as the matching count
of its own unit rather than drifting.

USDA household-measure labels bake the count in ("0.5 cup"), so
draftPortion splits it out — 3 × "0.5 cup" reads as "1.5 cup", same grams.

One draft↔favorite mapping pair now serves every surface that saves or
opens a favorite (log sheet, favorites route, calculator, quick tab,
ingredient search), and the shared-favorite copy in family.py carries the
measure through too.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KqXjgMRArAxubgd5f5UYgC

Copy link
Copy Markdown
Owner Author

Backend — lint, type-check & tests is red, but not from this PR's changes.

The job failed at the Audit dependencies (pip-audit) step. The two steps that actually check this diff both passed:

  • Lint (ruff) — passed
  • Type-check (mypy) — Success: no issues found in 78 source files
  • Audit dependencies — failed, 10 known vulnerabilities in 3 packages
  • Tests — skipped (the job stopped at the audit step)

Same failure on the base branch: run 31128880028 on main at d49e0b6 — the exact commit this branch is based on — failed at step 7 Audit dependencies with Tests skipped, ruff and mypy green. Every PR run since Aug 10 (#286–#289, #291) fails the same way. This is the CVE feed catching up to pinned lockfile versions, not anything in the diff.

What pip-audit reports:

Package Pinned Advisory Fixed in
aiohttp 3.14.1 PYSEC-2026-3545 3.14.3
aiohttp 3.14.1 PYSEC-2026-3546, PYSEC-2026-3547 3.14.2
cryptography 49.0.0 PYSEC-2026-3552 50.0.0
pip 26.1.2 PYSEC-2026-3721 26.2

No open PR fixes these — the current dependabot PRs cover alembic, uvicorn, litellm, sqlalchemy and pnpm/action-setup, and all of them are red for this same reason. So there's nothing to port into this branch.

Proposed fix, as its own PR rather than widening this one (it touches the lockfiles for every service, and cryptography 49→50 is a major bump worth isolating):

cd backend
pip-compile --allow-unsafe --generate-hashes --output-file=requirements.lock pyproject.toml \
  --upgrade-package aiohttp --upgrade-package cryptography
pip-compile --allow-unsafe --extra=dev --generate-hashes --output-file=requirements-dev.lock pyproject.toml \
  --upgrade-package aiohttp --upgrade-package cryptography --upgrade-package pip

aiohttp and cryptography are transitive (litellm, pywebpush, argon2-cffi), so they only move in the lock. The pip entry is worth a look on its own: requirements-dev.lock pins pip==26.1.2 via --allow-unsafe, and the CI install log shows it downgrading the runner's pip 26.2.1 to the vulnerable 26.1.2. Dropping the pip pin instead of bumping it may be the better call.

Backend checks for this diff were run locally against Python 3.12 and pass: ruff clean, mypy luma --ignore-missing-imports clean (78 files), pytest 201 passed. Migrations — apply & check and Frontend are green on this PR.


Generated by Claude Code

@d3mocide
d3mocide merged commit a2b78d9 into main Sep 16, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants