From e814f3b5b38950124085a9d3691a10464bff4dc0 Mon Sep 17 00:00:00 2001 From: Oier Bravo Urtasun Date: Mon, 18 May 2026 12:38:52 +0200 Subject: [PATCH] =?UTF-8?q?docs(fase-15):=20history=20record=20=E2=80=94?= =?UTF-8?q?=20common=20items=20management=20+=20exclude-on-list?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes the Fase-15 cycle. Final counts: 177 pgTAP + 179 Vitest integration + 47 Vitest unit + 93 Playwright + 1 gated = 497 + 3 skipped. The plan estimated +14; actual is +21 (the extra came from the schema-invariant pgTAP assertions on the new index + RPC posture). Co-Authored-By: Claude Opus 4.7 --- docs/history/fase-15-common-items.md | 93 ++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 docs/history/fase-15-common-items.md diff --git a/docs/history/fase-15-common-items.md b/docs/history/fase-15-common-items.md new file mode 100644 index 0000000..4a3dc82 --- /dev/null +++ b/docs/history/fase-15-common-items.md @@ -0,0 +1,93 @@ +# Fase 15 — Common items management (weighted suggestions + exclude-on-list) + +**MVP2 · 7/7. Re-opened the cycle after Fase 14 with two concrete asks on the "add item" flow.** + +**Status: ✅ Shipped 2026-05-18.** + +Two user-driven additions on top of the existing `item_frequency` table from migration 006: + +1. **Promote items** — admins can boost specific names so they always lead the suggestion strip, regardless of historical `use_count`. +2. **Hide items already on the list** — the dropdown no longer nudges the user to re-add names that are already present in the active list. + +Plan: `plan/fase-15-common-items.md`. Migration: `supabase/migrations/026_item_frequency_weight.sql`. pgTAP: `supabase/tests/018_item_frequency_weight.sql`. + +## What changed + +### 15.1 — `item_frequency.weight` + admin RPCs + +- `weight integer NOT NULL DEFAULT 0` added to `item_frequency`. No CHECK constraint — the column stays open so future tiers ("strong boost", "soft hide") don't force another schema diff. The UI is the contract for the 3-way mapping, not the DB. +- `item_frequency_collective_count_idx` dropped, replaced by `item_frequency_collective_order_idx (collective_id, weight DESC, use_count DESC, last_used_at DESC)` so the suggestion query scans in order without a sort step. +- Two SECURITY DEFINER RPCs gate writes: + - `public.set_item_frequency_weight(p_collective_id uuid, p_name text, p_weight integer)` — upsert: if the row doesn't exist, inserts with `use_count = 0`; if it does, sets `weight`. Raises `P0002` for non-admins, `P0001` for unauthenticated / empty name. + - `public.purge_item_frequency(p_collective_id uuid, p_name text)` — deletes the row from `item_frequency` only. Does NOT touch `shopping_items` (the modal copy explicitly says this; existing items on lists are unaffected). The next time someone adds that name to a list, the existing trigger recreates the row with `weight = 0`. +- The deny-all INSERT/UPDATE/DELETE policies from migration 006 are preserved; the RPCs are the only write path. Both `GRANT EXECUTE ... TO authenticated` so PostgREST will call them. + +### 15.2 — Store + query + +- `fetchSuggestions(collectiveId, prefix, options?)` in `apps/web/src/lib/stores/lists.ts` extended with `{ excludeNames?: string[]; limit?: number }`. Ordering switches from `.order('use_count')` to `weight DESC, use_count DESC, last_used_at DESC`. `excludeNames` is lowercased + trimmed + deduped before being sent; when empty, the `.not()` filter is skipped entirely (PostgREST renders an empty `(...)` list as a SQL error, and zero entries would mean zero effect anyway). When the list exceeds `EXCLUDE_NAMES_HARD_CAP = 200`, the filter degrades to a no-op rather than fail the whole query — past that point we'd risk hitting Kong's ~16 KiB request URI cap (see "Gotchas" below). +- New `apps/web/src/lib/stores/commonItems.ts` is the unbounded counterpart used by the manage view: `loadCommonItems` (full catalogue, same ordering, no `LIMIT`), `setWeight` (RPC + optimistic in-store update), `purge` (RPC + optimistic remove). + +### 15.3 — Manage UI + +`apps/web/src/routes/(app)/collective/manage/+page.svelte` gains a new "Common items" subsection visible to admins + members (guests see nothing): + +- Table per row: name, last_used + use_count blurb, a 3-way **Hide / Normal / Boost** segmented control that writes `weight = -50 / 0 / 50`, a small "Remove" button that opens a confirmation modal explaining "items already in lists are not touched". +- Members see the same table but every action button is disabled with a tooltip "Only admins can manage common items". +- "Add common item" button (admin only) — opens a modal with a name input + the same 3-way control, defaulting to **Boost**. Useful for seeding the catalogue before lists actually mention an item. +- Client-side substring search filters the table; empty / filtered-empty states have their own copy. +- `data-weight-state` attribute on each `
  • ` exposes the current visual state to the e2e suite so the segmented control is testable without scraping classnames. +- The segmented control is a row of three `