A new Recipes page at /recipes lets a cook paste a plain-text… #5

Merged
cmoriarty merged 3 commits from osf/69-172-comparison-arm-groups-scale-recipes into compare/groups 2026-10-17 06:09:52 -04:00
Owner

What was done

  • A new Recipes page at /recipes lets a cook paste a plain-text recipe, choose the number of servings and Metric or US, and get the recipe back scaled and converted.
  • A pasted recipe is read one ingredient a line — a quantity (whole number, decimal, fraction or mixed number), an optional unit and a name — and a line that does not read is reported beside its line number with the reason while the other ingredients are still shown.
  • Every amount is scaled to the requested servings (an empty Servings field keeps the recipe's own) and shown in the chosen unit system, each in the unit of its kind that keeps the number readable.
  • "Add to list" builds a name-sorted shopping list from up to five recipes, adding up the same ingredient in the same unit and listing the same ingredient once per different unit, and the list travels in hidden form fields rather than the database so a fresh visit starts empty.
  • The reader, scaler, converter and shopping list each have their own unit tests, alongside page and route tests and a Playwright browser test that drives the whole flow in a real browser.
  • The README documents the page and the CHANGELOG names the change under Unreleased so the release-notes check passes.
  • The change passed the unit tests and the project's CI, and a judge served the app and drove /recipes as a person would — a fresh page, a recipe with an unread line, add-to-list, and an invalid-servings entry.
  • Not done: a weight is never converted into a volume (a cup of flour is not shown in grams) because that needs a density per ingredient.

How to review

  1. Check out the work branch; it is diffed against compare/groups.
  2. Run the unit tests with npm test after npm install; every suite passes, including the new reader, scaling, conversion, shopping-list, page and route tests.
  3. Run the page's browser test — npx playwright install chromium once, then npm run test:e2e — which starts the server and drives /recipes, asserting 14 oz flour, 4 eggs, the Line 3 unread report, and the 6 eggs / 14 oz flour list (both this and npm start want the Postgres the README's Run section describes, set on DATABASE_URL).
  4. For a click-through, start the server with npm start and open http://localhost:3000/recipes: paste Serves 2, 200 g flour, a pinch of salt, 2 eggs, enter 4, choose US and press Show recipe to see 14 oz flour, 4 eggs and Line 3: a pinch of salt (no quantity), then press Add to list to see the list.
  5. Read src/recipe.ts first (the shared types and the reader), then the /recipes routes in src/server.ts, which sit before the /<code> branch so the page is not answered as a short link.
  6. Skim the README's "Recipes" section and the CHANGELOG.md entry under ## Unreleased.

Code review

Reviewed the four new modules (reading, scaling, converting, shopping list), the /recipes page and routes, and all their tests against the change's five spec deltas. The implementation matches every pinned scenario: all 47 scenarios are covered by unit, page, server and e2e tests; 86 narrowed vitest tests (including the pre-existing server route tests), tsc --noEmit, openspec validate --strict, scripts/ci/release-notes.sh and playwright test --list all pass. No generated, secret or scratch files are carried by the commit (node_modules/, test-results/ and playwright-report/ are already ignored). I fixed two minor CSS issues in the new page (a dead .problem rule and radio inputs inheriting the text-input border/padding) and recorded one edge case left as designed: a quantity below 5e-7 is written to a hidden list field as 0 and its line is dropped on the next round trip, which is the literal result of design.md's six-decimals rule and is not pinned by any spec scenario.

Left for you

  • minor, src/server.ts:32: ingredientLine writes the quantity to six decimals with trailing zeros dropped; a positive quantity below 5e-7 (e.g. a pasted 0.0000001 ml, which displays as 0.1) becomes 0.000000 and thus 0 in the hidden list field, so parseIngredientLine drops the line (no quantity) on the next submission and the ingredient silently leaves the shopping list. This is the literal result of design.md's six-decimals rule and no spec scenario pins it, so left as designed.

Fixed before the commit

  • minor, src/pages.ts:41: Dead CSS rule .problem { color: #b00020; } added by this change: the lines-that-did-not-read list uses class="problems" (and messages use class="error"), so the rule never matched. Removed it.
  • minor, src/pages.ts:39: The Metric/US radio inputs inherited the generic input rule (1px border, padding), rendering each radio inside a small bordered box. Extended the change's own .choice input rule to flex: none; padding: 0; border: 0;.

Tests

All 47 scenarios of scale-recipes-and-shopping-list have a tagged test.

The test lint found, in the run's tests on the last check:

Recorded, not decided

  • e2e/recipes.spec.ts:1 B7: no relative import: this file asserts on things it constructed itself, which is the measured 'asserted on my own mock' failure
  • test/recipes-page.test.ts:1 B7: nothing imported here looks like the module under test ('recipes-page')
  • test/recipes-server.test.ts:1 B7: nothing imported here looks like the module under test ('recipes-server')

Braid run

  • Workflow: Braid's OpenSpec flow, change scale-recipes-and-shopping-list (23 steps)
  • Issue: cmoriarty/braid-combined-test#4
  • Model: vLLM · qwen3.8-27b
  • Time: 1 h 12 m from start to this pull request, 1 h 9 m of it in the model
  • Tokens: 7.9M input, 47k output, 156k reasoning, over 175 model calls
  • Pre-flight estimate: fits, p50 193k / p80 298k tokens against a 394k-token step allowance
  • CI: passed its 6 jobs on 602203c039
  • Run: run_01M54H8M577MXQ87ARQVWW46BP

Opened by Braid.

## What was done - A new Recipes page at `/recipes` lets a cook paste a plain-text recipe, choose the number of servings and Metric or US, and get the recipe back scaled and converted. - A pasted recipe is read one ingredient a line — a quantity (whole number, decimal, fraction or mixed number), an optional unit and a name — and a line that does not read is reported beside its line number with the reason while the other ingredients are still shown. - Every amount is scaled to the requested servings (an empty Servings field keeps the recipe's own) and shown in the chosen unit system, each in the unit of its kind that keeps the number readable. - "Add to list" builds a name-sorted shopping list from up to five recipes, adding up the same ingredient in the same unit and listing the same ingredient once per different unit, and the list travels in hidden form fields rather than the database so a fresh visit starts empty. - The reader, scaler, converter and shopping list each have their own unit tests, alongside page and route tests and a Playwright browser test that drives the whole flow in a real browser. - The README documents the page and the CHANGELOG names the change under Unreleased so the release-notes check passes. - The change passed the unit tests and the project's CI, and a judge served the app and drove `/recipes` as a person would — a fresh page, a recipe with an unread line, add-to-list, and an invalid-servings entry. - **Not done:** a weight is never converted into a volume (a cup of flour is not shown in grams) because that needs a density per ingredient. ## How to review 1. Check out the work branch; it is diffed against `compare/groups`. 2. Run the unit tests with `npm test` after `npm install`; every suite passes, including the new reader, scaling, conversion, shopping-list, page and route tests. 3. Run the page's browser test — `npx playwright install chromium` once, then `npm run test:e2e` — which starts the server and drives `/recipes`, asserting `14 oz flour`, `4 eggs`, the `Line 3` unread report, and the `6 eggs` / `14 oz flour` list (both this and `npm start` want the Postgres the README's Run section describes, set on `DATABASE_URL`). 4. For a click-through, start the server with `npm start` and open http://localhost:3000/recipes: paste `Serves 2`, `200 g flour`, `a pinch of salt`, `2 eggs`, enter 4, choose US and press Show recipe to see `14 oz flour`, `4 eggs` and `Line 3: a pinch of salt (no quantity)`, then press Add to list to see the list. 5. Read `src/recipe.ts` first (the shared types and the reader), then the `/recipes` routes in `src/server.ts`, which sit before the `/<code>` branch so the page is not answered as a short link. 6. Skim the README's "Recipes" section and the `CHANGELOG.md` entry under `## Unreleased`. ## Code review Reviewed the four new modules (reading, scaling, converting, shopping list), the /recipes page and routes, and all their tests against the change's five spec deltas. The implementation matches every pinned scenario: all 47 scenarios are covered by unit, page, server and e2e tests; 86 narrowed vitest tests (including the pre-existing server route tests), tsc --noEmit, openspec validate --strict, scripts/ci/release-notes.sh and playwright test --list all pass. No generated, secret or scratch files are carried by the commit (node_modules/, test-results/ and playwright-report/ are already ignored). I fixed two minor CSS issues in the new page (a dead .problem rule and radio inputs inheriting the text-input border/padding) and recorded one edge case left as designed: a quantity below 5e-7 is written to a hidden list field as 0 and its line is dropped on the next round trip, which is the literal result of design.md's six-decimals rule and is not pinned by any spec scenario. **Left for you** - minor, `src/server.ts:32`: ingredientLine writes the quantity to six decimals with trailing zeros dropped; a positive quantity below 5e-7 (e.g. a pasted 0.0000001 ml, which displays as 0.1) becomes 0.000000 and thus 0 in the hidden list field, so parseIngredientLine drops the line (no quantity) on the next submission and the ingredient silently leaves the shopping list. This is the literal result of design.md's six-decimals rule and no spec scenario pins it, so left as designed. **Fixed before the commit** - minor, `src/pages.ts:41`: Dead CSS rule `.problem { color: #b00020; }` added by this change: the lines-that-did-not-read list uses class="problems" (and messages use class="error"), so the rule never matched. Removed it. - minor, `src/pages.ts:39`: The Metric/US radio inputs inherited the generic `input` rule (1px border, padding), rendering each radio inside a small bordered box. Extended the change's own `.choice input` rule to `flex: none; padding: 0; border: 0;`. ## Tests All 47 scenarios of `scale-recipes-and-shopping-list` have a tagged test. The test lint found, in the run's tests on the last check: **Recorded, not decided** - `e2e/recipes.spec.ts:1` B7: no relative import: this file asserts on things it constructed itself, which is the measured 'asserted on my own mock' failure - `test/recipes-page.test.ts:1` B7: nothing imported here looks like the module under test ('recipes-page') - `test/recipes-server.test.ts:1` B7: nothing imported here looks like the module under test ('recipes-server') ## Braid run - **Workflow:** Braid's OpenSpec flow, change `scale-recipes-and-shopping-list` (23 steps) - **Issue:** cmoriarty/braid-combined-test#4 - **Model:** vLLM · qwen3.8-27b - **Time:** 1 h 12 m from start to this pull request, 1 h 9 m of it in the model - **Tokens:** 7.9M input, 47k output, 156k reasoning, over 175 model calls - **Pre-flight estimate:** fits, p50 193k / p80 298k tokens against a 394k-token step allowance - **CI:** passed its 6 jobs on `602203c039` - **Run:** [run_01M54H8M577MXQ87ARQVWW46BP](http://openspecflow.internal.underthere.xyz/run/run_01M54H8M577MXQ87ARQVWW46BP) _Opened by Braid._
check: the code of fix-loop round 1, for the project's CI
All checks were successful
CI / Unit tests (push) Successful in 51s
CI / Release notes (pull_request) Successful in 10s
CI / Unit tests (pull_request) Successful in 57s
CI / Browser tests (pull_request) Successful in 1m1s
CI / Release notes (push) Successful in 9s
CI / Browser tests (push) Successful in 1m23s
602203c039
cmoriarty changed title from Scale a recipe, convert its units, and build a shopping list to A new Recipes page at /recipes lets a cook paste a plain-text… 2026-10-17 06:09:30 -04:00
cmoriarty force-pushed osf/69-172-comparison-arm-groups-scale-recipes from 602203c039
All checks were successful
CI / Unit tests (push) Successful in 51s
CI / Release notes (pull_request) Successful in 10s
CI / Unit tests (pull_request) Successful in 57s
CI / Browser tests (pull_request) Successful in 1m1s
CI / Release notes (push) Successful in 9s
CI / Browser tests (push) Successful in 1m23s
to 4f0f5de55f
All checks were successful
CI / Release notes (push) Successful in 10s
CI / Unit tests (push) Successful in 54s
CI / Browser tests (push) Successful in 1m0s
CI / Release notes (pull_request) Successful in 11s
CI / Browser tests (pull_request) Successful in 1m21s
CI / Unit tests (pull_request) Successful in 54s
2026-10-17 06:09:30 -04:00
Compare
cmoriarty merged commit 1546fba65f into compare/groups 2026-10-17 06:09:52 -04:00
cmoriarty deleted branch osf/69-172-comparison-arm-groups-scale-recipes 2026-10-17 06:09:53 -04:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
cmoriarty/braid-combined-test!5
No description provided.