feat: Added summary, topic_tags, and key_moment_count fields to Creator…

- "backend/schemas.py"
- "backend/routers/creators.py"

GSD-Task: S03/T01
This commit is contained in:
jlightner 2026-04-03 09:07:34 +00:00
parent 0234a87429
commit 539274ce58
11 changed files with 507 additions and 3 deletions

View file

@ -7,6 +7,6 @@ Transform the Creator detail page from a plain technique listing into a polished
| ID | Slice | Risk | Depends | Done | After this |
|----|-------|------|---------|------|------------|
| S01 | Frontend Schema Sync + Hero Section | low | — | ✅ | Creator page shows hero with large avatar, name, bio text, and genre pills. All backend fields consumed. |
| S02 | Social Links + Stats Section | low | S01 | | Social link icons visible in hero. Stats bar shows technique/video/moment counts and topic breakdown. |
| S02 | Social Links + Stats Section | low | S01 | | Social link icons visible in hero. Stats bar shows technique/video/moment counts and topic breakdown. |
| S03 | Featured Technique + Technique Grid Restyle | low | S01 | ⬜ | Featured technique card shown prominently. All technique cards use recent-card styling. |
| S04 | Admin Profile Editing + Mobile Polish | low | S01, S02 | ⬜ | Admin can edit bio, social links for a creator. Page looks good at 375px. |

View file

@ -0,0 +1,85 @@
---
id: S02
parent: M017
milestone: M017
provides:
- moment_count field on CreatorDetail API response
- SocialIcons component reusable across pages
- Stats bar with technique/video/moment counts
requires:
- slice: S01
provides: Hero section layout with creator-hero__info div, stats bar, CreatorDetail schema with social_links field
affects:
- S04
key_files:
- frontend/src/components/SocialIcons.tsx
- frontend/src/pages/CreatorDetail.tsx
- frontend/src/App.css
- backend/schemas.py
- backend/routers/creators.py
- frontend/src/api/public-client.ts
key_decisions:
- Used X icon for twitter/x — both keys map to same SVG
- Added Twitch as 9th platform since it's common in music production community
- moment_count query uses KeyMoment→SourceVideo join, same pattern as existing video_count
patterns_established:
- SocialIcons component follows CategoryIcons.tsx pattern — shared style constants, named exports per icon, case-insensitive key lookup with fallback
observability_surfaces:
- none
drill_down_paths:
- .gsd/milestones/M017/slices/S02/tasks/T01-SUMMARY.md
- .gsd/milestones/M017/slices/S02/tasks/T02-SUMMARY.md
duration: ""
verification_result: passed
completed_at: 2026-04-03T09:01:35.873Z
blocker_discovered: false
---
# S02: Social Links + Stats Section
**Added social link icons in the creator hero section and expanded the stats bar to show technique, video, and moment counts.**
## What Happened
Two tasks completed cleanly. T01 added `moment_count` to the backend: a new field on the CreatorDetail Pydantic schema, a KeyMoment→SourceVideo join query in the creators router, and the matching TypeScript type in the frontend client. T02 created a SocialIcons component with inline SVGs for 9 platforms (Instagram, YouTube, Bandcamp, SoundCloud, Twitter/X, Spotify, Facebook, Twitch) plus a globe fallback. Social links render as clickable icons in the hero section with hover color transitions. The stats bar now shows technique, video, and moment counts with dot separators. CSS styles follow existing patterns (CSS custom properties for colors, flex layout).
## Verification
TypeScript compilation (`npx tsc --noEmit`) passed with zero errors. Production build (`npm run build`) succeeded — 59 modules, clean output. Spot-checked all plan must-haves: moment_count on schema, join query in router, frontend type synced, SocialIcons with 9 platforms + fallback, social links as `<a target=_blank rel=noopener noreferrer>`, stats bar with three counts, CSS for social links and separators.
## Requirements Advanced
None.
## Requirements Validated
None.
## New Requirements Surfaced
None.
## Requirements Invalidated or Re-scoped
None.
## Deviations
T02 added Twitch as a 9th platform icon beyond the 8 specified in the plan. Minor addition, no impact.
## Known Limitations
Social links only render if the creator has `social_links` populated in the database. No admin editing UI for social links yet — that's S04.
## Follow-ups
S04 will add admin editing for bio and social links, plus mobile polish.
## Files Created/Modified
- `backend/schemas.py` — Added moment_count: int = 0 to CreatorDetail
- `backend/routers/creators.py` — Added moment_count query via KeyMoment→SourceVideo join
- `frontend/src/api/public-client.ts` — Added moment_count: number to CreatorDetailResponse
- `frontend/src/components/SocialIcons.tsx` — New component: 9 platform SVG icons + globe fallback
- `frontend/src/pages/CreatorDetail.tsx` — Added social links rendering and expanded stats bar
- `frontend/src/App.css` — Added styles for social links and stats separators

View file

@ -0,0 +1,44 @@
# S02: Social Links + Stats Section — UAT
**Milestone:** M017
**Written:** 2026-04-03T09:01:35.873Z
## UAT: S02 — Social Links + Stats Section
### Preconditions
- Chrysopedia running at ub01:8096
- At least one creator exists with `social_links` populated (JSON object with platform→URL pairs)
- At least one creator exists with technique pages, source videos, and key moments
### Test 1: Social Links Display
1. Navigate to a creator detail page for a creator with social_links populated
2. **Expected:** Social link icons appear in the hero section between bio text and genre pills
3. Each icon should be a clickable link opening in a new tab (`target=_blank`)
4. Hover over a social icon — color should transition from muted to accent
### Test 2: Social Links — No Links
1. Navigate to a creator detail page for a creator with no social_links (empty or null)
2. **Expected:** No social links section rendered — no empty container, no spacing gap
### Test 3: Platform Icon Coverage
1. On a creator with diverse social links (instagram, youtube, bandcamp, soundcloud, twitter, spotify, facebook, twitch)
2. **Expected:** Each platform shows its distinct SVG icon
3. For an unrecognized platform key, a globe/website fallback icon should render
### Test 4: Stats Bar — All Three Counts
1. Navigate to a creator detail page
2. **Expected:** Stats bar shows "N techniques · M videos · P moments" with correct counts
3. Singular form used when count is 1 (e.g., "1 technique" not "1 techniques")
### Test 5: Moment Count Accuracy
1. Compare the moment count shown on the creator page with the actual key moment count for that creator's videos in the database
2. **Expected:** Count matches — the query joins KeyMoment→SourceVideo→creator_id correctly
### Test 6: Stats Separator Styling
1. Inspect the dot separators (·) between stats
2. **Expected:** Separators use muted text color with slight horizontal margins
### Edge Cases
- Creator with 0 techniques, 0 videos, 0 moments → stats bar shows "0 techniques · 0 videos · 0 moments"
- Creator with exactly 1 of each → singular forms: "1 technique · 1 video · 1 moment"
- Social link URL that is malformed → renders as clickable link (browser handles the URL)

View file

@ -0,0 +1,30 @@
{
"schemaVersion": 1,
"taskId": "T02",
"unitId": "M017/S02/T02",
"timestamp": 1775206834107,
"passed": false,
"discoverySource": "task-plan",
"checks": [
{
"command": "cd frontend",
"exitCode": 0,
"durationMs": 5,
"verdict": "pass"
},
{
"command": "npx tsc --noEmit",
"exitCode": 1,
"durationMs": 850,
"verdict": "fail"
},
{
"command": "npm run build",
"exitCode": 254,
"durationMs": 89,
"verdict": "fail"
}
],
"retryAttempt": 1,
"maxRetries": 2
}

View file

@ -1,6 +1,74 @@
# S03: Featured Technique + Technique Grid Restyle
**Goal:** Highlight featured technique, restyle technique grid to match homepage cards
**Goal:** Featured technique card shown prominently on creator page. All technique cards use recent-card styling with summary, tags, and moment count.
**Demo:** After this: Featured technique card shown prominently. All technique cards use recent-card styling.
## Tasks
- [x] **T01: Added summary, topic_tags, and key_moment_count fields to CreatorTechniqueItem and enriched the creator detail query with a correlated KeyMoment count subquery** — Extend the backend schema and query so the creator detail endpoint returns enriched technique data needed for the featured card and restyled grid.
## Steps
1. Read `backend/schemas.py` and add three fields to `CreatorTechniqueItem`:
- `summary: str | None = None`
- `topic_tags: list[str] | None = None`
- `key_moment_count: int = 0`
2. Read `backend/routers/creators.py` (the creator detail endpoint around line 140-175). Update the technique query to also select `TechniquePage.summary` and `TechniquePage.topic_tags`. For `key_moment_count`, add a correlated subquery: `select(func.count(KeyMoment.id)).where(KeyMoment.technique_page_id == TechniquePage.id).correlate(TechniquePage).scalar_subquery().label('key_moment_count')`. Pass all new fields when constructing `CreatorTechniqueItem` instances.
3. Verify the schema loads: `cd backend && python -c "from schemas import CreatorTechniqueItem; print(CreatorTechniqueItem.model_fields.keys())"`
## Must-Haves
- [ ] `CreatorTechniqueItem` has `summary`, `topic_tags`, `key_moment_count` fields
- [ ] Creator detail endpoint query selects summary, topic_tags, and computes key_moment_count
- [ ] Existing fields (title, slug, topic_category, created_at) unchanged
## Verification
- `cd backend && python -c "from schemas import CreatorTechniqueItem; fields = list(CreatorTechniqueItem.model_fields.keys()); assert 'summary' in fields and 'topic_tags' in fields and 'key_moment_count' in fields, f'Missing fields: {fields}'; print('OK:', fields)"`
- Estimate: 20m
- Files: backend/schemas.py, backend/routers/creators.py
- Verify: cd backend && python -c "from schemas import CreatorTechniqueItem; fields = list(CreatorTechniqueItem.model_fields.keys()); assert 'summary' in fields and 'topic_tags' in fields and 'key_moment_count' in fields; print('OK')"
- [ ] **T02: Add featured technique card and restyle grid to recent-card pattern** — Update the frontend to consume the enriched technique fields: render the first technique as a featured card with gradient border, and restyle remaining cards to match the recent-card pattern.
## Steps
1. Read `frontend/src/api/public-client.ts` and add to the `CreatorTechniqueItem` interface:
- `summary: string | null`
- `topic_tags: string[] | null`
- `key_moment_count: number`
2. Read `frontend/src/pages/CreatorDetail.tsx`. In the technique list section:
- Extract the first technique from the sorted array as `featuredTechnique`, remaining as `gridTechniques`
- Above the existing grid, render the featured technique card (only when it exists) as a `Link` to `/techniques/{slug}` with class `creator-featured`. Include: title, summary (if present), category badge, TagList for topic_tags (if present), moment count.
- Change the grid cards from `creator-technique-card` to `recent-card` class. Add summary, topic_tags (TagList), and moment count spans inside each card.
- Import `TagList` component if not already imported.
3. Read `frontend/src/App.css`. Add `.creator-featured` styles adapted from `.home-featured`:
- Same gradient `border-image` (linear-gradient 135deg cyan→purple)
- Same surface background, padding, text alignment
- Omit the creator link (we're already on the creator page)
- Add `__title`, `__summary`, `__meta`, `__moments` sub-classes
- Add a `.creator-featured__label` for a 'Featured Technique' heading
- Remove or comment out the old `.creator-technique-card` rules (or leave them — they become dead CSS)
4. Run `cd frontend && npx tsc --noEmit` — must exit 0.
5. Run `cd frontend && npm run build` — must exit 0.
## Must-Haves
- [ ] `CreatorTechniqueItem` interface has summary, topic_tags, key_moment_count
- [ ] Featured technique card rendered above grid with gradient border and summary
- [ ] Grid technique cards use `recent-card` class pattern with summary, tags, moment count
- [ ] TypeScript compiles with zero errors
- [ ] Production build succeeds
## Verification
- `cd frontend && npx tsc --noEmit` exits 0
- `cd frontend && npm run build` exits 0
- `grep -q 'creator-featured' frontend/src/pages/CreatorDetail.tsx` confirms featured card class
- `grep -q 'recent-card' frontend/src/pages/CreatorDetail.tsx` confirms grid restyle
- Estimate: 30m
- Files: frontend/src/api/public-client.ts, frontend/src/pages/CreatorDetail.tsx, frontend/src/App.css
- Verify: cd frontend && npx tsc --noEmit && npm run build && grep -q 'creator-featured' src/pages/CreatorDetail.tsx && grep -q 'recent-card' src/pages/CreatorDetail.tsx && echo 'ALL OK'

View file

@ -0,0 +1,76 @@
# S03 Research: Featured Technique + Technique Grid Restyle
## Summary
Straightforward slice. Two changes: (1) add a featured technique card to the creator page, (2) restyle all technique cards to match the existing `recent-card` pattern. Both require enriching `CreatorTechniqueItem` with additional fields from the backend.
## Recommendation
Enrich the backend `CreatorTechniqueItem` schema and query with summary, topic_tags, and key_moment_count. On the frontend, select the first technique as "featured" (most recent, since the query is ordered by `created_at DESC`), render it with a prominent card styled after `home-featured`, and restyle the remaining cards to reuse the `recent-card` class pattern.
## Implementation Landscape
### Backend Changes
**File: `backend/schemas.py` (line 46)**
`CreatorTechniqueItem` currently has only: title, slug, topic_category, created_at.
Add: `summary: str | None`, `topic_tags: list[str] | None`, `key_moment_count: int`.
**File: `backend/routers/creators.py` (line 143-162)**
The creator detail endpoint selects only 4 columns from `technique_pages`. Needs to also select `summary`, `topic_tags`. For `key_moment_count`, either:
- Option A: Subquery count per technique page (adds one extra SQL expression to the existing select)
- Option B: Separate query to count moments grouped by technique_page_id
Option A is cleaner — add a correlated subquery or use `func.count` with a LEFT JOIN + GROUP BY.
**File: `frontend/src/api/public-client.ts` (line 177)**
`CreatorTechniqueItem` interface — add `summary`, `topic_tags`, `key_moment_count` to match backend.
### Frontend Changes
**File: `frontend/src/pages/CreatorDetail.tsx`**
- Extract the first technique from the sorted array as the "featured" technique (most recent by default sort)
- Render featured technique in a prominent card above the grid, styled like `home-featured` (gradient border, summary, tags, moment count)
- Render remaining techniques using `recent-card` class instead of `creator-technique-card`
- The featured card only shows when there's at least one technique
**File: `frontend/src/App.css`**
- Add `.creator-featured` styles (can reuse/adapt `home-featured` pattern — gradient border-image, larger text, summary)
- Update technique list cards to use `recent-card` styles — either reuse class directly or adapt `creator-technique-card` to match
- The existing `creator-technique-card` hover has `transform: scale(1.02)` which `recent-card` doesn't — drop it for consistency
### "Featured" Selection Logic
No `featured_technique_slug` exists on the Creator or TechniquePage model. The `featured` boolean on Creator marks the creator as featured (used on homepage), not which technique is featured.
**Approach:** Select the first technique in the sorted list as "featured" client-side. This is the most recent technique by default (backend returns ordered by `created_at DESC`). No backend changes needed for selection logic. If admin wants to control which technique is featured, that's a future enhancement.
### Existing Patterns to Reuse
| Pattern | Source | Notes |
|---------|--------|-------|
| `home-featured` card | `App.css:1526` | Gradient border-image, summary, meta badges, creator link, moment count |
| `recent-card` | `App.css:1605` | Standard technique card — surface bg, border, hover glow, no transform |
| `card-stagger` animation | Various pages | `--stagger-index` CSS var for entrance animation |
| `TagList` component | `components/TagList.tsx` | Renders topic_tags as pills |
| `badge--category` | Various | Category badge styling |
### Constraints
- `CreatorTechniqueItem` enrichment is a schema expansion — fully backward compatible, new fields are optional/nullable
- `border-image` strips `border-radius` (per KNOWLEDGE.md) — the featured card won't have rounded corners, matching `home-featured`
- The featured card shouldn't duplicate the creator name (we're already on the creator page)
### Natural Seams
**T01 — Backend enrichment:** Extend `CreatorTechniqueItem` schema + query in creators.py. Verify with `curl` or integration test.
**T02 — Frontend featured card + grid restyle:** Update `CreatorTechniqueItem` interface, add featured card rendering, restyle technique grid. Verify with `tsc --noEmit` + `npm run build`.
### Verification
- `cd backend && python -c "from schemas import CreatorTechniqueItem; print(CreatorTechniqueItem.model_fields.keys())"` — confirms new fields
- `curl localhost:8000/api/v1/creators/{slug}` — response includes summary, topic_tags, key_moment_count in techniques array
- `cd frontend && npx tsc --noEmit` — zero type errors
- `cd frontend && npm run build` — clean production build
- Visual: featured card visible on creator page with summary and tags, remaining cards match recent-card styling

View file

@ -0,0 +1,45 @@
---
estimated_steps: 14
estimated_files: 2
skills_used: []
---
# T01: Enrich CreatorTechniqueItem with summary, topic_tags, key_moment_count
Extend the backend schema and query so the creator detail endpoint returns enriched technique data needed for the featured card and restyled grid.
## Steps
1. Read `backend/schemas.py` and add three fields to `CreatorTechniqueItem`:
- `summary: str | None = None`
- `topic_tags: list[str] | None = None`
- `key_moment_count: int = 0`
2. Read `backend/routers/creators.py` (the creator detail endpoint around line 140-175). Update the technique query to also select `TechniquePage.summary` and `TechniquePage.topic_tags`. For `key_moment_count`, add a correlated subquery: `select(func.count(KeyMoment.id)).where(KeyMoment.technique_page_id == TechniquePage.id).correlate(TechniquePage).scalar_subquery().label('key_moment_count')`. Pass all new fields when constructing `CreatorTechniqueItem` instances.
3. Verify the schema loads: `cd backend && python -c "from schemas import CreatorTechniqueItem; print(CreatorTechniqueItem.model_fields.keys())"`
## Must-Haves
- [ ] `CreatorTechniqueItem` has `summary`, `topic_tags`, `key_moment_count` fields
- [ ] Creator detail endpoint query selects summary, topic_tags, and computes key_moment_count
- [ ] Existing fields (title, slug, topic_category, created_at) unchanged
## Verification
- `cd backend && python -c "from schemas import CreatorTechniqueItem; fields = list(CreatorTechniqueItem.model_fields.keys()); assert 'summary' in fields and 'topic_tags' in fields and 'key_moment_count' in fields, f'Missing fields: {fields}'; print('OK:', fields)"`
## Inputs
- ``backend/schemas.py` — current CreatorTechniqueItem with title, slug, topic_category, created_at`
- ``backend/routers/creators.py` — creator detail endpoint with technique query`
- ``backend/models.py` — TechniquePage model with summary, topic_tags columns and KeyMoment relationship`
## Expected Output
- ``backend/schemas.py` — CreatorTechniqueItem with 3 new fields: summary, topic_tags, key_moment_count`
- ``backend/routers/creators.py` — enriched technique query with summary, topic_tags, key_moment_count subquery`
## Verification
cd backend && python -c "from schemas import CreatorTechniqueItem; fields = list(CreatorTechniqueItem.model_fields.keys()); assert 'summary' in fields and 'topic_tags' in fields and 'key_moment_count' in fields; print('OK')"

View file

@ -0,0 +1,76 @@
---
id: T01
parent: S03
milestone: M017
provides: []
requires: []
affects: []
key_files: ["backend/schemas.py", "backend/routers/creators.py"]
key_decisions: ["Used correlated scalar subquery for key_moment_count rather than JOIN+GROUP BY to keep existing row-based iteration pattern intact"]
patterns_established: []
drill_down_paths: []
observability_surfaces: []
duration: ""
verification_result: "Ran schema field assertion: all three new fields (summary, topic_tags, key_moment_count) present alongside original four fields (title, slug, topic_category, created_at). Exit code 0."
completed_at: 2026-04-03T09:07:31.302Z
blocker_discovered: false
---
# T01: Added summary, topic_tags, and key_moment_count fields to CreatorTechniqueItem and enriched the creator detail query with a correlated KeyMoment count subquery
> Added summary, topic_tags, and key_moment_count fields to CreatorTechniqueItem and enriched the creator detail query with a correlated KeyMoment count subquery
## What Happened
---
id: T01
parent: S03
milestone: M017
key_files:
- backend/schemas.py
- backend/routers/creators.py
key_decisions:
- Used correlated scalar subquery for key_moment_count rather than JOIN+GROUP BY to keep existing row-based iteration pattern intact
duration: ""
verification_result: passed
completed_at: 2026-04-03T09:07:31.302Z
blocker_discovered: false
---
# T01: Added summary, topic_tags, and key_moment_count fields to CreatorTechniqueItem and enriched the creator detail query with a correlated KeyMoment count subquery
**Added summary, topic_tags, and key_moment_count fields to CreatorTechniqueItem and enriched the creator detail query with a correlated KeyMoment count subquery**
## What Happened
Added three new optional fields to CreatorTechniqueItem in backend/schemas.py: summary (str|None), topic_tags (list[str]|None), and key_moment_count (int, default 0). Updated the creator detail endpoint in backend/routers/creators.py to select TechniquePage.summary and TechniquePage.topic_tags, and added a correlated scalar subquery counting KeyMoment rows per technique page. Imported KeyMoment model. All existing fields remain unchanged.
## Verification
Ran schema field assertion: all three new fields (summary, topic_tags, key_moment_count) present alongside original four fields (title, slug, topic_category, created_at). Exit code 0.
## Verification Evidence
| # | Command | Exit Code | Verdict | Duration |
|---|---------|-----------|---------|----------|
| 1 | `cd backend && python -c "from schemas import CreatorTechniqueItem; fields = list(CreatorTechniqueItem.model_fields.keys()); assert 'summary' in fields and 'topic_tags' in fields and 'key_moment_count' in fields; print('OK')"` | 0 | ✅ pass | 500ms |
## Deviations
None.
## Known Issues
None.
## Files Created/Modified
- `backend/schemas.py`
- `backend/routers/creators.py`
## Deviations
None.
## Known Issues
None.

View file

@ -0,0 +1,65 @@
---
estimated_steps: 31
estimated_files: 3
skills_used: []
---
# T02: Add featured technique card and restyle grid to recent-card pattern
Update the frontend to consume the enriched technique fields: render the first technique as a featured card with gradient border, and restyle remaining cards to match the recent-card pattern.
## Steps
1. Read `frontend/src/api/public-client.ts` and add to the `CreatorTechniqueItem` interface:
- `summary: string | null`
- `topic_tags: string[] | null`
- `key_moment_count: number`
2. Read `frontend/src/pages/CreatorDetail.tsx`. In the technique list section:
- Extract the first technique from the sorted array as `featuredTechnique`, remaining as `gridTechniques`
- Above the existing grid, render the featured technique card (only when it exists) as a `Link` to `/techniques/{slug}` with class `creator-featured`. Include: title, summary (if present), category badge, TagList for topic_tags (if present), moment count.
- Change the grid cards from `creator-technique-card` to `recent-card` class. Add summary, topic_tags (TagList), and moment count spans inside each card.
- Import `TagList` component if not already imported.
3. Read `frontend/src/App.css`. Add `.creator-featured` styles adapted from `.home-featured`:
- Same gradient `border-image` (linear-gradient 135deg cyan→purple)
- Same surface background, padding, text alignment
- Omit the creator link (we're already on the creator page)
- Add `__title`, `__summary`, `__meta`, `__moments` sub-classes
- Add a `.creator-featured__label` for a 'Featured Technique' heading
- Remove or comment out the old `.creator-technique-card` rules (or leave them — they become dead CSS)
4. Run `cd frontend && npx tsc --noEmit` — must exit 0.
5. Run `cd frontend && npm run build` — must exit 0.
## Must-Haves
- [ ] `CreatorTechniqueItem` interface has summary, topic_tags, key_moment_count
- [ ] Featured technique card rendered above grid with gradient border and summary
- [ ] Grid technique cards use `recent-card` class pattern with summary, tags, moment count
- [ ] TypeScript compiles with zero errors
- [ ] Production build succeeds
## Verification
- `cd frontend && npx tsc --noEmit` exits 0
- `cd frontend && npm run build` exits 0
- `grep -q 'creator-featured' frontend/src/pages/CreatorDetail.tsx` confirms featured card class
- `grep -q 'recent-card' frontend/src/pages/CreatorDetail.tsx` confirms grid restyle
## Inputs
- ``frontend/src/api/public-client.ts` — CreatorTechniqueItem interface to extend`
- ``frontend/src/pages/CreatorDetail.tsx` — current technique list rendering with creator-technique-card class`
- ``frontend/src/App.css` — existing .home-featured and .recent-card styles to reference/adapt`
- ``backend/schemas.py` — T01 output: enriched CreatorTechniqueItem schema as reference for field names`
## Expected Output
- ``frontend/src/api/public-client.ts` — CreatorTechniqueItem with summary, topic_tags, key_moment_count`
- ``frontend/src/pages/CreatorDetail.tsx` — featured card + recent-card grid rendering`
- ``frontend/src/App.css` — .creator-featured styles added`
## Verification
cd frontend && npx tsc --noEmit && npm run build && grep -q 'creator-featured' src/pages/CreatorDetail.tsx && grep -q 'recent-card' src/pages/CreatorDetail.tsx && echo 'ALL OK'

View file

@ -12,7 +12,7 @@ from sqlalchemy import func, select
from sqlalchemy.ext.asyncio import AsyncSession
from database import get_session
from models import Creator, SourceVideo, TechniquePage
from models import Creator, KeyMoment, SourceVideo, TechniquePage
from schemas import CreatorBrowseItem, CreatorDetail, CreatorRead, CreatorTechniqueItem
logger = logging.getLogger("chrysopedia.creators")
@ -137,12 +137,22 @@ async def get_creator(
)).scalar() or 0
# Technique pages for this creator
key_moment_count_sq = (
select(func.count(KeyMoment.id))
.where(KeyMoment.technique_page_id == TechniquePage.id)
.correlate(TechniquePage)
.scalar_subquery()
.label("key_moment_count")
)
technique_rows = (await db.execute(
select(
TechniquePage.title,
TechniquePage.slug,
TechniquePage.topic_category,
TechniquePage.created_at,
TechniquePage.summary,
TechniquePage.topic_tags,
key_moment_count_sq,
)
.where(TechniquePage.creator_id == creator.id)
.order_by(TechniquePage.created_at.desc())
@ -152,6 +162,8 @@ async def get_creator(
CreatorTechniqueItem(
title=t.title, slug=t.slug,
topic_category=t.topic_category, created_at=t.created_at,
summary=t.summary, topic_tags=t.topic_tags,
key_moment_count=t.key_moment_count,
)
for t in technique_rows
]

View file

@ -49,6 +49,9 @@ class CreatorTechniqueItem(BaseModel):
slug: str
topic_category: str
created_at: datetime
summary: str | None = None
topic_tags: list[str] | None = None
key_moment_count: int = 0
class CreatorDetail(CreatorRead):