13 KiB
phase, verified, status, score, re_verification, gaps, human_verification
| phase | verified | status | score | re_verification | gaps | human_verification | ||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 26-discovery-landing-page | 2026-04-10T14:00:00Z | passed | 12/12 must-haves verified | false |
|
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)