AI Feature · MenuMeld v2.30+

Eat your meals in the right order.

When you plan two or more meals in the same time slot, MenuMeld instantly asks Gemini for the best sequence — soup first, carbs last, dessert at the end — and delivers it to your Lock Screen as a clear liquid-glass Live Activity, no tapping required.

Build a meal, see the order

Pick at least 2 dishes from a time slot. The real backend fires Gemini immediately on every notify — here we just show the ~1 second round-trip. Result matches the real app output format: ordered steps + brief reasons + an overall explanation.

Your plate

0 selected
M
MenuMeld
Blood-Sugar Friendly Order
Select 2 or more meals, then tap "Suggest eating order" to see the Live Activity preview.
Clear liquid glass · Lock Screen preview

What happens when you add a meal

Four steps, no debounce. Gemini is invoked immediately on every notify, and the old in-flight task for the same slot is superseded. Click a step to see what it does.

01 · iOS

Meal changed

Add, edit, delete, or copy a meal in today's slot.

02 · iOS

Notify backend

Fire-and-forget POST /v1/ai/eating-order/notify.

03 · Server

Gemini call

Immediate asyncio task. Supersedes older jobs.

04 · iOS

Live Activity

APNS push-to-start + poll on foreground.

Key design decisions

Instant fire-and-forget

The notify endpoint spawns the Gemini task via asyncio.create_task and returns immediately. No Redis debounce, no sweeper — every notify kicks off analysis right away.

🎯

Only today, only 2+

Triggers exclusively when the active date is today and the slot has 2 or more meals. Single-meal slots get no suggestion (there's no order to suggest).

📲

Push-to-start Live Activity

APNS delivers the result straight to the Lock Screen and Dynamic Island — no need for the user to open the app. A 2-minute fallback sweeper retries on transient failures.

🌐

Three languages

English, Traditional Chinese (zh-Hant for TW/HK/MO), Simplified Chinese (zh-Hans for CN/SG). Gemini is prompted in the user's locale so reasons read naturally.

🛡

Supersedes prior tasks

A new notification for the same (user, date, slot) marks in-flight tasks as "superseded" and invalidates their cache, so you never see a stale order.

🚦

Rate-limit aware

Runs inside the shared AI daily/monthly budget. If you've hit the limit, the eating-order job silently skips rather than failing loudly.

The three endpoints & the data model

Everything the iOS client actually calls, with the exact field names from the backend schema.

POST /v1/ai/eating-order/notify
Fire-and-forget. Body: date, time_slot, meal_description, locale, live_activity_push_token. Returns {status: "accepted"} and immediately schedules the Gemini task.
GET /v1/ai/eating-order/status
Polled on app foreground and date change. Returns status: none | pending | processing | completed plus the full EatingOrderResponse on completion.
DELETE /v1/ai/tasks/{id}
Acknowledges a completed task so it's not re-shown. The iOS app marks tasks acknowledged automatically on first poll.
EatingOrderStep
order: int, meal_name: str, brief_reason: str — one entry per dish, ordered 1..N.
EatingOrderResponse
steps: [EatingOrderStep], overall_reason: str, time_slot: str, model: str (defaults to gemini-2.5-flash-lite).
Live Activity shape
EatingOrderAttributes.ContentState carries steps, overallReason, and isAppOpened. The last flag triggers auto-dismissal when the user opens the app.