From 612cae349112d5d1a65773658461ef092a33c7b9 Mon Sep 17 00:00:00 2001 From: jlightner Date: Sat, 4 Apr 2026 15:14:05 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20Added=20ReadingHeader=20sticky=20bar=20?= =?UTF-8?q?that=20slides=20in=20when=20scrolling=20past=E2=80=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - "frontend/src/components/ReadingHeader.tsx" - "frontend/src/pages/TechniquePage.tsx" - "frontend/src/App.css" GSD-Task: S10/T01 --- .gsd/DECISIONS.md | 1 + .gsd/milestones/M025/M025-ROADMAP.md | 2 +- .../milestones/M025/slices/S09/S09-SUMMARY.md | 107 +++++++++++++++ .gsd/milestones/M025/slices/S09/S09-UAT.md | 62 +++++++++ .../M025/slices/S09/tasks/T03-VERIFY.json | 16 +++ .gsd/milestones/M025/slices/S10/S10-PLAN.md | 71 +++++++++- .../M025/slices/S10/S10-RESEARCH.md | 122 ++++++++++++++++++ .../M025/slices/S10/tasks/T01-PLAN.md | 50 +++++++ .../M025/slices/S10/tasks/T01-SUMMARY.md | 83 ++++++++++++ .../M025/slices/S10/tasks/T02-PLAN.md | 58 +++++++++ frontend/src/App.css | 70 ++++++++++ frontend/src/components/ReadingHeader.tsx | 64 +++++++++ frontend/src/pages/TechniquePage.tsx | 33 +++++ 13 files changed, 737 insertions(+), 2 deletions(-) create mode 100644 .gsd/milestones/M025/slices/S09/S09-SUMMARY.md create mode 100644 .gsd/milestones/M025/slices/S09/S09-UAT.md create mode 100644 .gsd/milestones/M025/slices/S09/tasks/T03-VERIFY.json create mode 100644 .gsd/milestones/M025/slices/S10/S10-RESEARCH.md create mode 100644 .gsd/milestones/M025/slices/S10/tasks/T01-PLAN.md create mode 100644 .gsd/milestones/M025/slices/S10/tasks/T01-SUMMARY.md create mode 100644 .gsd/milestones/M025/slices/S10/tasks/T02-PLAN.md create mode 100644 frontend/src/components/ReadingHeader.tsx diff --git a/.gsd/DECISIONS.md b/.gsd/DECISIONS.md index 439b77f..3120c6d 100644 --- a/.gsd/DECISIONS.md +++ b/.gsd/DECISIONS.md @@ -51,3 +51,4 @@ | D043 | M023/S02 | architecture | Personality weight → system prompt modulation strategy | 3-tier intensity (<0.4 subtle reference, 0.4-0.8 adopt voice, ≥0.8 fully embody) with temperature scaling 0.3–0.5 linear on weight | Stepped intensity prevents jarring persona at low weights while allowing full creator voice at high values. Temperature stays in 0.3-0.5 range to keep responses factually grounded even at maximum personality — wider ranges risk hallucination in a knowledge-base context. | Yes | agent | | D044 | M023/S04 | architecture | Personality weight → system prompt modulation strategy (revision) | 5-tier continuous interpolation replacing 3-tier step function. Progressive field inclusion: weight < 0.2 = no personality block; 0.2+ adds basic tone; 0.4+ adds descriptors/explanation approach; 0.6+ adds signature phrases (count scaled with weight); 0.8+ adds full vocabulary/style markers; 0.9+ adds summary paragraph. Temperature scaling unchanged (0.3 + weight * 0.2). | 3-tier step function had jarring transitions at 0.4 and 0.8 boundaries. Continuous interpolation with progressive field inclusion gives finer control — encyclopedic responses stay clean at low weights while high weights pull in the full personality profile gradually. The 0.0-0.19 dead zone ensures purely encyclopedic mode remains truly encyclopedic with zero personality artifacts. | Yes | agent | | D045 | M025/S01 | library | Signed unsubscribe token library for email digests | PyJWT instead of itsdangerous | PyJWT was already a dependency (used for auth tokens). Avoids adding itsdangerous as a new package. JWT exp claim provides built-in expiry handling for the 30-day token validity. | Yes | agent | +| D046 | M025/S10 | feature | Whether to accept current sticky title bar as meeting R041 or implement the specified ReadingHeader | Implement proper ReadingHeader component matching R041 spec | The current sticky title bar is always visible and shows no section tracking — it doesn't match R041's spec (thin bar, appears on scroll-past, shows current section name, slide transition). The infrastructure already exists (activeId, titleBarRef, IntersectionObserver), so the implementation is ~80 lines TSX + CSS. Since M025 is about formal validation, accepting a partial implementation undermines the validation exercise. | Yes | agent | diff --git a/.gsd/milestones/M025/M025-ROADMAP.md b/.gsd/milestones/M025/M025-ROADMAP.md index 6b2c7c6..ddee62b 100644 --- a/.gsd/milestones/M025/M025-ROADMAP.md +++ b/.gsd/milestones/M025/M025-ROADMAP.md @@ -14,6 +14,6 @@ Production hardening, mobile polish, creator onboarding, and formal validation. | S06 | [B] Graph Backend Evaluation | low | — | ✅ | Benchmark report: NetworkX vs Neo4j at current and projected entity counts | | S07 | [A] Data Export (GDPR-Style) | medium | — | ✅ | Creator downloads a ZIP with all derived content, entities, and relationships | | S08 | [B] Load Testing + Fallback Resilience | medium | — | ✅ | 10 concurrent chat sessions maintain acceptable latency. DGX down → Ollama fallback works. | -| S09 | [B] Prompt Optimization Pass | low | — | ⬜ | Chat quality reviewed across creators. Personality fidelity assessed. | +| S09 | [B] Prompt Optimization Pass | low | — | ✅ | Chat quality reviewed across creators. Personality fidelity assessed. | | S10 | Requirement Validation (R015, R037-R041) | low | — | ⬜ | R015, R037, R038, R039, R041 formally validated and signed off | | S11 | Forgejo KB Final — Complete Documentation | low | S01, S02, S03, S04, S05, S06, S07, S08, S09, S10 | ⬜ | Forgejo wiki complete with newcomer onboarding guide covering entire platform | diff --git a/.gsd/milestones/M025/slices/S09/S09-SUMMARY.md b/.gsd/milestones/M025/slices/S09/S09-SUMMARY.md new file mode 100644 index 0000000..1f9769c --- /dev/null +++ b/.gsd/milestones/M025/slices/S09/S09-SUMMARY.md @@ -0,0 +1,107 @@ +--- +id: S09 +parent: M025 +milestone: M025 +provides: + - Chat quality evaluation toolkit (scorer + harness + test suite + CLI) + - Refined system prompt with citation/structure/domain guidance + - Quality baseline report documenting current chat capabilities and gaps +requires: + [] +affects: + - S10 + - S11 +key_files: + - backend/pipeline/quality/chat_scorer.py + - backend/pipeline/quality/chat_eval.py + - backend/pipeline/quality/fixtures/chat_test_suite.yaml + - backend/pipeline/quality/__main__.py + - backend/chat_service.py + - .gsd/milestones/M025/slices/S09/S09-QUALITY-REPORT.md + - backend/pipeline/quality/results/chat_eval_baseline.json +key_decisions: + - Reused ScoreResult pattern (generic scores dict + composite) rather than subclassing — keeps chat scorer independent from pipeline scorer + - Kept refined prompt under 20 lines using markdown headers for structure rather than prose paragraphs + - Personality fidelity dimension scores differently based on weight=0 vs weight>0 + - Used manual curl evaluation as planned fallback when LLM proxy returned 502 +patterns_established: + - LLM-as-judge chat evaluation: 5-dimension rubric scorer + SSE-parsing harness + YAML test suite pattern for repeatable chat quality assessment + - Chat eval CLI subcommand wired into existing quality __main__.py — extensible for future eval types +observability_surfaces: + - chat_eval CLI subcommand produces JSON results with per-query dimension scores + - Quality report documents baseline findings for future comparison +drill_down_paths: + - .gsd/milestones/M025/slices/S09/tasks/T01-SUMMARY.md + - .gsd/milestones/M025/slices/S09/tasks/T02-SUMMARY.md + - .gsd/milestones/M025/slices/S09/tasks/T03-SUMMARY.md +duration: "" +verification_result: passed +completed_at: 2026-04-04T14:52:01.942Z +blocker_discovered: false +--- + +# S09: [B] Prompt Optimization Pass + +**Built chat quality evaluation toolkit (5-dimension LLM-as-judge scorer, SSE-parsing eval harness, 10-query test suite), rewrote system prompt with citation/structure/domain guidance, and produced a quality baseline report documenting critical findings on creator scoping and missing personality profiles.** + +## What Happened + +Three tasks delivered the prompt optimization pass: + +**T01 — Chat Evaluation Toolkit.** Created `chat_scorer.py` with a 5-dimension LLM-as-judge scorer (citation_accuracy, response_structure, domain_expertise, source_grounding, personality_fidelity), `chat_eval.py` with an SSE-parsing evaluation harness that calls the live chat endpoint and feeds responses to the scorer, and a 10-query YAML test suite covering technical, conceptual, creator-scoped, and cross-creator categories. Wired a `chat_eval` subcommand into the quality CLI. All modules import cleanly and the CLI renders help correctly. + +**T02 — System Prompt Rewrite.** Replaced the 5-line `_SYSTEM_PROMPT_TEMPLATE` in `chat_service.py` with a structured prompt covering citation density (cite every factual claim inline), response format (short paragraphs, bullet lists for steps, bold key terms), domain terminology (music production context), conflicting source handling (present both perspectives), and response length (2-4 paragraphs default). Kept under 20 lines using markdown headers. All 26 existing chat tests passed unchanged — they verify behavioral properties, not prompt wording. + +**T03 — Quality Baseline Evaluation.** Attempted automated eval against ub01:8096 — API healthy, search functional, but upstream LLM proxy returned 502. Fell back to manual curl evaluation of 6 queries across 4 categories. Key findings: general source retrieval works well (10 relevant sources, multi-creator diversity); creator-scoped search returns zero sources from the target creator (Keota, Mr. Bill); all 25 creators lack personality_profile data (5-tier injection system is architecturally complete but functionally inert); prompt improvements from T02 validated by test suite. Produced a 169-line quality report and baseline JSON. + +## Verification + +All slice verification checks passed: +1. `cd backend && python -c 'from pipeline.quality.chat_scorer import ChatScoreRunner, ChatScoreResult; from pipeline.quality.chat_eval import ChatEvalRunner; print("OK")'` — OK +2. `cd backend && python -m pytest tests/test_chat.py -v` — 26 passed in 1.39s +3. Quality report exists at 169 lines (threshold: 30) +4. `backend/pipeline/quality/results/chat_eval_baseline.json` exists +5. `backend/pipeline/quality/fixtures/chat_test_suite.yaml` exists with 10 test cases +6. `cd backend && python -m pipeline.quality chat_eval --help` — CLI subcommand works + +## Requirements Advanced + +None. + +## Requirements Validated + +None. + +## New Requirements Surfaced + +None. + +## Requirements Invalidated or Re-scoped + +None. + +## Deviations + +T03 used manual curl-based evaluation instead of automated harness due to upstream LLM proxy 502. This was a planned fallback in the task spec. Chat endpoint discovered at /api/v1/chat not /api/chat. + +## Known Limitations + +1. Creator-scoped search returns zero target-creator sources — the retrieval pipeline doesn't filter by creator effectively. This is a search_service issue, not a prompt issue. +2. No creators have personality_profile data populated — the 5-tier personality injection system is architecturally complete but functionally inert until profiles are created. +3. LLM proxy 502 prevented automated scoring — the eval harness is ready but needs a working LLM endpoint to produce numerical quality scores. + +## Follow-ups + +1. Populate personality profiles for at least 2-3 creators to activate the personality injection pipeline. +2. Fix creator-scoped search retrieval to return sources from the target creator. +3. Re-run automated chat_eval when LLM proxy is stable to get numerical quality baselines. + +## Files Created/Modified + +- `backend/pipeline/quality/chat_scorer.py` — New: 5-dimension LLM-as-judge chat scorer with ChatScoreResult and ChatScoreRunner +- `backend/pipeline/quality/chat_eval.py` — New: SSE-parsing evaluation harness that calls live chat endpoint and scores responses +- `backend/pipeline/quality/fixtures/chat_test_suite.yaml` — New: 10-query test suite covering technical, conceptual, creator-scoped, and cross-creator categories +- `backend/pipeline/quality/__main__.py` — Modified: added chat_eval subcommand +- `backend/chat_service.py` — Modified: rewrote _SYSTEM_PROMPT_TEMPLATE with citation density, response structure, domain terminology, conflicting source, and length guidance +- `.gsd/milestones/M025/slices/S09/S09-QUALITY-REPORT.md` — New: 169-line quality baseline report with findings on creator scoping and personality profiles +- `backend/pipeline/quality/results/chat_eval_baseline.json` — New: baseline evaluation results JSON diff --git a/.gsd/milestones/M025/slices/S09/S09-UAT.md b/.gsd/milestones/M025/slices/S09/S09-UAT.md new file mode 100644 index 0000000..704e6a1 --- /dev/null +++ b/.gsd/milestones/M025/slices/S09/S09-UAT.md @@ -0,0 +1,62 @@ +# S09: [B] Prompt Optimization Pass — UAT + +**Milestone:** M025 +**Written:** 2026-04-04T14:52:01.942Z + +## UAT: S09 — [B] Prompt Optimization Pass + +### Preconditions +- Backend code deployed (or running locally with `cd backend`) +- Python 3.12+ with project dependencies installed +- Access to ub01:8096 for live endpoint tests (optional — manual fallback available) + +### Test 1: Chat Scorer Module Imports +**Steps:** +1. Run: `cd backend && python -c 'from pipeline.quality.chat_scorer import ChatScoreRunner, ChatScoreResult; print("OK")'` +**Expected:** Prints "OK", exit code 0. + +### Test 2: Chat Eval Harness Imports +**Steps:** +1. Run: `cd backend && python -c 'from pipeline.quality.chat_eval import ChatEvalRunner; print("OK")'` +**Expected:** Prints "OK", exit code 0. + +### Test 3: Chat Eval CLI Subcommand +**Steps:** +1. Run: `cd backend && python -m pipeline.quality chat_eval --help` +**Expected:** Shows usage with `--suite`, `--base-url`, `--output`, `--timeout` arguments. Exit code 0. + +### Test 4: YAML Test Suite Loads Correctly +**Steps:** +1. Run: `cd backend && python -c "import yaml; suite = yaml.safe_load(open('pipeline/quality/fixtures/chat_test_suite.yaml')); cases = suite['test_cases']; print(f'{len(cases)} cases'); assert len(cases) >= 10; categories = set(c['category'] for c in cases); print(f'Categories: {categories}'); assert 'technical' in categories and 'creator_scoped' in categories"` +**Expected:** Reports 10+ cases, categories include 'technical' and 'creator_scoped'. Exit code 0. + +### Test 5: All Chat Tests Pass After Prompt Rewrite +**Steps:** +1. Run: `cd backend && python -m pytest tests/test_chat.py -v` +**Expected:** 26 tests pass. No failures. + +### Test 6: System Prompt Contains Citation Guidance +**Steps:** +1. Run: `cd backend && python -c "from chat_service import _SYSTEM_PROMPT_TEMPLATE; assert 'citation' in _SYSTEM_PROMPT_TEMPLATE.lower() or '[N]' in _SYSTEM_PROMPT_TEMPLATE; print('Citation guidance present')"` +**Expected:** Prints "Citation guidance present". The prompt includes inline citation format guidance. + +### Test 7: System Prompt Contains Domain Awareness +**Steps:** +1. Run: `cd backend && python -c "from chat_service import _SYSTEM_PROMPT_TEMPLATE; t = _SYSTEM_PROMPT_TEMPLATE.lower(); assert 'music' in t or 'audio' in t or 'production' in t; print('Domain awareness present')"` +**Expected:** Prints "Domain awareness present". The prompt mentions the music production domain. + +### Test 8: Quality Report Exists and Has Substance +**Steps:** +1. Run: `wc -l .gsd/milestones/M025/slices/S09/S09-QUALITY-REPORT.md` +**Expected:** At least 30 lines. +2. Run: `grep -c '##' .gsd/milestones/M025/slices/S09/S09-QUALITY-REPORT.md` +**Expected:** At least 3 section headings. + +### Test 9: Baseline JSON Exists +**Steps:** +1. Run: `python -c "import json; d = json.load(open('backend/pipeline/quality/results/chat_eval_baseline.json')); print(f'Keys: {list(d.keys())}')" ` +**Expected:** Valid JSON with structured evaluation data. + +### Edge Cases +- **Chat eval with unreachable endpoint:** `cd backend && python -m pipeline.quality chat_eval --suite pipeline/quality/fixtures/chat_test_suite.yaml --base-url http://localhost:99999 --timeout 2` should fail gracefully with a connection error, not a traceback. +- **Scorer with weight=0 personality:** The personality_fidelity dimension should still produce a valid score (checking that personality is appropriately absent). diff --git a/.gsd/milestones/M025/slices/S09/tasks/T03-VERIFY.json b/.gsd/milestones/M025/slices/S09/tasks/T03-VERIFY.json new file mode 100644 index 0000000..0005a09 --- /dev/null +++ b/.gsd/milestones/M025/slices/S09/tasks/T03-VERIFY.json @@ -0,0 +1,16 @@ +{ + "schemaVersion": 1, + "taskId": "T03", + "unitId": "M025/S09/T03", + "timestamp": 1775314244259, + "passed": true, + "discoverySource": "task-plan", + "checks": [ + { + "command": "test -f .gsd/milestones/M025/slices/S09/S09-QUALITY-REPORT.md", + "exitCode": 0, + "durationMs": 9, + "verdict": "pass" + } + ] +} diff --git a/.gsd/milestones/M025/slices/S10/S10-PLAN.md b/.gsd/milestones/M025/slices/S10/S10-PLAN.md index 282f1e7..00b1644 100644 --- a/.gsd/milestones/M025/slices/S10/S10-PLAN.md +++ b/.gsd/milestones/M025/slices/S10/S10-PLAN.md @@ -1,6 +1,75 @@ # S10: Requirement Validation (R015, R037-R041) -**Goal:** Formally validate all outstanding active requirements from Phase 1 +**Goal:** Requirements R015, R037, R038, R039, and R041 are formally validated against their stated criteria and marked as validated in REQUIREMENTS.md. **Demo:** After this: R015, R037, R038, R039, R041 formally validated and signed off ## Tasks +- [x] **T01: Added ReadingHeader sticky bar that slides in when scrolling past technique page title, showing truncated article title with CSS transition** — Build a thin sticky reading header that appears when the user scrolls past the technique page title bar. Shows truncated article title + current section name derived from activeId. Slides in/out with CSS transition. Responsive at 375px. + +The TechniquePage already has: +- `activeId` state driven by IntersectionObserver on section headings +- `titleBarRef` ref on the existing title bar div +- `displaySections` (BodySectionV2[]) with heading text +- `slugify()` imported from TableOfContents + +Steps: +1. Read `frontend/src/pages/TechniquePage.tsx` to understand the activeId/titleBarRef setup +2. Create `frontend/src/components/ReadingHeader.tsx`: + - Props: `title: string`, `activeId: string`, `sections: BodySectionV2[]`, `visible: boolean` + - Resolve `activeId` to human-readable section name by matching against sections array (handle compound slugs with `--` separator for subsections) + - Render: thin bar with truncated title (CSS text-overflow: ellipsis) + " · " + section name + - Use `transform: translateY(-100%)` when hidden, `translateY(0)` when visible, with `transition: transform 300ms ease` + - Position: fixed, top: 0, full width, z-index: 60 (above technique-title-bar at z-index: 50) +3. Add CSS to `frontend/src/App.css` in a new `/* ── Reading Header */` section: + - Height ~40px, dark background matching page, subtle bottom border + - Title truncated with ellipsis, section name in accent color + - Mobile (max-width: 600px): smaller font, section name may wrap +4. In `TechniquePage.tsx`: + - Add state: `const [titlePastView, setTitlePastView] = useState(false)` + - Add IntersectionObserver on `titleBarRef` that sets titlePastView to true when title bar exits viewport (isIntersecting === false) + - Import and render `` above the main content, passing title, activeId, displaySections, and titlePastView +5. Rebuild frontend on ub01: `ssh ub01 'cd /vmPool/r/repos/xpltdco/chrysopedia && git pull && docker compose build chrysopedia-web-8096 && docker compose up -d chrysopedia-web-8096'` +6. Verify: open http://ub01:8096/techniques/{any-slug}, scroll past title, confirm reading header slides in with section name + - Estimate: 45m + - Files: frontend/src/components/ReadingHeader.tsx, frontend/src/pages/TechniquePage.tsx, frontend/src/App.css + - Verify: ssh ub01 'curl -sf http://localhost:8096/ | grep -q ReadingHeader || true' && echo 'Build deployed' — then browser verification: navigate to a technique page, scroll past title, confirm reading header appears with section tracking +- [ ] **T02: Browser-validate R015, R037, R038, R039, R041 and update requirement statuses** — Run browser-based validation against the live deployment at ub01:8096 for all five requirements. Capture evidence (screenshots, timing, assertions). Update each requirement to 'validated' status in REQUIREMENTS.md. + +Validation plan per requirement: + +**R015 — 30-Second Retrieval Target:** +- Navigate to http://ub01:8096 +- Record start time +- Type 'snare' in search, click first result +- Verify technique page content is visible (h1, body sections) +- Record end time, assert < 30 seconds + +**R037 — Landing Page Visual Consistency:** +- Navigate to http://ub01:8096 at 1280px viewport +- Screenshot homepage — verify card alignment, stats scorecard visible +- Switch to 375px viewport, screenshot — verify no layout breakage +- Assert count-up animation fired (stats section has non-zero numbers) + +**R038 — Pipeline Admin UI Fixes:** +- Navigate to http://ub01:8096/admin/pipeline (login if needed — check if auth is required) +- Verify collapse toggle exists and is clickable +- Verify creator dropdown is populated (has >1 option) +- Switch to 375px, screenshot — verify no vertical text on cards + +**R039 — Brand Minimum:** +- Navigate to http://ub01:8096 +- Assert favicon link in DOM: `link[rel='icon']` with href containing 'favicon' +- Assert OG tags: `meta[property='og:title']`, `meta[property='og:image']` +- Assert logo element visible in header +- Verify assets return 200: `ssh ub01 'curl -sf -o /dev/null -w "%{http_code}" http://localhost:8096/favicon.svg'` + +**R041 — Sticky Reading Header:** +- Navigate to a technique page (pick one with 4+ sections) +- Scroll past the title bar +- Assert reading header element is visible with section name +- Test at 375px viewport + +After all pass, update `.gsd/REQUIREMENTS.md` — change R015, R037, R038, R039, R041 from 'active' to 'validated' with evidence notes. + - Estimate: 45m + - Files: .gsd/REQUIREMENTS.md + - Verify: grep -c 'validated' .gsd/REQUIREMENTS.md shows 5 more validated requirements than before (R015, R037, R038, R039, R041 all show Status: validated) diff --git a/.gsd/milestones/M025/slices/S10/S10-RESEARCH.md b/.gsd/milestones/M025/slices/S10/S10-RESEARCH.md new file mode 100644 index 0000000..a7bc4af --- /dev/null +++ b/.gsd/milestones/M025/slices/S10/S10-RESEARCH.md @@ -0,0 +1,122 @@ +# S10 Research — Requirement Validation (R015, R037-R041) + +## Summary + +This slice formally validates five active requirements against their stated criteria. No new code is expected — this is a verification-and-signoff pass. The stack is fully deployed and healthy on ub01:8096. Four of five requirements appear fully satisfied. R041 has a notable gap: the current implementation uses a persistent sticky title bar rather than the specified "thin bar that appears on scroll with section tracking." + +## Requirement-by-Requirement Assessment + +### R015 — 30-Second Retrieval Target +**Status:** Ready to validate +**Criteria:** Timed test: Alt+Tab → search → read technique → under 30 seconds. +**Evidence so far:** +- Search API responds in ~2.0s consistently (`curl` to `/api/v1/search?q=snare&limit=5`) +- Search returns relevant results with title, summary, creator, and direct links +- Technique pages load with full content (title bar, body sections, ToC) +- Estimated path: page load (~1s) + type query (~3s) + API response (~2s) + click result (~1s) + technique load (~1s) + read insight (~5s) ≈ 13s — well under 30 +**Verification approach:** Browser automation: navigate to homepage, type a search query, click first result, verify technique content is visible. Time the entire flow. The 2s API latency is the bottleneck but leaves ample margin. + +### R037 — Landing Page Visual Consistency +**Status:** Ready to validate +**Criteria:** Visual comparison at 1280px and 375px shows consistent alignment, spacing, card radius. No jagged center column. Featured card has rounded corners. Stats scorecard has animated count-up. +**Evidence so far:** +- `Home.tsx` uses `useCountUp` hook for animated stats scorecard ✓ +- CSS custom properties (`--color-*`, `--spacing-*`) used throughout for consistency +- Card stagger animations present via `card-stagger` class +- Stats scorecard renders technique count + creator count with animation refs +**Verification approach:** Browser screenshots at 1280px and 375px. Check border-radius on featured card. Verify count-up animation fires (check that the number is non-zero after page load). + +### R038 — Pipeline Admin UI Fixes +**Status:** Ready to validate +**Criteria:** Collapse toggle works, mobile cards no vertical text, chevrons visible, button-group filter, creator dropdown populated. +**Evidence so far:** +- `AdminPipeline.tsx` has collapse toggle with `collapsed` state and `▸`/`▾` arrow indicators ✓ +- `StatusFilter` component uses `filter-tab` buttons (not text input) ✓ +- Creator dropdown: `creatorFilter` state + `