NutriTrace logo

NutriTrace

Self-hosted personal nutrition and calorie tracker

Alternative to: myfitnesspal, cronometer, lose it, waistline

NutriTrace screenshotNutriTrace screenshot

About Versions (107)

v1.2.1-dev03

2026-08-29

Third dev pre-release of the 1.2.1 patch cycle. Focused fix run on the OFF local-mirror path: seven community-reported bugs (#185 through #191) that only surface when a self-hoster has OFF_LOCAL_DB set. Default installs (public OFF API path) are unaffected.

Added

  • Chinese (Simplified) translation updates. Community contribution via Weblate.

Fixed

  • Product names with apostrophes were silently dropped from mirror search results (#185). Under @duckdb/node-api 1.4 the LIST columns come back wrapped as DuckDBListValue (real array under .items, struct fields under .entries), and the old _asArray fell through to the string fallback, which stringified the wrapper into DuckDB’s SQL repr and doubled the embedded single quotes. Neither the JSON parser nor the Python-repr parser accepted that escape, so any product with an apostrophe in the name (Ben & Jerry’s, KELLOGG’S, Dunkin’, anything possessive) came back with an empty product_name and got filtered out client-side. _asArray now recursively unwraps .items and .entries before the string fallback, and the downstream field readers already tolerated both shapes.
  • Search results within a rank were coming back in effectively random order, burying popular products (#186). The parquet search branch’s only sort key was the prefix-match rank; within a rank the parallel table scan filled pages in scan order, which put near-zero-scan regional clones ahead of mainline flagship products. Added popularity_key DESC NULLS LAST as the within-rank tiebreak when the column exists (detected once at init from information_schema so custom parquets without it don’t binder-error), with code ASC as a deterministic fallback.
  • The OFF quality-tier filter classed every mirror row as “Unknown” and hid all results under any tier selection (#187). _toOffProduct wasn’t forwarding the completeness score even though the client’s tier bucketer + result ranker both expected it. Now passed through on both the parquet and legacy .duckdb branches, guarded by a type check so rows without the field pass undefined (which the client already null-checks).
  • Typed or pasted barcodes in the search box returned “No results” even when the mirror had the product (#188). The scanner path uses a separate exact-code endpoint; text search never matched the code column. Added to the WHERE on both branches, so pasted barcodes now resolve and every plain-HTTP or desktop install (no camera scanner) gets a working barcode lookup.
  • Mirror search never loaded more than the first page even when hundreds of results matched (#189). The response count was rows.length (page size), so the client’s hasMore math could never fire. Now runs a parallel COUNT(*) with the same predicates and returns the real total (Number()-wrapped so the BigInt serializes). Both ORDER BY clauses also picked up code ASC as a final unique tiebreak so pages partition cleanly without duplicating or dropping rows across boundaries.
  • Multi-word searches only matched exact adjacent phrases in the exact typed order (#190). The whole query was one %q% pattern, so “dunkin croissant” would silently miss a product named “Bacon Egg and Cheese Croissant (Dunkin’)” because the two words are not adjacent, and reversing the words changed results. searchByName now tokenizes on whitespace and ANDs per-token substring matches against name, brands, and code. The prefix-rank CASE still uses the full phrase so exact-phrase starts sort first.
  • A search returning two rows with the same barcode froze the entire app until reload (#191). The stock OFF snapshot contains 60 duplicated codes across 4.7M rows (two genuinely different products can share a barcode via OFF data errors), which triggered Svelte’s each_key_duplicate on the search-results list and killed the render loop. Both search-result each blocks (visibleApiResults and the all-mode _allModeItems) now suffix their keys with the list index so a duplicate key is physically impossible. Duplicate rows stay visible on purpose so users can pick which of two barcode-collision entries matches their product.

Security

  • No CVE-driven dependency bumps this cycle. npm audit --production reports 0 vulnerabilities; no open Dependabot alerts.