From c1d0dfaee900f5b5433a740867014e2a4afef2ef Mon Sep 17 00:00:00 2001 From: Oier Bravo Urtasun Date: Mon, 18 May 2026 03:41:19 +0200 Subject: [PATCH] docs(fase-11): history doc + final test counts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fase 11 closed. 385 tests green (+26 vs Fase 10): pgTAP 96→104, Vitest integration 151→157, Vitest unit 38→45, Playwright 74→79. 3 skipped unchanged (2 upstream Realtime presence, 1 gated rate-limit). Co-Authored-By: Claude Opus 4.7 --- docs/history/fase-11-item-tags-importance.md | 132 +++++++++++++++++++ 1 file changed, 132 insertions(+) create mode 100644 docs/history/fase-11-item-tags-importance.md diff --git a/docs/history/fase-11-item-tags-importance.md b/docs/history/fase-11-item-tags-importance.md new file mode 100644 index 0000000..5120849 --- /dev/null +++ b/docs/history/fase-11-item-tags-importance.md @@ -0,0 +1,132 @@ +# Fase 11 — Item tags + importance UX (MVP2 3/6) + +**Status: ✅ complete · 2026-05-18.** + +Third slice of the MVP2 cycle. Adds collective-scoped item tags (model + UI + filter) and closes the loop on drag-reorder by making remote reorders visible without a reload. + +Final test counts: **104 pgTAP + 157 Vitest integration + 45 Vitest unit + 79 Playwright = 385 green; 3 skipped (2 upstream Realtime presence + 1 gated rate-limit).** Delta vs Fase 10: + +| Suite | Before | After | Δ | +|---|---|---|---| +| pgTAP | 96 | 104 | +8 | +| Vitest integration | 151 | 157 | +6 | +| Vitest unit | 38 | 45 | +7 | +| Playwright E2E | 74 | 79 | +5 | +| **Total** | **359** | **385** | **+26** | + +--- + +## 11.1 — Tag model + +**Migration `022_item_tags.sql`** — two new tables under `public`: + +- `item_tags(id, collective_id, name, color, created_at)` with `UNIQUE (collective_id, name)` and `CHECK color IN ('slate','red','amber','green','sky','indigo','pink','stone')`. Scope is collective: the same string in two collectives is two distinct rows. +- `shopping_item_tags(item_id, tag_id)` many-to-many bridge, both ends `ON DELETE CASCADE`. + +RLS: + +- `item_tags`: `is_member(collective_id)` for SELECT (guests included), `is_active_member(collective_id)` for INSERT / UPDATE / DELETE (guests read-only). +- `shopping_item_tags`: gated via a new `item_collective_id(uuid)` STABLE helper that resolves the parent collective in a single function call. The INSERT policy also enforces `item.collective = tag.collective` so a foreign tag cannot be attached to a home item. + +Both tables join `supabase_realtime` with `REPLICA IDENTITY FULL`, matching `007_realtime_publication.sql`. + +`packages/types/src/database.ts` and `domain.ts` hand-extended with `ItemTag`, `ItemWithTags`, `ItemTagColor`, and the new `item_collective_id` Function entry. `just db-types` is gated and skips with a notice; the Supabase CLI is not installed on this host. + +**pgTAP `014_item_tags.sql`** — 8 schema-level assertions: publication membership × 2, REPLICA IDENTITY × 2, UNIQUE violation, CHECK violation, cascade on tag delete, cascade on collective delete. RLS semantics (which roles can write, cross-collective attach rejection, no read from foreign collective) are covered by Vitest integration because pgTAP runs as superuser. + +--- + +## 11.2 — Stores + domain types + +**`apps/web/src/lib/stores/tags.ts`** (new): collective-scoped store with `loadTags(collectiveId)`, `createTag`, `renameTag`, `recolorTag`, `deleteTag`, `attachTag(itemId, tagId)`, `detachTag(itemId, tagId)`, and a realtime subscription per active collective. Re-subscribes when the active collective changes. Per the CLAUDE.md GoTrue-lock gotcha, this store is created from `onMount` lifecycles only, never from inside `onAuthStateChange`. + +**`apps/web/src/lib/stores/lists.ts`** — new `loadListItems(listId)` issues a single PostgREST embedding (`*, shopping_item_tags(item_tags(*))`) and flattens to `ItemWithTags[]`. The cast goes through `unknown` because the curated `database.ts` does not declare the `shopping_items ↔ shopping_item_tags` relationship; runtime resolves it from the FK. The original `loadItems(listId)` is kept for tests + the realtime fallback path. + +**Vitest integration `tests/rls-item-tags.test.ts`** — 6 IT-series assertions: member create OK, guest reject, non-member empty read, foreign-tag attach rejected, UNIQUE violation, cascade on item delete. + +--- + +## 11.3 — UI: chips, picker, modal, filter, settings + +**`TagChip.svelte`** — pill (`text-xs px-2 py-0.5 rounded-full`) with `bg-{color}-100 text-{color}-700` and a `dark:` variant. Three usage shapes: + +- display-only `` +- `onRemove` → renders an inline X (used by picker-selected chips) +- `onSelect` → renders the whole chip as a single `