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:
parent
0234a87429
commit
539274ce58
11 changed files with 507 additions and 3 deletions
|
|
@ -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. |
|
||||
|
|
|
|||
85
.gsd/milestones/M017/slices/S02/S02-SUMMARY.md
Normal file
85
.gsd/milestones/M017/slices/S02/S02-SUMMARY.md
Normal 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
|
||||
44
.gsd/milestones/M017/slices/S02/S02-UAT.md
Normal file
44
.gsd/milestones/M017/slices/S02/S02-UAT.md
Normal 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)
|
||||
30
.gsd/milestones/M017/slices/S02/tasks/T02-VERIFY.json
Normal file
30
.gsd/milestones/M017/slices/S02/tasks/T02-VERIFY.json
Normal 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
|
||||
}
|
||||
|
|
@ -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'
|
||||
|
|
|
|||
76
.gsd/milestones/M017/slices/S03/S03-RESEARCH.md
Normal file
76
.gsd/milestones/M017/slices/S03/S03-RESEARCH.md
Normal 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
|
||||
45
.gsd/milestones/M017/slices/S03/tasks/T01-PLAN.md
Normal file
45
.gsd/milestones/M017/slices/S03/tasks/T01-PLAN.md
Normal 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')"
|
||||
76
.gsd/milestones/M017/slices/S03/tasks/T01-SUMMARY.md
Normal file
76
.gsd/milestones/M017/slices/S03/tasks/T01-SUMMARY.md
Normal 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.
|
||||
65
.gsd/milestones/M017/slices/S03/tasks/T02-PLAN.md
Normal file
65
.gsd/milestones/M017/slices/S03/tasks/T02-PLAN.md
Normal 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'
|
||||
|
|
@ -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
|
||||
]
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue