# Changelog All notable changes to CookTrace are documented here. Format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). --- ## [Unreleased] --- ## [1.4.0] - 2026-10-02 CookTrace on your wrist, offline mode for a shop with no signal, kitchen roles for a shared household library, cooking more than one dish at once, preliminary foldable support, and Spanish. ### Added - **CookTrace on your wrist.** A Wear OS app for watches on Wear OS 3 and up. Your shopping list by aisle, ticked off with a trolley in one hand, working with no signal. Press Cook on the phone and the recipe arrives as two checklists, ingredients and steps, and a step that says "simmer for 20 minutes" offers that timer. Several timers run at once and each buzzes on your wrist, "I cooked this" writes straight into your cook diary, and a tile and a watch face complication show what is left to buy or the timer running. Either device can tick something off and the other follows. [Wear OS](https://traceapps.github.io/docs/cooktrace/wear/). - **Offline mode.** Built for a supermarket with no signal: the shopping list opens, and you can add, tick, edit, remove and clear. Recipes, cookbooks, the pantry and the diary read from what the app has already seen, pictures included, and the installed app opens with no connection at all. The pantry, the cook diary, your recipes and their notes, settings, your profile, and photos of what you cooked, a recipe or a pantry item can all change offline, and go up on their own when the connection returns, with an amber cloud on the menu button while anything is waiting. Importing, sharing, kitchens, editing a cookbook, Trace and anything admin need a connection and say so; a change your server refuses is set aside and named in plain words. [Cook and shop without a connection](https://traceapps.github.io/docs/cooktrace/features/#offline). - **Kitchen roles, so a household can share one recipe library without sharing the keys to the server** ([#52](https://github.com/TraceApps/cooktrace/issues/52)). The Head Chef gives each member a role: a Sous Chef edits what is shared into the Kitchen, a Line Cook cooks from it. Deleting, sharing onward, visibility, category, rating and favorites stay with the recipe's owner. A Kitchen can also be handed to another member, the way out of the old "owner cannot leave" dead end. Thanks to @herver1971 for the idea and the first implementation in [#53](https://github.com/TraceApps/cooktrace/pull/53). [Kitchens](https://traceapps.github.io/docs/cooktrace/kitchens/). - **Cook more than one thing at once.** Start a second recipe while the first is still going, and a slim bar shows what is on the go from anywhere in the app: one tap goes back to the dish, or picks from several. Timers change color as they run down, and one that goes off names the dish it belongs to. - **Preliminary foldable support.** Half open like a book, the crease becomes a divider rather than something content sits across: - A recipe opens like a cookbook: the ingredients on the page left of the fold, the method on the right. - Settings puts its section list on one side and the section itself on the other. - Shopping deals its aisles into columns, and the pantry spotlight shows when there is room for it. - The cook diary keeps its toolbar under the header, and a year of cooking fits the width. - Manage splits its rail and its grid on the room available. - Dialogs, sheets, running timers and Trace keep off the crease, and a menu opened near it takes the roomier side. - In laptop posture Trace sits on the half lying flat, leaving the recipe readable on the half standing up. - Recipes, lists and photos still cross the fold freely, since an opened foldable is a bigger screen to read on. - The same two-column layouts appear on a tablet or any wide window, not only on a desktop-sized screen. - Diagnostics reports what the hinge is doing, so you can tell whether your phone reports one at all. - **Spanish.** Pick it in Settings → Regional & Units, or it follows your browser or phone. Translated by @herver1971 on Weblate. Thank you! - **Settings has a Support page**, next to About: Ko-fi and GitHub Sponsors, plus free ways to help (star the repo, report a bug, translate). ### Changed - **Search ignores accents.** "oregano" finds "Orégano" and "cafe" finds "Café", in every search box, the REST API and the MCP tools, on names and brands alike. Text without accents matches exactly as before. Thanks to @herver1971 for the pantry fix in [#62](https://github.com/TraceApps/cooktrace/pull/62) that started it. - **Update checks are off until you turn them on, and your server does the asking.** Every browser and phone used to ask GitHub directly every 4 hours. Setup now asks, and a fresh install contacts nothing on its own. Existing installs keep checking as before, and `UPDATE_CHECK=off` keeps them off for good. Reported on r/selfhosted. - **Fonts are served by your own instance.** The app loaded its fonts from Google on every page load, so Google saw the address of everyone who opened it. Reported on r/selfhosted. - **The in-app updater reuses an update it already downloaded.** Coming back to Updates goes straight to installing, and the button says Install. Older downloads are cleared so they stop piling up on the phone. - **About links to the TraceApps family** instead of naming the other apps. - **A connection problem says what kind it was.** The diagnostic log records a timeout, an address that would not resolve, a refused connection or a rejected certificate, and how long it waited, where all of them used to read "Failed to fetch". ### Fixed - **The Docker image starts on virtual machines whose CPU cannot run the PDF reader** ([#59](https://github.com/TraceApps/cooktrace/issues/59), thanks @bajtekv). It stopped with "Illegal instruction" before the server came up. Only PDF import is affected on such a machine now, and it says so. - **The check on a pantry item can be unticked again** ([#55](https://github.com/TraceApps/cooktrace/issues/55)). Ticking it, or restocking from the shopping list, puts 1 in On Hand, unticking sets 0, and the card, the button and the item's sheet agree. Thanks to @herver1971 for the report and the fix in [#56](https://github.com/TraceApps/cooktrace/pull/56). - **A cookbook cover uploads from Manage, and so does a recipe video** ([#63](https://github.com/TraceApps/cooktrace/issues/63), thanks @herver1971). Both said "Upload failed" and saved nothing, so a smart cookbook could not get a cover at all. - **The All search shows your own pantry.** With the All chip, or several sources pinned, the pantry part of the results was always empty. Your items appear badged as yours, and tapping one opens it. - **The cook dashboard no longer disappears after one failed request** at launch; the heatmap shows on its own data and the stats are retried. - **The shopping list's Add button fits on a phone** instead of running off the right edge. - **A file of the wrong type, or over the size limit, is turned away with a clear message** instead of a server error. A file that is too large says what the limit is. - **"A New Version Is Available" on the web says what it means, and Reload works.** A tab left open also notices a new version now. - **The Trace button no longer covers sheets and dialogs** opened on top of it. - **Settings on desktop:** pages line up with the section list beside them, and the list keeps its place when you open a section instead of scrolling back to Profile. ### Security - **multer** bumped 2.3.0 to 2.4.0, closes [GHSA-3pph-fpjx-jg34](https://github.com/advisories/GHSA-3pph-fpjx-jg34) (moderate: an upload cut off at just the wrong moment could leave its file on disk). Both routes that write uploads to disk require signing in. - **undici** bumped 6.28.0 to 6.29.0, and the copy cheerio uses 7.29.0 to 7.30.0, closes [GHSA-3wwx-pv8p-q78v](https://github.com/advisories/GHSA-3wwx-pv8p-q78v) (moderate: a WebSocket server could crash the process). CookTrace opens no WebSocket connections. - **nodemailer** bumped 9.1.1 to 10.0.12, closes [GHSA-6vj9-mwq6-2f5v](https://github.com/advisories/GHSA-6vj9-mwq6-2f5v) (moderate: separate mail transports could share one TLS server name). - **fast-uri** bumped 3.1.7 to 3.1.8, closes [GHSA-hrr3-gc8f-f4qj](https://github.com/advisories/GHSA-hrr3-gc8f-f4qj) (moderate: a host written with percent-encoded letters was not normalized the same way). It only comes in through the MCP server, which is off by default. - **ip-address** bumped 10.7.0 to 10.7.3, closes [GHSA-j6r3-76f7-8jcv](https://github.com/advisories/GHSA-j6r3-76f7-8jcv) and [GHSA-h3mg-xc3c-68pw](https://github.com/advisories/GHSA-h3mg-xc3c-68pw) (moderate: subnet checks across IPv4 and IPv6, and unbounded work on a long IPv6 address). It only comes in through the MCP server's rate limiter. - **brace-expansion** bumped 5.0.9 to 5.0.12 and 2.1.4 to 2.1.7, closes [GHSA-q2hr-2g5m-vwhr](https://github.com/advisories/GHSA-q2hr-2g5m-vwhr) (moderate: slow expansion of a crafted pattern). Build tooling only; neither the app nor the server uses it. - `npm audit` reports 0 vulnerabilities for the app and the server. --- ## [1.4.0-dev04] - 2026-09-29 (pre-release) A dev pre-release of the 1.4.0 minor. Spanish, a Support page in Settings, uploads that say what went wrong, and security updates. ### Added - **Spanish.** CookTrace can be used in Spanish: pick it in Settings → Regional & Units, or it follows your browser or phone. Translated almost in full by @herver1971 on Weblate. Thank you! - **Settings has a Support page**, next to About: Ko-fi and GitHub Sponsors, plus free ways to help (star the repo, report a bug, translate). It replaces the support row that used to sit in About. ### Changed - **The in-app updater reuses an update it already downloaded.** Coming back to Updates goes straight to installing instead of downloading the whole APK again, and the button says Install. Older downloads are cleared so they stop piling up on the phone. - **About links to the TraceApps family** instead of naming the other apps, a list that had already gone out of date. ### Fixed - **A file of the wrong type, or over the size limit, is turned away with a clear message.** Uploading one answered with a server error instead of saying what was wrong. A file that is too large now says what the limit is. - **A cookbook cover uploads from Manage, and so does a recipe video.** Both said "Upload failed" and saved nothing, although the file had already reached your server. Manage → Cookbooks is the only place a smart cookbook gets its cover, so smart cookbooks could not have one at all. Thanks to @herver1971 for the report and the diagnosis in [#63](https://github.com/TraceApps/cooktrace/issues/63). - **A cookbook cover picked with no connection is saved as a picture file** once you are back online, not stored whole inside the cookbook, where it made every list of cookbooks heavier to load. Updating a cookbook now checks its cover the way creating one always did. - **The shopping list's Add button fits on a phone.** On a phone-width screen the button beside the item picker ran off the right edge, leaving only "+ A" showing. - **Settings pages line up with the section list** on desktop and foldables. Most pages started 12px below the list beside it. - **The Settings section list keeps its place** on desktop and foldables. Every click in it scrolled the list back to Profile. ### Security - **multer** bumped 2.3.0 to 2.4.0, closes [GHSA-3pph-fpjx-jg34](https://github.com/advisories/GHSA-3pph-fpjx-jg34) (moderate: an upload cut off at just the wrong moment could leave its file behind on disk). Both routes that write uploads to disk require signing in. - **undici** bumped 6.28.0 to 6.29.0, and the copy cheerio uses 7.29.0 to 7.30.0, closes [GHSA-3wwx-pv8p-q78v](https://github.com/advisories/GHSA-3wwx-pv8p-q78v) (moderate: a WebSocket server could crash the process). CookTrace opens no WebSocket connections. - **nodemailer** bumped 9.1.1 to 10.0.12 on the server, closes [GHSA-6vj9-mwq6-2f5v](https://github.com/advisories/GHSA-6vj9-mwq6-2f5v) (moderate: separate mail transports could share one TLS server name). Removed from the web app's own dependencies, where nothing used it. - `npm audit` reports 0 vulnerabilities for the app and the server. --- ## [1.4.0-dev03] - 2026-09-27 (pre-release) A dev pre-release of the 1.4.0 minor. A first pass at foldables, and search that no longer cares about accents. ### Added - **Preliminary foldable support.** Half open like a book, the crease becomes a divider rather than something content sits across: - A recipe opens like a cookbook: the ingredients on the page left of the fold, the method on the right. - Settings puts its section list on one side and the section itself on the other. - Shopping deals its aisles into columns, and the pantry spotlight shows when there is room for it. - The cook diary keeps its toolbar under the header, and a year of cooking fits the width. - Manage splits its rail and its grid on the room available. - Dialogs, sheets, running timers and Trace keep off the crease, and a menu opened near it takes the roomier side rather than being cut in half by the hinge. - In laptop posture Trace sits on the half lying flat, leaving the recipe readable on the half standing up. - Recipes, lists and photos still cross the fold freely, since an opened foldable is a bigger screen to read on. - Diagnostics reports what the hinge is doing, so you can tell whether your phone reports one at all. ### Fixed - **Search ignores accents.** Typing "oregano" finds "Orégano", "cafe" finds "Café", "limon" finds "Limón". This holds for the pantry, recipes, cookbooks, the cook diary, tags, units, the Manage lists, Settings, Trace, the public API and the MCP tools, on names and brands alike. A kitchen kept in Spanish, Portuguese, French or another language with accents no longer looks like the item is missing because nobody types the accent on a phone. Text without accents matches exactly as before. Thanks to @herver1971 for the report and the pantry fix in [#62](https://github.com/TraceApps/cooktrace/pull/62). - **The All search shows your own pantry again.** With the All chip, or several sources pinned, the pantry part of the results was always empty. Your items appear alongside the OFF, USDA and NutriTrace results now, badged as yours, and tapping one opens that item instead of starting a duplicate of it. - **The cook dashboard no longer disappears after one failed stats call.** A single failed request at launch hid the heatmap and the summary tiles for the whole session, with no retry. The heatmap shows on its own data now, and the stats call retries once. - **A connection problem says what kind it was.** When the app cannot reach your server, the diagnostic log records the kind of failure and how long it waited. A timeout, an address that would not resolve, a refused connection and a rejected certificate all used to read "Failed to fetch". ### Security - No dependency changes. `npm audit --omit=dev` reports 0 vulnerabilities for the app and the server. --- ## [1.4.0-dev02] - 2026-09-25 (pre-release) Second dev pre-release of the 1.4.0 minor. Three fixes, each from a real install: the image not starting on some virtual machines, the in-app updater offering the watch build to phones, and the pantry stock check that could not be unticked. ### Fixed - **The Docker image would not start on some virtual machines**, stopping with "Illegal instruction" before the server came up ([#59](https://github.com/TraceApps/cooktrace/issues/59), thanks @bajtekv). The PDF reader used for recipe imports loads a graphics library whose prebuilt binary needs CPU instructions that some virtual CPUs, including QEMU's default, do not provide. Loading it killed the whole process at startup, and that kind of crash cannot be caught. PDFs are now read in a separate short-lived process, so the server starts and runs normally everywhere, and on a machine whose CPU cannot run the reader only PDF import is affected, with a message saying so rather than a dead container. A PDF that takes more than a minute is given up on instead of holding the request open. - **The in-app updater can no longer hand a phone the watch build.** A release carries both APKs, and they share a package id so the watch app installs straight over the phone one. The updater took whichever `.apk` the release listed first, which is upload order and no promise at all. It now picks the phone's build by name, and offers nothing at all rather than a watch build. - **The check on a pantry item can be unticked again, and ticking it puts 1 in On Hand.** Tapping + on an item's photo marked it in stock but left On Hand blank, and from then on the check could not be unticked: every tap marked it in stock again, and the only way out was opening the item and typing 0. The item's own sheet also showed it as Out of Stock while the card showed it checked. Ticking now sets On Hand to 1 (or keeps the number that was there), unticking sets it to 0, and the card, the button and the sheet agree. [#55](https://github.com/TraceApps/cooktrace/issues/55) --- ## [1.4.0-dev01] - 2026-09-22 (pre-release) First dev pre-release of the 1.4.0 minor. Two headlines: CookTrace on a watch, with your shopping list and a recipe you can cook from, and offline mode, so a supermarket with no signal stops being a problem. Also in: kitchen roles, so a household can share a recipe library without sharing the keys to the server, update checks that are off until you ask for them, and fonts served by your own instance instead of Google. ### Added - **Kitchen roles, so a household can share one recipe library without sharing the keys to the server** ([#52](https://github.com/TraceApps/cooktrace/issues/52)). A recipe shared into a Kitchen used to be read-only for everyone but its owner, so fixing a quantity meant asking them or making everyone an admin. Each member now has a role its Head Chef sets: a Sous Chef edits what is shared into the Kitchen, a Line Cook cooks from it. Deleting, sharing onward, visibility, category, rating and favourites stay with the owner. A Kitchen can also be handed to another member, which is the way out of the old "owner cannot leave" dead end. Thanks to @herver1971 for the idea and the first implementation in [#53](https://github.com/TraceApps/cooktrace/pull/53). [Kitchens](https://traceapps.github.io/docs/cooktrace/kitchens/). - **CookTrace on your wrist.** A Wear OS app for watches running Wear OS 3 and up. The shopping list is its home: your list by aisle, ticked off with a trolley in one hand, working in a shop with no signal. Press Cook on the phone and the recipe arrives on the watch as two checklists, ingredients and steps, and a step that says "simmer for 20 minutes" offers that timer rather than making you dial it. Several timers run at once and each buzzes on your wrist. "I cooked this" writes straight into your cook diary. Either device can tick something off and the other follows. [Wear OS](https://traceapps.github.io/docs/cooktrace/wear/). - **Offline mode** ([#211](https://github.com/TraceApps/nutritrace/issues/211) in NutriTrace, the same idea here). A supermarket with no signal is the case this is built for: your shopping list opens, you can add, tick, edit, remove and clear, and it is all there when you come back into range. Recipes, cookbooks, the pantry and the diary read from what this browser has already seen, pictures included, so you can cook from a recipe with no signal. What you change goes up on its own when the connection returns, as the very requests the app would have made. The menu button shows an amber cloud while anything is waiting. [Cook and shop without a connection](https://traceapps.github.io/docs/cooktrace/features/#offline). - **What else works in offline mode.** The pantry (adding, editing, removing, in or out of stock), the cook diary, your recipes and their notes, settings, your profile and picture, and photos everywhere you can attach one: what you cooked, a recipe's own photo, a cookbook cover and a pantry item. - **Offline mode says what it cannot reach.** Importing recipes, sharing a recipe or cookbook, kitchens and their members, Trace and anything admin need a connection. A change your server refuses is set aside and named in plain words, everything else still goes up, and the reason is written to the diagnostics log, while a server that is merely busy is retried instead. ### Changed - **Update checks are off until you turn them on, and your server does the asking.** Every browser and phone used to ask GitHub directly every 4 hours. Setup now asks, skipping the question leaves checks off, and a fresh install contacts nothing on its own. Existing installs keep checking as before. `UPDATE_CHECK=off` keeps them off for good. Reported on r/selfhosted. ### Fixed - **A list you have already opened is still there offline after your changes go up.** When queued work reached your server, the app dropped its copy of the lists that change touched so they would be read again, but nothing reads a screen you do not open. Come back offline without opening it and your shopping list said it needed a connection, empty. Those lists are read back the moment the change goes up now. - **A browser low on room keeps your shopping list.** Making space for something you changed cleared the whole copy at once, taking the list you were standing in the shop with. It now gives up the oldest half first, and only clears everything if that is still not enough. What the browser keeps also counts a read as recent use, so the list you look at is the last thing dropped rather than the first. - **"A New Version Is Available" on the web now says what it means, and Reload works.** The browser banner used the Android wording and its Reload button could do nothing at all, and a tab left open never noticed a new version. Same fix in all four Trace apps. - **Ticking things off on the phone no longer wakes the watch once per tap.** Each change was sent to the watch on its own, so a shop run kept the radio busy; they go in one batch now. - **The Trace button no longer covers what's on top of it.** It floated above every sheet and dialog, so wherever you had dragged it, it could sit over a title or a button. Same fix as NutriTrace [#233](https://github.com/TraceApps/nutritrace/issues/233). - **The installed app survives a reload with no connection.** It kept only a fallback page, so its own code came from the network and reopening it in a dead zone left a blank screen. - **Something deleted while online stays deleted when the connection goes.** An older copy of the list could be carried over, so what you removed came back the moment you were offline. Reported in testing. - **A photo kept offline is scaled to something a request comfortably carries**, so your server never turns it away after you have been told it was saved. - **Adding a photo with no connection no longer fails on an installed app.** The part of the app that keeps a photo was fetched from your server at the exact moment there was nothing to fetch from. - **A change made with no connection is answered in the shape that screen expects**, so nothing looks like it failed when it was saved and waiting. - **A picture kept offline holds its transparency, and an unusual camera format is converted rather than lost.** A drawing could come back with a black background, and an iPhone's HEIC would have been refused on arrival without saying so. - **The copy this browser keeps now has a ceiling**, and if storage runs out, what you have changed is kept and the copy makes way for it. - **A sync no longer empties the copy this browser keeps.** Everything was cleared once the queue went up, so losing signal again left you with nothing to look at. - **Work changed while a sync was running no longer waits for you to do something else** before it goes up. - **What a row created offline became is now remembered on disk**, not just while the page stays open, so a sync that stopped halfway can't leave work queued against an id your server never had. - **Fonts are served by your own instance.** The app loaded Inter and the icon font from Google on every page load, so Google saw the address of everyone who opened it, whatever your settings said. Reported on r/selfhosted. ### Security - No security fixes this cycle. `npm audit` reports 0 vulnerabilities for the app and the server, and there are no open Dependabot alerts. The privacy changes above (fonts, update checks) came out of a review on r/selfhosted. --- ## [1.3.0] - 2026-09-20 Minor release. Big themes: CookTrace opens up to other software (a Model Context Protocol server, personal access tokens, outgoing webhooks, a public REST API, and NutriTrace federation in both directions), a smarter shopping list, and a batch of Android fixes. **Action needed when you update: the container now listens on port 3003 instead of 3001.** If your compose file has `"3003:3001"`, change it to `"3003:3003"`; if a reverse proxy or tunnel reaches the container directly (`cooktrace:3001`, or a Traefik `loadbalancer.server.port=3001` label), point it at `3003`. Until you do, CookTrace won't respond after the update. Installs that set `PORT` themselves are not affected, and the host port stays 3003, so bookmarks and the Android app's server address keep working. ### Changed - **The container now listens on port 3003, the same as the host port. Action needed when you update.** The image used to listen on 3001 inside the container while the sample compose file published it on 3003, so the two numbers never matched, and 3001 was also NutriTrace's port. Both are 3003 now. See the note above for what to change. The weekly summary email's Open CookTrace button, used when no app URL is set, pointed at `localhost:3000` and now points at `localhost:3003`, and running from source starts the server on `:3003`, so it no longer collides with a NutriTrace checkout on the same machine. - **Trace settings now match NutriTrace.** The Base URL and API Key fields save when you leave them (or press Enter) instead of through a Save button beside each field, which on a phone in portrait sat past the edge of the screen; the connection is only re-tested when the value changed. On a server where AI is configured through environment variables, the section says so, shows the provider and model the server actually uses, and hides the base URL and API key fields. Smart Log gains a Voice Input Language setting for when you speak a different language than your device is set to. - **The shopping list fills the screen on desktop.** On wide screens the aisle and recipe cards sat in rows as tall as their tallest card, leaving empty space under the short groups. Short groups now stack into that space, and the cards re-pack as groups collapse, items are checked off, or the window is resized. Phones and Flat view are unchanged. - **Food Sources settings reorganized.** NutriTrace Federation now lives inside Settings, Food Sources under its own "NutriTrace" sub-heading (between USDA and Barcode Scanner) instead of a separate top-level section, since federation is currently used purely as another food source. - **Sync status pill in the sidebar**, with one colour rule for sync state across the app. - **Claude Fable 5.1 in Trace's model list.** It is now the most capable Claude option; Fable 5 stays selectable, marked as previous. ### Added - **Model Context Protocol (MCP) server.** CookTrace exposes a read + write + destructive MCP endpoint at `/api/mcp` so Claude Desktop, Cursor, Codex, and other MCP-aware agents can search recipes, browse the pantry and shopping list, log a cook, and (with the right scope) create recipes or pantry items. Fourteen tools across three independently-gated tiers (`mcp:read` / `mcp:write` / `mcp:destroy`), each requiring both a server-side env flag and a matching token scope. Off by default. See [docs/cooktrace/mcp.md](https://traceapps.github.io/docs/cooktrace/mcp/). - **Personal access tokens.** New Settings, API Tokens section (admin, multi-user mode) to mint, scope, and revoke tokens. Shared by MCP, the public API, and federation. - **Outgoing webhooks.** Configure a target URL in Settings, Webhooks and CookTrace fires a signed HTTP POST the instant a recipe is logged as cooked, the shopping list is fully checked off, or a pantry item runs out of stock. Off by default (`WEBHOOKS_ENABLED=1`). HMAC-SHA256 signed, 3 delivery attempts with backoff, and a "send test event" button. Target URLs are validated against an SSRF guard. See [docs/cooktrace/webhooks.md](https://traceapps.github.io/docs/cooktrace/webhooks/). - **General-purpose public REST API** at `/api/v1/cook-diary`, `/api/v1/shopping`, and a pantry stock write route, for your own scripts and automations. Off by default (`PUBLIC_API_ENABLED=1`; `PUBLIC_API_WRITE_ENABLED=1` unlocks the writes). Reuses the `mcp:read`/`mcp:write` token scopes. See [docs/cooktrace/public-api.md](https://traceapps.github.io/docs/cooktrace/public-api/). - **NutriTrace federation, both directions.** NutriTrace can now pull your CookTrace recipes (`GET /api/v1/recipes`, with per-ingredient nutrition resolved through variant/generic inheritance) and your pantry (`GET /api/v1/pantry`, leaf rows shaped for NT's foods library, with search and paging). Gated by new `read:recipes` and `read:pantry` token scopes, independent of each other and of MCP. - **NutriTrace joins the Pantry search chips.** A connected NutriTrace instance's food catalog is a fourth search source alongside Open Food Facts and USDA when adding a pantry item, picked the same way (chip, long-press to pin, included in "All" mode). - **Shopping API for sister apps.** A `shopping` token scope covering list, add, check, and clear, always on and reaching nothing but the shopping list. It is what NoteTrace uses to send a checklist's items across and show the list back. - **The shopping list combines duplicate items.** In By Aisle and Flat views, items with the same name and unit show as one row with the amounts added up and a pill for each recipe they came from. Checking, removing, dragging or re-aisling that row applies to every copy behind it. Different units stay separate, and a copy with no amount makes the row show none rather than a wrong total. By Recipe view still lists each recipe's own items, and nothing changed in the database, sync, or the Android app's storage. - **Clear Checked can restock your pantry.** The confirmation lists the matching pantry items that are currently out of stock, ticked by default, so what you bought goes back in stock without a second trip through the Pantry tab. An item is only offered when the row is linked to a pantry item or its name matches exactly one; a generic such as Milk gets a dropdown for which variant you bought; the toast has an Undo. - **Support the iOS fund.** The README and Settings, About name what the fund covers. ### Fixed - **The shopping list froze mid-drag.** Reordering threw an `each_key_duplicate` error and left the page unresponsive, because the drag library renames its placeholder to the dragged row's own id one frame after a drag starts, so a fast pointer move could hand Svelte the same id twice. - **Shopping search crashed when two pantry items shared a name.** Suggestions are deduplicated by name inside the shared picker, which also protects the seven other screens that use it (recipe category, tags, tools, pantry category, cookbook tags, Kitchens invite). - **Cook history and comments returned 403 on a recipe shared with you.** Both reads checked only ownership or group visibility and ignored the kitchen share that granted access to the recipe itself. - **Marking a pantry item back in stock left its quantity at 0**, so it still read as out of stock and couldn't be toggled out again. An explicit `null` quantity now clears the stored value instead of being treated as "leave unchanged". - **Pantry "Expiring Soon" showed already-expired items as still counting down** on wide screens (for example "63d past" instead of "Expired"). The ribbon lists both soon-to-expire and already-expired items, so it is now titled "Expiring & Expired". - **An expanded generic pantry item's photo ballooned to a huge size on desktop** when it had variants. - **Android: the back button closes what's open first.** With a sheet, dialog, menu or photo viewer open, back left the page underneath with it still showing. Back now closes the newest sheet, dialog, menu, picker, photo viewer or Trace chat first, one at a time, then the sidebar, then goes back a page. - **Android: dragging the Trace button or a reorder handle no longer refreshes the page.** Dragging those while scrolled to the top was treated as pull-to-refresh. Pulling down anywhere else still refreshes. - **Android: sheets and dialogs stay below the status bar**, including with the keyboard up, where the JSON import dialog's buttons could sit off the top of the screen. Same fix as NutriTrace [#228](https://github.com/TraceApps/nutritrace/issues/228). - **Error and delete colours come from the theme.** Everything used an `--error` token that was never defined, so it fell back to two different hardcoded reds and ignored the light theme. They all use the real `--danger` token now. ### Security - **Comments could be posted to any recipe by ID.** `POST /api/recipes/:id/comments` had no ownership, share, or visibility check at all; it now requires the same access reading comments does. - **Backup archives are no longer reachable from the public uploads directory.** `BACKUPS_PATH` defaults to a directory inside `UPLOADS_PATH`, and `/uploads` is served ahead of the auth middleware so an Android WebView `` can load images without an `Authorization` header. A full-backup ZIP sitting there was fetchable by URL, while every `/api/full-backup` route is admin-only. It now returns 404. Scheduled backups are off by default, so an install that never enabled them and never created one by hand had nothing there to reach, and there is no directory listing, 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. A custom `BACKUPS_PATH` elsewhere inside the uploads directory is covered too. - **Uploads** are served from a sandboxed set of safe extensions. - **multer** bumped 2.2.0 to 2.3.0, closes a HIGH advisory (denial of service via aborted uploads holding file handles open). - **nodemailer** bumped 9.0.3 to 9.1.1 (root and server), closes a moderate advisory (recipient-header validation bypass). - **adm-zip** bumped 0.6.0 to 0.6.1, closes the symlink-extraction advisory. - **devalue** bumped 5.8.1 to 5.9.4, closes [GHSA-9rgm-9g3h-6x36](https://github.com/advisories/GHSA-9rgm-9g3h-6x36) (denial of service via malformed input, moderate). - `npm audit` reports 0 vulnerabilities for the app and the server. --- ## [1.3.0-dev.03] - 2026-09-19 (pre-release) Third dev pre-release of the 1.3.0 minor. **Action needed when you update:** the container now listens on port 3003 instead of 3001 (see Changed). Also Android fixes for the back button, pull-to-refresh and dialog placement, webhooks for changes made in the Android app, and a desktop shopping layout that fills the screen. ### Changed - **The container now listens on port 3003, the same as the host port. Action needed when you update.** The image used to listen on 3001 inside the container while the sample compose file published it on 3003, so the two numbers never matched, and 3001 was also NutriTrace's port. Both are 3003 now. If your compose file has `"3003:3001"`, change it to `"3003:3003"`; if a reverse proxy or tunnel reaches the container directly (`cooktrace:3001`, or a Traefik `loadbalancer.server.port=3001` label), point it at `3003`. Until you do, CookTrace won't respond after the update. Installs that set `PORT` themselves are not affected, and the host port stays 3003, so bookmarks and the Android app's server address keep working. The weekly summary email's Open CookTrace button, used when no app URL is set, pointed at `localhost:3000` and now points at `localhost:3003`, and running from source starts the server on `:3003`, so it no longer collides with a NutriTrace checkout on the same machine. - **The shopping list fills the screen on desktop.** On wide screens the aisle and recipe cards sat in rows as tall as their tallest card, leaving empty space under the short groups. Short groups now stack into that space, so more of the list fits without scrolling, and the cards re-pack as groups collapse, items are checked off, or the window is resized. Phones and Flat view are unchanged. ### Fixed - **Dragging the Trace button or a reorder handle no longer refreshes the page.** In the Android app connected to a server, dragging the Trace button, a shopping list or recipe ingredient handle, or a cookbook card downward while the page was scrolled to the top was treated as pull-to-refresh and synced. Dragging those no longer counts as a pull; pulling down anywhere else still refreshes as before. - **The Android back button closes what's open first.** Back only knew how to go back a page, so with a sheet, dialog, menu or photo viewer open it left the page underneath with it still showing. Back now closes the newest sheet, dialog, menu, picker, photo viewer or Trace chat first, one at a time, the same as its own close button (a dialog closes as Cancel, and the camera stops). The sync merge questions still need an answer, so back leaves them open. It also closes the slide-out sidebar if that's showing. With nothing open, back goes back a page and then offers to exit, as before. - **Sheets and dialogs stay below the status bar.** The JSON import dialog, with the keyboard up, started above the top of the screen, so its close button and its Import button couldn't be reached; on a tall phone it reached under the status bar even without the keyboard. The same could happen to the shopping list and Cook Diary dialogs, Log a Cook, the photo viewer, the cookbook dialogs, the other import dialogs and the shared sheet used across the app. The Android app draws under the status bar, and these were capped only at a share of the screen, so one that filled its cap (a tall one, or any with the keyboard up) could start under the status bar. They now always stop below it and scroll their content instead. Nothing changes where there's room, or on a computer. Same fix as NutriTrace [#228](https://github.com/TraceApps/nutritrace/issues/228). - **Webhooks never fired for changes made in the Android app.** The app saves locally and uploads through sync, and the sync upload ran Kitchen auto-share but no webhook checks, so cooking a recipe, finishing the shopping list, or running out of a pantry item on the phone sent nothing. The upload now fires `meal.cooked`, `shopping_list.completed` and `pantry.out_of_stock` on the same transitions the web routes use, after the write commits, and sends one completion event per upload however many items it checked. ### Security - **devalue** (pulled in by Svelte) bumped 5.8.1 → 5.9.4, closes [GHSA-9rgm-9g3h-6x36](https://github.com/advisories/GHSA-9rgm-9g3h-6x36) (denial of service via malformed input, moderate). --- ## [1.3.0-dev.02] - 2026-09-17 (pre-release) Second dev pre-release of the 1.3.0 minor. Outgoing webhooks and a general public REST API, a shopping API for sister apps, shopping-list items that combine and can restock the pantry, plus a batch of fixes found in a review of everything since dev.01. ### Added - **Outgoing webhooks.** Configure a target URL in Settings, Webhooks and CookTrace fires a signed HTTP POST the instant a recipe is logged as cooked, the shopping list is fully checked off, or a pantry item runs out of stock. 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 unless `ALLOW_PRIVATE_WEBHOOK_URLS=1`), the same guard image-localizer.js now uses internally for its own external-image downloads. See `docs/webhooks.md`. - **General-purpose public REST API** at `/api/v1/cook-diary`, `/api/v1/shopping`, and a pantry stock write route, for your own scripts and automations rather than the NutriTrace federation contract the rest of `/api/v1` documents. Off by default (`PUBLIC_API_ENABLED=1`; `PUBLIC_API_WRITE_ENABLED=1` additionally unlocks logging a cook, checking a shopping item, and updating pantry stock). Reuses the `mcp:read`/`mcp:write` token scopes MCP already defines, one token works for both interfaces. See `docs/public-api.md`. - **Shopping API for sister apps.** A `shopping` token scope covering list, add, check, and clear, so a sister app (NoteTrace) can drive the list without the general public API switch. - **The shopping list combines duplicate items.** In By Aisle and Flat views, items with the same name and unit show as a single row with the amounts added up and a pill for each recipe they came from. Checking, removing, dragging or re-aisling that row applies to every copy behind it; editing the amount folds them into one item, while editing just the name or unit keeps each recipe's own amount. Different units stay separate, and a copy with no amount makes the row show none rather than a wrong total. By Recipe view still lists each recipe's own items, and nothing changed in the database, sync, or the Android app's storage. - **Clear Checked can restock your pantry.** The confirmation now lists the matching pantry items that are currently out of stock, ticked by default, so what you bought goes back in stock without a second trip through the Pantry tab. An item is only offered when the row is linked to a pantry item or its name matches exactly one, a generic like Bread gets a dropdown to pick the variant, and the toast has an Undo. - **Support the iOS fund.** The README and Settings, About name what the fund covers (both developer accounts, tax and fees included). ### Changed - **Trace settings now match NutriTrace.** The Base URL and API Key fields save when you leave them (or press Enter) instead of through a Save button beside each field, which on a phone in portrait sat past the edge of the screen; the connection is only re-tested when the value changed. On a server where AI is configured through environment variables, the section now says so at the top, shows the provider and model the server actually uses (rather than your own settings, greyed out), and hides the base URL and API key fields since the server holds them. Smart Log gains a Voice Input Language setting for when you speak a different language than your device is set to. - **Claude Fable 5.1 in Trace's model list.** It is now the most capable Claude option; Fable 5 stays selectable, marked as previous. - **Sync status pill in the sidebar**, with one colour rule for sync state across the app. - Toasts can now carry an action button, which the restock flow uses for Undo. ### Fixed - **The shopping list froze mid-drag.** Reordering threw `each_key_duplicate` and left the page unresponsive: svelte-dnd-action renames its placeholder to the dragged row's own id one frame after a drag starts, so a fast pointer move could hand Svelte the same id twice. - **Shopping search crashed when two pantry items shared a name.** Suggestions are now deduplicated by name inside the shared picker, which also protects the seven other screens that use it (recipe category, tags, tools, pantry category, cookbook tags, Kitchens invite). - **Cook history and comments returned 403 on a recipe shared with you.** Both reads checked only ownership or group visibility and ignored the kitchen share that granted access to the recipe itself. - **Marking a planned meal as cooked never fired `meal.cooked`.** The webhook was wired into the two insert paths but not into the update that flips a planned entry, which is how the Diary does it. - **`PATCH /api/v1/pantry/:id/stock` returned 403 for a write-scoped token.** The read-only federation router gated every method on `read:pantry`, so the documented write route was unreachable. - **Settings, Webhooks could not save anything.** Its requests carried no CSRF header (or Bearer token on native), so create, enable/disable, delete and test all came back 403. - **Marking a pantry item back in stock left its quantity at 0**, so it still read as out of stock and could not be toggled out again. An explicit `null` quantity now clears the stored value instead of being treated as "leave unchanged". ### Security - **Comments could be posted to any recipe by ID.** `POST /api/recipes/:id/comments` had no ownership, share, or visibility check at all; it now requires the same access reading comments does. - **MCP read tools were handed to a `mcp:write`-only token.** Read tools registered unconditionally, so only two of the three advertised tiers were really enforced. Each tier now requires its own scope. - **Backup archives are no longer reachable from the public uploads directory.** `BACKUPS_PATH` defaults to a directory inside `UPLOADS_PATH`, and `/uploads` is served ahead of the auth middleware so an Android WebView `` can load images without an `Authorization` header. A full-backup ZIP sitting in that directory was therefore fetchable by URL, while every `/api/full-backup` route 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, since `express.static` percent-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. A custom `BACKUPS_PATH` pointing somewhere else inside the uploads directory is now covered too, rather than only the default `backups` name. - **The SSRF guard classified only the first resolved address.** A host publishing both a public and a private record could pass the check and then be connected to privately, since the request that follows resolves independently. Every resolved address must now pass. - **adm-zip** bumped 0.6.0 → 0.6.1, closing the symlink-extraction advisory (no fixed release existed when this was last reviewed). - **Uploads** are served from a sandboxed set of safe extensions. --- ## [1.3.0-dev.01] - 2026-09-10 (pre-release) NutriTrace can now pull a user's CookTrace recipes and pantry directly through a new read-only federation API, CookTrace gets a Model Context Protocol server for AI agents, and NutriTrace joins Open Food Facts and USDA as a fourth Pantry search source. Plus a pantry display bug fix and dependency security bumps. ### Added - **NutriTrace can pull CookTrace recipes.** New `GET /api/v1/recipes` (search) and `GET /api/v1/recipes/:id` (full detail) let a connected NutriTrace instance search and import a recipe as an NT meal: servings, portion/unit, image, the stored nutrition rollup, and a per-ingredient nutrition snapshot resolved through variant/generic inheritance (an ingredient linked to a generic whose variants each carry their own numbers still ships real values instead of a blank). Gated by a new `read:recipes` token scope, independent of the MCP scopes. - **NutriTrace federation: pantry-read endpoint.** New `GET /api/v1/pantry` returns every leaf pantry row for the token owner (standalone items and variants), shaped for direct POST into NutriTrace's foods library. Generic parents that have variants are deliberately skipped: their leaf variants carry the real nutrition, and a placeholder next to the leaves in NT's foods list would be misleading. Variant rows carry the parent name in the display name ("Flour, Bread") so they read cleanly on the NT side. Uses the same 4-level nutrition resolver `/api/v1/recipes` uses, and derives calories from carbs / protein / fat via Atwater factors when a pantry row only stored macros. Gated by the new `read:pantry` scope (independent of `read:recipes` so users can grant just one). - **`/api/v1/pantry` gains `q`, `limit`, and `offset`.** Backs NutriTrace's Foods-tab CookTrace source chip (search-and-pick a single pantry row). Omitting `q` still returns everything, which is what a bulk import uses. Filtering and paging both run after the leaf-only pass, so `total` always counts importable rows rather than raw pantry rows. `q` matches the composed display name (plus brand and category), so a variant stored as "Bread" under a "Flour" generic is still found by typing `flour`. - **`read:pantry` and `read:recipes` API-token scopes.** Ticked at token-creation time in Settings, API Tokens, New Token; independent of the MCP scopes and of each other. - **Model Context Protocol (MCP) server.** CookTrace now exposes a read + write + destructive MCP endpoint at `/api/mcp` so Claude Desktop, Cursor, Codex, and other MCP-aware agents can search recipes, browse the pantry and shopping list, log a cook, and (with the right scope) create recipes or pantry items directly. Fourteen tools across three independently-gated tiers (`mcp:read` / `mcp:write` / `mcp:destroy`), each requiring both a server-side env flag and a matching token scope. Off by default. See [docs/cooktrace/mcp.md](https://traceapps.github.io/docs/cooktrace/mcp/) for setup. - **Personal access tokens.** New Settings → API Tokens section (admin, multi-user mode) to mint, scope, and revoke tokens. Currently the sole consumer is MCP and NutriTrace federation; built as a general-purpose token store for future API surfaces. - **NutriTrace joins the Pantry search chips.** A connected NutriTrace instance's food catalog is now a fourth search source alongside Open Food Facts and USDA when adding a pantry item, picked the same way (chip, long-press to pin, included in "All" mode). - **Food Sources settings reorganized.** NutriTrace Federation now lives inside Settings → Food Sources under its own "NutriTrace" sub-heading (between USDA and Barcode Scanner) instead of a separate top-level section, since federation is currently used purely as another food source. ### Fixed - **Pantry "Expiring Soon" spotlight showed already-expired items as still counting down** on wide screens (for example "63d past" instead of "Expired"). The ribbon deliberately lists both soon-to-expire and already-expired items together, so it's now titled "Expiring & Expired" and the day-count label reads "Expired" once a date has passed. - **Expanded generic pantry item's photo ballooned to a huge size on desktop** when it had variants. A full-row grid span wasn't being respected by the photo's own full-width sizing. ### Security - **multer** bumped 2.2.0 → 2.3.0, closes a HIGH advisory (denial of service via aborted uploads holding file handles open). - **nodemailer** bumped 9.0.3 → 9.1.1 (root and server), closes a moderate advisory (recipient-header validation bypass). --- ## [1.2.0] - 2026-09-02 Minor release. Big themes: ingredient-to-step linking with inline Cook Mode rendering, a wide-screen desktop pass across six main pages plus the Cookbook view, real drag-and-drop cookbook reordering with a settable cover image, and a large batch of fixes accumulated since v1.1.3 (Kitchen auto-share, CSRF on import dialogs, email links behind a reverse proxy, single-user-mode data adoption, pantry sort/search edge cases, and more). ### Added - **Ingredient links on recipe steps.** Editor gets a collapsible "Link ingredients" panel per step with chip toggles for every named ingredient. In Cook Mode each step renders its linked ingredients inline right below the step text so quantities are visible without scrolling back to the top. Tapping an inline ingredient checks it off in the top list too, and marking a step done cascades the check to its linked ingredients. Tandoor imports carry their existing step-to-ingredient adjacency across automatically ([#40](https://github.com/TraceApps/cooktrace/issues/40)). - **Wide-screen desktop layouts across the six main pages.** Settings switches to a two-pane rail-plus-content shell matching NutriTrace and LiftTrace. Manage's section-swap gets a cross-fade plus a sort control and an "Unused" quick-filter chip. Shopping tiles category groups into a masonry grid with a sticky toolbar, auto-collapse for finished sections, and a progress bar. Diary switches its day list to a card grid on wide screens, with roomier Month cells (more cook pills fit per day) and bigger Photos tiles. Pantry gets a category-chip wrap, a variant-row span for expanded generics, and an amber "Expiring soon" ribbon. Recipes' loading skeleton renders more placeholder cards (8 to 12) so it fills a wide grid instead of leaving a gap under a short first batch. Recipe view, Recipe editor, and Cookbook view also get wider ultrawide-monitor caps so nothing is left with dead space on either side past ~1440-1480px. - **Cookbook view gets search, drag-and-drop reorder, and a settable cover image.** Search box + sort dropdown (Manual Order / A-Z / Favorites First) once a cookbook has more than one recipe. Manual order supports picking a card up from anywhere on it (not just a small handle) and dropping it into a new position, with the rest of the grid sliding smoothly out of the way. Cover image is settable from the same picker recipe step photos use. Cookbook recipe cards also now match the Recipes tab's cards exactly (size, category badge, star rating, tags, pantry-match pill, uniform row heights); they were previously thinner and missing most of that. - **Diary search + meal-type filter.** Sticky toolbar gains a text search across recipe names, notes, and cook name, plus Breakfast / Lunch / Dinner / Snack quick chips. Each day-header shows a cook-count pill. - **Shared recipes get a category filter.** Chips are scoped to only the categories that appear in what other users shared with you (search and sort already applied there). - **Kitchens invite autocomplete.** Type-to-narrow picker pulls from the server user list (multi-user mode only). Freeform typing still works as a fallback. - **Admin inline edit user.** Full name and email edit in place from Settings → User Management. Username stays immutable as the stable identifier. - **PWA update flow.** In-app prompt asks to reload when a new version is out. Red dot on the Settings nav icon while an update is pending. Update-check cadence is configurable (Hourly / Every 4 hours / Every 12 hours / Daily / Manual). - **iOS PWA viewport lock.** Horizontal touch pan no longer drifts the whole page on iPhone Safari or the installed PWA. Rubber-band bounce stays contained inside the app. - **Server honors `HTTP_PROXY`, `HTTPS_PROXY`, `NO_PROXY` env vars** via undici's `EnvHttpProxyAgent`, so every outbound scrape / import / AI call routes through a configured forward proxy without per-call-site changes. A missing scheme on either var now gives a clear startup error instead of an opaque one. ### Changed - **Pantry source-chip (OFF / USDA / All) is now a filter, not an append.** With an active query, picking one of these hides the local pantry list and shows only that source's external results. Local pantry stays visible when the Pantry chip is picked or when the query is empty. Grid / list view toggle hides accordingly when the pantry list itself is hidden. - **Move/Copy dialog's toggle slides between Move and Copy** instead of snapping, and dropped the redundant "(keep here too)" / "(remove from here)" explainer text. - Profile page renders without its own header when opened from inside Settings; the Save button moves inline for the embedded view. - Recipe close button reliably returns to the Recipes list from any origin. - Cookbook import dialog's "Settings → Trace Assistant" link jumps straight to `/settings/ai` instead of the Settings index. ### Fixed - **Kitchen auto-share was silently dropping every recipe created on the native Android app.** The mobile-write path (`/api/sync/push`) went straight to the DB and never called the fan-out hook, so Kitchen members saw nothing new from anyone cooking on their phone. Every recipe insert path now fans out uniformly. Toggling auto-share off then back on backfills any recipes created before this cut. - **Kitchen "N recipes shared" count double-counted** in kitchens with two or more members. Now counts distinct recipes. - **File / cookbook / URL / bulk import dialogs returned "Invalid CSRF token" on PWA.** Raw fetch calls now attach the CSRF header via a shared helper so every mutating import endpoint works ([#43](https://github.com/TraceApps/cooktrace/issues/43)). - **Password reset links, invite links, and the SMTP test email's embedded logo rendered as `http://` behind a TLS-terminating reverse proxy**, which mail providers flagged as spam. Now honors `X-Forwarded-Proto` and `X-Forwarded-Host` ([#42](https://github.com/TraceApps/cooktrace/pull/42), thanks @clifmo). - **Data left behind in single-user mode is adopted on upgrade.** Instances that already enabled user management on an earlier build had their unowned rows stranded for good. Startup now adopts them, once, when exactly one account exists. - **Enabling user management no longer strands data written in single-user mode** ([TraceApps/docs#2](https://github.com/TraceApps/docs/issues/2)). Cookbooks, recipe / pantry categories, custom + disabled units, and Trace chat history are now claimed alongside recipes / pantry / diary / shopping, in one transaction. - **Deleting an account no longer leaves its hidden-unit preferences behind.** `disabled_units` is now included in every account-removal path (self-delete, admin delete, disable user management, lockout recovery) and the OIDC first-login bootstrap. - **Android native app showed a blank screen when opening a recipe someone shared with you.** The single-recipe read path now falls back to a server fetch on a local cache miss, instead of rendering against `null`. - **Recipe step photos now persist.** They were being silently dropped on every save before. - **Pantry items no longer disappear when sorted A-Z** if their category slug doesn't match a current pantry-categories catalog entry ([#41](https://github.com/TraceApps/cooktrace/issues/41), thanks @xiaojwus). Orphaned buckets now fall into Uncategorized instead of vanishing. - **Variant Create button** enables when only an override name is typed. Button label now reflects which name will actually be created. - **"All" mode pantry search results** render with full name, brand, barcode, and thumbnail instead of a blank row. - **External OFF / USDA search results** render at full opacity instead of picking up the local out-of-stock dimming. - **Split-chip pills (OFF, USDA)** light up as one unit on hover and active state instead of just the half the cursor is over. - **Recipe save now guards against a stale or misbehaving client wiping `ingredients` / `steps` / `tags` / `tools`** by sending an empty value over existing content. Legitimate deletes via the per-item delete flows still work. - **Clearing the Trace chat now asks for confirmation** instead of wiping the conversation on a single tap, with no way back. - **Cook-diary rows with multiple broken phone-local photos now converge in one sync cycle** instead of one cycle per photo. - **Missing-photo placeholder in the diary photos view** is more robust to future markup changes around it. - **Ingredient-name suggestions on mobile no longer cover the keyboard and most of the screen.** The field used a native `` / `` combo, which mobile WebViews render as their own OS-level picker with no awareness of the app's layout or the on-screen keyboard. Replaced with an in-app dropdown that positions itself against the actual visible viewport, same approach the unit picker next to it already used. ### Security - **fast-uri** bumped 3.1.5 → 3.1.7, closes four HIGH advisories: [GHSA-5jgf-p345-68v8](https://github.com/advisories/GHSA-5jgf-p345-68v8), [GHSA-f65p-4m7j-42xc](https://github.com/advisories/GHSA-f65p-4m7j-42xc), [GHSA-fph4-wmhf-6fwf](https://github.com/advisories/GHSA-fph4-wmhf-6fwf), and [GHSA-jqff-g426-hqxp](https://github.com/advisories/GHSA-jqff-g426-hqxp) (host confusion and SSRF via malformed URI normalization). - **browserslist** bumped 4.28.1 → 4.28.8, closes [GHSA-c83g-rgw3-j3cx](https://github.com/advisories/GHSA-c83g-rgw3-j3cx) (unbounded memory growth, HIGH) and [GHSA-73wf-gq98-2v4g](https://github.com/advisories/GHSA-73wf-gq98-2v4g) (crash / prototype write via untrusted stats file, HIGH). - **@xmldom/xmldom** bumped 0.8.13 → 0.8.15, closes [GHSA-6gmq-8vp8-gcm6](https://github.com/advisories/GHSA-6gmq-8vp8-gcm6) (XML fragment injection, moderate). - **qs** (server) bumped 6.15.3 → 6.16.0, closes [GHSA-x5fp-wj9c-mxmx](https://github.com/advisories/GHSA-x5fp-wj9c-mxmx) (array-limit bypass) and [GHSA-4mjr-xmp4-gh2g](https://github.com/advisories/GHSA-4mjr-xmp4-gh2g) (denial of service), both moderate. - All standard transitive bumps via `npm audit fix` (client + server). `npm audit` and `npm test` both clean after. --- ## [1.2.0-dev.03] - 2026-08-30 (pre-release) Third dev pre-release of the 1.2.0 minor. Cookbook view gets search, drag-and-drop reorder, a settable cover image, and full card parity with the Recipes tab. ### Added - **Cookbook recipe grid gets search + sort.** Search box + sort dropdown (Manual Order / A-Z / Favorites First) appear once a cookbook has more than one recipe. A no-matches empty state shows when a search returns nothing. Smart cookbooks keep search but hide sort (their order comes from the saved filter). - **Cookbook recipes are drag-and-drop reorderable.** Manual order supports picking a card up from anywhere on it and dropping it into a new position, with every other card sliding smoothly out of the way rather than popping to its new spot. Only active in Manual order view; a search or any other sort disables it, same as the old up/down buttons did. - **Cookbook cover image is settable.** Tap the cover thumbnail (owner, non-smart cookbooks) to pick a new cover via the same image picker recipe step photos use. ### Changed - **Cookbook recipe cards now match the Recipes tab exactly:** same size, same fields (category badge, star rating, tags, pantry-match pill), same uniform row heights. They were previously thinner and missing all of that; the server was pulling a narrower, unhydrated column set for cookbook reads than every other recipe-card endpoint uses. - **Move/Copy dialog's toggle slides between Move and Copy** instead of snapping, and dropped the redundant "(keep here too)" / "(remove from here)" explainer text. - Recipe view, Recipe editor, and Cookbook view content width now scales up further on ultrawide monitors instead of leaving dead space on either side past ~1440-1480px. ### Fixed - **`HTTP_PROXY` / `HTTPS_PROXY` missing a scheme now gives a clear startup error** instead of an opaque one (port of a NutriTrace fix). --- ## [1.2.0-dev.02] - 2026-08-26 (pre-release) Second dev pre-release of the 1.2.0 minor. One fix for a blank screen when opening a shared recipe on the native Android app. ### Fixed - **Android native app showed a blank screen when opening a recipe someone shared with you.** The single-recipe read path (`NtApi.getRecipe`) dispatches to the cached impl on native connected mode, which reads from local SQLite. Local only holds recipes the user owns (sync-push writes owned recipes), so any shared recipe id missed and the view rendered against `null`. The cached impl now falls back to a server fetch on a local miss, so shared recipes open correctly. Owned recipes still hit local first for offline / speed. --- ## [1.2.0-dev.01] - 2026-08-25 (pre-release) First dev pre-release of the 1.2.0 minor. The version jumps past 1.1.4 because the accumulated scope on dev is well beyond patch: whole desktop UI overhaul across six pages, ingredient-to-step linking with Cook Mode inline render, PWA update prompt, admin inline-edit user, kitchen invite autocomplete, iOS viewport lock, plus the earlier single-user data-claim work. Roughly forty commits since 1.1.4-dev02. ### Added - **Ingredient links on recipe steps.** Editor gets a collapsible "Link ingredients" panel per step with chip toggles for every named ingredient. In Cook Mode each step renders its linked ingredients inline right below the step text so quantities are visible without scrolling back to the top. Tapping an inline ingredient checks it off in the top list too, and marking a step done cascades the check to its linked ingredients. Tandoor imports carry their existing step-to-ingredient adjacency across automatically ([#40](https://github.com/TraceApps/cooktrace/issues/40)). - **Wide-screen desktop layouts across the six main pages.** Settings switches to a two-pane rail-plus-content shell matching NutriTrace and LiftTrace. Manage rail pins on scroll with a cross-fade on section swap plus a sort control and an "Unused" quick-filter chip. Shopping tiles category groups into a masonry grid with a sticky toolbar, auto-collapse for finished sections, and a progress bar. Diary condenses day-groups into a card grid with denser Month cells and Photos tiles. Pantry gets a category-chip wrap, a variant-row span for expanded generics, and an amber "Expiring soon" ribbon. Recipes gets skeleton-count parity with the grid. - **Diary search + meal-type filter.** Sticky toolbar gains a text search across recipe names, notes, and cook name, plus Breakfast / Lunch / Dinner / Snack quick chips. Each day-header shows a cook-count pill. - **Shared recipes search + sort + category filter.** Chips are scoped to only the categories that appear in what other users shared with you. - **Kitchens invite autocomplete.** Type-to-narrow picker pulls from the server user list (multi-user mode only). Freeform typing still works as a fallback. - **Admin inline edit user.** Full name and email edit in place from Settings → User Management. Username stays immutable as the stable identifier. - **PWA update flow.** In-app prompt asks to reload when a new version is out. Red dot on the Settings nav icon while an update is pending. Update-check cadence is configurable (Hourly / Every 4 hours / Every 12 hours / Daily / Manual). - **iOS PWA viewport lock.** Horizontal touch pan no longer drifts the whole page on iPhone Safari or the installed PWA. Rubber-band bounce stays contained inside the app. - **Server honors `HTTP_PROXY`, `HTTPS_PROXY`, `NO_PROXY` env vars** via undici's `EnvHttpProxyAgent`, so every outbound scrape / import / AI call routes through a configured forward proxy without per-call-site changes. - **Step photos persist.** Recipe step photos survive save-and-reload (they were being silently dropped on every save before). ### Changed - Profile page renders without its own header when opened from inside Settings; the Save button moves inline for the embedded view. - Recipe close button reliably returns to the Recipes list from any origin. - Cookbook import dialog's "Settings → Trace Assistant" link jumps straight to `/settings/ai` instead of the Settings index. ### Fixed - **Kitchen auto-share was silently dropping every recipe created on the native Android app.** The mobile-write path (`/api/sync/push`) went straight to the DB and never called the fan-out hook, so Kitchen members saw nothing new from anyone cooking on their phone. Every recipe insert path now fans out uniformly. Toggling auto-share off then back on backfills any recipes created before this cut. - **Kitchen "N recipes shared" count double-counted** in kitchens with two or more members. Now counts distinct recipes. - **File / cookbook / URL / bulk import dialogs returned "Invalid CSRF token" on PWA.** Raw fetch calls now attach the CSRF header via a shared helper so every mutating import endpoint works ([#43](https://github.com/TraceApps/cooktrace/issues/43)). - **Password reset links, invite links, and the SMTP test email's embedded logo rendered as `http://` behind a TLS-terminating reverse proxy**, which mail providers flagged as spam. Now honors `X-Forwarded-Proto` and `X-Forwarded-Host` ([#42](https://github.com/TraceApps/cooktrace/pull/42), thanks @clifmo). - **Data left behind in single-user mode is adopted on upgrade.** Instances that already enabled user management on an earlier build had their unowned rows stranded for good. Startup now adopts them, once, when exactly one account exists. - **Enabling user management no longer strands data written in single-user mode** ([TraceApps/docs#2](https://github.com/TraceApps/docs/issues/2)). Cookbooks, recipe / pantry categories, custom + disabled units, and Trace chat history are now claimed alongside recipes / pantry / diary / shopping, in one transaction. Same handover runs whether the first account is created with a password or by the first OIDC sign-in. - **Deleting an account no longer leaves its hidden-unit preferences behind.** `disabled_units` was missing from every account-removal path (self-delete, admin delete, disable user management, lockout recovery). Same incomplete list existed a second time in the OIDC first-login bootstrap; both paths now share one implementation. - Show Shared Recipes toggle now mixes shared recipes into the main grid even when the owned list is empty. Search and filter apply uniformly across owned and shared. - Recipes tab: search input, sort, and category chips render even when the owned count is zero if shared-mix is on. - Various pantry polish (split-chip active state, chip typography parity, variant create button, source-chip filter, all-mode field shape, external-search opacity). ### Security - CSRF header now attached to every raw-fetch mutating call so file / cookbook / URL / bulk imports enforce CSRF the same as every other POST ([#43](https://github.com/TraceApps/cooktrace/issues/43)). - Password reset and invite URLs now land on the correct HTTPS origin behind a TLS proxy, preventing accidental link degradation to `http://` ([#42](https://github.com/TraceApps/cooktrace/pull/42)). - No new dependencies. `npm audit` reports 0 vulnerabilities. --- ## [1.1.4-dev02] - 2026-08-16 (pre-release) Second dev pre-release of the 1.1.4 patch. Six findings from the code review of dev01: two silent data-loss guards on the recipe write path, a sync photo reconcile that took multiple cycles to converge, and a Trace confirmation dialog with UI polish. ### Fixed - **Clearing the Trace chat now asks for confirmation.** A single tap on the header button previously wiped the entire conversation with no way back. Ports the same guard LiftTrace added in [TraceApps/lifttrace#50](https://github.com/TraceApps/lifttrace/pull/50) so behavior stays uniform across the three Trace apps. Also fixes a z-index bug where the confirm dialog opened behind the Trace panel. - **Recipe save from a client sending flat-shape ingredients no longer silently reverts.** The server-side empty-guard required every ingredient element to expose an `.items` field (grouped shape). Any client sending the legacy flat shape (`[{name, quantity, unit}, ...]`) — older builds, AI generators, some importers — was misclassified as empty, so the caller's non-empty ingredients were silently dropped in favor of the server's prior JSON. Now distinguishes by whether the element carries an own `items` field. - **Removing all tags from a recipe no longer silently reverts.** The tag chip UI's "remove all" edit sent `tags: []`, but the empty-guard treated that as "don't overwrite server tags", so on next refresh the tags reappeared. Tags removed from the guard list entirely — small blast radius (flat string array), common UI action, wrong tradeoff to preserve. - **Cook-diary rows with multiple broken phone-local photos now converge in one sync cycle** instead of one cycle per photo. Prior implementation processed each broken photo as its own job and spliced the array during iteration, silently shifting later jobs' cached array indexes so only the first got rewritten each pass. - **Missing-photo placeholder in the diary photos view no longer breaks silently if the tile markup gains a wrapper.** The `on:error` handler now uses `closest('.photo-tile')` / `closest('.lightbox-img-wrap')` instead of `parentElement`, so future DOM changes can't silently regress the fallback. ### Changed - **Photo-reconcile SQL string literals switched to single quotes** (SQL standard) so the reconcile pass doesn't silently fail on any future `@capacitor-community/sqlite` build that disables `SQLITE_DQS_DML` (the SQLite team's own recommended default). The queries previously used SQLite's legacy double-quoted-string tolerance, which was silently OK today but would break on a strict-mode upgrade. - **Pantry orphan-slug console warning no longer spams the console.** The warn sat inside a reactive block that re-runs on every keystroke; users with orphan-slug items previously saw it 3× per typed word. Deduped via a module-scope Set so each unique orphan slug logs at most once per page load. ### Security - No new dependencies. `npm audit` reports 0 vulnerabilities. --- ## [1.1.4-dev01] - 2026-08-16 (pre-release) First dev pre-release of the 1.1.4 patch. Bug fixes and pantry UI polish from the last two weeks plus a server-side recipe-write safety guard. ### Fixed - **Pantry items no longer disappear when sorted A-Z** if their category slug doesn't match a current pantry-categories catalog entry ([#41](https://github.com/TraceApps/cooktrace/issues/41), thanks @xiaojwus). The grouping code silently dropped orphaned buckets; they now fall into Uncategorized so nothing is invisible. Trigger isn't specific to Chinese category names, but non-ASCII category slugs are one common way to hit slug drift between item and catalog. - **Variant Create button** enables when only an override name is typed (previously required a brand or the click did nothing). Button label now reflects which name will actually be created. - **"All" mode search results** render with full name, brand, barcode, and thumbnail. Previously showed only the source label with blank content due to a field-shape mismatch in the merged list. - **External OFF / USDA search results** render at full opacity. The out-of-stock signal for local pantry rows was leaking to external rows. - **Split-chip pills (OFF, USDA)** light up as one unit on hover and active state instead of just the half the cursor is over; text sizes match the non-split chips. - **Recipe save no longer wipes `ingredients` / `steps` / `tags` / `tools`** from a stale mobile client. Server-side empty-guards refuse an incoming empty value on a field that already has content. Legitimate deletes still work via the per-item delete flows. ### Changed - **Pantry source-chip (OFF / USDA / All) is now a filter, not an append.** With an active query, picking one of these hides the local pantry list and shows only that source's external results. Local pantry stays visible when the Pantry chip is picked or when the query is empty. - **Grid / list view toggle** hides when the pantry list itself is hidden (external source chip with active query). The toggle has nothing to act on in that state. ### Security - No new dependencies. `npm audit` reports 0 vulnerabilities. --- ## [1.1.3] - 2026-08-08 ### Fixed - **Phone-taken cook diary photos now sync across every device.** Photos attached from the phone used to save to Capacitor's private filesystem and store a `_capacitor_file_` URL that only worked on the exact device (and install) that took the picture — every other device saw a broken image. Uploads now go server-first in native connected mode, storing a portable `/uploads/` URL that every device can render. Offline photo-taking still works — the photo saves locally when there's no network, and gets promoted to a portable server URL on the next successful sync. Existing broken entries auto-heal on the next sync too: if the underlying file still exists on any connected device, it re-uploads and rewrites the URL; if the file is gone from all installs, the URL clears and a subtle placeholder shows in the photos view instead of a broken-image glyph. - **Pull-to-refresh no longer triggers mid-page.** The top-of-scroll check missed pages that used a nested scroll container (recipe view, editor pages), causing a downward swipe anywhere on those pages to fire a sync. Now walks up from the touched element to find the actual scrolling container and only fires when THAT container is at the top. - **Settings sidebar duplicate labels + i18n key deduplication.** The Food Sources sidebar label was rendering twice, and the Settings i18n file had a small number of duplicate `settings.*` stubs. ### Changed - **AI model picker refreshed.** Added Google **Gemini 3.x** and OpenAI **GPT-5.6** to the model presets. (Claude Opus 5 and Claude Fable 5 were already added in v1.1.2.) ### Internal - Ported LiftTrace's i18n CI check (duplicate-key + unresolved-code-reference detection). - Docker workflow: dropped the semver-tag trigger that was firing a spurious failed run on every release tag push. --- ## [1.1.2] - 2026-08-05 ### Added - **Docker Hub mirror.** Images now publish to both `ghcr.io/traceapps/cooktrace` (primary) and `traceapps/cooktrace` on [Docker Hub](https://hub.docker.com/r/traceapps/cooktrace). Identical multi-arch tag set on both registries; workflow simplified to publish only `:latest` (main pushes) and `:dev` (dev pushes) — semver family tags and per-commit `:sha-*` tags are intentionally not emitted (historical versions live on the Releases page). - **AI provider model picker: Claude Opus 5 and Claude Fable 5** now selectable under Settings → Trace → Model (alongside the existing Claude Opus 4.8 option). Opus 5 is the current Opus flagship; Fable 5 is Anthropic's most capable widely released model. ### Changed - **Open Food Facts barcode lookup moved to the current v3 endpoint.** OFF officially deprecated the v0 and v2 product endpoints in favor of `/api/v3/`. Product schema is unchanged (all the same fields), only the envelope status shape differs (v3 returns `"success"` vs v0/v2's `1`). CookTrace now accepts both so mirror hits and live v3 hits are treated the same. Text search continues on search-a-licious (already current-canonical). Parallel to NutriTrace's #133 migration; both apps are now on the two endpoints OFF actively recommends. - **Micronutrient units display as `mcg` instead of `µg`** (Vitamin A / Vitamin D / Vitamin K / Folate (B9) / Vitamin B12). Same unit (microgram), consumer-friendlier spelling used by supplement labels, prescriptions, and the FDA. Keeps parity with NutriTrace's #137 fix and avoids the CSS uppercase transform edge case where `µg` case-maps to `Μg` (Greek Capital Mu) and can visually read as `MG`. No data change. - **Default host port in the reference `docker-compose.yml` bumped from `3000` to `3003`.** Family sequence is now NutriTrace `3001`, LiftTrace `3002`, CookTrace `3003`, all off the very-common `:3000`. **Existing installs are not affected** — the container-side port is unchanged (still `3001` internally), so any existing compose file's `3000:3001` mapping keeps working. Only new copy-paste installs get the new default. ### Fixed - **Silent ntfy push-notification failure for every user with ntfy configured.** `fetch()` requires HTTP header values to be Latin-1 (ByteString); the hardcoded `CookTrace — ` prefix contained a U+2014 em-dash and silently threw `Cannot convert argument to a ByteString` on every ntfy send since v1.1.0, dropping all notifications for anyone using ntfy. Added `_encodeHeaderValue()` which RFC 2047-encodes the Title header when non-Latin-1 characters are present; ntfy decodes this format natively per their [docs](https://docs.ntfy.sh/publish/#e-mail-style-headers). Titles arrive intact (em-dash + emoji preserved) instead of dropping the entire notification. Credit: @clifmo (PR #35). ### Security - **undici** bumped 7.19.0 → 7.29.0 — closes [GHSA-9m3f-h34x-vv7c](https://github.com/advisories/GHSA-9m3f-h34x-vv7c) (cross-user info disclosure + parse-time crash via degenerate private cache directives, HIGH) plus four MEDIUM CVEs (retry-interceptor response desync, cookie attribute injection, Cache-Control directive info disclosure via whitespace, CRLF injection via blob-type). - **fast-uri** bumped 3.0.1 → 3.1.5 — closes [GHSA-7p8r-x3mc-p8w7](https://github.com/advisories/GHSA-7p8r-x3mc-p8w7) (host confusion via backslash authority introducer, HIGH). - **nanoid** bumped to 3.3.17 — closes [GHSA-2v37-7h3g-55p8](https://github.com/advisories/GHSA-2v37-7h3g-55p8) (infinite loop when custom generator size is zero, HIGH). --- ## [1.1.1] - 2026-08-03 ### Fixed - **Server-connection banner no longer covers the phone's notification bar.** The red "server unreachable" banner sat edge-to-edge at viewport top:0, which on Android slid it up over the status bar / clock / hamburger. Now floats as a rounded card below the status bar and the app's compact header, matching NutriTrace. --- ## [1.1.0] - 2026-08-02 > **⚠ Upgrade note.** The Android app identifier change (`app.cooktrace.local`, see "Changed" below) invalidates the WebView's cached auth cookie. Server-connected Android users will need to re-enter their server URL and sign back in once after upgrading; standalone Android users lose their theme / accent / display prefs but keep all recipe, pantry, diary, and shopping data (that lives in local SQLite, unaffected). PWA / browser users are not affected. ### Added - **Pantry page: configurable default search source.** New setting under **Settings → Food Sources → Pantry Search → Default Search Source** picks which chip the Pantry page opens with (options: All, My Pantry, OFF, USDA, filtered to whatever external sources are enabled). Existing users keep the current default (My Pantry); anyone who prefers all-sources fan-out on every visit can switch to All and it sticks across sessions + devices. Ported from NutriTrace #128. - **Pull-to-refresh sync (Android).** In native server mode, swipe down from the top of any page to trigger a manual sync. Matches NutriTrace's behavior for family consistency. - **Smart connection banner.** When sync fails, the banner explains what actually went wrong (no network vs cellular-only vs server unreachable vs HTTP error) with a Retry button, instead of a generic "sync error". Structured classification via `describeConnectionIssue` mirrors NT. - **Cloud icon in hamburger menu goes red on server disconnect.** Previously only lit up when the OS reported offline; now also triggers on server-side outages and cellular-vs-LAN routing mismatches, matching NT's behavior. - **Optional email on the "Create Admin Account" form.** Shows up only when SMTP is configured via environment variables (`SMTP_HOST`/`SMTP_USER`/etc. in docker-compose), so we know the server can actually reach that address at that point. Stored on the admin's user record for password-reset and invite emails later. - **In-app updates.** New Settings → Updates panel checks GitHub Releases for a newer version and, on Android, downloads the signed APK and hands off to the system installer via FileProvider. One primary button drives the whole flow (Check Now → Download & Install → Downloading X%). Skip This Version link when an update's available. Collapsible "What's new" panel below the button renders the release notes inline (markdown) with a "View on GitHub" link. Silent shade notification when the OS notification permission is granted; top-of-app banner as fallback when permission's denied. Opt-in Stable or Dev channels. Same shared TraceApps signing key means Android upgrades in place with no reinstall. - **Accent-tinted browser chrome.** The browser tab bar / address strip now picks up your current accent color via ``. Running CookTrace alongside NutriTrace / LiftTrace? Pick a distinct accent per install and the tabs read as visually different at a glance. Favicon stays the branded CookTrace mark. ### Changed - **Bitwarden / password managers now show a real app identifier instead of "localhost" (Android).** The Android app used to serve its WebView from `https://localhost/`, so autofill entries saved through Bitwarden / 1Password / etc. showed up as "localhost" — indistinguishable from any other localhost app. CookTrace now identifies itself as `app.cooktrace.local`, which reads clearly in autofill dialogs and in your saved-credentials list. **One-time upgrade cost:** the origin change orphans locally cached web-only state, so on first launch after upgrading you'll need to re-enter your server URL + log in again (server-connected users), and your theme / accent / display prefs will reset to defaults (standalone users). **Your recipe, pantry, and shopping data is unaffected** — that lives in a local SQLite database that's separate from the WebView. - **SMTP "Username" field relabeled to "Email or Username".** Most SMTP providers want the full email as the username; label change removes the guesswork. - **Full i18n retrofit across the app.** Every hardcoded UI string has been extracted into `src/i18n/en.json` and reads via `svelte-i18n`. Covers Settings (main page + Backup, Notifications, ServerConnection, Auth, ImportFromNT, Email, Trace, UserManagement, Kitchens, Import, Federation, Nutrition), Pantry (PantryItemSheet + PantryEditor + Pantry + PantryView), Recipes (Recipes + RecipeEditor + RecipeView + CookbookView + PublicRecipe + import/comment/cook dialogs + NutritionFacts), core routes (CookDiary, Shopping, NativeSetup, Login, Wizard, Profile, Manage), manage tables (Cookbooks, Units, Pantry/Recipe Categories, Taxonomy), Trace AI, and shared UI (BarcodeScanner, TimePicker, CookHeatmap, ImagePicker, IconPicker, timer pills, Sidebar). ~460 new keys added, Weblate-ready. Chicago-style title case for labels/buttons/headings, sentence case for body prose / errors / placeholders / toasts. Uses paired `_desc` sibling keys and column-aligned values (Fathom-inspired conventions carried across the TraceApps family) so translators get inline context. ### Fixed - **OFF country filter works again + expanded country list.** The Pantry's Open Food Facts country filter was being passed to search-a-licious with the wrong query shape (`countries_tags_en=`), so selecting Norway or any non-World option silently had no effect. Fixed by using search-a-licious's native inline Lucene syntax (`q= +countries_tags:"en:"`). While in there: expanded the picker from 15 → 30 countries, alphabetized, and added Argentina, Austria, Belgium, Chile, Denmark, Finland, Ireland, Netherlands, New Zealand, Norway, Poland, Portugal, Singapore, South Africa, South Korea, Sweden, Switzerland. Same fix ported from NutriTrace's #131 (@JacosVerksted), adapted to search-a-licious. - **App icon no longer shows a white halo.** The bundled icon PNGs had ~15px of solid white padding baked into their corners. On tinted browser chrome the halo was visible around the tab favicon; in-app the icon looked framed. Corners now clear cleanly. Icon URLs also cache-busted with the app version so shipped icon fixes actually take effect without users needing to clear their browser cache. - **Create Admin form password field no longer crushed** (parity with NT #122). The password input on the Enable User Management form was rendering as a colored sliver because of a flex-layout bug. Password + Confirm now sit symmetrically side-by-side, each with its own eye toggle sharing show/hide state. --- ## [1.0.3] - 2026-07-28 ### Fixed - **Backups, SMTP config, and other admin panels now work in single-user mode.** With User Management turned off, `POST /api/full-backup`, `PUT /api/app-config`, `POST /api/app-config/test-email`, `POST /api/off-local/refresh`, and `GET /api/updates/server-status` all returned 403 "Admin only", even though CookTrace's own frontend correctly identifies the sole owner as effectively-admin. `requireAdmin` middleware now passes through when User Management is off, mirroring `requireAuth`. Fourteen previously-broken admin routes come back to life. LiftTrace already had this fix; parity restored. --- ## [1.0.2] - 2026-07-28 ### Added - **Unified docs site** at [traceapps.github.io/docs/](https://traceapps.github.io/docs/) covering CookTrace, LiftTrace, and NutriTrace in one place. Docker install, OIDC recipes for Authentik / Keycloak / Pocket-ID / Authelia / Google / Auth0, Trace AI setup for every provider, mobile install, backups, troubleshooting, and full per-app feature guides. - **Documentation badge** in the README hero pointing at the new docs section, plus a transparent-background logo and a jump strip near the top. - **GPT-5.6 chat parameter support.** When the configured OpenAI model is GPT-5.6 or newer, requests use `max_completion_tokens` + `reasoning_effort` instead of the legacy `max_tokens` shape. Older models are unaffected. ### Fixed - **Backup JSON export was silently missing 12 tables.** `user_settings`, `ai_chat_history`, `recipe_categories`, `recipe_comments`, `pantry_categories`, `custom_units`, `disabled_units`, `cookbooks`, `recipe_cookbook_links`, `recipe_shares`, `cookbook_shares`, and `kitchens` + `kitchen_members` were not captured by the portable JSON export. Restore now brings everything the ZIP contains. - **Kitchen-share restore ordering bug.** Restoring a backup nulled out `via_kitchen_id` on every kitchen-fanned share because `kitchens` restored after `recipe_shares` / `cookbook_shares`, triggering the `ON DELETE SET NULL` cascade on the just-inserted rows. Kitchens are now restored first, provenance survives. - **Gemini AI default bumped** to `gemini-2.5-flash` (was `gemini-1.5-flash`, which Google is retiring). Saved configs that still point at any retired 1.5 / 2.0 ID auto-remap server-side so requests never 404 against a dead endpoint. - **oai-compat vision requests** (LiteLLM, LM Studio, LocalAI, vLLM) now work end-to-end. Image content is normalized to OpenAI wire shape at the proxy boundary, so strict-schema endpoints stop rejecting requests that mix the internal `{type:'image', dataUrl}` shape with OpenAI's `{type:'image_url', image_url}` shape. - **README env-var defaults corrected.** `IMPORT_ZIP_MAX_MB` and `BACKUP_UPLOAD_MAX_MB` were misdocumented as `256` and `1024`; both actually default to `512`. ### Changed - **README restructured** from 491 to 185 lines using a headline-features + docs-links pattern. Deep feature explanations moved to the docs site. - **Welcome-on-registration email removed** for parity with LiftTrace and NutriTrace. Invited users already received the invite email; self-registered users no longer receive a redundant confirmation. Password reset, invites, and weekly summaries still send. - **Hybrid dev-release model documented in DEPLOY.md.** Rolling `dev-latest` remains the primary tester channel; occasional milestone `v-dev.N` pre-releases exist for anyone who wants to pin a specific build. --- ## [1.0.1] - 2026-07-25 ### Added - **Origin-country flag on OFF search results in Pantry.** Each Open Food Facts result now shows a small flag emoji next to the food name when OFF has origin data for it (from `origins_tags`, falling back to `manufacturing_places_tags`). Lets you see at a glance whether that "olive oil" is Italian, Greek, or Spanish without opening the entry. Countries OFF doesn't have origin data for show no flag rather than a misleading placeholder. - **Data-completeness indicator on OFF search results in Pantry.** Small colored dot next to each OFF row: green when the entry has most nutriment fields filled in, orange for partial, grey for sparse. Long-press or hover shows the exact percentage. - **USDA data-type badge on USDA search results in Pantry.** Small color-coded label next to each USDA row (Foundation / SR Legacy / Survey / Branded / Experimental) so you can favor curated Foundation entries over manufacturer-submitted branded ones for common ingredients. - **Per-source tier filter dropdowns on Pantry search.** A caret next to the OFF and USDA source chips opens a checkbox panel to narrow results by tier. Defaults to all tiers active; a small dot on the source chip signals when a subset is applied. Filter is client-side (no extra API calls) and also applies in All-mode and multi-source mode. - **All-mode Pantry search.** New "All" chip fans out to Pantry + OFF + USDA in parallel with per-row source badges, so you can compare local matches against external DBs in one list. - **Long-press to combine sources on Pantry search.** Long-press any external source chip to pin it alongside another — results become a merged multi-source view with per-row source badges (same fan-out as All-mode, but limited to your pinned set). Long-press an already-pinned chip to remove it. Regular tap exits multi mode back to single-source. - **"Custom…" option on the Model dropdown for Claude, OpenAI, and Gemini.** Enter any model ID the vendor supports without waiting for the preset list to catch up. Same behavior the OpenAI Compatible provider has always had. - **Retirement remap for retired Gemini models.** Saved selections of `gemini-1.5-*` or `gemini-2.0-*` (both retired by Google) are quietly upgraded to the current default at request time. - **SSO-only mode via environment variable.** Set `OIDC_ENABLE_EMAIL_PASSWORD_LOGIN=0` (or `false` / `no`) at boot to disable password login server-wide, so users must sign in via an OIDC provider. Locks the corresponding admin UI toggle with an env-lock note. Mirrors the pattern across the TraceApps family (asked for on LT #16). - **Settings search covers new territory:** auto-share, send test, cookbook sharing. ### Changed - **OFF search results in Pantry are now sorted by data quality within each fetched page.** Entries with images and more complete nutrition data surface higher than sparse ones. OFF's server-side relevance still picks the initial batch; the re-rank runs within the batch to reduce the "many near-identical variants" search noise. - **USDA search results in Pantry are now sorted by data-type tier within each fetched page.** Foundation and SR Legacy entries surface above the millions of Branded entries that would otherwise dominate common searches like "chicken" or "milk". - **Claude model presets refreshed.** Sonnet bumped to Sonnet 5, Opus 4.8 added as a "smartest" tier option, older Sonnet 4.6 removed. ### Fixed - **AI Assistant section in Settings would not expand.** The Trace settings component ran an initialization block that referenced a Svelte `$:` reactive variable before Svelte had a chance to run the reactive declaration, throwing `TypeError: Cannot read properties of undefined (reading 'includes')` at mount time. That crash left the parent `openSections` state half-applied, so tapping the section-toggle looked like nothing happened. Fixed by inlining the lookup so no reactive dependency is required at init. - **OIDC sign-in now works on Android first-install for OIDC-only servers.** NativeSetup previously required a username + password to submit, blocking users on Authentik / Keycloak / Authelia-backed servers with password login disabled. The setup form is now a two-step flow: enter server URL → app fetches `/api/auth/status` → renders whichever auth methods the server actually supports (password fields only when enabled, OIDC provider buttons with logos when configured, both when both). - **OIDC callbacks no longer fail on the first attempt** with a spurious `callback_failed`. openid-client v5's default 3.5 s outgoing HTTP timeout was tight enough that cold token-exchange requests to slower IdPs would sometimes time out. Bumped to 10 seconds. ### Security - **fast-uri bumped to 3.1.4** (GHSA-4c8g-83qw-93j6, high). ReDoS in URI parsing. - **brace-expansion bumped to 5.0.8** (GHSA-mh99-v99m-4gvg, high). DoS via unbounded expansion length. - **body-parser bumped to 2.3.0** (GHSA-v422-hmwv-36x6, low). DoS when an invalid `limit` value silently disables size enforcement. --- ## [1.0.0] - 2026-07-18 Retiring `-rc.N`. This release and every future one uses strict semver (PATCH for fixes, MINOR for features, MAJOR for breaking). Docker images now publish under multiple tags (`:1.0.0`, `:1.0`, `:1`, `:latest`, `:dev`). Existing rc.N image tags stay live indefinitely. ### Added - **Multi-Tag Docker Publishing.** `:1.0.0` exact, `:1.0` for auto-patch, `:1` for auto-minor, `:latest` for absolute latest. - **`:dev` Docker Tag.** Rolling image built from the `dev` branch. Not recommended for production. - **Send Test Email Dialog.** Settings → Email → Send Test now asks where to send the test, pre-filled with your account email. - **Public Contributor Docs.** New `ARCHITECTURE.md` and `ROADMAP.md` at the repo root. ### Changed - **Retired the `-rc.N` suffix.** CookTrace now follows strict semver. - **Send Test actually sends an email.** Was verifying SMTP handshake only; now sends a branded HTML email so you get end-to-end proof. - **Email section moved under Admin** (matches NutriTrace and LiftTrace). - **Password field uses a Change button** when the server has a stored value, instead of showing fake dots. ### Fixed - **Recipes on the Android app showed the first-connect date instead of the real creation date.** Existing libraries heal on the next sync after upgrade. - **Email settings blank on the Android app.** SMTP form now loads from the server correctly on Android server-connected mode. - **Trace AI chef hat clipped at the top of the FAB.** ### Security - **adm-zip 0.5.x → 0.6.0** (CVE-2026-39244, high). Crafted ZIP file could trigger a 4GB memory allocation during full-backup restore. --- ## [1.0.0-rc.5] - 2026-07-13 ### Added - **Auto-Share with a Kitchen.** Turn on Auto-Share Your Recipes in any Kitchen and every recipe you own (past and future) is shared with current members automatically. New members who join later pick up existing members' recipes too. Great for household setups where everyone should see the same library without having to share each recipe by hand. - **Shared Recipes Tab.** A dedicated Shared tab sits between Recipes and Cookbooks and lists every recipe others have shared with you, directly or via a Kitchen. Each card shows a chip for which Kitchen the grant came through, plus a count badge on the tab itself. - **Blend Shared Recipes into Main List (Optional).** Toggle under Settings → Cooking. Off by default. On to see your own and shared recipes together in the main Recipes tab. - **Cookbook Sharing.** Share a cookbook with specific users or a whole Kitchen. Shared cookbooks appear alongside your own with a Shared chip. Recipes inside a shared cookbook that you haven't been granted individually appear as a locked placeholder, so cookbook access can't be used to bypass recipe permissions. - **Cookbooks Can Hold Shared Recipes.** Previously you could only add your own recipes to your cookbooks. Now anything someone else has shared with you can be organized into your cookbooks the same way. ### Changed - **Leaving a Kitchen Leaves Your Contributions Behind.** If you leave or are removed from a Kitchen, recipes and cookbooks you shared into it stay with the remaining members. Only incoming grants (things others shared with you via that Kitchen) are revoked. ### Fixed - **Trace AI panel raw keys.** A duplicate `trace` block in the English translation file was silently dropped by the JSON parser, so several AI-panel labels rendered as their raw key (e.g. `trace.panel_sub`). Merged and de-duped. --- ## [1.0.0-rc.4] - 2026-07-10 ### Added - **AI Base URL support in env-locked mode.** Self-hosters who run their LLM on a private Docker network (Ollama, LM Studio, LocalAI, or any OpenAI-compatible endpoint) can now proxy Trace chat through the server via `AI_BASE_URL`. Previously the OpenAI-compatible flow ran client-only, meaning the browser had to reach the LLM directly, awkward when the LLM lives on a private network the browser can't see. `AI_PROVIDER=oai-compat` plus `AI_BASE_URL=http://ollama:11434` in `.env` now works end-to-end. ### Fixed - **Kitchens created on the PWA now appear on the Android app.** Same class of bug also affected the pending invites list, users list, share peers, session config, and the Mealie / Tandoor / Paprika ZIP import — all silently returned empty on Android in server-connected mode because the local API layer was catching those calls before they could reach the server. - **Pending invites populate correctly.** The User Management section's data-load hook was orphaned, so opening Settings → Users showed empty state even when invites had been created. Fires on section open now. - **Full-backup restore no longer silently drops recipe columns.** Restore was using hardcoded INSERT column lists — any column added by later ALTER migrations (`rest_minutes`, `total_minutes`, `category_id`, `share_token`, `video_url`) would silently vanish on restore. Now schema-driven so future ALTER TABLE additions are automatically covered. --- ## [1.0.0-rc.3] - 2026-07-05 ### Added - **Shopping List Aisle Grouping.** The list can now group by aisle in addition to by recipe or flat. Every pantry category picks up an optional Default Aisle label (Produce, Dairy, Bakery, whatever your store calls it); anything added to the shopping list from a linked pantry row inherits that aisle automatically. A chip row at the top of the list toggles between By Aisle, By Recipe, and Flat. The choice sticks per user across devices. - **Change Aisle Per Item.** Long-press a shopping row and pick a known aisle from a chip list or type a fresh label. Custom aisles work as their own groups without any pantry-category setup, write "Freezer" once and it becomes a group. - **Drag to Reorder Shopping Items.** Grab a row's handle to reorder within a group. Cross-group drops in By Aisle mode reassign the moved item's aisle to match where you dropped it, so shuffling the list is the same gesture as reclassifying an item. - **Hide Checked Items Toggle.** New Shopping List section under Settings → Cooking. Choose Sink to Bottom (default; items stay visible so you can uncheck by mistake) or Hide (items disappear from view with a one-tap "Show N Checked" toggle in the status bar). - **AI Scan Label for Pantry Nutrition.** Take a photo of a nutrition label from the pantry item editor and Trace extracts name, brand, serving size, and every nutrient it can read. Complementary to the existing barcode scanner: barcode looks up Open Food Facts for name-brand products, label scan handles store-brand jars, homemade batches, and imports OFF doesn't cover. Requires an AI provider configured in Settings. - **Per-Row Edit on Shopping Rows.** Long-press any row for Edit, Change Aisle, or Delete. Edit opens a modal for name, quantity, and unit so refining items after add is a two-tap loop. ### Changed - **Simplified Shopping Quick-Add.** The primary add row is now name plus optional qty plus a labeled Add pill, matching Google Keep, Bring!, AnyList, and OurGroceries. Unit moved to the per-row Edit modal so a "milk, eggs, bread" sweep is name-tap-Add-repeat, not three fields per item. The Add pill carries a real text label so it's visible in portrait at every phone size. - **Consolidated Bulk-Add into a + Menu.** The header shrinks to two buttons: Share and a single + button. Tapping + opens an action sheet with Add from Recipe and Add from Planned Cooks, each with a proper label instead of a bare icon. - **"Add from Meal Plan" Renamed to "Add from Planned Cooks".** Matches the Diary's language everywhere else (Plan a Cook, Planned tag). - **Recipe Editor Ingredient Rows on Mobile.** The three per-row action buttons (link to pantry, section divider, delete) collapse behind a single kebab menu on small screens so the ingredient name field has room to read. Adding a note is one tap in the kebab; the note appears on a second inline row. Small indicator icons on the row signal what's stashed behind the menu. Desktop layout is unchanged. - **Multi-Select on Pantry and Recipes.** Enter selection mode from the long-press menu on any item. The header title flips to "N Selected" and the top-right buttons become trash + cancel on Pantry, or trash + add-to-cookbook + cancel on Recipes. Matches NutriTrace's pattern. Replaces the old floating action pill. - **Recipe Editor Landscape on Phone.** Ingredients and Steps stay stacked when a phone rotates to landscape. The two-column view only kicks in on tablet-landscape widths and up, so the fields no longer get crushed on the pivot. ### Fixed - Cook Mode reliably keeps the screen awake on Android. The Web Wake Lock path silently stopped working on some Android WebView versions; the Android app now uses the native `KeepAwake` plugin. PWA continues to use the Web Wake Lock API. - Cook photos taken during a diary log render immediately on the Diary Photos tab. Older photos still render via a backstop conversion. - Bulk-add from a recipe or planned cook populates each row's aisle from the linked pantry item's category, so items land pre-grouped instead of piling into Uncategorized. - Long-pressing a Shopping row no longer lets you drag it while the action menu is open. The finger-hold that opened the menu was still feeding pointer moves into the reorder tracker. - Cancel out of Pantry multi-select no longer leaves an invisible layer blocking taps until reload. - The Shopping quick-add box no longer freezes the app on mobile when the pantry has hundreds of items. The suggestion list is now capped so Android WebView doesn't try to render every pantry row at once. ### Security - **Dependency Security Updates.** Bumped `multer` 1.4.5-lts.1 → 2.2.0 (patches three high-severity DoS CVEs on the LTS line), `nodemailer` 8.0.7 → 9.0.3 (patches five CVEs including a TLS OAuth certificate-validation bypass and CRLF header injection), and `vite` 7.3.3 → 7.3.6 (dev-only, patches a `server.fs.deny` bypass on Windows). Package overrides pull `esbuild` forward to ^0.25.0 to clear a dev-server CORS advisory nested under svelte-i18n. Clears every actionable Dependabot alert. No user-facing changes; self-hosters running Docker just need to pull the new image. --- ## [1.0.0-rc.2] - 2026-07-02 ### Added - **Pantry Variants.** A pantry row can now carry brand-specific rows underneath it. Recipes link to the parent (Whole Milk); each variant (Greenwise, Publix) has its own barcode, photo, stock, and expiration date. Add Variant with inline suggestions: type a brand and existing pantry items surface as tap-to-attach chips. Delete of a parent with variants asks whether to keep the variants as standalone items (default, safer) or remove them together. - **Recipe Nutrition Source on the Parent.** A generic pantry item can either use its own nutrition numbers (default) or pull from one of its variants, so recipe math reflects the label of the product you actually cook with. - **Pantry Expiration Dates.** Every pantry row gains an Expires On field with an in-sheet calendar picker. Amber pill on cards for anything expiring within two weeks, red for past. Expiring Soon filter chip surfaces the whole set in one tap. Variant expiries surface as chips inside the parent sheet's Variants list. - **Expiration Digest Notification.** Once-a-day roll-up covering everything expiring within a chosen window (1 / 3 / 5 / 7 / 14 days), delivered at a chosen time. Push goes out through Apprise, Gotify, or ntfy plus the local device channel. Past-expiry items always included regardless of window. Settings → Notifications → Reminders → Expiration Digest. - **Rest Time Field on Recipes.** Any hands-off period, bread rise, meat rest, dough chill, marinate, soak, ferment. Left blank stays hidden on the recipe view; set adds a Rest meta pill next to Prep and Cook. Rolls into the auto-calculated Total Time below. - **Total Time Manual Override.** Optional field in the recipe editor. Leave blank and the app auto-calculates from prep + cook + rest; set a value and it wins. Placeholder shows the auto-calc live so you see the fallback before overriding. - **Clear Link on the Category Picker.** A small Clear link appears in the pantry item's Category label when a category is set, wiping the selection in one tap. - **New Preserves Recipe Category.** Jams, pickles, curds, chutneys, ferments, canning recipes, none of which fit cleanly under Sauce, Bread, or Dessert. Added to the default seeded chapter list with a berry / pomegranate color that reads clearly distinct from Bread. Existing accounts keep their category list untouched; only fresh installs get the new default automatically (add it in Settings → Recipe Categories if you want it on an existing account). - **Animated Variant Expand and Collapse.** Chevron rotates smoothly on the parent card, variant cards drop in from above with a spring easing on expand, slide back up on collapse. Honors OS-level reduce-motion preferences. - **Import Cookbook.** A single multi-recipe PDF (a whole cookbook, a printed booklet, a folder of scanned recipe pages) can now import as multiple recipes in one pass. The AI parser splits the file into individual recipes, previews each, you pick which to save. (Issue #2 phase 3) - **Bulk File Import.** Pick a folder or a ZIP archive of recipe files and import everything in one go. Per-file preview and dedup before commit. (Issue #2 phase 2) - **Hybrid File Import.** PDF, RTF, TXT, and Markdown files import through a heuristic parser first (fast, no AI cost); the AI parser catches anything the heuristic can't structure. Both paths land in the same preview + edit flow. - **Batch URL Import.** Paste a list of recipe URLs and import them all in one pass. Scanned PDFs and image-only pages route through an AI vision fallback so they parse instead of failing silently. - **Use Camera Shortcut in File Import.** Snap a photo of a handwritten recipe card or a printed page directly from the import dialog, alongside the file picker. - **Swedish Translation.** Contributed by @olsson82 via PR #3. Switch in Settings → Regional & Units → Language. Coverage is what @olsson82 translated at PR time: primarily the auth flows (Login, Register, Reset Password, Accept Invite, Profile), User Management, sidebar labels, and a couple of common UI components. The rest of the app went through an i18n instrumentation pass this release so a much larger set of strings (bottom nav, main-route titles, empty states, Settings section headers, Recipe view labels, Recipe editor fields, Pantry item sheet, Trace welcome) is now extractable and registered under English keys. Those newly-extracted keys fall through to English for Swedish users until a Swedish speaker translates them in a follow-up PR — we didn't want to put in-house wording into @olsson82's file. A pull request filling in the remaining sv.json entries is warmly welcome. - **Language Picker.** Regional & Units gains a Language dropdown populated from the registered locale list, so Swedish (and any future translation) is actually reachable from the UI. The translation existed in PR #3, but the picker to select it was never wired. ### Changed - **Animated Page Banners.** The illustrated SVG headers on Recipes, Pantry, Diary, Shopping, and Settings are retired in favor of four animated background styles picked in Settings → Appearance: Shimmer (subtle sweep, default), Drift (slow horizontal float), Pulse (color breathe), and Aurora (Northern Lights sweep). All four honor the OS's reduce-motion preference. - **Import Menu Collapsed.** URL and File imports used to be two separate top-level entries; they now sit under a single Import entry that expands to show both sub-options. Same functionality, cleaner surface. - **Password Rule Hints for Autofill.** Password inputs on the wizard, register, reset, and profile screens now declare their requirements via the `passwordrules` attribute, so iOS and Chrome autofill generate a password that actually passes CookTrace's 8-plus-mixed-plus-symbol check instead of one that gets rejected. - **Recipe Category Colors Tightened.** Snack, Sauce, and the new Preserves each moved off crowded hue bands so the chip row scans cleanly at a glance. Snack shifted from yellow to teal (was too close to Breakfast's amber), Sauce shifted from violet to indigo (was too close to Appetizer's purple), and Preserves sits at berry / pomegranate rather than rust (was too close to Bread). Existing accounts keep their current colors; only fresh installs get the new palette. - **Recipe Times Show Hours and Minutes.** Every recipe-time display now reads in hours and minutes instead of raw minutes. A 75-minute recipe shows as `1h 15m` on list cards, detail view, cookbook cards, public share pages, the shareable card, and the file-import preview. The editor input still takes raw minutes. - **Smart Pantry Search Under Variants.** Query the parent name and you get the parent card alone. Query only a variant's brand and you get that variant alone with a "Variant of Whole Milk" subtitle, no duplicate parent. Query both and the parent expands with the matching variant nested underneath. - **Expires On Uses the In-Sheet Calendar Picker.** Matches every other date entry in the app instead of the browser's default `mm/dd/yyyy` widget. - **Tandoor Imports: `waiting_time` Maps to Rest.** Tandoor's waiting_time is a hands-off signal by convention, so it lands in Rest Time instead of Cook Time. Working_time still maps to Prep. Cook stays null on Tandoor imports; the auto-calculated Total covers everything correctly. - **Native Pantry Updates Only Write Fields the Payload Carries.** A stock toggle no longer rewrites brand, category, or FK columns as a side effect. Prevents unrelated data from getting nulled on partial edits. ### Fixed - **Pantry Stock Toggle No Longer Crashes on Native.** The top-right quick-toggle on any pantry card threw "NOT NULL constraint failed: pantry_items.name" because the partial update was writing every column with undefined placeholders. Only present fields get written now. - **Variant Relationships Survive Mobile Edits.** Setting Greenwise as a variant of Whole Milk on the PWA now stays set on the phone through unrelated stock toggles, quantity bumps, and brand edits. Was silently reverting because the sync order was push-then-pull, so a stale local row propagated back to the server before the server's fresher state pulled down. Order is pull-then-push now, and the server preserves FK columns via COALESCE when the client sends NULL. - **`[object Object]` Category Pill on Mobile.** The category pill on the pantry sheet was rendering the raw category object rather than its name on Android. Native queries now join the category the same way the PWA server does. - **Category Casing Mismatch Between PWA and Mobile.** "Dairy & eggs" on Android vs "Dairy & Eggs" on the PWA. The pill now reads the live category row instead of falling into a hardcoded slug-to-label map with older casing. - **Dropdown Menus Close on Outside Scroll.** Combobox dropdowns portaled to the body layer were blocking wheel and touchmove events from reaching the sheet underneath, so scrolling the page while a dropdown was open did nothing. Outside scroll now closes the dropdown and lets the scroll through. - **Confirm Dialogs Stack Correctly Over the Sheet.** "Remove from pantry?" and similar confirms were rendering behind the item's own edit surface, making the buttons invisible. - **Native DB Init Survives Upgrades.** A pantry index creation ran against an old schema on first launch after an APK upgrade and blocked the whole database from initializing. Moved the index into the migration that adds the column so it runs after the ALTER. - **Trace AI Provider Fields Are Read-Only After Save.** Once an AI provider config is saved in Settings → Trace, the API key and Base URL fields lock so you can't accidentally overwrite them by tapping through the form. Editing requires an explicit Change toggle. (Issue #5) - **Animated Banner Sticky Pinning.** The animated header was picking up an explicit `position: sticky` that broke pinning on Recipes, Pantry, Diary, Shopping, and Settings. The redundant declaration is dropped and the sticky wrapper handles the pinning cleanly. - **Recipe Editor Dropdowns Are Scrollable Again.** Opening the Category, Tags, or Kitchen Gear picker in the recipe editor was capping the list at eight items, so anything past the eighth entry could only be reached by typing to filter. The cap is removed so the whole catalog is browseable via the popover's own scroll. --- ## v1.0.0-rc.1 (2026-06-07) First public release candidate. Brings the dev branch to feature completeness and parity with the wider TraceApps family (NutriTrace, LiftTrace), then layers polish, dependency upgrades, and the Android local-mode build that lets CookTrace run standalone on a phone without a server. ### Android app — standalone or server-connected - **First-launch wizard** offers Use Locally (pure offline, on-device SQLite) or Connect to Server (URL + login). Mode is switchable later in Settings, with a merge dialog when bringing existing local data to a server for the first time. - **Differential sync engine** with 30-second background timer + visibilitychange resume. Push pending writes, pull recent changes, surface real failure messages in the sync banner. - **Offline image cache**: sync downloads every server image to the device so recipe / pantry / diary thumbnails render without a connection. Cache filenames are hashed so cross-product collisions on OpenFoodFacts URLs no longer overwrite each other. - **Native barcode scanning** via ML Kit. Capacitor 8 base. Shared TraceApps release keystore so reinstalling between dev and release builds doesn't wipe local data. ### Recipe importers - **Mealie**, **Tandoor**, **Paprika** export-zip importers with per-recipe selection, dedup, and category / tag carryover. - **Mealie timeline import**: pulls cook-event history (with photos and notes), backdates recipe created_at to the original add date, matches cook events to your existing recipes. - Drag-and-drop file picker, scan + commit two-step flow. ### NutriTrace federation - **Smart View on OFF / Share to OFF dual button** in the pantry item editor. When the barcode already exists on Open Food Facts, the button switches to "View on OFF" and opens the product page; when it doesn't, the button uploads the local row and reports back with a verify status row ("Confirmed live on Open Food Facts" / "Submitted, may take a few minutes to appear"). - **Persistent Connected pill** in Settings → NutriTrace federation so you can see at a glance whether the integration is working without re-running the test. - **OFF nutriment _modifier filter**: values OFF flags as algorithmic estimates (rather than label-derived facts) no longer get imported as real measured values. - **UPC-A → EAN-13 canonical-form normalization** in the OFF lookup so 12-digit scans hit the same product as their 13-digit equivalents. ### Trace AI assistant - **Hold-to-record voice on the FAB** for hands-free Smart Log. Hold the FAB to start recording (haptic + beep), release to send to the AI, slide your finger >100px away to cancel mid-recording. Status pill above the FAB shows the gesture state ("Listening… Release to Send" / "✕ Release to Cancel"). - **Chat panel** now slides up as a full-width mobile bottom sheet with a drag handle, dimmed backdrop, and rounded top corners. Desktop keeps the floating-card layout, anchored bottom-right. - **Always-visible Clear Chat button** in the panel header (was hidden when there were no messages). - **In-chat mic button removed** for parity with NutriTrace and LiftTrace — voice now lives only on the FAB hold gesture. ### Nutrition Facts box - **FDA-correct sodium placement**: sodium moves up into the macros block (between Cholesterol and Total Carbohydrate) where it belongs on a real food label, rather than sitting under Protein in its own rule. - **Servings per recipe** line added above the Serving Size, matching the "Servings Per Container" pattern on packaging. - **Per-serving grams when computable**: the Serving Size line shows `~85g` instead of `1 of 8` when every ingredient in the recipe resolves to a known weight or density. Otherwise it falls back to `1 of N` or `Per serving`. ### Pantry item editor (single unified surface) - **Slide-up sheet** is now the single editor for every pantry-item flow (view, edit, create, barcode scan). The full-page editor is retired from the navigation surface. - **Inline edit**: view-mode fields flip to inputs in place within the same two-column grid so the surface doesn't change shape. - **Header actions** (Edit / Delete in view, Cancel / Save in edit) sit as icon buttons in the top right of the sheet, mirroring RecipeView's chrome pattern. Red Close + Delete + Cancel, green Save. - **In Stock derives from quantity** — `quantity === 0` reads as out of stock, `null` or `> 0` reads as in stock. The explicit toggle was retired. Items you keep but never count (salt, oil) stay in stock without needing a number. ### Appearance + chrome - **Trace Every Recipe — From Pantry to Plate**: new tagline across the README, sidebar, About card, Wizard welcome, and PWA manifest. - **Gradient page-header banner** as a middle option between Animated and Off. New users land on Gradient as the default; existing users keep whatever they had. - **Compact page header** when banners are off — saves ~40px on every page without the illustrated SVG. - **"You're All Set" Wizard celebration** before landing on the Recipes tab — trophy icon + confetti, ~1.8s beat. Honors prefers-reduced-motion. - **Title case sweep** across UI chrome: relative times ("3 Days Ago"), heatmap stats ("117 Cooks · 80 Active Days"), recipe header ("Last Cooked", "Cooked N Times"), nutrition box ("Servings Per Recipe"). - **Native number-input spinner buttons hidden** globally — every number input across the app renders without the up/down arrows. - **40×40 page-header action buttons** standardized across routes to match the floating menu button. No more mixed 30/34/40 px sizing. ### Scheduled backups - **Auto-backup**: schedule full backups daily, weekly, or monthly on the server side, plus a local-mode equivalent on Android. Optional retention window auto-prunes older snapshots. - **Backup error toasts** when the list fetch fails (was a silent empty list). ### OIDC SSO - **RP-initiated logout** on PWA and Android — signing out of CookTrace also signs you out of the identity provider when the provider supports the end-session endpoint. ### Dependency upgrades - **Svelte 4 → 5** with the `runes: false` + `compatibility.componentApi: 4` compat shim. Same component API, smaller runtime. - **Vite 5 → 7** + **vite-plugin-pwa 0.19 → 1.3**. - **Express 4 → 5** with the new `path-to-regexp` v8 wildcard syntax. - **bcryptjs 2 → 3** (ESM). - **nodemailer 8.0.3 → 8.0.7**. ### Diagnostics - **Image cache URL hashing** — fixes a collision bug where two OpenFoodFacts products whose URLs end with the same filename (`front_en.4.400.jpg`) would overwrite each other in the local cache and display each other's pictures. - **Log + crash file share via Directory.Cache** so the receiving app (Drive, Files, Solid Explorer) actually gets the file contents instead of the share-intent's title text. ## v0.11.0-beta.1 (2026-05-11) Polish and power pass across every primary surface. Diary, Recipes, Pantry, Shopping, Manage, and the share flow all picked up real features without changing the underlying shape of the app. ### Diary v2 - **Meal-type slots** on planned and logged cooks (breakfast, lunch, dinner, snack) with a chip picker in the planner and the log dialog - **Per-cook rating** separate from the recipe's overall rating, so one bad attempt doesn't drag down a five-star recipe - **Stats card** at the top with This Week, Current Streak, Longest Streak, and Most Cooked This Month. Current-streak has a grace window so today not being cooked yet doesn't reset the run - **Cook activity heatmap**: GitHub-style contribution graph over the last year, sized to fill the card on desktop and horizontally scrollable on phones. Click a cell to jump the Month view there - **Photos view**: chronological tile grid of every cook photo, with rating and multi-photo indicators. The lightbox pages between siblings on multi-photo cooks - **Filter by recipe**: chip and picker apply across List, Month, and Photos. Dedicated empty state when the filter has no matches in the visible range - **Dashboard layout on wide screens**: heatmap and stats card go side-by-side at 1280px and above instead of stacking, freeing screen room for the actual entries ### Sharing - **Recipe card** moved from a server-rendered URL to a client-side PNG attached to the share sheet. Works fully offline, every recipient gets a real image inline regardless of channel - **Full recipe content** on the card (hero, title, ingredients, numbered steps) instead of a teaser image. Plain-text version rides along in the share text body so Mail, SMS, and iMessage carry the recipe too - **Pagination for long recipes**: one PNG per page (about 1500px tall max) so nothing gets cut off, and the share sheet receives all pages when the platform supports multi-file sharing - **Shopping list card**: same client-side aesthetic as the recipe card, plus a Share as Text option for plain clipboard / SMS use - **Brand mark** in every share-card footer next to the wordmark - **Recipe View share icon** in the header so you don't have to back out to the list and long-press to share what you're reading - **Desktop multi-page download fix**: staggered downloads so Chromium delivers every page after the user approves the prompt ### Pantry - **Expiration dates** UI on items: date picker in the editor, an amber or red banner on the list when anything is within seven days (or already past), and a per-card expiry pill - **Recipe usage pill** ("Used in N") on every card, with the count computed via a single ingredient-scan pass - **Sort menu** in the toolbar: A to Z, Last Updated, Most Used, Expires Soon. Non-default sorts flatten the category grouping so the order isn't fighting the structure ### Shopping - **Add from Meal Plan** bulk-imports the ingredients from every planned cook in a date range. Dedupes by name and unit (summing quantities when both sides have one), honours the existing "only missing from pantry" filter - **Share** action in the header with image or plain-text options ### Recipes - **Bulk Add to Cookbook** from the multi-select toolbar so a batch of recipes lands in a cookbook in one operation ### Manage hub - **Usage badges** on Recipe Categories, Pantry Categories, and Units (in addition to Tags and Kitchen Gear, which already had them). Mint pill when in use, dim border-only pill when zero, so stale taxonomies stand out - **Material Symbols icon picker** for Pantry Categories: visual grid with search instead of typing icon names from memory - **Drag-reorder** on Recipe and Pantry Categories matching the existing Cookbooks pattern ### Micro-interactions - **Heart pop** on the recipe favorite toggle and **star pop** on per-cook ratings: small scale-up-and-settle keyframes that honour `prefers-reduced-motion` ## v0.10.0-beta.1 — first beta (2026-05-08) First beta tag on the dev repo. Everything from the build-out plus the polish phases (1–11) is shipped, end-to-end, and documented. README is a complete tour. No public release repo yet. ### What landed since v0.1.0-dev - **Importers** — URL scrape, paste/upload (txt, JSON, JSON-LD, schema.org), and Mealie / Tandoor / Paprika export-zip flows - **NutriTrace federation** — proxy + Settings UI + connectivity test; ingredient food-link picker; cooked-recipe → NT diary log - **Recipe view polish** — sticky search, sticky save bar in editor, drag-reorder for ingredients AND steps, step text markdown formatting, print stylesheet, recipe-card SVG share, route transitions, category color stripe along card edge - **Pantry polish** — slide-up sheet with in-place edit, inline nutrient cards with FDA-style %DV, sub-sheet for linked recipes, inline cross-family unit converter (~250-entry density catalog), cookbook fraction typography - **Recipe extras** — categories with color, comments (rich-text, bold/italic/lists), cookbooks collections, recipe sharing, kitchens, kitchen gear field - **Trace AI** — persistent chat history per user with rate limiting, multi-provider config (OpenAI / Anthropic / Gemini / Ollama) - **Backup audit** — full-backup now captures recipe_categories, recipe_comments, pantry_categories, custom_units, disabled_units, cookbooks, recipe_cookbook_links, recipe_shares, kitchens, kitchen_members in addition to core tables - **i18n sweep** — 442-key en.json, zero missing keys ## v0.1.0-dev — full app build-out (2026-05-04) CookTrace went from an empty shell to a feature-complete recipe app across a single intensive day of dev work. Every tab is real, every Slice 1–2 item is shipped, and Phases 3–6 of the roadmap are in. ### Recipes - Full recipe model: name, description, hero image, **rating (★)**, **favorite (♥)**, **yield text** (e.g. "12 cookies"), prep / cook minutes, default servings, **scaling chips** (×0.5/×1/×2/×3) plus a custom serving input - **Ingredient groups** — Mealie-style sections like "Sauce" or "Dough"; default is one unnamed group rendering as a flat list - Steps support an **optional summary** rendering as "Step 1: Preheat Oven" when filled - **FDA-style Nutrition Facts box** between Steps and Notes — full nutriment catalog (31 fields), sub-row indenting, %DV column, vitamins/minerals separated by a thick rule. Sodium ↔ salt auto-derives via the EU regulatory factor with a calculator-icon badge on the derived row - **Cook history** list (date / notes / photo / edit / delete) below the box once you've cooked it at least once - "I cooked this" opens a **Cook Log dialog** (date picker + notes + photo) rather than a one-click log - **Cook Mode** — "Start cooking" button requests Screen Wake Lock, enlarges body text, persists ingredient + step checkboxes per recipe in localStorage so a stove interruption doesn't lose your place. Sticky banner with Reset Checks + Exit - **Long-press / right-click** any recipe card → context menu: Open · Favorite · Plan a cook · Add to shopping list · Duplicate · Share card · Delete ### Recipes list cards - Hero image, favorite-heart overlay, 5-star rating row, time + serves + last-cooked + **pantry-match pill** (color-coded full / partial / none — counts ingredients you have in stock) - Search, action-sheet "+" with four import paths (Manual · URL · Paste/upload · Photo when Trace AI is configured) ### Pantry - Real CRUD page replacing the stub: alphabetical list with in-stock toggle (optimistic), search, filter chips (All / In stock / Out), Add modal with name + quantity + unit (UnitPicker) + notes + optional image. 36×36 thumbnail in the list row. - **Auto-populates from recipe saves** — every ingredient name you use in a recipe becomes a pantry row (case-insensitive dedup, "Flour" + "flour" share one entry). Recipe ingredient → `pantry_item_id` links happen server-side in a single transaction on save. - "X / Y in pantry" pill on every recipe card, computed server-side from the user's in-stock set. ### Diary (Phase 3) - Two views: **List** (60d back / 30d forward, grouped by date with Planned/Past separators) and **Month** (calendar grid with pill entries per day, today ringed in accent color) - **Plan-a-cook** modal: date picker + searchable recipe picker → one click writes a `kind=planned` cook_diary row - Each entry: clickable thumbnail navigates to the recipe; "Cooked" button on planned entries one-taps the conversion; delete on every row ### Shopping (Phase 4) - Quick-add row at the top: name + quantity + UnitPicker + add - Items grouped by aisle (Other pinned bottom), checkboxes per row with optimistic toggle + revert-on-error - "X remaining · Y checked" status bar with bulk Clear-Checked action - **"Add from recipe"** opens a modal with searchable recipe picker + "only add what's missing from pantry" toggle (default on). One tap pulls the missing ingredients onto the list with their quantity + unit + pantry_id link - Empty state CTA points at the same flow ### URL recipe scraper (Phase 4) - POST `/api/recipes/scrape` fetches any URL, parses schema.org/Recipe JSON-LD, normalises into our shape (handles @graph nesting, HowToSection trees, ISO 8601 durations, all standard nutrition fields, keyword splitting), then runs through the regular create flow so pantry-linking + sodium/salt derivation apply. - SSRF-guarded: http(s)-only, blocks loopback + private IP ranges, 8s timeout, 5MB cap, identifies as CookTrace via User-Agent. - Wired into the "+" menu URL import dialog. ### Recipe-card share (Phase 6) - GET `/api/recipes/:id/card.png` returns a server-rendered Pinterest-style 600×800 SVG (hero image with bottom-fade overlay, word-wrapped name, time/serves/yield subtitle, COOKTRACE watermark). Renders correctly in browsers, Slack, Discord, iMessage link previews. Wired into the long-press menu Share action via navigator.share / clipboard fallback. ### NutriTrace federation (Phase 5) - Settings → "NutriTrace federation" section: URL + access-token fields, show/hide on the token, **Test** button (proxies through the server to NT `/api/auth/me`), enable toggle (gated on URL+token). - `/api/nt/test`, `/api/nt/foods`, `/api/nt/log-meal` server proxy — bearer token never leaves the server. - Foundation ready; auto-log-cooked-to-NT-diary + Pantry NT food picker land as consumer wiring tickets next. ### Trace AI assistant (Phase 5) - Floating chat FAB on every page (gradient circle with the TraceFace mascot) — only renders when AI is enabled in Settings. - Slide-up chat panel with conversation history, "thinking" dots, refresh-to-clear, multi-line input that submits on Enter. - Settings → Trace Assistant: enable toggle, assistant name, provider dropdown (Claude / OpenAI / Gemini / OpenAI-compatible), API key with show/hide, custom base URL when "OpenAI-compat" is picked, model dropdown / freeform field, **Test** button. envLocks.ai disables the key field when AI is configured server-side via env vars. - Server proxy at `/api/ai/chat` was already in place — Trace.svelte just calls it. API key never reaches the WebView. ### Ingredient unit picker - Replaced the previous datalist with a full **UnitPicker** combobox. 37 cooking units in 5 categories (Volume US, Volume Metric, Weight US, Weight Metric, Count / descriptive). Click opens for browse (shows everything regardless of saved value); type to switch into search mode and filter. Free-text fallback for splash / drizzle / to taste / etc. Custom units configurable via a `customUnits` setting (Setting UI to pick coming). ### Image picker (Slice 2D) - `ImagePicker.svelte` — three buttons (Camera / Upload / URL), ghost styling, capped 420px-wide centered preview. Camera works on PWA via `getUserMedia` (in-page popup with capture button) and on Capacitor via `@capacitor/camera`. Wired into RecipeEditor, CookLogDialog, and Pantry edit modal. ### Settings (refactored to NT-uniform) - Every section is now collapsible (chevron toggles, slide animation, closed by default). Header: accent-colored material icon in a rounded square. - Theme is a `