fix: disable bundled_units so quantities keep their authored units - #433
Merged
Conversation
The cooklang bundled_units feature loads the full unit database into Converter::default(), which silently converts quantities to a "best" unit (5/8 cup -> 10 tbsp) and approximates fractions lossily (1 5/8 -> 1 2/3, a ~2.6% error shown as if exact). It also made the terminal and web UI disagree on the same recipe. No dependency leaks it anymore: cooklang-reports and cooklang-language-server both declare default-features = false. The feature was only enabled by our own explicit declaration, based on a stale comment about past feature unification. With the feature off, Converter::default() is empty, so quantities stay exactly as authored in every output format, and both UIs agree. Snapshot changes beyond units: `scalable` flips false -> true because the conversion path rebuilt quantities via Quantity::new (which hardcodes scalable: false), silently dropping the parser's scalable flag; and the scaled-recipe snapshot loses a floating-point error term left over from the lossy 3 tsp -> 1 tbsp round-trip. Addresses the unit-conversion and consistency parts of #432.
ReviewSmall, well-scoped change with a clear root-cause writeup. A few notes: Code quality
Behavior / potential issue worth flagging
Test coverage
Security / performance
Overall this looks correct and matches the stated goal. The shopping-list aggregation implication is the one thing I'd want explicitly confirmed as intended before merging. |
This was referenced Aug 13, 2026
dubadub
added a commit
that referenced
this pull request
Aug 13, 2026
`cooklang::quantity::GroupedQuantity` keeps the quantities it could not add
together in a `HashMap` keyed by unit name, and Rust randomises `HashMap`
iteration per process. Anything rendering a group in its own order therefore
printed a different order on every run:
flour 1 cup, 100 g
flour 100 g, 1 cup
This only became visible with #433, which removed the `bundled_units` feature
so quantities keep their authored units: with no unit database no unit is
"known", so every component lands in that map and any ingredient measured two
ways came out in random order. This is the fourth time `HashMap` iteration
order has reached user-visible output here (see #427).
`format::quantity::ordered_components` fixes one rule — components are ordered
by unit name, the unitless one first, components sharing a unit keeping the
order they were added — and every site that renders or serialises a group now
goes through it: the shopping list's human table, Markdown, JSON and YAML
writers and `AggregatedList`'s own rendered strings, the recipe human, Markdown,
LaTeX, Typst and schema.org formatters, and the web UI's ingredient lists and
the `grouped_ingredients` array of the recipe API. The order the units were
written in is not recoverable — the map has already lost it — so unit name is
what is left that is deterministic, identical on every platform, and short
enough to document in a sentence.
`crates/core/Cargo.toml` drops `bundled_units` here too, which is what surfaced
the bug: enabling it in core would switch it back on for `cookcli` through
feature unification and undo #433. The core tests it invalidates are updated
with it — `ml` and `l` no longer add up, `1 c` is now `1 cup`, and `scale(1.0)`
has no database left to re-fit against.
dubadub
added a commit
that referenced
this pull request
Aug 13, 2026
…dled_units These ten were recorded while `cooklang`'s unit database was compiled in. With it gone (#433) quantities keep the units they were authored in, so the recorded output legitimately changes: 1500 ml -> 1 l, 500 ml ml and l no longer add up 1 c -> 1 cup no database to abbreviate against 1.5 l -> 1500 ml scaling no longer re-fits 1 tbsp -> 3 tsp likewise Re-recorded only after the preceding commit made the output stable: the generated snapshots were checked byte-identical across six consecutive runs first, because without that fix the components of `1 l, 500 ml` and `1 cup, 200 g` come out of a `HashMap` and five to eight of these fourteen tests fail at random on any given run. Accepted with `INSTA_UPDATE=always cargo test --test shopping_list_characterization_test`, scoped to this one target so no other golden file could be rewritten; #433's own recipe snapshots are untouched.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Disables the cooklang
bundled_unitsfeature so quantities are displayed exactly as authored, in the units the recipe author wrote.bundled_unitsloads the full unit database intoConverter::default(), which:5/8 cup→10 tbsp),1 5/8→1 2/3, a ~2.6% error presented as exact),1.625 c, terminal1 2/3 c).Root cause
No dependency leaks the feature anymore —
cooklang-reportsandcooklang-language-serverboth declarecooklangwithdefault-features = false. It was enabled solely by our own explicit declaration inCargo.toml, guarded by a stale comment about past feature unification. The comment now documents why the feature must stay off and how to find a future leak (cargo tree -e features -i cooklang).Behavior after
With the feature off,
Converter::default()falls back toConverter::empty()(no code changes needed):Authored units preserved, exact values, full unit names, and terminal/web UI now agree.
Snapshot changes worth noting
scalableflipsfalse→true: the old conversion path rebuilt quantities viaQuantity::new, which hardcodesscalable: false, silently dropping the parser's scalable flag. The new value is the correct one.err: -6.76e-8) left over from the lossy3 tsp→1 tbspround-trip.Addresses the silent-conversion and web/terminal-consistency parts of #432. The remaining part (opt-in fraction display, e.g.
1 5/8 cups) stays open there.Checks
cargo fmt --check,cargo clippy, and the full test suite pass.