# Roadmap Ideas and planned enhancements. Grouped by area. No commitment to order or timeline. Items marked ~~strikethrough~~ have been implemented. --- ## From README (moved 2026-07-27) **Coming soon:** - **Cross-domain Dashboard.** Configurable widgets that correlate nutrition, activity, sleep, and body stats (e.g. sleep duration vs weight trend). - **PWA passkeys.** WebAuthn-based passwordless sign-in on the browser side, matching the biometric flow already live on Android. **Future:** - **iOS app.** Pending hardware and Apple Developer account access. --- ## Wellness: Reporting & Insights ### ~~Phase 1: Trends tab~~ *(done, sparklines on each metric card)* ### ~~Phase 2: Derived insights~~ *(done)* - ~~Sleep debt, rolling 7/14/30-day deficit~~ - ~~Chronotype, early bird / night owl from average sleep midpoint~~ - ~~Daily Readiness score, HRV + RHR + sleep + activity penalty~~ - ~~Stress Management score, smoothed HRV + RHR + sleep~~ - ~~Sleep start/end stored as `sleep_start_min` / `sleep_end_min`~~ ### Phase 3: Dashboard / cross-domain correlation A dedicated **Dashboard** page that correlates data across all domains (nutrition + activity + sleep + body stats). - Widget grid, user-configurable - Example widgets: - Sleep duration vs weight trend overlay - Steps vs net calories (burned – eaten) - "Best week" pattern summary - Today at a glance (streak tracker) --- ## Wellness: Additional Integrations ### ~~Garmin Connect~~ *(done: experimental, OAuth 1.0a)* ### ~~Withings~~ *(done: body comp, ECG, vascular age, metabolic age, EDA, segmental)* ### ~~Fitbit GPS / Activity Routes~~ *(done: TCX parsed via location OAuth scope, route map on workout detail)* ### ~~Google Health Connect (Android)~~ *(done: v0.35, see Phase 2 entry below)* ### Apple Health (iOS) - Requires a native iOS wrapper (WebKit `WKWebView` + Swift bridge) - Or: export-based import (Apple Health XML export → parse + ingest) ### Gadgetbridge (direct integration): likely deferred - Gadgetbridge is the Android FOSS app that talks to a long list of fitness trackers (Mi Band, Amazfit, Pebble, Bangle.js, Casio, older Fitbits, Huawei, Sony, etc.) without manufacturer accounts. Big match for NutriTrace's self-hosted ethos. - **Already works via Health Connect today.** Gadgetbridge can write steps, heart rate, sleep, weight, and several other metrics to Health Connect; NutriTrace's existing Health Connect integration picks them up with zero code change. This is the recommended path and covers the "does it work with my Mi Band" case for the vast majority of askers. Document it in README + DEPLOY as the supported route. - **Direct integration would buy access to data Health Connect doesn't carry** (full SpO2 history, device-specific metrics, raw activity segments). Three implementation options, all Android-only: - Intent broadcast listener, register a receiver for Gadgetbridge's broadcasts, write to wellness_data. ~3-5 days. Sparse data fidelity (broadcasts fire on events, not on backfill). - Shared SQLite read of Gadgetbridge's local DB, requires the user enabling shared storage + ongoing schema-version chasing as Gadgetbridge evolves. ~1-2 weeks plus maintenance tax. - OpenTracks-style intent contract, cleanest but requires Gadgetbridge to expose a stable surface they haven't committed to. - **Hold unless multiple users specifically ask** for data the HC path can't deliver. The route-through-HC story is good enough that direct integration is hard to justify on cost / value. --- ## Android App (Capacitor) ### ~~Phase 1: Native shell + offline mode~~ *(done)* - ~~Capacitor 8 wrapping Svelte PWA~~ - ~~Local SQLite via @capacitor-community/sqlite~~ - ~~NativeSetup wizard (Use Locally / Connect to Server)~~ - ~~Native barcode scanner (@capacitor-mlkit/barcode-scanning)~~ - ~~Native camera for food/meal/avatar photos~~ - ~~CapacitorHttp for OFF/USDA search (CORS bypass)~~ - ~~Platform detection (isNative, apiUrl, getServerUrl, getNativeMode)~~ - ~~Server connection with merge dialog~~ - ~~Service worker disabled in Capacitor~~ - ~~App icon at all mipmap densities~~ ### Phase 2: Sync & platform integrations - ~~**Differential sync**: only push/pull changed records since last sync (timestamp-based), instead of full merge on every connect~~ - ~~**Offline cache in server mode**: mirror server data in local SQLite so the app works when server is down; sync diff when back online~~ - ~~**Health Connect integration**~~ *(done, released in v0.35; in production)* - **Background sync**: periodic background task (via @capacitor/background-runner or WorkManager bridge) to sync diary/foods/wellness with server when connected - ~~**Local full backup (ZIP)**: create full backup on device (JSZip) including images, for phone-to-phone transfer without a server~~ *(done, v0.35.2-beta)* - **iOS app**: Capacitor already supports iOS; need HealthKit integration + App Store setup ### Phase 3: Distribution - **Obtainium**: list NutriTrace as an Obtainium-discoverable app so users can auto-track new GitHub Releases without checking manually. Requires the GitHub Releases page to consistently attach a signed APK with a stable filename pattern. - **IzzyOnDroid F-Droid repo**: lower bar than F-Droid main (no reproducible builds required). Path to broader F-Droid eventually. - **F-Droid main repo**: requires reproducible builds. Larger lift; defer until 1.0+ has had a few public releases. - **Play Store**: gated on 1.0 GA. Needs developer account, listing assets (icon, screenshots, description), privacy policy URL, content rating, target SDK compliance. --- ## ~~Shared Food Database~~ *(done: Food Sharing, experimental)* - ~~Visibility: private / group / specific users~~ - ~~Copy-on-use model for shared items~~ - ~~Bulk share from Settings~~ - ~~"From Others" source filter in Foods~~ --- ## Diary Enhancements ### ~~Calorie budget bar in diary header~~ *(done: bottom bar with progress strip)* ### ~~Meal-level macro summary~~ *(done: per-meal P/C/F bar + text)* ### ~~Quick-log (voice / text)~~ *(done: Smart Log v3, hold Trace button; water logging added v0.38.2-beta)* ### ~~Dynamic Calorie Goal~~ *(done: v0.38.3-beta, Experimental)* - ~~Fixed (current, default) vs Dynamic (device calories_out × factor)~~ - ~~Gate behind connected Fitbit/Garmin/Health Connect, hidden if no device~~ - ~~Factor: 0.80 (lose) / 1.00 (maintain) / 1.20 (gain)~~ - ~~Uses yesterday's final burn, falls back to fixed goal if no data~~ - ~~Touchpoints: diary bar (dynamic pill), goals page (badge + annotation)~~ - ~~Statistics goal line integration~~ *(done, v0.39.11, labeled "Base Goal" when dynamic mode is on)* ### ~~Adaptive TDEE~~ *(RELEASED 2026-05-10, Experimental)* - Server lib `server/lib/adaptive-tdee.js` runs the calc on demand from `GET /api/goals/adaptive-tdee`. 35-day rolling window, 21-day minimum for "ready"; weight series interpolated between known measurements (priority: Withings > Fitbit > Garmin > Health Connect, falls back to manual `body_stats.weight`); intake is sum of `diary.items` calories. Linear-regression slope × 7700 kcal/kg = daily energy balance; `tdee = avg_intake − balance`. - Settings → Goals → Calorie Goal Mode is now a 3-way segmented control: Fixed | Dynamic | Adaptive. Goal factor (Lose / Maintain / Gain) applies to all three. Adaptive mode is selectable any time but falls back to the fixed goal until 21 valid days are collected. - Goals page shows a readiness card with progress bar + the learned TDEE, trend (kg/week), confidence %, and weight source. "How it works" expander documents the math + best-practices ("weigh frequently, log consistently, don't switch goals mid-window, weigh at the same time of day"). README has a matching `## Adaptive TDEE` section. - Diary bar + Statistics goal-line use the adaptive value when ready (📈 cue). Statistics labels the goal line "Base Goal" when in adaptive or dynamic mode, same as before. ### ~~Intermittent Fasting tracker: v1~~ *(RELEASED 2026-05-10, opt-in)* - Logged fasts on a new `fasts` table (user_id, start_at, end_at, goal_hours, soft-delete). CASCADE on user delete; included in differential sync via /api/sync/pull and push. - Server endpoints under /api/fasts: start, /:id/end, GET list, GET /active, PATCH (edit start/goal/notes), DELETE (soft). - src/stores/fasting.js exposes activeFast / fastHistory / elapsedMs stores + startFast/endFast/deleteFast/fastingStats helpers. - FastingWidget.svelte on the Diary (above meals), opt-in via Settings → Diary → 'Show Fasting Tracker'. Three states: idle (14:10 / 16:8 / 18:6 / 20:4 / OMAD / Custom hours picker), active (elapsed + progress bar + target end + End Fast), goal-reached (green styling + optional notification via existing notify('fastingNotifyOnGoal', ...) helper). - FastingInsights.svelte at the bottom of Statistics, 4 stat tiles (avg / longest / current streak / longest streak) + 14-day mini-chart rendered with pure CSS bars (no Chart.js dep). Renders only when fastingEnabled is on. - AI Trace gains get_fasting_history(days) tool, callable as "what's my fasting streak?" etc. - Settings keys (USER_PREFS): fastingEnabled, fastingDefaultHours, fastingNotifyOnGoal. ### ~~Intermittent Fasting: v1.1 polish~~ *(RELEASED 2026-05-10)* - **Edit start time on active fast**: tap 'Started 8:32 PM' on the active widget; datetime-local picker calls PATCH /api/fasts/:id. Guarded against future times and >7 days ago. - **Last-fast hint on idle state**: 'Last fast: 14.3h · ended 2h ago' under the title when at least one fast is in history and no fast is active. - **Saved custom goal presets (max 3)**: name + bookmark-add inside the Custom mode; saved presets appear as inline chips alongside the built-in 14:10/16:8/18:6/20:4/OMAD chips. Long-press to remove. USER_PREFS adds fastingCustomPresets. - **History with delete**: FastingInsights gets an expandable 'Show Recent Fasts' list (last 20 completed fasts); per-row × button with confirm dialog removes via DELETE /api/fasts/:id (soft-delete). - **Recurring schedule**: auto-starts a fast at a chosen time on selected days of week. Both client (FastingWidget mount + visibilitychange) and server scheduler (15-min tick) check independently; deconflicted via fastingScheduleLastFired (YYYY-MM-DD) + active-fast 409. 4-hour grace window so a late app-open doesn't backdate a missed early-morning schedule. USER_PREFS adds fastingScheduleEnabled, fastingScheduleTime, fastingScheduleDays, fastingScheduleGoal, fastingScheduleLastFired. ### Intermittent Fasting: deferred (possible future polish) - **Food-window enforcement**: block or warn when the user logs food during an active fast. Currently the app stays neutral (the fast keeps running, food still logs). Opt-in toggle would be the safe shape so it doesn't paternalize. Plus a 'last meal in your eating window' awareness so the warning is contextual. - **Goal-reached celebration polish**: currently the widget flips to green + a notification fires. Could add: a one-shot toast ('You hit your 16h goal, keep going or end?'), a haptic buzz on native via @capacitor/haptics, an optional sound chime. Same channel/styling as the existing goal-celebration confetti for nutrient goals, extending celebrateGoal() to cover fasting is the minimal change. - **Auto-detect fasting from diary gaps**: infer a fast from the natural gap between yesterday's last logged meal and today's first. Clever but unreliable for users who skip breakfast vs. truly fast. Defer until / unless we have a way to distinguish 'forgot to log' from 'fasted'. - **Day-of-week-specific goals**: different goal hours per weekday (e.g. 16:8 weekdays, 20:4 Sundays). Custom presets + manual start cover this today; automating it would interact awkwardly with the single recurring-schedule design, likely needs a per-day mini scheduler grid in Settings. - **Multiple concurrent schedules**: eat-stop-eat (24h fast 2x/week) vs. daily 16:8 vs. occasional extended fasts. Current model is one recurring schedule. Could grow into 'fast templates' with multiple schedules pointing at named templates. - **Live tile / lock-screen widget**: Android live-update widget showing elapsed time without opening the app. Capacitor doesn't expose AppWidget directly; would need a custom plugin. ### Saved Activities library - Save commonly-logged activities (e.g. "Morning hike, 60 min, 300 kcal") as reusable templates - Picker UI on the Activity sheet to choose a template, optionally edit before saving to diary - Interim partial-coverage already released: name autocomplete from past entries (``) + Trace AI calorie estimation from name + duration - Defer until multiple users specifically ask for the full library, autocomplete may already cover most of the value - Originally requested by tellis82 in #12 ### Meal Planning for multi-user households (deferred, trigger to build: 2+ user requests) Lets one user pre-fill meals into another user's diary. Natural fit for households where one person plans / preps for the family. Overlaps with the coach-prescribes-meals pattern LiftTrace already echoes via `coach_prescriptions`, so the family bones for this concept exist. Two tiers, cut Tier 1 first if any of this happens at all: **Tier 1, Push meal to another user's diary** - One-time action. Pick a meal / recipe from your library, pick a user, pick a date and meal slot, send. Lands on the recipient's diary as a normal entry with a small "planned by <you>" badge so they see it wasn't theirs. - Recipient can edit / delete just like their own entries; calorie + macro counts contribute to their goals the same way. - New permission grant: "X can plan meals for Y" (per-pair, bidirectional opt-in). Pattern off LiftTrace's trainer-client model rather than a broad role. - Server: new `planned_meals` table OR extend diary with a `planned_by_user_id` nullable column; POST `/api/diary` accepts a `target_user_id`. - UI: "Send to user…" action in Meal kebab; permission grant in Settings → User Management; push notification on receive (`notifMealPlanned`, default on, toggleable). - ~2-3 days of work. Self-contained, no schema migration for existing diary entries. **Tier 2, Meal plan template** (only if Tier 1 lands well) - 7-day grid: 7 days × 4 meals = 28 slots. Build the plan once, assign to users, optionally have it recur weekly. - Mirrors LiftTrace's `program_assignments` table shape. - New tables: `meal_plans` (id, name, owner_user_id, rows), `meal_plan_assignments` (plan_id, assigned_to_user_id, start_date, recurrence). - Server: scheduler tick applies due plan entries to recipients' diaries the day they're due. - UI: plan editor calendar grid + assignment manager. - ~1 week of work. Bigger surface. **Skipped tiers:** - Coach / dietitian mode (multi-tenant client management, billing, notes per client, etc.) is its own product. Not in NT's scope. **Open design questions for when this gets built:** - Permission model granularity: per-pair grant vs broad "planner" role. Per-pair is more flexible, broad is simpler. Pattern off LiftTrace. - Conflict resolution: if recipient logs their own food on a planned meal slot, do we merge, replace, or keep both? Default: keep both as separate diary items; recipient deletes manually if needed. - Notification toggle: receiving user should be able to mute `notifMealPlanned` if a planner is over-eager. - Goal interaction: pre-planned meals count toward goals the same way as self-logged. No special handling needed. **Naming decision:** Settle on **"Meal Planning"** for the user-facing label (generic, MFP-familiar). Don't narrow to "Family" or "Household" since the mechanism also serves the coach-pre-plans use case if someone wants that workflow without us building Tier 3. Originally proposed 2026-06-01 conversation. No issue filed yet. --- ## Foods / Nutrition ### ~~Fuzzy food search~~ *(done: v0.39.11, `_fuzzyMatch` + `_editDist` in Foods.svelte: exact substring → word-by-word → edit-distance ≤1 for words ≥4 chars; covers local foods, meals, recipes)* ### ~~"Most Used" / "Recently Used" food sort~~ *(done: `usage_count` and `last_used_at` columns on foods/meals, two new sort modes in the Foods picker, Android sync wiring fixed in rc.29. Originally tellis82 #12/#6.)* ### Nutrient calculator overlay - Select two foods → side-by-side comparison panel ### Recipe scaling from servings count - Input "I want 6 servings" → auto-scale all ingredient quantities ### ~~Nutrition CSV importer: v1~~ *(RELEASED, MyFitnessPal, LoseIt, Cronometer, generic-spreadsheet adapters)* - v1 supports MyFitnessPal, LoseIt, Cronometer, and a generic spreadsheet shape. Adapters in `server/lib/nutrition-import/`, route at `/api/nutrition-import/{preview,commit}`, UI is `SettingsNutritionImport.svelte` mounted under Settings → Backup with an EXPERIMENTAL badge. Skip / Merge / Replace per-date semantics. Auto-detects locale (US M/D vs EU D/M), CSV delimiter (comma vs semicolon), and meal-name aliases; falls back to the user's last meal slot for unmatched labels. - v2 candidates: **MacroFactor** (no published schema, needs real user export samples to pin against; cut as "experimental, bring your own export" once we have 2-3 samples), **FatSecret** (no user-facing CSV; would need OAuth API connector, separate feature), **YAZIO** (unverified schema, defer until a user sends a sample). Waistline import was deprioritized at user request (not a migration audience NT shares). - Driving issue: community thread 2026-04-29. ### ~~Bulk Food Import: paste JSON / upload CSV~~ *(RELEASED to public in rc.28)* Issue #21 (duplaja). Distinct from the Nutrition CSV importer above , that one ingests **diary** entries from MFP/Cronometer/etc; this is for adding **foods** to the user's catalog from a hand-rolled or LLM- extracted source. Released to dev: single entry point at Settings → Backup → "Bulk Import Foods" (deliberately not added to the Foods page Add menu, see decision note below). Source files: src/lib/food-import-template.js (template generator), src/lib/food-import-parse.js (parser + validator), src/components/foods/BulkImportModal.svelte (two-tab modal with preview pane). **Decision: single Settings-only entry point** (NOT also a Foods-page Add menu item as originally drafted). Rationale: bulk import is expected to be a once-or-twice-a-year operation per user; the Foods- page Add button stays single-tap for the common case. Reversible, if demand for a Foods-page shortcut shows up in feedback, adding one is a ~10 minute change. **Why it's worth doing.** Users with foods that aren't on Open Food Facts currently have to type each one into the Food Editor by hand. With this they can snap a label photo, ask an LLM to extract the nutrition into the documented schema, paste, and commit. Same flow unlocks bulk-add for power users (CSV). **Entry points** (single modal, two front doors): 1. **Foods page → Add menu → "Bulk Import (JSON/CSV)"**: primary, discoverable where users go to add foods. 2. **Settings → Backup → "Bulk Import Foods"**: secondary, for users who think of imports as a Settings thing. **Modal layout:** - Two tabs: **JSON** and **CSV**. - Each tab has a **"Download Template"** link at the top. - JSON tab: textarea (paste) plus a file-upload alternative. - CSV tab: file upload (typing CSV by hand is no one's idea of fun). - **Preview pane** below: parsed foods (name + calories + portion) rendered before commit, with row-level errors flagged. - Submit only enables when everything parses. **Template generation, CRITICAL DESIGN POINT:** The template MUST be generated programmatically from the `NUTRIMENTS` constant in `src/lib/nutrition.js` (the existing source of truth for the app's nutrient catalog). DO NOT hard-code the template, a future addition to NUTRIMENTS would silently leave the template stale and the imported foods would lose those nutrients. Implementation sketch: ```js // src/lib/food-import-template.js import { NUTRIMENTS } from './nutrition.js'; export function buildJsonTemplate() { const nutrition = Object.fromEntries(NUTRIMENTS.map(n => [n.id, 0])); nutrition.calories = 200; // example values for the demo row return { foods: [{ name: 'Example Food', brand: 'Brand Name', barcode: '', portion: 100, unit: 'g', category: '', nutrition, }], }; } export function buildCsvTemplate() { const headers = ['name','brand','barcode','portion','unit','category', ...NUTRIMENTS.map(n => n.id)]; const example = ['Example Food','Brand','',100,'g','', ...NUTRIMENTS.map(n => n.id === 'calories' ? 200 : '')]; return headers.join(',') + '\n' + example.join(',') + '\n'; } ``` **Schema (documented in the template + a /docs page):** - Required: `name`, `nutrition.calories` - Defaults: `portion=100`, `unit='g'`, other nutrients = 0 / null - Per-row error messages so a bad row doesn't tank the whole import - Dedup-by-barcode: if a row has a barcode that already exists for the user, mark as a skip (consistent with rc.21 rapid-scan dedup) **Server side:** reuse the existing POST `/api/data/import` which already accepts `foodList`. Optionally add a thin POST `/api/foods/bulk` that returns inserted ids for the preview-pane round trip. **Validation pass:** 1. Parse (JSON.parse or csv split with proper quote handling). 2. For each row: check `name` non-empty, `calories` numeric. 3. Coerce all nutrient values to Number (or 0 if blank). 4. Build summary: N rows valid, M rows skipped (duplicate barcode), K rows errored. 5. Render in preview pane. User clicks Commit → POST. **Out of scope for v1:** image attachment per-row (would need URLs or embedded base64), recipes/meals (foods only), edit-after-preview (commit-as-shown). **Driving:** Issue #21. Reporter has a userscript that already extracts nutrition labels into JSON via an LLM; this would let them paste the output directly. ### ~~Local Open Food Facts data dump for offline barcode / name lookups~~ *(RELEASED: `server/lib/off-local.js` + `off-local-scheduler.js` run a local mirror with barcode + product-name lookups. Nightly refresh scheduled server-side. What's still open: none, item's original ask is fully covered.)* Issue #22 (duplaja). Possible-but-not-planned. Would let self-hosters in air-gapped or strict-egress environments do barcode + food-name lookups against a locally-stored OFF dump instead of hitting the live API. Same path would also keep barcode scanning useful during OFF outages. **Why this isn't in the near-term queue:** - OFF bulk data is ~2-3 GB compressed, 10-15 GB raw. Serving it fast for barcode + name lookups means a real piece of infra (SQLite with FTS5 import, or embedded DuckDB to query the parquet directly). - Nightly refresh logic, the dump changes daily; would need a cron-driven re-import to stay current, plus disk space for two copies during the swap. - Today's OFF integration already proxies through the server, so the browser never talks to OFF directly. The "no outside API calls" concern is partially addressed at the network shape level. - Audience is narrow: most self-hosters have internet egress; OFF outages are rare and short. - The Bulk Food Import feature (queued above) covers "I want full control over my catalog without touching OFF" for users who curate their own foods, which is the more common shape of this request. **If demand grows (3+ users asking), starting point:** 1. Admin setting in Settings → Connected Services → Open Food Facts: "Local OFF dump path" (filesystem path to a parquet or pre-imported SQLite file). 2. Import script (or in-process via worker) that reads the parquet subset (just the columns we use: code, product_name, brands, nutriments, image URLs) and writes to a dedicated `off_local` SQLite database with FTS5 on product_name + brands. 3. Lookup flow: check `off_local` first; fall back to live API if the barcode isn't found AND the admin allows it. 4. Refresh mechanism: nightly cron OR a manual "Refresh OFF dump" button in admin Settings. 5. Bundle a Python script for stripping the dump to just the columns we use (the reporter suggested this). --- ## Goals ### Rolling weekly / monthly goals - Option to track goals over a week or month period, not just daily - Useful for intermittent fasting or flexible dieting approaches ### ~~AI-suggested goal adjustment~~ *(done: v0.38.4-beta, Goal Insights toggle in Settings → AI Assistant)* --- ## Statistics ### Body composition chart - Weight / body fat % / muscle mass plotted together (Withings data available) ### ~~Weekly summary email~~ *(done: v0.38.5-beta, configurable day/time, push + email)* --- ## AI Assistant (Trace) ### Food photo logging via Trace chat: auto-pipe to Smart Log - *Image attachments to Trace chat already released*, users can attach a meal photo and Claude/GPT-4o vision identifies foods + estimates portions in plain text reply. - **Still pending:** intercept a vision response that looks like a food list, pipe it into the Smart Log matcher, and open the Smart Log review modal for confirmation before adding to diary. Reuses existing Smart Log infra; no new UI needed beyond what Trace chat already supports. ### ~~Local / self-hosted LLM support~~ *(done: generic `oai-compat` provider in `src/lib/aiChat.js` accepting any OpenAI-compatible base URL, no API key required; covers Ollama, LocalAI, LM Studio, vLLM, llama.cpp. Megathread/README list this. Tool-use caveats stand: smaller local models silently break Goal Insights + Smart Log; documenting verified models is a follow-up.)* --- ## UI / UX Polish ### Shared NutritionFactsBox card: phased rollout TraceApps brand cohesion: CookTrace already uses an FDA-style "Nutrition Facts" card for pantry items (view layout) with a tap-Edit toggle that swaps the card for an input-box form (edit layout). The pattern is universally readable (mirrors physical packaging) and gives the TraceApps family a recognizable shared component. Plan to roll it into NutriTrace in phases so no single change carries too much blast radius. **~~Phase 1, Photo log confirmation card~~** *(RELEASED rc.42, `NutritionFactsBox.svelte` in `src/components/ui/`, `propose_quick_calories` tool in `aiChat.js`, review card renders in Trace chat with Discard / Edit / Add to Diary)* - Port CookTrace's `NutritionFactsBox.svelte` into NT as `src/components/ui/NutritionFactsBox.svelte`. Maintain visual parity with CookTrace; treat divergence as a bug. - Add a new AI tool `propose_quick_calories` that returns a structured estimate (name, serving, kcal, macros) instead of writing to the diary. Used when the user attaches a meal image to Trace OR explicitly asks "log this, but review first." - Trace.svelte renders the card inline in the chat thread when the tool fires, with three actions: Discard / Edit / Add to Diary. - Discard removes the card from the thread. - Edit flips to the input-box form variant (CookTrace pattern). - Add to Diary calls `log_quick_calories` with the (possibly edited) values, then collapses to a small "Logged X kcal to " row. - System prompt rule: when a meal photo is attached, use `propose_quick_calories` instead of `log_quick_calories`. Never auto-write from a photo. **~~Phase 2, Existing food detail view~~** *(RELEASED, `src/components/ui/FoodDetailSheet.svelte` renders the read-only card from the Foods list info-icon entry point; tap-to-log muscle memory preserved)* - Add a read-only food-detail-view that renders the same card for any saved food. Reached via an info-icon entry point in the food list (not via direct tap, which stays the existing tap-to-log shortcut so the everyday flow stays fast). - "Edit" button on the card flips to the existing FoodEditor form. "Add to Diary" routes to the existing quantity prompt. - Inflexible default: tap on a food in the library still opens the quantity prompt as today. Don't break the muscle memory. **Phase 3+, Meals / Recipes / Diary edit sheet** *(further out)* - Roll out the same card pattern to Meals view, Recipes view, and the diary-item edit sheet (currently four-color macro pills). One surface per release, no big-bang. - Stop at any phase if user response is mixed. The pattern is self-contained per surface. **Why phased, not all at once** - NT's everyday "tap food → log it" flow is fast and central; inserting a card view in the middle of it would slow the most-common interaction for marginal value beyond Phase 1. - Each new surface is a real test-surface expansion; rolling one at a time keeps regressions traceable. - CookTrace was built around the card from day one; NT users learned "tap = edit" since launch and switching them in one release is a jarring change. Phased rollout lets the new pattern land in a contained context (Phase 1's photo-log card) before becoming the default elsewhere. ### ~~Empty-state polish~~ *(done: contextual empty states across Diary, Foods, Goals, Wellness, MealEditor, Settings; Foods + Statistics empty-state messages added v0.39.11)* ### ~~Error visibility / sync status~~ *(done: offline `cloud_off` badge in the top-bar surfaces server-unreachable state; toast-on-retry for failed pushes)* ### Accessibility - ActionSheet: add `role="dialog"` and focus trap - Form inputs: explicit `