diff --git a/.gsd/milestones/M011/M011-ROADMAP.md b/.gsd/milestones/M011/M011-ROADMAP.md index d03b48a..097c0e7 100644 --- a/.gsd/milestones/M011/M011-ROADMAP.md +++ b/.gsd/milestones/M011/M011-ROADMAP.md @@ -6,7 +6,7 @@ Transform Chrysopedia from functionally adequate to engaging and accessible. Add ## Slice Overview | ID | Slice | Risk | Depends | Done | After this | |----|-------|------|---------|------|------------| -| S01 | Interaction Delight & Discovery | medium | — | ⬜ | Cards animate on hover with scale+shadow. Card grids stagger entrance. Featured technique has glow treatment. Random Technique button navigates to a random page. | +| S01 | Interaction Delight & Discovery | medium | — | ✅ | Cards animate on hover with scale+shadow. Card grids stagger entrance. Featured technique has glow treatment. Random Technique button navigates to a random page. | | S02 | Topics, Creator Stats & Card Polish | medium | — | ⬜ | Topics page loads collapsed, expands with animation. Creator stats show colored topic pills. Cards limit tags to 4 with +N more. Empty subtopics show Coming soon. | | S03 | Global Search & Mobile Navigation | medium | — | ⬜ | Compact search bar in nav on all pages. Cmd+K focuses it. Mobile viewport shows hamburger menu. | | S04 | Accessibility & SEO Fixes | low | — | ⬜ | Every page has single H1, skip-to-content link, AA contrast text, and descriptive browser tab title. | diff --git a/.gsd/milestones/M011/slices/S01/S01-SUMMARY.md b/.gsd/milestones/M011/slices/S01/S01-SUMMARY.md new file mode 100644 index 0000000..4a45a23 --- /dev/null +++ b/.gsd/milestones/M011/slices/S01/S01-SUMMARY.md @@ -0,0 +1,106 @@ +--- +id: S01 +parent: M011 +milestone: M011 +provides: + - Card hover animation pattern (scale+shadow) on all card types + - Stagger entrance animation utility class + - GET /api/v1/techniques/random endpoint + - Random Technique button on homepage +requires: + [] +affects: + [] +key_files: + - frontend/src/App.css + - frontend/src/pages/Home.tsx + - frontend/src/pages/TopicsBrowse.tsx + - frontend/src/pages/CreatorDetail.tsx + - frontend/src/pages/SubTopicPage.tsx + - frontend/src/pages/SearchResults.tsx + - backend/routers/techniques.py + - frontend/src/api/public-client.ts +key_decisions: + - CSS stagger pattern uses --stagger-index custom property with calc() delay rather than nth-child selectors — works with dynamic lists + - Dedicated /random endpoint returning just {slug} rather than reusing sort=random on list endpoint — cleaner API surface + - /random route placed before /{slug} to avoid FastAPI slug capture +patterns_established: + - card-stagger utility class: add className='card-stagger' and style={{ '--stagger-index': i }} to any .map() loop for entrance animations + - Featured section visual treatment: gradient border-image + double box-shadow glow +observability_surfaces: + - none +drill_down_paths: + - .gsd/milestones/M011/slices/S01/tasks/T01-SUMMARY.md + - .gsd/milestones/M011/slices/S01/tasks/T02-SUMMARY.md +duration: "" +verification_result: passed +completed_at: 2026-03-31T08:25:58.040Z +blocker_discovered: false +--- + +# S01: Interaction Delight & Discovery + +**Added card hover animations (scale+shadow) on all 6 card types, staggered entrance animations across 5 page components, gradient-border glow on featured technique, and a Random Technique button with backend endpoint.** + +## What Happened + +Two tasks delivered CSS interaction polish and a random discovery feature. + +T01 added `transform: scale(1.02)` hover transitions with `will-change: transform` to all 6 card types (recent-card, creator-technique-card, subtopic-technique-card, search-result-card, topic-card, nav-card). Created a `@keyframes cardEnter` animation (opacity 0→1, translateY 12px→0, 300ms ease-out) with a `.card-stagger` utility class driven by a `--stagger-index` CSS custom property. Applied stagger indices via JSX style props across Home.tsx, TopicsBrowse.tsx, CreatorDetail.tsx, SubTopicPage.tsx, and SearchResults.tsx. The featured technique section got a gradient `border-image` and double box-shadow glow treatment. + +T02 added a `GET /api/v1/techniques/random` backend endpoint (placed before the `/{slug}` route to avoid capture) that returns `{slug}` via `ORDER BY random() LIMIT 1`, with 404 when no techniques exist. Frontend gets `fetchRandomTechnique()` in the API client and a 🎲 Random Technique button on the homepage with loading and error states. + +Both tasks verified with TypeScript (`tsc --noEmit`) and Vite production build (50 modules). All grep checks confirm features landed in expected files. + +## Verification + +All slice-level checks pass: +- `npx tsc --noEmit` — exit 0, no type errors +- `npm run build` — exit 0, 50 modules, 806ms build +- `grep cardEnter App.css` — found +- `grep card-stagger App.css` — found +- `grep fetchRandomTechnique public-client.ts` — found +- `grep /random techniques.py` — found +- `grep Random Home.tsx` — found +- `grep stagger-index` across all 5 page components — found in each (Home:3, TopicsBrowse:1, CreatorDetail:1, SubTopicPage:1, SearchResults:1) + +## Requirements Advanced + +- R016 — All 6 card types have scale(1.02) hover with smooth 200ms transition. 5 page components use staggered cardEnter animation. +- R017 — Featured technique section has gradient border-image and double box-shadow glow, visually distinct from regular cards. +- R018 — Random Technique button on homepage calls GET /random endpoint and navigates to result. Loading and error states implemented. + +## Requirements Validated + +None. + +## New Requirements Surfaced + +None. + +## Requirements Invalidated or Re-scoped + +None. + +## Deviations + +SearchResultCard needed a staggerIndex prop threaded through (not anticipated in plan). topic-card had no existing :hover rule — one was added. border-image removes border-radius on the featured card (CSS limitation) but the glow box-shadow still provides the visual treatment. + +## Known Limitations + +border-image CSS property strips border-radius on the featured technique card. The glow effect (box-shadow) still provides visual distinction but the card corners are square rather than rounded. + +## Follow-ups + +None. + +## Files Created/Modified + +- `frontend/src/App.css` — Added card hover scale(1.02) transitions, @keyframes cardEnter, .card-stagger utility, .home-featured glow treatment, .btn--random and .home-random styles +- `frontend/src/pages/Home.tsx` — Added stagger indices to nav-cards and recent cards, Random Technique button with loading/error states +- `frontend/src/pages/TopicsBrowse.tsx` — Added card-stagger class and stagger-index to topic cards +- `frontend/src/pages/CreatorDetail.tsx` — Added card-stagger class and stagger-index to creator technique cards +- `frontend/src/pages/SubTopicPage.tsx` — Added card-stagger class and stagger-index to subtopic technique cards +- `frontend/src/pages/SearchResults.tsx` — Added card-stagger class and stagger-index to search result cards, threaded staggerIndex prop +- `backend/routers/techniques.py` — Added GET /random endpoint before /{slug} route +- `frontend/src/api/public-client.ts` — Added fetchRandomTechnique() API client function diff --git a/.gsd/milestones/M011/slices/S01/S01-UAT.md b/.gsd/milestones/M011/slices/S01/S01-UAT.md new file mode 100644 index 0000000..51acf4c --- /dev/null +++ b/.gsd/milestones/M011/slices/S01/S01-UAT.md @@ -0,0 +1,77 @@ +# S01: Interaction Delight & Discovery — UAT + +**Milestone:** M011 +**Written:** 2026-03-31T08:25:58.041Z + +# S01 UAT: Interaction Delight & Discovery + +## Preconditions +- Chrysopedia running at http://ub01:8096 +- At least 1 technique page exists in the database +- Modern browser (Chrome/Firefox/Safari) + +## Test Cases + +### TC-01: Card Hover Animation — Homepage Recent Cards +1. Navigate to http://ub01:8096 +2. Hover over any card in the "Recently Added" section +3. **Expected:** Card smoothly scales up (~2%) with enhanced shadow over ~200ms +4. Move mouse away from card +5. **Expected:** Card smoothly returns to original size + +### TC-02: Card Hover Animation — All Card Types +1. Navigate to Topics page → hover a topic card +2. Navigate to any topic → hover a subtopic technique card +3. Navigate to Creators page → click a creator → hover a creator technique card +4. Use search → hover a search result card +5. On homepage → hover a nav card (Topics/Creators) +6. **Expected:** All 5 card types exhibit the same scale+shadow hover effect + +### TC-03: Staggered Entrance Animation — Homepage +1. Hard refresh http://ub01:8096 (Ctrl+Shift+R) +2. Observe the nav cards and recently added cards +3. **Expected:** Cards fade in and slide up sequentially (not all at once). Each card appears ~60ms after the previous one. + +### TC-04: Staggered Entrance — Other Pages +1. Navigate to Topics page — cards should stagger in +2. Navigate to a Creator detail page — technique cards should stagger in +3. Navigate to a SubTopic page — technique cards should stagger in +4. Perform a search — result cards should stagger in +5. **Expected:** All pages show sequential card entrance, not simultaneous + +### TC-05: Featured Technique Glow +1. Navigate to homepage +2. Scroll to the "Featured Technique" section +3. **Expected:** The featured technique card has a visible gradient border and soft glow (cyan-tinted box shadow) that distinguishes it from regular cards +4. **Note:** Corners may be square due to border-image CSS limitation — this is known + +### TC-06: Random Technique Button — Happy Path +1. Navigate to homepage +2. Locate the 🎲 "Random Technique" button (between nav cards and featured section) +3. Click the button +4. **Expected:** Button shows brief loading state, then navigates to a technique page +5. Use browser back, click the button again +6. **Expected:** May navigate to a different technique (randomized server-side) + +### TC-07: Random Technique Button — Loading State +1. On homepage, open browser DevTools Network tab +2. Throttle network to "Slow 3G" +3. Click the Random Technique button +4. **Expected:** Button shows a loading indicator while the request is in flight +5. Remove throttle + +### TC-08: Random Technique API — Direct +1. Open http://ub01:8096/api/v1/techniques/random in browser or curl +2. **Expected:** JSON response `{"slug": "some-technique-slug"}` with 200 status +3. Refresh multiple times +4. **Expected:** Different slugs returned (assuming multiple techniques exist) + +### Edge Cases + +### TC-09: Card Stagger with Few Items +1. If a creator has only 1 technique, navigate to their detail page +2. **Expected:** Single card still animates in (no delay needed for index 0) + +### TC-10: Featured Technique Glow in Light/Contrast +1. On homepage, inspect the featured technique section +2. **Expected:** Glow is subtle — visible but not overpowering (cyan tint, not neon) diff --git a/.gsd/milestones/M011/slices/S01/tasks/T02-VERIFY.json b/.gsd/milestones/M011/slices/S01/tasks/T02-VERIFY.json new file mode 100644 index 0000000..93ecc2e --- /dev/null +++ b/.gsd/milestones/M011/slices/S01/tasks/T02-VERIFY.json @@ -0,0 +1,42 @@ +{ + "schemaVersion": 1, + "taskId": "T02", + "unitId": "M011/S01/T02", + "timestamp": 1774945478573, + "passed": false, + "discoverySource": "task-plan", + "checks": [ + { + "command": "cd frontend", + "exitCode": 0, + "durationMs": 5, + "verdict": "pass" + }, + { + "command": "npx tsc --noEmit", + "exitCode": 1, + "durationMs": 720, + "verdict": "fail" + }, + { + "command": "npm run build", + "exitCode": 254, + "durationMs": 96, + "verdict": "fail" + }, + { + "command": "grep -q 'fetchRandomTechnique' src/api/public-client.ts", + "exitCode": 2, + "durationMs": 7, + "verdict": "fail" + }, + { + "command": "grep -q '/random' ../backend/routers/techniques.py", + "exitCode": 2, + "durationMs": 6, + "verdict": "fail" + } + ], + "retryAttempt": 1, + "maxRetries": 2 +} diff --git a/.gsd/milestones/M011/slices/S02/S02-PLAN.md b/.gsd/milestones/M011/slices/S02/S02-PLAN.md index f05bc9b..070c70c 100644 --- a/.gsd/milestones/M011/slices/S02/S02-PLAN.md +++ b/.gsd/milestones/M011/slices/S02/S02-PLAN.md @@ -1,6 +1,125 @@ # S02: Topics, Creator Stats & Card Polish -**Goal:** Improve information density and visual clarity on Topics and Creator pages +**Goal:** Topics page loads collapsed with smooth expand/collapse animation. Creator stats show colored topic pills. Cards limit tags to 4 with +N overflow. Empty subtopics show "Coming soon" badge. **Demo:** After this: Topics page loads collapsed, expands with animation. Creator stats show colored topic pills. Cards limit tags to 4 with +N more. Empty subtopics show Coming soon. ## Tasks +- [x] **T01: Topics page loads with all categories collapsed; expand/collapse uses smooth CSS grid-template-rows animation instead of conditional render** — Change TopicsBrowse.tsx to start with all categories collapsed and add smooth CSS grid-template-rows animation for expand/collapse transitions. + +## Steps + +1. In `frontend/src/pages/TopicsBrowse.tsx`, change the `useEffect` that calls `setExpanded(new Set(data.map((c) => c.name)))` to `setExpanded(new Set())` so all categories start collapsed. + +2. Refactor the subtopics rendering from conditional `{isExpanded && (
...)}` to always-rendered with a CSS grid animation wrapper. Use the `display: grid; grid-template-rows: 0fr / 1fr` technique: + - Wrap the subtopics in a `
` that has `display: grid; grid-template-rows: 0fr; transition: grid-template-rows 300ms ease;` + - When `data-expanded="true"`, set `grid-template-rows: 1fr` + - The inner `
` gets `overflow: hidden; min-height: 0;` + - Always render both wrapper and inner div (remove the conditional render) + +3. Add the CSS in `frontend/src/App.css` near the existing `.topic-subtopics` rule (~line 2318): + - `.topic-subtopics-wrapper { display: grid; grid-template-rows: 0fr; transition: grid-template-rows 300ms ease; }` + - `.topic-subtopics-wrapper[data-expanded="true"] { grid-template-rows: 1fr; }` + - `.topic-subtopics { overflow: hidden; min-height: 0; }` (modify existing rule) + +4. Verify: `cd frontend && npx tsc --noEmit && npm run build` + - Estimate: 30m + - Files: frontend/src/pages/TopicsBrowse.tsx, frontend/src/App.css + - Verify: cd frontend && npx tsc --noEmit && npm run build +- [ ] **T02: Creator stats topic-colored pill badges** — Replace the run-on text stats on CreatorDetail page with colored pill badges using existing badge CSS classes. + +## Steps + +1. In `frontend/src/pages/CreatorDetail.tsx`, add import: `import { catSlug } from "../utils/catSlug";` + +2. Find the stats section (~line 108-126) that renders `` with dot separators. Replace the entire `.map(([cat, count], i) => ...)` block with pills: + ```tsx + .map(([cat, count]) => ( + + {cat}: {count} + + )) + ``` + Remove the dot separator spans (`queue-card__separator`) between them. + +3. Wrap the topic stat pills in a flex container: `` and add CSS in `frontend/src/App.css`: + ```css + .creator-detail__topic-pills { + display: flex; + flex-wrap: wrap; + gap: 0.375rem; + align-items: center; + } + ``` + +4. Remove the old `.creator-detail__topic-stat` class from App.css if it exists (or leave it — no harm). + +5. Verify: `cd frontend && npx tsc --noEmit && npm run build` + - Estimate: 20m + - Files: frontend/src/pages/CreatorDetail.tsx, frontend/src/App.css + - Verify: cd frontend && npx tsc --noEmit && npm run build +- [ ] **T03: TagList component, tag overflow limit, and empty subtopic handling** — Create a shared TagList component for tag overflow (R027), apply it across all 5 tag-rendering sites, and add empty subtopic handling in TopicsBrowse (R028). + +## Steps + +1. Create `frontend/src/components/TagList.tsx`: + ```tsx + interface TagListProps { + tags: string[]; + max?: number; + } + export default function TagList({ tags, max = 4 }: TagListProps) { + const visible = tags.slice(0, max); + const overflow = tags.length - max; + return ( + <> + {visible.map(tag => {tag})} + {overflow > 0 && +{overflow} more} + + ); + } + ``` + +2. Add `.pill--overflow` CSS in `frontend/src/App.css`: + ```css + .pill--overflow { + background: var(--color-surface-2); + color: var(--color-text-secondary); + font-style: italic; + } + ``` + +3. Replace tag rendering in all 5 sites with ``: + - `frontend/src/pages/Home.tsx` line ~201 (featured technique tags) — replace `featured.topic_tags.map(...)` with `` + - `frontend/src/pages/Home.tsx` line ~247 (recent technique card tags) — replace `t.topic_tags.map(...)` with `` + - `frontend/src/pages/SearchResults.tsx` line ~145 — replace `item.topic_tags.map(...)` with `` + - `frontend/src/pages/SubTopicPage.tsx` line ~149 — replace `t.topic_tags.map(...)` with `` + - `frontend/src/pages/CreatorDetail.tsx` line ~157 — replace `t.topic_tags.map(...)` with `` + Each site: add `import TagList from "../components/TagList";` and replace the `.map(tag => {tag})` with ``. + +4. In `frontend/src/pages/TopicsBrowse.tsx`, update the subtopic rendering inside the `.topic-subtopics` div. For subtopics with `st.technique_count === 0`: + - Render as a `` (not ``) with class `topic-subtopic topic-subtopic--empty` instead of a clickable link + - Add a small "Coming soon" badge: `Coming soon` + - Keep the subtopic name visible so users know the topic exists + Use a conditional: `st.technique_count === 0 ? ... : ...` + +5. Add CSS for empty subtopic in `frontend/src/App.css`: + ```css + .topic-subtopic--empty { + opacity: 0.5; + cursor: default; + } + .topic-subtopic--empty:hover { + background: transparent; + } + .pill--coming-soon { + font-size: 0.65rem; + background: var(--color-surface-2); + color: var(--color-text-secondary); + font-style: italic; + } + ``` + +6. Verify: `cd frontend && npx tsc --noEmit && npm run build` + - Estimate: 40m + - Files: frontend/src/components/TagList.tsx, frontend/src/pages/Home.tsx, frontend/src/pages/SearchResults.tsx, frontend/src/pages/SubTopicPage.tsx, frontend/src/pages/CreatorDetail.tsx, frontend/src/pages/TopicsBrowse.tsx, frontend/src/App.css + - Verify: cd frontend && npx tsc --noEmit && npm run build diff --git a/.gsd/milestones/M011/slices/S02/S02-RESEARCH.md b/.gsd/milestones/M011/slices/S02/S02-RESEARCH.md new file mode 100644 index 0000000..ad5997c --- /dev/null +++ b/.gsd/milestones/M011/slices/S02/S02-RESEARCH.md @@ -0,0 +1,96 @@ +# S02 Research: Topics, Creator Stats & Card Polish + +## Summary + +Straightforward frontend-only changes across 5–6 files. All patterns (CSS color system, catSlug utility, badge classes, expand/collapse state) already exist. No new dependencies, no backend changes, no unfamiliar technology. + +## Targeted Requirements + +| Req | Description | Status | +|-----|-------------|--------| +| R019 | Topics page default-collapsed with expand animation | Active — this slice | +| R026 | Creator stats topic-colored pills | Active — this slice | +| R027 | Tag overflow limit (4 + "+N more") | Active — this slice | +| R028 | Empty subtopic handling | Active — this slice | + +## Implementation Landscape + +### 1. Topics Default-Collapsed (R019) + +**File:** `frontend/src/pages/TopicsBrowse.tsx` + +- **Current:** Line ~42: `setExpanded(new Set(data.map((c) => c.name)))` — starts all-expanded. +- **Change:** `setExpanded(new Set())` — starts all-collapsed. +- **Animation:** The subtopics container (`div.topic-subtopics`) currently renders conditionally (`{isExpanded && ...}`). For smooth expand/collapse, replace the conditional render with CSS `max-height` + `overflow: hidden` + `transition`. Use a wrapper with `max-height: 0` when collapsed and `max-height: px` when expanded, or use CSS `grid-template-rows: 0fr / 1fr` technique (better, no fixed height needed). +- **CSS file:** `frontend/src/App.css` — `.topic-subtopics` starts at line 2318. Add transition properties. + +**Recommendation:** Use the CSS `display: grid; grid-template-rows: 0fr → 1fr` pattern for the smoothest transition without needing to know content height. Wrap subtopics in a grid container that transitions `grid-template-rows`. + +### 2. Creator Stats Colored Pills (R026) + +**File:** `frontend/src/pages/CreatorDetail.tsx` + +- **Current:** Lines ~97–110: Stats render as `Cat: N` with dot separators. +- **Change:** Replace with `{cat}: {count}` using `catSlug()` from `../utils/catSlug`. +- **Existing CSS:** `.badge--cat-sound-design`, `.badge--cat-mixing`, etc. already defined in App.css with bg/text color pairs for all 7 categories. +- **Import needed:** `import { catSlug } from "../utils/catSlug";` (not currently imported in CreatorDetail.tsx). +- **Layout:** Replace the run-on text with a flex-wrap container holding pill badges. + +### 3. Tag Overflow Limit (R027) + +**Files affected (5 tag-rendering sites):** + +| File | Line | Context | +|------|------|---------| +| `frontend/src/pages/Home.tsx` | ~201 | Featured technique tags | +| `frontend/src/pages/Home.tsx` | ~247 | Recent technique card tags | +| `frontend/src/pages/SearchResults.tsx` | ~143 | Search result card tags | +| `frontend/src/pages/SubTopicPage.tsx` | ~147 | Sub-topic technique card tags | +| `frontend/src/pages/CreatorDetail.tsx` | ~108 | Creator technique card tags | + +**Pattern:** All 5 sites use the same idiom: +```tsx +{t.topic_tags.map(tag => {tag})} +``` + +**Change:** Extract a shared helper or inline the pattern: +```tsx +{tags.slice(0, 4).map(tag => {tag})} +{tags.length > 4 && +{tags.length - 4} more} +``` + +**Recommendation:** Create a small `TagList` component in `frontend/src/components/TagList.tsx` to DRY up the 5 sites. Props: `tags: string[]`, `max?: number` (default 4). The component renders pills with overflow. This avoids repeating the slice/overflow logic 5 times. + +### 4. Empty Subtopic Handling (R028) + +**File:** `frontend/src/pages/TopicsBrowse.tsx` + +- **Current:** All subtopics render as clickable links regardless of `technique_count`. +- **Change:** When `st.technique_count === 0`, render as a non-clickable element with a "Coming soon" badge instead of a link. Keep it visible (don't hide) so users know the topic exists. +- **CSS:** Add a `.topic-subtopic--empty` modifier class with muted styling and a small "Coming soon" badge. + +## Key Observations + +1. **CSS color system is complete** — all 7 category color pairs exist as CSS custom properties and badge modifier classes. No new colors needed for creator stats pills. +2. **`catSlug()` utility exists** — converts category names to CSS-safe slugs (lowercase, spaces→hyphens). Already used in TopicsBrowse.tsx. +3. **S01 added `card-stagger` animation** — `@keyframes cardEnter` and `.card-stagger` class with `--stagger-index` CSS variable. Already applied to topic cards and creator technique cards. +4. **No existing expand/collapse animation** — subtopics use conditional rendering (`{isExpanded && ...}`), not CSS transitions. Needs refactoring for smooth animation. +5. **`grid-template-rows` transition** is well-supported (Chrome 57+, Firefox 66+, Safari 16+) and doesn't require knowing content height. + +## Natural Task Seams + +1. **Topics collapse + animation (R019)** — Self-contained in TopicsBrowse.tsx + App.css. Change default state, add CSS grid transition. +2. **Creator stats pills (R026)** — Self-contained in CreatorDetail.tsx + App.css. Import catSlug, replace text with badges. +3. **Tag overflow + empty subtopics (R027, R028)** — Create TagList component, update 5 files for tag overflow, update TopicsBrowse.tsx for empty subtopics. These can be one task since TagList is the shared piece. + +**Risk ordering:** Topics collapse/animation is slightly riskier (CSS transition refactor), so do it first. The others are mechanical. + +## Verification + +- `cd frontend && npx tsc --noEmit` — TypeScript compiles clean +- `cd frontend && npm run build` — Vite production build succeeds +- Browser verification at http://ub01:8096: + - Topics page loads collapsed, click expands with animation + - Creator detail shows colored pills for topic stats + - Cards with >4 tags show exactly 4 + "+N more" + - Empty subtopics show "Coming soon" badge diff --git a/.gsd/milestones/M011/slices/S02/tasks/T01-PLAN.md b/.gsd/milestones/M011/slices/S02/tasks/T01-PLAN.md new file mode 100644 index 0000000..922c5b9 --- /dev/null +++ b/.gsd/milestones/M011/slices/S02/tasks/T01-PLAN.md @@ -0,0 +1,40 @@ +--- +estimated_steps: 13 +estimated_files: 2 +skills_used: [] +--- + +# T01: Topics page default-collapsed with expand/collapse animation + +Change TopicsBrowse.tsx to start with all categories collapsed and add smooth CSS grid-template-rows animation for expand/collapse transitions. + +## Steps + +1. In `frontend/src/pages/TopicsBrowse.tsx`, change the `useEffect` that calls `setExpanded(new Set(data.map((c) => c.name)))` to `setExpanded(new Set())` so all categories start collapsed. + +2. Refactor the subtopics rendering from conditional `{isExpanded && (
...)}` to always-rendered with a CSS grid animation wrapper. Use the `display: grid; grid-template-rows: 0fr / 1fr` technique: + - Wrap the subtopics in a `
` that has `display: grid; grid-template-rows: 0fr; transition: grid-template-rows 300ms ease;` + - When `data-expanded="true"`, set `grid-template-rows: 1fr` + - The inner `
` gets `overflow: hidden; min-height: 0;` + - Always render both wrapper and inner div (remove the conditional render) + +3. Add the CSS in `frontend/src/App.css` near the existing `.topic-subtopics` rule (~line 2318): + - `.topic-subtopics-wrapper { display: grid; grid-template-rows: 0fr; transition: grid-template-rows 300ms ease; }` + - `.topic-subtopics-wrapper[data-expanded="true"] { grid-template-rows: 1fr; }` + - `.topic-subtopics { overflow: hidden; min-height: 0; }` (modify existing rule) + +4. Verify: `cd frontend && npx tsc --noEmit && npm run build` + +## Inputs + +- `frontend/src/pages/TopicsBrowse.tsx` +- `frontend/src/App.css` + +## Expected Output + +- `frontend/src/pages/TopicsBrowse.tsx` +- `frontend/src/App.css` + +## Verification + +cd frontend && npx tsc --noEmit && npm run build diff --git a/.gsd/milestones/M011/slices/S02/tasks/T01-SUMMARY.md b/.gsd/milestones/M011/slices/S02/tasks/T01-SUMMARY.md new file mode 100644 index 0000000..f07d149 --- /dev/null +++ b/.gsd/milestones/M011/slices/S02/tasks/T01-SUMMARY.md @@ -0,0 +1,76 @@ +--- +id: T01 +parent: S02 +milestone: M011 +provides: [] +requires: [] +affects: [] +key_files: ["frontend/src/pages/TopicsBrowse.tsx", "frontend/src/App.css"] +key_decisions: ["Used CSS grid-template-rows 0fr/1fr technique for smooth height animation without JS measurement"] +patterns_established: [] +drill_down_paths: [] +observability_surfaces: [] +duration: "" +verification_result: "TypeScript type check (npx tsc --noEmit) and production build (npm run build) both pass cleanly with exit code 0." +completed_at: 2026-03-31T08:30:49.223Z +blocker_discovered: false +--- + +# T01: Topics page loads with all categories collapsed; expand/collapse uses smooth CSS grid-template-rows animation instead of conditional render + +> Topics page loads with all categories collapsed; expand/collapse uses smooth CSS grid-template-rows animation instead of conditional render + +## What Happened +--- +id: T01 +parent: S02 +milestone: M011 +key_files: + - frontend/src/pages/TopicsBrowse.tsx + - frontend/src/App.css +key_decisions: + - Used CSS grid-template-rows 0fr/1fr technique for smooth height animation without JS measurement +duration: "" +verification_result: passed +completed_at: 2026-03-31T08:30:49.224Z +blocker_discovered: false +--- + +# T01: Topics page loads with all categories collapsed; expand/collapse uses smooth CSS grid-template-rows animation instead of conditional render + +**Topics page loads with all categories collapsed; expand/collapse uses smooth CSS grid-template-rows animation instead of conditional render** + +## What Happened + +Changed the useEffect in TopicsBrowse.tsx to initialize the expanded set as empty instead of pre-filling all category names. Replaced the conditional {isExpanded && (...)} render with an always-rendered wrapper div using the grid-template-rows: 0fr/1fr animation technique. Added CSS rules for the wrapper, expanded state, and updated the inner div with overflow: hidden and min-height: 0 for smooth clipping. + +## Verification + +TypeScript type check (npx tsc --noEmit) and production build (npm run build) both pass cleanly with exit code 0. + +## Verification Evidence + +| # | Command | Exit Code | Verdict | Duration | +|---|---------|-----------|---------|----------| +| 1 | `cd frontend && npx tsc --noEmit && npm run build` | 0 | ✅ pass | 3700ms | + + +## Deviations + +None. + +## Known Issues + +None. + +## Files Created/Modified + +- `frontend/src/pages/TopicsBrowse.tsx` +- `frontend/src/App.css` + + +## Deviations +None. + +## Known Issues +None. diff --git a/.gsd/milestones/M011/slices/S02/tasks/T02-PLAN.md b/.gsd/milestones/M011/slices/S02/tasks/T02-PLAN.md new file mode 100644 index 0000000..ffb5410 --- /dev/null +++ b/.gsd/milestones/M011/slices/S02/tasks/T02-PLAN.md @@ -0,0 +1,52 @@ +--- +estimated_steps: 23 +estimated_files: 2 +skills_used: [] +--- + +# T02: Creator stats topic-colored pill badges + +Replace the run-on text stats on CreatorDetail page with colored pill badges using existing badge CSS classes. + +## Steps + +1. In `frontend/src/pages/CreatorDetail.tsx`, add import: `import { catSlug } from "../utils/catSlug";` + +2. Find the stats section (~line 108-126) that renders `` with dot separators. Replace the entire `.map(([cat, count], i) => ...)` block with pills: + ```tsx + .map(([cat, count]) => ( + + {cat}: {count} + + )) + ``` + Remove the dot separator spans (`queue-card__separator`) between them. + +3. Wrap the topic stat pills in a flex container: `` and add CSS in `frontend/src/App.css`: + ```css + .creator-detail__topic-pills { + display: flex; + flex-wrap: wrap; + gap: 0.375rem; + align-items: center; + } + ``` + +4. Remove the old `.creator-detail__topic-stat` class from App.css if it exists (or leave it — no harm). + +5. Verify: `cd frontend && npx tsc --noEmit && npm run build` + +## Inputs + +- `frontend/src/pages/CreatorDetail.tsx` +- `frontend/src/App.css` +- `frontend/src/utils/catSlug.ts` + +## Expected Output + +- `frontend/src/pages/CreatorDetail.tsx` +- `frontend/src/App.css` + +## Verification + +cd frontend && npx tsc --noEmit && npm run build diff --git a/.gsd/milestones/M011/slices/S02/tasks/T03-PLAN.md b/.gsd/milestones/M011/slices/S02/tasks/T03-PLAN.md new file mode 100644 index 0000000..bae0e6a --- /dev/null +++ b/.gsd/milestones/M011/slices/S02/tasks/T03-PLAN.md @@ -0,0 +1,94 @@ +--- +estimated_steps: 56 +estimated_files: 7 +skills_used: [] +--- + +# T03: TagList component, tag overflow limit, and empty subtopic handling + +Create a shared TagList component for tag overflow (R027), apply it across all 5 tag-rendering sites, and add empty subtopic handling in TopicsBrowse (R028). + +## Steps + +1. Create `frontend/src/components/TagList.tsx`: + ```tsx + interface TagListProps { + tags: string[]; + max?: number; + } + export default function TagList({ tags, max = 4 }: TagListProps) { + const visible = tags.slice(0, max); + const overflow = tags.length - max; + return ( + <> + {visible.map(tag => {tag})} + {overflow > 0 && +{overflow} more} + + ); + } + ``` + +2. Add `.pill--overflow` CSS in `frontend/src/App.css`: + ```css + .pill--overflow { + background: var(--color-surface-2); + color: var(--color-text-secondary); + font-style: italic; + } + ``` + +3. Replace tag rendering in all 5 sites with ``: + - `frontend/src/pages/Home.tsx` line ~201 (featured technique tags) — replace `featured.topic_tags.map(...)` with `` + - `frontend/src/pages/Home.tsx` line ~247 (recent technique card tags) — replace `t.topic_tags.map(...)` with `` + - `frontend/src/pages/SearchResults.tsx` line ~145 — replace `item.topic_tags.map(...)` with `` + - `frontend/src/pages/SubTopicPage.tsx` line ~149 — replace `t.topic_tags.map(...)` with `` + - `frontend/src/pages/CreatorDetail.tsx` line ~157 — replace `t.topic_tags.map(...)` with `` + Each site: add `import TagList from "../components/TagList";` and replace the `.map(tag => {tag})` with ``. + +4. In `frontend/src/pages/TopicsBrowse.tsx`, update the subtopic rendering inside the `.topic-subtopics` div. For subtopics with `st.technique_count === 0`: + - Render as a `` (not ``) with class `topic-subtopic topic-subtopic--empty` instead of a clickable link + - Add a small "Coming soon" badge: `Coming soon` + - Keep the subtopic name visible so users know the topic exists + Use a conditional: `st.technique_count === 0 ? ... : ...` + +5. Add CSS for empty subtopic in `frontend/src/App.css`: + ```css + .topic-subtopic--empty { + opacity: 0.5; + cursor: default; + } + .topic-subtopic--empty:hover { + background: transparent; + } + .pill--coming-soon { + font-size: 0.65rem; + background: var(--color-surface-2); + color: var(--color-text-secondary); + font-style: italic; + } + ``` + +6. Verify: `cd frontend && npx tsc --noEmit && npm run build` + +## Inputs + +- `frontend/src/pages/Home.tsx` +- `frontend/src/pages/SearchResults.tsx` +- `frontend/src/pages/SubTopicPage.tsx` +- `frontend/src/pages/CreatorDetail.tsx` +- `frontend/src/pages/TopicsBrowse.tsx` +- `frontend/src/App.css` + +## Expected Output + +- `frontend/src/components/TagList.tsx` +- `frontend/src/pages/Home.tsx` +- `frontend/src/pages/SearchResults.tsx` +- `frontend/src/pages/SubTopicPage.tsx` +- `frontend/src/pages/CreatorDetail.tsx` +- `frontend/src/pages/TopicsBrowse.tsx` +- `frontend/src/App.css` + +## Verification + +cd frontend && npx tsc --noEmit && npm run build diff --git a/frontend/src/App.css b/frontend/src/App.css index d0f58f5..4aa9948 100644 --- a/frontend/src/App.css +++ b/frontend/src/App.css @@ -2315,7 +2315,19 @@ a.app-footer__repo:hover { /* ── Sub-topics inside card ───────────────────────────────────────────────── */ +.topic-subtopics-wrapper { + display: grid; + grid-template-rows: 0fr; + transition: grid-template-rows 300ms ease; +} + +.topic-subtopics-wrapper[data-expanded="true"] { + grid-template-rows: 1fr; +} + .topic-subtopics { + overflow: hidden; + min-height: 0; border-top: 1px solid var(--color-border); } diff --git a/frontend/src/pages/TopicsBrowse.tsx b/frontend/src/pages/TopicsBrowse.tsx index 044b918..97a5ca1 100644 --- a/frontend/src/pages/TopicsBrowse.tsx +++ b/frontend/src/pages/TopicsBrowse.tsx @@ -34,8 +34,8 @@ export default function TopicsBrowse() { const data = await fetchTopics(); if (!cancelled) { setCategories(data); - // All expanded by default - setExpanded(new Set(data.map((c) => c.name))); + // Start collapsed + setExpanded(new Set()); } } catch (err) { if (!cancelled) { @@ -151,7 +151,7 @@ export default function TopicsBrowse() {
- {isExpanded && ( +
{cat.sub_topics.map((st) => { const stSlug = st.name.toLowerCase().replace(/\s+/g, "-"); @@ -175,7 +175,7 @@ export default function TopicsBrowse() { ); })}
- )} +
); })}