feat(fase-5.10.0): drop shopping_items.unit column

The unit field had no edit path under the redesign (only name + qty
remain in the row; the double-tap overlay is name-only). Users can encode
the unit inside the name when it matters — e.g. "Milk 1L", "Eggs 1 doz".

Changes
  supabase/migrations/011_drop_shopping_items_unit.sql — DROP COLUMN
  supabase/seed.sql — unit values folded into names where relevant
  supabase/tests/002_item_frequency_trigger.sql — remove unit from INSERT
  packages/types — remove unit from ShoppingItem Row/Insert/Update
  apps/web/src/lib/stores/lists.ts — addItem/updateItem signatures
  apps/web/src/routes/(app)/lists/[id]/+page.svelte — purge newUnit /
    editUnit state + inputs + display
  apps/web/src/routes/(app)/lists/[id]/session/+page.svelte — simplify
    quantity-only render
  apps/web/messages/{en,es}.json — remove list_unit_label

Verification
  just test-db  → 34 pgTAP green
  just test-integration → 140 + 2 skipped
  just test-unit → 11 green
  just test-e2e → 39 + 2 skipped

The generated search tsvector on shopping_items.search already indexes
only `name`, so full-text search coverage is unchanged.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-13 19:00:08 +02:00
parent 5a63e5fc4a
commit d6cea5146f
11 changed files with 363 additions and 58 deletions

View File

@@ -91,7 +91,6 @@
"list_actions": "List actions",
"list_item_placeholder": "Add item (e.g. Yogurt 500g)",
"list_qty_label": "Qty",
"list_unit_label": "Unit",
"list_add_item": "Add item…",
"list_to_buy": "To buy",
"list_checked": "Checked",

View File

@@ -91,7 +91,6 @@
"list_actions": "Acciones de lista",
"list_item_placeholder": "Añadir producto…",
"list_qty_label": "Cant.",
"list_unit_label": "Unidad",
"list_add_item": "Añadir producto…",
"list_to_buy": "Por comprar",
"list_checked": "Marcado",

View File

@@ -150,7 +150,6 @@ export async function addItem(
listId: string,
name: string,
quantity: number | null,
unit: string | null,
userId: string,
sortOrder: number,
id?: string
@@ -160,7 +159,6 @@ export async function addItem(
list_id: listId,
name,
quantity,
unit,
sort_order: sortOrder,
created_by: userId
};
@@ -180,7 +178,7 @@ export async function updateItem(
patch: Partial<
Pick<
ShoppingItem,
'name' | 'quantity' | 'unit' | 'is_checked' | 'checked_by' | 'checked_at' | 'sort_order'
'name' | 'quantity' | 'is_checked' | 'checked_by' | 'checked_at' | 'sort_order'
>
>
) {

View File

@@ -49,7 +49,6 @@
// Add-item form
let newName = $state('');
let newQty = $state<number | null>(null);
let newUnit = $state('');
let nameInput: HTMLInputElement | undefined = $state();
// Suggestions
@@ -60,7 +59,6 @@
let editingId = $state<string | null>(null);
let editName = $state('');
let editQty = $state<number | null>(null);
let editUnit = $state('');
// Swipe-to-delete (touch only)
let swipeOffsets = $state<Record<string, number>>({});
@@ -169,7 +167,6 @@
list_id: listId,
name,
quantity: newQty,
unit: newUnit.trim() || null,
is_checked: false,
checked_by: null,
checked_at: null,
@@ -188,14 +185,12 @@
items = [...items, optimistic];
newName = '';
newQty = null;
newUnit = '';
nameInput?.focus();
const real = await addItem(
listId,
name,
newQty,
optimistic.unit,
$currentUser.id,
sortOrder,
tempId
@@ -221,7 +216,6 @@
list_id: listId,
name,
quantity: newQty,
unit: optimistic.unit,
sort_order: sortOrder,
created_by: $currentUser.id
}
@@ -268,7 +262,6 @@
editingId = item.id;
editName = item.name;
editQty = item.quantity;
editUnit = item.unit ?? '';
}
async function commitEdit(item: ShoppingItem) {
@@ -276,18 +269,12 @@
editingId = null;
const name = editName.trim();
if (!name) return;
if (name === item.name && editQty === item.quantity && (editUnit || null) === item.unit) return;
if (name === item.name && editQty === item.quantity) return;
items = items.map((i) =>
i.id === item.id
? { ...i, name, quantity: editQty, unit: editUnit.trim() || null }
: i
i.id === item.id ? { ...i, name, quantity: editQty } : i
);
await updateItem(item.id, {
name,
quantity: editQty,
unit: editUnit.trim() || null
});
await updateItem(item.id, { name, quantity: editQty });
}
function handleEditKeydown(e: KeyboardEvent, item: ShoppingItem) {
@@ -531,15 +518,6 @@
class="w-14 bg-transparent text-sm text-text-secondary outline-none
border-b border-slate-300 dark:border-slate-600 pb-0.5 text-right"
/>
<input
type="text"
bind:value={editUnit}
onblur={() => commitEdit(item)}
onkeydown={(e) => handleEditKeydown(e, item)}
placeholder={m.list_unit_label()}
class="w-14 bg-transparent text-sm text-text-secondary outline-none
border-b border-slate-300 dark:border-slate-600 pb-0.5"
/>
</div>
{:else}
<!-- Item display -->
@@ -550,9 +528,6 @@
<span class="block truncate text-sm text-slate-800 dark:text-slate-200">
{item.name}
</span>
{#if item.unit}
<span class="text-[12px] text-text-muted">{item.unit}</span>
{/if}
</button>
<!-- Quantity stepper -->
@@ -662,16 +637,9 @@
type="number"
min="1"
placeholder={m.list_qty_label()}
class="w-12 bg-transparent text-sm text-text-secondary placeholder:text-text-muted
outline-none text-right"
/>
<input
bind:value={newUnit}
type="text"
placeholder={m.list_unit_label()}
onkeydown={handleAddKeydown}
class="w-12 bg-transparent text-sm text-text-secondary placeholder:text-text-muted
outline-none"
outline-none text-right"
/>
</div>
<button

View File

@@ -134,10 +134,8 @@
></button>
<div class="flex-1 min-w-0">
<p class="truncate text-base font-medium">{item.name}</p>
{#if item.quantity || item.unit}
<p class="text-sm text-text-muted">
{item.quantity ?? ''}{item.unit ? ' ' + item.unit : ''}
</p>
{#if item.quantity}
<p class="text-sm text-text-muted">{item.quantity}</p>
{/if}
</div>
</li>