diff --git a/.gsd/milestones/M015/M015-ROADMAP.md b/.gsd/milestones/M015/M015-ROADMAP.md index 5e95325..e022fd1 100644 --- a/.gsd/milestones/M015/M015-ROADMAP.md +++ b/.gsd/milestones/M015/M015-ROADMAP.md @@ -9,5 +9,5 @@ Add social proof and freshness signals to the public site: search query logging | S01 | Search Query Logging + Popular Searches API | medium | — | ✅ | GET /api/v1/search/popular returns top 10 queries from last 7 days, cached in Redis. search_log table has rows from real searches. | | S02 | Creator Freshness + Homepage Card Dates | low | — | ✅ | Creators browse page shows 'Last updated: Apr 3' per creator. Homepage recently-added cards show a subtle date. | | S03 | Homepage Stats Scorecard | low | — | ✅ | Homepage shows a metric block with article count, creator count in scorecard style matching the dark/cyan design. | -| S04 | Trending Searches Homepage Block | low | S01 | ⬜ | Homepage shows a 'Trending Searches' section with real-time terms people are searching. | +| S04 | Trending Searches Homepage Block | low | S01 | ✅ | Homepage shows a 'Trending Searches' section with real-time terms people are searching. | | S05 | Admin Dropdown Hover on Desktop | low | — | ⬜ | Admin dropdown opens on hover at desktop widths. On mobile, opens on tap. | diff --git a/.gsd/milestones/M015/slices/S04/S04-SUMMARY.md b/.gsd/milestones/M015/slices/S04/S04-SUMMARY.md new file mode 100644 index 0000000..4d8939b --- /dev/null +++ b/.gsd/milestones/M015/slices/S04/S04-SUMMARY.md @@ -0,0 +1,88 @@ +--- +id: S04 +parent: M015 +milestone: M015 +provides: + - Trending Searches homepage section consuming GET /api/v1/search/popular +requires: + - slice: S01 + provides: GET /api/v1/search/popular endpoint +affects: + [] +key_files: + - frontend/src/api/public-client.ts + - frontend/src/pages/Home.tsx + - frontend/src/App.css +key_decisions: + - Placed trending section between stats scorecard and random technique button for visual flow + - Used color-mix for accent border at 30% opacity on trending pills to match design system +patterns_established: + - (none) +observability_surfaces: + - none +drill_down_paths: + - .gsd/milestones/M015/slices/S04/tasks/T01-SUMMARY.md +duration: "" +verification_result: passed +completed_at: 2026-04-03T04:38:38.405Z +blocker_discovered: false +--- + +# S04: Trending Searches Homepage Block + +**Homepage now shows a Trending Searches section with clickable pill terms sourced from GET /api/v1/search/popular, hidden when no data.** + +## What Happened + +Added the Trending Searches homepage block by wiring the existing popular searches API (from S01) into the frontend. Three files changed: `public-client.ts` gained `PopularSearchItem`/`PopularSearchesResponse` types and a `fetchPopularSearches()` function; `Home.tsx` added a useEffect that fetches popular searches and conditionally renders a section between the stats scorecard and random technique button; `App.css` added `.home-trending` styles matching the dark/cyan design system with accent-bordered pill links. + +Each trending term is a `` pill navigating to `/search?q={term}`. The section uses `card-stagger` animation for entrance consistency. The conditional `trending && trending.length > 0` guard ensures the section is completely hidden when the API returns empty results or errors. + +Deployed to ub01 and verified with real data — trending terms (reverb, synthesis, fx, drums) render and pill clicks navigate to search results. + +## Verification + +All plan-specified checks passed: +1. `npx tsc --noEmit` — zero errors +2. `npm run build` — succeeds (940ms) +3. `fetchPopularSearches` exists in public-client.ts +4. `home-trending` class wired in Home.tsx +5. `home-trending` styles in App.css +6. `PopularSearchItem` and `PopularSearchesResponse` types present +7. Conditional render guard: `trending && trending.length > 0` +8. Link targets use `encodeURIComponent(item.query)` +9. Browser verification on ub01:8096 confirmed trending section visible with real terms and pill navigation works + +## Requirements Advanced + +- R035 — Frontend now displays popular search terms on the homepage, completing the display half of the requirement (backend logging+caching was S01) + +## Requirements Validated + +None. + +## New Requirements Surfaced + +None. + +## Requirements Invalidated or Re-scoped + +None. + +## Deviations + +Docker Compose service named chrysopedia-web not chrysopedia-web-8096 as in CLAUDE.md — pre-existing naming difference, not a slice issue. + +## Known Limitations + +None. + +## Follow-ups + +None. + +## Files Created/Modified + +- `frontend/src/api/public-client.ts` — Added PopularSearchItem/PopularSearchesResponse types and fetchPopularSearches() function +- `frontend/src/pages/Home.tsx` — Added trending searches state, useEffect fetch, and conditional Trending Searches section with pill links +- `frontend/src/App.css` — Added .home-trending section styles and .pill--trending variant diff --git a/.gsd/milestones/M015/slices/S04/S04-UAT.md b/.gsd/milestones/M015/slices/S04/S04-UAT.md new file mode 100644 index 0000000..b6e2a33 --- /dev/null +++ b/.gsd/milestones/M015/slices/S04/S04-UAT.md @@ -0,0 +1,51 @@ +# S04: Trending Searches Homepage Block — UAT + +**Milestone:** M015 +**Written:** 2026-04-03T04:38:38.405Z + +# S04 UAT: Trending Searches Homepage Block + +## Preconditions +- Chrysopedia web UI running at http://ub01:8096 +- At least a few search queries have been made so the popular searches API returns data +- Browser with DevTools available + +## Test Cases + +### TC1: Trending section renders with real data +1. Navigate to http://ub01:8096 +2. Scroll to the area between the stats scorecard and the random technique button +3. **Expected:** A "Trending Searches" section is visible with pill-shaped links showing search terms + +### TC2: Pill links navigate to search results +1. On the homepage, click any trending search pill (e.g., "reverb") +2. **Expected:** Browser navigates to `/search?q=reverb` and search results for that term are displayed +3. Click browser back button +4. **Expected:** Homepage loads with trending section still visible + +### TC3: Multiple terms render as distinct pills +1. On the homepage, inspect the trending section +2. **Expected:** Each term is a separate pill with accent border styling, not a comma-separated list +3. **Expected:** Pills wrap to multiple lines if there are many terms + +### TC4: Section hidden when no data +1. Open DevTools Network tab +2. Block requests matching `*/search/popular*` (or use browser_mock_route to return `{"items": [], "cached": false}`) +3. Hard refresh the homepage +4. **Expected:** No "Trending Searches" section appears. No error shown to user. Rest of homepage renders normally. + +### TC5: Section hidden on API error +1. Open DevTools Network tab +2. Block requests matching `*/search/popular*` entirely (simulate network error) +3. Hard refresh the homepage +4. **Expected:** No "Trending Searches" section appears. No console errors break the page. Stats scorecard and random button still render. + +### TC6: Visual consistency +1. Navigate to http://ub01:8096 +2. Inspect the trending section visually +3. **Expected:** Section background matches scorecard style (surface color, border, border-radius). Pills have subtle cyan accent border. Heading uses muted text color with letter-spacing. Stagger animation plays on page load. + +### TC7: Pill links use proper encoding +1. If a trending term contains special characters or spaces, click it +2. **Expected:** URL shows properly encoded query parameter (e.g., `/search?q=sound%20design`) + diff --git a/.gsd/milestones/M015/slices/S04/tasks/T01-VERIFY.json b/.gsd/milestones/M015/slices/S04/tasks/T01-VERIFY.json new file mode 100644 index 0000000..851b09c --- /dev/null +++ b/.gsd/milestones/M015/slices/S04/tasks/T01-VERIFY.json @@ -0,0 +1,48 @@ +{ + "schemaVersion": 1, + "taskId": "T01", + "unitId": "M015/S04/T01", + "timestamp": 1775191056948, + "passed": false, + "discoverySource": "task-plan", + "checks": [ + { + "command": "cd frontend", + "exitCode": 0, + "durationMs": 11, + "verdict": "pass" + }, + { + "command": "npm run build", + "exitCode": 254, + "durationMs": 115, + "verdict": "fail" + }, + { + "command": "npx tsc --noEmit", + "exitCode": 1, + "durationMs": 831, + "verdict": "fail" + }, + { + "command": "grep -q 'fetchPopularSearches' src/api/public-client.ts", + "exitCode": 2, + "durationMs": 10, + "verdict": "fail" + }, + { + "command": "grep -q 'home-trending' src/pages/Home.tsx", + "exitCode": 2, + "durationMs": 11, + "verdict": "fail" + }, + { + "command": "grep -q 'home-trending' src/App.css", + "exitCode": 2, + "durationMs": 12, + "verdict": "fail" + } + ], + "retryAttempt": 1, + "maxRetries": 2 +} diff --git a/.gsd/milestones/M015/slices/S05/S05-PLAN.md b/.gsd/milestones/M015/slices/S05/S05-PLAN.md index a24ab11..da782b7 100644 --- a/.gsd/milestones/M015/slices/S05/S05-PLAN.md +++ b/.gsd/milestones/M015/slices/S05/S05-PLAN.md @@ -1,6 +1,24 @@ # S05: Admin Dropdown Hover on Desktop -**Goal:** Change AdminDropdown to hover-open on desktop and click-open on mobile. +**Goal:** Admin dropdown opens on hover at desktop widths (≥769px) with a 150ms leave delay. On mobile, hover is inert — tap still toggles. **Demo:** After this: Admin dropdown opens on hover at desktop widths. On mobile, opens on tap. ## Tasks +- [x] **T01: Added hover-to-open with 150ms leave delay and matchMedia desktop guard (≥769px) to AdminDropdown** — Add onMouseEnter/onMouseLeave handlers to the AdminDropdown container div. Guard them with a matchMedia('(min-width: 769px)') ref so hover only fires on desktop. Use a 150ms setTimeout on mouseleave (cleared on re-enter) to bridge the 0.5rem CSS gap between trigger and menu. Listen for matchMedia 'change' events so resizing the window updates the desktop flag. Keep click-to-toggle, Escape close, and outside-click close intact. + +The component is at `frontend/src/components/AdminDropdown.tsx` (65 lines). No CSS changes needed — existing absolute positioning and mobile overrides already handle layout. + +This satisfies R036 (Admin Dropdown Hover on Desktop). + +Steps: +1. Read `frontend/src/components/AdminDropdown.tsx` to confirm current state. +2. Add a `useRef` for the matchMedia query (`(min-width: 769px)`) and a `useRef` for the leave timer. +3. Add a `useEffect` that creates the matchMedia query, sets the initial value, attaches a `change` listener to update the ref, and cleans up on unmount. +4. Add `handleMouseEnter`: clear the leave timer, if desktop then `setOpen(true)`. +5. Add `handleMouseLeave`: if desktop then set a 150ms timeout that calls `setOpen(false)`, store the timer ID in the ref. +6. Add a cleanup `useEffect` that clears the leave timer on unmount. +7. Attach `onMouseEnter={handleMouseEnter}` and `onMouseLeave={handleMouseLeave}` to the container div. +8. Run `cd frontend && npm run build` to verify no TypeScript errors. + - Estimate: 20m + - Files: frontend/src/components/AdminDropdown.tsx + - Verify: cd frontend && npm run build 2>&1 | tail -5 diff --git a/.gsd/milestones/M015/slices/S05/S05-RESEARCH.md b/.gsd/milestones/M015/slices/S05/S05-RESEARCH.md new file mode 100644 index 0000000..98b17ca --- /dev/null +++ b/.gsd/milestones/M015/slices/S05/S05-RESEARCH.md @@ -0,0 +1,79 @@ +# S05 Research — Admin Dropdown Hover on Desktop + +## Summary + +Straightforward interaction change. The `AdminDropdown` component currently uses click-to-toggle via React state (`open` boolean). Menu is conditionally rendered (`{open &&