# S03: Technique Page Redesign + Video Source on Moments

**Goal:** Technique page matches the reference layout: meta stats line below title, key moments show source video filename, signal chains render as monospace flow blocks with arrow separators instead of numbered lists.
**Demo:** After this: Technique page matches reference HTML layout with video source on key moments, signal chain blocks, and proper section structure

## Tasks
- [x] **T01: Added video_filename field to KeyMomentSummary schema and populated it via chained selectinload in the technique detail endpoint** — Extend the backend technique detail endpoint to include the source video filename on each key moment. This is the data prerequisite for all frontend changes in T02.

## Steps

1. In `backend/schemas.py`, add `video_filename: str = ""` to the `KeyMomentSummary` class.
2. In `backend/routers/techniques.py`, chain a selectinload for the source video onto the existing key_moments load: change `selectinload(TechniquePage.key_moments)` to `selectinload(TechniquePage.key_moments).selectinload(KeyMoment.source_video)`. Import `SourceVideo` from models if not already imported.
3. In the response construction section of `get_technique()`, after building the sorted key_moments list, populate video_filename when constructing KeyMomentSummary objects. Change the model_validate call to manually construct the dict or use a post-processing step:
   ```python
   key_moment_items = []
   for km in key_moments:
       item = KeyMomentSummary.model_validate(km)
       item.video_filename = km.source_video.filename if km.source_video else ""
       key_moment_items.append(item)
   ```
4. Verify locally or on ub01 that `curl http://ub01:8096/api/v1/techniques/wave-shaping-synthesis-m-wave-shaper | python3 -m json.tool` now includes `video_filename` in each key_moments entry.

## Must-Haves

- [ ] `KeyMomentSummary` schema has `video_filename: str = ""` field
- [ ] Technique detail query uses chained selectinload for source_video
- [ ] Each key moment in API response includes populated `video_filename`
- [ ] Existing fields and behavior unchanged
  - Estimate: 20m
  - Files: backend/schemas.py, backend/routers/techniques.py
  - Verify: ssh ub01 'cd /vmPool/r/repos/xpltdco/chrysopedia && docker compose build chrysopedia-api && docker compose up -d chrysopedia-api && sleep 3 && curl -s http://localhost:8096/api/v1/techniques/ | python3 -m json.tool | head -5' && ssh ub01 'curl -s http://localhost:8096/api/v1/techniques/wave-shaping-synthesis-m-wave-shaper | python3 -c "import sys,json; d=json.load(sys.stdin); kms=d.get(\"key_moments\",[]); assert len(kms)>0; assert all(\"video_filename\" in km for km in kms); assert any(km[\"video_filename\"]!=\"\" for km in kms); print(f\"OK: {len(kms)} moments, all have video_filename\")"'
- [x] **T02: Redesigned technique page frontend: meta stats line, video filenames on key moments, monospace signal chain flow blocks with arrow separators** — Redesign the frontend technique page to match the spec layout. Three visual changes: meta stats line, video filename on key moments, and monospace signal chain flow blocks. All CSS must use existing `var(--*)` tokens.

## Steps

1. In `frontend/src/api/public-client.ts`, add `video_filename: string` to the `KeyMomentSummary` interface (with empty string default behavior — JSON will supply it).

2. In `frontend/src/pages/TechniquePage.tsx`, add a meta stats line between the header and summary sections:
   - Compute unique source count: `new Set(technique.key_moments.map(km => km.video_filename).filter(Boolean)).size`
   - Moment count: `technique.key_moments.length`
   - Last updated: format `technique.updated_at` as a readable date
   - Render: `<div className="technique-header__stats">Compiled from {N} source{s} · {M} key moment{s} · Last updated {date}</div>`

3. In the key moments section, add the video filename between the title and timestamp:
   ```tsx
   {km.video_filename && (
     <span className="technique-moment__source">{km.video_filename}</span>
   )}
   ```

4. Redesign the signal chains section. Replace the `<ol className="technique-chain__steps">` with a monospace flow block:
   ```tsx
   <div className="technique-chain__flow">
     {steps.map((step, j) => (
       <span key={j}>
         {j > 0 && <span className="technique-chain__arrow"> → </span>}
         <span className="technique-chain__step">{String(step)}</span>
       </span>
     ))}
   </div>
   ```

5. In `frontend/src/App.css`, add CSS classes using only `var(--*)` tokens:
   - `.technique-header__stats` — secondary text color, smaller font, margin below header
   - `.technique-moment__source` — truncated text, muted color, monospace-ish, smaller font
   - `.technique-chain__flow` — monospace font, horizontal wrap, background surface, padding, border-radius
   - `.technique-chain__arrow` — accent color (cyan)
   - `.technique-chain__step` — inline display
   - Remove or restyle `.technique-chain__steps` (the old `<ol>` style)

6. Run `cd frontend && npm run build` to verify zero TypeScript errors.

7. Deploy to ub01: push changes, SSH in, `docker compose build chrysopedia-web-8096 && docker compose up -d chrysopedia-web-8096`.

## Constraints
- All CSS colors MUST use `var(--*)` tokens. The S02 dark theme established 84 semantic custom properties. Grep verification: `grep -P '#[0-9a-fA-F]{3,8}' frontend/src/App.css | grep -v ':root' | grep -v '^\/\*'` should return zero new hex colors.
- Do NOT modify `:root` custom property definitions unless adding a genuinely new semantic token.

## Must-Haves

- [ ] TypeScript `KeyMomentSummary` includes `video_filename: string`
- [ ] Meta stats line renders below technique title
- [ ] Key moment rows show video filename
- [ ] Signal chains render as monospace flow with arrow separators
- [ ] All new CSS uses `var(--*)` tokens only
- [ ] `npm run build` succeeds with zero errors
  - Estimate: 45m
  - Files: frontend/src/api/public-client.ts, frontend/src/pages/TechniquePage.tsx, frontend/src/App.css
  - Verify: cd frontend && npm run build 2>&1 | tail -5 && echo 'BUILD OK' && grep -cP '#[0-9a-fA-F]{3,8}' src/App.css | grep -v ':root' || echo 'No hardcoded colors outside :root'
