diff --git a/.gsd/milestones/M009/M009-ROADMAP.md b/.gsd/milestones/M009/M009-ROADMAP.md index 1f74de6..e712278 100644 --- a/.gsd/milestones/M009/M009-ROADMAP.md +++ b/.gsd/milestones/M009/M009-ROADMAP.md @@ -7,5 +7,5 @@ The homepage is Chrysopedia's front door. Right now it's a search box — functi | ID | Slice | Risk | Depends | Done | After this | |----|-------|------|---------|------|------------| | S01 | Homepage Hero & Value Proposition | medium | — | ✅ | Homepage shows tagline, value description, how-it-works steps, Start Exploring CTA, and popular topic quick-links above the fold | -| S02 | About Page | low | — | ⬜ | About page at /about explains what Chrysopedia is, how content is extracted, and who maintains it. Link visible in footer. | +| S02 | About Page | low | — | ✅ | About page at /about explains what Chrysopedia is, how content is extracted, and who maintains it. Link visible in footer. | | S03 | Featured Content & Content Teasers | low | S01 | ⬜ | Homepage shows a featured technique spotlight and Recently Added section with enriched cards | diff --git a/.gsd/milestones/M009/slices/S02/S02-SUMMARY.md b/.gsd/milestones/M009/slices/S02/S02-SUMMARY.md new file mode 100644 index 0000000..2915ccf --- /dev/null +++ b/.gsd/milestones/M009/slices/S02/S02-SUMMARY.md @@ -0,0 +1,76 @@ +--- +id: S02 +parent: M009 +milestone: M009 +provides: + - About page at /about with what/how/who sections + - Footer navigation link to /about +requires: + [] +affects: + [] +key_files: + - frontend/src/pages/About.tsx + - frontend/src/App.tsx + - frontend/src/components/AppFooter.tsx + - frontend/src/App.css +key_decisions: + - Used CSS counter-based numbered pipeline steps rather than manual numbering for the extraction pipeline section +patterns_established: + - (none) +observability_surfaces: + - none +drill_down_paths: + - .gsd/milestones/M009/slices/S02/tasks/T01-SUMMARY.md +duration: "" +verification_result: passed +completed_at: 2026-03-31T05:42:31.536Z +blocker_discovered: false +--- + +# S02: About Page + +**Added /about page with three content sections and a footer navigation link.** + +## What Happened + +Created About.tsx with three sections: what Chrysopedia is (purpose and name origin), how content is extracted (five-step pipeline breakdown using CSS counter-based numbered steps), and who maintains it (links to xpltd GitHub org and repo). Added the /about route in App.tsx. Added an About link in AppFooter.tsx using react-router-dom Link. Styled with .about-* classes in App.css following existing BEM and CSS custom property patterns, including a responsive breakpoint for the hero title. TypeScript compiles cleanly, Vite build produces 47 modules with no warnings. + +## Verification + +npx tsc --noEmit: exit 0, zero errors. npm run build: exit 0, 47 modules, no warnings. Route /about registered in App.tsx. Footer link present in AppFooter.tsx pointing to /about. + +## Requirements Advanced + +None. + +## Requirements Validated + +None. + +## New Requirements Surfaced + +None. + +## Requirements Invalidated or Re-scoped + +None. + +## Deviations + +None. + +## Known Limitations + +None. + +## Follow-ups + +None. + +## Files Created/Modified + +- `frontend/src/pages/About.tsx` — New page component with three content sections (what, how, who) +- `frontend/src/App.tsx` — Added /about route +- `frontend/src/components/AppFooter.tsx` — Added About link +- `frontend/src/App.css` — Added .about-* styles with responsive breakpoint diff --git a/.gsd/milestones/M009/slices/S02/S02-UAT.md b/.gsd/milestones/M009/slices/S02/S02-UAT.md new file mode 100644 index 0000000..68efe99 --- /dev/null +++ b/.gsd/milestones/M009/slices/S02/S02-UAT.md @@ -0,0 +1,38 @@ +# S02: About Page — UAT + +**Milestone:** M009 +**Written:** 2026-03-31T05:42:31.536Z + +## UAT: About Page + +### Preconditions +- Frontend is running (dev server or production build served) +- Browser open to the application root + +### Test 1: Footer link navigates to About page +1. Scroll to the page footer on any page (homepage, technique page, etc.) +2. Locate the "About" link in the footer +3. Click the link +4. **Expected:** Browser navigates to `/about`. Page displays "About Chrysopedia" heading. + +### Test 2: What section content +1. Navigate to `/about` +2. Locate the "What Is Chrysopedia?" section +3. **Expected:** Section explains Chrysopedia turns long-form tutorials into a searchable knowledge base. Mentions the name origin (chrysopoeia — transmutation). + +### Test 3: How section content +1. On the `/about` page, locate the "How Content Is Extracted" section +2. **Expected:** Five numbered pipeline steps are displayed (Transcription, Segmentation, Key Moment Extraction, Classification, Technique Synthesis). Each step has a brief description. + +### Test 4: Who section content +1. On the `/about` page, locate the "Who Maintains This" section +2. **Expected:** Section mentions xpltd with a link to the GitHub organization. Contains a link to the Chrysopedia repository. + +### Test 5: Direct URL access +1. Type `/about` directly in the browser address bar and press Enter +2. **Expected:** About page loads correctly without 404 or redirect. + +### Test 6: Responsive layout +1. Navigate to `/about` +2. Resize browser to mobile width (~375px) +3. **Expected:** Hero title and section content reflow to single column. Text remains readable. No horizontal overflow. diff --git a/.gsd/milestones/M009/slices/S02/tasks/T01-VERIFY.json b/.gsd/milestones/M009/slices/S02/tasks/T01-VERIFY.json new file mode 100644 index 0000000..cbaa368 --- /dev/null +++ b/.gsd/milestones/M009/slices/S02/tasks/T01-VERIFY.json @@ -0,0 +1,30 @@ +{ + "schemaVersion": 1, + "taskId": "T01", + "unitId": "M009/S02/T01", + "timestamp": 1774935714608, + "passed": false, + "discoverySource": "task-plan", + "checks": [ + { + "command": "cd frontend", + "exitCode": 0, + "durationMs": 4, + "verdict": "pass" + }, + { + "command": "npx tsc --noEmit", + "exitCode": 1, + "durationMs": 755, + "verdict": "fail" + }, + { + "command": "npm run build", + "exitCode": 254, + "durationMs": 81, + "verdict": "fail" + } + ], + "retryAttempt": 1, + "maxRetries": 2 +} diff --git a/.gsd/milestones/M009/slices/S03/S03-PLAN.md b/.gsd/milestones/M009/slices/S03/S03-PLAN.md index caa1928..5b17e63 100644 --- a/.gsd/milestones/M009/slices/S03/S03-PLAN.md +++ b/.gsd/milestones/M009/slices/S03/S03-PLAN.md @@ -1,6 +1,53 @@ # S03: Featured Content & Content Teasers -**Goal:** Demonstrate content value immediately — a first-time visitor sees a real technique before searching +**Goal:** Homepage shows a featured technique spotlight and recently-added section with enriched grid-layout cards **Demo:** After this: Homepage shows a featured technique spotlight and Recently Added section with enriched cards ## Tasks +- [x] **T01: Added sort=random|recent query param to list_techniques endpoint and wired it through the frontend API client** — Add an optional `sort` query parameter to the backend `list_techniques` endpoint in `backend/routers/techniques.py`. Accepts 'recent' (default, existing created_at DESC behavior) and 'random' (using func.random(), same pattern as creators.py line 67). Update `TechniqueListParams` in `frontend/src/api/public-client.ts` to include `sort?: string` and pass it as a query string param in `fetchTechniques`. + +Backend change pattern (from creators.py): +```python +sort: Annotated[str, Query()] = "recent", +``` +Then in the query building: +```python +if sort == "random": + stmt = stmt.order_by(func.random()) +else: + stmt = stmt.order_by(TechniquePage.created_at.desc()) +``` + +Replace the existing hardcoded `.order_by(TechniquePage.created_at.desc())` with the conditional. + - 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: + +**1. Featured Technique Spotlight** (above recently-added section): +- New useEffect fetching `fetchTechniques({ sort: 'random', limit: 1 })` into `featured` state +- Renders a `.home-featured` section with: technique title (linked to /techniques/{slug}), summary (full, not truncated), creator name (linked to /creators/{creator_slug}), topic_category badge, topic_tags pills, key_moment_count +- Left-aligned content (breaks from hero's center alignment) +- Silently hidden when fetch fails or returns empty (same pattern as popular topics) +- Has a subtle accent left border for visual distinction + +**2. Enriched Recently Added grid**: +- Change `.recent-list` from flex-column to CSS grid: `grid-template-columns: repeat(2, 1fr)` with responsive collapse to 1 column at 640px +- Widen `.recent-section` from `max-width: 36rem` to `42rem` +- Filter out the featured technique by ID from the recently-added list to avoid duplication: `recent.filter(t => t.id !== featured?.id)` +- Add more prominent summary display (up to 150 chars instead of 100) + +**Styles** in App.css: +- `.home-featured` section: max-width 42rem, centered margin, left-aligned text, 3px accent left border, surface background, card-style padding +- `.home-featured__title`: larger font, linked +- `.home-featured__summary`: full summary text, secondary color +- `.home-featured__meta`: badge + tags + moments row +- `.home-featured__creator`: creator link +- Updated `.recent-list` to grid layout +- Updated `.recent-section` max-width +- Responsive rules at 640px for grid collapse + +Use BEM naming under `.home-featured` prefix. Use CSS custom properties from D017. + - Estimate: 45m + - Files: frontend/src/pages/Home.tsx, frontend/src/App.css + - Verify: cd frontend && npx vite build && grep -q 'home-featured' src/pages/Home.tsx && grep -q 'home-featured' src/App.css && grep -q 'grid-template-columns' src/App.css diff --git a/.gsd/milestones/M009/slices/S03/S03-RESEARCH.md b/.gsd/milestones/M009/slices/S03/S03-RESEARCH.md new file mode 100644 index 0000000..bd287a5 --- /dev/null +++ b/.gsd/milestones/M009/slices/S03/S03-RESEARCH.md @@ -0,0 +1,67 @@ +# S03 Research: Featured Content & Content Teasers + +## Depth: Light + +Straightforward frontend work — adding two new homepage sections using established patterns and existing API endpoints. No new backend routes required; no unfamiliar technology. + +## Summary + +The homepage (Home.tsx, 306 lines) already has: hero section with tagline/value-prop/how-it-works, search bar with typeahead, nav cards, popular topics pills, and a "Recently Added" section fetching `GET /api/v1/techniques?limit=5` sorted by `created_at DESC`. The slice adds: + +1. **Featured Technique Spotlight** — a prominent single-technique card above the recently-added section, highlighting one technique with more detail (summary, creator, tags, moment count). +2. **Enriched Recently Added cards** — upgrade the existing `recent-card` elements with richer visual treatment (summary preview, better badge layout, visual hierarchy). + +## Recommendation + +### Featured Technique Spotlight + +No dedicated backend endpoint needed. Two viable approaches: + +- **Option A (simple):** Fetch `GET /api/v1/techniques?limit=1` — gets the most recently created technique. Deterministic, zero backend changes. +- **Option B (random):** Add a `sort=random` parameter to the existing `list_techniques` endpoint (same pattern as `creators.py` line 67: `func.random()`), then fetch `?limit=1&sort=random`. Slightly more interesting UX — different technique on each visit. + +**Recommendation: Option B.** The pattern is already established in the creators endpoint. Adding `sort` param to `/techniques` is a 5-line backend change. A rotating spotlight is more engaging and aligns with R014's creator equity spirit (no single technique permanently promoted). + +### Enriched Recently Added + +The existing `recent-card` already renders title, creator_name, topic_category badge, topic_tags pills, summary (truncated to 100 chars), and moment count. The `TechniqueListItem` type has all needed fields. Enrichment is purely CSS/layout: + +- Wider card layout (current max-width: 36rem is narrow for cards with metadata) +- Grid layout instead of single-column stack (2 columns on desktop) +- More prominent summary text +- Better visual hierarchy between title and metadata + +### No new API types needed + +`TechniqueListItem` already includes: title, slug, topic_category, topic_tags, summary, creator_name, creator_slug, source_quality, key_moment_count. All fields needed for both the spotlight and enriched cards are present. + +## Implementation Landscape + +### Files to modify + +| File | Change | +|------|--------| +| `backend/routers/techniques.py` | Add optional `sort` query param (random/recent, default recent) to `list_techniques` | +| `frontend/src/api/public-client.ts` | Add `sort` to `TechniqueListParams` | +| `frontend/src/pages/Home.tsx` | Add featured spotlight section, fetch with `sort=random&limit=1`; restructure recently-added layout | +| `frontend/src/App.css` | Add `.home-featured` spotlight styles, update `.recent-*` styles for grid layout and enriched cards | + +### Existing patterns to follow + +- **BEM naming under `.home-` prefix** (S01 pattern): `.home-featured__title`, `.home-featured__summary`, etc. +- **Optional data-driven sections silently hide on API error** (S01 popular topics pattern): featured section should not show error UI if fetch fails +- **CSS custom properties** from D017: `--color-accent`, `--color-bg-surface`, `--color-border`, `--color-badge-category-*` +- **Card hover transition**: `border-color 0.15s, box-shadow 0.15s` with `--color-accent-hover` / `--color-accent-subtle` + +### Constraints + +- Homepage hero section is `text-align: center` — the featured section should break from this with left-aligned content for readability +- Recently Added is currently `max-width: 36rem` — can widen to 42rem or match the how-it-works grid width for card grid +- The featured technique fetch is a separate API call from the recently-added fetch — need to exclude the featured technique from the recently-added list to avoid duplication (filter client-side by ID) + +### Verification approach + +- `cd frontend && npx vite build` — zero TypeScript errors +- Source inspection: confirm `.home-featured` section renders with spotlight technique data +- Source inspection: confirm recently-added uses grid layout with enriched card markup +- Visual inspection via browser if available diff --git a/.gsd/milestones/M009/slices/S03/tasks/T01-PLAN.md b/.gsd/milestones/M009/slices/S03/tasks/T01-PLAN.md new file mode 100644 index 0000000..a8a147b --- /dev/null +++ b/.gsd/milestones/M009/slices/S03/tasks/T01-PLAN.md @@ -0,0 +1,37 @@ +--- +estimated_steps: 13 +estimated_files: 2 +skills_used: [] +--- + +# T01: Add sort=random query param to list_techniques endpoint and API client + +Add an optional `sort` query parameter to the backend `list_techniques` endpoint in `backend/routers/techniques.py`. Accepts 'recent' (default, existing created_at DESC behavior) and 'random' (using func.random(), same pattern as creators.py line 67). Update `TechniqueListParams` in `frontend/src/api/public-client.ts` to include `sort?: string` and pass it as a query string param in `fetchTechniques`. + +Backend change pattern (from creators.py): +```python +sort: Annotated[str, Query()] = "recent", +``` +Then in the query building: +```python +if sort == "random": + stmt = stmt.order_by(func.random()) +else: + stmt = stmt.order_by(TechniquePage.created_at.desc()) +``` + +Replace the existing hardcoded `.order_by(TechniquePage.created_at.desc())` with the conditional. + +## Inputs + +- ``backend/routers/techniques.py` — existing list_techniques endpoint to add sort param to` +- ``frontend/src/api/public-client.ts` — existing TechniqueListParams and fetchTechniques to add sort support` + +## Expected Output + +- ``backend/routers/techniques.py` — list_techniques with sort=random|recent query param` +- ``frontend/src/api/public-client.ts` — TechniqueListParams with sort field, fetchTechniques passing sort param` + +## Verification + +cd frontend && npx tsc --noEmit && grep -q 'sort.*random' ../backend/routers/techniques.py && grep -q 'sort' src/api/public-client.ts diff --git a/.gsd/milestones/M009/slices/S03/tasks/T01-SUMMARY.md b/.gsd/milestones/M009/slices/S03/tasks/T01-SUMMARY.md new file mode 100644 index 0000000..2d98af1 --- /dev/null +++ b/.gsd/milestones/M009/slices/S03/tasks/T01-SUMMARY.md @@ -0,0 +1,78 @@ +--- +id: T01 +parent: S03 +milestone: M009 +provides: [] +requires: [] +affects: [] +key_files: ["backend/routers/techniques.py", "frontend/src/api/public-client.ts"] +key_decisions: ["Followed same pattern as creators.py for sort param (func.random() for random, created_at DESC for recent)"] +patterns_established: [] +drill_down_paths: [] +observability_surfaces: [] +duration: "" +verification_result: "TypeScript compiles cleanly (npx tsc --noEmit), backend grep confirms sort=random logic present, frontend grep confirms sort param wired through." +completed_at: 2026-03-31T05:46:25.296Z +blocker_discovered: false +--- + +# T01: Added sort=random|recent query param to list_techniques endpoint and wired it through the frontend API client + +> Added sort=random|recent query param to list_techniques endpoint and wired it through the frontend API client + +## What Happened +--- +id: T01 +parent: S03 +milestone: M009 +key_files: + - backend/routers/techniques.py + - frontend/src/api/public-client.ts +key_decisions: + - Followed same pattern as creators.py for sort param (func.random() for random, created_at DESC for recent) +duration: "" +verification_result: passed +completed_at: 2026-03-31T05:46:25.296Z +blocker_discovered: false +--- + +# T01: Added sort=random|recent query param to list_techniques endpoint and wired it through the frontend API client + +**Added sort=random|recent query param to list_techniques endpoint and wired it through the frontend API client** + +## What Happened + +Added an optional `sort` query parameter to `list_techniques` in `backend/routers/techniques.py`, accepting "recent" (default, existing created_at DESC) and "random" (using func.random()). Replaced the hardcoded order_by with a conditional. On the frontend, added `sort?: string` to `TechniqueListParams` and wired it through `fetchTechniques` as a query string parameter. + +## Verification + +TypeScript compiles cleanly (npx tsc --noEmit), backend grep confirms sort=random logic present, frontend grep confirms sort param wired through. + +## Verification Evidence + +| # | Command | Exit Code | Verdict | Duration | +|---|---------|-----------|---------|----------| +| 1 | `cd frontend && npx tsc --noEmit` | 0 | ✅ pass | 7900ms | +| 2 | `grep -q 'sort.*random' backend/routers/techniques.py` | 0 | ✅ pass | 100ms | +| 3 | `grep -q 'sort' frontend/src/api/public-client.ts` | 0 | ✅ pass | 100ms | + + +## Deviations + +None. + +## Known Issues + +None. + +## Files Created/Modified + +- `backend/routers/techniques.py` +- `frontend/src/api/public-client.ts` + + +## Deviations +None. + +## Known Issues +None. diff --git a/.gsd/milestones/M009/slices/S03/tasks/T02-PLAN.md b/.gsd/milestones/M009/slices/S03/tasks/T02-PLAN.md new file mode 100644 index 0000000..25a16eb --- /dev/null +++ b/.gsd/milestones/M009/slices/S03/tasks/T02-PLAN.md @@ -0,0 +1,49 @@ +--- +estimated_steps: 22 +estimated_files: 2 +skills_used: [] +--- + +# T02: Add featured spotlight and enriched recently-added grid to homepage + +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 +- Renders a `.home-featured` section with: technique title (linked to /techniques/{slug}), summary (full, not truncated), creator name (linked to /creators/{creator_slug}), topic_category badge, topic_tags pills, key_moment_count +- Left-aligned content (breaks from hero's center alignment) +- Silently hidden when fetch fails or returns empty (same pattern as popular topics) +- Has a subtle accent left border for visual distinction + +**2. Enriched Recently Added grid**: +- Change `.recent-list` from flex-column to CSS grid: `grid-template-columns: repeat(2, 1fr)` with responsive collapse to 1 column at 640px +- Widen `.recent-section` from `max-width: 36rem` to `42rem` +- Filter out the featured technique by ID from the recently-added list to avoid duplication: `recent.filter(t => t.id !== featured?.id)` +- Add more prominent summary display (up to 150 chars instead of 100) + +**Styles** in App.css: +- `.home-featured` section: max-width 42rem, centered margin, left-aligned text, 3px accent left border, surface background, card-style padding +- `.home-featured__title`: larger font, linked +- `.home-featured__summary`: full summary text, secondary color +- `.home-featured__meta`: badge + tags + moments row +- `.home-featured__creator`: creator link +- Updated `.recent-list` to grid layout +- Updated `.recent-section` max-width +- Responsive rules at 640px for grid collapse + +Use BEM naming under `.home-featured` prefix. Use CSS custom properties from D017. + +## Inputs + +- ``frontend/src/pages/Home.tsx` — existing homepage with hero, search, nav cards, recently-added section` +- ``frontend/src/App.css` — existing styles including .recent-* classes and CSS custom properties` +- ``frontend/src/api/public-client.ts` — fetchTechniques with sort param (from T01)` + +## Expected Output + +- ``frontend/src/pages/Home.tsx` — homepage with featured spotlight section and enriched recently-added grid` +- ``frontend/src/App.css` — new .home-featured styles, updated .recent-list to grid layout, responsive rules` + +## Verification + +cd frontend && npx vite build && grep -q 'home-featured' src/pages/Home.tsx && grep -q 'home-featured' src/App.css && grep -q 'grid-template-columns' src/App.css diff --git a/backend/routers/techniques.py b/backend/routers/techniques.py index 6f0944b..36f19bd 100644 --- a/backend/routers/techniques.py +++ b/backend/routers/techniques.py @@ -33,6 +33,7 @@ router = APIRouter(prefix="/techniques", tags=["techniques"]) async def list_techniques( category: Annotated[str | None, Query()] = None, creator_slug: Annotated[str | None, Query()] = None, + sort: Annotated[str, Query()] = "recent", offset: Annotated[int, Query(ge=0)] = 0, limit: Annotated[int, Query(ge=1, le=100)] = 50, db: AsyncSession = Depends(get_session), @@ -72,7 +73,12 @@ async def list_techniques( Creator.slug == creator_slug ) - stmt = stmt.options(selectinload(TechniquePage.creator)).order_by(TechniquePage.created_at.desc()).offset(offset).limit(limit) + stmt = stmt.options(selectinload(TechniquePage.creator)) + if sort == "random": + stmt = stmt.order_by(func.random()) + else: + stmt = stmt.order_by(TechniquePage.created_at.desc()) + stmt = stmt.offset(offset).limit(limit) result = await db.execute(stmt) rows = result.all() diff --git a/frontend/src/api/public-client.ts b/frontend/src/api/public-client.ts index 254ea2a..04d03af 100644 --- a/frontend/src/api/public-client.ts +++ b/frontend/src/api/public-client.ts @@ -218,6 +218,7 @@ export interface TechniqueListParams { offset?: number; category?: string; creator_slug?: string; + sort?: string; } export async function fetchTechniques( @@ -228,6 +229,7 @@ export async function fetchTechniques( if (params.offset !== undefined) qs.set("offset", String(params.offset)); if (params.category) qs.set("category", params.category); if (params.creator_slug) qs.set("creator_slug", params.creator_slug); + if (params.sort) qs.set("sort", params.sort); const query = qs.toString(); return request( `${BASE}/techniques${query ? `?${query}` : ""}`,