Vibecode Cronometer Gold
track this build5 phases, 15 steps, beginner friendly0%The core loop is buildable, but a dependable replacement becomes a real weekend or multi-day project. For Cronometer Gold, log user-entered nutrition and show transparent nutrient totals without diagnosis. The hard boundary is curated food database, barcode data, device integrations, reports, and mobile apps, plus hardware data, content, coaching, and trust.
You are building a lean indie version of Cronometer Gold. Create the following project files first, then implement the application by following them. Keep the files updated as decisions change. Do not collapse this into a single README or prompt. ===== README.md ===== # Cronometer Gold · indie build A food log backed by USDA FoodData Central, which is public domain data: search foods, record portions with real units, see full micronutrient totals rather than only calories, view trends over time in neutral language, and export everything you have logged. Estimated effort: **multi-day**. Work `BUILD_PLAN.md` top to bottom · every phase ends in a check that has to pass before the next one starts. ## Stack | Part | Choice | Why | | --- | --- | --- | | Runtime | TypeScript with Vite, built as an installable progressive web application | logging happens at a table with a phone in one hand, and an app store review is not worth it for one user | | Food data | USDA FoodData Central, cached locally | public domain under CC0, roughly six hundred thousand foods, and free with an API key | | Storage | IndexedDB for the log and the food cache | logging must work with no signal, and the food cache is what makes that possible | | Units | A unit conversion layer over gram-based nutrient data | every nutrient figure is per hundred grams, and every portion a person names is not | | Charts | Hand-drawn SVG | a handful of neutral trend charts, where a library would cost more than it saves | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **Node.js 22 or newer** · free - Why: Runs the build and the copy lint. - Get it: nodejs.org, or nvm install 22. - Verify: node --version prints v22 or higher - [ ] **A FoodData Central API key** · free; roughly one thousand requests per hour per address - Why: Without it there is no food data. The demo key exists but is rate limited far below anything usable. - Get it: Sign up at fdc.nal.usda.gov/api-key-signup, which issues a free data.gov key by email in a few minutes. No payment details are required. - Verify: A search request returns results rather than a rate limit error - [ ] **A phone you can install the application on** · free - Why: Logging happens away from a desk, and one-handed use and offline behaviour cannot be judged in a desktop browser. - Get it: Serve over HTTPS to your phone with a tunnelling tool and install to the home screen. - Verify: It installs to the home screen and opens without browser chrome - [ ] **Someone willing to read the interface strings** · free - Why: Phase 6 needs a second reader for tone. You cannot audit your own wording for pressure, and this subject matter makes that audit worth more than usual. - Get it: Ask one person, hand them the printed strings, and ask specifically whether anything reads as judgement or instruction. - [ ] **A decision about reference intakes** · free - Why: Showing population reference figures next to personal totals invites reading them as targets, which is exactly what this application refuses to do. - Get it: Decide before Phase 5 whether to show them at all. Omitting them is simpler and safer; showing them requires careful labelling and no comparative colouring. - Verify: The decision and its reasoning are written in the README ## Quick start ```sh curl 'https://api.nal.usda.gov/fdc/v1/foods/search?query=oats&api_key=YOUR_KEY' ``` Then copy `.env.example` to `.env` and fill in the values it documents. ## Honest limits This build deliberately does not replace: - A curated and deduplicated food database with normalised portions. - Barcode scanning against a commercial product catalogue. - Wearable and health platform integrations. - Any coaching, target setting or nutritional advice. - curated food database, barcode data, device integrations, reports, and mobile apps - wearable hardware data - coach network - clinical validation - large content library and social network If one of those is essential to you, that is the reason to keep paying for Cronometer Gold, and the README should say so rather than pretend. ===== BRIEF.md ===== # Build brief · Cronometer Gold The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me a nutrition log built on the public USDA food database, showing transparent totals without setting targets for you to replace Cronometer Gold. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Rule zero This is a record, not a diet product. It logs what you entered and shows the nutrient totals plainly. It must not diagnose, must not score a day as good or bad, must not set calorie or weight targets, and must not use streaks or warnings to pressure anyone. Nutrition tracking sits close to disordered eating for a meaningful number of people, and an application that adds judgement can do real harm. Phase 5 makes that promise mechanical with a copy lint that fails the build, rather than leaving it as a good intention that erodes over a year of small additions. ### Stack (fixed, do not substitute) - Runtime: TypeScript with Vite, built as an installable progressive web application · logging happens at a table with a phone in one hand, and an app store review is not worth it for one user - Food data: USDA FoodData Central, cached locally · public domain under CC0, roughly six hundred thousand foods, and free with an API key - Storage: IndexedDB for the log and the food cache · logging must work with no signal, and the food cache is what makes that possible - Units: A unit conversion layer over gram-based nutrient data · every nutrient figure is per hundred grams, and every portion a person names is not - Charts: Hand-drawn SVG · a handful of neutral trend charts, where a library would cost more than it saves ### Data model (create this before Phase 1) - foods(fdc_id, description, data_type, brand, serving_json, nutrients_json, cached_at) · cached from the API, keyed by its identifier - portions(id, fdc_id, label, gram_weight) · the named servings the database provides, plus any you add - entries(id, at, fdc_id, quantity, portion_id, grams, meal) · grams is always computed and stored, so a portion definition changing later does not rewrite history - recipes(id, name), recipe_items(recipe_id, fdc_id, grams) · a recipe is a food composed of other foods - nutrient_defs(id, name, unit, display_group) · so the interface can show a full panel rather than four headline numbers - Everything is stored in grams. Portions are an input convenience and a display preference, never the stored quantity. ### Phase 1 · Food data you can actually query Build: Get an API key and query FoodData Central. Cache every food you fetch, keyed by its identifier. Understand the data types before you build on them, because they differ in completeness and in how much you should trust them. Trap: The API allows roughly a thousand requests an hour per address, which a search-as-you-type box will exhaust in an afternoon. Debounce properly, cache every result, and search the local cache first. Trap: The data types are not equivalent. Foundation and SR Legacy entries carry full laboratory micronutrient panels; Branded entries carry only what a manufacturer put on the label, which is usually a dozen nutrients. Showing a branded food's empty micronutrient panel as though it were zero is wrong and misleading. Done when: A search returns foods with nutrient data. The same search offline returns the cached results. A branded food with no iron figure displays as unknown rather than as zero, and the interface says which data type each food came from. Do not build yet: No logging, no totals, no charts. ### Phase 2 · Logging with units that make sense Build: Log an entry by choosing a food, a portion and a quantity. Convert to grams using the portion weights the database supplies, and store the grams. Support meals, editing and deleting, and make repeating yesterday's entry one tap. Trap: Storing quantity and portion rather than grams means a corrected portion definition silently rewrites every historical entry that used it. Compute grams at entry time and store them; keep the portion only for display. Done when: Logging one cup of a food whose database portion says a cup is 240 grams stores 240 grams. Changing that portion's definition afterwards does not alter the stored grams of the existing entry. Deleting an entry removes it from the day's totals immediately. Do not build yet: No totals panel and no charts yet. ### Phase 3 · Totals that are honest about what they do not know Build: Sum nutrients across a day's entries, grouped into macronutrients, vitamins and minerals. Where a food is missing a nutrient, exclude it from that total and say how many foods contributed. A total silently computed from half the entries is worse than no total. Trap: Treating a missing nutrient as zero produces a day that appears deficient in something because one branded food did not print it on a label. Track how many of the day's foods actually carried each nutrient and show that count next to the figure. Done when: A day containing one branded food with no magnesium figure shows the magnesium total with a coverage note naming the number of contributing foods. Macronutrient totals match a hand calculation for a three item day. Switching a food from branded to a foundation equivalent changes the coverage note. Do not build yet: No trends and no recipes yet. ### Phase 4 · Recipes and repeated meals Build: A recipe is a named set of foods with gram weights, plus how many servings it makes. Logging a serving of a recipe expands to its constituent nutrients. Editing a recipe does not rewrite entries already logged from it. Trap: Expanding a recipe by reference at display time means editing the recipe next month changes what you ate last month. Snapshot the nutrient contribution at log time, exactly as with portions and grams. Done when: Logging one serving of a four ingredient recipe produces the same nutrient totals as logging the four ingredients at a quarter of their weights. Editing the recipe afterwards does not change the already logged entry. A recipe containing another recipe resolves correctly. Do not build yet: No trends yet. ### Phase 5 · Neutral trends, enforced by a test Build: Charts of nutrient intake over weeks, with gaps left as gaps. No targets, no goals, no grades, no streaks, no warnings, no body composition scoring. Add a copy lint with a banned word list that fails the build if judgement language reaches the interface. Trap: Reference intake figures are population averages for planning, not personal targets, and presenting one as a line labelled goal turns a reference into an instruction. If you show them at all, label them as population references and never colour a value red for falling below one. Done when: Trend charts render with unlogged days as gaps rather than zeroes. The copy lint fails when a banned word is deliberately added and passes on the shipped strings. No screen shows a target, a grade, a streak or a red value. Reference figures, where shown, are labelled as population references. Do not build yet: Export and sharing come next. ### Phase 6 · Export, and a review by someone else Build: Export the whole log as JSON and CSV, and import it back. Then have someone other than you read every interface string specifically for tone, and record what changed. If the application is shared with anyone, add authentication first. Trap: A food log is unusually sensitive personal data and, for some people, a record they would find distressing to see reflected back with any judgement attached. Treat both the data and the wording as things that need another person's eyes before release. Done when: Export, clear all data, import reproduces every entry and recipe exactly, verified by a checksum. A second person has reviewed the interface strings and their notes are recorded. Any shared deployment requires authentication. Do not build yet: Nothing after this. ### Out of scope (and why) - The curated food database. USDA data is excellent and public, but the curation, deduplication and portion normalisation behind a commercial database is real ongoing work that you inherit. - Barcode scanning against a commercial product catalogue, which is a licensed dataset rather than a feature. - Wearable and health platform integrations, which need hardware and platform permissions. - Any coaching, target setting or nutritional advice. That is a different product with a duty of care, and this one deliberately refuses it. ### README must contain - That the food data is USDA FoodData Central, public domain under CC0, and how to get a free key - That the API allows roughly a thousand requests an hour, and how caching keeps you under it - That data types differ, and that a missing nutrient is shown as unknown rather than zero - That everything is stored in grams and portions are a display convenience - That this application sets no targets and gives no advice, and that the copy lint enforces it ===== AGENTS.md ===== # Agent instructions · Cronometer Gold indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: TypeScript with Vite, built as an installable progressive web application, USDA FoodData Central, cached locally, IndexedDB for the log and the food cache, A unit conversion layer over gram-based nutrient data, Hand-drawn SVG. Do not substitute. - Work one phase at a time, in order. Do not start a phase until every "Done when" item of the previous one passes. - Prefer the fewest moving parts that satisfy the step. No frameworks, services or dependencies the plan does not name. - Secrets live in `.env`, never in source or logs. Keep `.env.example` current when a variable is introduced. - Do not invent cryptography, security guarantees, APIs or compliance claims. - Add a focused test for every destructive, security-sensitive or data-loss path the plan names. - Run the project checks before declaring a phase complete, and record any deliberate shortcut in the README under "Tradeoffs". ## Known traps - The API allows roughly a thousand requests an hour per address, which a search-as-you-type box will exhaust in an afternoon. Debounce properly, cache every result, and search the local cache first. - The data types are not equivalent. Foundation and SR Legacy entries carry full laboratory micronutrient panels; Branded entries carry only what a manufacturer put on the label, which is usually a dozen nutrients. Showing a branded food's empty micronutrient panel as though it were zero is wrong and misleading. - Storing quantity and portion rather than grams means a corrected portion definition silently rewrites every historical entry that used it. Compute grams at entry time and store them; keep the portion only for display. - Treating a missing nutrient as zero produces a day that appears deficient in something because one branded food did not print it on a label. Track how many of the day's foods actually carried each nutrient and show that count next to the figure. - Expanding a recipe by reference at display time means editing the recipe next month changes what you ate last month. Snapshot the nutrient contribution at log time, exactly as with portions and grams. - Reference intake figures are population averages for planning, not personal targets, and presenting one as a line labelled goal turns a reference into an instruction. If you show them at all, label them as population references and never colour a value red for falling below one. ===== BUILD_PLAN.md ===== # Build plan · Cronometer Gold A food log backed by USDA FoodData Central, which is public domain data: search foods, record portions with real units, see full micronutrient totals rather than only calories, view trends over time in neutral language, and export everything you have logged. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · Food data you can actually query A search returns real foods with real nutrient data, and it works the second time without a network. ### Steps 1. Get a key and read the data types Sign up at api.data.gov for a free key. Then fetch one food of each data type and look at what is actually in it before designing anything. ```sh curl 'https://api.nal.usda.gov/fdc/v1/foods/search?query=oats&api_key=YOUR_KEY' ``` 2. Cache aggressively and search locally first Store every fetched food. Search the local cache before the network, which is both faster and how you stay under the rate limit. Files: `src/food-cache.ts` 3. Distinguish missing from zero A nutrient absent from a record is unknown, not zero. This distinction runs through every total you will compute later, so get it right here. Files: `src/nutrients.ts` ### Done when - [ ] A search returns foods with nutrient data - [ ] The same search works offline from the cache - [ ] A missing nutrient displays as unknown rather than zero - [ ] The data type of each food is visible in the interface ### Watch out - The API allows roughly a thousand requests an hour per address, which a search-as-you-type box will exhaust in an afternoon. Debounce properly, cache every result, and search the local cache first. - The data types are not equivalent. Foundation and SR Legacy entries carry full laboratory micronutrient panels; Branded entries carry only what a manufacturer put on the label, which is usually a dozen nutrients. Showing a branded food's empty micronutrient panel as though it were zero is wrong and misleading. ## Phase 2 · Logging with units that make sense You can log a cup of something and the grams are right. ### Steps 1. Build the entry flow Search, pick a portion, set a quantity, choose a meal. Few taps, large targets, because this happens at a table. Files: `src/log.ts` 2. Convert and store grams Grams computed once at entry. The portion label is kept for display only. Files: `src/units.ts` 3. Add repeat and edit Repeating a previous entry is the most common action after the first week. Make it one tap. Files: `src/log.ts` ### Done when - [ ] A named portion converts to the database's gram weight - [ ] Changing a portion definition does not alter existing entries - [ ] Deleting an entry updates totals immediately - [ ] Repeating a previous entry takes one tap ### Watch out - Storing quantity and portion rather than grams means a corrected portion definition silently rewrites every historical entry that used it. Compute grams at entry time and store them; keep the portion only for display. ## Phase 3 · Totals that are honest about what they do not know The daily panel shows full micronutrients and is clear about coverage. ### Steps 1. Sum with coverage tracking For each nutrient, accumulate the value and count how many entries supplied one. Both numbers go to the interface. Files: `src/totals.ts` 2. Group nutrients for display Macronutrients, vitamins, minerals. A flat list of eighty numbers is not information. Files: `src/nutrients.ts` 3. Verify against a hand calculation Three foods, computed by hand, asserted in a test. This is the arithmetic everything else depends on. Files: `src/totals.test.ts` ### Done when - [ ] A missing nutrient reduces coverage rather than the total - [ ] Coverage is shown next to each nutrient figure - [ ] Macronutrient totals match a hand calculation for a three item day - [ ] Nutrients are grouped rather than listed flat ### Watch out - Treating a missing nutrient as zero produces a day that appears deficient in something because one branded food did not print it on a label. Track how many of the day's foods actually carried each nutrient and show that count next to the figure. ## Phase 4 · Recipes and repeated meals The meal you make every week is logged in one action. ### Steps 1. Build recipes as gram-weighted sets Ingredients in grams, plus a serving count. Grams throughout, exactly as with entries. Files: `src/recipes.ts` 2. Snapshot nutrients at log time Store the computed contribution with the entry. The recipe is a template, not a live reference. Files: `src/log.ts` 3. Handle nesting A recipe used as an ingredient. Resolve depth-first and guard against a recipe containing itself. Files: `src/recipes.ts` ### Done when - [ ] A recipe serving matches its ingredients logged individually - [ ] Editing a recipe does not alter existing entries - [ ] A nested recipe resolves correctly - [ ] A self-referencing recipe is rejected rather than looping ### Watch out - Expanding a recipe by reference at display time means editing the recipe next month changes what you ate last month. Snapshot the nutrient contribution at log time, exactly as with portions and grams. ## Phase 5 · Neutral trends, enforced by a test You can see change over time, and the application cannot start judging without a test failing. ### Steps 1. Draw trend charts in SVG Weekly averages per nutrient, gaps preserved. A day with no entries is missing data, not a zero intake day. Files: `src/charts.ts` 2. Write the copy lint Scan interface strings for judgement and diagnostic language: goal, target, over, under, exceeded, cheat, guilty, earned, burn off, good day, bad day, streak. Fail the build, not warn. Files: `scripts/copy-lint.ts`, `scripts/banned-words.json` 3. Handle reference intakes carefully or not at all If you show them, label them as population references and never colour by comparison. Omitting them entirely is a legitimate and safer choice. Files: `src/reference.ts` ### Done when - [ ] Unlogged days appear as gaps, not zeroes - [ ] The copy lint fails on a planted word and passes on the shipped strings - [ ] No screen shows a target, grade, streak or red value - [ ] Reference figures, if present, are labelled as population references ### Watch out - Reference intake figures are population averages for planning, not personal targets, and presenting one as a line labelled goal turns a reference into an instruction. If you show them at all, label them as population references and never colour a value red for falling below one. ## Not in this build - A curated and deduplicated food database with normalised portions. - Barcode scanning against a commercial product catalogue. - Wearable and health platform integrations. - Any coaching, target setting or nutritional advice. ## After v1, if you want it - Offline barcode lookup against the branded subset you have already cached - A weekly summary that reports coverage rather than performance - Importing a log exported from another tracker, which is mostly a mapping exercise ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required · secret. Issued free by fdc.nal.usda.gov/api-key-signup. Proxy requests through your own small endpoint if you deploy this anywhere public, so the key is not in the client bundle. VITE_FDC_API_KEY=replace-with-your-data-gov-key # Required. How many foods to keep cached locally. Higher means fewer API calls and more storage. VITE_FDC_CACHE_MAX=5000 # Required. Search-as-you-type debounce. Lower values burn through the hourly rate limit. VITE_SEARCH_DEBOUNCE_MS=400 # Required. Whether population reference figures appear at all. False is the safer default and requires no careful labelling. VITE_SHOW_REFERENCE_INTAKES=false # Required. How often to prompt for an export, because browser storage can be evicted. VITE_EXPORT_REMINDER_DAYS=30
You are building a lean indie version of Cronometer Gold. Create the following project files first, then implement the application by following them. Keep the files updated as decisions change. Do not collapse this into a single README or prompt. ===== README.md ===== # Cronometer Gold · indie build A food log backed by USDA FoodData Central, which is public domain data: search foods, record portions with real units, see full micronutrient totals rather than only calories, view trends over time in neutral language, and export everything you have logged. Estimated effort: **multi-day**. Work `BUILD_PLAN.md` top to bottom · every phase ends in a check that has to pass before the next one starts. ## Stack | Part | Choice | Why | | --- | --- | --- | | Runtime | TypeScript with Vite, built as an installable progressive web application | logging happens at a table with a phone in one hand, and an app store review is not worth it for one user | | Food data | USDA FoodData Central, cached locally | public domain under CC0, roughly six hundred thousand foods, and free with an API key | | Storage | IndexedDB for the log and the food cache | logging must work with no signal, and the food cache is what makes that possible | | Units | A unit conversion layer over gram-based nutrient data | every nutrient figure is per hundred grams, and every portion a person names is not | | Charts | Hand-drawn SVG | a handful of neutral trend charts, where a library would cost more than it saves | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **Node.js 22 or newer** · free - Why: Runs the build and the copy lint. - Get it: nodejs.org, or nvm install 22. - Verify: node --version prints v22 or higher - [ ] **A FoodData Central API key** · free; roughly one thousand requests per hour per address - Why: Without it there is no food data. The demo key exists but is rate limited far below anything usable. - Get it: Sign up at fdc.nal.usda.gov/api-key-signup, which issues a free data.gov key by email in a few minutes. No payment details are required. - Verify: A search request returns results rather than a rate limit error - [ ] **A phone you can install the application on** · free - Why: Logging happens away from a desk, and one-handed use and offline behaviour cannot be judged in a desktop browser. - Get it: Serve over HTTPS to your phone with a tunnelling tool and install to the home screen. - Verify: It installs to the home screen and opens without browser chrome - [ ] **Someone willing to read the interface strings** · free - Why: Phase 6 needs a second reader for tone. You cannot audit your own wording for pressure, and this subject matter makes that audit worth more than usual. - Get it: Ask one person, hand them the printed strings, and ask specifically whether anything reads as judgement or instruction. - [ ] **A decision about reference intakes** · free - Why: Showing population reference figures next to personal totals invites reading them as targets, which is exactly what this application refuses to do. - Get it: Decide before Phase 5 whether to show them at all. Omitting them is simpler and safer; showing them requires careful labelling and no comparative colouring. - Verify: The decision and its reasoning are written in the README ## Quick start ```sh curl 'https://api.nal.usda.gov/fdc/v1/foods/search?query=oats&api_key=YOUR_KEY' ``` Then copy `.env.example` to `.env` and fill in the values it documents. ## Honest limits This build deliberately does not replace: - A curated and deduplicated food database with normalised portions. - Barcode scanning against a commercial product catalogue. - Wearable and health platform integrations. - Any coaching, target setting or nutritional advice. - curated food database, barcode data, device integrations, reports, and mobile apps - wearable hardware data - coach network - clinical validation - large content library and social network If one of those is essential to you, that is the reason to keep paying for Cronometer Gold, and the README should say so rather than pretend. ===== BRIEF.md ===== # Build brief · Cronometer Gold The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me a nutrition log built on the public USDA food database, showing transparent totals without setting targets for you to replace Cronometer Gold. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Rule zero This is a record, not a diet product. It logs what you entered and shows the nutrient totals plainly. It must not diagnose, must not score a day as good or bad, must not set calorie or weight targets, and must not use streaks or warnings to pressure anyone. Nutrition tracking sits close to disordered eating for a meaningful number of people, and an application that adds judgement can do real harm. Phase 5 makes that promise mechanical with a copy lint that fails the build, rather than leaving it as a good intention that erodes over a year of small additions. ### Stack (fixed, do not substitute) - Runtime: TypeScript with Vite, built as an installable progressive web application · logging happens at a table with a phone in one hand, and an app store review is not worth it for one user - Food data: USDA FoodData Central, cached locally · public domain under CC0, roughly six hundred thousand foods, and free with an API key - Storage: IndexedDB for the log and the food cache · logging must work with no signal, and the food cache is what makes that possible - Units: A unit conversion layer over gram-based nutrient data · every nutrient figure is per hundred grams, and every portion a person names is not - Charts: Hand-drawn SVG · a handful of neutral trend charts, where a library would cost more than it saves ### Data model (create this before Phase 1) - foods(fdc_id, description, data_type, brand, serving_json, nutrients_json, cached_at) · cached from the API, keyed by its identifier - portions(id, fdc_id, label, gram_weight) · the named servings the database provides, plus any you add - entries(id, at, fdc_id, quantity, portion_id, grams, meal) · grams is always computed and stored, so a portion definition changing later does not rewrite history - recipes(id, name), recipe_items(recipe_id, fdc_id, grams) · a recipe is a food composed of other foods - nutrient_defs(id, name, unit, display_group) · so the interface can show a full panel rather than four headline numbers - Everything is stored in grams. Portions are an input convenience and a display preference, never the stored quantity. ### Phase 1 · Food data you can actually query Build: Get an API key and query FoodData Central. Cache every food you fetch, keyed by its identifier. Understand the data types before you build on them, because they differ in completeness and in how much you should trust them. Trap: The API allows roughly a thousand requests an hour per address, which a search-as-you-type box will exhaust in an afternoon. Debounce properly, cache every result, and search the local cache first. Trap: The data types are not equivalent. Foundation and SR Legacy entries carry full laboratory micronutrient panels; Branded entries carry only what a manufacturer put on the label, which is usually a dozen nutrients. Showing a branded food's empty micronutrient panel as though it were zero is wrong and misleading. Done when: A search returns foods with nutrient data. The same search offline returns the cached results. A branded food with no iron figure displays as unknown rather than as zero, and the interface says which data type each food came from. Do not build yet: No logging, no totals, no charts. ### Phase 2 · Logging with units that make sense Build: Log an entry by choosing a food, a portion and a quantity. Convert to grams using the portion weights the database supplies, and store the grams. Support meals, editing and deleting, and make repeating yesterday's entry one tap. Trap: Storing quantity and portion rather than grams means a corrected portion definition silently rewrites every historical entry that used it. Compute grams at entry time and store them; keep the portion only for display. Done when: Logging one cup of a food whose database portion says a cup is 240 grams stores 240 grams. Changing that portion's definition afterwards does not alter the stored grams of the existing entry. Deleting an entry removes it from the day's totals immediately. Do not build yet: No totals panel and no charts yet. ### Phase 3 · Totals that are honest about what they do not know Build: Sum nutrients across a day's entries, grouped into macronutrients, vitamins and minerals. Where a food is missing a nutrient, exclude it from that total and say how many foods contributed. A total silently computed from half the entries is worse than no total. Trap: Treating a missing nutrient as zero produces a day that appears deficient in something because one branded food did not print it on a label. Track how many of the day's foods actually carried each nutrient and show that count next to the figure. Done when: A day containing one branded food with no magnesium figure shows the magnesium total with a coverage note naming the number of contributing foods. Macronutrient totals match a hand calculation for a three item day. Switching a food from branded to a foundation equivalent changes the coverage note. Do not build yet: No trends and no recipes yet. ### Phase 4 · Recipes and repeated meals Build: A recipe is a named set of foods with gram weights, plus how many servings it makes. Logging a serving of a recipe expands to its constituent nutrients. Editing a recipe does not rewrite entries already logged from it. Trap: Expanding a recipe by reference at display time means editing the recipe next month changes what you ate last month. Snapshot the nutrient contribution at log time, exactly as with portions and grams. Done when: Logging one serving of a four ingredient recipe produces the same nutrient totals as logging the four ingredients at a quarter of their weights. Editing the recipe afterwards does not change the already logged entry. A recipe containing another recipe resolves correctly. Do not build yet: No trends yet. ### Phase 5 · Neutral trends, enforced by a test Build: Charts of nutrient intake over weeks, with gaps left as gaps. No targets, no goals, no grades, no streaks, no warnings, no body composition scoring. Add a copy lint with a banned word list that fails the build if judgement language reaches the interface. Trap: Reference intake figures are population averages for planning, not personal targets, and presenting one as a line labelled goal turns a reference into an instruction. If you show them at all, label them as population references and never colour a value red for falling below one. Done when: Trend charts render with unlogged days as gaps rather than zeroes. The copy lint fails when a banned word is deliberately added and passes on the shipped strings. No screen shows a target, a grade, a streak or a red value. Reference figures, where shown, are labelled as population references. Do not build yet: Export and sharing come next. ### Phase 6 · Export, and a review by someone else Build: Export the whole log as JSON and CSV, and import it back. Then have someone other than you read every interface string specifically for tone, and record what changed. If the application is shared with anyone, add authentication first. Trap: A food log is unusually sensitive personal data and, for some people, a record they would find distressing to see reflected back with any judgement attached. Treat both the data and the wording as things that need another person's eyes before release. Done when: Export, clear all data, import reproduces every entry and recipe exactly, verified by a checksum. A second person has reviewed the interface strings and their notes are recorded. Any shared deployment requires authentication. Do not build yet: Nothing after this. ### Out of scope (and why) - The curated food database. USDA data is excellent and public, but the curation, deduplication and portion normalisation behind a commercial database is real ongoing work that you inherit. - Barcode scanning against a commercial product catalogue, which is a licensed dataset rather than a feature. - Wearable and health platform integrations, which need hardware and platform permissions. - Any coaching, target setting or nutritional advice. That is a different product with a duty of care, and this one deliberately refuses it. ### README must contain - That the food data is USDA FoodData Central, public domain under CC0, and how to get a free key - That the API allows roughly a thousand requests an hour, and how caching keeps you under it - That data types differ, and that a missing nutrient is shown as unknown rather than zero - That everything is stored in grams and portions are a display convenience - That this application sets no targets and gives no advice, and that the copy lint enforces it ===== AGENTS.md ===== # Agent instructions · Cronometer Gold indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: TypeScript with Vite, built as an installable progressive web application, USDA FoodData Central, cached locally, IndexedDB for the log and the food cache, A unit conversion layer over gram-based nutrient data, Hand-drawn SVG. Do not substitute. - Work one phase at a time, in order. Do not start a phase until every "Done when" item of the previous one passes. - Prefer the fewest moving parts that satisfy the step. No frameworks, services or dependencies the plan does not name. - Secrets live in `.env`, never in source or logs. Keep `.env.example` current when a variable is introduced. - Do not invent cryptography, security guarantees, APIs or compliance claims. - Add a focused test for every destructive, security-sensitive or data-loss path the plan names. - Run the project checks before declaring a phase complete, and record any deliberate shortcut in the README under "Tradeoffs". ## Known traps - The API allows roughly a thousand requests an hour per address, which a search-as-you-type box will exhaust in an afternoon. Debounce properly, cache every result, and search the local cache first. - The data types are not equivalent. Foundation and SR Legacy entries carry full laboratory micronutrient panels; Branded entries carry only what a manufacturer put on the label, which is usually a dozen nutrients. Showing a branded food's empty micronutrient panel as though it were zero is wrong and misleading. - Storing quantity and portion rather than grams means a corrected portion definition silently rewrites every historical entry that used it. Compute grams at entry time and store them; keep the portion only for display. - Treating a missing nutrient as zero produces a day that appears deficient in something because one branded food did not print it on a label. Track how many of the day's foods actually carried each nutrient and show that count next to the figure. - Expanding a recipe by reference at display time means editing the recipe next month changes what you ate last month. Snapshot the nutrient contribution at log time, exactly as with portions and grams. - Reference intake figures are population averages for planning, not personal targets, and presenting one as a line labelled goal turns a reference into an instruction. If you show them at all, label them as population references and never colour a value red for falling below one. ===== BUILD_PLAN.md ===== # Build plan · Cronometer Gold A food log backed by USDA FoodData Central, which is public domain data: search foods, record portions with real units, see full micronutrient totals rather than only calories, view trends over time in neutral language, and export everything you have logged. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · Food data you can actually query A search returns real foods with real nutrient data, and it works the second time without a network. ### Steps 1. Get a key and read the data types Sign up at api.data.gov for a free key. Then fetch one food of each data type and look at what is actually in it before designing anything. ```sh curl 'https://api.nal.usda.gov/fdc/v1/foods/search?query=oats&api_key=YOUR_KEY' ``` 2. Cache aggressively and search locally first Store every fetched food. Search the local cache before the network, which is both faster and how you stay under the rate limit. Files: `src/food-cache.ts` 3. Distinguish missing from zero A nutrient absent from a record is unknown, not zero. This distinction runs through every total you will compute later, so get it right here. Files: `src/nutrients.ts` ### Done when - [ ] A search returns foods with nutrient data - [ ] The same search works offline from the cache - [ ] A missing nutrient displays as unknown rather than zero - [ ] The data type of each food is visible in the interface ### Watch out - The API allows roughly a thousand requests an hour per address, which a search-as-you-type box will exhaust in an afternoon. Debounce properly, cache every result, and search the local cache first. - The data types are not equivalent. Foundation and SR Legacy entries carry full laboratory micronutrient panels; Branded entries carry only what a manufacturer put on the label, which is usually a dozen nutrients. Showing a branded food's empty micronutrient panel as though it were zero is wrong and misleading. ## Phase 2 · Logging with units that make sense You can log a cup of something and the grams are right. ### Steps 1. Build the entry flow Search, pick a portion, set a quantity, choose a meal. Few taps, large targets, because this happens at a table. Files: `src/log.ts` 2. Convert and store grams Grams computed once at entry. The portion label is kept for display only. Files: `src/units.ts` 3. Add repeat and edit Repeating a previous entry is the most common action after the first week. Make it one tap. Files: `src/log.ts` ### Done when - [ ] A named portion converts to the database's gram weight - [ ] Changing a portion definition does not alter existing entries - [ ] Deleting an entry updates totals immediately - [ ] Repeating a previous entry takes one tap ### Watch out - Storing quantity and portion rather than grams means a corrected portion definition silently rewrites every historical entry that used it. Compute grams at entry time and store them; keep the portion only for display. ## Phase 3 · Totals that are honest about what they do not know The daily panel shows full micronutrients and is clear about coverage. ### Steps 1. Sum with coverage tracking For each nutrient, accumulate the value and count how many entries supplied one. Both numbers go to the interface. Files: `src/totals.ts` 2. Group nutrients for display Macronutrients, vitamins, minerals. A flat list of eighty numbers is not information. Files: `src/nutrients.ts` 3. Verify against a hand calculation Three foods, computed by hand, asserted in a test. This is the arithmetic everything else depends on. Files: `src/totals.test.ts` ### Done when - [ ] A missing nutrient reduces coverage rather than the total - [ ] Coverage is shown next to each nutrient figure - [ ] Macronutrient totals match a hand calculation for a three item day - [ ] Nutrients are grouped rather than listed flat ### Watch out - Treating a missing nutrient as zero produces a day that appears deficient in something because one branded food did not print it on a label. Track how many of the day's foods actually carried each nutrient and show that count next to the figure. ## Phase 4 · Recipes and repeated meals The meal you make every week is logged in one action. ### Steps 1. Build recipes as gram-weighted sets Ingredients in grams, plus a serving count. Grams throughout, exactly as with entries. Files: `src/recipes.ts` 2. Snapshot nutrients at log time Store the computed contribution with the entry. The recipe is a template, not a live reference. Files: `src/log.ts` 3. Handle nesting A recipe used as an ingredient. Resolve depth-first and guard against a recipe containing itself. Files: `src/recipes.ts` ### Done when - [ ] A recipe serving matches its ingredients logged individually - [ ] Editing a recipe does not alter existing entries - [ ] A nested recipe resolves correctly - [ ] A self-referencing recipe is rejected rather than looping ### Watch out - Expanding a recipe by reference at display time means editing the recipe next month changes what you ate last month. Snapshot the nutrient contribution at log time, exactly as with portions and grams. ## Phase 5 · Neutral trends, enforced by a test You can see change over time, and the application cannot start judging without a test failing. ### Steps 1. Draw trend charts in SVG Weekly averages per nutrient, gaps preserved. A day with no entries is missing data, not a zero intake day. Files: `src/charts.ts` 2. Write the copy lint Scan interface strings for judgement and diagnostic language: goal, target, over, under, exceeded, cheat, guilty, earned, burn off, good day, bad day, streak. Fail the build, not warn. Files: `scripts/copy-lint.ts`, `scripts/banned-words.json` 3. Handle reference intakes carefully or not at all If you show them, label them as population references and never colour by comparison. Omitting them entirely is a legitimate and safer choice. Files: `src/reference.ts` ### Done when - [ ] Unlogged days appear as gaps, not zeroes - [ ] The copy lint fails on a planted word and passes on the shipped strings - [ ] No screen shows a target, grade, streak or red value - [ ] Reference figures, if present, are labelled as population references ### Watch out - Reference intake figures are population averages for planning, not personal targets, and presenting one as a line labelled goal turns a reference into an instruction. If you show them at all, label them as population references and never colour a value red for falling below one. ## Not in this build - A curated and deduplicated food database with normalised portions. - Barcode scanning against a commercial product catalogue. - Wearable and health platform integrations. - Any coaching, target setting or nutritional advice. ## After v1, if you want it - Offline barcode lookup against the branded subset you have already cached - A weekly summary that reports coverage rather than performance - Importing a log exported from another tracker, which is mostly a mapping exercise ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required · secret. Issued free by fdc.nal.usda.gov/api-key-signup. Proxy requests through your own small endpoint if you deploy this anywhere public, so the key is not in the client bundle. VITE_FDC_API_KEY=replace-with-your-data-gov-key # Required. How many foods to keep cached locally. Higher means fewer API calls and more storage. VITE_FDC_CACHE_MAX=5000 # Required. Search-as-you-type debounce. Lower values burn through the hourly rate limit. VITE_SEARCH_DEBOUNCE_MS=400 # Required. Whether population reference figures appear at all. False is the safer default and requires no careful labelling. VITE_SHOW_REFERENCE_INTAKES=false # Required. How often to prompt for an export, because browser storage can be evicted. VITE_EXPORT_REMINDER_DAYS=30
You are building a production product version of Cronometer Gold. Create the following project files first, then implement the application by following them. Keep the files updated as decisions change. Do not collapse this into a single README or prompt. ===== PRODUCT.md ===== # Cronometer Gold · product brief ## Problem The core loop is buildable, but a dependable replacement becomes a real weekend or multi-day project. For Cronometer Gold, log user-entered nutrition and show transparent nutrient totals without diagnosis. The hard boundary is curated food database, barcode data, device integrations, reports, and mobile apps, plus hardware data, content, coaching, and trust. ## Product outcome A nutrition log built on public data, honest about what it does not know, storing everything in grams so history never rewrites itself, showing trends without targets, and reviewed by another person for tone before anyone else sees it. ## Target user A builder who needs a maintainable product foundation, not a one-off demo. ## Required capabilities - iOS or Android device - optional HealthKit or Health Connect permission - local storage ## Explicit non-goals for v1 - A curated and deduplicated food database with normalised portions. - Barcode scanning against a commercial product catalogue. - Wearable and health platform integrations. - Any coaching, target setting or nutritional advice. - curated food database, barcode data, device integrations, reports, and mobile apps - wearable hardware data - coach network - clinical validation - large content library and social network ## Success criteria - The copy lint passes and is proved to fail on a planted word - A missing nutrient is confirmed to display as unknown rather than zero - Export and import round trip matches by checksum - A second person has reviewed the interface strings for tone - No screen shows a target, grade, streak or comparative colouring ===== BRIEF.md ===== # Build brief · Cronometer Gold The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me a nutrition log built on the public USDA food database, showing transparent totals without setting targets for you to replace Cronometer Gold. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Rule zero This is a record, not a diet product. It logs what you entered and shows the nutrient totals plainly. It must not diagnose, must not score a day as good or bad, must not set calorie or weight targets, and must not use streaks or warnings to pressure anyone. Nutrition tracking sits close to disordered eating for a meaningful number of people, and an application that adds judgement can do real harm. Phase 5 makes that promise mechanical with a copy lint that fails the build, rather than leaving it as a good intention that erodes over a year of small additions. ### Stack (fixed, do not substitute) - Runtime: TypeScript with Vite, built as an installable progressive web application · logging happens at a table with a phone in one hand, and an app store review is not worth it for one user - Food data: USDA FoodData Central, cached locally · public domain under CC0, roughly six hundred thousand foods, and free with an API key - Storage: IndexedDB for the log and the food cache · logging must work with no signal, and the food cache is what makes that possible - Units: A unit conversion layer over gram-based nutrient data · every nutrient figure is per hundred grams, and every portion a person names is not - Charts: Hand-drawn SVG · a handful of neutral trend charts, where a library would cost more than it saves ### Data model (create this before Phase 1) - foods(fdc_id, description, data_type, brand, serving_json, nutrients_json, cached_at) · cached from the API, keyed by its identifier - portions(id, fdc_id, label, gram_weight) · the named servings the database provides, plus any you add - entries(id, at, fdc_id, quantity, portion_id, grams, meal) · grams is always computed and stored, so a portion definition changing later does not rewrite history - recipes(id, name), recipe_items(recipe_id, fdc_id, grams) · a recipe is a food composed of other foods - nutrient_defs(id, name, unit, display_group) · so the interface can show a full panel rather than four headline numbers - Everything is stored in grams. Portions are an input convenience and a display preference, never the stored quantity. ### Phase 1 · Food data you can actually query Build: Get an API key and query FoodData Central. Cache every food you fetch, keyed by its identifier. Understand the data types before you build on them, because they differ in completeness and in how much you should trust them. Trap: The API allows roughly a thousand requests an hour per address, which a search-as-you-type box will exhaust in an afternoon. Debounce properly, cache every result, and search the local cache first. Trap: The data types are not equivalent. Foundation and SR Legacy entries carry full laboratory micronutrient panels; Branded entries carry only what a manufacturer put on the label, which is usually a dozen nutrients. Showing a branded food's empty micronutrient panel as though it were zero is wrong and misleading. Done when: A search returns foods with nutrient data. The same search offline returns the cached results. A branded food with no iron figure displays as unknown rather than as zero, and the interface says which data type each food came from. Do not build yet: No logging, no totals, no charts. ### Phase 2 · Logging with units that make sense Build: Log an entry by choosing a food, a portion and a quantity. Convert to grams using the portion weights the database supplies, and store the grams. Support meals, editing and deleting, and make repeating yesterday's entry one tap. Trap: Storing quantity and portion rather than grams means a corrected portion definition silently rewrites every historical entry that used it. Compute grams at entry time and store them; keep the portion only for display. Done when: Logging one cup of a food whose database portion says a cup is 240 grams stores 240 grams. Changing that portion's definition afterwards does not alter the stored grams of the existing entry. Deleting an entry removes it from the day's totals immediately. Do not build yet: No totals panel and no charts yet. ### Phase 3 · Totals that are honest about what they do not know Build: Sum nutrients across a day's entries, grouped into macronutrients, vitamins and minerals. Where a food is missing a nutrient, exclude it from that total and say how many foods contributed. A total silently computed from half the entries is worse than no total. Trap: Treating a missing nutrient as zero produces a day that appears deficient in something because one branded food did not print it on a label. Track how many of the day's foods actually carried each nutrient and show that count next to the figure. Done when: A day containing one branded food with no magnesium figure shows the magnesium total with a coverage note naming the number of contributing foods. Macronutrient totals match a hand calculation for a three item day. Switching a food from branded to a foundation equivalent changes the coverage note. Do not build yet: No trends and no recipes yet. ### Phase 4 · Recipes and repeated meals Build: A recipe is a named set of foods with gram weights, plus how many servings it makes. Logging a serving of a recipe expands to its constituent nutrients. Editing a recipe does not rewrite entries already logged from it. Trap: Expanding a recipe by reference at display time means editing the recipe next month changes what you ate last month. Snapshot the nutrient contribution at log time, exactly as with portions and grams. Done when: Logging one serving of a four ingredient recipe produces the same nutrient totals as logging the four ingredients at a quarter of their weights. Editing the recipe afterwards does not change the already logged entry. A recipe containing another recipe resolves correctly. Do not build yet: No trends yet. ### Phase 5 · Neutral trends, enforced by a test Build: Charts of nutrient intake over weeks, with gaps left as gaps. No targets, no goals, no grades, no streaks, no warnings, no body composition scoring. Add a copy lint with a banned word list that fails the build if judgement language reaches the interface. Trap: Reference intake figures are population averages for planning, not personal targets, and presenting one as a line labelled goal turns a reference into an instruction. If you show them at all, label them as population references and never colour a value red for falling below one. Done when: Trend charts render with unlogged days as gaps rather than zeroes. The copy lint fails when a banned word is deliberately added and passes on the shipped strings. No screen shows a target, a grade, a streak or a red value. Reference figures, where shown, are labelled as population references. Do not build yet: Export and sharing come next. ### Phase 6 · Export, and a review by someone else Build: Export the whole log as JSON and CSV, and import it back. Then have someone other than you read every interface string specifically for tone, and record what changed. If the application is shared with anyone, add authentication first. Trap: A food log is unusually sensitive personal data and, for some people, a record they would find distressing to see reflected back with any judgement attached. Treat both the data and the wording as things that need another person's eyes before release. Done when: Export, clear all data, import reproduces every entry and recipe exactly, verified by a checksum. A second person has reviewed the interface strings and their notes are recorded. Any shared deployment requires authentication. Do not build yet: Nothing after this. ### Out of scope (and why) - The curated food database. USDA data is excellent and public, but the curation, deduplication and portion normalisation behind a commercial database is real ongoing work that you inherit. - Barcode scanning against a commercial product catalogue, which is a licensed dataset rather than a feature. - Wearable and health platform integrations, which need hardware and platform permissions. - Any coaching, target setting or nutritional advice. That is a different product with a duty of care, and this one deliberately refuses it. ### README must contain - That the food data is USDA FoodData Central, public domain under CC0, and how to get a free key - That the API allows roughly a thousand requests an hour, and how caching keeps you under it - That data types differ, and that a missing nutrient is shown as unknown rather than zero - That everything is stored in grams and portions are a display convenience - That this application sets no targets and gives no advice, and that the copy lint enforces it ===== ARCHITECTURE.md ===== # Architecture · Cronometer Gold ## Stack | Part | Choice | Why | | --- | --- | --- | | Runtime | TypeScript with Vite, built as an installable progressive web application | logging happens at a table with a phone in one hand, and an app store review is not worth it for one user | | Food data | USDA FoodData Central, cached locally | public domain under CC0, roughly six hundred thousand foods, and free with an API key | | Storage | IndexedDB for the log and the food cache | logging must work with no signal, and the food cache is what makes that possible | | Units | A unit conversion layer over gram-based nutrient data | every nutrient figure is per hundred grams, and every portion a person names is not | | Charts | Hand-drawn SVG | a handful of neutral trend charts, where a library would cost more than it saves | ## Modules Each module has one owner concern and a documented way to replace it. | Module | Owns | How to replace it | | --- | --- | --- | | Food source | Fetching, caching and normalising USDA records. | Another food database changes the adapter. The missing-versus-zero distinction must survive any swap, because it is a correctness property rather than a data source detail. | | Units | Portion to gram conversion. | Self-contained and heavily testable. Grams as the stored unit is the invariant. | | Totals | Summation and coverage tracking. | Pure functions over entries. Coverage is the part that makes the numbers honest and must not be dropped for simplicity. | | Charts | Showing change without interpreting it. | Any renderer, provided gaps stay gaps and nothing is coloured by comparison to a reference. | | Copy lint | Keeping the tone promise mechanical. | The word list grows over time; the build failure on violation should never become a warning. | ## Configuration Every runtime setting is an environment variable documented in `.env.example`, validated at startup, with a safe local default wherever one exists. - `VITE_FDC_API_KEY` · required, secret · Issued free by fdc.nal.usda.gov/api-key-signup. Proxy requests through your own small endpoint if you deploy this anywhere public, so the key is not in the client bundle. - `VITE_FDC_CACHE_MAX` · required · How many foods to keep cached locally. Higher means fewer API calls and more storage. - `VITE_SEARCH_DEBOUNCE_MS` · required · Search-as-you-type debounce. Lower values burn through the hourly rate limit. - `VITE_SHOW_REFERENCE_INTAKES` · required · Whether population reference figures appear at all. False is the safer default and requires no careful labelling. - `VITE_EXPORT_REMINDER_DAYS` · required · How often to prompt for an export, because browser storage can be evicted. ## Production baseline - Security: least privilege, input validation at every boundary, secret redaction in logs, rate limits on abuse-prone paths, no invented security primitives. - Data: explicit schema and migrations, transactional writes where integrity matters, backup and restore procedures that have been exercised. - Integrations: adapters around third-party providers, idempotent webhook or job processing, bounded retries, timeouts. - Observability: structured logs with request or operation ids, an error-tracking hook, and health and readiness checks where a server exists. - Quality: unit tests for domain rules, integration tests at module boundaries, one end-to-end test of the critical path. ## Decision records For each dependency in the stack table, keep a short note: why it was chosen, its failure mode, and how it is replaced. Do not add infrastructure until a requirement in `PRODUCT.md` justifies it. ===== AGENTS.md ===== # Agent instructions · Cronometer Gold product build - Read `PRODUCT.md` and `ARCHITECTURE.md` before changing code. The stack is fixed: TypeScript with Vite, built as an installable progressive web application, USDA FoodData Central, cached locally, IndexedDB for the log and the food cache, A unit conversion layer over gram-based nutrient data, Hand-drawn SVG. - Implement milestone by milestone from `MILESTONES.md`; keep each change reviewable and leave the application runnable at every commit. - Treat authentication, payments, encryption, imports, webhooks and destructive actions as high-risk boundaries when present. - Never invent cryptography or silently weaken a requirement to make a check pass. - Put every external service behind an interface with a deterministic fake for tests. - Add migrations and rollback or recovery notes for every persistent data change. - Log useful operational context without credentials, tokens, passwords or personal data. - Update documentation and run every check before completing a milestone. ## Known traps - The API allows roughly a thousand requests an hour per address, which a search-as-you-type box will exhaust in an afternoon. Debounce properly, cache every result, and search the local cache first. - The data types are not equivalent. Foundation and SR Legacy entries carry full laboratory micronutrient panels; Branded entries carry only what a manufacturer put on the label, which is usually a dozen nutrients. Showing a branded food's empty micronutrient panel as though it were zero is wrong and misleading. - Storing quantity and portion rather than grams means a corrected portion definition silently rewrites every historical entry that used it. Compute grams at entry time and store them; keep the portion only for display. - Treating a missing nutrient as zero produces a day that appears deficient in something because one branded food did not print it on a label. Track how many of the day's foods actually carried each nutrient and show that count next to the figure. - Expanding a recipe by reference at display time means editing the recipe next month changes what you ate last month. Snapshot the nutrient contribution at log time, exactly as with portions and grams. - Reference intake figures are population averages for planning, not personal targets, and presenting one as a line labelled goal turns a reference into an instruction. If you show them at all, label them as population references and never colour a value red for falling below one. - A food log is unusually sensitive personal data and, for some people, a record they would find distressing to see reflected back with any judgement attached. Treat both the data and the wording as things that need another person's eyes before release. ===== MILESTONES.md ===== # Delivery milestones · Cronometer Gold Estimated effort: **multi-day** for the indie phases; the production-only milestones add the trust and operability layer. ## M1 · Food data you can actually query A search returns real foods with real nutrient data, and it works the second time without a network. ### Steps 1. Get a key and read the data types Sign up at api.data.gov for a free key. Then fetch one food of each data type and look at what is actually in it before designing anything. ```sh curl 'https://api.nal.usda.gov/fdc/v1/foods/search?query=oats&api_key=YOUR_KEY' ``` 2. Cache aggressively and search locally first Store every fetched food. Search the local cache before the network, which is both faster and how you stay under the rate limit. Files: `src/food-cache.ts` 3. Distinguish missing from zero A nutrient absent from a record is unknown, not zero. This distinction runs through every total you will compute later, so get it right here. Files: `src/nutrients.ts` ### Done when - [ ] A search returns foods with nutrient data - [ ] The same search works offline from the cache - [ ] A missing nutrient displays as unknown rather than zero - [ ] The data type of each food is visible in the interface ### Watch out - The API allows roughly a thousand requests an hour per address, which a search-as-you-type box will exhaust in an afternoon. Debounce properly, cache every result, and search the local cache first. - The data types are not equivalent. Foundation and SR Legacy entries carry full laboratory micronutrient panels; Branded entries carry only what a manufacturer put on the label, which is usually a dozen nutrients. Showing a branded food's empty micronutrient panel as though it were zero is wrong and misleading. ## M2 · Logging with units that make sense You can log a cup of something and the grams are right. ### Steps 1. Build the entry flow Search, pick a portion, set a quantity, choose a meal. Few taps, large targets, because this happens at a table. Files: `src/log.ts` 2. Convert and store grams Grams computed once at entry. The portion label is kept for display only. Files: `src/units.ts` 3. Add repeat and edit Repeating a previous entry is the most common action after the first week. Make it one tap. Files: `src/log.ts` ### Done when - [ ] A named portion converts to the database's gram weight - [ ] Changing a portion definition does not alter existing entries - [ ] Deleting an entry updates totals immediately - [ ] Repeating a previous entry takes one tap ### Watch out - Storing quantity and portion rather than grams means a corrected portion definition silently rewrites every historical entry that used it. Compute grams at entry time and store them; keep the portion only for display. ## M3 · Totals that are honest about what they do not know The daily panel shows full micronutrients and is clear about coverage. ### Steps 1. Sum with coverage tracking For each nutrient, accumulate the value and count how many entries supplied one. Both numbers go to the interface. Files: `src/totals.ts` 2. Group nutrients for display Macronutrients, vitamins, minerals. A flat list of eighty numbers is not information. Files: `src/nutrients.ts` 3. Verify against a hand calculation Three foods, computed by hand, asserted in a test. This is the arithmetic everything else depends on. Files: `src/totals.test.ts` ### Done when - [ ] A missing nutrient reduces coverage rather than the total - [ ] Coverage is shown next to each nutrient figure - [ ] Macronutrient totals match a hand calculation for a three item day - [ ] Nutrients are grouped rather than listed flat ### Watch out - Treating a missing nutrient as zero produces a day that appears deficient in something because one branded food did not print it on a label. Track how many of the day's foods actually carried each nutrient and show that count next to the figure. ## M4 · Recipes and repeated meals The meal you make every week is logged in one action. ### Steps 1. Build recipes as gram-weighted sets Ingredients in grams, plus a serving count. Grams throughout, exactly as with entries. Files: `src/recipes.ts` 2. Snapshot nutrients at log time Store the computed contribution with the entry. The recipe is a template, not a live reference. Files: `src/log.ts` 3. Handle nesting A recipe used as an ingredient. Resolve depth-first and guard against a recipe containing itself. Files: `src/recipes.ts` ### Done when - [ ] A recipe serving matches its ingredients logged individually - [ ] Editing a recipe does not alter existing entries - [ ] A nested recipe resolves correctly - [ ] A self-referencing recipe is rejected rather than looping ### Watch out - Expanding a recipe by reference at display time means editing the recipe next month changes what you ate last month. Snapshot the nutrient contribution at log time, exactly as with portions and grams. ## M5 · Neutral trends, enforced by a test You can see change over time, and the application cannot start judging without a test failing. ### Steps 1. Draw trend charts in SVG Weekly averages per nutrient, gaps preserved. A day with no entries is missing data, not a zero intake day. Files: `src/charts.ts` 2. Write the copy lint Scan interface strings for judgement and diagnostic language: goal, target, over, under, exceeded, cheat, guilty, earned, burn off, good day, bad day, streak. Fail the build, not warn. Files: `scripts/copy-lint.ts`, `scripts/banned-words.json` 3. Handle reference intakes carefully or not at all If you show them, label them as population references and never colour by comparison. Omitting them entirely is a legitimate and safer choice. Files: `src/reference.ts` ### Done when - [ ] Unlogged days appear as gaps, not zeroes - [ ] The copy lint fails on a planted word and passes on the shipped strings - [ ] No screen shows a target, grade, streak or red value - [ ] Reference figures, if present, are labelled as population references ### Watch out - Reference intake figures are population averages for planning, not personal targets, and presenting one as a line labelled goal turns a reference into an instruction. If you show them at all, label them as population references and never colour a value red for falling below one. ## M6 · Export, and a review by someone else (production only) Your log is portable, and a second person has read what the application says. ### Steps 1. Export losslessly and import back JSON with a schema version for completeness, CSV for spreadsheets. Round-trip equality is the test worth having. Files: `src/export.ts`, `src/import.ts` 2. Run a tone review with someone else Print every string. Ask specifically whether anything reads as judgement or instruction. Record what you changed. Files: `docs/tone-review.md` 3. Authenticate anything shared If it leaves your own device, put a login in front of it. This data should not be reachable by a URL guess. Files: `src/auth.ts` ### Done when - [ ] Export, clear, import reproduces everything exactly by checksum - [ ] A second reader's tone notes are recorded and acted on - [ ] Shared deployments require authentication - [ ] The CSV contains readable food names and explicit units ### Watch out - A food log is unusually sensitive personal data and, for some people, a record they would find distressing to see reflected back with any judgement attached. Treat both the data and the wording as things that need another person's eyes before release. ===== OPERATIONS.md ===== # Operations · Cronometer Gold ## Backup The user's export file is the backup. Browser storage can be evicted without warning, so prompt for export on a schedule and make it prominent. ## Restore Import the JSON export into an empty installation. The round-trip checksum test is what makes this trustworthy. Do a restore drill before the first real user, and write the date here when it passes. ## Monitoring If the key is proxied through your own endpoint, watch the request rate against the hourly limit and the cache hit rate. A falling hit rate means the cache is being evicted and the limit is about to be hit. ## Incident checklist Rate limit exhaustion is the realistic incident, and it presents as search returning nothing. Fail with a message naming the limit and the reset window rather than an empty result, which reads as the food not existing. 1. Contain the issue without destroying evidence or user data. 2. Record the timeline and affected scope. 3. Rotate exposed secrets and revoke compromised sessions or credentials. 4. Restore from a verified backup when needed. 5. Document the root cause, the remediation and the regression test. ## Release gate - [ ] The copy lint passes and is proved to fail on a planted word - [ ] A missing nutrient is confirmed to display as unknown rather than zero - [ ] Export and import round trip matches by checksum - [ ] A second person has reviewed the interface strings for tone - [ ] No screen shows a target, grade, streak or comparative colouring ## Launch constraint Do not market omitted Cronometer Gold capabilities as implemented. The non-goals in `PRODUCT.md` remain user-visible limitations until they are deliberately delivered. ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required · secret. Issued free by fdc.nal.usda.gov/api-key-signup. Proxy requests through your own small endpoint if you deploy this anywhere public, so the key is not in the client bundle. VITE_FDC_API_KEY=replace-with-your-data-gov-key # Required. How many foods to keep cached locally. Higher means fewer API calls and more storage. VITE_FDC_CACHE_MAX=5000 # Required. Search-as-you-type debounce. Lower values burn through the hourly rate limit. VITE_SEARCH_DEBOUNCE_MS=400 # Required. Whether population reference figures appear at all. False is the safer default and requires no careful labelling. VITE_SHOW_REFERENCE_INTAKES=false # Required. How often to prompt for an export, because browser storage can be evicted. VITE_EXPORT_REMINDER_DAYS=30
# Cronometer Gold · indie build A food log backed by USDA FoodData Central, which is public domain data: search foods, record portions with real units, see full micronutrient totals rather than only calories, view trends over time in neutral language, and export everything you have logged. Estimated effort: **multi-day**. Work `BUILD_PLAN.md` top to bottom · every phase ends in a check that has to pass before the next one starts. ## Stack | Part | Choice | Why | | --- | --- | --- | | Runtime | TypeScript with Vite, built as an installable progressive web application | logging happens at a table with a phone in one hand, and an app store review is not worth it for one user | | Food data | USDA FoodData Central, cached locally | public domain under CC0, roughly six hundred thousand foods, and free with an API key | | Storage | IndexedDB for the log and the food cache | logging must work with no signal, and the food cache is what makes that possible | | Units | A unit conversion layer over gram-based nutrient data | every nutrient figure is per hundred grams, and every portion a person names is not | | Charts | Hand-drawn SVG | a handful of neutral trend charts, where a library would cost more than it saves | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **Node.js 22 or newer** · free - Why: Runs the build and the copy lint. - Get it: nodejs.org, or nvm install 22. - Verify: node --version prints v22 or higher - [ ] **A FoodData Central API key** · free; roughly one thousand requests per hour per address - Why: Without it there is no food data. The demo key exists but is rate limited far below anything usable. - Get it: Sign up at fdc.nal.usda.gov/api-key-signup, which issues a free data.gov key by email in a few minutes. No payment details are required. - Verify: A search request returns results rather than a rate limit error - [ ] **A phone you can install the application on** · free - Why: Logging happens away from a desk, and one-handed use and offline behaviour cannot be judged in a desktop browser. - Get it: Serve over HTTPS to your phone with a tunnelling tool and install to the home screen. - Verify: It installs to the home screen and opens without browser chrome - [ ] **Someone willing to read the interface strings** · free - Why: Phase 6 needs a second reader for tone. You cannot audit your own wording for pressure, and this subject matter makes that audit worth more than usual. - Get it: Ask one person, hand them the printed strings, and ask specifically whether anything reads as judgement or instruction. - [ ] **A decision about reference intakes** · free - Why: Showing population reference figures next to personal totals invites reading them as targets, which is exactly what this application refuses to do. - Get it: Decide before Phase 5 whether to show them at all. Omitting them is simpler and safer; showing them requires careful labelling and no comparative colouring. - Verify: The decision and its reasoning are written in the README ## Quick start ```sh curl 'https://api.nal.usda.gov/fdc/v1/foods/search?query=oats&api_key=YOUR_KEY' ``` Then copy `.env.example` to `.env` and fill in the values it documents. ## Honest limits This build deliberately does not replace: - A curated and deduplicated food database with normalised portions. - Barcode scanning against a commercial product catalogue. - Wearable and health platform integrations. - Any coaching, target setting or nutritional advice. - curated food database, barcode data, device integrations, reports, and mobile apps - wearable hardware data - coach network - clinical validation - large content library and social network If one of those is essential to you, that is the reason to keep paying for Cronometer Gold, and the README should say so rather than pretend.
# Build brief · Cronometer Gold The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me a nutrition log built on the public USDA food database, showing transparent totals without setting targets for you to replace Cronometer Gold. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Rule zero This is a record, not a diet product. It logs what you entered and shows the nutrient totals plainly. It must not diagnose, must not score a day as good or bad, must not set calorie or weight targets, and must not use streaks or warnings to pressure anyone. Nutrition tracking sits close to disordered eating for a meaningful number of people, and an application that adds judgement can do real harm. Phase 5 makes that promise mechanical with a copy lint that fails the build, rather than leaving it as a good intention that erodes over a year of small additions. ### Stack (fixed, do not substitute) - Runtime: TypeScript with Vite, built as an installable progressive web application · logging happens at a table with a phone in one hand, and an app store review is not worth it for one user - Food data: USDA FoodData Central, cached locally · public domain under CC0, roughly six hundred thousand foods, and free with an API key - Storage: IndexedDB for the log and the food cache · logging must work with no signal, and the food cache is what makes that possible - Units: A unit conversion layer over gram-based nutrient data · every nutrient figure is per hundred grams, and every portion a person names is not - Charts: Hand-drawn SVG · a handful of neutral trend charts, where a library would cost more than it saves ### Data model (create this before Phase 1) - foods(fdc_id, description, data_type, brand, serving_json, nutrients_json, cached_at) · cached from the API, keyed by its identifier - portions(id, fdc_id, label, gram_weight) · the named servings the database provides, plus any you add - entries(id, at, fdc_id, quantity, portion_id, grams, meal) · grams is always computed and stored, so a portion definition changing later does not rewrite history - recipes(id, name), recipe_items(recipe_id, fdc_id, grams) · a recipe is a food composed of other foods - nutrient_defs(id, name, unit, display_group) · so the interface can show a full panel rather than four headline numbers - Everything is stored in grams. Portions are an input convenience and a display preference, never the stored quantity. ### Phase 1 · Food data you can actually query Build: Get an API key and query FoodData Central. Cache every food you fetch, keyed by its identifier. Understand the data types before you build on them, because they differ in completeness and in how much you should trust them. Trap: The API allows roughly a thousand requests an hour per address, which a search-as-you-type box will exhaust in an afternoon. Debounce properly, cache every result, and search the local cache first. Trap: The data types are not equivalent. Foundation and SR Legacy entries carry full laboratory micronutrient panels; Branded entries carry only what a manufacturer put on the label, which is usually a dozen nutrients. Showing a branded food's empty micronutrient panel as though it were zero is wrong and misleading. Done when: A search returns foods with nutrient data. The same search offline returns the cached results. A branded food with no iron figure displays as unknown rather than as zero, and the interface says which data type each food came from. Do not build yet: No logging, no totals, no charts. ### Phase 2 · Logging with units that make sense Build: Log an entry by choosing a food, a portion and a quantity. Convert to grams using the portion weights the database supplies, and store the grams. Support meals, editing and deleting, and make repeating yesterday's entry one tap. Trap: Storing quantity and portion rather than grams means a corrected portion definition silently rewrites every historical entry that used it. Compute grams at entry time and store them; keep the portion only for display. Done when: Logging one cup of a food whose database portion says a cup is 240 grams stores 240 grams. Changing that portion's definition afterwards does not alter the stored grams of the existing entry. Deleting an entry removes it from the day's totals immediately. Do not build yet: No totals panel and no charts yet. ### Phase 3 · Totals that are honest about what they do not know Build: Sum nutrients across a day's entries, grouped into macronutrients, vitamins and minerals. Where a food is missing a nutrient, exclude it from that total and say how many foods contributed. A total silently computed from half the entries is worse than no total. Trap: Treating a missing nutrient as zero produces a day that appears deficient in something because one branded food did not print it on a label. Track how many of the day's foods actually carried each nutrient and show that count next to the figure. Done when: A day containing one branded food with no magnesium figure shows the magnesium total with a coverage note naming the number of contributing foods. Macronutrient totals match a hand calculation for a three item day. Switching a food from branded to a foundation equivalent changes the coverage note. Do not build yet: No trends and no recipes yet. ### Phase 4 · Recipes and repeated meals Build: A recipe is a named set of foods with gram weights, plus how many servings it makes. Logging a serving of a recipe expands to its constituent nutrients. Editing a recipe does not rewrite entries already logged from it. Trap: Expanding a recipe by reference at display time means editing the recipe next month changes what you ate last month. Snapshot the nutrient contribution at log time, exactly as with portions and grams. Done when: Logging one serving of a four ingredient recipe produces the same nutrient totals as logging the four ingredients at a quarter of their weights. Editing the recipe afterwards does not change the already logged entry. A recipe containing another recipe resolves correctly. Do not build yet: No trends yet. ### Phase 5 · Neutral trends, enforced by a test Build: Charts of nutrient intake over weeks, with gaps left as gaps. No targets, no goals, no grades, no streaks, no warnings, no body composition scoring. Add a copy lint with a banned word list that fails the build if judgement language reaches the interface. Trap: Reference intake figures are population averages for planning, not personal targets, and presenting one as a line labelled goal turns a reference into an instruction. If you show them at all, label them as population references and never colour a value red for falling below one. Done when: Trend charts render with unlogged days as gaps rather than zeroes. The copy lint fails when a banned word is deliberately added and passes on the shipped strings. No screen shows a target, a grade, a streak or a red value. Reference figures, where shown, are labelled as population references. Do not build yet: Export and sharing come next. ### Phase 6 · Export, and a review by someone else Build: Export the whole log as JSON and CSV, and import it back. Then have someone other than you read every interface string specifically for tone, and record what changed. If the application is shared with anyone, add authentication first. Trap: A food log is unusually sensitive personal data and, for some people, a record they would find distressing to see reflected back with any judgement attached. Treat both the data and the wording as things that need another person's eyes before release. Done when: Export, clear all data, import reproduces every entry and recipe exactly, verified by a checksum. A second person has reviewed the interface strings and their notes are recorded. Any shared deployment requires authentication. Do not build yet: Nothing after this. ### Out of scope (and why) - The curated food database. USDA data is excellent and public, but the curation, deduplication and portion normalisation behind a commercial database is real ongoing work that you inherit. - Barcode scanning against a commercial product catalogue, which is a licensed dataset rather than a feature. - Wearable and health platform integrations, which need hardware and platform permissions. - Any coaching, target setting or nutritional advice. That is a different product with a duty of care, and this one deliberately refuses it. ### README must contain - That the food data is USDA FoodData Central, public domain under CC0, and how to get a free key - That the API allows roughly a thousand requests an hour, and how caching keeps you under it - That data types differ, and that a missing nutrient is shown as unknown rather than zero - That everything is stored in grams and portions are a display convenience - That this application sets no targets and gives no advice, and that the copy lint enforces it
# Agent instructions · Cronometer Gold indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: TypeScript with Vite, built as an installable progressive web application, USDA FoodData Central, cached locally, IndexedDB for the log and the food cache, A unit conversion layer over gram-based nutrient data, Hand-drawn SVG. Do not substitute. - Work one phase at a time, in order. Do not start a phase until every "Done when" item of the previous one passes. - Prefer the fewest moving parts that satisfy the step. No frameworks, services or dependencies the plan does not name. - Secrets live in `.env`, never in source or logs. Keep `.env.example` current when a variable is introduced. - Do not invent cryptography, security guarantees, APIs or compliance claims. - Add a focused test for every destructive, security-sensitive or data-loss path the plan names. - Run the project checks before declaring a phase complete, and record any deliberate shortcut in the README under "Tradeoffs". ## Known traps - The API allows roughly a thousand requests an hour per address, which a search-as-you-type box will exhaust in an afternoon. Debounce properly, cache every result, and search the local cache first. - The data types are not equivalent. Foundation and SR Legacy entries carry full laboratory micronutrient panels; Branded entries carry only what a manufacturer put on the label, which is usually a dozen nutrients. Showing a branded food's empty micronutrient panel as though it were zero is wrong and misleading. - Storing quantity and portion rather than grams means a corrected portion definition silently rewrites every historical entry that used it. Compute grams at entry time and store them; keep the portion only for display. - Treating a missing nutrient as zero produces a day that appears deficient in something because one branded food did not print it on a label. Track how many of the day's foods actually carried each nutrient and show that count next to the figure. - Expanding a recipe by reference at display time means editing the recipe next month changes what you ate last month. Snapshot the nutrient contribution at log time, exactly as with portions and grams. - Reference intake figures are population averages for planning, not personal targets, and presenting one as a line labelled goal turns a reference into an instruction. If you show them at all, label them as population references and never colour a value red for falling below one.
# Build plan · Cronometer Gold A food log backed by USDA FoodData Central, which is public domain data: search foods, record portions with real units, see full micronutrient totals rather than only calories, view trends over time in neutral language, and export everything you have logged. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · Food data you can actually query A search returns real foods with real nutrient data, and it works the second time without a network. ### Steps 1. Get a key and read the data types Sign up at api.data.gov for a free key. Then fetch one food of each data type and look at what is actually in it before designing anything. ```sh curl 'https://api.nal.usda.gov/fdc/v1/foods/search?query=oats&api_key=YOUR_KEY' ``` 2. Cache aggressively and search locally first Store every fetched food. Search the local cache before the network, which is both faster and how you stay under the rate limit. Files: `src/food-cache.ts` 3. Distinguish missing from zero A nutrient absent from a record is unknown, not zero. This distinction runs through every total you will compute later, so get it right here. Files: `src/nutrients.ts` ### Done when - [ ] A search returns foods with nutrient data - [ ] The same search works offline from the cache - [ ] A missing nutrient displays as unknown rather than zero - [ ] The data type of each food is visible in the interface ### Watch out - The API allows roughly a thousand requests an hour per address, which a search-as-you-type box will exhaust in an afternoon. Debounce properly, cache every result, and search the local cache first. - The data types are not equivalent. Foundation and SR Legacy entries carry full laboratory micronutrient panels; Branded entries carry only what a manufacturer put on the label, which is usually a dozen nutrients. Showing a branded food's empty micronutrient panel as though it were zero is wrong and misleading. ## Phase 2 · Logging with units that make sense You can log a cup of something and the grams are right. ### Steps 1. Build the entry flow Search, pick a portion, set a quantity, choose a meal. Few taps, large targets, because this happens at a table. Files: `src/log.ts` 2. Convert and store grams Grams computed once at entry. The portion label is kept for display only. Files: `src/units.ts` 3. Add repeat and edit Repeating a previous entry is the most common action after the first week. Make it one tap. Files: `src/log.ts` ### Done when - [ ] A named portion converts to the database's gram weight - [ ] Changing a portion definition does not alter existing entries - [ ] Deleting an entry updates totals immediately - [ ] Repeating a previous entry takes one tap ### Watch out - Storing quantity and portion rather than grams means a corrected portion definition silently rewrites every historical entry that used it. Compute grams at entry time and store them; keep the portion only for display. ## Phase 3 · Totals that are honest about what they do not know The daily panel shows full micronutrients and is clear about coverage. ### Steps 1. Sum with coverage tracking For each nutrient, accumulate the value and count how many entries supplied one. Both numbers go to the interface. Files: `src/totals.ts` 2. Group nutrients for display Macronutrients, vitamins, minerals. A flat list of eighty numbers is not information. Files: `src/nutrients.ts` 3. Verify against a hand calculation Three foods, computed by hand, asserted in a test. This is the arithmetic everything else depends on. Files: `src/totals.test.ts` ### Done when - [ ] A missing nutrient reduces coverage rather than the total - [ ] Coverage is shown next to each nutrient figure - [ ] Macronutrient totals match a hand calculation for a three item day - [ ] Nutrients are grouped rather than listed flat ### Watch out - Treating a missing nutrient as zero produces a day that appears deficient in something because one branded food did not print it on a label. Track how many of the day's foods actually carried each nutrient and show that count next to the figure. ## Phase 4 · Recipes and repeated meals The meal you make every week is logged in one action. ### Steps 1. Build recipes as gram-weighted sets Ingredients in grams, plus a serving count. Grams throughout, exactly as with entries. Files: `src/recipes.ts` 2. Snapshot nutrients at log time Store the computed contribution with the entry. The recipe is a template, not a live reference. Files: `src/log.ts` 3. Handle nesting A recipe used as an ingredient. Resolve depth-first and guard against a recipe containing itself. Files: `src/recipes.ts` ### Done when - [ ] A recipe serving matches its ingredients logged individually - [ ] Editing a recipe does not alter existing entries - [ ] A nested recipe resolves correctly - [ ] A self-referencing recipe is rejected rather than looping ### Watch out - Expanding a recipe by reference at display time means editing the recipe next month changes what you ate last month. Snapshot the nutrient contribution at log time, exactly as with portions and grams. ## Phase 5 · Neutral trends, enforced by a test You can see change over time, and the application cannot start judging without a test failing. ### Steps 1. Draw trend charts in SVG Weekly averages per nutrient, gaps preserved. A day with no entries is missing data, not a zero intake day. Files: `src/charts.ts` 2. Write the copy lint Scan interface strings for judgement and diagnostic language: goal, target, over, under, exceeded, cheat, guilty, earned, burn off, good day, bad day, streak. Fail the build, not warn. Files: `scripts/copy-lint.ts`, `scripts/banned-words.json` 3. Handle reference intakes carefully or not at all If you show them, label them as population references and never colour by comparison. Omitting them entirely is a legitimate and safer choice. Files: `src/reference.ts` ### Done when - [ ] Unlogged days appear as gaps, not zeroes - [ ] The copy lint fails on a planted word and passes on the shipped strings - [ ] No screen shows a target, grade, streak or red value - [ ] Reference figures, if present, are labelled as population references ### Watch out - Reference intake figures are population averages for planning, not personal targets, and presenting one as a line labelled goal turns a reference into an instruction. If you show them at all, label them as population references and never colour a value red for falling below one. ## Not in this build - A curated and deduplicated food database with normalised portions. - Barcode scanning against a commercial product catalogue. - Wearable and health platform integrations. - Any coaching, target setting or nutritional advice. ## After v1, if you want it - Offline barcode lookup against the branded subset you have already cached - A weekly summary that reports coverage rather than performance - Importing a log exported from another tracker, which is mostly a mapping exercise
# Copy to .env and fill in. Never commit .env; this file documents it. # Required · secret. Issued free by fdc.nal.usda.gov/api-key-signup. Proxy requests through your own small endpoint if you deploy this anywhere public, so the key is not in the client bundle. VITE_FDC_API_KEY=replace-with-your-data-gov-key # Required. How many foods to keep cached locally. Higher means fewer API calls and more storage. VITE_FDC_CACHE_MAX=5000 # Required. Search-as-you-type debounce. Lower values burn through the hourly rate limit. VITE_SEARCH_DEBOUNCE_MS=400 # Required. Whether population reference figures appear at all. False is the safer default and requires no careful labelling. VITE_SHOW_REFERENCE_INTAKES=false # Required. How often to prompt for an export, because browser storage can be evicted. VITE_EXPORT_REMINDER_DAYS=30
# Cronometer Gold · product brief ## Problem The core loop is buildable, but a dependable replacement becomes a real weekend or multi-day project. For Cronometer Gold, log user-entered nutrition and show transparent nutrient totals without diagnosis. The hard boundary is curated food database, barcode data, device integrations, reports, and mobile apps, plus hardware data, content, coaching, and trust. ## Product outcome A nutrition log built on public data, honest about what it does not know, storing everything in grams so history never rewrites itself, showing trends without targets, and reviewed by another person for tone before anyone else sees it. ## Target user A builder who needs a maintainable product foundation, not a one-off demo. ## Required capabilities - iOS or Android device - optional HealthKit or Health Connect permission - local storage ## Explicit non-goals for v1 - A curated and deduplicated food database with normalised portions. - Barcode scanning against a commercial product catalogue. - Wearable and health platform integrations. - Any coaching, target setting or nutritional advice. - curated food database, barcode data, device integrations, reports, and mobile apps - wearable hardware data - coach network - clinical validation - large content library and social network ## Success criteria - The copy lint passes and is proved to fail on a planted word - A missing nutrient is confirmed to display as unknown rather than zero - Export and import round trip matches by checksum - A second person has reviewed the interface strings for tone - No screen shows a target, grade, streak or comparative colouring
# Build brief · Cronometer Gold The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me a nutrition log built on the public USDA food database, showing transparent totals without setting targets for you to replace Cronometer Gold. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Rule zero This is a record, not a diet product. It logs what you entered and shows the nutrient totals plainly. It must not diagnose, must not score a day as good or bad, must not set calorie or weight targets, and must not use streaks or warnings to pressure anyone. Nutrition tracking sits close to disordered eating for a meaningful number of people, and an application that adds judgement can do real harm. Phase 5 makes that promise mechanical with a copy lint that fails the build, rather than leaving it as a good intention that erodes over a year of small additions. ### Stack (fixed, do not substitute) - Runtime: TypeScript with Vite, built as an installable progressive web application · logging happens at a table with a phone in one hand, and an app store review is not worth it for one user - Food data: USDA FoodData Central, cached locally · public domain under CC0, roughly six hundred thousand foods, and free with an API key - Storage: IndexedDB for the log and the food cache · logging must work with no signal, and the food cache is what makes that possible - Units: A unit conversion layer over gram-based nutrient data · every nutrient figure is per hundred grams, and every portion a person names is not - Charts: Hand-drawn SVG · a handful of neutral trend charts, where a library would cost more than it saves ### Data model (create this before Phase 1) - foods(fdc_id, description, data_type, brand, serving_json, nutrients_json, cached_at) · cached from the API, keyed by its identifier - portions(id, fdc_id, label, gram_weight) · the named servings the database provides, plus any you add - entries(id, at, fdc_id, quantity, portion_id, grams, meal) · grams is always computed and stored, so a portion definition changing later does not rewrite history - recipes(id, name), recipe_items(recipe_id, fdc_id, grams) · a recipe is a food composed of other foods - nutrient_defs(id, name, unit, display_group) · so the interface can show a full panel rather than four headline numbers - Everything is stored in grams. Portions are an input convenience and a display preference, never the stored quantity. ### Phase 1 · Food data you can actually query Build: Get an API key and query FoodData Central. Cache every food you fetch, keyed by its identifier. Understand the data types before you build on them, because they differ in completeness and in how much you should trust them. Trap: The API allows roughly a thousand requests an hour per address, which a search-as-you-type box will exhaust in an afternoon. Debounce properly, cache every result, and search the local cache first. Trap: The data types are not equivalent. Foundation and SR Legacy entries carry full laboratory micronutrient panels; Branded entries carry only what a manufacturer put on the label, which is usually a dozen nutrients. Showing a branded food's empty micronutrient panel as though it were zero is wrong and misleading. Done when: A search returns foods with nutrient data. The same search offline returns the cached results. A branded food with no iron figure displays as unknown rather than as zero, and the interface says which data type each food came from. Do not build yet: No logging, no totals, no charts. ### Phase 2 · Logging with units that make sense Build: Log an entry by choosing a food, a portion and a quantity. Convert to grams using the portion weights the database supplies, and store the grams. Support meals, editing and deleting, and make repeating yesterday's entry one tap. Trap: Storing quantity and portion rather than grams means a corrected portion definition silently rewrites every historical entry that used it. Compute grams at entry time and store them; keep the portion only for display. Done when: Logging one cup of a food whose database portion says a cup is 240 grams stores 240 grams. Changing that portion's definition afterwards does not alter the stored grams of the existing entry. Deleting an entry removes it from the day's totals immediately. Do not build yet: No totals panel and no charts yet. ### Phase 3 · Totals that are honest about what they do not know Build: Sum nutrients across a day's entries, grouped into macronutrients, vitamins and minerals. Where a food is missing a nutrient, exclude it from that total and say how many foods contributed. A total silently computed from half the entries is worse than no total. Trap: Treating a missing nutrient as zero produces a day that appears deficient in something because one branded food did not print it on a label. Track how many of the day's foods actually carried each nutrient and show that count next to the figure. Done when: A day containing one branded food with no magnesium figure shows the magnesium total with a coverage note naming the number of contributing foods. Macronutrient totals match a hand calculation for a three item day. Switching a food from branded to a foundation equivalent changes the coverage note. Do not build yet: No trends and no recipes yet. ### Phase 4 · Recipes and repeated meals Build: A recipe is a named set of foods with gram weights, plus how many servings it makes. Logging a serving of a recipe expands to its constituent nutrients. Editing a recipe does not rewrite entries already logged from it. Trap: Expanding a recipe by reference at display time means editing the recipe next month changes what you ate last month. Snapshot the nutrient contribution at log time, exactly as with portions and grams. Done when: Logging one serving of a four ingredient recipe produces the same nutrient totals as logging the four ingredients at a quarter of their weights. Editing the recipe afterwards does not change the already logged entry. A recipe containing another recipe resolves correctly. Do not build yet: No trends yet. ### Phase 5 · Neutral trends, enforced by a test Build: Charts of nutrient intake over weeks, with gaps left as gaps. No targets, no goals, no grades, no streaks, no warnings, no body composition scoring. Add a copy lint with a banned word list that fails the build if judgement language reaches the interface. Trap: Reference intake figures are population averages for planning, not personal targets, and presenting one as a line labelled goal turns a reference into an instruction. If you show them at all, label them as population references and never colour a value red for falling below one. Done when: Trend charts render with unlogged days as gaps rather than zeroes. The copy lint fails when a banned word is deliberately added and passes on the shipped strings. No screen shows a target, a grade, a streak or a red value. Reference figures, where shown, are labelled as population references. Do not build yet: Export and sharing come next. ### Phase 6 · Export, and a review by someone else Build: Export the whole log as JSON and CSV, and import it back. Then have someone other than you read every interface string specifically for tone, and record what changed. If the application is shared with anyone, add authentication first. Trap: A food log is unusually sensitive personal data and, for some people, a record they would find distressing to see reflected back with any judgement attached. Treat both the data and the wording as things that need another person's eyes before release. Done when: Export, clear all data, import reproduces every entry and recipe exactly, verified by a checksum. A second person has reviewed the interface strings and their notes are recorded. Any shared deployment requires authentication. Do not build yet: Nothing after this. ### Out of scope (and why) - The curated food database. USDA data is excellent and public, but the curation, deduplication and portion normalisation behind a commercial database is real ongoing work that you inherit. - Barcode scanning against a commercial product catalogue, which is a licensed dataset rather than a feature. - Wearable and health platform integrations, which need hardware and platform permissions. - Any coaching, target setting or nutritional advice. That is a different product with a duty of care, and this one deliberately refuses it. ### README must contain - That the food data is USDA FoodData Central, public domain under CC0, and how to get a free key - That the API allows roughly a thousand requests an hour, and how caching keeps you under it - That data types differ, and that a missing nutrient is shown as unknown rather than zero - That everything is stored in grams and portions are a display convenience - That this application sets no targets and gives no advice, and that the copy lint enforces it
# Architecture · Cronometer Gold ## Stack | Part | Choice | Why | | --- | --- | --- | | Runtime | TypeScript with Vite, built as an installable progressive web application | logging happens at a table with a phone in one hand, and an app store review is not worth it for one user | | Food data | USDA FoodData Central, cached locally | public domain under CC0, roughly six hundred thousand foods, and free with an API key | | Storage | IndexedDB for the log and the food cache | logging must work with no signal, and the food cache is what makes that possible | | Units | A unit conversion layer over gram-based nutrient data | every nutrient figure is per hundred grams, and every portion a person names is not | | Charts | Hand-drawn SVG | a handful of neutral trend charts, where a library would cost more than it saves | ## Modules Each module has one owner concern and a documented way to replace it. | Module | Owns | How to replace it | | --- | --- | --- | | Food source | Fetching, caching and normalising USDA records. | Another food database changes the adapter. The missing-versus-zero distinction must survive any swap, because it is a correctness property rather than a data source detail. | | Units | Portion to gram conversion. | Self-contained and heavily testable. Grams as the stored unit is the invariant. | | Totals | Summation and coverage tracking. | Pure functions over entries. Coverage is the part that makes the numbers honest and must not be dropped for simplicity. | | Charts | Showing change without interpreting it. | Any renderer, provided gaps stay gaps and nothing is coloured by comparison to a reference. | | Copy lint | Keeping the tone promise mechanical. | The word list grows over time; the build failure on violation should never become a warning. | ## Configuration Every runtime setting is an environment variable documented in `.env.example`, validated at startup, with a safe local default wherever one exists. - `VITE_FDC_API_KEY` · required, secret · Issued free by fdc.nal.usda.gov/api-key-signup. Proxy requests through your own small endpoint if you deploy this anywhere public, so the key is not in the client bundle. - `VITE_FDC_CACHE_MAX` · required · How many foods to keep cached locally. Higher means fewer API calls and more storage. - `VITE_SEARCH_DEBOUNCE_MS` · required · Search-as-you-type debounce. Lower values burn through the hourly rate limit. - `VITE_SHOW_REFERENCE_INTAKES` · required · Whether population reference figures appear at all. False is the safer default and requires no careful labelling. - `VITE_EXPORT_REMINDER_DAYS` · required · How often to prompt for an export, because browser storage can be evicted. ## Production baseline - Security: least privilege, input validation at every boundary, secret redaction in logs, rate limits on abuse-prone paths, no invented security primitives. - Data: explicit schema and migrations, transactional writes where integrity matters, backup and restore procedures that have been exercised. - Integrations: adapters around third-party providers, idempotent webhook or job processing, bounded retries, timeouts. - Observability: structured logs with request or operation ids, an error-tracking hook, and health and readiness checks where a server exists. - Quality: unit tests for domain rules, integration tests at module boundaries, one end-to-end test of the critical path. ## Decision records For each dependency in the stack table, keep a short note: why it was chosen, its failure mode, and how it is replaced. Do not add infrastructure until a requirement in `PRODUCT.md` justifies it.
# Agent instructions · Cronometer Gold product build - Read `PRODUCT.md` and `ARCHITECTURE.md` before changing code. The stack is fixed: TypeScript with Vite, built as an installable progressive web application, USDA FoodData Central, cached locally, IndexedDB for the log and the food cache, A unit conversion layer over gram-based nutrient data, Hand-drawn SVG. - Implement milestone by milestone from `MILESTONES.md`; keep each change reviewable and leave the application runnable at every commit. - Treat authentication, payments, encryption, imports, webhooks and destructive actions as high-risk boundaries when present. - Never invent cryptography or silently weaken a requirement to make a check pass. - Put every external service behind an interface with a deterministic fake for tests. - Add migrations and rollback or recovery notes for every persistent data change. - Log useful operational context without credentials, tokens, passwords or personal data. - Update documentation and run every check before completing a milestone. ## Known traps - The API allows roughly a thousand requests an hour per address, which a search-as-you-type box will exhaust in an afternoon. Debounce properly, cache every result, and search the local cache first. - The data types are not equivalent. Foundation and SR Legacy entries carry full laboratory micronutrient panels; Branded entries carry only what a manufacturer put on the label, which is usually a dozen nutrients. Showing a branded food's empty micronutrient panel as though it were zero is wrong and misleading. - Storing quantity and portion rather than grams means a corrected portion definition silently rewrites every historical entry that used it. Compute grams at entry time and store them; keep the portion only for display. - Treating a missing nutrient as zero produces a day that appears deficient in something because one branded food did not print it on a label. Track how many of the day's foods actually carried each nutrient and show that count next to the figure. - Expanding a recipe by reference at display time means editing the recipe next month changes what you ate last month. Snapshot the nutrient contribution at log time, exactly as with portions and grams. - Reference intake figures are population averages for planning, not personal targets, and presenting one as a line labelled goal turns a reference into an instruction. If you show them at all, label them as population references and never colour a value red for falling below one. - A food log is unusually sensitive personal data and, for some people, a record they would find distressing to see reflected back with any judgement attached. Treat both the data and the wording as things that need another person's eyes before release.
# Delivery milestones · Cronometer Gold Estimated effort: **multi-day** for the indie phases; the production-only milestones add the trust and operability layer. ## M1 · Food data you can actually query A search returns real foods with real nutrient data, and it works the second time without a network. ### Steps 1. Get a key and read the data types Sign up at api.data.gov for a free key. Then fetch one food of each data type and look at what is actually in it before designing anything. ```sh curl 'https://api.nal.usda.gov/fdc/v1/foods/search?query=oats&api_key=YOUR_KEY' ``` 2. Cache aggressively and search locally first Store every fetched food. Search the local cache before the network, which is both faster and how you stay under the rate limit. Files: `src/food-cache.ts` 3. Distinguish missing from zero A nutrient absent from a record is unknown, not zero. This distinction runs through every total you will compute later, so get it right here. Files: `src/nutrients.ts` ### Done when - [ ] A search returns foods with nutrient data - [ ] The same search works offline from the cache - [ ] A missing nutrient displays as unknown rather than zero - [ ] The data type of each food is visible in the interface ### Watch out - The API allows roughly a thousand requests an hour per address, which a search-as-you-type box will exhaust in an afternoon. Debounce properly, cache every result, and search the local cache first. - The data types are not equivalent. Foundation and SR Legacy entries carry full laboratory micronutrient panels; Branded entries carry only what a manufacturer put on the label, which is usually a dozen nutrients. Showing a branded food's empty micronutrient panel as though it were zero is wrong and misleading. ## M2 · Logging with units that make sense You can log a cup of something and the grams are right. ### Steps 1. Build the entry flow Search, pick a portion, set a quantity, choose a meal. Few taps, large targets, because this happens at a table. Files: `src/log.ts` 2. Convert and store grams Grams computed once at entry. The portion label is kept for display only. Files: `src/units.ts` 3. Add repeat and edit Repeating a previous entry is the most common action after the first week. Make it one tap. Files: `src/log.ts` ### Done when - [ ] A named portion converts to the database's gram weight - [ ] Changing a portion definition does not alter existing entries - [ ] Deleting an entry updates totals immediately - [ ] Repeating a previous entry takes one tap ### Watch out - Storing quantity and portion rather than grams means a corrected portion definition silently rewrites every historical entry that used it. Compute grams at entry time and store them; keep the portion only for display. ## M3 · Totals that are honest about what they do not know The daily panel shows full micronutrients and is clear about coverage. ### Steps 1. Sum with coverage tracking For each nutrient, accumulate the value and count how many entries supplied one. Both numbers go to the interface. Files: `src/totals.ts` 2. Group nutrients for display Macronutrients, vitamins, minerals. A flat list of eighty numbers is not information. Files: `src/nutrients.ts` 3. Verify against a hand calculation Three foods, computed by hand, asserted in a test. This is the arithmetic everything else depends on. Files: `src/totals.test.ts` ### Done when - [ ] A missing nutrient reduces coverage rather than the total - [ ] Coverage is shown next to each nutrient figure - [ ] Macronutrient totals match a hand calculation for a three item day - [ ] Nutrients are grouped rather than listed flat ### Watch out - Treating a missing nutrient as zero produces a day that appears deficient in something because one branded food did not print it on a label. Track how many of the day's foods actually carried each nutrient and show that count next to the figure. ## M4 · Recipes and repeated meals The meal you make every week is logged in one action. ### Steps 1. Build recipes as gram-weighted sets Ingredients in grams, plus a serving count. Grams throughout, exactly as with entries. Files: `src/recipes.ts` 2. Snapshot nutrients at log time Store the computed contribution with the entry. The recipe is a template, not a live reference. Files: `src/log.ts` 3. Handle nesting A recipe used as an ingredient. Resolve depth-first and guard against a recipe containing itself. Files: `src/recipes.ts` ### Done when - [ ] A recipe serving matches its ingredients logged individually - [ ] Editing a recipe does not alter existing entries - [ ] A nested recipe resolves correctly - [ ] A self-referencing recipe is rejected rather than looping ### Watch out - Expanding a recipe by reference at display time means editing the recipe next month changes what you ate last month. Snapshot the nutrient contribution at log time, exactly as with portions and grams. ## M5 · Neutral trends, enforced by a test You can see change over time, and the application cannot start judging without a test failing. ### Steps 1. Draw trend charts in SVG Weekly averages per nutrient, gaps preserved. A day with no entries is missing data, not a zero intake day. Files: `src/charts.ts` 2. Write the copy lint Scan interface strings for judgement and diagnostic language: goal, target, over, under, exceeded, cheat, guilty, earned, burn off, good day, bad day, streak. Fail the build, not warn. Files: `scripts/copy-lint.ts`, `scripts/banned-words.json` 3. Handle reference intakes carefully or not at all If you show them, label them as population references and never colour by comparison. Omitting them entirely is a legitimate and safer choice. Files: `src/reference.ts` ### Done when - [ ] Unlogged days appear as gaps, not zeroes - [ ] The copy lint fails on a planted word and passes on the shipped strings - [ ] No screen shows a target, grade, streak or red value - [ ] Reference figures, if present, are labelled as population references ### Watch out - Reference intake figures are population averages for planning, not personal targets, and presenting one as a line labelled goal turns a reference into an instruction. If you show them at all, label them as population references and never colour a value red for falling below one. ## M6 · Export, and a review by someone else (production only) Your log is portable, and a second person has read what the application says. ### Steps 1. Export losslessly and import back JSON with a schema version for completeness, CSV for spreadsheets. Round-trip equality is the test worth having. Files: `src/export.ts`, `src/import.ts` 2. Run a tone review with someone else Print every string. Ask specifically whether anything reads as judgement or instruction. Record what you changed. Files: `docs/tone-review.md` 3. Authenticate anything shared If it leaves your own device, put a login in front of it. This data should not be reachable by a URL guess. Files: `src/auth.ts` ### Done when - [ ] Export, clear, import reproduces everything exactly by checksum - [ ] A second reader's tone notes are recorded and acted on - [ ] Shared deployments require authentication - [ ] The CSV contains readable food names and explicit units ### Watch out - A food log is unusually sensitive personal data and, for some people, a record they would find distressing to see reflected back with any judgement attached. Treat both the data and the wording as things that need another person's eyes before release.
# Operations · Cronometer Gold ## Backup The user's export file is the backup. Browser storage can be evicted without warning, so prompt for export on a schedule and make it prominent. ## Restore Import the JSON export into an empty installation. The round-trip checksum test is what makes this trustworthy. Do a restore drill before the first real user, and write the date here when it passes. ## Monitoring If the key is proxied through your own endpoint, watch the request rate against the hourly limit and the cache hit rate. A falling hit rate means the cache is being evicted and the limit is about to be hit. ## Incident checklist Rate limit exhaustion is the realistic incident, and it presents as search returning nothing. Fail with a message naming the limit and the reset window rather than an empty result, which reads as the food not existing. 1. Contain the issue without destroying evidence or user data. 2. Record the timeline and affected scope. 3. Rotate exposed secrets and revoke compromised sessions or credentials. 4. Restore from a verified backup when needed. 5. Document the root cause, the remediation and the regression test. ## Release gate - [ ] The copy lint passes and is proved to fail on a planted word - [ ] A missing nutrient is confirmed to display as unknown rather than zero - [ ] Export and import round trip matches by checksum - [ ] A second person has reviewed the interface strings for tone - [ ] No screen shows a target, grade, streak or comparative colouring ## Launch constraint Do not market omitted Cronometer Gold capabilities as implemented. The non-goals in `PRODUCT.md` remain user-visible limitations until they are deliberately delivered.
# Copy to .env and fill in. Never commit .env; this file documents it. # Required · secret. Issued free by fdc.nal.usda.gov/api-key-signup. Proxy requests through your own small endpoint if you deploy this anywhere public, so the key is not in the client bundle. VITE_FDC_API_KEY=replace-with-your-data-gov-key # Required. How many foods to keep cached locally. Higher means fewer API calls and more storage. VITE_FDC_CACHE_MAX=5000 # Required. Search-as-you-type debounce. Lower values burn through the hourly rate limit. VITE_SEARCH_DEBOUNCE_MS=400 # Required. Whether population reference figures appear at all. False is the safer default and requires no careful labelling. VITE_SHOW_REFERENCE_INTAKES=false # Required. How often to prompt for an export, because browser storage can be evicted. VITE_EXPORT_REMINDER_DAYS=30
$ choose a build depth, inspect the files, then open the complete pack in your agent
People still pay for Cronometer Gold because people pay for polished mobile capture, trusted content, hardware integration, and coaching rather than the basic logbook. The recurring cost buys sensor permissions, data quality, notifications, safety language, accessibility, sync, privacy, content review, and support, not just the visible interface.
xcurated food database, barcode data, device integrations, reports, and mobile apps
xwearable hardware data
xcoach network
xclinical validation
xlarge content library and social network
Don't feel like building it? These folks already made it free.
all 4 free alternatives to Cronometer Gold →· no votes, no pay-to-list · just what's real
Cronometer Gold pricing
| plan | monthly | annual (per mo) | what you get |
|---|---|---|---|
| basic | $0/user | $0/user | Track foods, exercise, biometrics, and notes; up to 84 nutrients; charts/reports limited to a 7-day window |
| gold | $10.99/user | $5/user | Up to 95 nutrients, long-range charts/reports, fasting timer, recipe importer, and no ads |
| pro | $39.99/workspace | — | 10 client seats included; professional dashboard and client management; 30-day trial |
free tierBasic plan: up to 84 tracked nutrients and a 7-day chart/report window; no published food-diary entry cap.
billingGold monthly + annual; Pro monthly with a 30-day trial; subscriptions auto-renew and are paid in advance
hidden costsCronometer Pro includes only 10 client seats; each additional client costs $2.50/month.
verified 2026-08-14 · source ↗
Vibecode Cronometer Gold
Kinda. The core of Cronometer Gold is buildable in a weekend with the prompt on this page, but there are real gaps: curated food database, barcode data, device integrations, reports, and mobile apps, wearable hardware data. Read the honest list above before committing.
How much does Cronometer Gold cost?
Cronometer Gold costs about $10.99/month (Gold, checked 2026-08-14), which is $131.88 per year.
What do I lose by replacing Cronometer Gold?
Honestly: curated food database, barcode data, device integrations, reports, and mobile apps; wearable hardware data; coach network; clinical validation; large content library and social network. If any of those are load-bearing for you, keep paying.
Is there an open-source alternative to Cronometer Gold?
Yes: wger (Meals, workouts and trend charts in one open-source bunker; polish is not the main feature.) OpenNutriTracker (Calories, macros and micronutrients stay on your phone; the app declines to become your diet coach.) Waistline (A plain Android calorie diary that keeps its data local and its opinions to itself.) All 4 curated free alternatives are at vibecodeit.com/cronometer-gold/alternatives. The prompt is for when you want it exactly your way.