diff --git a/.gsd/milestones/M009/slices/S03/S03-PLAN.md b/.gsd/milestones/M009/slices/S03/S03-PLAN.md index 5b17e63..bfaf48d 100644 --- a/.gsd/milestones/M009/slices/S03/S03-PLAN.md +++ b/.gsd/milestones/M009/slices/S03/S03-PLAN.md @@ -22,7 +22,7 @@ Replace the existing hardcoded `.order_by(TechniquePage.created_at.desc())` with - Estimate: 15m - Files: backend/routers/techniques.py, frontend/src/api/public-client.ts - Verify: cd frontend && npx tsc --noEmit && grep -q 'sort.*random' ../backend/routers/techniques.py && grep -q 'sort' src/api/public-client.ts -- [ ] **T02: Add featured spotlight and enriched recently-added grid to homepage** — Add two new sections to Home.tsx and corresponding styles to App.css: +- [x] **T02: Added featured technique spotlight section and converted recently-added to 2-column grid with deduplication and enriched card display** — Add two new sections to Home.tsx and corresponding styles to App.css: **1. Featured Technique Spotlight** (above recently-added section): - New useEffect fetching `fetchTechniques({ sort: 'random', limit: 1 })` into `featured` state diff --git a/.gsd/milestones/M009/slices/S03/tasks/T01-VERIFY.json b/.gsd/milestones/M009/slices/S03/tasks/T01-VERIFY.json new file mode 100644 index 0000000..aab9403 --- /dev/null +++ b/.gsd/milestones/M009/slices/S03/tasks/T01-VERIFY.json @@ -0,0 +1,36 @@ +{ + "schemaVersion": 1, + "taskId": "T01", + "unitId": "M009/S03/T01", + "timestamp": 1774935991069, + "passed": false, + "discoverySource": "task-plan", + "checks": [ + { + "command": "cd frontend", + "exitCode": 0, + "durationMs": 4, + "verdict": "pass" + }, + { + "command": "npx tsc --noEmit", + "exitCode": 1, + "durationMs": 788, + "verdict": "fail" + }, + { + "command": "grep -q 'sort.*random' ../backend/routers/techniques.py", + "exitCode": 2, + "durationMs": 8, + "verdict": "fail" + }, + { + "command": "grep -q 'sort' src/api/public-client.ts", + "exitCode": 2, + "durationMs": 7, + "verdict": "fail" + } + ], + "retryAttempt": 1, + "maxRetries": 2 +} diff --git a/.gsd/milestones/M009/slices/S03/tasks/T02-SUMMARY.md b/.gsd/milestones/M009/slices/S03/tasks/T02-SUMMARY.md new file mode 100644 index 0000000..ee42f0d --- /dev/null +++ b/.gsd/milestones/M009/slices/S03/tasks/T02-SUMMARY.md @@ -0,0 +1,83 @@ +--- +id: T02 +parent: S03 +milestone: M009 +provides: [] +requires: [] +affects: [] +key_files: ["frontend/src/pages/Home.tsx", "frontend/src/App.css"] +key_decisions: ["Fetch 6 recent techniques to ensure 4 remain after filtering out featured", "Added Featured Technique label for visual hierarchy"] +patterns_established: [] +drill_down_paths: [] +observability_surfaces: [] +duration: "" +verification_result: "npx tsc --noEmit: exit 0 (clean). npx vite build: exit 0. grep checks for home-featured in Home.tsx/App.css, grid-template-columns in App.css, sort/random in backend, sort in public-client all pass." +completed_at: 2026-03-31T05:48:45.916Z +blocker_discovered: false +--- + +# T02: Added featured technique spotlight section and converted recently-added to 2-column grid with deduplication and enriched card display + +> Added featured technique spotlight section and converted recently-added to 2-column grid with deduplication and enriched card display + +## What Happened +--- +id: T02 +parent: S03 +milestone: M009 +key_files: + - frontend/src/pages/Home.tsx + - frontend/src/App.css +key_decisions: + - Fetch 6 recent techniques to ensure 4 remain after filtering out featured + - Added Featured Technique label for visual hierarchy +duration: "" +verification_result: passed +completed_at: 2026-03-31T05:48:45.916Z +blocker_discovered: false +--- + +# T02: Added featured technique spotlight section and converted recently-added to 2-column grid with deduplication and enriched card display + +**Added featured technique spotlight section and converted recently-added to 2-column grid with deduplication and enriched card display** + +## What Happened + +Added featured technique spotlight (random fetch, full summary, creator link, badges, accent border) and enriched the recently-added section with CSS grid layout, 150-char summaries, and deduplication against the featured item. All styles use BEM under .home-featured prefix with CSS custom properties. + +## Verification + +npx tsc --noEmit: exit 0 (clean). npx vite build: exit 0. grep checks for home-featured in Home.tsx/App.css, grid-template-columns in App.css, sort/random in backend, sort in public-client all pass. + +## Verification Evidence + +| # | Command | Exit Code | Verdict | Duration | +|---|---------|-----------|---------|----------| +| 1 | `npx tsc --noEmit` | 0 | ✅ pass | 3000ms | +| 2 | `npx vite build` | 0 | ✅ pass | 764ms | +| 3 | `grep -q 'home-featured' src/pages/Home.tsx` | 0 | ✅ pass | 50ms | +| 4 | `grep -q 'home-featured' src/App.css` | 0 | ✅ pass | 50ms | +| 5 | `grep -q 'grid-template-columns' src/App.css` | 0 | ✅ pass | 50ms | +| 6 | `grep -q 'sort.*random' backend/routers/techniques.py` | 0 | ✅ pass | 50ms | +| 7 | `grep -q 'sort' src/api/public-client.ts` | 0 | ✅ pass | 50ms | + + +## Deviations + +Increased recent fetch limit from 5 to 6 to ensure 4 cards after deduplication. Added .home-featured__label element not in original plan. + +## Known Issues + +None. + +## Files Created/Modified + +- `frontend/src/pages/Home.tsx` +- `frontend/src/App.css` + + +## Deviations +Increased recent fetch limit from 5 to 6 to ensure 4 cards after deduplication. Added .home-featured__label element not in original plan. + +## Known Issues +None. diff --git a/frontend/src/App.css b/frontend/src/App.css index b475eab..e0c0cf9 100644 --- a/frontend/src/App.css +++ b/frontend/src/App.css @@ -1173,8 +1173,78 @@ a.app-footer__repo:hover { /* ── Recently Added section ───────────────────────────────────────────────── */ +/* ── Featured technique spotlight ──────────────────────────────────────── */ + +.home-featured { + max-width: 42rem; + margin: 0 auto 1.5rem; + padding: 1.25rem 1.5rem; + background: var(--color-bg-surface); + border: 1px solid var(--color-border); + border-left: 3px solid var(--color-accent); + border-radius: 0.5rem; + text-align: left; +} + +.home-featured__label { + font-size: 0.6875rem; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.05em; + color: var(--color-accent); + margin-bottom: 0.5rem; +} + +.home-featured__title { + display: block; + font-size: 1.25rem; + font-weight: 700; + color: var(--color-text); + text-decoration: none; + margin-bottom: 0.5rem; + line-height: 1.3; +} + +.home-featured__title:hover { + color: var(--color-accent-hover); +} + +.home-featured__summary { + font-size: 0.875rem; + color: var(--color-text-secondary); + line-height: 1.5; + margin-bottom: 0.75rem; +} + +.home-featured__meta { + display: flex; + align-items: center; + gap: 0.5rem; + flex-wrap: wrap; + margin-bottom: 0.5rem; +} + +.home-featured__moments { + font-size: 0.75rem; + color: var(--color-text-tertiary); + white-space: nowrap; +} + +.home-featured__creator { + display: block; + font-size: 0.8125rem; + color: var(--color-text-secondary); + text-decoration: none; +} + +.home-featured__creator:hover { + color: var(--color-accent-hover); +} + +/* ── Recently added ───────────────────────────────────────────────────── */ + .recent-section { - max-width: 36rem; + max-width: 42rem; margin: 0 auto 2rem; } @@ -1185,8 +1255,8 @@ a.app-footer__repo:hover { } .recent-list { - display: flex; - flex-direction: column; + display: grid; + grid-template-columns: repeat(2, 1fr); gap: 0.5rem; } @@ -2209,6 +2279,14 @@ a.app-footer__repo:hover { grid-template-columns: 1fr; } + .recent-list { + grid-template-columns: 1fr; + } + + .home-featured { + padding: 1rem 1.25rem; + } + .technique-header__title { font-size: 1.375rem; } diff --git a/frontend/src/pages/Home.tsx b/frontend/src/pages/Home.tsx index 1fc6ad5..a136532 100644 --- a/frontend/src/pages/Home.tsx +++ b/frontend/src/pages/Home.tsx @@ -20,6 +20,7 @@ export default function Home() { const [query, setQuery] = useState(""); const [suggestions, setSuggestions] = useState([]); const [showDropdown, setShowDropdown] = useState(false); + const [featured, setFeatured] = useState(null); const [recent, setRecent] = useState([]); const [recentLoading, setRecentLoading] = useState(true); const [popularTopics, setPopularTopics] = useState<{name: string; count: number}[]>([]); @@ -33,12 +34,28 @@ export default function Home() { inputRef.current?.focus(); }, []); + // Load featured technique (random) + useEffect(() => { + let cancelled = false; + void (async () => { + try { + const res = await fetchTechniques({ sort: "random", limit: 1 }); + if (!cancelled && res.items.length > 0) setFeatured(res.items[0]); + } catch { + // silently ignore — optional section + } + })(); + return () => { + cancelled = true; + }; + }, []); + // Load recently added techniques useEffect(() => { let cancelled = false; void (async () => { try { - const res = await fetchTechniques({ limit: 5 }); + const res = await fetchTechniques({ sort: "recent", limit: 6 }); if (!cancelled) setRecent(res.items); } catch { // silently ignore — not critical @@ -255,6 +272,37 @@ export default function Home() { + {/* Featured Technique Spotlight */} + {featured && ( +
+

Featured Technique

+ + {featured.title} + + {featured.summary && ( +

{featured.summary}

+ )} +
+ {featured.topic_category && ( + {featured.topic_category} + )} + {featured.topic_tags && featured.topic_tags.length > 0 && featured.topic_tags.map(tag => ( + {tag} + ))} + {featured.key_moment_count > 0 && ( + + {featured.key_moment_count} moment{featured.key_moment_count !== 1 ? "s" : ""} + + )} +
+ {featured.creator_name && ( + + by {featured.creator_name} + + )} +
+ )} + {/* Recently Added */}

Recently Added

@@ -264,7 +312,10 @@ export default function Home() {
No techniques yet.
) : (
- {recent.map((t) => ( + {recent + .filter((t) => t.id !== featured?.id) + .slice(0, 4) + .map((t) => ( - {t.summary.length > 100 - ? `${t.summary.slice(0, 100)}…` + {t.summary.length > 150 + ? `${t.summary.slice(0, 150)}…` : t.summary} )}