v0.1.29 - Mix Editor % edits no longer rebalance other ingredients
Editing one ingredient's % now converts only that row to kg against the Total mix anchor; other ingredients are left untouched (no proportional redistribution). Removing a row likewise leaves the rest as-is. The hard "percentages must total 100%" save guard is relaxed (kg is canonical and the backend does not require 100%); the % chip remains as live feedback. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "hunter-app",
|
||||
"version": "0.1.28",
|
||||
"version": "0.1.29",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "hunter-app",
|
||||
"version": "0.1.28",
|
||||
"version": "0.1.29",
|
||||
"dependencies": {
|
||||
"@fontsource/inter": "^5.2.8",
|
||||
"lucide-svelte": "^1.0.1"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "hunter-app",
|
||||
"version": "0.1.28",
|
||||
"version": "0.1.29",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
@@ -103,57 +103,24 @@
|
||||
ingredientBaseline = ingredientDrafts.map((row) => ({ ...row }));
|
||||
}
|
||||
|
||||
// kg overrides %: recompute the reference total from the kg column, then
|
||||
// re-derive every row's percentage so they always sum to 100.
|
||||
function applyKgEdit() {
|
||||
const total = ingredientDrafts.reduce((sum, row) => sum + Number(row.quantity_kg || 0), 0);
|
||||
totalReference = round4(total);
|
||||
ingredientDrafts = ingredientDrafts.map((row) => ({
|
||||
...row,
|
||||
percentage: total > 0 ? round4((Number(row.quantity_kg || 0) / total) * 100) : 0
|
||||
}));
|
||||
}
|
||||
|
||||
// Editing a row's % sets it to that share of the mix and spreads the rest
|
||||
// across the other rows in their existing proportions, so the column always
|
||||
// re-totals 100 and kg stays the canonical value. The total mix kg is held
|
||||
// constant; only the split moves. Without this rebalance a single % edit
|
||||
// would leave the total off 100 and the save guard would block the change.
|
||||
// Editing a row's % converts only that row to kilograms against the Total mix
|
||||
// anchor. Every other ingredient is left exactly as it is — changing one
|
||||
// ingredient's share never rebalances or recalculates the rest of the recipe.
|
||||
// Percentages are free to sum to anything; the chip reports the running total
|
||||
// and kg stays the canonical saved value.
|
||||
function applyPercentEdit(index: number) {
|
||||
const total = Number(totalReference || 0);
|
||||
const rows = ingredientDrafts;
|
||||
const count = rows.length;
|
||||
|
||||
// Clamp to a sane 0–100 share.
|
||||
let target = Number(rows[index].percentage || 0);
|
||||
// Clamp to a sane 0–100 share for the edited row only.
|
||||
let target = Number(ingredientDrafts[index].percentage || 0);
|
||||
if (!Number.isFinite(target) || target < 0) target = 0;
|
||||
if (target > 100) target = 100;
|
||||
|
||||
// A single ingredient is always the whole mix.
|
||||
if (count === 1) {
|
||||
ingredientDrafts = rows.map((row) => ({
|
||||
...row,
|
||||
percentage: 100,
|
||||
quantity_kg: round4(total)
|
||||
}));
|
||||
return;
|
||||
}
|
||||
|
||||
const remaining = 100 - target;
|
||||
const otherOldSum = rows.reduce(
|
||||
(sum, row, rowIndex) => (rowIndex === index ? sum : sum + Number(row.percentage || 0)),
|
||||
0
|
||||
ingredientDrafts = ingredientDrafts.map((row, rowIndex) =>
|
||||
rowIndex === index
|
||||
? { ...row, percentage: round4(target), quantity_kg: round4((target / 100) * total) }
|
||||
: row
|
||||
);
|
||||
|
||||
ingredientDrafts = rows.map((row, rowIndex) => {
|
||||
if (rowIndex === index) {
|
||||
return { ...row, percentage: round4(target), quantity_kg: round4((target / 100) * total) };
|
||||
}
|
||||
// Keep the other rows' relative split; if they were all at 0, share evenly.
|
||||
const share =
|
||||
otherOldSum > 0 ? (Number(row.percentage || 0) / otherOldSum) * remaining : remaining / (count - 1);
|
||||
return { ...row, percentage: round4(share), quantity_kg: round4((share / 100) * total) };
|
||||
});
|
||||
}
|
||||
|
||||
// Editing the Total mix (kg) rescales every row's kg from its current %.
|
||||
@@ -293,9 +260,10 @@
|
||||
}
|
||||
|
||||
function removeIngredient(index: number) {
|
||||
// Drop the row and leave the remaining ingredients' % and kg exactly as they
|
||||
// are — removing one ingredient never recalculates the others.
|
||||
ingredientDrafts = ingredientDrafts.filter((_, rowIndex) => rowIndex !== index);
|
||||
if (!ingredientDrafts.length) ingredientDrafts = [emptyIngredient()];
|
||||
applyKgEdit();
|
||||
}
|
||||
|
||||
function ingredientWarnings() {
|
||||
@@ -312,11 +280,6 @@
|
||||
if (Number(row.quantity_kg) <= 0) return [`Ingredient row ${index + 1} needs a quantity greater than zero.`];
|
||||
}
|
||||
|
||||
// Percentages must add up to 100 before a change can be saved.
|
||||
if (Math.abs(percentTotal - 100) > 0.1) {
|
||||
return [`Percentages must total 100% (currently ${percentTotal.toFixed(2)}%).`];
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
@@ -687,7 +650,7 @@
|
||||
<div class="ingredient-footer">
|
||||
<span class="footer-total">Total {ingredientTotalKg.toFixed(2)} kg</span>
|
||||
<button class="clear-button" type="button" onclick={addIngredient}>Add ingredient</button>
|
||||
<button class="apply-button" type="button" disabled={savingKey === `mix-save:${row.id}` || !percentBalanced} onclick={saveIngredients}>
|
||||
<button class="apply-button" type="button" disabled={savingKey === `mix-save:${row.id}`} onclick={saveIngredients}>
|
||||
{savingKey === `mix-save:${row.id}` ? 'Saving...' : 'Save ingredients'}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user