test(22): persist human verification items as UAT
This commit is contained in:
@@ -0,0 +1,44 @@
|
||||
---
|
||||
status: partial
|
||||
phase: 22-add-from-catalog-thread-integration
|
||||
source: [22-VERIFICATION.md]
|
||||
started: 2026-04-06T15:00:00Z
|
||||
updated: 2026-04-06T15:00:00Z
|
||||
---
|
||||
|
||||
## Current Test
|
||||
|
||||
[awaiting human testing]
|
||||
|
||||
## Tests
|
||||
|
||||
### 1. Add to Collection from catalog search overlay (collection mode)
|
||||
expected: Clicking Add on a catalog card in collection mode opens AddToCollectionModal with category dropdown, notes textarea, and purchase price input. Submitting creates the item and shows 'Added to Collection' toast.
|
||||
result: [pending]
|
||||
|
||||
### 2. Add to Collection from global item detail page
|
||||
expected: Clicking 'Add to Collection' on /global-items/:id opens AddToCollectionModal with the correct item name pre-filled. Submit creates the item.
|
||||
result: [pending]
|
||||
|
||||
### 3. Add to Thread (existing thread) from catalog search overlay (thread mode)
|
||||
expected: Clicking Add in thread mode opens AddToThreadModal with a dropdown listing active threads. Selecting a thread and submitting adds the item as a candidate and shows a toast with the thread name. Subsequent adds pre-select the same thread (session memory).
|
||||
result: [pending]
|
||||
|
||||
### 4. New Thread creation from thread picker
|
||||
expected: Selecting '+ New Thread...' in the thread picker switches to create mode showing thread name + category fields. Submitting creates the thread and candidate in one step and shows 'Created [name] with first candidate' toast.
|
||||
result: [pending]
|
||||
|
||||
### 5. Thread resolution with catalog-linked candidate (CATFLOW-06 regression)
|
||||
expected: Resolving a thread whose winning candidate has a globalItemId creates a new collection item with the global item link. Verifiable in /collection after resolution.
|
||||
result: [pending]
|
||||
|
||||
## Summary
|
||||
|
||||
total: 5
|
||||
passed: 0
|
||||
issues: 0
|
||||
pending: 5
|
||||
skipped: 0
|
||||
blocked: 0
|
||||
|
||||
## Gaps
|
||||
@@ -0,0 +1,172 @@
|
||||
---
|
||||
phase: 22-add-from-catalog-thread-integration
|
||||
verified: 2026-04-06T14:30:00Z
|
||||
status: human_needed
|
||||
score: 9/9 must-haves verified
|
||||
human_verification:
|
||||
- test: "Add to Collection from catalog search overlay (collection mode)"
|
||||
expected: "Clicking Add on a catalog card in collection mode opens AddToCollectionModal with category dropdown, notes textarea, and purchase price input. Submitting creates the item and shows 'Added to Collection' toast."
|
||||
why_human: "Full modal submit + toast feedback requires a running browser and authenticated session."
|
||||
- test: "Add to Collection from global item detail page"
|
||||
expected: "Clicking 'Add to Collection' on /global-items/:id opens AddToCollectionModal with the correct item name pre-filled. Submit creates the item."
|
||||
why_human: "Requires browser navigation and session auth."
|
||||
- test: "Add to Thread (existing thread) from catalog search overlay (thread mode)"
|
||||
expected: "Clicking Add in thread mode opens AddToThreadModal with a dropdown listing active threads. Selecting a thread and submitting adds the item as a candidate and shows a toast with the thread name. Subsequent adds pre-select the same thread (session memory)."
|
||||
why_human: "Session thread memory and real-time thread list require a running app with seed data."
|
||||
- test: "New Thread creation from thread picker"
|
||||
expected: "Selecting '+ New Thread...' in the thread picker switches to create mode showing thread name + category fields. Submitting creates the thread and candidate in one step and shows 'Created [name] with first candidate' toast."
|
||||
why_human: "Requires browser, active session, and real category data."
|
||||
- test: "Thread resolution with catalog-linked candidate (CATFLOW-06 regression)"
|
||||
expected: "Resolving a thread whose winning candidate has a globalItemId creates a new collection item with the global item link. Verifiable in /collection after resolution."
|
||||
why_human: "End-to-end resolution flow requires a running app with a seeded catalog-linked thread."
|
||||
---
|
||||
|
||||
# Phase 22: Add-from-Catalog & Thread Integration — Verification Report
|
||||
|
||||
**Phase Goal:** Users can add catalog items to their collection and to threads directly from search
|
||||
**Verified:** 2026-04-06T14:30:00Z
|
||||
**Status:** human_needed
|
||||
**Re-verification:** No — initial verification
|
||||
|
||||
---
|
||||
|
||||
## Goal Achievement
|
||||
|
||||
### Observable Truths
|
||||
|
||||
| # | Truth | Status | Evidence |
|
||||
|---|-------|--------|----------|
|
||||
| 1 | Clicking Add on a catalog search card in collection mode opens the AddToCollectionModal | ✓ VERIFIED | `CatalogSearchOverlay.tsx` line 105: `if (catalogSearchMode === "collection") { openAddToCollection(item.id, itemName) }` |
|
||||
| 2 | AddToCollectionModal shows category dropdown, optional notes, optional purchase price, and submit/cancel buttons | ✓ VERIFIED | `AddToCollectionModal.tsx` — all four form fields present (lines 99–173): category `<select>`, notes `<textarea>`, purchase price `<input type="number">`, submit + cancel buttons |
|
||||
| 3 | Submitting the modal creates a reference item with globalItemId and personal fields | ✓ VERIFIED | `AddToCollectionModal.tsx` line 56–63: `createItem.mutate({ name, categoryId, globalItemId, notes, purchasePriceCents })`. API route uses `createItemSchema` which accepts `globalItemId`. Service writes all fields to DB |
|
||||
| 4 | Success toast appears after adding item to collection | ✓ VERIFIED | `AddToCollectionModal.tsx` line 66: `toast.success("Added to Collection")` on mutation `onSuccess`. `Toaster` rendered in `__root.tsx` line 214 |
|
||||
| 5 | Clicking Add to Collection on the global item detail page opens the same modal | ✓ VERIFIED | `$globalItemId.tsx` line 136: `onClick={() => openAddToCollection(item.id, ...)`. Same UIStore action consumed by `AddToCollectionModal` |
|
||||
| 6 | Clicking Add on a catalog search card in thread mode opens the AddToThreadModal with a thread picker | ✓ VERIFIED | `CatalogSearchOverlay.tsx` line 108: `if (catalogSearchMode === "thread") { openAddToThread(item.id, itemName) }`. `AddToThreadModal` renders thread `<select>` in "pick" mode |
|
||||
| 7 | User can select an existing active thread and the catalog item is added as a candidate | ✓ VERIFIED | `AddToThreadModal.tsx` lines 94–121: `handleAddToExistingThread()` calls `apiPost(/api/threads/${selectedThreadId}/candidates, { globalItemId, ... })`. Query cache invalidated afterward |
|
||||
| 8 | User can choose New Thread which shows thread name + category fields and creates thread + candidate in one step | ✓ VERIFIED | `AddToThreadModal.tsx` lines 123–151: `handleCreateThreadAndAdd()` calls `createThread.mutateAsync()` then `apiPost` to add candidate. "New Thread..." option at line 202 switches `mode` to "create" |
|
||||
| 9 | After creating a new thread, subsequent adds in same session default to that thread | ✓ VERIFIED | Both submit handlers call `setCatalogSessionThreadId(...)`. Initialization `useEffect` (line 49–66) pre-selects `catalogSessionThreadId` when modal re-opens in the same session |
|
||||
|
||||
**Score: 9/9 truths verified**
|
||||
|
||||
---
|
||||
|
||||
### Required Artifacts
|
||||
|
||||
| Artifact | Expected | Status | Details |
|
||||
|----------|----------|--------|---------|
|
||||
| `src/client/components/AddToCollectionModal.tsx` | Add-to-collection confirmation modal (min 80 lines) | ✓ VERIFIED | 179 lines. Exports `AddToCollectionModal`. Contains `useCreateItem`, `useCategories`, `toast.success("Added to Collection")`, `globalItemId` in mutate call, `purchasePriceCents` |
|
||||
| `src/client/stores/uiStore.ts` | Modal state slices for addToCollectionModal, addToThreadModal, catalogSessionThreadId | ✓ VERIFIED | All three slices present (lines 62–73 interface, lines 143–158 implementation). `closeCatalogSearch` resets `catalogSessionThreadId: null` (line 140) |
|
||||
| `src/client/routes/__root.tsx` | Toaster and AddToCollectionModal rendered at root | ✓ VERIFIED | `import { Toaster }` line 11, `<AddToCollectionModal />` line 211, `<Toaster position="bottom-right" richColors />` line 214 |
|
||||
| `src/client/components/AddToThreadModal.tsx` | Thread picker modal with new thread creation flow (min 120 lines) | ✓ VERIFIED | 290 lines. Exports `AddToThreadModal`. Contains `useThreads`, `useCreateThread`, `useGlobalItem`, `apiPost`, `setCatalogSessionThreadId`, `toast.success`, `globalItemId` in apiPost body, "pick"/"create" mode state, "+ New Thread..." option |
|
||||
| `src/client/routes/__root.tsx` (Plan 02) | AddToThreadModal rendered at root | ✓ VERIFIED | `import { AddToThreadModal }` line 14, `<AddToThreadModal />` line 213 |
|
||||
|
||||
---
|
||||
|
||||
### Key Link Verification
|
||||
|
||||
| From | To | Via | Status | Details |
|
||||
|------|----|-----|--------|---------|
|
||||
| `CatalogSearchOverlay.tsx` | `uiStore.ts` | `openAddToCollection` call replacing handleAddStub | ✓ WIRED | `handleAddStub` absent; `openAddToCollection` called at line 106; `openAddToThread` called at line 108 |
|
||||
| `AddToCollectionModal.tsx` | `/api/items` | `useCreateItem` mutation with `globalItemId` | ✓ WIRED | `useCreateItem()` imported and called; `globalItemId` passed in mutation payload (line 60) |
|
||||
| `$globalItemId.tsx` | `uiStore.ts` | `openAddToCollection` on button click | ✓ WIRED | `useUIStore` imported; `openAddToCollection` destructured (line 14); called on button `onClick` (line 136) |
|
||||
| `uiStore.ts` | `AddToThreadModal.tsx` | `addToThreadModal` + `catalogSessionThreadId` consumed | ✓ WIRED | `AddToThreadModal` reads `addToThreadModal`, `catalogSessionThreadId`, `setCatalogSessionThreadId` from `useUIStore` |
|
||||
| `AddToThreadModal.tsx` | `/api/threads` | `useCreateThread` for new thread creation | ✓ WIRED | `useCreateThread` imported (line 6); `createThread.mutateAsync()` called in `handleCreateThreadAndAdd` (line 129) |
|
||||
| `AddToThreadModal.tsx` | `/api/threads/:threadId/candidates` | `apiPost` for candidate creation | ✓ WIRED | `apiPost` called at lines 100 and 133 with `globalItemId` in request body |
|
||||
| `AddToThreadModal.tsx` | `uiStore.ts` | `setCatalogSessionThreadId` to remember thread selection | ✓ WIRED | Called at lines 111 and 141 after successful candidate add |
|
||||
|
||||
---
|
||||
|
||||
### Data-Flow Trace (Level 4)
|
||||
|
||||
| Artifact | Data Variable | Source | Produces Real Data | Status |
|
||||
|----------|---------------|--------|--------------------|--------|
|
||||
| `AddToCollectionModal.tsx` | `categories` | `useCategories()` → `GET /api/categories` → Drizzle query | Yes — DB query returns category rows | ✓ FLOWING |
|
||||
| `AddToCollectionModal.tsx` | `createItem` mutation result | `useCreateItem()` → `apiPost /api/items` → item service → Drizzle insert | Yes — DB insert with `globalItemId` field | ✓ FLOWING |
|
||||
| `AddToThreadModal.tsx` | `threads` (active threads) | `useThreads()` → `GET /api/threads` → Drizzle query | Yes — filtered to `status === "active"` client-side | ✓ FLOWING |
|
||||
| `AddToThreadModal.tsx` | `globalItem` | `useGlobalItem(globalItemId)` → `GET /api/global-items/:id` → Drizzle query | Yes — `weightGrams`/`priceCents` passed to candidate payload | ✓ FLOWING |
|
||||
| `AddToThreadModal.tsx` | candidate creation result | `apiPost /api/threads/:id/candidates` → thread service → Drizzle insert | Yes — `globalItemId` stored on candidate row (confirmed by test at line 657) | ✓ FLOWING |
|
||||
|
||||
---
|
||||
|
||||
### Behavioral Spot-Checks
|
||||
|
||||
Step 7b: SKIPPED — all entry points are React components requiring a running browser. No runnable CLI or server-only logic was introduced by this phase. The build is the appropriate automated check.
|
||||
|
||||
Build verification (from SUMMARY.md — both plans report `bun run build` exits 0, code 0 on type checks):
|
||||
|
||||
| Behavior | Check | Status |
|
||||
|----------|-------|--------|
|
||||
| No TypeScript errors in new files | `bun run build` (plan 01 + 02 acceptance) | ✓ PASS (per summaries) |
|
||||
| CATFLOW-06 regression — resolveThread with globalItemId | `tests/services/thread.service.test.ts` lines 704–725 | ✓ COVERED — test exists and verifies `result.item?.globalItemId === gi.id` |
|
||||
| Commits exist for documented hashes | `git log f309c73 ed76236 c33b7c7` | ✓ VERIFIED — all three hashes resolve |
|
||||
|
||||
---
|
||||
|
||||
### Requirements Coverage
|
||||
|
||||
| Requirement | Source Plan | Description | Status | Evidence |
|
||||
|-------------|-------------|-------------|--------|----------|
|
||||
| CATFLOW-03 | 22-01-PLAN.md | User can add a catalog item to collection as a reference item with personal fields (category, notes, purchase price, image, quantity) | ✓ SATISFIED | `AddToCollectionModal` provides category, notes, purchase price. Image inherited via service-layer `globalItemId` join (item.service.ts lines 34, 74). Quantity defaults to 1 at DB level. Image upload and quantity fields explicitly excluded from modal scope per DISCUSSION-LOG decision D-02 (compact modal = minimal friction). REQUIREMENTS.md marks CATFLOW-03 complete across Phases 19 and 22. |
|
||||
| CATFLOW-05 | 22-02-PLAN.md | Thread candidates can be added from catalog with global item link | ✓ SATISFIED | `AddToThreadModal` creates candidates via `apiPost /api/threads/:id/candidates` with `globalItemId` in payload. Thread service stores `globalItemId` on candidate row. |
|
||||
| CATFLOW-06 | 22-02-PLAN.md | Thread resolution with catalog-linked candidate creates reference item with auto-link | ✓ SATISFIED | Existing `thread.service.test.ts` line 704 test: "resolveThread with candidate having globalItemId creates a reference item". No Phase 22 code change was required — existing `resolveThread` service already handles this correctly, confirmed by test coverage. |
|
||||
|
||||
No orphaned requirements: all three IDs declared in plan frontmatter map to confirmed implementations. REQUIREMENTS.md shows CATFLOW-03, CATFLOW-05, CATFLOW-06 all marked `[x]` complete with Phase 22 listed.
|
||||
|
||||
---
|
||||
|
||||
### Anti-Patterns Found
|
||||
|
||||
| File | Pattern | Severity | Impact |
|
||||
|------|---------|----------|--------|
|
||||
| `CatalogSearchOverlay.tsx` line 409/420 | `onAdd={() => handleAdd(item)}` — `onAdd` prop no longer receives `MouseEvent` (CardProps type changed from `(e: React.MouseEvent) => void` to `() => void`) | ℹ️ Info | `e.stopPropagation()` from the original plan stub was dropped — the Add button is inside a card that has no `onClick` navigation handler, so event bubbling is benign. Not a functional issue. |
|
||||
|
||||
No TODO/FIXME/placeholder code, no empty return stubs, no hardcoded empty data arrays flowing to user-visible output.
|
||||
|
||||
---
|
||||
|
||||
### Human Verification Required
|
||||
|
||||
The following flows require a running browser with an authenticated session. Automated code inspection confirms the wiring is complete; the tests below verify user-observable behavior.
|
||||
|
||||
#### 1. Add to Collection — catalog search entry point
|
||||
|
||||
**Test:** Open the app, click the FAB, select "Add to Collection". In the catalog overlay, search for any item and click "Add".
|
||||
**Expected:** AddToCollectionModal opens showing the item name, a category dropdown pre-selected to the first category, a notes textarea, and a purchase price input. Fill in a category, click "Add to Collection". A toast "Added to Collection" appears. Navigate to /collection — the item is present.
|
||||
**Why human:** Modal render and toast display require a live browser. Toast timing and overlay persistence after submit cannot be verified statically.
|
||||
|
||||
#### 2. Add to Collection — global item detail page entry point
|
||||
|
||||
**Test:** Navigate to `/global-items/:id` for any item. Verify both "Add to Collection" (filled/primary) and "Add to Thread" (outlined/secondary) buttons are visible. Click "Add to Collection".
|
||||
**Expected:** Same AddToCollectionModal opens with the correct item name. Submit works and toast appears.
|
||||
**Why human:** Button visibility and correct styling require browser rendering.
|
||||
|
||||
#### 3. Add to Thread — existing thread selection + session memory
|
||||
|
||||
**Test:** Ensure at least one active thread exists. Click FAB > "Start Thread". In the overlay, click "Add" on any item.
|
||||
**Expected:** AddToThreadModal opens in "pick" mode showing active threads in a dropdown with category names alongside thread names. Select a thread and click "Add as Candidate". Toast "Added to [Thread Name]" appears. Click "Add" on another item — the previously selected thread is pre-selected.
|
||||
**Why human:** Session thread memory requires observing state across multiple modal open/close cycles. Thread dropdown population requires real thread data.
|
||||
|
||||
#### 4. New Thread creation from thread picker
|
||||
|
||||
**Test:** In thread mode, click "Add" on a catalog item. In the thread picker dropdown, select "+ New Thread...".
|
||||
**Expected:** Form switches to "New Thread + Candidate" mode with thread name input and category dropdown. A "Back to thread picker" link is visible if active threads exist. Fill in a thread name, select a category, click "Create & Add". Toast "Created [name] with first candidate" appears. Click "Add" on another item — the new thread is pre-selected.
|
||||
**Why human:** Mode switching animation, back-navigation between pick/create modes, and toast content require browser observation.
|
||||
|
||||
#### 5. Thread resolution regression (CATFLOW-06)
|
||||
|
||||
**Test:** Resolve a thread whose winning candidate has a `globalItemId` (i.e., was added from the catalog in Flow 3 or 4). Click "Pick Winner" on that candidate.
|
||||
**Expected:** The resolved item appears in the collection with its `globalItemId` set, inheriting global item data (weight, price, image from catalog).
|
||||
**Why human:** Full resolution → collection navigation requires a live app and real DB state. The unit test at line 704 covers the service logic; this test verifies the UI path.
|
||||
|
||||
---
|
||||
|
||||
### Gaps Summary
|
||||
|
||||
No automated gaps found. All 9 observable truths are VERIFIED in code. All 3 requirement IDs are satisfied. All key links are wired. All artifacts pass Levels 1–4 (exist, substantive, wired, data flowing).
|
||||
|
||||
Five items are routed to human verification because they involve browser rendering, toast display, modal state transitions, and real session/DB state that cannot be confirmed by static code inspection alone.
|
||||
|
||||
---
|
||||
|
||||
_Verified: 2026-04-06T14:30:00Z_
|
||||
_Verifier: Claude (gsd-verifier)_
|
||||
Reference in New Issue
Block a user