docs(21): research phase domain
This commit is contained in:
391
.planning/phases/21-item-catalog-detail-pages/21-RESEARCH.md
Normal file
391
.planning/phases/21-item-catalog-detail-pages/21-RESEARCH.md
Normal file
@@ -0,0 +1,391 @@
|
|||||||
|
# Phase 21: Item & Catalog Detail Pages - Research
|
||||||
|
|
||||||
|
**Researched:** 2026-04-06
|
||||||
|
**Domain:** Frontend routing, detail page patterns, edit mode toggle, panel removal
|
||||||
|
**Confidence:** HIGH
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
This phase creates full detail pages for collection items, enhances the existing catalog detail page, adds candidate detail routes, and removes slide-out panels from the application. The entire scope is frontend-only -- no new API endpoints or service changes are required. All necessary hooks (`useItem`, `useUpdateItem`, `useDeleteItem`, `useDuplicateItem`, `useGlobalItem`, `useUpdateCandidate`, `useDeleteCandidate`) and API routes already exist.
|
||||||
|
|
||||||
|
The primary complexity is the edit mode toggle pattern (read-only by default, fields transform to inputs when "Edit" is pressed) and coordinating the panel removal without breaking existing flows. TanStack Router's file-based routing makes adding new routes mechanical -- create the file, export a `Route` constant, and the route tree auto-generates.
|
||||||
|
|
||||||
|
**Primary recommendation:** Build detail pages first (item, candidate, catalog enhancement), then rewire card click handlers, then remove panels and clean up UIStore in a final wave. This ordering ensures the navigation targets exist before redirecting clicks to them.
|
||||||
|
|
||||||
|
<user_constraints>
|
||||||
|
## User Constraints (from CONTEXT.md)
|
||||||
|
|
||||||
|
### Locked Decisions
|
||||||
|
- D-01 through D-07: Private item detail page at `/items/:id` with hero section, personal section, edit mode toggle, back navigation, and actions (duplicate, delete).
|
||||||
|
- D-08 through D-12: Public catalog detail page at `/global-items/:id` enhanced with "Add to Collection" button (stub), hero layout, no edit functionality.
|
||||||
|
- D-13 through D-16: Candidate detail page at `/threads/:threadId/candidates/:candidateId` with edit mode toggle and thread-specific actions.
|
||||||
|
- D-17 through D-20: Navigation changes -- ItemCard, CandidateCard, CandidateListItem, and catalog search result clicks navigate to detail pages.
|
||||||
|
- D-21 through D-26: Remove SlideOutPanel instances from `__root.tsx`, remove panel state from UIStore, keep `SlideOutPanel.tsx`, `ItemForm.tsx`, `CandidateForm.tsx` files.
|
||||||
|
- D-27: "Add item" flow goes through catalog search (FAB). No more direct "add item" panel.
|
||||||
|
- D-28: Adding candidates to threads -- keep a simple button/form pattern (not a slide-out).
|
||||||
|
|
||||||
|
### Claude's Discretion
|
||||||
|
- Exact layout proportions and spacing for detail pages
|
||||||
|
- Whether to use tabs or sections for organizing detail page content
|
||||||
|
- How the edit mode transition animates (if at all)
|
||||||
|
- "Add Candidate" button pattern on thread page (inline form, modal, or navigate to add route)
|
||||||
|
- Whether to show a "Linked to catalog" indicator on private items (subtle, not prominent)
|
||||||
|
- Mobile layout adaptations for detail pages
|
||||||
|
|
||||||
|
### Deferred Ideas (OUT OF SCOPE)
|
||||||
|
- Reviews/ratings section on detail pages
|
||||||
|
- Community stats (average weight, price history)
|
||||||
|
- Setup appearances ("This item is in 3 setups")
|
||||||
|
- "Similar items" recommendation section
|
||||||
|
- Image gallery with multiple photos
|
||||||
|
</user_constraints>
|
||||||
|
|
||||||
|
<phase_requirements>
|
||||||
|
## Phase Requirements
|
||||||
|
|
||||||
|
| ID | Description | Research Support |
|
||||||
|
|----|-------------|------------------|
|
||||||
|
| DETAIL-01 | Clicking a collection item navigates to a full detail page (`/items/:id`) showing all item data | New route file at `src/client/routes/items/$itemId.tsx`; existing `useItem(id)` hook returns all merged data via COALESCE |
|
||||||
|
| DETAIL-02 | Clicking a catalog search result navigates to a public detail page (`/global-items/:id`) with "Add to Collection" button | Existing route at `src/client/routes/global-items/$globalItemId.tsx` needs enhancement; `useGlobalItem(id)` returns data with ownerCount |
|
||||||
|
| DETAIL-03 | Item detail page has edit mode toggle for modifying personal fields | `useState` for edit mode boolean; reuse validation logic from `ItemForm.tsx`; `useUpdateItem` mutation for save |
|
||||||
|
| DETAIL-04 | Thread candidates navigate to detail pages instead of opening slide-out panels | New route at `src/client/routes/threads/$threadId/candidates/$candidateId.tsx`; `useThread(threadId)` returns candidates array |
|
||||||
|
| DETAIL-05 | Slide-out panels for items and candidates are removed from the application | Remove from `__root.tsx` lines 189-221; clean UIStore panel state (lines 3-28 of store) |
|
||||||
|
</phase_requirements>
|
||||||
|
|
||||||
|
## Project Constraints (from CLAUDE.md)
|
||||||
|
|
||||||
|
- **Routing**: TanStack Router with file-based routes in `src/client/routes/`. Route tree auto-generated to `routeTree.gen.ts` -- never edit manually.
|
||||||
|
- **Data fetching**: TanStack React Query via custom hooks. Mutations invalidate related query keys.
|
||||||
|
- **UI state**: Zustand store for panel/dialog state only -- server data lives in React Query.
|
||||||
|
- **Styling**: Tailwind CSS v4.
|
||||||
|
- **Testing**: Bun test runner for unit/integration. Playwright for E2E.
|
||||||
|
- **Path alias**: `@/*` maps to `./src/*`.
|
||||||
|
- **Dev command**: `bun run dev` starts both client and server.
|
||||||
|
- **Lint**: `bun run lint` -- Biome check (tabs, double quotes, organized imports).
|
||||||
|
|
||||||
|
## Standard Stack
|
||||||
|
|
||||||
|
### Core (already installed, no new dependencies)
|
||||||
|
| Library | Purpose | Why Standard |
|
||||||
|
|---------|---------|--------------|
|
||||||
|
| TanStack Router | File-based routing, route params | Already used throughout app |
|
||||||
|
| TanStack React Query | Data fetching, caching, mutations | All data fetching goes through this |
|
||||||
|
| Zustand | UI state management | Panel/dialog state management |
|
||||||
|
| Tailwind CSS v4 | Styling | Project standard |
|
||||||
|
| Framer Motion | Animations (AnimatePresence) | Already used for page transitions |
|
||||||
|
|
||||||
|
### No New Dependencies Required
|
||||||
|
This phase adds no new libraries. All required functionality is covered by the existing stack.
|
||||||
|
|
||||||
|
## Architecture Patterns
|
||||||
|
|
||||||
|
### New Route Files to Create
|
||||||
|
```
|
||||||
|
src/client/routes/
|
||||||
|
├── items/
|
||||||
|
│ └── $itemId.tsx # /items/:id - private item detail
|
||||||
|
├── threads/
|
||||||
|
│ └── $threadId/
|
||||||
|
│ └── candidates/
|
||||||
|
│ └── $candidateId.tsx # /threads/:threadId/candidates/:candidateId
|
||||||
|
├── global-items/
|
||||||
|
│ └── $globalItemId.tsx # EXISTING - enhance with Add button
|
||||||
|
```
|
||||||
|
|
||||||
|
### Pattern 1: TanStack Router File-Based Route
|
||||||
|
**What:** Create route file with `createFileRoute`, export `Route` constant.
|
||||||
|
**When to use:** Every new page.
|
||||||
|
**Example (from existing codebase):**
|
||||||
|
```typescript
|
||||||
|
// src/client/routes/global-items/$globalItemId.tsx
|
||||||
|
import { createFileRoute, Link } from "@tanstack/react-router";
|
||||||
|
|
||||||
|
export const Route = createFileRoute("/global-items/$globalItemId")({
|
||||||
|
component: GlobalItemDetail,
|
||||||
|
});
|
||||||
|
|
||||||
|
function GlobalItemDetail() {
|
||||||
|
const { globalItemId } = Route.useParams();
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
For nested routes like `/threads/$threadId/candidates/$candidateId`, TanStack Router requires the directory structure to match. The file goes at `src/client/routes/threads/$threadId/candidates/$candidateId.tsx` with route path `"/threads/$threadId/candidates/$candidateId"`.
|
||||||
|
|
||||||
|
**IMPORTANT:** The existing `src/client/routes/threads/$threadId.tsx` must be renamed to `src/client/routes/threads/$threadId/index.tsx` (or `route.tsx`) to allow the nested candidates directory to exist alongside it. TanStack Router file-based routing requires this restructuring when adding child routes under a parameterized parent.
|
||||||
|
|
||||||
|
### Pattern 2: Edit Mode Toggle
|
||||||
|
**What:** Local `useState` boolean controls read-only vs. editable view. No Zustand needed.
|
||||||
|
**When to use:** Item detail and candidate detail pages.
|
||||||
|
**Example:**
|
||||||
|
```typescript
|
||||||
|
function ItemDetail() {
|
||||||
|
const [isEditing, setIsEditing] = useState(false);
|
||||||
|
const [form, setForm] = useState<FormData>(/* initial */);
|
||||||
|
const updateItem = useUpdateItem();
|
||||||
|
const { data: item } = useItem(Number(itemId));
|
||||||
|
|
||||||
|
// Sync form state when entering edit mode or when item data loads
|
||||||
|
useEffect(() => {
|
||||||
|
if (item && isEditing) {
|
||||||
|
setForm(/* map item to form fields */);
|
||||||
|
}
|
||||||
|
}, [item, isEditing]);
|
||||||
|
|
||||||
|
function handleSave() {
|
||||||
|
updateItem.mutate({ id: item.id, ...payload }, {
|
||||||
|
onSuccess: () => setIsEditing(false),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleCancel() {
|
||||||
|
setIsEditing(false);
|
||||||
|
// Form resets on next edit mode entry via useEffect
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{isEditing ? (
|
||||||
|
<input value={form.name} onChange={...} />
|
||||||
|
) : (
|
||||||
|
<h1>{item.name}</h1>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Pattern 3: Detail Page Layout (gallery-like)
|
||||||
|
**What:** Consistent layout: back nav, hero image, title/badges, content sections, action buttons.
|
||||||
|
**When to use:** All three detail pages.
|
||||||
|
**Example structure (from existing `$globalItemId.tsx`):**
|
||||||
|
```typescript
|
||||||
|
<div className="max-w-3xl mx-auto px-4 sm:px-6 lg:px-8 py-6">
|
||||||
|
{/* Back navigation */}
|
||||||
|
<Link to="/collection" className="text-sm text-gray-400 hover:text-gray-600">
|
||||||
|
← Back to collection
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
{/* Hero image */}
|
||||||
|
<div className="aspect-[16/9] bg-gray-50 rounded-xl overflow-hidden mb-6">
|
||||||
|
<img ... />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Title + badges */}
|
||||||
|
<h1 className="text-2xl font-bold text-gray-900 mb-3">{item.name}</h1>
|
||||||
|
<div className="flex flex-wrap gap-2 mb-6">
|
||||||
|
{/* weight, price, category badges */}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Content sections */}
|
||||||
|
{/* ... notes, product link, etc. */}
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Pattern 4: Navigation from Card Components
|
||||||
|
**What:** Replace `openEditPanel(id)` calls with TanStack Router `useNavigate()` or `Link` component.
|
||||||
|
**When to use:** ItemCard, CandidateCard, CandidateListItem, CatalogSearchOverlay grid/list cards.
|
||||||
|
**Example:**
|
||||||
|
```typescript
|
||||||
|
// Before (ItemCard.tsx)
|
||||||
|
const openEditPanel = useUIStore((s) => s.openEditPanel);
|
||||||
|
onClick={() => openEditPanel(id)}
|
||||||
|
|
||||||
|
// After
|
||||||
|
import { useNavigate } from "@tanstack/react-router";
|
||||||
|
const navigate = useNavigate();
|
||||||
|
onClick={() => navigate({ to: "/items/$itemId", params: { itemId: String(id) } })}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Anti-Patterns to Avoid
|
||||||
|
- **Do NOT edit `routeTree.gen.ts` manually** -- it auto-generates when route files change.
|
||||||
|
- **Do NOT put edit form state in Zustand** -- local `useState` per detail page is correct. Zustand is for cross-component UI state only.
|
||||||
|
- **Do NOT remove `ItemForm.tsx` or `CandidateForm.tsx`** -- reuse their validation logic and field definitions in the edit mode sections of detail pages.
|
||||||
|
- **Do NOT remove SlideOutPanel.tsx component file** -- D-25 explicitly keeps it for potential future use.
|
||||||
|
|
||||||
|
## Don't Hand-Roll
|
||||||
|
|
||||||
|
| Problem | Don't Build | Use Instead | Why |
|
||||||
|
|---------|-------------|-------------|-----|
|
||||||
|
| Form validation | Custom validation from scratch | Copy validation logic from `ItemForm.tsx` / `CandidateForm.tsx` | Proven patterns, consistent behavior |
|
||||||
|
| Image URL resolution | Manual URL building | `withImageUrl` / `withImageUrls` from storage service (already called in routes) | Server already returns `imageUrl` field in API response |
|
||||||
|
| Loading/error states | Custom skeleton per page | Follow existing pattern from `$globalItemId.tsx` (shimmer placeholders, error state with back link) | Consistency |
|
||||||
|
| Confirmation dialogs | New dialog components | Existing `ConfirmDialog` pattern in `__root.tsx` and `useUIStore.openConfirmDelete` | Already works for items; same pattern for candidates on detail pages |
|
||||||
|
|
||||||
|
## Common Pitfalls
|
||||||
|
|
||||||
|
### Pitfall 1: Nested Route File Structure for Candidates
|
||||||
|
**What goes wrong:** Creating `src/client/routes/threads/$threadId/candidates/$candidateId.tsx` while `$threadId.tsx` exists as a file (not a directory) causes TanStack Router confusion.
|
||||||
|
**Why it happens:** TanStack Router file-based routing treats `$threadId.tsx` and `$threadId/` directory as conflicting. You need to restructure.
|
||||||
|
**How to avoid:** Rename `src/client/routes/threads/$threadId.tsx` to `src/client/routes/threads/$threadId/index.tsx` (or `route.tsx`). Then create `$threadId/candidates/$candidateId.tsx` as a sibling directory.
|
||||||
|
**Warning signs:** Route tree generation errors, blank page at `/threads/:threadId`.
|
||||||
|
|
||||||
|
### Pitfall 2: Removing Panel State Before Wiring Navigation
|
||||||
|
**What goes wrong:** Removing UIStore panel state and `__root.tsx` panel JSX before the card click handlers are updated causes runtime errors (calling undefined functions).
|
||||||
|
**Why it happens:** Components like `ItemCard` still import `useUIStore((s) => s.openEditPanel)`.
|
||||||
|
**How to avoid:** Order of operations: (1) Create detail pages, (2) Update card click handlers to navigate, (3) Then remove panel state and JSX.
|
||||||
|
**Warning signs:** TypeScript errors on removed store properties, blank areas where panels used to be.
|
||||||
|
|
||||||
|
### Pitfall 3: Edit Mode Form Not Syncing with Server Data
|
||||||
|
**What goes wrong:** User enters edit mode, but form shows stale or default data.
|
||||||
|
**Why it happens:** `useItem(id)` fetch may not have completed when edit mode is toggled, or form state is initialized once and never updated.
|
||||||
|
**How to avoid:** Use `useEffect` to sync form state when both `isEditing === true` AND item data changes. Alternatively, initialize form data from item data only when entering edit mode (in the toggle handler).
|
||||||
|
**Warning signs:** Form fields show empty or previous values.
|
||||||
|
|
||||||
|
### Pitfall 4: CandidateCard/CandidateListItem Need threadId for Navigation
|
||||||
|
**What goes wrong:** Navigation to `/threads/:threadId/candidates/:candidateId` requires both IDs, but the component may only have `candidateId`.
|
||||||
|
**Why it happens:** CandidateCard already receives `threadId` as a prop. CandidateListItem receives it via `candidate.threadId`. Both are safe.
|
||||||
|
**How to avoid:** Verify both components have access to `threadId` before changing click handlers. Both do.
|
||||||
|
**Warning signs:** Navigation produces undefined params.
|
||||||
|
|
||||||
|
### Pitfall 5: Catalog Search Overlay Card Click vs. Add Button
|
||||||
|
**What goes wrong:** Making the entire card clickable navigates away from the search overlay, losing context.
|
||||||
|
**Why it happens:** D-19 says card click navigates to `/global-items/:id`, but the overlay is open.
|
||||||
|
**How to avoid:** Close the catalog search overlay before navigating. Or use `Link` component and let the navigation naturally close the overlay (overlay checks `catalogSearchOpen` state). The card body navigates; the "Add" button stays as a separate action with `e.stopPropagation()`.
|
||||||
|
**Warning signs:** Overlay stays visible on top of detail page, or user loses search context unexpectedly.
|
||||||
|
|
||||||
|
### Pitfall 6: "Add Candidate" on Thread Page After Panel Removal
|
||||||
|
**What goes wrong:** The thread page currently opens the candidate add panel via `openCandidateAddPanel()`. After panels are removed, there's no way to add candidates.
|
||||||
|
**Why it happens:** D-28 says to keep a simple button/form pattern, but the existing flow is entirely panel-based.
|
||||||
|
**How to avoid:** Replace the "Add Candidate" button action on the thread page. Options: (1) inline form that expands on the thread page, (2) small modal dialog, (3) navigate to a dedicated add route. Recommendation: use a modal dialog -- it's the lightest change and consistent with existing dialog patterns in the app.
|
||||||
|
**Warning signs:** No way to add candidates after panel removal.
|
||||||
|
|
||||||
|
## Code Examples
|
||||||
|
|
||||||
|
### Creating the Item Detail Route
|
||||||
|
```typescript
|
||||||
|
// src/client/routes/items/$itemId.tsx
|
||||||
|
import { createFileRoute, Link, useNavigate } from "@tanstack/react-router";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { useFormatters } from "../../hooks/useFormatters";
|
||||||
|
import { useDeleteItem, useDuplicateItem, useItem, useUpdateItem } from "../../hooks/useItems";
|
||||||
|
|
||||||
|
export const Route = createFileRoute("/items/$itemId")({
|
||||||
|
component: ItemDetailPage,
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### Restructuring Thread Route for Nesting
|
||||||
|
```
|
||||||
|
# Before:
|
||||||
|
src/client/routes/threads/$threadId.tsx
|
||||||
|
|
||||||
|
# After:
|
||||||
|
src/client/routes/threads/$threadId/index.tsx # same content, path unchanged
|
||||||
|
src/client/routes/threads/$threadId/candidates/
|
||||||
|
$candidateId.tsx # new candidate detail
|
||||||
|
```
|
||||||
|
|
||||||
|
The `index.tsx` file maps to `/threads/$threadId` exactly as before. The nested `candidates/$candidateId.tsx` maps to `/threads/$threadId/candidates/$candidateId`.
|
||||||
|
|
||||||
|
### UIStore Cleanup (properties to remove)
|
||||||
|
```typescript
|
||||||
|
// Remove these from UIState interface and implementation:
|
||||||
|
panelMode: "closed" | "add" | "edit";
|
||||||
|
editingItemId: number | null;
|
||||||
|
openAddPanel: () => void;
|
||||||
|
openEditPanel: (itemId: number) => void;
|
||||||
|
closePanel: () => void;
|
||||||
|
|
||||||
|
candidatePanelMode: "closed" | "add" | "edit";
|
||||||
|
editingCandidateId: number | null;
|
||||||
|
openCandidateAddPanel: () => void;
|
||||||
|
openCandidateEditPanel: (id: number) => void;
|
||||||
|
closeCandidatePanel: () => void;
|
||||||
|
|
||||||
|
// Keep these (still used):
|
||||||
|
confirmDeleteItemId / openConfirmDelete / closeConfirmDelete // used by ConfirmDialog
|
||||||
|
confirmDeleteCandidateId / openConfirmDeleteCandidate / closeConfirmDeleteCandidate // candidate delete
|
||||||
|
resolveThreadId / resolveCandidateId / openResolveDialog / closeResolveDialog // resolve dialog
|
||||||
|
// ... all other non-panel state
|
||||||
|
```
|
||||||
|
|
||||||
|
### Updating ItemCard Click Handler
|
||||||
|
```typescript
|
||||||
|
// src/client/components/ItemCard.tsx
|
||||||
|
// Replace:
|
||||||
|
import { useUIStore } from "../stores/uiStore";
|
||||||
|
const openEditPanel = useUIStore((s) => s.openEditPanel);
|
||||||
|
// With:
|
||||||
|
import { useNavigate } from "@tanstack/react-router";
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
// onClick changes from:
|
||||||
|
onClick={() => openEditPanel(id)}
|
||||||
|
// To:
|
||||||
|
onClick={() => navigate({ to: "/items/$itemId", params: { itemId: String(id) } })}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Adding "Add to Collection" Button on Catalog Detail Page
|
||||||
|
```typescript
|
||||||
|
// In global-items/$globalItemId.tsx, add after hero/header:
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={handleAddToCollection}
|
||||||
|
className="bg-gray-700 text-white rounded-lg px-4 py-2 text-sm font-medium hover:bg-gray-800 transition-colors"
|
||||||
|
>
|
||||||
|
Add to Collection
|
||||||
|
</button>
|
||||||
|
```
|
||||||
|
This is a stub per D-10 -- actual add flow wired in Phase 22.
|
||||||
|
|
||||||
|
## Validation Architecture
|
||||||
|
|
||||||
|
### Test Framework
|
||||||
|
| Property | Value |
|
||||||
|
|----------|-------|
|
||||||
|
| Framework | Playwright + Bun test |
|
||||||
|
| Config file | `playwright.config.ts` (E2E), `bunfig.toml` (unit) |
|
||||||
|
| Quick run command | `bun test tests/routes/` |
|
||||||
|
| Full suite command | `bun run test:e2e` |
|
||||||
|
|
||||||
|
### Phase Requirements to Test Map
|
||||||
|
| Req ID | Behavior | Test Type | Automated Command | File Exists? |
|
||||||
|
|--------|----------|-----------|-------------------|-------------|
|
||||||
|
| DETAIL-01 | Item detail page renders at `/items/:id` | E2E | `bun run test:e2e -- --grep "item detail"` | No -- Wave 0 |
|
||||||
|
| DETAIL-02 | Catalog detail page has "Add to Collection" button | E2E | `bun run test:e2e -- --grep "catalog detail"` | No -- Wave 0 |
|
||||||
|
| DETAIL-03 | Edit mode toggle shows/hides form inputs | E2E | `bun run test:e2e -- --grep "edit mode"` | No -- Wave 0 |
|
||||||
|
| DETAIL-04 | Candidate card click navigates to detail page | E2E | `bun run test:e2e -- --grep "candidate detail"` | No -- Wave 0 |
|
||||||
|
| DETAIL-05 | No SlideOutPanel instances in rendered DOM | E2E | `bun run test:e2e -- --grep "panel removed"` | No -- Wave 0 |
|
||||||
|
|
||||||
|
### Sampling Rate
|
||||||
|
- **Per task commit:** `bun run lint && bun run dev` (manual visual check -- frontend-heavy)
|
||||||
|
- **Per wave merge:** `bun run test:e2e`
|
||||||
|
- **Phase gate:** Full E2E suite green before `/gsd:verify-work`
|
||||||
|
|
||||||
|
### Wave 0 Gaps
|
||||||
|
- [ ] `e2e/item-detail.spec.ts` -- covers DETAIL-01, DETAIL-03
|
||||||
|
- [ ] `e2e/catalog-detail.spec.ts` -- covers DETAIL-02
|
||||||
|
- [ ] `e2e/candidate-detail.spec.ts` -- covers DETAIL-04
|
||||||
|
- [ ] `e2e/panel-removal.spec.ts` -- covers DETAIL-05
|
||||||
|
- [ ] E2E seed data: verify `e2e/seed.ts` creates items and threads with candidates for detail page testing
|
||||||
|
|
||||||
|
## Sources
|
||||||
|
|
||||||
|
### Primary (HIGH confidence)
|
||||||
|
- Codebase inspection: `src/client/routes/global-items/$globalItemId.tsx` -- existing detail page pattern
|
||||||
|
- Codebase inspection: `src/client/routes/__root.tsx` -- panel instances to remove (lines 189-221)
|
||||||
|
- Codebase inspection: `src/client/stores/uiStore.ts` -- panel state to clean (full file reviewed)
|
||||||
|
- Codebase inspection: `src/client/components/ItemCard.tsx` -- current click handler using `openEditPanel`
|
||||||
|
- Codebase inspection: `src/client/components/CandidateCard.tsx` -- current click handler using `openCandidateEditPanel`
|
||||||
|
- Codebase inspection: `src/client/components/CandidateListItem.tsx` -- current click handler
|
||||||
|
- Codebase inspection: `src/client/components/ItemForm.tsx` -- form fields and validation to reuse
|
||||||
|
- Codebase inspection: `src/client/components/CandidateForm.tsx` -- candidate form to reuse
|
||||||
|
- Codebase inspection: `src/client/hooks/useItems.ts` -- `useItem`, `useUpdateItem`, `useDeleteItem`, `useDuplicateItem` hooks
|
||||||
|
- Codebase inspection: `src/client/hooks/useCandidates.ts` -- `useUpdateCandidate`, `useDeleteCandidate` hooks
|
||||||
|
- Codebase inspection: `src/client/hooks/useGlobalItems.ts` -- `useGlobalItem` hook with ownerCount
|
||||||
|
- Codebase inspection: `src/server/services/item.service.ts` -- `getItemById` COALESCE merge pattern
|
||||||
|
- Codebase inspection: `src/server/routes/items.ts` -- `withImageUrl` applied to single item responses
|
||||||
|
|
||||||
|
### Secondary (MEDIUM confidence)
|
||||||
|
- TanStack Router file-based routing: nested parameterized routes require directory restructuring (verified against established pattern in `src/client/routes/setups/$setupId.tsx`)
|
||||||
|
|
||||||
|
## Metadata
|
||||||
|
|
||||||
|
**Confidence breakdown:**
|
||||||
|
- Standard stack: HIGH -- no new dependencies, all existing
|
||||||
|
- Architecture: HIGH -- all patterns verified against existing codebase
|
||||||
|
- Pitfalls: HIGH -- identified from direct code inspection of affected files
|
||||||
|
|
||||||
|
**Research date:** 2026-04-06
|
||||||
|
**Valid until:** 2026-05-06 (stable -- frontend patterns, no external dependencies)
|
||||||
Reference in New Issue
Block a user