diff --git a/.gsd/milestones/M009/slices/S01/S01-PLAN.md b/.gsd/milestones/M009/slices/S01/S01-PLAN.md index bc03bc7..27849f1 100644 --- a/.gsd/milestones/M009/slices/S01/S01-PLAN.md +++ b/.gsd/milestones/M009/slices/S01/S01-PLAN.md @@ -24,7 +24,7 @@ Constraints: - Estimate: 30m - Files: frontend/src/pages/Home.tsx, frontend/src/App.css - Verify: cd frontend && npm run build 2>&1 | tail -5 -- [ ] **T02: Add popular topic quick-links fetched from topics API** — Add a popular topics section to the homepage that fetches topic data from the existing fetchTopics API, sorts sub-topics by technique_count, and renders the top 8-10 as clickable pill/chip links. Each pill navigates to the search page scoped to that topic. +- [x] **T02: Added popular topics pill-link section to homepage that fetches from topics API, sorts sub-topics by technique_count, and renders top 8 as clickable pills** — Add a popular topics section to the homepage that fetches topic data from the existing fetchTopics API, sorts sub-topics by technique_count, and renders the top 8-10 as clickable pill/chip links. Each pill navigates to the search page scoped to that topic. Steps: 1. Read current `frontend/src/pages/Home.tsx` (will include T01 changes) and `frontend/src/api/public-client.ts` (for TopicCategory/TopicSubTopic interfaces and fetchTopics function) diff --git a/.gsd/milestones/M009/slices/S01/tasks/T01-VERIFY.json b/.gsd/milestones/M009/slices/S01/tasks/T01-VERIFY.json new file mode 100644 index 0000000..ed4be16 --- /dev/null +++ b/.gsd/milestones/M009/slices/S01/tasks/T01-VERIFY.json @@ -0,0 +1,16 @@ +{ + "schemaVersion": 1, + "taskId": "T01", + "unitId": "M009/S01/T01", + "timestamp": 1774935330455, + "passed": true, + "discoverySource": "task-plan", + "checks": [ + { + "command": "cd frontend", + "exitCode": 0, + "durationMs": 6, + "verdict": "pass" + } + ] +} diff --git a/.gsd/milestones/M009/slices/S01/tasks/T02-SUMMARY.md b/.gsd/milestones/M009/slices/S01/tasks/T02-SUMMARY.md new file mode 100644 index 0000000..229900a --- /dev/null +++ b/.gsd/milestones/M009/slices/S01/tasks/T02-SUMMARY.md @@ -0,0 +1,76 @@ +--- +id: T02 +parent: S01 +milestone: M009 +provides: [] +requires: [] +affects: [] +key_files: ["frontend/src/pages/Home.tsx", "frontend/src/App.css"] +key_decisions: ["Used uppercase secondary-text heading for Popular Topics to differentiate from primary content hierarchy"] +patterns_established: [] +drill_down_paths: [] +observability_surfaces: [] +duration: "" +verification_result: "cd frontend && npm run build — zero errors, clean build (52KB CSS, 224KB JS gzipped in 748ms)" +completed_at: 2026-03-31T05:37:07.483Z +blocker_discovered: false +--- + +# T02: Added popular topics pill-link section to homepage that fetches from topics API, sorts sub-topics by technique_count, and renders top 8 as clickable pills + +> Added popular topics pill-link section to homepage that fetches from topics API, sorts sub-topics by technique_count, and renders top 8 as clickable pills + +## What Happened +--- +id: T02 +parent: S01 +milestone: M009 +key_files: + - frontend/src/pages/Home.tsx + - frontend/src/App.css +key_decisions: + - Used uppercase secondary-text heading for Popular Topics to differentiate from primary content hierarchy +duration: "" +verification_result: passed +completed_at: 2026-03-31T05:37:07.484Z +blocker_discovered: false +--- + +# T02: Added popular topics pill-link section to homepage that fetches from topics API, sorts sub-topics by technique_count, and renders top 8 as clickable pills + +**Added popular topics pill-link section to homepage that fetches from topics API, sorts sub-topics by technique_count, and renders top 8 as clickable pills** + +## What Happened + +Imported fetchTopics into Home.tsx, added useEffect that flattens all sub_topics across categories, sorts by technique_count descending, takes top 8. Renders as pill links to /search?q={topic}&scope=topics. Section hidden when no data or API error. CSS uses existing pill token system with bordered hover state transitioning to cyan accent. + +## Verification + +cd frontend && npm run build — zero errors, clean build (52KB CSS, 224KB JS gzipped in 748ms) + +## Verification Evidence + +| # | Command | Exit Code | Verdict | Duration | +|---|---------|-----------|---------|----------| +| 1 | `cd frontend && npm run build` | 0 | ✅ pass | 748ms | + + +## Deviations + +Removed unused TopicCategory type import that caused TS6133 + +## Known Issues + +None. + +## Files Created/Modified + +- `frontend/src/pages/Home.tsx` +- `frontend/src/App.css` + + +## Deviations +Removed unused TopicCategory type import that caused TS6133 + +## Known Issues +None. diff --git a/frontend/src/App.css b/frontend/src/App.css index 21a64d2..4816e47 100644 --- a/frontend/src/App.css +++ b/frontend/src/App.css @@ -953,6 +953,50 @@ a.app-footer__repo:hover { transform: translateY(-1px); } +/* ── Popular topics quick-links ───────────────────────────────────────────── */ + +.home-popular-topics { + margin-top: 2.5rem; + text-align: center; +} + +.home-popular-topics__title { + font-size: 0.875rem; + font-weight: 600; + color: var(--color-text-secondary); + text-transform: uppercase; + letter-spacing: 0.05em; + margin-bottom: 0.75rem; +} + +.home-popular-topics__list { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.5rem; + max-width: 36rem; + margin: 0 auto; +} + +.pill--topic-quick { + display: inline-block; + padding: 0.375rem 0.875rem; + border-radius: 9999px; + font-size: 0.8125rem; + font-weight: 500; + background: var(--color-pill-bg); + color: var(--color-pill-text); + border: 1px solid var(--color-border); + text-decoration: none; + transition: border-color 0.15s, background 0.15s, color 0.15s; +} + +.pill--topic-quick:hover { + border-color: var(--color-accent); + background: var(--color-accent-subtle); + color: var(--color-accent); +} + /* ── Search form ──────────────────────────────────────────────────────────── */ .search-container { diff --git a/frontend/src/pages/Home.tsx b/frontend/src/pages/Home.tsx index 0e85bba..1fc6ad5 100644 --- a/frontend/src/pages/Home.tsx +++ b/frontend/src/pages/Home.tsx @@ -11,6 +11,7 @@ import { Link, useNavigate } from "react-router-dom"; import { searchApi, fetchTechniques, + fetchTopics, type SearchResultItem, type TechniqueListItem, } from "../api/public-client"; @@ -21,6 +22,7 @@ export default function Home() { const [showDropdown, setShowDropdown] = useState(false); const [recent, setRecent] = useState([]); const [recentLoading, setRecentLoading] = useState(true); + const [popularTopics, setPopularTopics] = useState<{name: string; count: number}[]>([]); const navigate = useNavigate(); const inputRef = useRef(null); const debounceRef = useRef | null>(null); @@ -49,6 +51,26 @@ export default function Home() { }; }, []); + // Load popular topics + useEffect(() => { + let cancelled = false; + void (async () => { + try { + const categories = await fetchTopics(); + const all = categories.flatMap((cat) => + cat.sub_topics.map((st) => ({ name: st.name, count: st.technique_count })) + ); + all.sort((a, b) => b.count - a.count); + if (!cancelled) setPopularTopics(all.slice(0, 8)); + } catch { + // optional section — silently ignore + } + })(); + return () => { + cancelled = true; + }; + }, []); + // Close dropdown on outside click useEffect(() => { function handleClick(e: MouseEvent) { @@ -198,6 +220,23 @@ export default function Home() { Start Exploring + + {popularTopics.length > 0 && ( +
+

Popular Topics

+
+ {popularTopics.map((topic) => ( + + {topic.name} + + ))} +
+
+ )} {/* Navigation cards */}