feat: Added sort=random|recent query param to list_techniques endpoint…

- "backend/routers/techniques.py"
- "frontend/src/api/public-client.ts"

GSD-Task: S03/T01
This commit is contained in:
jlightner 2026-03-31 05:46:31 +00:00
parent 32114ec360
commit 0b27e5752e
11 changed files with 433 additions and 3 deletions

View file

@ -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 |

View file

@ -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

View file

@ -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.

View file

@ -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
}

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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.

View file

@ -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

View file

@ -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()

View file

@ -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<TechniqueListResponse>(
`${BASE}/techniques${query ? `?${query}` : ""}`,