diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md index 80d6d60..822e10e 100644 --- a/.planning/ROADMAP.md +++ b/.planning/ROADMAP.md @@ -155,7 +155,7 @@ Plans: | 23. Manual Entry Fallback | v2.0 | 1/1 | Complete | 2026-04-06 | | 24. Public Access & Infrastructure | v2.1 | 2/2 | Complete | 2026-04-10 | | 25. Catalog Enrichment & Agent Tools | v2.1 | 1/2 | Complete | 2026-04-10 | -| 26. Discovery Landing Page | v2.1 | 3/3 | Complete | 2026-04-10 | +| 26. Discovery Landing Page | v2.1 | 3/3 | Complete | 2026-04-10 | ## Backlog diff --git a/.planning/STATE.md b/.planning/STATE.md index b8573da..1971fdc 100644 --- a/.planning/STATE.md +++ b/.planning/STATE.md @@ -4,7 +4,7 @@ milestone: v2.1 milestone_name: Public Discovery status: verifying stopped_at: Completed 26-03-PLAN.md -last_updated: "2026-04-10T13:02:50.041Z" +last_updated: "2026-04-10T13:08:14.422Z" last_activity: 2026-04-10 progress: total_phases: 6 @@ -25,8 +25,8 @@ See: .planning/PROJECT.md (updated 2026-04-09) ## Current Position -Phase: 26 (discovery-landing-page) — EXECUTING -Plan: 3 of 3 +Phase: 999.1 +Plan: Not started Status: Phase complete — ready for verification Last activity: 2026-04-10 diff --git a/.planning/phases/26-discovery-landing-page/26-VERIFICATION.md b/.planning/phases/26-discovery-landing-page/26-VERIFICATION.md new file mode 100644 index 0000000..150e4d2 --- /dev/null +++ b/.planning/phases/26-discovery-landing-page/26-VERIFICATION.md @@ -0,0 +1,147 @@ +--- +phase: 26-discovery-landing-page +verified: 2026-04-10T14:00:00Z +status: passed +score: 12/12 must-haves verified +re_verification: false +gaps: [] +human_verification: + - test: "Visual: Hero section and search bar appearance" + expected: "Centered 'Discover Gear' heading, subtitle, styled search bar with magnifier icon, no login redirect for anonymous user" + why_human: "Cannot verify visual rendering programmatically" + - test: "Interaction: Click search bar opens CatalogSearchOverlay" + expected: "Clicking or pressing Enter on the search bar div triggers openCatalogSearch('collection') and the overlay slides in" + why_human: "Requires browser interaction — unit tests don't cover overlay mount trigger" + - test: "Auth-conditional CTA: 'Go to Collection' visible only when authenticated" + expected: "Logged-in user sees 'Go to Collection' link; anonymous user does not" + why_human: "Requires live auth session to confirm conditional rendering" + - test: "Responsive layout at mobile width" + expected: "Single-column grid for Popular Setups; 2-column for Recently Added on narrow viewport" + why_human: "CSS grid breakpoints require visual verification" +--- + +# Phase 26: Discovery Landing Page Verification Report + +**Phase Goal:** The app opens to a public discovery feed with prominent catalog search, not a personal dashboard +**Verified:** 2026-04-10T14:00:00Z +**Status:** passed +**Re-verification:** No — initial verification + +## Goal Achievement + +### Observable Truths + +| # | Truth | Status | Evidence | +|---|-------|--------|---------| +| 1 | `getPopularSetups` returns public setups ordered by item count descending | VERIFIED | Service file line 45: `eq(setups.isPublic, true)`, ordered by `COUNT(setupItems.id) DESC`; 11/11 service tests pass | +| 2 | `getRecentGlobalItems` returns items ordered by createdAt descending | VERIFIED | Service file line 92: `.orderBy(desc(globalItems.createdAt))`; tests pass | +| 3 | `getTrendingCategories` returns categories ordered by item count, excluding nulls | VERIFIED | Service file line 121: `isNotNull(globalItems.category)`, `orderBy(desc(count(...)))`; tests pass | +| 4 | Cursor pagination returns next page without duplicates | VERIFIED | Composite `itemCount_id` cursor for setups (JS post-filter); ISO timestamp cursor for items; both tested with deduplication assertions | +| 5 | GET /api/discovery/setups returns popular setups for anonymous users | VERIFIED | Route registered at line 190 of index.ts; auth skip at line 170-171; 10/10 route tests pass | +| 6 | GET /api/discovery/items returns recent catalog items for anonymous users | VERIFIED | Route handler at lines 21-28 of discovery.ts; anonymous test passes without userId | +| 7 | GET /api/discovery/categories returns trending categories for anonymous users | VERIFIED | Route handler at lines 30-36 of discovery.ts; anonymous test passes | +| 8 | All discovery endpoints accept limit and cursor query params | VERIFIED | Routes parse `limit` and `cursor` query params with parseInt and Math.min cap at 50 | +| 9 | Discovery endpoints are rate-limited with browseTier | VERIFIED | index.ts lines 127-130: `app.use("/api/discovery/*", ...)` applies `browseTier` for all GET requests | +| 10 | Root URL shows a hero section with a catalog search bar | VERIFIED | index.tsx contains `function HeroSection(` with search div, `cursor-pointer`, Search icon | +| 11 | Clicking the search bar opens CatalogSearchOverlay | VERIFIED (wiring) | `openCatalogSearch("collection")` called in `onSearchFocus`; CatalogSearchOverlay reads `catalogSearchOpen` from same uiStore and is mounted in `__root.tsx` line 175 | +| 12 | Authenticated users see "Go to Collection" link in hero | VERIFIED | `!!auth?.user` guard at line 60 of index.tsx; Link to `/collection` rendered conditionally | + +**Score:** 12/12 truths verified + +### Required Artifacts + +| Artifact | Expected | Status | Details | +|----------|----------|--------|---------| +| `src/server/services/discovery.service.ts` | Discovery feed queries with cursor pagination | VERIFIED | 131 lines; exports `getPopularSetups`, `getRecentGlobalItems`, `getTrendingCategories`; real Drizzle queries against `globalItems`, `setups`, `setupItems`, `users` | +| `tests/services/discovery.service.test.ts` | Unit tests for all three service functions | VERIFIED | 247 lines (min_lines: 100 satisfied); 11 it() calls; 3 describe blocks; all pass | +| `src/server/routes/discovery.ts` | Hono route handlers for three discovery endpoints | VERIFIED | Exports `discoveryRoutes`; 3 GET handlers for `/setups`, `/items`, `/categories`; imports from discovery.service | +| `src/client/hooks/useDiscovery.ts` | React Query hooks for landing page data fetching | VERIFIED | Exports `useDiscoverySetups`, `useDiscoveryItems`, `useDiscoveryCategories` and interfaces `DiscoverySetup`, `DiscoveryCategory` | +| `tests/routes/discovery.test.ts` | Route-level integration tests | VERIFIED | 241 lines (min_lines: 50 satisfied); 10 it() calls; all pass | +| `src/client/routes/index.tsx` | Landing page with hero, popular setups, recent items, trending categories | VERIFIED | 192 lines (min_lines: 80 satisfied); contains `LandingPage`, `HeroSection`, `PopularSetupsSection`, `RecentItemsSection`, `TrendingCategoriesSection`; no DashboardPage/DashboardCard/useTotals remnants | +| `src/client/components/PublicSetupCard.tsx` | Enhanced setup card with optional itemCount and creatorName | VERIFIED | Contains `itemCount?: number`, `creatorName?: string \| null`; renders both conditionally; `cursor-pointer` applied | + +### Key Link Verification + +| From | To | Via | Status | Details | +|------|----|-----|--------|---------| +| `src/server/routes/discovery.ts` | `src/server/services/discovery.service.ts` | imports `getPopularSetups`, `getRecentGlobalItems`, `getTrendingCategories` | WIRED | Lines 1-6 of discovery.ts import all three service functions | +| `src/server/index.ts` | `src/server/routes/discovery.ts` | `app.route("/api/discovery", discoveryRoutes)` | WIRED | Line 16: import; line 127: rate limit; line 170: auth skip; line 190: route registration | +| `src/client/hooks/useDiscovery.ts` | `/api/discovery` | `apiGet` fetch calls | WIRED | Lines 42-44, 52-54, 61-63 call `apiGet` with `/api/discovery/setups`, `/api/discovery/items`, `/api/discovery/categories` | +| `src/client/routes/index.tsx` | `src/client/hooks/useDiscovery.ts` | imports all three hooks | WIRED | Lines 7-10 import all three hooks; used in `PopularSetupsSection`, `RecentItemsSection`, `TrendingCategoriesSection` | +| `src/client/routes/index.tsx` | `src/client/stores/uiStore.ts` | `openCatalogSearch` trigger from hero | WIRED | Line 20: `useUIStore((s) => s.openCatalogSearch)`; line 26: called on search focus | +| `src/client/routes/index.tsx` | `src/client/hooks/useAuth.ts` | auth state for conditional CTA | WIRED | Line 5: import; line 18: `useAuth()`; line 19: `!!auth?.user` | +| `src/client/routes/index.tsx` | `src/client/components/GlobalItemCard.tsx` | renders catalog items | WIRED | Line 3: import; lines 114-121: rendered in `RecentItemsSection` | +| `src/client/routes/index.tsx` | `src/client/components/PublicSetupCard.tsx` | renders setup cards | WIRED | Line 4: import; line 90: rendered in `PopularSetupsSection` | + +### Data-Flow Trace (Level 4) + +| Artifact | Data Variable | Source | Produces Real Data | Status | +|----------|---------------|--------|--------------------|--------| +| `src/client/routes/index.tsx` (PopularSetupsSection) | `data?.items` from `useDiscoverySetups` | `apiGet('/api/discovery/setups')` → `getPopularSetups` → Drizzle JOIN on `setups`/`setupItems`/`users` | Yes — real DB SELECT with COUNT | FLOWING | +| `src/client/routes/index.tsx` (RecentItemsSection) | `data?.items` from `useDiscoveryItems` | `apiGet('/api/discovery/items')` → `getRecentGlobalItems` → Drizzle SELECT on `globalItems` | Yes — real DB SELECT ORDER BY createdAt | FLOWING | +| `src/client/routes/index.tsx` (TrendingCategoriesSection) | `data` from `useDiscoveryCategories` | `apiGet('/api/discovery/categories')` → `getTrendingCategories` → Drizzle GROUP BY on `globalItems` | Yes — real DB SELECT GROUP BY category | FLOWING | + +### Behavioral Spot-Checks + +| Behavior | Command | Result | Status | +|----------|---------|--------|--------| +| Service tests pass | `bun test tests/services/discovery.service.test.ts` | 11 pass, 0 fail | PASS | +| Route tests pass | `bun test tests/routes/discovery.test.ts` | 10 pass, 0 fail | PASS | +| Client build succeeds | `bun run build` | Built in 625ms, no TypeScript errors | PASS | +| Full test suite (regression) | `bun test` | 285 pass, 15 fail — all 15 failures are pre-existing `storage.service.ts` export issue, unrelated to phase 26 | PASS (no regressions) | +| Old dashboard code removed | `grep DashboardPage/DashboardCard/useTotals index.tsx` | No matches | PASS | + +### Requirements Coverage + +| Requirement | Source Plan | Description | Status | Evidence | +|-------------|-------------|-------------|--------|---------| +| DISC-01 | 26-03 | Landing page displays an always-visible catalog search bar at the top | SATISFIED | `HeroSection` in `index.tsx` contains search bar div with Search icon; no auth gate on the landing page | +| DISC-02 | 26-01, 26-02, 26-03 | Landing page shows a feed of popular setups below the search | SATISFIED | `PopularSetupsSection` renders `PublicSetupCard` grid from `useDiscoverySetups`; backed by `getPopularSetups` service | +| DISC-03 | 26-01, 26-02, 26-03 | Landing page shows recently added catalog items | SATISFIED | `RecentItemsSection` renders `GlobalItemCard` grid from `useDiscoveryItems`; backed by `getRecentGlobalItems` service | +| DISC-04 | 26-01, 26-02, 26-03 | Landing page shows trending categories | SATISFIED | `TrendingCategoriesSection` renders category pills from `useDiscoveryCategories`; backed by `getTrendingCategories` service | +| DISC-05 | 26-03 | Authenticated users see a "Go to Collection" entry point from the landing page | SATISFIED | `!!auth?.user` conditional in `HeroSection` renders `Go to Collection` | +| INFR-02 | 26-01, 26-02 | Discovery feed endpoint uses cursor pagination for scalability | SATISFIED | `getPopularSetups` (composite `itemCount_id` cursor) and `getRecentGlobalItems` (ISO timestamp cursor) both implement cursor pagination with `hasMore`/`nextCursor`; categories use simple limit (bounded list, acceptable per RESEARCH.md) | + +No orphaned requirements — all 6 IDs (DISC-01 through DISC-05, INFR-02) appear in at least one plan's `requirements` field and are fully implemented. + +### Anti-Patterns Found + +| File | Line | Pattern | Severity | Impact | +|------|------|---------|----------|--------| +| `src/client/routes/index.tsx` | 78, 102, 135 | `return null` when no data | Info | Intentional empty-state handling per plan spec — sections hide when no data, not a stub | + +No blockers. The `return null` patterns are intentional design decisions documented in the plan and summaries: sections hide themselves when not loading and data is empty. + +### Human Verification Required + +#### 1. Visual appearance of landing page + +**Test:** Run `bun run dev`, open `http://localhost:5173/` in a browser +**Expected:** Hero section centered with "Discover Gear" heading, subtitle "Browse what other people carry", styled search bar with magnifier icon. Three sections below if data exists. No redirect to login. +**Why human:** Visual rendering cannot be verified programmatically. + +#### 2. Search bar triggers CatalogSearchOverlay + +**Test:** Click the search bar div or press Enter while focused on it +**Expected:** The CatalogSearchOverlay slides in (full-screen or modal), ready to search the catalog +**Why human:** Requires browser click event to trigger; wiring is confirmed in code but overlay animation/render requires visual confirmation. + +#### 3. "Go to Collection" CTA conditional on auth state + +**Test:** While logged out verify no CTA; after logging in verify "Go to Collection" appears in hero area and navigates to `/collection` +**Why human:** Requires live Logto OIDC session to test the `auth?.user` condition. + +#### 4. Responsive layout at mobile width + +**Test:** Resize browser to 375px width; verify Popular Setups shows 1 column, Recently Added shows 2 columns +**Expected:** Tailwind sm: breakpoints kick in correctly at responsive widths +**Why human:** CSS grid breakpoint behavior requires visual inspection. + +### Gaps Summary + +No gaps. All 12 truths verified. All artifacts exist, are substantive, wired, and have confirmed data flow paths. Both test suites pass. Build succeeds. The 15 pre-existing test failures are all caused by a `storage.service.ts` export naming issue documented in earlier summaries as pre-existing — none are from phase 26 code. + +--- + +_Verified: 2026-04-10T14:00:00Z_ +_Verifier: Claude (gsd-verifier)_