docs: Updated 7 Forgejo wiki pages with M024 feature documentation cove…

- "Home.md"
- "Data-Model.md"
- "API-Surface.md"
- "Frontend.md"
- "Pipeline.md"
- "Player.md"
- "Chat-Engine.md"

GSD-Task: S06/T01
This commit is contained in:
jlightner 2026-04-04 11:56:06 +00:00
parent 0eecee4271
commit a4a6187de2
9 changed files with 556 additions and 2 deletions

View file

@ -374,3 +374,9 @@
**Context:** Some API endpoints need to be publicly accessible but provide different behavior for authenticated users (e.g., showing draft posts to the owner). Using the standard `get_current_user` dependency rejects unauthenticated requests with 401.
**Fix:** Create `get_optional_user` using `OAuth2PasswordBearer(auto_error=False)`. When `auto_error=False`, missing/invalid tokens return `None` instead of raising 401. The endpoint receives `Optional[User]` and branches on whether the user is present and matches the resource owner.
## Pass CSS module styles as Record<string, string> to shared utilities
**Context:** When extracting a shared React utility (e.g., `parseChatCitations`) that renders JSX with CSS module class names, the utility needs the calling component's styles object. TypeScript's `CSSModuleClasses` type is structurally incompatible across module boundaries — passing `styles` typed as the specific module's interface causes type errors.
**Fix:** Type the styles parameter as `Record<string, string>`. CSS modules already satisfy this shape at runtime. This allows any component to pass its own CSS module styles to the shared utility without type gymnastics.

View file

@ -10,5 +10,5 @@ Shorts pipeline goes end-to-end with captioning and templates. Player gets key m
| S02 | [A] Key Moment Pins on Player Timeline | low | — | ✅ | Key technique moments appear as clickable pins on the player timeline |
| S03 | [A] Embed Support (iframe Snippet) | low | — | ✅ | Creators can copy an iframe embed snippet to put the player on their own site |
| S04 | [B] Auto-Captioning + Template System | medium | — | ✅ | Shorts have Whisper-generated animated subtitles and creator-configurable intro/outro cards |
| S05 | [B] Citation UX Improvements | low | — | | Chat citations show timestamp links that seek the player and source cards with video thumbnails |
| S05 | [B] Citation UX Improvements | low | — | | Chat citations show timestamp links that seek the player and source cards with video thumbnails |
| S06 | Forgejo KB Update — Shorts, Embed, Citations | low | S01, S02, S03, S04, S05 | ⬜ | Forgejo wiki updated with shorts pipeline, embed system, citation architecture |

View file

@ -0,0 +1,94 @@
---
id: S05
parent: M024
milestone: M024
provides:
- Video metadata (source_video_id, start_time, end_time, video_filename) in search results and chat SSE source events
- Shared chatCitations.tsx and formatTime.ts utilities
requires:
[]
affects:
- S06
key_files:
- backend/search_service.py
- backend/chat_service.py
- frontend/src/utils/chatCitations.tsx
- frontend/src/utils/formatTime.ts
- frontend/src/api/chat.ts
- frontend/src/pages/ChatPage.tsx
- frontend/src/pages/ChatPage.module.css
- frontend/src/components/ChatWidget.tsx
- frontend/src/components/ChatWidget.module.css
key_decisions:
- Batch-fetch SourceVideo filenames using same pattern as creator batch-fetch
- Video fields are empty/None for non-key_moment types to keep dict shape uniform
- Pass CSS module styles as Record<string, string> to shared citation parser to avoid CSSModuleClasses structural typing mismatch
patterns_established:
- Shared parseChatCitations utility for citation rendering across ChatPage and ChatWidget
- Shared formatTime utility replacing duplicated time formatting across 4+ files
observability_surfaces:
- none
drill_down_paths:
- .gsd/milestones/M024/slices/S05/tasks/T01-SUMMARY.md
- .gsd/milestones/M024/slices/S05/tasks/T02-SUMMARY.md
duration: ""
verification_result: passed
completed_at: 2026-04-04T11:47:32.918Z
blocker_discovered: false
---
# S05: [B] Citation UX Improvements
**Chat citations now carry video metadata (source_video_id, start_time, end_time, video_filename) from backend through SSE to frontend, where timestamp badge links and video filename labels appear on source cards.**
## What Happened
Two tasks delivered end-to-end citation UX improvements for chat source cards.
**T01 (Backend):** Propagated video metadata through three code paths. In `_enrich_qdrant_results()`, added batch-fetch of SourceVideo filenames (same pattern as creator batch-fetch) and four video fields to the enriched dict. In `_keyword_search_and()`, added the same four fields from the already-joined SourceVideo. In `_build_sources()` in chat_service.py, passed all four fields through to SSE source events. Non-key_moment types get empty/None values, keeping dict shape uniform.
**T02 (Frontend):** Extended the ChatSource TypeScript interface with video fields. Created `utils/chatCitations.tsx` — a shared `parseChatCitations()` function that replaces duplicate implementations in both ChatPage and ChatWidget. Created `utils/formatTime.ts` — a shared hour-aware time formatter replacing copies in 4+ files. Updated source card rendering in both components: when `start_time` is defined, a timestamp badge links to `/watch/:id?t=N`; video filename appears as subtle metadata. Added CSS classes `.timestampBadge` and `.videoMeta` to both module stylesheets.
## Verification
Backend: Both `from chat_service import _build_sources` and `from search_service import SearchService` import cleanly (exit 0). Frontend: `npm run build` passes with zero TypeScript/Vite errors. Deduplication confirmed: no `CITATION_RE` or local `parseCitations` functions remain in ChatPage.tsx or ChatWidget.tsx.
## Requirements Advanced
None.
## Requirements Validated
None.
## New Requirements Surfaced
None.
## Requirements Invalidated or Re-scoped
None.
## Deviations
T01: Fixed operator precedence bug — `(sv.filename or "") if sv else ""` needed parens. T02: Used `Record<string, string>` for styles param instead of strict CSSModuleClasses interface. Added `.sourceContent` wrapper div in ChatWidget for vertical layout.
## Known Limitations
Timestamp badges link to `/watch/:id?t=N` but actual player seek-on-load depends on WatchPage reading the `t` query param (already implemented in prior slices). No runtime integration test against live Qdrant/SSE — verified via import checks and build.
## Follow-ups
None.
## Files Created/Modified
- `backend/search_service.py` — Added video metadata fields to _enrich_qdrant_results() and _keyword_search_and() for key_moment results
- `backend/chat_service.py` — Added video fields to _build_sources() SSE source events
- `frontend/src/api/chat.ts` — Extended ChatSource interface with source_video_id, start_time, end_time, video_filename
- `frontend/src/utils/chatCitations.tsx` — New shared parseChatCitations utility extracted from ChatPage and ChatWidget
- `frontend/src/utils/formatTime.ts` — New shared formatTime utility replacing duplicated implementations
- `frontend/src/pages/ChatPage.tsx` — Replaced local citation parser with shared import, added timestamp badges and video metadata to source cards
- `frontend/src/pages/ChatPage.module.css` — Added .timestampBadge and .videoMeta CSS classes
- `frontend/src/components/ChatWidget.tsx` — Replaced local citation parser with shared import, added timestamp badges and video metadata to source cards
- `frontend/src/components/ChatWidget.module.css` — Added .timestampBadge, .videoMeta, and .sourceContent CSS classes

View file

@ -0,0 +1,60 @@
# S05: [B] Citation UX Improvements — UAT
**Milestone:** M024
**Written:** 2026-04-04T11:47:32.918Z
## UAT: Citation UX Improvements
### Preconditions
- Chrysopedia stack running on ub01:8096
- At least one key_moment with a source_video_id exists in the database
- Chat endpoint functional with LightRAG backend
### Test 1: Backend — Video metadata in search results
1. Open browser to `http://ub01:8096`
2. Search for a term that matches a key moment (e.g., a technique name from a known video)
3. Open browser dev tools → Network tab
4. Observe the search API response JSON
5. **Expected:** Key moment results include `source_video_id` (non-empty string), `start_time` (number or null), `end_time` (number or null), `video_filename` (string or empty)
### Test 2: Backend — Video metadata in chat SSE source events
1. Open the chat interface at `http://ub01:8096/chat`
2. Ask a question that should retrieve key moment sources
3. Open browser dev tools → Network tab → find the SSE stream
4. Inspect the `sources` SSE event data
5. **Expected:** Source objects for key_moment-type results include `source_video_id`, `start_time`, `end_time`, `video_filename` fields
### Test 3: Frontend — Timestamp badge on chat source cards
1. In the chat page, after receiving a response with key_moment sources
2. Look at the source cards below the response
3. **Expected:** Source cards for key moments show a timestamp badge (e.g., "1:23 2:45") that is a clickable link
4. Click the timestamp badge
5. **Expected:** Navigates to `/watch/{video_id}?t={start_time}` — the video watch page at the correct timestamp
### Test 4: Frontend — Video filename metadata on source cards
1. In the chat page with key_moment sources visible
2. Look at the source card metadata
3. **Expected:** Video filename appears as subtle text below or beside the source title
### Test 5: Frontend — ChatWidget has same citation features
1. Navigate to a technique page that has the ChatWidget in the sidebar
2. Ask a question that returns key_moment sources
3. **Expected:** Source cards in the widget also show timestamp badges and video filename, matching ChatPage behavior
### Test 6: Frontend — Shared citation parser deduplication
1. Open `frontend/src/pages/ChatPage.tsx` in editor
2. Search for `CITATION_RE` or `function parseCitations`
3. **Expected:** Neither found — all citation parsing uses the shared `utils/chatCitations.tsx`
4. Repeat for `frontend/src/components/ChatWidget.tsx`
5. **Expected:** Same — no local citation parsing code
### Test 7: Frontend — formatTime utility
1. Open `frontend/src/utils/formatTime.ts`
2. **Expected:** Exports a `formatTime(seconds: number): string` function
3. Format: `M:SS` for times under 1 hour, `H:MM:SS` for 1 hour or more
4. Verify ChatPage and ChatWidget import from this shared utility (not local copies)
### Edge Cases
- **Non-key_moment sources:** Technique page sources should render normally without timestamp badges (start_time is undefined/null)
- **Missing video_filename:** Source card should render without the filename line when video_filename is empty
- **Zero start_time:** A start_time of 0 should still show the badge (formatted as "0:00")

View file

@ -0,0 +1,16 @@
{
"schemaVersion": 1,
"taskId": "T02",
"unitId": "M024/S05/T02",
"timestamp": 1775303160365,
"passed": true,
"discoverySource": "task-plan",
"checks": [
{
"command": "cd frontend",
"exitCode": 0,
"durationMs": 6,
"verdict": "pass"
}
]
}

View file

@ -1,6 +1,88 @@
# S06: Forgejo KB Update — Shorts, Embed, Citations
**Goal:** Document new systems in Forgejo knowledgebase
**Goal:** Forgejo wiki updated with M024 feature documentation covering shorts publishing, embed support, key moment pins, auto-captioning/templates, and citation UX improvements.
**Demo:** After this: Forgejo wiki updated with shorts pipeline, embed system, citation architecture
## Tasks
- [x] **T01: Updated 7 Forgejo wiki pages with M024 feature documentation covering shorts publishing, embed support, timeline pins, auto-captioning, templates, and citation UX** — Clone the Chrysopedia Forgejo wiki on ub01, update Home.md, Data-Model.md, API-Surface.md, Frontend.md, Pipeline.md, Player.md, and Chat-Engine.md with M024 S01-S05 feature content, then commit and push.
**CRITICAL:** Never use the Forgejo wiki PATCH API — it corrupts pages (see KNOWLEDGE.md). Git clone → edit → push only.
All SSH/git operations happen on ub01 via `ssh ub01`.
Wiki repo: `https://git.xpltd.co/xpltdco/chrysopedia.wiki.git`
## Content to add per page:
### Home.md
Add to the feature list / recent additions:
- Shorts publishing flow: public shareable URLs via /shorts/{token}, share_token generated at pipeline completion
- Embed support: chrome-free /embed/:videoId player, iframe snippet with audio-aware height
- Key moment timeline pins: 12px color-coded circle pins (technique=cyan, settings=amber, reasoning=purple, workflow=green), active-state highlighting, touch-friendly hit areas
- Inline collapsible player on technique pages with bibliography seek wiring
- Auto-captioning: ASS karaoke subtitles from Whisper word-level timings, \k tags for word-by-word highlighting
- Template system: creator-configurable intro/outro cards via admin API, ffmpeg lavfi card rendering, concat demuxer assembly
- Citation UX: timestamp badge links to /watch/:id?t=N, video filename on source cards, shared chatCitations.tsx + formatTime.ts utilities
### Data-Model.md
Add new columns:
- `share_token` (String(16), unique-indexed, nullable) on GeneratedShort — generated via secrets.token_urlsafe(8) at pipeline completion
- `captions_enabled` (Boolean, default false) on GeneratedShort — indicates successful ASS subtitle generation
- `shorts_template` (JSONB, nullable) on Creator — intro/outro card config with parse_template_config normalizer
Add migrations: 026 (share_token + backfill + unique index), 027 (captions_enabled), 028 (shorts_template)
### API-Surface.md
Add new endpoints:
- `GET /api/v1/public/shorts/{share_token}` — unauthenticated, returns metadata + presigned MinIO URL, 404 for missing/non-complete shorts
- `GET /api/v1/admin/creators/{id}/shorts-template` — returns JSONB template config
- `PUT /api/v1/admin/creators/{id}/shorts-template` — updates template config with validation (hex color, duration 1.0-5.0)
Update: `POST generate-shorts` now accepts `captions` boolean parameter
Update endpoint total count (was ~92, now ~95)
### Frontend.md
Add new pages/components:
- ShortPlayer (`/shorts/:token`) — public page outside ProtectedRoute, fetches via unauthenticated API, renders video + metadata + share/embed copy buttons
- EmbedPlayer (`/embed/:videoId`) — chrome-free, registered at top-level Routes before AppShell catch-all, content-type-aware height (120px audio, 405px video), "Powered by Chrysopedia" branding
- ChapterMarkers upgrade: 12px circle pins replacing 3px lines, color-coded by content_type via --color-pin-{type} CSS custom properties, active-state 1.3x scale, ::before inset:-6px touch targets, tooltips with title + time range + content type
- Inline collapsible player on TechniquePage between summary and body, grid-template-rows 0fr/1fr animation, multi-source-video selector, bibliography seek buttons
Add shared utilities:
- `utils/clipboard.ts` — shared copyToClipboard (navigator.clipboard + execCommand fallback)
- `utils/chatCitations.tsx` — shared parseChatCitations replacing duplicate implementations
- `utils/formatTime.ts` — shared hour-aware time formatter
Add HighlightQueue updates: share link + embed code copy buttons, collapsible template config panel (intro/outro text, duration sliders, show/hide, color picker, font), per-highlight captions toggle
### Pipeline.md
Add new modules:
- `caption_generator.py`: generate_ass_captions() converts Whisper word-level timings to ASS subtitles with \k karaoke tags, clip-relative timing. Non-blocking — failures log WARNING, don't fail stage.
- `card_renderer.py`: render_card() (lavfi color + drawtext), render_card_to_file() (ffmpeg executor), build_concat_list() + concat_segments() (ffmpeg concat demuxer), parse_template_config() (JSONB normalizer with defaults). Cards include anullsrc silent audio for codec-compatible concat.
- `shorts_generator.py` updates: extract_clip() accepts optional ass_path for subtitle burn-in, extract_clip_with_template() for intro/main/outro concatenation
- stage_generate_shorts updates: loads transcripts for captions, loads creator templates for cards, generates share_token on completion
Note 45 unit tests (17 caption + 28 card)
### Player.md
Add key moment pins section:
- ChapterMarkers: 12px circle pins, color-coded (technique=cyan, settings=amber, reasoning=purple, workflow=green)
- Active-state: 1.3x scale when playback within time range
- Touch: ::before pseudo-element with inset:-6px
- Tooltips: title + formatted time range + content type label
Add inline player on technique pages:
- Collapsible section between summary and body
- Multi-source-video selector for multi-video technique pages
- Bibliography time links render as seek buttons when inline player active, Links to WatchPage otherwise
Add embed player:
- /embed/:videoId — chrome-free, no header/nav/footer
- Content-type-aware: video (405px) vs audio (120px)
- "Powered by Chrysopedia" branding link with noopener
### Chat-Engine.md
Add citation metadata propagation:
- Backend: search_service enriches results with source_video_id, start_time, end_time, video_filename via batch-fetch of SourceVideo filenames
- Backend: chat_service _build_sources() passes video fields through SSE source events
- Frontend: ChatSource interface extended with video fields
- Frontend: shared parseChatCitations() replaces duplicate citation parsers in ChatPage and ChatWidget
- Frontend: timestamp badge links to /watch/:id?t=N on source cards
- Frontend: video filename metadata on source cards
- Estimate: 45m
- Files: Home.md (wiki), Data-Model.md (wiki), API-Surface.md (wiki), Frontend.md (wiki), Pipeline.md (wiki), Player.md (wiki), Chat-Engine.md (wiki)
- Verify: 1. `ssh ub01 'cd /tmp/chrysopedia-wiki-m024 && git log --oneline -1'` — shows M024 commit
2. `ssh ub01 'curl -s https://git.xpltd.co/api/v1/repos/xpltdco/chrysopedia/wiki/pages -H "Authorization: token $(cat /vmPool/r/repos/xpltdco/.forgejo-token)" | python3 -c "import sys,json; print(len(json.load(sys.stdin)))"'` — returns 20
3. `ssh ub01 'cd /tmp/chrysopedia-wiki-m024 && grep -c share_token Data-Model.md'` — returns ≥1

View file

@ -0,0 +1,99 @@
# S06 Research: Forgejo KB Update — Shorts, Embed, Citations
## Summary
Straightforward documentation slice following the established pattern (D034, prior KB slices M019/S06 through M023/S05). Clone Forgejo wiki via git on ub01, update existing pages with M024 feature content from S01-S05, commit, push. **Never use the Forgejo wiki PATCH API** (corrupts pages per KNOWLEDGE.md).
## Recommendation
Single task. Clone wiki, update 7 pages, push. Content sourced entirely from S01-S05 summaries already inlined in planner context. No new decisions to document from M024.
## Implementation Landscape
### Established Pattern (from M023/S05, M022/S07, etc.)
1. SSH to ub01
2. `git clone https://git.xpltd.co/xpltdco/chrysopedia.wiki.git` into a temp dir
3. Edit markdown files
4. `git add . && git commit -m "..." && git push origin main`
5. Verify via wiki pages API count (currently 20 pages)
### Current Wiki State
20 pages exist. Relevant ones to update:
| Page | What to add from M024 |
|------|----------------------|
| **Home.md** | Shorts publishing flow (public URLs), embed support (iframe snippets), key moment timeline pins, auto-captioning + template system, citation timestamp links |
| **Data-Model.md** | `share_token` (String(16), unique-indexed) on GeneratedShort, `captions_enabled` (Boolean) on GeneratedShort, `shorts_template` (JSONB) on Creator. Migrations 026-028. |
| **API-Surface.md** | 3 new endpoints: GET /api/v1/public/shorts/{share_token} (unauthenticated), GET/PUT /api/v1/admin/creators/{id}/shorts-template. `captions` param added to generate-shorts. Update total from ~71 to ~74. |
| **Frontend.md** | ShortPlayer (public /shorts/:token), EmbedPlayer (chrome-free /embed/:videoId before AppShell), ChapterMarkers (12px circle pins, color-coded by content_type, active state), inline collapsible player on TechniquePage with bibliography seek, shared copyToClipboard utility, shared chatCitations.tsx + formatTime.ts utilities |
| **Pipeline.md** | caption_generator.py (ASS karaoke subtitles from word-level timings, \k tags), card_renderer.py (ffmpeg lavfi drawtext cards, concat demuxer for intro/main/outro), parse_template_config normalizer, non-blocking enrichment pattern |
| **Player.md** | Key moment pins (color-coded: technique=cyan, settings=amber, reasoning=purple, workflow=green), active-state highlighting, touch-friendly ::before hit areas, inline player on technique pages, embed player |
| **Chat-Engine.md** | Citation video metadata propagation: source_video_id, start_time, end_time, video_filename in search results and SSE source events. Batch-fetch SourceVideo filenames. Timestamp badge links to /watch/:id?t=N |
### New Content by Feature
**S01 — Shorts Publishing Flow:**
- share_token column (secrets.token_urlsafe(8)), generated at pipeline completion
- Alembic 026: add column + backfill existing + unique index
- Public endpoint GET /api/v1/public/shorts/{share_token} — unauthenticated, returns metadata + presigned MinIO URL
- Returns 404 for both missing and non-complete shorts (no status leaking)
- ShortPlayer.tsx at /shorts/:token — public page outside ProtectedRoute
- Share link (🔗) and embed code (📋) copy buttons on HighlightQueue for completed shorts
**S02 — Key Moment Pins:**
- ChapterMarkers.tsx: 12px circle pins replacing 3px line markers
- Color-coded by content_type via --color-pin-{type} CSS custom properties
- Active-state highlighting (1.3x scale when playback within time range)
- Tooltips: title + formatted time range + content type label
- ::before pseudo-element with inset:-6px for touch-friendly hit areas
- Inline collapsible player on TechniquePage between summary and body sections
- Multi-source-video selector dropdown for multi-video technique pages
- Bibliography time links → seek buttons when inline player active, Links otherwise
**S03 — Embed Support:**
- EmbedPlayer.tsx at /embed/:videoId — chrome-free (no header/nav/footer)
- Route registered at top-level Routes before AppShell catch-all
- Video/audio support with content-type-aware height (120px audio, 405px video)
- "Powered by Chrysopedia" branding link with noopener
- Copy Embed Code button on WatchPage with audio-aware iframe snippet
- Shared copyToClipboard utility extracted to utils/clipboard.ts
**S04 — Auto-Captioning + Template System:**
- caption_generator.py: ASS subtitles with \k karaoke tags, word-by-word highlighting from Whisper word timings
- card_renderer.py: ffmpeg lavfi (color + drawtext) card generation, anullsrc silent audio for codec-compatible concat
- build_concat_list + concat_segments: ffmpeg concat demuxer for intro/main/outro assembly
- parse_template_config: JSONB normalizer with defaults
- Non-blocking: caption/card failures log WARNING, never fail parent short
- shorts_template JSONB on Creator model (Alembic 028)
- captions_enabled Boolean on GeneratedShort (Alembic 027)
- Admin API: GET/PUT /api/v1/admin/creators/{id}/shorts-template
- Frontend: collapsible template config panel in HighlightQueue, per-highlight captions toggle
- 45 unit tests (17 caption + 28 card)
**S05 — Citation UX Improvements:**
- Backend: video metadata (source_video_id, start_time, end_time, video_filename) added to search_service enrichment and chat_service SSE source events
- Batch-fetch SourceVideo filenames pattern (same as creator batch-fetch)
- Frontend: chatCitations.tsx shared utility, formatTime.ts shared utility
- Timestamp badge links to /watch/:id?t=N on source cards
- Video filename metadata on source cards
### No New Decisions
M024 made no new registered decisions (no D04x entries). Existing decisions (D042-D044 from M023) already documented in prior KB update.
### Constraints
- **CRITICAL:** Never use Forgejo wiki PATCH API. Git clone → edit → push only (KNOWLEDGE.md).
- Wiki repo URL: `https://git.xpltd.co/xpltdco/chrysopedia.wiki.git`
- All SSH/git operations happen on ub01 via `ssh ub01`
- Content must come from S01-S05 summaries — no fabrication
- Current endpoint count: ~92 (from rg count of @router decorators across all routers)
- Current wiki page count: 20
### Verification
1. `git push origin main` — exit code 0
2. Wiki page count API — should remain 20 (no new pages, only updates)
3. Spot-check one updated page via API

View file

@ -0,0 +1,109 @@
---
estimated_steps: 67
estimated_files: 7
skills_used: []
---
# T01: Update 7 Forgejo wiki pages with M024 feature content
Clone the Chrysopedia Forgejo wiki on ub01, update Home.md, Data-Model.md, API-Surface.md, Frontend.md, Pipeline.md, Player.md, and Chat-Engine.md with M024 S01-S05 feature content, then commit and push.
**CRITICAL:** Never use the Forgejo wiki PATCH API — it corrupts pages (see KNOWLEDGE.md). Git clone → edit → push only.
All SSH/git operations happen on ub01 via `ssh ub01`.
Wiki repo: `https://git.xpltd.co/xpltdco/chrysopedia.wiki.git`
## Content to add per page:
### Home.md
Add to the feature list / recent additions:
- Shorts publishing flow: public shareable URLs via /shorts/{token}, share_token generated at pipeline completion
- Embed support: chrome-free /embed/:videoId player, iframe snippet with audio-aware height
- Key moment timeline pins: 12px color-coded circle pins (technique=cyan, settings=amber, reasoning=purple, workflow=green), active-state highlighting, touch-friendly hit areas
- Inline collapsible player on technique pages with bibliography seek wiring
- Auto-captioning: ASS karaoke subtitles from Whisper word-level timings, \k tags for word-by-word highlighting
- Template system: creator-configurable intro/outro cards via admin API, ffmpeg lavfi card rendering, concat demuxer assembly
- Citation UX: timestamp badge links to /watch/:id?t=N, video filename on source cards, shared chatCitations.tsx + formatTime.ts utilities
### Data-Model.md
Add new columns:
- `share_token` (String(16), unique-indexed, nullable) on GeneratedShort — generated via secrets.token_urlsafe(8) at pipeline completion
- `captions_enabled` (Boolean, default false) on GeneratedShort — indicates successful ASS subtitle generation
- `shorts_template` (JSONB, nullable) on Creator — intro/outro card config with parse_template_config normalizer
Add migrations: 026 (share_token + backfill + unique index), 027 (captions_enabled), 028 (shorts_template)
### API-Surface.md
Add new endpoints:
- `GET /api/v1/public/shorts/{share_token}` — unauthenticated, returns metadata + presigned MinIO URL, 404 for missing/non-complete shorts
- `GET /api/v1/admin/creators/{id}/shorts-template` — returns JSONB template config
- `PUT /api/v1/admin/creators/{id}/shorts-template` — updates template config with validation (hex color, duration 1.0-5.0)
Update: `POST generate-shorts` now accepts `captions` boolean parameter
Update endpoint total count (was ~92, now ~95)
### Frontend.md
Add new pages/components:
- ShortPlayer (`/shorts/:token`) — public page outside ProtectedRoute, fetches via unauthenticated API, renders video + metadata + share/embed copy buttons
- EmbedPlayer (`/embed/:videoId`) — chrome-free, registered at top-level Routes before AppShell catch-all, content-type-aware height (120px audio, 405px video), "Powered by Chrysopedia" branding
- ChapterMarkers upgrade: 12px circle pins replacing 3px lines, color-coded by content_type via --color-pin-{type} CSS custom properties, active-state 1.3x scale, ::before inset:-6px touch targets, tooltips with title + time range + content type
- Inline collapsible player on TechniquePage between summary and body, grid-template-rows 0fr/1fr animation, multi-source-video selector, bibliography seek buttons
Add shared utilities:
- `utils/clipboard.ts` — shared copyToClipboard (navigator.clipboard + execCommand fallback)
- `utils/chatCitations.tsx` — shared parseChatCitations replacing duplicate implementations
- `utils/formatTime.ts` — shared hour-aware time formatter
Add HighlightQueue updates: share link + embed code copy buttons, collapsible template config panel (intro/outro text, duration sliders, show/hide, color picker, font), per-highlight captions toggle
### Pipeline.md
Add new modules:
- `caption_generator.py`: generate_ass_captions() converts Whisper word-level timings to ASS subtitles with \k karaoke tags, clip-relative timing. Non-blocking — failures log WARNING, don't fail stage.
- `card_renderer.py`: render_card() (lavfi color + drawtext), render_card_to_file() (ffmpeg executor), build_concat_list() + concat_segments() (ffmpeg concat demuxer), parse_template_config() (JSONB normalizer with defaults). Cards include anullsrc silent audio for codec-compatible concat.
- `shorts_generator.py` updates: extract_clip() accepts optional ass_path for subtitle burn-in, extract_clip_with_template() for intro/main/outro concatenation
- stage_generate_shorts updates: loads transcripts for captions, loads creator templates for cards, generates share_token on completion
Note 45 unit tests (17 caption + 28 card)
### Player.md
Add key moment pins section:
- ChapterMarkers: 12px circle pins, color-coded (technique=cyan, settings=amber, reasoning=purple, workflow=green)
- Active-state: 1.3x scale when playback within time range
- Touch: ::before pseudo-element with inset:-6px
- Tooltips: title + formatted time range + content type label
Add inline player on technique pages:
- Collapsible section between summary and body
- Multi-source-video selector for multi-video technique pages
- Bibliography time links render as seek buttons when inline player active, Links to WatchPage otherwise
Add embed player:
- /embed/:videoId — chrome-free, no header/nav/footer
- Content-type-aware: video (405px) vs audio (120px)
- "Powered by Chrysopedia" branding link with noopener
### Chat-Engine.md
Add citation metadata propagation:
- Backend: search_service enriches results with source_video_id, start_time, end_time, video_filename via batch-fetch of SourceVideo filenames
- Backend: chat_service _build_sources() passes video fields through SSE source events
- Frontend: ChatSource interface extended with video fields
- Frontend: shared parseChatCitations() replaces duplicate citation parsers in ChatPage and ChatWidget
- Frontend: timestamp badge links to /watch/:id?t=N on source cards
- Frontend: video filename metadata on source cards
## Inputs
- ``~/.gsd/milestones/M024/slices/S01/S01-SUMMARY.md` — shorts publishing flow details`
- ``~/.gsd/milestones/M024/slices/S02/S02-SUMMARY.md` — key moment pins details`
- ``~/.gsd/milestones/M024/slices/S03/S03-SUMMARY.md` — embed support details`
- ``~/.gsd/milestones/M024/slices/S04/S04-SUMMARY.md` — auto-captioning + template details`
- ``~/.gsd/milestones/M024/slices/S05/S05-SUMMARY.md` — citation UX details`
## Expected Output
- ``Home.md` — updated with M024 feature summary`
- ``Data-Model.md` — updated with share_token, captions_enabled, shorts_template columns and migrations 026-028`
- ``API-Surface.md` — updated with 3 new endpoints and captions parameter`
- ``Frontend.md` — updated with ShortPlayer, EmbedPlayer, ChapterMarkers upgrade, inline player, shared utilities`
- ``Pipeline.md` — updated with caption_generator, card_renderer, shorts_generator changes`
- ``Player.md` — updated with pin markers, inline player, embed player`
- ``Chat-Engine.md` — updated with citation metadata propagation`
## Verification
1. `ssh ub01 'cd /tmp/chrysopedia-wiki-m024 && git log --oneline -1'` — shows M024 commit
2. `ssh ub01 'curl -s https://git.xpltd.co/api/v1/repos/xpltdco/chrysopedia/wiki/pages -H "Authorization: token $(cat /vmPool/r/repos/xpltdco/.forgejo-token)" | python3 -c "import sys,json; print(len(json.load(sys.stdin)))"'` — returns 20
3. `ssh ub01 'cd /tmp/chrysopedia-wiki-m024 && grep -c share_token Data-Model.md'` — returns ≥1

View file

@ -0,0 +1,88 @@
---
id: T01
parent: S06
milestone: M024
provides: []
requires: []
affects: []
key_files: ["Home.md", "Data-Model.md", "API-Surface.md", "Frontend.md", "Pipeline.md", "Player.md", "Chat-Engine.md"]
key_decisions: ["Used git clone/push for wiki updates per KNOWLEDGE.md (never use Forgejo wiki PATCH API)"]
patterns_established: []
drill_down_paths: []
observability_surfaces: []
duration: ""
verification_result: "All 3 verification checks pass: git log shows M024 commit 0451abf, Forgejo API returns 20 wiki pages, grep finds share_token in Data-Model.md."
completed_at: 2026-04-04T11:56:03.189Z
blocker_discovered: false
---
# T01: Updated 7 Forgejo wiki pages with M024 feature documentation covering shorts publishing, embed support, timeline pins, auto-captioning, templates, and citation UX
> Updated 7 Forgejo wiki pages with M024 feature documentation covering shorts publishing, embed support, timeline pins, auto-captioning, templates, and citation UX
## What Happened
---
id: T01
parent: S06
milestone: M024
key_files:
- Home.md
- Data-Model.md
- API-Surface.md
- Frontend.md
- Pipeline.md
- Player.md
- Chat-Engine.md
key_decisions:
- Used git clone/push for wiki updates per KNOWLEDGE.md (never use Forgejo wiki PATCH API)
duration: ""
verification_result: passed
completed_at: 2026-04-04T11:56:03.190Z
blocker_discovered: false
---
# T01: Updated 7 Forgejo wiki pages with M024 feature documentation covering shorts publishing, embed support, timeline pins, auto-captioning, templates, and citation UX
**Updated 7 Forgejo wiki pages with M024 feature documentation covering shorts publishing, embed support, timeline pins, auto-captioning, templates, and citation UX**
## What Happened
Cloned the Chrysopedia Forgejo wiki on ub01, read all 5 slice summaries (S01-S05) for content details, updated Home.md, Data-Model.md, API-Surface.md, Frontend.md, Pipeline.md, Player.md, and Chat-Engine.md with comprehensive M024 feature documentation, then committed and pushed via git (per KNOWLEDGE.md rule to never use Forgejo wiki PATCH API).
## Verification
All 3 verification checks pass: git log shows M024 commit 0451abf, Forgejo API returns 20 wiki pages, grep finds share_token in Data-Model.md.
## Verification Evidence
| # | Command | Exit Code | Verdict | Duration |
|---|---------|-----------|---------|----------|
| 1 | `ssh ub01 'cd /tmp/chrysopedia-wiki-m024 && git log --oneline -1'` | 0 | ✅ pass | 500ms |
| 2 | `ssh ub01 'curl -s .../wiki/pages | python3 -c "..."'` | 0 | ✅ pass | 800ms |
| 3 | `ssh ub01 'cd /tmp/chrysopedia-wiki-m024 && grep -c share_token Data-Model.md'` | 0 | ✅ pass | 300ms |
## Deviations
None.
## Known Issues
None.
## Files Created/Modified
- `Home.md`
- `Data-Model.md`
- `API-Surface.md`
- `Frontend.md`
- `Pipeline.md`
- `Player.md`
- `Chat-Engine.md`
## Deviations
None.
## Known Issues
None.