148 lines
13 KiB
Markdown
148 lines
13 KiB
Markdown
---
|
|
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 `<Link to="/collection">Go to Collection</Link>` |
|
|
| 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)_
|