Repository navigation
v1.3.0-dev02
Pre-release
Pre-release
Second dev pre-release of the 1.3.0 minor. Adds MCP date-range reads, outgoing webhooks and a general-purpose REST API, import of Cronometer's Daily Nutrition export, and a round of iPhone fixes, plus security fixes for uploads and five server dependencies.
Added
- MCP date-range reads (#215). New
list_diary_entries_rangeandget_daily_totals_rangetools, and optionalstart/enddates onget_recent_foodsandget_recent_meals. Bounds are inclusive and either can be left out to leave that side open; with neither, the range tools cover the last 90 days.list_diary_entries_rangereturns at most 366 logged days per call, since full item lists get large;get_daily_totals_rangehas no limit. The existing single-day tools are unchanged. Thanks @kgenerozov. - General-purpose public REST API at
/api/v1/diary,/api/v1/goals, and/api/v1/meals, for your own scripts and automations rather than the sister-app federation contract the rest of/api/v1documents. Off by default (PUBLIC_API_ENABLED=1;PUBLIC_API_WRITE_ENABLED=1additionally unlocks logging a food, water, a meal, or a body stat). Reuses themcp:read/mcp:writetoken scopes MCP already defines, one token works for both interfaces. Seedocs/public-api.md. - Outgoing webhooks. Configure a target URL in Settings, Webhooks and NutriTrace fires a signed HTTP POST the instant a food is logged, water is logged, a body stat is logged, or a daily nutrition goal is reached. Off by default (
WEBHOOKS_ENABLED=1). HMAC-SHA256 signed, 3 delivery attempts with backoff, a "send test event" button to verify a target without waiting for a real event. Target URLs are validated against a shared SSRF guard (blocks loopback/private/link-local/cloud-metadata addresses unlessALLOW_PRIVATE_WEBHOOK_URLS=1). Seedocs/webhooks.md. - Diagnostic logs now record screen layout. With Diagnostic Mode on (Settings, Diagnostics), NutriTrace writes a one-line snapshot of the layout at startup, on each page change, when a field gains or loses focus, when the keyboard or browser toolbar changes the visible area, on zoom and rotation, on returning to the app, and when scrolling stops. Each line has the screen, window, visible-area and page sizes, zoom level, the device's safe-area insets, where the tab bar and Diary's summary bar sit, and the focused field's font size. It helps sort out display problems on phones I can't test on directly, like the iPhone reports in #208 and #212: turn logging on, reproduce the problem, then Copy the logs and send them. Only sizes and positions are recorded, never what you type, and nothing is measured while logging is off.
Changed
- Claude Fable 5.1 in Trace's model list. It is now the most capable Claude option; Fable 5 stays selectable, marked as previous.
Fixed
- iPhone and iPad no longer zoom in when you tap a text field (#212). Safari on iOS zooms the page into any field whose text is smaller than 16px as soon as it gets focus, and leaves it zoomed, which pushed sheets like Body Stats partly off screen. NutriTrace's fields are 15px, so on iPhone and iPad they are now 16px (the Foods search, Settings search, Body Stats and every other field). Fields that are larger on purpose, like the calorie box in Quick Calories, keep their size. Android, desktop and Mac are unchanged. Thanks @Sanket-Pati-007 for the report.
- Copy buttons work when NutriTrace is opened over plain http (#212). Browsers only provide the clipboard to secure (HTTPS) pages, so on an http address, such as a LAN IP or a tailnet without HTTPS, every Copy button failed: diagnostic logs, the calibration export, API tokens, webhook secrets, the Docker update command and the wellness redirect URIs. They now fall back to the browser's older copy command, which still works there. The wellness redirect URI buttons also show an error now if copying fails, instead of doing nothing.
- Open Food Facts search keeps its best matches at the top (#213). NutriTrace reorders each page of OFF results so entries with a photo and more complete data come first, but it did that across the whole page, overriding how well each result matched the search. A product with no photo and sparse data, common for regions OFF contributors photograph less, could sink far down the list: searching "Blue Label Marie Biscuits", which OFF ranks first by a wide margin, put it 38th, under a beef steak. Results now keep OFF's order wherever its relevance scores are clearly different, and photos, completeness and Nutri-Score only sort results that match about equally well (such as the dozen identical "Nutella" entries). A Nutri-Score of "unknown" no longer counts as having one. Separately, the check added in #192 to tell a local OFF mirror's results from public ones didn't work, because OFF's public search responds in the same format, so public results were being sorted as mirror results; mirror results are now recognised by having no relevance score. Local mirror ordering is otherwise unchanged. Thanks @NoBackups for the report and the digging.
- Macro goals set "As percent" now show and convert correctly (#209, #210). Applying a macro preset kept a stale "As percent" flag, so the preset's gram target was read as a percentage and Diary and Statistics showed it hugely inflated (a 137 g protein target became 626 g). Presets now clear the flag. Separately, the Goals page's Macros card, preview ring and preset chip read a percent goal as grams (30% showed as "30 g"), and ticking "As percent" only changed the label instead of converting the value. Both now convert, so Goals, Diary and Statistics agree. If you already hit the preset bug, re-applying your preset once clears it. Thanks @drunkenpeleg for the report and the fix.
- Cronometer import now handles the Daily Nutrition export, because the per-food one no longer has nutrition. Cronometer has stopped including the nutrient columns in its "Export Food & Recipe Entries" file, at least on free accounts: it arrives as just
Day, Time, Group, Food Name, Amount, Category, confirmed on three separate accounts in August and September 2026. NutriTrace previously rejected that file with a generic error and its instructions named an export button ("Servings") that Cronometer had renamed, so there was no way to get Cronometer data in at all. NutriTrace now imports Export Daily Nutrition as well: tick "Include diary group rows" and each day's meals import as one entry per meal, otherwise you get one entry per day. That is coarser than per-food, but it is the only export Cronometer still fills with numbers. The per-food import stays for files that do have nutrition, and a per-food file without it now says exactly that and points to the export that works. Two bugs found by running real exports through the importer are fixed along the way: Cronometer writes 12-hour times ("7:45 PM") and the AM/PM was being dropped, so every evening entry was saved as morning; and entries Cronometer exports with blank calories, such as supplements and most spices, were skipped along with their vitamins and minerals. Dates are no longer assumed to be month-first, since Cronometer writes them in the account's own format, and the day's "Total" row is left out so a day's calories are not counted twice (recognised by its numbers as well as its name, so a translated total doesn't slip through). ZIP detection reads the file's contents instead of trusting its name. - Diary's summary bar sits flush with the bottom of the screen on iPhone (#208). On an iPhone the summary bar could float with a see-through strip below it. With the tab bar on, the two bars were positioned separately (the summary bar at a distance worked out from the tab bar's height plus the home-indicator area), so anything that moved one without the other pulled them apart; they now sit together in one container anchored to the bottom of the screen. With the menu button instead of the tab bar, the bar was lifted clear of the home indicator but nothing painted the space underneath, so the diary showed through; the bar's own background now fills it. Layout on Android and desktop is unchanged. Thanks @zen-pavel for the report and the screenshots.
- Workaround for a black strip along the bottom of the iPhone home-screen app (#208). When NutriTrace is opened from the Home Screen, iOS can size the app shorter than the screen by the height of the status bar, leaving a black strip at the bottom (about 62px on a Pro Max) until something is scrolled. The reporter's screenshots show exactly that, and the strip is gone in the one taken after scrolling. On Home Screen launches only, the page now starts out a full screen tall, which should make iOS correct the size straight away instead of waiting for a scroll. This could not be tested on an iPhone here, so the new layout lines in the diagnostic logs record the screen, window and page heights to confirm whether it works. Safari tabs, Android and desktop are unaffected.
Security
- Backup archives are no longer reachable from the public uploads directory.
BACKUPS_PATHdefaults to a directory insideUPLOADS_PATH, and/uploadsis served ahead of the auth middleware so an Android WebView<img>can load images without anAuthorizationheader. A full-backup ZIP sitting in that directory was therefore fetchable by URL, while every/api/full-backuproute is admin-only. It now returns 404 like anything else outside the served set. Scheduled backups are off by default, so an install that never enabled them and never created one by hand had nothing there to reach; there is no directory listing either, so a filename had to be known or guessed. The archive holds a full database dump, so if yours has been internet-facing with backups enabled, a look through your access log for/uploads/backups/will settle it either way. The exclusion tests the resolved filesystem path rather than the request URL, sinceexpress.staticpercent-decodes a path before opening the file while a route prefix matches the raw one, and the two disagree on exactly the inputs an attacker would pick. - Uploaded files can no longer be served as web pages. Uploads were saved under whatever file extension the uploader sent, and the extension decides what type a file is served as, so a file uploaded as audio but named
.htmlwould be served as a page from your NutriTrace address. Uploads are now saved under an extension that matches their actual type (anything unrecognised becomes.binand downloads instead of opening), and every file under/uploadsis served withX-Content-Type-Options: nosniffand a sandboxing content security policy, so nothing stored there can run script. Uploaded images still load as before. - Server dependency updates clear five packages with known vulnerabilities. multer 2.4.0 (GHSA-wc9g-mqfw-jrwm, GHSA-qfvm-cv95-jqjf, GHSA-535w-7cp7-47q4, high: denial of service through crafted multipart uploads; GHSA-qvfw-j98x-7q72, low), fast-uri 3.1.8 (GHSA-5jgf-p345-68v8, GHSA-f65p-4m7j-42xc, GHSA-fph4-wmhf-6fwf, GHSA-jqff-g426-hqxp, high: URL host confusion), hono 4.13.8 (three moderate advisories), qs 6.16.0 (two moderate advisories), and adm-zip 0.6.1 (GHSA-vwc7-r8mq-g2x9, moderate: extraction follows symlinks). All are minor or patch updates; the full test suite passes on them, and uploads were retested against a running server with identical results.
npm audit --omit=devnow reports 0 vulnerabilities for both the app and the server. Pulling the new image is all that's needed.
iPhone and iPad testers: if the Home Screen app still opens with a black strip along the bottom, or a text field still zooms when you tap it, turn on Settings, Diagnostics, Diagnostic Mode, reproduce it, then tap View logs, Copy, and paste the lines starting with [viewport] into #208 (black strip) or #212 (zoom).