Adds GET /export/json that bundles inventory and saved recipes into a
single timestamped JSON file for data portability. The export envelope
includes schema version and export timestamp so future import logic can
handle version differences.
Frontend: new primary-styled JSON download button in the Export card with
a short description of what is included.
Closes#62
When a barcode is not found in Open Food Facts, the service now tries
Open Beauty Facts and Open Products Facts before giving up. All three
share the same API format; only the host URL differs.
When all databases miss, the scan endpoint sets needs_manual_entry=true
in the result. The frontend detects this, shows a calm informational
message, and switches to manual entry mode automatically.
Also fixes a latent bug where not-found scans showed 'Added: item to
pantry' due to the success condition checking barcodes_found (always 1)
instead of added_to_inventory.
Closes#65
#12 — partial consume:
- POST /inventory/items/{id}/consume now accepts optional {quantity}
body; decrements by that amount and only marks status=consumed when
quantity reaches zero (store.partial_consume_item)
- OFFs barcode scan pre-fills sub-unit quantity when product data
includes a pack size (number_of_units or 'N x ...' quantity string)
- Consume button shows quantity-aware label and opens ActionDialog
with number input for multi-unit items ('use some or all')
- consumeItem() in api.ts now returns InventoryItem and accepts
optional quantity param
#60 — disposal logging:
- Migration 031: adds disposal_reason TEXT column to inventory_items
(status='discarded' was already in the CHECK constraint)
- POST /inventory/items/{id}/discard endpoint with optional DiscardRequest
body (free text or preset reason)
- Calm framing: 'item not used' not 'wasted'; reason presets avoid
blame language ('went bad before I could use it', 'too much — had excess')
- Muted discard button (X icon, tertiary color) — not alarming
Shared:
- New ActionDialog.vue component for dialogs with inline inputs
(quantity stepper or reason dropdown); keeps ConfirmDialog simple
- disposal_reason field added to InventoryItemResponse
Closes#12Closes#60
- Backend: new /api/v1/feedback/attach endpoint uploads image to
Forgejo as an issue asset, then pins it as a comment so the
screenshot is visible inline on the issue
- Frontend: file input in feedback form (all types, max 5 MB)
with inline thumbnail preview and remove button
- Attach call is non-fatal: if upload fails after issue creation,
the issue is still filed and the user sees success
- Screenshot state clears on modal reset
Closes#82
- Settings: add unit_system key (metric | imperial, default metric)
- Recipe LLM prompts: inject unit instruction into L3 and L4 prompts
so generated recipes use the user's preferred units throughout
- Frontend: new utils/units.ts converter (mirrors Python units.py)
- Inventory list: display quantities converted to preferred units
- Settings view: metric/imperial toggle with save button
- Settings store: load/save unit_system alongside cooking_equipment
Closes#81
Adds full meal planning workflow to Kiwi:
- Weekly meal plan creation with configurable meal types (Paid gate)
- Drag-and-assign recipe slots per day
- Prep session generation with sequenced task lists and time estimates
- LLM-assisted full-week plan and timing fill-in (BYOK-unlockable)
- Community feed (local ActivityPub-compat + cloud federation)
- Build Your Own recipe tab with assembly templates
- Save/bookmark any recipe with star rating, notes, and style tags
- Shopping list export from built recipes
- Tab reorder: Saved > Build > Community > Find > Browse
- Auto-redirect from empty Saved tab to Build
- Custom ingredient injection persists in candidate list
- z-index fix: save modal above recipe detail panel
- Route ordering fix: /recipes/saved before /{recipe_id} catch-all
- Persist built recipes to recipes table on /build so they get real DB IDs
and can be bookmarked via saved_recipes (FK was pointing at negative IDs)
- Populate missing_ingredients in build_from_selection() from role_overrides
vs pantry diff -- backend now owns shopping list computation
- Remove client-side cartItems tracking; shopping list derived from
builtRecipe.missing_ingredients instead
- Fix saved_recipes 422: mount saved_recipes router before recipes router in
routes.py so /recipes/saved isn't captured by /recipes/{recipe_id}
- Bump SaveRecipeModal z-index to 500 (above detail-overlay at 400)
- Replace "Add to pantry" primary action with "Grocery list" clipboard copy;
"Add to pantry" demoted to compact secondary button
Wires the three Build Your Own API routes into the recipes router,
registered before the catch-all /{recipe_id} route to avoid shadowing.
Adds 5 endpoint tests covering template list count/shape, candidate
response structure, successful recipe build, and 404 on unknown template.
Adds GET /community/posts, GET /community/posts/{slug}, GET /community/feed.rss,
GET /community/local-feed, POST /community/posts, DELETE /community/posts/{slug},
POST /community/posts/{slug}/fork, and POST /community/posts/{slug}/fork-adapt (501 stub).
Wires init_community_store into main.py lifespan. 7 new tests; 115 total passing.
- Add GET /{plan_id}/prep-session endpoint so frontend can retrieve existing sessions without creating
- Fix list_plans response_model from list[dict] to list[PlanSummary] with proper _plan_summary() mapping
- Replace assert in store.update_prep_task with ValueError (assert is stripped under python -O)
- Add day_of_week 0-6 validation to upsert_slot endpoint
- Remove MagicMock sqlite artifact files left by pytest (already in .gitignore)
- upsert_slot: raise 422 immediately if meal_type not in VALID_MEAL_TYPES
- update_prep_task: assert whitelist safety contract after dict comprehension
- CreatePlanRequest: week_start typed as date with must_be_monday validator; str() cast at call site
- PrepTask: frozen=True; build_prep_tasks rewired to use (priority, kwargs) tuples so frozen instances are built with correct sequence_order in one pass (no post-construction mutation)
- Move deferred import json to file-level in meal_plans.py
- Fix test dates: "2026-04-14" was a Tuesday; updated request bodies to "2026-04-13" (Monday)
Replace hand-rolled feedback.py with make_feedback_router() from
circuitforge_core.api.feedback. Update tests to mount the shared
router on a minimal FastAPI app and mock at the core module level.
- feedback.py: add GET /feedback/status endpoint (returns {enabled: bool})
so frontend can probe on mount instead of optimistic-enable; remove
unused get_db import
- FeedbackButton.vue: probe /feedback/status on mount, start hidden;
drop redundant 503-hide path (status probe makes it redundant)
- pyproject.toml: declare requests>=2.31 (used by feedback.py Forgejo calls)
- tests/api/test_feedback.py: 7 tests — status endpoint (no-token, token,
demo mode), POST 503/403, happy path with mocked Forgejo, 502 on error