Skip to content

About MacroIndex

MacroIndex is built for fast daily macro tracking. Log food quickly, set goals once, and keep your progress visible without extra clutter.

Daily macro tracking with goal history.
Quick add, saved foods, and meal logging.
OCR label scanning with confidence indicators.
AI-assisted nutrition lookup and voice logging.

Legal

Wellness estimate disclaimer

MacroIndex provides general nutrition estimates for wellness and educational use. It is not medical advice, does not diagnose or treat conditions, and does not replace care from a licensed clinician or dietitian.

Add to Home Screen

iPhone / iPad (Safari)
  1. Open MacroIndex in Safari.
  2. Tap the Share button.
  3. Select "Add to Home Screen", then tap Add.
Android (Chrome)
  1. Open MacroIndex in Chrome.
  2. Tap the menu (three dots).
  3. Select "Install app" or "Add to Home screen".

Changelog

Current release: v1.11.0

v1.11.0 · 2026-08-23

Added

  • Dark mode. MacroIndex now follows your device's appearance setting: when your phone or computer is in dark mode, the whole app renders on a dark ground — every page, the navigation, the bottom dock, forms, calendars, sheets, and flash messages. Light mode is completely unchanged, and there is no new toggle or any layout/design change: the implementation is a single stylesheet (static/css/dark.css) that remaps the exact color utilities the templates already use to dark equivalents inside @media (prefers-color-scheme: dark).
  • Grounds move to a dark gray ramp (page #111827, surfaces #1f2937) with the text ramp inverted; purple/amber/emerald/red notice tints keep their hue as translucent dark equivalents.
  • Brand, status, and data colors are deliberately untouched: the purple brand fills, calendar status dots, and chart segment colors are identical in both modes (purple text/borders lighten slightly for contrast on dark).
  • Native controls (inputs, scrollbars, pickers) switch via color-scheme: dark; the wordmark renders white on dark; the Swagger API docs panel stays a light island since Swagger UI ships its own light theme.

Notes

  • No template layout, component, spacing, or typography changes — verified by comparing computed styles in light mode before and after (identical).
v1.10.1 · 2026-08-22

Fixed

  • Deleting a food that belongs to a meal no longer fails. delete_food now removes the food's MealItem rows before deleting the food. Previously the MealItem.food_id foreign key (NOT NULL, no cascade) was left dangling: on Postgres the delete raised an IntegrityError and 500'd — so a food that had ever been added to a meal could never be deleted — and on SQLite the row orphaned and silently understated the meal's totals. The meal itself is preserved (an emptied meal simply shows no items); meals are intentionally not auto-deleted because FoodLog.meal_id is itself a foreign key to meals. The success message notes how many meals the food was removed from. (Launch blocker from the release review.)
  • AI lookup fallback model now actually runs. app/ai_client.py referenced FuturesTimeoutError in the primary→fallback retry branch but never imported it, so any primary-model failure raised NameError — the fallback-model retry was dead code and diagnostic logs recorded the bogus NameError instead of the real upstream error. Added the missing from concurrent.futures import TimeoutError as FuturesTimeoutError import.
  • API write endpoints reject non-finite and oversized numbers with 400, not 500. _parse_number now rejects NaN/Infinity (which slipped past the minimum check because nan comparisons are always false and +inf is never below a finite minimum) and integers larger than a Postgres INTEGER column can hold. Previously POST /api/food_logs with servings: Infinity crashed with an unhandled 500, and non-finite floats could persist and serialize back as invalid JSON, poisoning downstream totals.

Improved — Ask AI

  • "Verified" now means verified. An AI result is only badged as verified when its cited source is credible, not merely present. The source URL must be on an authoritative nutrition domain (.gov/.edu, USDA, FDA, Open Food Facts, etc.) or on the brand's own domain (a brand token appears in the host). Previously any URL string next to a self-reported "official" tag earned the verified badge, so a plausible-looking or fabricated link passed. Items that fail this check are shown as estimates with a short reason.
  • Fabricated macros are caught. Each verified item's calories must be consistent with its protein/carbs/fat via the 4/4/9 rule (within tolerance); internally impossible numbers are downgraded to an estimate to be double-checked, regardless of the cited source.
  • Structured source URL. The model response schema gains a dedicated source_url field (used first, with the old note-scraping kept as a fallback), so verification no longer depends on regex-scraping free text.
  • Lower AI cost on generic foods. Plain whole-food queries (e.g. "a banana", "2 scrambled eggs", "grilled chicken breast") now skip the paid web-search tool and answer from reference values, while branded/restaurant queries still use web search. Controlled by OPENAI_WEB_SEARCH_SMART (default on); set it to false to always use web search.
  • Macro precision. Macro grams in the AI schema are now decimals instead of integers, so low-macro items (e.g. 0.4 g fat) are no longer rounded away.
  • Every AI answer is labeled by how it was retrieved. Results now carry a retrieval marker — Web (looked up live via web search) or Reference (standard values from AI knowledge) — shown as a chip next to the Verified/Estimated badge on both the Ask AI page and the Today sheet. Foods saved from an AI result are tagged ai_web or ai_memory alongside the existing verification tag, so a memory-derived answer backed by a credible source carries both markers. The marker survives the verified cache via the resolver name, so cache-served answers keep their original label.
  • Transient failures no longer poison the cache. If an upstream AI call fails mid-lookup, the app no longer overwrites an existing (stale-but-verified) cache entry with a "not verified" marker, and it tells the user the sources were unreachable rather than implying no data exists.

Notes

  • Regression tests added: deleting a food that is in a meal (tests/test_meals.py); the AI primary→fallback retry path attempting both models on timeout, source-credibility gating, calorie-consistency gating, and the smart web-search classifier (tests/test_ai.py); and non-finite/oversized numeric rejection across the write endpoints (tests/test_api_write.py). Suite is now 142 tests.
  • Still recommended (not in this release): password-change/reset should invalidate other sessions and the 30-day remember cookie (needs a rotating per-user session stamp); harden the Apple OAuth account-linking path before enabling APPLE_OAUTH_ENABLED.
v1.10.0 · 2026-07-30

Added

  • Interactive API docs (Swagger UI). /api/docs now renders the OpenAPI spec with a self-hosted, pinned Swagger UI (swagger-ui-dist 5.32.11, vendored under static/vendor/swagger-ui/ — no CDN, CSP unchanged). Includes Try it out: paste a personal API key via the Authorize button and exercise any endpoint against your own account. /api/openapi.json remains the single source of truth, and the deploy smoke test now verifies the docs page and the vendored bundle.
  • Personal API keys (admin opt-in). Once an administrator enables API access for an account, that user can generate their own API keys from the Profile page (up to 5 active). The raw key is shown exactly once at creation; only a hash and an 8-character prefix are stored. Keys can be revoked from the same card, which lists each key's name, prefix, creation date, and last use.
  • Admin API access toggle (off by default). API access is opt-in: new and existing accounts start with it off, and the admin console's Users & plans table gains a per-user Enable API / Disable API control. Until enabled — or after being disabled — all of that user's keys return 403 api_access_disabled and new keys cannot be created; disabling never deletes keys, so access can be restored with one click. Admin accounts always have API access. The table shows an "API on" chip and each user's active-key count, and the admin API-keys table gains an Owner column now that keys are no longer admin-only.

Added (continued)

  • Per-key API rate limits. Personal keys are capped at 60 requests/minute per key (burst) and 2,000 requests/hour per account summed across all of a user's keys (sustained), so minting extra keys buys no extra budget. Over-limit requests return 429 rate_limited with a Retry-After header computing the exact seconds until the window resets. Admin keys are exempt, matching every other cap. Tunable via API_RATE_PER_MINUTE and API_RATE_PER_HOUR_USER.

Changed

  • API keys are now scoped to their owner. Personal keys read and write only the owner's data: reads are filtered to the owner, writes default to the owner, and passing any other user_id (in a query string or request body) returns 403 forbidden_user_scope. Requesting someone else's food by id returns 404 rather than confirming it exists. Admin-owned keys keep the previous unrestricted behavior, including cross-user user_id. The per-user data endpoints (foods, logs, goals, weights, water, tags, supplements, meals, and all five write endpoints) now accept personal keys; /api/users, /api/api_keys, the transaction/scan logs, and the shared food cache remain admin-key only.
  • OpenAPI spec and /api/docs updated to describe personal keys, the scoping rules, and the new error codes; spec version bumped to 1.1.0.

Fixed

  • Suspended accounts' API keys now stop working. Previously suspension blocked login and live sessions but not API-key requests; suspended owners' keys now return 403 account_suspended.
  • Keys with no owning user (possible only for legacy rows) no longer authenticate at all.

Notes

  • New users.api_access_enabled column (added automatically at boot). NULL/unset and false both mean no API access; only an explicit admin "enable" (true) grants it. Admins are exempt.
  • Test suite grows from 118 to 136 tests, covering scoping (including a full sweep of every read endpoint against foreign user_id requests and row leaks), the opt-in default, the admin toggle lifecycle, suspension, revocation, the key cap, the profile create/revoke flow, and the rate limits (per-key burst, per-user budget across keys, admin exemption).
v1.9.2 · 2026-07-29

Changed

  • Updated the legal documents to accurately describe what the Service now does. Policy version defaults bumped to 2026-07-29 so new acceptances record the updated documents.

Terms of Service - Added a full Acceptable Use Policy (Section 7) enumerating prohibited content and conduct, and — importantly — stating the automated enforcement policy users are agreeing to: two warnings then automatic permanent suspension on a third standard violation, immediate permanent suspension without warning for severe categories (child sexual content, instructions for violent wrongdoing, credible threats), cumulative lifetime counts, enforcement data retention including IP address, no refunds for accounts suspended under this section, and reporting of unlawful content to authorities. - Stated explicitly that self-harm-related requests are handled as a support matter, are not violations, and will not result in a ban. - Added Usage Limits and Fair Use (Section 8) covering per-plan daily AI/OCR allowances, our ability to change or disable those features, and that cache-served lookups do not consume allowance. - Added Subscriptions, Billing, and Cancellation (Section 9): recurring auto-renewal, cancellation via the billing portal, access through the end of a paid period, non-refundability, price changes, taxes, and complimentary access grants. - Added crisis-support resources to the "not medical advice" section, added an anti-ban-evasion clause, and noted that API keys are credentials granting broad access.

Privacy Policy - Disclosed the abuse-prevention data we now collect: flagged-event records (account, timestamp, category — not the flagged content), cumulative strike counts, and, on suspension, the IP address and user agent of the triggering request, with the purposes for each. - Disclosed AI processing in accurate detail: content-safety screening before any paid model call, the one-way hashed safety identifier sent to the AI provider, the shared nutrition-lookup cache (with a warning not to submit personal information), and that diagnostic logs are metadata only — request and response bodies are never stored. - Disclosed that scan images are never retained, and that uploaded images may be screened for prohibited content. - Added a Payments section: card details are never received or stored by us; we hold only plan level and processor identifiers. - Expanded the processor list to match reality (hosting, AI, OCR, payments, email, OAuth, error monitoring) and noted that error reports are automatically scrubbed. - Replaced the vague retention placeholders with specific per-category retention, including that usage counters are auto-deleted after about seven days and that reversing a suspension clears its enforcement record. - Pointed users at the self-service export and deletion controls that already exist on the Profile page.

AI Feature Disclosure - Documented the safety screening step, the hashed safety identifier, the metadata-only logging, the shared query cache, and the per-plan daily allowance.

v1.9.1 · 2026-07-29

Fixed

  • Fixed OCR label scanning, which was silently returning nothing in production. Three separate defects, found by running real FDA nutrition labels through the pipeline:
  • No OCR engine was reachable. USE_GCV=true was paired with a GOOGLE_APPLICATION_CREDENTIALS file path left over from the old PythonAnywhere host, so the Vision client never initialized; RapidOCR was disabled; and pytesseract cannot work on Render's native Python runtime because it has no Tesseract system binary (that only exists in the Docker image, which Render does not use). Every engine failed and the chain returned empty text without raising, so scans appeared to succeed with a blank form and "confidence: low".
  • Calories were misread on every standard label. The parser searched only forward from the word "Calories" and took the last number found, but two-column Nutrition Facts panels put the value before the word, so it picked up the adjacent % Daily Value figure instead — 230 kcal was read as 10. It now considers text on both sides, takes the nearest plausible value (5–2000 kcal), and ignores the "2,000 calories a day" reference footnote.
  • Macros were missed when OCR dropped spaces. Patterns anchored on a trailing \b, which never matches between a letter and a digit, so run-together text like TotalCarbohydrate37g silently failed. Anchors are now negative lookaheads.
  • The context-based calorie fallback no longer returns implausible values (a 0 reading previously prefilled the food form with zero).

Added

  • Google Vision credentials can be supplied as GOOGLE_APPLICATION_CREDENTIALS_JSON (the raw service-account JSON in an environment variable), which is the workable option on hosts without a persistent secrets path. The file-path variable still works where it resolves.
  • Vision client initialization failures are now logged as errors instead of being swallowed, and a missing credentials file is called out explicitly.
  • NoOcrEngineAvailable is raised when no engine can even be attempted, so a misconfigured deployment surfaces as "OCR is temporarily unavailable" rather than a blank extraction.
  • Regression tests built from the OCR output of real FDA 2014 and 2016 labels (stored as text fixtures so they run in CI without an OCR engine), plus coverage for the footnote, run-together text, and no-engine cases.
  • Explicit coverage for Google Vision's reading order: Vision emits "Calories 230" where RapidOCR emits "230 / Calories". The old parser returned 10 for both orderings — the defect was overshooting into the % Daily Value column, not the ordering — so the fix is engine-independent and Vision is now tested directly.

Changed

  • render.yaml sets USE_GCV=true; Google Vision is the production OCR engine.
  • Tightened a calorie assertion in the OCR tests that accepted any positive number, which is why the misparse went unnoticed.
v1.9.0 · 2026-07-29

Added

  • Content moderation: every Ask AI query, retry detail, and voice transcript is screened with OpenAI's free Moderation API before any paid model call. Flagged input is refused with a generic message, spends nothing, and is logged (blocked_moderation). Fails open on moderation outages so availability is never held hostage.
  • Safety identifiers: all OpenAI requests now carry a hashed per-user identifier (user parameter), so any abusive traffic is attributed by OpenAI to the individual end user rather than the whole organization account.
  • Account suspension: new is_suspended flag with a Suspend/Unsuspend control in the admin console (admins cannot be suspended). Suspended accounts cannot sign in and live sessions are evicted on their next request.
  • Abuse strike system with two severity tiers, counted as a lifetime cumulative total per account (never reset daily, so repeat offenders cannot stay under a rolling threshold):
  • Standard violations: two warnings, then automatic permanent suspension on the third strike (ABUSE_STRIKES_LIMIT, default 3).
  • Severe violations — automatic permanent suspension on the first strike. The default set follows common trust-and-safety practice: sexual/minors, illicit/violent, hate/threatening, harassment/threatening (child sexual content, instructions for violent wrongdoing, and credible threats). Tunable via ABUSE_SEVERE_CATEGORIES.
  • Self-harm categories are deliberately excluded from enforcement entirely (ABUSE_EXEMPT_CATEGORIES): such requests are still blocked from reaching the model, but record no strike and receive a supportive message with crisis resources. A calorie-tracking app is exactly where disordered-eating distress can surface, and the standard response is support, not punishment.
  • Suspension never expires on its own; only an admin can reverse it, and doing so clears the account's strike history.
  • Admin accounts are exempt from auto-suspension.
  • Ban forensics: every automatic suspension records the client IP, user agent, timestamp, and triggering violation on the account (suspended_ip, suspended_user_agent, suspended_at, suspended_reason), shown in the admin console. The console also flags when one IP has produced multiple bans, surfacing ban-evasion signup patterns. Strike log lines include the client IP. Reversing a suspension clears the stored forensics along with the strike count.
  • SafeSearch pre-screen for scan uploads when Google Cloud Vision is enabled: images likely containing adult or violent content are rejected before OCR and recorded as strikes. (With the default local OCR engines, uploaded images never leave the server.)
  • Input length caps: Ask AI queries (400 chars), retry details (300), and voice transcripts (400) are bounded server-side, limiting both abuse surface and token spend.
  • API auth-failure rate limiting: repeated invalid X-API-Key attempts from one IP are throttled with HTTP 429.

Fixed

  • Fixed user-agent capture across the app: bool(request.user_agent) is False in Werkzeug unless a browser-parsing library is installed, so truthiness-guarded reads silently stored None. Terms-of-service and privacy-policy acceptance records (at signup and during onboarding) were losing their user-agent audit trail as a result; they, and the new ban forensics, now read the User-Agent header directly.

Changed

  • New environment variables (documented in README): ABUSE_STRIKES_LIMIT, ABUSE_SEVERE_CATEGORIES, and ABUSE_EXEMPT_CATEGORIES.
  • users table gains abuse_strike_count (lifetime blocked-abuse events), shown in the admin console alongside each account.
v1.8.0 · 2026-07-29

Added

  • Write endpoints on the JSON API (admin API key), built for programmatic clients such as an MCP server:
  • POST /api/food_logs — quick-add with explicit macros, or log a saved food via food_id + servings with macros computed server-side.
  • POST /api/water_logs — one row per day; mode: set overwrites (web-app semantics), mode: add increments.
  • POST /api/weight — one entry per day, overwritten, matching the web app.
  • POST /api/supplement_logs — set a supplement's taken state for a day (upsert), with ownership validation.
  • POST /api/foods — create a saved food, including tags.
  • All writes accept optional date (YYYY-MM-DD, backdated entries stamped at that day's midnight, exactly like the web app) and optional user_id (defaults to the API key's owner).
  • OpenAPI 3.1 specification served at GET /api/openapi.json (public), covering the full read and write surface, auth scheme, and the app's time conventions — suitable for generating MCP tools directly.
  • Human-readable API documentation at GET /api/docs: server-rendered, self-hosted, CSP-safe (no third-party JavaScript).

Changed

  • API validation errors now return structured JSON ({"error": ..., "field": ...}) with HTTP 400.
v1.7.3 · 2026-07-29

Changed

  • Split the 3,700-line app/foods.py into focused modules with an acyclic import graph — no behavior change, all foods.* endpoint names preserved:
  • app/ai_client.py — OpenAI transport, bounded-retry configuration, prompt/schema constants, metadata-only transaction logging, Sentry tagging, and the shared AI/OCR burst rate limiter.
  • app/ask_ai.py — Ask AI scope gate, quantity parsing, response transformation, verified-nutrition cache, server-side conversation state, progress snapshots, save-as-food/meal flows, and the Ask AI routes.
  • app/voice.py — voice intent routing, fuzzy saved-food matching, tracking previews, and the voice-command endpoint.
  • app/scan.py — OCR scan routes, the strict-ceiling engine wrapper, and scan telemetry.
  • app/foods.py now contains what its name says: foods/meals CRUD, saved-food search, and the public pages.
  • Test stubs and monkeypatch targets updated to the new module homes; the full suite is unchanged and green.
v1.7.2 · 2026-07-29

Fixed

  • Fixed a concurrency race in Ask AI state saving caught by the first CI run: concurrent saves and clears for the same user (for example a double-submitted Ask AI form) could collide in the ORM's select-then-write pattern and fail with a 500 (IntegrityError/StaleDataError). State is now written with an atomic dialect-native upsert (INSERT ... ON CONFLICT (user_id) DO UPDATE) on both Postgres and SQLite, eliminating the race window entirely.
v1.7.1 · 2026-07-29

Added

  • Continuous integration: GitHub Actions runs the full pytest suite on every push to main and on pull requests (.github/workflows/ci.yml, Python 3.10 to match production).
  • Composite index ix_food_logs_user_logged on food_logs (user_id, logged_at) matching the hot day-range query shape (created automatically at startup).
  • limit parameter (default 1000, max 5000) on /api/food_logs, /api/water_logs, and /api/supplement_logs, which previously returned unbounded result sets.

Fixed

  • Editing a log entry's servings now recomputes macros from the source food when one is linked, so repeated edits can no longer accumulate integer-rounding drift on calories.
  • The Ask AI nutrition cache now uses the app-wide naive-local time convention instead of UTC, fixing staleness comparisons that were off by the UTC offset in the /api/chatgpt_foods?stale_only=1 filter.

Removed

  • Dead code: _openai_chat() (never called) and refresh_stale_chatgpt_cache() (background cache refreshing was deliberately dropped — the cache is a demand-driven dedupe layer, not a maintained dataset).
  • Stray test_askai_timing.py scratch file from the repository root.
  • Stale documentation: removed the PythonAnywhere legacy deployment guide, the SQLite-to-Postgres migration section, and references to files that no longer exist (RENDER_CUTOVER_RUNBOOK.md, scripts/migrate_sqlite_to_postgres.py); rewrote the README project structure to match the current codebase and refreshed docs/erd.md and docs/testing/system-map.md.

Changed

  • Modernized legacy SQLAlchemy Query.get() calls to db.session.get() in app code and tests, clearing most deprecation warnings from test runs.
v1.7.0 · 2026-07-29

Added

  • Plans and entitlements (Free / Pro):
  • Admins always have Pro access in code — the product owner never pays.
  • Admin console gains a "Users & plans" section: grant or revoke complimentary Pro (comped accounts) per user. Stripe-managed subscriptions cannot be toggled manually and say so.
  • Per-plan daily AI/OCR quotas: DAILY_AI_OCR_CAP_FREE (default 5/day) for free accounts; DAILY_AI_OCR_CAP (default 50/day) now applies to Pro/admin accounts. The global cap is unchanged.
  • Profile gains a "Your plan" card: plan badge (Free / Pro / Pro · Complimentary / Admin), live "AI usage today" meter, upgrade buttons when billing is configured, and Manage billing for subscribers.
  • Stripe subscription scaffolding (ships dormant until configured):
  • POST /billing/checkout (monthly or annual price), POST /billing/portal, and POST /billing/webhook with signature verification.
  • Webhook handles checkout.session.completed, customer.subscription.updated (including cancel-at-period-end, which auto-downgrades when the paid period lapses), and customer.subscription.deleted.
  • Admin-comped accounts are never modified by webhook events.
  • Configuration: STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET, STRIPE_PRICE_MONTHLY, STRIPE_PRICE_ANNUAL. Until set, billing routes respond gracefully and the UI shows "coming soon".
  • users table gains plan, plan_source, plan_expires_at, stripe_customer_id, stripe_subscription_id (migrated automatically at startup; existing users backfilled to free).

Changed

  • Daily AI/OCR budget enforcement is now plan-aware — the same counters, with the per-user limit resolved from the user's effective plan.
  • Added stripe==15.3.1 to dependencies.
v1.6.0 · 2026-07-29

Added

  • Daily AI/OCR spend caps, enforced at every OpenAI and OCR call site:
  • DAILY_AI_OCR_CAP (per user per day, default 50) and DAILY_AI_OCR_CAP_GLOBAL (all users, default 500); values <= 0 disable a cap.
  • Ask AI cache hits stay free — only real lookups consume budget.
  • Capped requests get a friendly "resets tomorrow" message with HTTP 429 and are recorded in the transaction logs (blocked_daily_cap).
  • This counter layer is also the enforcement point where future paid-plan quotas will plug in.
  • AI_FEATURES_ENABLED / OCR_FEATURES_ENABLED kill switches for Ask AI + voice and label scanning (confirm/cancel of an existing voice preview keeps working while disabled).
  • usage_counters table: durable counters shared across workers. Auth rate limits (login, signup, password reset, OAuth link) now use it, so they survive deploys and apply across all gunicorn workers instead of per-process memory.
  • Trends page (/trends, linked from navigation): 7/30/90-day views with calories-vs-goal bars (per-day goal markers), weight trend against goal weight, daily water intake, and stat tiles for logging streak, goal hit rate, average calories, and average protein — plus an accessible daily data table.
  • Service worker (/sw.js) and offline page: static assets are cached for faster loads and navigations fall back to an offline screen when the network is unavailable. Authenticated pages are never cached.

Changed

  • Tailwind CSS is now compiled at build time and self-hosted in production (pinned standalone CLI v3.4.17 in the Render build command). The CDN + inline config remain only as a local-development fallback when the compiled stylesheet is absent.
  • Content-Security-Policy drops cdn.tailwindcss.com from script-src when serving the self-hosted stylesheet.
  • Static assets now serve with Cache-Control: public, max-age=86400 instead of the global no-store (pages remain no-store); the stylesheet link carries a version cache-buster tied to the build.
v1.5.10 · 2026-07-29

Changed

  • Ask AI progress switched from a server-sent-events stream to instant JSON polling (GET /foods/ask-ai/progress/<id> now returns a snapshot immediately).
  • The SSE stream held a gunicorn worker thread for up to 120 seconds per lookup; with 2 workers x 4 threads, a few concurrent Ask AI users could stall every request slot.
  • The loading UI keeps the same live stage messages and its rotating-message fallback; both the Today sheet and the standalone Ask AI page now poll once per second.
  • Onboarding flow guard caches completion in the session (keyed by user id), removing one to two database existence checks from every authenticated request.
  • API key authentication narrows candidates by the stored key prefix, so each request verifies a single hash instead of hashing against every active key.
  • The Today calendar resolves each day's status against one pre-fetched goal history instead of issuing a goal query per logged day (previously up to ~31 queries per page view).

Added

  • scripts/render_post_deploy_smoke_test.py — the post-deploy validation script the README has referenced since the Render cutover.
  • Validates health, expected release version (derived from the newest changelog), security headers, SEO routes, 404 handling, and canonical redirects.
  • Optional read-only admin API check when MACROINDEX_API_KEY is set in the environment (the key is never printed or stored).
  • Regression test covering the Ask AI progress polling endpoint.
v1.5.9 · 2026-07-29

Fixed

  • Fixed a production 500 when logging foods or meals with long names: food_logs.name now matches the 512-character width of foods.name/meals.name (Postgres column widened at startup).
  • Fixed feedback screenshots disappearing on deploy/restart: screenshots are now stored in the database (app_feedback.screenshot_blob/screenshot_mime) instead of the ephemeral local filesystem.
  • Uploads are validated, downscaled to at most 1920px, and re-encoded (which also strips EXIF metadata).
  • Legacy disk-stored screenshots continue to be served, and are removed during account deletion.
  • Account data export now serializes binary columns as base64 instead of failing.
  • Fixed a per-IP rate-limit bypass: client-forgeable X-Forwarded-For headers are no longer parsed directly. Proxy headers are honored only through Werkzeug ProxyFix, enabled by the new TRUSTED_PROXY_COUNT environment variable (set to 1 on Render). This also makes request.is_secure and externally generated OAuth URLs proxy-correct.
  • Fixed potential session loss after large Ask AI results: Ask AI state moved out of the session cookie (which browsers silently drop past ~4KB) into a new server-side ask_ai_states table (one row per user, cleared on account deletion).
  • Fixed a startup migration race between gunicorn workers: Render now starts with --preload so boot-time schema migrations run once in the master process, and the app disposes pooled DB connections before workers fork.

Added

  • TRUSTED_PROXY_COUNT environment variable (documented in README, set in render.yaml) controlling how many reverse proxies are trusted for X-Forwarded-* headers.
  • ask_ai_states table for server-side Ask AI conversation state (documented in docs/erd.md).

Changed

  • Render start command is now gunicorn --preload --bind 0.0.0.0:$PORT --workers 2 --threads 4 wsgi:app.
  • Updated tests to cover DB-stored screenshots and server-side Ask AI state.
v1.5.8 · 2026-02-27

Added

  • Sentry logging and profiling controls for Flask app startup:
  • Added SENTRY_ENABLE_LOGS toggle (defaults to enabled when SENTRY_DSN is set).
  • Added SENTRY_PROFILE_SESSION_SAMPLE_RATE for profiling session sampling.
  • Added SENTRY_PROFILE_LIFECYCLE with validated values (trace/manual, default trace).

Changed

  • Updated Sentry documentation and environment examples:
  • .env.example now includes logging + profiling environment variables.
  • README.md now documents Sentry logs/profiling settings and sample values.
v1.5.7 · 2026-02-27

Added

  • Optional Sentry monitoring integration for the Flask app factory.
  • Initializes only when SENTRY_DSN is configured.
  • Uses conservative defaults (send_default_pii=false, traces_sample_rate=0.05).
  • Supports SENTRY_ENVIRONMENT, SENTRY_TRACES_SAMPLE_RATE, SENTRY_MAX_REQUEST_BODY_SIZE, and optional SENTRY_RELEASE.
  • AI/OCR observability upgrades across Ask AI, Voice, and OCR scan routes:
  • Added handled-error reporting hooks to Sentry for Ask AI/Voice/OCR fallback and error paths.
  • Added OCR operation status/latency/request-size/error metadata in ocr_scan_logs.
  • Expanded OCR metadata to include engine_version, attempt_count, result_completeness, http_status, normalized_request_bytes, original_request_bytes, and failure_code.
  • Switched OCR log storage to metadata-only and removed raw provider payload persistence (provider_response) from ocr_scan_logs.
  • Added Sentry event scrubbing for common sensitive fields (query, transcript, details, tokens, emails).
  • Added custom Sentry route tags + transaction names for ask_ai_submit, voice_command, and scan_post.
  • Added automatic release fallback to RENDER_GIT_COMMIT when SENTRY_RELEASE is not set.
  • Added optional APP_VERSION Sentry tag to correlate events with changelog versions.

Changed

  • Added sentry-sdk to Python dependencies.
  • Updated voice-command error responses to avoid leaking raw exception text to users.
  • Updated docs/env examples for Sentry + APP_VERSION usage.
  • Set SENTRY_ENVIRONMENT=production in render.yaml for production deployments.
v1.5.6 · 2026-02-26

Changed

  • Ask AI page UI alignment and action styling updates:
  • Query actions now align right with Clear left of Ask AI.
  • Result action rows are right-aligned.
  • Renamed Quick add items to Quick-add.
  • Quick-add now uses the same purple treatment as Ask AI.
  • Save as Food switched to white button styling.
  • Quick-add remains the far-right action in save rows.
  • Ask AI assumptions presentation:
  • Converted assumptions list to a collapsed expansion panel.
  • Label updated to AI assumptions.
  • Ask AI state and submit UX:
  • Entering Ask AI from another page now clears stale prior query/result state unless intentionally prefilled.
  • Starting a new Ask AI query now hides previous results immediately while processing.
  • Ask AI live progress visibility:
  • Added SSE progress endpoint for Ask AI stage updates (/foods/ask-ai/progress/<progress_id>).
  • Purple loading indicator now shows server-driven progress stages for query, estimate, and retry actions.
  • Existing POST + redirect behavior is preserved (no functional flow change to save/finalize paths).
  • Today log Ask AI sheet parity improvements:
  • Updated Ask AI sheet controls and button ordering to match Ask AI page styling patterns.
  • Added live SSE stage text to the sheet’s purple loading indicator.
  • Right-aligned sheet action rows and ensured Quick-add is the far-right action.
  • Ask AI result action buttons in the Today sheet now stay on a single horizontal row on mobile (no stacked/piled layout).
  • Added a collapsed AI assumptions expansion panel in the Today sheet to match Ask AI page reasoning/caret behavior.
v1.5.5 · 2026-02-25

Added

  • In-app feedback intake system for authenticated users:
  • Added submission flow at /feedback/new with minimal fields (type, description, optional screenshot).
  • Added user history page at /feedback/mine with mobile-first card layout and status visibility.
  • Added admin triage pages at /admin/feedback and /admin/feedback/<id> for filtering, review, and status updates.

Changed

  • Admin management and discoverability:
  • Centralized admin management on /admin.
  • Moved invite-only signup controls and invite whitelist management to the admin page.
  • Moved admin API key generation/revocation controls to the admin page.
  • Added Admin navigation links (desktop nav, mobile nav panel, and mobile more-actions sheet).
  • Added an Open admin settings shortcut on the profile page for admins.
  • Updated admin page information architecture and styling so access controls, API keys, and feedback triage are grouped in one place.
  • Simplified feedback schema and UX to remove low-signal end-user fields:
  • Removed title, repro steps, expected behavior, actual behavior, severity, feature-use-case, who-it-helps, and usage-frequency from active feedback workflow.
  • Admin search now targets feedback description + user email.
  • Ask AI and Voice quantity handling is now app-driven and more reliable:
  • Guardrail scope detection now better accepts counted food phrases (for example: 5 olives, 2 pieces of toast) while preserving strict out-of-scope blocking for non-nutrition intents.
  • Voice transcript forwarding now preserves richer dictated text so Ask AI receives the intended phrase content.
  • Ask AI item cards now display Serving used so users can verify quantity pickup before saving.
  • Updated Ask AI save behaviors for quantity-aware workflows:
  • Save as Food now stores foods as one-serving definitions while keeping macros scaled to the requested quantity for that saved record.
  • Save as Meal continues to persist item-level serving multipliers in meal breakdowns.
  • New Quick add items action now creates each parsed item as a food and immediately logs separate entries to Today with the requested multi-servings applied.

Removed

  • Removed legacy profile-based admin POST endpoints:
  • /profile/api-keys/new
  • /profile/api-keys/<id>/revoke
  • /profile/invite-only
  • /profile/invite-emails/add
  • /profile/invite-emails/<id>/remove
  • Removed stale README guidance that said admin options were in profile.

Database

  • Cleaned app_feedback persistence model to keep only active fields:
  • Retained: user linkage, type, description, status/priority, metadata, screenshot path, admin notes/response, timestamps.
  • Removed legacy feedback columns no longer used by the app flow.

Testing

  • Verified admin behavior and permissions after consolidation:
  • pytest tests/test_admin.py -> 2 passed
  • Verified Ask AI + voice + quick-add quantity flow behavior:
  • pytest tests/test_ai.py -> 15 passed
  • pytest tests/test_foods.py tests/test_tracking.py -> 5 passed
v1.5.4 · 2026-02-16

Changed

  • OCR scan flow now uses server-only image normalization for all browsers.
  • Removed client-side file rewriting/compression in /scan to avoid Safari-specific upload fragility.
  • Large image normalization now resizes only when needed and preserves OCR fidelity by using PNG for transformed images.
  • OCR retry classification narrowed to transient failures instead of broad runtime exceptions.

Fixed

  • Fixed OCR strict-ceiling behavior so request threads are no longer vulnerable to blocking on executor shutdown after timeout.
  • Improved calorie fallback parsing to avoid selecting unrelated large numbers (for example sodium/mg values) when calorie labels are ambiguous.
  • Added Google Vision provider response size capping/sanitization before persistence to reduce log bloat and over-retention risk.
  • Added lightweight OCR route diagnostics (request_id, preprocess metrics, provider/confidence, failure class) to improve Render incident visibility.

Testing

  • Expanded OCR reliability coverage with explicit tests for 429, slow OCR ceiling handling, and malformed OCR output behavior.
  • OCR test suite now passes with the new scenarios: pytest -q tests/test_ocr.py -> 9 passed.

Investigated

  • Investigated reported AI instability and latency concerns during this cycle; no app-side regression was identified in the touched OCR/scan paths.
  • Current evidence suggests the observed AI inconsistency was upstream model behavior rather than an application logic fault.
v1.5.3 · 2026-02-16

Added

  • Comprehensive QA/testing harness and documentation:
  • New pytest suite across auth, foods, meals, tracking, AI, OCR, API, admin, security, and performance smoke.
  • Offline-first stubs and network guard for OpenAI/OCR test isolation.
  • Session-spooling load tooling and hobby-plan simulation reporting under docs/testing/.
  • Request correlation IDs in app responses via X-Request-ID for easier production incident tracing.

Changed

  • AI/OCR reliability hardening:
  • Bounded retry with exponential backoff + jitter for idempotent upstream AI/OCR operations.
  • Strict request-thread ceilings with graceful fallback behavior when upstream calls are slow/unavailable.
  • Added route-level AI/OCR abuse controls:
  • Per-user and global rate limits for /foods/ask-ai, /foods/voice-command, and /scan.

Fixed

  • Fixed concurrent Ask AI cache write races by making cache upserts conflict-safe on chatgpt_foods.canonical_query.
  • Reduced likelihood of long blocking requests on AI/OCR routes causing degraded user experience under upstream timeout/429 scenarios.
v1.5.2 · 2026-02-16

Added

  • SEO foundation for public pages:
  • Dynamic meta description, canonical URL, Open Graph, and Twitter cards in the base template.
  • Structured data (WebSite + SoftwareApplication) on the landing page.
  • Public crawler endpoints: /robots.txt and /sitemap.xml.

Changed

  • Voice capture flow now stays active until the user taps Stop listening.
  • Voice submission flow now sends captured transcript on stop (including buffered interim transcript text when available).
  • Public-page SEO metadata now defaults to indexable settings while authenticated/private app surfaces default to noindex.
  • Schema sizing updated for user-generated content:
  • foods.name -> VARCHAR(512)
  • meals.name -> VARCHAR(512)
  • food_tags.name -> VARCHAR(255)
  • chatgpt_foods.reason_not_verified -> TEXT

Fixed

  • Fixed Ask AI cache writes that could 500 due to long reason_not_verified strings exceeding column limits.
  • Added defensive truncation/normalization on AI cache string writes to match DB limits for title/source fields.
  • Added Postgres startup column-type migration steps so deployed databases auto-align to updated field sizes.
  • Reduced SEO discoverability gaps by adding canonical/metadata and crawl directives to public routes.
v1.5.1 · 2026-02-15

Added

  • Profile data portability tools:
  • Download my data (JSON) export action scoped to the signed-in user.
  • Delete account and all data action with explicit typed confirmation (DELETE MY ACCOUNT).
  • Backend export payload builder for user-owned records (foods, logs, meals, goals, water/weight, supplements, OCR/AI logs, OAuth identities, and related metadata).
  • Backend hard-delete flow for account removal that clears user-linked data and logs the user out.

Changed

  • Profile page now includes a dedicated Your data section and a Danger zone section for privacy-control actions.
  • /today install card visibility handling now toggles the hidden attribute in addition to CSS class toggling to prevent spacing artifacts.

Fixed

  • Fixed OCR calorie parsing for labels where calories are followed by serving text and percent markers (example pattern: Calories 1 package 110 % Daily Value).
  • Fixed desktop top-spacing inconsistency on /today after interacting with install prompt UI.
  • Fixed residual vertical gap caused by hidden install-card elements participating in section spacing utilities.
v1.5.0 · 2026-02-15

Added

  • Public marketing landing page for logged-out users with feature sections and real product screenshots.
  • Dedicated legal document pages and markdown-driven legal content loader (Terms, Privacy, AI Disclosure, Cookie Notice).
  • Persistent policy assent capture on signup/onboarding, including acceptance timestamps, versions, IP, and user-agent metadata.
  • Desktop authenticated nav actions for Ask AI and Scan with icon affordances.
  • One-time Postgres cleanup script for legacy OpenAI payload columns: scripts/drop_openai_payload_columns.py.

Changed

  • OpenAI transaction logging is now metadata-only (status, latency, byte counts, provider request id, token usage).
  • About page reorganized into clearer sections (product overview, legal, install guidance, and changelog).
  • Today page install prompt UX refined (smaller card, dismiss button, "do not show again", and install guidance link).
  • PWA standalone behavior now repurposes bottom-bar Voice slot to open Ask AI flow.
  • Landing screenshots now display in phone-style frames with lightbox expansion and no image cropping.

Fixed

  • Supplements bulk action now preserves clicked submit button intent during AJAX (Mark all taken / Clear all).
  • OCR entry-point copy now reflects camera support (Browse files + take a photo).
  • In-product AI/OCR/voice disclosure coverage expanded to avoid missing compliance context in key flows.
  • Desktop top-nav overflow adjusted so Ask AI and Scan remain visible at common widths.
v1.4.0 · 2026-02-15

Added

  • Google OAuth sign-in flow with OIDC callback handling and account identity linking.
  • Password reset system with one-time expiring tokens, forgot/reset screens, and Resend email delivery.
  • OAuth link confirmation step for existing email/password users (requires password before linking Google).
  • Profile "Connected accounts" section with Google linked/not-linked status.
  • Today page Ask AI bottom-sheet experience with save-to-food/save-to-meal logging back into Today.
  • Global scan-label bottom-sheet from the mobile toolbar across authenticated surfaces.
  • First-log coachmark guidance (log first food!) for onboarding-to-first-entry handoff.
  • Onboarding "Target setup" toggle with Assisted goals vs Set your own goals.
  • Onboarding custom macro fields plus optional water-goal and supplements capture.
  • Release-grade smoke test script: scripts/smoke_test_release_1_4_0.py.

Changed

  • Login and signup pages now support Google sign-in entry points when Google OAuth is configured.
  • Apple OAuth is now gated by APPLE_OAUTH_ENABLED so it can stay off by default until intentionally enabled.
  • Ask AI trigger on Today now uses modal trigger semantics instead of tab semantics.
  • Onboarding submit now locks on first submit and shows Saving... to prevent duplicate submissions.
  • Docker Compose runtime now includes password-reset mail env vars (RESEND_API_KEY, MAIL_FROM, APP_BASE_URL, PASSWORD_RESET_TOKEN_MINUTES).
  • Onboarding copy/flow now emphasizes immediate logging and includes non-medical wellness-estimate framing.
  • Assisted onboarding target math now factors current weight, goal weight, and activity level more directly.
  • Foods and Meals top sections were aligned for a more consistent header/action layout pattern.
  • Ask AI sheet interaction polish: clearer loading state and better action sequencing back to Today.

Fixed

  • Corrected Google link behavior so existing password accounts are not silently linked.
  • Fixed password reset delivery path in Docker runtime by forwarding required env vars to the web container.
  • Resolved Resend API 403 error code: 1010 behavior in runtime requests by sending explicit API headers.
  • Improved first-link QA reliability by exposing OAuth link state in Profile and expanding reset script cleanup coverage.
  • Prevented voice-trigger usage while users are still in onboarding flow.
  • Fixed coachmark visibility/positioning issues across mobile and desktop, including interaction-driven hide/show behavior.
v1.3.2 · 2026-02-12

Added

  • Header brand update: replaced text wordmark with the horizontal MacroIndex logo in the top toolbar.
  • Ask AI source links now render as compact source chips with readable host labels and external-link affordance.
  • Weight progress now reflects progress toward goal using start-to-goal journey math (supports both gain and loss goals).

Changed

  • Updated README Latest Release pointer to v1.3.2.
  • Water consumed slider accent color now matches the Fat macro color token (#9fb3d9) for visual consistency.
  • Voice overlay helper copy simplified by removing the microphone permission status line under the voice bubble.
  • Quick-add suggested foods (empty search) now returns a shorter top list (3 items) for faster selection.
  • Toolbar logo asset updated with transparent background to avoid navbar color mismatch.

Fixed

  • Accessibility semantics improved across key templates (label/input bindings, keyboard behavior, and dialog handling).
  • Added destructive-action confirmation coverage for entry/meal/profile management flows.
  • Improved mobile/overlay interaction quality with better focus management and escape handling.
  • Mobile overflow issues caused by long names were hardened across Today entries, supplements, meals, meal detail, and Ask AI item rows.
  • Weight logging now overwrites the existing value for the same day instead of creating duplicate same-day entries.
v1.3.1 · 2026-02-12

Added

  • Voice logging overlay with start/stop controls, transcript preview, and confirmation flow.
  • Two-step voice confirmation for all voice actions before any data is written.
  • Combined voice action support in one utterance:
  • saved food + tracking updates (water and/or supplements)
  • quick add macros + tracking updates
  • Desktop voice trigger button in the top navigation.
  • Animated voice ring feedback while listening/speaking.
  • Auto-run Ask AI lookup when voice-confirmed redirect is selected.

Changed

  • Mobile sticky action bar behavior and spacing for better usability and reduced overlap.
  • Bottom action active-state logic now syncs with route/hash changes more reliably.
  • Voice confirmation copy now uses a cleaner, human-style action summary (removed confidence diagnostics from UI).
  • Voice parser prompt/schema expanded to extract multiple action slots in a single pass.
  • Supplement-only voice phrases are now treated as valid in-scope nutrition logging commands.

Fixed

  • Voice stop/start no longer reuses prior transcript or stale pending confirmations.
  • Improved microphone permission handling and error messaging for browser/OS edge cases.
  • Food fuzzy matching now has stricter guardrails to prevent incorrect same-brand matches.
  • Serving extraction now avoids misreading unrelated numbers (for example water ounces) as servings.
  • Ask AI redirect from voice now immediately starts processing after navigation.
v1.3.0 · 2026-02-09

Added

  • Ask AI nutrition flow powered by OpenAI Responses API with structured JSON output and optional web search.
  • Per-request OpenAI audit logging in openai_transaction_logs, including successful calls, failures, and preflight-blocked requests.
  • Multi-item Ask AI UX improvements:
  • item-level macro chips for each returned item,
  • item include/exclude checkboxes,
  • live macro/name recalculation for selected items,
  • save as Food (aggregate) or Meal (individual foods + meal container).
  • Rejected Ask AI queries now render a clear red-tinted user message for inappropriate/out-of-scope requests.
  • New read-only admin API endpoints:
  • /api/meals
  • /api/meal_items
  • /api/ocr_scan_logs
  • /api/openai_transaction_logs
  • /api/chatgpt_foods
  • Render deployment support and runbook artifacts (render.yaml, RENDER_CUTOVER_RUNBOOK.md) plus migration/smoke scripts for PostgreSQL cutover.

Changed

  • Rebranded app/product naming from MacroLedger to MacroIndex across app UI, templates, docs, and metadata.
  • Docker/runtime updated for production-style startup with Gunicorn (--preload) and managed PostgreSQL-first deployment.
  • Ask AI fallback behavior simplified to OpenAI-first flow, with explicit estimate confirmation when official verification is unavailable.
  • Food creation/editing standardized to fixed serving text (1 serving) while keeping optional grams/ounces amount inputs.
  • Meal editing flow simplified to implicit 1.0 servings per meal item and cleaner meal edit UI.
  • Navbar/mobile navigation refined for better density and high-traffic access (including mobile Foods action).

Removed

  • Open Food Facts integration and related scheduler/import/API/runtime/UI paths.
  • Nutrition adapter subsystem from active runtime and codebase:
  • removed adapter resolver files and smoke script,
  • removed adapter model/bootstrap hooks.
  • Ask AI stale-cache manual refresh button from UI and its now-unused route handler.
  • Legacy/duplicate utility paths and stale code branches superseded by current Ask AI flow.

Fixed

  • Improved reliability around startup schema races by preloading app before worker fork.
  • Hardened migration robustness for SQLite -> PostgreSQL copy with safer table ordering and FK sanitization behavior.
  • Ask AI response normalization and blocked-request handling consistency.
v1.2.2 · 2026-02-08

Added

  • Today page customization dropdown under the calendar to hide/show Weight, Water, and Supplements per user.
  • Per-user persistence for Today section visibility.
  • Meals: create meals and add foods with servings.
  • Food notes and optional grams/oz fields.
  • Loading indicators for Today AJAX updates and food search.
  • Meals list search, mobile card layout, and macro totals display.
  • Meals in Today search with logging as a single meal entry.
  • Meal entries tracked in logs with meal_id.

Changed

  • Today entry edit control now uses a stable toggle + inline form instead of the details element.
  • Quick lookup layout tightened to prevent horizontal expansion.
  • Meals navigation moved under Foods and sub-nav added to Foods/Meals pages.
  • Base font size set to 16px for consistent typography.
  • Docker Compose port binding limited to localhost while keeping container host binding open.
  • Meal builder uses plus buttons to add foods and logs as a single meal entry.

Docs

  • Updated deployment notes in the README.
v1.2.1 · 2026-01-31

Added

  • API endpoints for weight, water, and food tags.

Changed

  • Docker image base moved to Python 3.10.

Fixed

  • App startup tolerates SQLite lock during imports.
v1.2.0 · 2026-01-27

Added

  • Weight tracking with persistent goal weight and inline edit on Today.
  • Water tracking with daily goal, slider + numeric input, and goal edit flow.
  • Mobile Foods page cards with search-first results and reduced scrolling.
  • Admin page logout button.

Changed

  • Calendar dots and macro progress bars now use goal-based status colors.
  • Post actions on Today page refresh in-place without scroll jumps.
  • Login and root routes now land on Log Macros.
  • Docker Compose exposes the app on port 8005 for LAN access.
  • Docker image now uses Python 3.10.

Fixed

  • Consistent action button styling across Foods and Today log results.
v1.1.0 · 2026-01-20

Fixed

  • Supplement checkmarks now persist across days and are stored correctly.
  • Supplement toggle UI now works under CSP (no inline event handlers).
  • Food deletion no longer removes historical log entries.
  • Container startup error fixed in API datetime import.

Added

  • Smooth in-page refresh for Today actions to prevent scroll jumps.
  • Edit servings for today’s log entries (mobile + desktop).
  • Tag autocomplete for quick add and food form using existing tags.
  • Delete action added to the Foods list with confirmation.
  • Food list and log entry actions now use consistent Edit/Delete buttons.
  • Log-from-foods now shows the 3 most recent foods by default.
  • About page changelog section that auto-loads versioned entries.
  • Eastern-time logging utilities with exact entry timestamps.
  • log_date recorded for food logs, supplement logs, and goal history.
  • Lightweight auto-migration to add/backfill log_date columns.

Changed

  • All model timestamp defaults now use Eastern time.
  • Transaction timestamps now record exact entry time; future/past entries use midnight for the transaction date with log_date capturing entry time.