Summary
+ +content-to-kb-automator is 82% complete across 10 milestones. $172.23 spent. Currently executing M009/S01.
+Blockers
+ +Progress
+ ++ + M001 + Chrysopedia Foundation — Infrastructure, Pipeline Core, and Skeleton UI + 5/5 + + +
++ + S01 + Docker Compose + Database + Whisper Script + low + + critical + +
+- [object Object]
- env_file uses required: false so docker compose config validates on fresh clones
- POSTGRES_PASSWORD uses :-changeme default instead of :? to avoid config failures
- PostgreSQL exposed on host port 5433 to avoid conflicts with other projects
- SQLAlchemy relationship import aliased to sa_relationship to avoid column name clash
- Separate PostgreSQL enum type names to avoid collisions (key_moment_content_type vs content_type)
- Health check at /health performs real DB SELECT 1; lightweight /api/v1/health also available
- Whisper import deferred so --help works without openai-whisper installed
- Sample transcript uses realistic music production content for downstream pipeline testing
- Docker Compose service naming: chrysopedia-{role} (chrysopedia-db, chrysopedia-api, etc.)
- Backend router pattern: backend/routers/{domain}.py with prefix-per-router mounted under /api/v1
- SQLAlchemy async pattern: asyncpg engine + async_sessionmaker + get_session dependency
- Pydantic v2 schema pattern: Base/Create/Read variants per entity with model_config from_attributes=True
- Config via pydantic-settings BaseSettings loading from .env with sensible defaults
- Alembic async migration pattern with run_async_migrations() wrapper
- UUID primary keys with gen_random_uuid() server default for all entities
+ + S02 + Transcript Ingestion API + low + S01 + + +1 slack +
+- Used NullPool for test engine to avoid asyncpg connection contention in pytest-asyncio
- Fixed _now() helper to return naive UTC datetimes for asyncpg TIMESTAMP WITHOUT TIME ZONE compatibility
- Used slugify helper inline in ingest.py rather than a shared utils module
- Set file_path to {creator_folder}/{source_file} for new SourceVideo records
- pytest-asyncio integration test pattern: function-scoped NullPool engine + ASGI transport client with dependency overrides
- Multipart JSON file upload pattern for FastAPI endpoints
- Creator auto-detection from folder_name with find-or-create and slugify
+ + S03 + LLM Extraction Pipeline + Qdrant Integration + high + S02 + critical + +
+- Sync OpenAI/SQLAlchemy/Qdrant throughout Celery tasks — no async in worker context (D004)
- Embedding/Qdrant stage is non-blocking side-effect — failures don't break pipeline (D005)
- Stage 4 classification stored in Redis (24h TTL) due to missing KeyMoment columns
- Pipeline dispatch from ingest is best-effort; manual trigger returns 503 on Celery failure
- LLMClient retries once with JSON nudge on malformed LLM output before failing
- Celery task pattern: @celery_app.task(bind=True, max_retries=3) with sync SQLAlchemy session per task
- LLM client pattern: primary → fallback → fail, with Pydantic response parsing
- Non-blocking side-effect pattern: max_retries=0, catch-all exception handler, pipeline continues
- Prompt template pattern: plain text files in prompts/ dir, XML-style content fencing, loaded at runtime
- Pipeline test pattern: patch module-level _engine/_SessionLocal globals to redirect stages to test DB
+ + S04 + Review Queue Admin UI + medium + S03 + + +
+- Redis mode toggle uses per-request get_redis() with aclose() — no connection pool (D007)
- API client uses bare fetch() with shared request() helper — no external HTTP library
- MomentDetail fetches full queue to find moment by ID since no single-moment GET endpoint exists
- Split creates new moment with '(split)' title suffix; merge combines summaries with double-newline separator
- Split dialog validates timestamp client-side before API call
- React + Vite + TypeScript frontend pattern: strict TS config, Vite dev proxy to backend, typed API client with fetch()-based request helper
- Reusable component extraction (StatusBadge, ModeToggle) for consistent styling across admin pages
- Review router pattern: async SQLAlchemy with joined loads for cross-table data (moment + video + creator)
- Redis as runtime config store with config.py fallback for settings that need to be mutable at runtime
+ + S05 + Search-First Web UI + medium + S03 + + +
+- [object Object]
- [object Object]
- [object Object]
- [object Object]
- [object Object]
- [object Object]
- 300ms asyncio.wait_for timeout on both embedding and Qdrant calls
- Topics endpoint loads canonical_tags.yaml at request time and counts tag matches from DB
- Mocked SearchService at router dependency level for integration tests
- Duplicated request<T> helper in public-client.ts to avoid coupling public and admin API clients
- Async service class pattern: create separate async client wrappers for FastAPI when sync clients exist for Celery
- Graceful degradation pattern: embedding/Qdrant timeout → keyword ILIKE fallback with fallback_used flag
- Typed public API client: separate from admin client, each with own request<T> helper
- URL param-driven search: query state in URL params for shareable/bookmarkable search results
- Router-level service mocking: patch SearchService at dependency level for clean integration tests
+ + M002 + M002: Chrysopedia Deployment — GitHub, ub01 Docker Stack, and Production Wiring + 3/3 + + +
++ + S01 + Fix Compose Config, Add Qdrant/Embeddings, Push to GitHub + medium + + critical + +
+- [object Object]
- env_file uses required: false so docker compose config validates on fresh clones
- POSTGRES_PASSWORD uses :-changeme default instead of :? to avoid config failures
- PostgreSQL exposed on host port 5433 to avoid conflicts with other projects
- SQLAlchemy relationship import aliased to sa_relationship to avoid column name clash
- Separate PostgreSQL enum type names to avoid collisions (key_moment_content_type vs content_type)
- Health check at /health performs real DB SELECT 1; lightweight /api/v1/health also available
- Whisper import deferred so --help works without openai-whisper installed
- Sample transcript uses realistic music production content for downstream pipeline testing
- Docker Compose service naming: chrysopedia-{role} (chrysopedia-db, chrysopedia-api, etc.)
- Backend router pattern: backend/routers/{domain}.py with prefix-per-router mounted under /api/v1
- SQLAlchemy async pattern: asyncpg engine + async_sessionmaker + get_session dependency
- Pydantic v2 schema pattern: Base/Create/Read variants per entity with model_config from_attributes=True
- Config via pydantic-settings BaseSettings loading from .env with sensible defaults
- Alembic async migration pattern with run_async_migrations() wrapper
- UUID primary keys with gen_random_uuid() server default for all entities
+ + S02 + Deploy to ub01 — Clone, Build, Start, Migrate + medium + S01 + + +1 slack +
+- Used NullPool for test engine to avoid asyncpg connection contention in pytest-asyncio
- Fixed _now() helper to return naive UTC datetimes for asyncpg TIMESTAMP WITHOUT TIME ZONE compatibility
- Used slugify helper inline in ingest.py rather than a shared utils module
- Set file_path to {creator_folder}/{source_file} for new SourceVideo records
- pytest-asyncio integration test pattern: function-scoped NullPool engine + ASGI transport client with dependency overrides
- Multipart JSON file upload pattern for FastAPI endpoints
- Creator auto-detection from folder_name with find-or-create and slugify
+ + S03 + CLAUDE.md Redirect and Development Path Setup + low + S02 + critical + +
+- Sync OpenAI/SQLAlchemy/Qdrant throughout Celery tasks — no async in worker context (D004)
- Embedding/Qdrant stage is non-blocking side-effect — failures don't break pipeline (D005)
- Stage 4 classification stored in Redis (24h TTL) due to missing KeyMoment columns
- Pipeline dispatch from ingest is best-effort; manual trigger returns 503 on Celery failure
- LLMClient retries once with JSON nudge on malformed LLM output before failing
- Celery task pattern: @celery_app.task(bind=True, max_retries=3) with sync SQLAlchemy session per task
- LLM client pattern: primary → fallback → fail, with Pydantic response parsing
- Non-blocking side-effect pattern: max_retries=0, catch-all exception handler, pipeline continues
- Prompt template pattern: plain text files in prompts/ dir, XML-style content fencing, loaded at runtime
- Pipeline test pattern: patch module-level _engine/_SessionLocal globals to redirect stages to test DB
+ + M003 + M003: Domain + DNS + Per-Stage LLM Model Routing + 2/2 + + +
++ + S01 + Domain Setup — DNS, Reverse Proxy, SSL + medium + + critical + +
+- [object Object]
- env_file uses required: false so docker compose config validates on fresh clones
- POSTGRES_PASSWORD uses :-changeme default instead of :? to avoid config failures
- PostgreSQL exposed on host port 5433 to avoid conflicts with other projects
- SQLAlchemy relationship import aliased to sa_relationship to avoid column name clash
- Separate PostgreSQL enum type names to avoid collisions (key_moment_content_type vs content_type)
- Health check at /health performs real DB SELECT 1; lightweight /api/v1/health also available
- Whisper import deferred so --help works without openai-whisper installed
- Sample transcript uses realistic music production content for downstream pipeline testing
- Docker Compose service naming: chrysopedia-{role} (chrysopedia-db, chrysopedia-api, etc.)
- Backend router pattern: backend/routers/{domain}.py with prefix-per-router mounted under /api/v1
- SQLAlchemy async pattern: asyncpg engine + async_sessionmaker + get_session dependency
- Pydantic v2 schema pattern: Base/Create/Read variants per entity with model_config from_attributes=True
- Config via pydantic-settings BaseSettings loading from .env with sensible defaults
- Alembic async migration pattern with run_async_migrations() wrapper
- UUID primary keys with gen_random_uuid() server default for all entities
+ + S02 + Per-Stage LLM Model Routing + Think-Tag Stripping + low + + + +1 slack +
+- Used NullPool for test engine to avoid asyncpg connection contention in pytest-asyncio
- Fixed _now() helper to return naive UTC datetimes for asyncpg TIMESTAMP WITHOUT TIME ZONE compatibility
- Used slugify helper inline in ingest.py rather than a shared utils module
- Set file_path to {creator_folder}/{source_file} for new SourceVideo records
- pytest-asyncio integration test pattern: function-scoped NullPool engine + ASGI transport client with dependency overrides
- Multipart JSON file upload pattern for FastAPI endpoints
- Creator auto-detection from folder_name with find-or-create and slugify
+ + M004 + M004: UI Polish, Bug Fixes, Technique Page Redesign, and Article Versioning + 4/4 + + +
++ + S01 + Fix API Bugs — Review Detail 422 + Creators Page + low + + critical + +
+- [object Object]
- env_file uses required: false so docker compose config validates on fresh clones
- POSTGRES_PASSWORD uses :-changeme default instead of :? to avoid config failures
- PostgreSQL exposed on host port 5433 to avoid conflicts with other projects
- SQLAlchemy relationship import aliased to sa_relationship to avoid column name clash
- Separate PostgreSQL enum type names to avoid collisions (key_moment_content_type vs content_type)
- Health check at /health performs real DB SELECT 1; lightweight /api/v1/health also available
- Whisper import deferred so --help works without openai-whisper installed
- Sample transcript uses realistic music production content for downstream pipeline testing
- Docker Compose service naming: chrysopedia-{role} (chrysopedia-db, chrysopedia-api, etc.)
- Backend router pattern: backend/routers/{domain}.py with prefix-per-router mounted under /api/v1
- SQLAlchemy async pattern: asyncpg engine + async_sessionmaker + get_session dependency
- Pydantic v2 schema pattern: Base/Create/Read variants per entity with model_config from_attributes=True
- Config via pydantic-settings BaseSettings loading from .env with sensible defaults
- Alembic async migration pattern with run_async_migrations() wrapper
- UUID primary keys with gen_random_uuid() server default for all entities
+ + S02 + Dark Theme + Cyan Accents + Mobile Responsive Fix + medium + + + +1 slack +
+- Used NullPool for test engine to avoid asyncpg connection contention in pytest-asyncio
- Fixed _now() helper to return naive UTC datetimes for asyncpg TIMESTAMP WITHOUT TIME ZONE compatibility
- Used slugify helper inline in ingest.py rather than a shared utils module
- Set file_path to {creator_folder}/{source_file} for new SourceVideo records
- pytest-asyncio integration test pattern: function-scoped NullPool engine + ASGI transport client with dependency overrides
- Multipart JSON file upload pattern for FastAPI endpoints
- Creator auto-detection from folder_name with find-or-create and slugify
+ + S03 + Technique Page Redesign + Video Source on Moments + medium + S01 + critical + +
+- Sync OpenAI/SQLAlchemy/Qdrant throughout Celery tasks — no async in worker context (D004)
- Embedding/Qdrant stage is non-blocking side-effect — failures don't break pipeline (D005)
- Stage 4 classification stored in Redis (24h TTL) due to missing KeyMoment columns
- Pipeline dispatch from ingest is best-effort; manual trigger returns 503 on Celery failure
- LLMClient retries once with JSON nudge on malformed LLM output before failing
- Celery task pattern: @celery_app.task(bind=True, max_retries=3) with sync SQLAlchemy session per task
- LLM client pattern: primary → fallback → fail, with Pydantic response parsing
- Non-blocking side-effect pattern: max_retries=0, catch-all exception handler, pipeline continues
- Prompt template pattern: plain text files in prompts/ dir, XML-style content fencing, loaded at runtime
- Pipeline test pattern: patch module-level _engine/_SessionLocal globals to redirect stages to test DB
+ + S04 + Article Versioning + Pipeline Tuning Metadata + high + S03 + + +
+- Redis mode toggle uses per-request get_redis() with aclose() — no connection pool (D007)
- API client uses bare fetch() with shared request() helper — no external HTTP library
- MomentDetail fetches full queue to find moment by ID since no single-moment GET endpoint exists
- Split creates new moment with '(split)' title suffix; merge combines summaries with double-newline separator
- Split dialog validates timestamp client-side before API call
- React + Vite + TypeScript frontend pattern: strict TS config, Vite dev proxy to backend, typed API client with fetch()-based request helper
- Reusable component extraction (StatusBadge, ModeToggle) for consistent styling across admin pages
- Review router pattern: async SQLAlchemy with joined loads for cross-table data (moment + video + creator)
- Redis as runtime config store with config.py fallback for settings that need to be mutable at runtime
+ + M005 + M005: Pipeline Dashboard, Technique Page Redesign, Key Moment Cards + 3/3 + + +
++ + S01 + Pipeline Admin Dashboard + high + + critical + +
+- [object Object]
- env_file uses required: false so docker compose config validates on fresh clones
- POSTGRES_PASSWORD uses :-changeme default instead of :? to avoid config failures
- PostgreSQL exposed on host port 5433 to avoid conflicts with other projects
- SQLAlchemy relationship import aliased to sa_relationship to avoid column name clash
- Separate PostgreSQL enum type names to avoid collisions (key_moment_content_type vs content_type)
- Health check at /health performs real DB SELECT 1; lightweight /api/v1/health also available
- Whisper import deferred so --help works without openai-whisper installed
- Sample transcript uses realistic music production content for downstream pipeline testing
- Docker Compose service naming: chrysopedia-{role} (chrysopedia-db, chrysopedia-api, etc.)
- Backend router pattern: backend/routers/{domain}.py with prefix-per-router mounted under /api/v1
- SQLAlchemy async pattern: asyncpg engine + async_sessionmaker + get_session dependency
- Pydantic v2 schema pattern: Base/Create/Read variants per entity with model_config from_attributes=True
- Config via pydantic-settings BaseSettings loading from .env with sensible defaults
- Alembic async migration pattern with run_async_migrations() wrapper
- UUID primary keys with gen_random_uuid() server default for all entities
+ + S02 + Technique Page 2-Column Layout + medium + + + +1 slack +
+- Used NullPool for test engine to avoid asyncpg connection contention in pytest-asyncio
- Fixed _now() helper to return naive UTC datetimes for asyncpg TIMESTAMP WITHOUT TIME ZONE compatibility
- Used slugify helper inline in ingest.py rather than a shared utils module
- Set file_path to {creator_folder}/{source_file} for new SourceVideo records
- pytest-asyncio integration test pattern: function-scoped NullPool engine + ASGI transport client with dependency overrides
- Multipart JSON file upload pattern for FastAPI endpoints
- Creator auto-detection from folder_name with find-or-create and slugify
+ + S03 + Key Moment Card Redesign + low + S02 + critical + +
+- Sync OpenAI/SQLAlchemy/Qdrant throughout Celery tasks — no async in worker context (D004)
- Embedding/Qdrant stage is non-blocking side-effect — failures don't break pipeline (D005)
- Stage 4 classification stored in Redis (24h TTL) due to missing KeyMoment columns
- Pipeline dispatch from ingest is best-effort; manual trigger returns 503 on Celery failure
- LLMClient retries once with JSON nudge on malformed LLM output before failing
- Celery task pattern: @celery_app.task(bind=True, max_retries=3) with sync SQLAlchemy session per task
- LLM client pattern: primary → fallback → fail, with Pydantic response parsing
- Non-blocking side-effect pattern: max_retries=0, catch-all exception handler, pipeline continues
- Prompt template pattern: plain text files in prompts/ dir, XML-style content fencing, loaded at runtime
- Pipeline test pattern: patch module-level _engine/_SessionLocal globals to redirect stages to test DB
+ + M006 + M006: Admin Nav, Pipeline Log Views, Commit SHA, Tag Polish, Topics Redesign, Footer + 6/6 + + +
++ + S01 + Admin Navigation Dropdown + Header Cleanup + low + + critical + +
+- [object Object]
- env_file uses required: false so docker compose config validates on fresh clones
- POSTGRES_PASSWORD uses :-changeme default instead of :? to avoid config failures
- PostgreSQL exposed on host port 5433 to avoid conflicts with other projects
- SQLAlchemy relationship import aliased to sa_relationship to avoid column name clash
- Separate PostgreSQL enum type names to avoid collisions (key_moment_content_type vs content_type)
- Health check at /health performs real DB SELECT 1; lightweight /api/v1/health also available
- Whisper import deferred so --help works without openai-whisper installed
- Sample transcript uses realistic music production content for downstream pipeline testing
- Docker Compose service naming: chrysopedia-{role} (chrysopedia-db, chrysopedia-api, etc.)
- Backend router pattern: backend/routers/{domain}.py with prefix-per-router mounted under /api/v1
- SQLAlchemy async pattern: asyncpg engine + async_sessionmaker + get_session dependency
- Pydantic v2 schema pattern: Base/Create/Read variants per entity with model_config from_attributes=True
- Config via pydantic-settings BaseSettings loading from .env with sensible defaults
- Alembic async migration pattern with run_async_migrations() wrapper
- UUID primary keys with gen_random_uuid() server default for all entities
+ + S02 + Pipeline Page: Head/Tail Log View + Token Count + low + + + +1 slack +
+- Used NullPool for test engine to avoid asyncpg connection contention in pytest-asyncio
- Fixed _now() helper to return naive UTC datetimes for asyncpg TIMESTAMP WITHOUT TIME ZONE compatibility
- Used slugify helper inline in ingest.py rather than a shared utils module
- Set file_path to {creator_folder}/{source_file} for new SourceVideo records
- pytest-asyncio integration test pattern: function-scoped NullPool engine + ASGI transport client with dependency overrides
- Multipart JSON file upload pattern for FastAPI endpoints
- Creator auto-detection from folder_name with find-or-create and slugify
+ + S03 + Git Commit SHA in Pipeline Version Metadata + low + + critical + +
+- Sync OpenAI/SQLAlchemy/Qdrant throughout Celery tasks — no async in worker context (D004)
- Embedding/Qdrant stage is non-blocking side-effect — failures don't break pipeline (D005)
- Stage 4 classification stored in Redis (24h TTL) due to missing KeyMoment columns
- Pipeline dispatch from ingest is best-effort; manual trigger returns 503 on Celery failure
- LLMClient retries once with JSON nudge on malformed LLM output before failing
- Celery task pattern: @celery_app.task(bind=True, max_retries=3) with sync SQLAlchemy session per task
- LLM client pattern: primary → fallback → fail, with Pydantic response parsing
- Non-blocking side-effect pattern: max_retries=0, catch-all exception handler, pipeline continues
- Prompt template pattern: plain text files in prompts/ dir, XML-style content fencing, loaded at runtime
- Pipeline test pattern: patch module-level _engine/_SessionLocal globals to redirect stages to test DB
+ + S04 + Technique Page: Sidebar Reorder, Creator Emphasis, Tag Polish + medium + + + +
+- Redis mode toggle uses per-request get_redis() with aclose() — no connection pool (D007)
- API client uses bare fetch() with shared request() helper — no external HTTP library
- MomentDetail fetches full queue to find moment by ID since no single-moment GET endpoint exists
- Split creates new moment with '(split)' title suffix; merge combines summaries with double-newline separator
- Split dialog validates timestamp client-side before API call
- React + Vite + TypeScript frontend pattern: strict TS config, Vite dev proxy to backend, typed API client with fetch()-based request helper
- Reusable component extraction (StatusBadge, ModeToggle) for consistent styling across admin pages
- Review router pattern: async SQLAlchemy with joined loads for cross-table data (moment + video + creator)
- Redis as runtime config store with config.py fallback for settings that need to be mutable at runtime
+ + S05 + Topics Page Redesign + Music Theory Category + high + + + +
+- [object Object]
- [object Object]
- [object Object]
- [object Object]
- [object Object]
- [object Object]
- 300ms asyncio.wait_for timeout on both embedding and Qdrant calls
- Topics endpoint loads canonical_tags.yaml at request time and counts tag matches from DB
- Mocked SearchService at router dependency level for integration tests
- Duplicated request<T> helper in public-client.ts to avoid coupling public and admin API clients
- Async service class pattern: create separate async client wrappers for FastAPI when sync clients exist for Celery
- Graceful degradation pattern: embedding/Qdrant timeout → keyword ILIKE fallback with fallback_used flag
- Typed public API client: separate from admin client, each with own request<T> helper
- URL param-driven search: query state in URL params for shareable/bookmarkable search results
- Router-level service mocking: patch SearchService at dependency level for clean integration tests
+ + S06 + App Footer with Version Info + low + + + +
+- Vite define with JSON.stringify for build-time constant injection
- execSync for local git SHA with VITE_GIT_COMMIT env var fallback for Docker builds
- ARG+ENV pattern in Dockerfile.web matching existing API service pattern
- Read package.json via fs.readFileSync to avoid TS module resolution issues in Vite config
- Vite build-time constants via define + JSON.stringify + TypeScript declare const for type safety
- Docker ARG → ENV passthrough for build-time environment variables consumed by Node/Vite
+ + M007 + M007: Pipeline Transparency, Auto-Ingest, Admin UX Polish, and Mobile Fixes + 6/6 + + +
++ + S01 + Pipeline Debug Mode — Full LLM I/O Capture and Token Accounting + medium + + critical + +
+- [object Object]
- env_file uses required: false so docker compose config validates on fresh clones
- POSTGRES_PASSWORD uses :-changeme default instead of :? to avoid config failures
- PostgreSQL exposed on host port 5433 to avoid conflicts with other projects
- SQLAlchemy relationship import aliased to sa_relationship to avoid column name clash
- Separate PostgreSQL enum type names to avoid collisions (key_moment_content_type vs content_type)
- Health check at /health performs real DB SELECT 1; lightweight /api/v1/health also available
- Whisper import deferred so --help works without openai-whisper installed
- Sample transcript uses realistic music production content for downstream pipeline testing
- Docker Compose service naming: chrysopedia-{role} (chrysopedia-db, chrysopedia-api, etc.)
- Backend router pattern: backend/routers/{domain}.py with prefix-per-router mounted under /api/v1
- SQLAlchemy async pattern: asyncpg engine + async_sessionmaker + get_session dependency
- Pydantic v2 schema pattern: Base/Create/Read variants per entity with model_config from_attributes=True
- Config via pydantic-settings BaseSettings loading from .env with sensible defaults
- Alembic async migration pattern with run_async_migrations() wrapper
- UUID primary keys with gen_random_uuid() server default for all entities
+ + S02 + Debug Payload Viewer — Inline View, Copy, and Export in Admin UI + low + S01 + + +1 slack +
+- Used NullPool for test engine to avoid asyncpg connection contention in pytest-asyncio
- Fixed _now() helper to return naive UTC datetimes for asyncpg TIMESTAMP WITHOUT TIME ZONE compatibility
- Used slugify helper inline in ingest.py rather than a shared utils module
- Set file_path to {creator_folder}/{source_file} for new SourceVideo records
- pytest-asyncio integration test pattern: function-scoped NullPool engine + ASGI transport client with dependency overrides
- Multipart JSON file upload pattern for FastAPI endpoints
- Creator auto-detection from folder_name with find-or-create and slugify
+ + S03 + Transcript Folder Watcher — Auto-Ingest Service + medium + + critical + +
+- Sync OpenAI/SQLAlchemy/Qdrant throughout Celery tasks — no async in worker context (D004)
- Embedding/Qdrant stage is non-blocking side-effect — failures don't break pipeline (D005)
- Stage 4 classification stored in Redis (24h TTL) due to missing KeyMoment columns
- Pipeline dispatch from ingest is best-effort; manual trigger returns 503 on Celery failure
- LLMClient retries once with JSON nudge on malformed LLM output before failing
- Celery task pattern: @celery_app.task(bind=True, max_retries=3) with sync SQLAlchemy session per task
- LLM client pattern: primary → fallback → fail, with Pydantic response parsing
- Non-blocking side-effect pattern: max_retries=0, catch-all exception handler, pipeline continues
- Prompt template pattern: plain text files in prompts/ dir, XML-style content fencing, loaded at runtime
- Pipeline test pattern: patch module-level _engine/_SessionLocal globals to redirect stages to test DB
+ + S04 + Admin UX Audit — Prune, Streamline, and Polish + low + S02 + + +
+- Redis mode toggle uses per-request get_redis() with aclose() — no connection pool (D007)
- API client uses bare fetch() with shared request() helper — no external HTTP library
- MomentDetail fetches full queue to find moment by ID since no single-moment GET endpoint exists
- Split creates new moment with '(split)' title suffix; merge combines summaries with double-newline separator
- Split dialog validates timestamp client-side before API call
- React + Vite + TypeScript frontend pattern: strict TS config, Vite dev proxy to backend, typed API client with fetch()-based request helper
- Reusable component extraction (StatusBadge, ModeToggle) for consistent styling across admin pages
- Review router pattern: async SQLAlchemy with joined loads for cross-table data (moment + video + creator)
- Redis as runtime config store with config.py fallback for settings that need to be mutable at runtime
+ + S05 + Key Moment Card Text Overflow Fix + low + + + +
+- [object Object]
- [object Object]
- [object Object]
- [object Object]
- [object Object]
- [object Object]
- 300ms asyncio.wait_for timeout on both embedding and Qdrant calls
- Topics endpoint loads canonical_tags.yaml at request time and counts tag matches from DB
- Mocked SearchService at router dependency level for integration tests
- Duplicated request<T> helper in public-client.ts to avoid coupling public and admin API clients
- Async service class pattern: create separate async client wrappers for FastAPI when sync clients exist for Celery
- Graceful degradation pattern: embedding/Qdrant timeout → keyword ILIKE fallback with fallback_used flag
- Typed public API client: separate from admin client, each with own request<T> helper
- URL param-driven search: query state in URL params for shareable/bookmarkable search results
- Router-level service mocking: patch SearchService at dependency level for clean integration tests
+ + S06 + Mobile Viewport Overflow Fix — Technique Pages and Global Content + low + S05 + + +
+- Vite define with JSON.stringify for build-time constant injection
- execSync for local git SHA with VITE_GIT_COMMIT env var fallback for Docker builds
- ARG+ENV pattern in Dockerfile.web matching existing API service pattern
- Read package.json via fs.readFileSync to avoid TS module resolution issues in Vite config
- Vite build-time constants via define + JSON.stringify + TypeScript declare const for type safety
- Docker ARG → ENV passthrough for build-time environment variables consumed by Node/Vite
+ + M008 + M008: Credibility Debt Cleanup — Broken Links, Test Data, Jargon, Empty Metrics + 3/3 + + +
++ + S01 + Fix Key Moment Search Links + high + + critical + +
+- [object Object]
- env_file uses required: false so docker compose config validates on fresh clones
- POSTGRES_PASSWORD uses :-changeme default instead of :? to avoid config failures
- PostgreSQL exposed on host port 5433 to avoid conflicts with other projects
- SQLAlchemy relationship import aliased to sa_relationship to avoid column name clash
- Separate PostgreSQL enum type names to avoid collisions (key_moment_content_type vs content_type)
- Health check at /health performs real DB SELECT 1; lightweight /api/v1/health also available
- Whisper import deferred so --help works without openai-whisper installed
- Sample transcript uses realistic music production content for downstream pipeline testing
- Docker Compose service naming: chrysopedia-{role} (chrysopedia-db, chrysopedia-api, etc.)
- Backend router pattern: backend/routers/{domain}.py with prefix-per-router mounted under /api/v1
- SQLAlchemy async pattern: asyncpg engine + async_sessionmaker + get_session dependency
- Pydantic v2 schema pattern: Base/Create/Read variants per entity with model_config from_attributes=True
- Config via pydantic-settings BaseSettings loading from .env with sensible defaults
- Alembic async migration pattern with run_async_migrations() wrapper
- UUID primary keys with gen_random_uuid() server default for all entities
+ + S02 + Trust & Credibility Cleanup + low + + + +1 slack +
+- Used NullPool for test engine to avoid asyncpg connection contention in pytest-asyncio
- Fixed _now() helper to return naive UTC datetimes for asyncpg TIMESTAMP WITHOUT TIME ZONE compatibility
- Used slugify helper inline in ingest.py rather than a shared utils module
- Set file_path to {creator_folder}/{source_file} for new SourceVideo records
- pytest-asyncio integration test pattern: function-scoped NullPool engine + ASGI transport client with dependency overrides
- Multipart JSON file upload pattern for FastAPI endpoints
- Creator auto-detection from folder_name with find-or-create and slugify
+ + S03 + Homepage Cards & Creator Metric Polish + low + + critical + +
+- Sync OpenAI/SQLAlchemy/Qdrant throughout Celery tasks — no async in worker context (D004)
- Embedding/Qdrant stage is non-blocking side-effect — failures don't break pipeline (D005)
- Stage 4 classification stored in Redis (24h TTL) due to missing KeyMoment columns
- Pipeline dispatch from ingest is best-effort; manual trigger returns 503 on Celery failure
- LLMClient retries once with JSON nudge on malformed LLM output before failing
- Celery task pattern: @celery_app.task(bind=True, max_retries=3) with sync SQLAlchemy session per task
- LLM client pattern: primary → fallback → fail, with Pydantic response parsing
- Non-blocking side-effect pattern: max_retries=0, catch-all exception handler, pipeline continues
- Prompt template pattern: plain text files in prompts/ dir, XML-style content fencing, loaded at runtime
- Pipeline test pattern: patch module-level _engine/_SessionLocal globals to redirect stages to test DB
+ + M009 + Homepage & First Impression + 0/3 + + +
++ + S01 + Homepage Hero & Value Proposition + medium + + critical + +
+- [object Object]
- env_file uses required: false so docker compose config validates on fresh clones
- POSTGRES_PASSWORD uses :-changeme default instead of :? to avoid config failures
- PostgreSQL exposed on host port 5433 to avoid conflicts with other projects
- SQLAlchemy relationship import aliased to sa_relationship to avoid column name clash
- Separate PostgreSQL enum type names to avoid collisions (key_moment_content_type vs content_type)
- Health check at /health performs real DB SELECT 1; lightweight /api/v1/health also available
- Whisper import deferred so --help works without openai-whisper installed
- Sample transcript uses realistic music production content for downstream pipeline testing
- Docker Compose service naming: chrysopedia-{role} (chrysopedia-db, chrysopedia-api, etc.)
- Backend router pattern: backend/routers/{domain}.py with prefix-per-router mounted under /api/v1
- SQLAlchemy async pattern: asyncpg engine + async_sessionmaker + get_session dependency
- Pydantic v2 schema pattern: Base/Create/Read variants per entity with model_config from_attributes=True
- Config via pydantic-settings BaseSettings loading from .env with sensible defaults
- Alembic async migration pattern with run_async_migrations() wrapper
- UUID primary keys with gen_random_uuid() server default for all entities
+ + S02 + About Page + low + + + +1 slack +
+- Used NullPool for test engine to avoid asyncpg connection contention in pytest-asyncio
- Fixed _now() helper to return naive UTC datetimes for asyncpg TIMESTAMP WITHOUT TIME ZONE compatibility
- Used slugify helper inline in ingest.py rather than a shared utils module
- Set file_path to {creator_folder}/{source_file} for new SourceVideo records
- pytest-asyncio integration test pattern: function-scoped NullPool engine + ASGI transport client with dependency overrides
- Multipart JSON file upload pattern for FastAPI endpoints
- Creator auto-detection from folder_name with find-or-create and slugify
+ + S03 + Featured Content & Content Teasers + low + S01 + critical + +
+- Sync OpenAI/SQLAlchemy/Qdrant throughout Celery tasks — no async in worker context (D004)
- Embedding/Qdrant stage is non-blocking side-effect — failures don't break pipeline (D005)
- Stage 4 classification stored in Redis (24h TTL) due to missing KeyMoment columns
- Pipeline dispatch from ingest is best-effort; manual trigger returns 503 on Celery failure
- LLMClient retries once with JSON nudge on malformed LLM output before failing
- Celery task pattern: @celery_app.task(bind=True, max_retries=3) with sync SQLAlchemy session per task
- LLM client pattern: primary → fallback → fail, with Pydantic response parsing
- Non-blocking side-effect pattern: max_retries=0, catch-all exception handler, pipeline continues
- Prompt template pattern: plain text files in prompts/ dir, XML-style content fencing, loaded at runtime
- Pipeline test pattern: patch module-level _engine/_SessionLocal globals to redirect stages to test DB
+ + M010 + Discovery, Navigation & Visual Identity + 0/4 + critical path + +
++ + S01 + Dedicated Sub-Topic Pages + high + + critical + +
+- [object Object]
- env_file uses required: false so docker compose config validates on fresh clones
- POSTGRES_PASSWORD uses :-changeme default instead of :? to avoid config failures
- PostgreSQL exposed on host port 5433 to avoid conflicts with other projects
- SQLAlchemy relationship import aliased to sa_relationship to avoid column name clash
- Separate PostgreSQL enum type names to avoid collisions (key_moment_content_type vs content_type)
- Health check at /health performs real DB SELECT 1; lightweight /api/v1/health also available
- Whisper import deferred so --help works without openai-whisper installed
- Sample transcript uses realistic music production content for downstream pipeline testing
- Docker Compose service naming: chrysopedia-{role} (chrysopedia-db, chrysopedia-api, etc.)
- Backend router pattern: backend/routers/{domain}.py with prefix-per-router mounted under /api/v1
- SQLAlchemy async pattern: asyncpg engine + async_sessionmaker + get_session dependency
- Pydantic v2 schema pattern: Base/Create/Read variants per entity with model_config from_attributes=True
- Config via pydantic-settings BaseSettings loading from .env with sensible defaults
- Alembic async migration pattern with run_async_migrations() wrapper
- UUID primary keys with gen_random_uuid() server default for all entities
+ + S02 + Related Techniques Cross-Linking + medium + + + +1 slack +
+- Used NullPool for test engine to avoid asyncpg connection contention in pytest-asyncio
- Fixed _now() helper to return naive UTC datetimes for asyncpg TIMESTAMP WITHOUT TIME ZONE compatibility
- Used slugify helper inline in ingest.py rather than a shared utils module
- Set file_path to {creator_folder}/{source_file} for new SourceVideo records
- pytest-asyncio integration test pattern: function-scoped NullPool engine + ASGI transport client with dependency overrides
- Multipart JSON file upload pattern for FastAPI endpoints
- Creator auto-detection from folder_name with find-or-create and slugify
+ + S03 + Topic Color Coding & Visual Polish + medium + S01 + critical + +
+- Sync OpenAI/SQLAlchemy/Qdrant throughout Celery tasks — no async in worker context (D004)
- Embedding/Qdrant stage is non-blocking side-effect — failures don't break pipeline (D005)
- Stage 4 classification stored in Redis (24h TTL) due to missing KeyMoment columns
- Pipeline dispatch from ingest is best-effort; manual trigger returns 503 on Celery failure
- LLMClient retries once with JSON nudge on malformed LLM output before failing
- Celery task pattern: @celery_app.task(bind=True, max_retries=3) with sync SQLAlchemy session per task
- LLM client pattern: primary → fallback → fail, with Pydantic response parsing
- Non-blocking side-effect pattern: max_retries=0, catch-all exception handler, pipeline continues
- Prompt template pattern: plain text files in prompts/ dir, XML-style content fencing, loaded at runtime
- Pipeline test pattern: patch module-level _engine/_SessionLocal globals to redirect stages to test DB
+ + S04 + Search Autocomplete & Suggestions + medium + + + +
+- Redis mode toggle uses per-request get_redis() with aclose() — no connection pool (D007)
- API client uses bare fetch() with shared request() helper — no external HTTP library
- MomentDetail fetches full queue to find moment by ID since no single-moment GET endpoint exists
- Split creates new moment with '(split)' title suffix; merge combines summaries with double-newline separator
- Split dialog validates timestamp client-side before API call
- React + Vite + TypeScript frontend pattern: strict TS config, Vite dev proxy to backend, typed API client with fetch()-based request helper
- Reusable component extraction (StatusBadge, ModeToggle) for consistent styling across admin pages
- Review router pattern: async SQLAlchemy with joined loads for cross-table data (moment + video + creator)
- Redis as runtime config store with config.py fallback for settings that need to be mutable at runtime
Timeline
+ +| # | Type | ID | Model | +Started | Duration | Cost | +Tokens | Tools | Tier | Routed | Trunc | CHF | +
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | +execute-task | +M001/S01/T01 | +opus-4-6 | +Mar 29, 2026, 09:39 PM | +3m 7s | +$1.11 | +1.50M | +32 | ++ | + | + | + |
| 2 | +execute-task | +M001/S01/T02 | +opus-4-6 | +Mar 29, 2026, 09:42 PM | +5m 39s | +$1.68 | +2.29M | +39 | ++ | + | + | + |
| 3 | +execute-task | +M001/S01/T03 | +opus-4-6 | +Mar 29, 2026, 09:48 PM | +6m 20s | +$2.71 | +4.17M | +67 | ++ | + | + | + |
| 4 | +execute-task | +M001/S01/T04 | +opus-4-6 | +Mar 29, 2026, 09:54 PM | +2m 44s | +$0.784 | +968.2k | +20 | ++ | + | + | + |
| 5 | +execute-task | +M001/S01/T05 | +opus-4-6 | +Mar 29, 2026, 09:57 PM | +2m 58s | +$0.898 | +953.1k | +26 | ++ | + | + | + |
| 6 | +complete-slice | +M001/S01 | +opus-4-6 | +Mar 29, 2026, 10:00 PM | +2m 6s | +$0.501 | +447.5k | +15 | ++ | + | + | + |
| 7 | +research-slice | +M001/S02 | +opus-4-6 | +Mar 29, 2026, 10:02 PM | +1m 48s | +$0.619 | +712.3k | +23 | ++ | + | + | + |
| 8 | +plan-slice | +M001/S02 | +opus-4-6 | +Mar 29, 2026, 10:04 PM | +2m 6s | +$0.623 | +589.9k | +15 | ++ | + | + | + |
| 9 | +execute-task | +M001/S02/T01 | +opus-4-6 | +Mar 29, 2026, 10:06 PM | +3m 1s | +$1.16 | +1.69M | +30 | ++ | + | + | + |
| 10 | +execute-task | +M001/S02/T02 | +opus-4-6 | +Mar 29, 2026, 10:09 PM | +6m 29s | +$2.90 | +4.01M | +61 | ++ | + | + | + |
| 11 | +complete-slice | +M001/S02 | +opus-4-6 | +Mar 29, 2026, 10:16 PM | +3m 41s | +$1.35 | +1.93M | +29 | ++ | + | + | + |
| 12 | +research-slice | +M001/S03 | +opus-4-6 | +Mar 29, 2026, 10:19 PM | +3m 3s | +$1.29 | +1.47M | +34 | ++ | + | + | + |
| 13 | +plan-slice | +M001/S03 | +opus-4-6 | +Mar 29, 2026, 10:23 PM | +4m 12s | +$1.11 | +1.03M | +20 | ++ | + | + | + |
| 14 | +execute-task | +M001/S03/T01 | +opus-4-6 | +Mar 29, 2026, 10:27 PM | +3m 16s | +$1.12 | +1.58M | +29 | ++ | + | + | + |
| 15 | +execute-task | +M001/S03/T02 | +opus-4-6 | +Mar 29, 2026, 10:30 PM | +5m 34s | +$1.74 | +2.26M | +41 | ++ | + | + | + |
| 16 | +execute-task | +M001/S03/T03 | +opus-4-6 | +Mar 29, 2026, 10:36 PM | +2m 57s | +$1.01 | +1.18M | +21 | ++ | + | + | + |
| 17 | +execute-task | +M001/S03/T04 | +opus-4-6 | +Mar 29, 2026, 10:39 PM | +1m 57s | +$0.796 | +1.08M | +26 | ++ | + | + | + |
| 18 | +execute-task | +M001/S03/T05 | +opus-4-6 | +Mar 29, 2026, 10:41 PM | +10m 23s | +$1.90 | +2.34M | +38 | ++ | + | + | + |
| 19 | +complete-slice | +M001/S03 | +opus-4-6 | +Mar 29, 2026, 10:51 PM | +8m 3s | +$1.32 | +1.68M | +37 | ++ | + | + | + |
| 20 | +research-slice | +M001/S04 | +opus-4-6 | +Mar 29, 2026, 10:59 PM | +2m 38s | +$1.23 | +1.54M | +25 | ++ | + | + | + |
| 21 | +plan-slice | +M001/S04 | +opus-4-6 | +Mar 29, 2026, 11:02 PM | +3m 5s | +$0.835 | +816.3k | +22 | ++ | + | + | + |
| 22 | +execute-task | +M001/S04/T01 | +opus-4-6 | +Mar 29, 2026, 11:05 PM | +8m 27s | +$1.60 | +2.06M | +32 | ++ | + | + | + |
| 23 | +execute-task | +M001/S04/T02 | +opus-4-6 | +Mar 29, 2026, 11:13 PM | +8m 9s | +$1.64 | +2.29M | +45 | ++ | + | + | + |
| 24 | +execute-task | +M001/S04/T03 | +opus-4-6 | +Mar 29, 2026, 11:21 PM | +7m 6s | +$1.83 | +2.12M | +33 | ++ | + | + | + |
| 25 | +complete-slice | +M001/S04 | +opus-4-6 | +Mar 29, 2026, 11:29 PM | +10m 12s | +$1.56 | +2.39M | +28 | ++ | + | + | + |
| 26 | +research-slice | +M001/S05 | +opus-4-6 | +Mar 29, 2026, 11:39 PM | +4m 50s | +$1.75 | +2.35M | +40 | ++ | + | + | + |
| 27 | +plan-slice | +M001/S05 | +opus-4-6 | +Mar 29, 2026, 11:44 PM | +5m 12s | +$2.01 | +2.80M | +20 | ++ | + | + | + |
| 28 | +execute-task | +M001/S05/T01 | +opus-4-6 | +Mar 29, 2026, 11:49 PM | +6m 33s | +$1.86 | +2.58M | +38 | ++ | + | + | + |
| 29 | +execute-task | +M001/S05/T02 | +opus-4-6 | +Mar 29, 2026, 11:55 PM | +5m 39s | +$1.71 | +1.41M | +26 | ++ | + | + | + |
| 30 | +execute-task | +M001/S05/T03 | +opus-4-6 | +Mar 30, 2026, 12:01 AM | +7m 35s | +$2.05 | +2.61M | +38 | ++ | + | + | + |
| 31 | +execute-task | +M001/S05/T04 | +opus-4-6 | +Mar 30, 2026, 12:09 AM | +4m 1s | +$1.64 | +2.05M | +32 | ++ | + | + | + |
| 32 | +complete-slice | +M001/S05 | +opus-4-6 | +Mar 30, 2026, 12:13 AM | +7m 5s | +$1.69 | +2.47M | +30 | ++ | + | + | + |
| 33 | +validate-milestone | +M001 | +opus-4-6 | +Mar 30, 2026, 12:20 AM | +2m 48s | +$1.05 | +1.33M | +23 | ++ | + | + | + |
| 34 | +complete-milestone | +M001 | +opus-4-6 | +Mar 30, 2026, 12:23 AM | +6m 38s | +$3.00 | +4.37M | +72 | ++ | + | + | + |
| 35 | +research-slice | +M004/S02 | +opus-4-6 | +Mar 30, 2026, 06:27 AM | +2m 33s | +$1.29 | +1.94M | +30 | ++ | + | + | + |
| 36 | +plan-slice | +M004/S02 | +opus-4-6 | +Mar 30, 2026, 06:30 AM | +2m 29s | +$0.724 | +857.1k | +22 | ++ | + | + | + |
| 37 | +execute-task | +M004/S02/T01 | +opus-4-6 | +Mar 30, 2026, 06:32 AM | +4m 15s | +$1.33 | +1.20M | +12 | ++ | + | + | + |
| 38 | +execute-task | +M004/S02/T02 | +opus-4-6 | +Mar 30, 2026, 06:37 AM | +3m 49s | +$1.87 | +3.03M | +56 | ++ | + | + | + |
| 39 | +complete-slice | +M004/S02 | +opus-4-6 | +Mar 30, 2026, 06:40 AM | +1m 42s | +$0.596 | +771.7k | +9 | ++ | + | + | + |
| 40 | +research-slice | +M004/S03 | +opus-4-6 | +Mar 30, 2026, 06:42 AM | +3m 10s | +$1.85 | +2.78M | +44 | ++ | + | + | + |
| 41 | +plan-slice | +M004/S03 | +opus-4-6 | +Mar 30, 2026, 06:45 AM | +1m 41s | +$0.601 | +686.3k | +14 | ++ | + | + | + |
| 42 | +execute-task | +M004/S03/T01 | +opus-4-6 | +Mar 30, 2026, 06:47 AM | +2m 26s | +$1.26 | +2.01M | +29 | ++ | + | + | + |
| 43 | +execute-task | +M004/S03/T02 | +opus-4-6 | +Mar 30, 2026, 06:52 AM | +4m 26s | +$2.68 | +4.35M | +45 | ++ | + | + | + |
| 44 | +complete-slice | +M004/S03 | +opus-4-6 | +Mar 30, 2026, 06:57 AM | +1m 38s | +$0.524 | +634.2k | +15 | ++ | + | + | + |
| 45 | +research-slice | +M004/S04 | +opus-4-6 | +Mar 30, 2026, 06:58 AM | +2m 53s | +$1.51 | +2.17M | +47 | ++ | + | + | + |
| 46 | +plan-slice | +M004/S04 | +opus-4-6 | +Mar 30, 2026, 07:01 AM | +2m 59s | +$1.22 | +1.52M | +32 | ++ | + | + | + |
| 47 | +execute-task | +M004/S04/T01 | +opus-4-6 | +Mar 30, 2026, 07:04 AM | +2m 36s | +$1.20 | +1.77M | +28 | ++ | + | + | + |
| 48 | +execute-task | +M004/S04/T02 | +opus-4-6 | +Mar 30, 2026, 07:07 AM | +10m 25s | +$2.37 | +3.78M | +48 | ++ | + | + | + |
| 49 | +execute-task | +M004/S04/T03 | +opus-4-6 | +Mar 30, 2026, 07:17 AM | +1m 48s | +$0.931 | +1.23M | +21 | ++ | + | + | + |
| 50 | +complete-slice | +M004/S04 | +opus-4-6 | +Mar 30, 2026, 07:19 AM | +2m 4s | +$0.807 | +1.06M | +15 | ++ | + | + | + |
| 51 | +validate-milestone | +M004 | +opus-4-6 | +Mar 30, 2026, 07:21 AM | +1m 13s | +$0.335 | +347.7k | +5 | ++ | + | + | + |
| 52 | +complete-milestone | +M004 | +opus-4-6 | +Mar 30, 2026, 07:22 AM | +2m 25s | +$0.981 | +1.42M | +19 | ++ | + | + | + |
| 53 | +execute-task | +M005/S01/T01 | +opus-4-6 | +Mar 30, 2026, 08:24 AM | +3m 44s | +$1.88 | +2.93M | +43 | ++ | + | + | + |
| 54 | +execute-task | +M005/S01/T02 | +opus-4-6 | +Mar 30, 2026, 08:27 AM | +2m 21s | +$0.939 | +1.37M | +29 | ++ | + | + | + |
| 55 | +execute-task | +M005/S01/T03 | +opus-4-6 | +Mar 30, 2026, 08:30 AM | +4m 55s | +$2.27 | +3.27M | +47 | ++ | + | + | + |
| 56 | +complete-slice | +M005/S01 | +opus-4-6 | +Mar 30, 2026, 08:35 AM | +3m 12s | +$1.53 | +2.41M | +26 | ++ | + | + | + |
| 57 | +research-slice | +M005/S02 | +opus-4-6 | +Mar 30, 2026, 08:38 AM | +1m 40s | +$0.769 | +912.7k | +16 | ++ | + | + | + |
| 58 | +plan-slice | +M005/S02 | +opus-4-6 | +Mar 30, 2026, 08:40 AM | +1m 43s | +$0.720 | +956.5k | +16 | ++ | + | + | + |
| 59 | +execute-task | +M005/S02/T01 | +opus-4-6 | +Mar 30, 2026, 08:41 AM | +6m 7s | +$3.56 | +6.03M | +74 | ++ | + | + | + |
| 60 | +complete-slice | +M005/S02 | +opus-4-6 | +Mar 30, 2026, 08:47 AM | +1m 37s | +$0.600 | +828.3k | +15 | ++ | + | + | + |
| 61 | +research-slice | +M005/S03 | +opus-4-6 | +Mar 30, 2026, 08:49 AM | +1m 23s | +$0.641 | +964.9k | +17 | ++ | + | + | + |
| 62 | +plan-slice | +M005/S03 | +opus-4-6 | +Mar 30, 2026, 08:50 AM | +1m 1s | +$0.332 | +356.0k | +6 | ++ | + | + | + |
| 63 | +execute-task | +M005/S03/T01 | +opus-4-6 | +Mar 30, 2026, 08:51 AM | +3m 49s | +$1.82 | +3.05M | +40 | ++ | + | + | + |
| 64 | +complete-slice | +M005/S03 | +opus-4-6 | +Mar 30, 2026, 08:55 AM | +1m 44s | +$0.732 | +1.05M | +13 | ++ | + | + | + |
| 65 | +validate-milestone | +M005 | +opus-4-6 | +Mar 30, 2026, 08:57 AM | +1m 48s | +$0.607 | +833.4k | +11 | ++ | + | + | + |
| 66 | +complete-milestone | +M005 | +opus-4-6 | +Mar 30, 2026, 08:59 AM | +2m 31s | +$1.02 | +1.48M | +20 | ++ | + | + | + |
| 67 | +research-slice | +M006/S01 | +opus-4-6 | +Mar 30, 2026, 10:57 AM | +1m 23s | +$0.666 | +820.0k | +14 | ++ | + | + | + |
| 68 | +plan-slice | +M006/S01 | +opus-4-6 | +Mar 30, 2026, 10:59 AM | +1m 15s | +$0.423 | +499.3k | +10 | ++ | + | + | + |
| 69 | +execute-task | +M006/S01/T01 | +opus-4-6 | +Mar 30, 2026, 11:00 AM | +1m 56s | +$0.919 | +1.43M | +23 | ++ | + | + | + |
| 70 | +complete-slice | +M006/S01 | +opus-4-6 | +Mar 30, 2026, 11:02 AM | +54s | +$0.329 | +354.8k | +6 | ++ | + | + | + |
| 71 | +research-slice | +M006/S02 | +opus-4-6 | +Mar 30, 2026, 11:03 AM | +2m 28s | +$1.13 | +1.66M | +32 | ++ | + | + | + |
| 72 | +plan-slice | +M006/S02 | +opus-4-6 | +Mar 30, 2026, 11:05 AM | +2m 43s | +$1.33 | +2.01M | +32 | ++ | + | + | + |
| 73 | +execute-task | +M006/S02/T01 | +opus-4-6 | +Mar 30, 2026, 11:08 AM | +2m 8s | +$0.939 | +1.39M | +19 | ++ | + | + | + |
| 74 | +execute-task | +M006/S02/T02 | +opus-4-6 | +Mar 30, 2026, 11:10 AM | +4m 36s | +$2.14 | +3.44M | +45 | ++ | + | + | + |
| 75 | +complete-slice | +M006/S02 | +opus-4-6 | +Mar 30, 2026, 11:15 AM | +1m 28s | +$0.540 | +659.5k | +11 | ++ | + | + | + |
| 76 | +research-slice | +M006/S03 | +opus-4-6 | +Mar 30, 2026, 11:16 AM | +2m 53s | +$1.41 | +2.19M | +39 | ++ | + | + | + |
| 77 | +plan-slice | +M006/S03 | +opus-4-6 | +Mar 30, 2026, 11:19 AM | +1m 50s | +$0.765 | +1.03M | +20 | ++ | + | + | + |
| 78 | +execute-task | +M006/S03/T01 | +opus-4-6 | +Mar 30, 2026, 11:21 AM | +2m 58s | +$1.58 | +2.43M | +26 | ++ | + | + | + |
| 79 | +execute-task | +M006/S03/T02 | +opus-4-6 | +Mar 30, 2026, 11:24 AM | +1m 12s | +$0.476 | +710.6k | +11 | ++ | + | + | + |
| 80 | +complete-slice | +M006/S03 | +opus-4-6 | +Mar 30, 2026, 11:25 AM | +1m 6s | +$0.332 | +348.3k | +6 | ++ | + | + | + |
| 81 | +research-slice | +M006/S04 | +opus-4-6 | +Mar 30, 2026, 11:26 AM | +3m 31s | +$2.09 | +3.41M | +47 | ++ | + | + | + |
| 82 | +plan-slice | +M006/S04 | +opus-4-6 | +Mar 30, 2026, 11:30 AM | +1m 37s | +$0.611 | +743.3k | +15 | ++ | + | + | + |
| 83 | +execute-task | +M006/S04/T01 | +opus-4-6 | +Mar 30, 2026, 11:32 AM | +2m 12s | +$1.08 | +1.59M | +26 | ++ | + | + | + |
| 84 | +complete-slice | +M006/S04 | +opus-4-6 | +Mar 30, 2026, 11:34 AM | +1m 6s | +$0.363 | +415.1k | +10 | ++ | + | + | + |
| 85 | +research-slice | +M006/S05 | +opus-4-6 | +Mar 30, 2026, 11:35 AM | +2m 43s | +$1.54 | +2.25M | +39 | ++ | + | + | + |
| 86 | +plan-slice | +M006/S05 | +opus-4-6 | +Mar 30, 2026, 11:38 AM | +4m 10s | +$2.02 | +3.19M | +48 | ++ | + | + | + |
| 87 | +execute-task | +M006/S05/T01 | +opus-4-6 | +Mar 30, 2026, 11:42 AM | +2m 0s | +$0.902 | +1.44M | +21 | ++ | + | + | + |
| 88 | +execute-task | +M006/S05/T02 | +opus-4-6 | +Mar 30, 2026, 11:44 AM | +4m 32s | +$1.90 | +2.92M | +40 | ++ | + | + | + |
| 89 | +complete-slice | +M006/S05 | +opus-4-6 | +Mar 30, 2026, 11:48 AM | +4m 45s | +$2.10 | +3.39M | +49 | ++ | + | + | + |
| 90 | +research-slice | +M006/S06 | +opus-4-6 | +Mar 30, 2026, 11:53 AM | +2m 20s | +$1.31 | +1.94M | +40 | ++ | + | + | + |
| 91 | +plan-slice | +M006/S06 | +opus-4-6 | +Mar 30, 2026, 11:55 AM | +2m 57s | +$1.49 | +2.17M | +32 | ++ | + | + | + |
| 92 | +execute-task | +M006/S06/T01 | +opus-4-6 | +Mar 30, 2026, 11:58 AM | +2m 2s | +$1.14 | +1.65M | +24 | ++ | + | + | + |
| 93 | +execute-task | +M006/S06/T02 | +opus-4-6 | +Mar 30, 2026, 12:00 PM | +4m 29s | +$2.14 | +3.49M | +57 | ++ | + | + | + |
| 94 | +complete-slice | +M006/S06 | +opus-4-6 | +Mar 30, 2026, 12:05 PM | +2m 6s | +$0.770 | +1.04M | +21 | ++ | + | + | + |
| 95 | +validate-milestone | +M006 | +opus-4-6 | +Mar 30, 2026, 12:07 PM | +2m 48s | +$1.06 | +1.53M | +27 | ++ | + | + | + |
| 96 | +complete-milestone | +M006 | +opus-4-6 | +Mar 30, 2026, 12:10 PM | +2m 45s | +$1.11 | +1.63M | +30 | ++ | + | + | + |
| 97 | +research-slice | +M007/S01 | +opus-4-6 | +Mar 30, 2026, 06:11 PM | +3m 38s | +$1.23 | +1.77M | +37 | ++ | + | + | + |
| 98 | +plan-slice | +M007/S01 | +opus-4-6 | +Mar 30, 2026, 06:15 PM | +3m 13s | +$0.901 | +1.11M | +26 | ++ | + | + | + |
| 99 | +execute-task | +M007/S01/T01 | +opus-4-6 | +Mar 30, 2026, 06:18 PM | +4m 54s | +$1.65 | +2.46M | +39 | ++ | + | + | + |
| 100 | +execute-task | +M007/S01/T02 | +opus-4-6 | +Mar 30, 2026, 06:23 PM | +31m 10s | +$4.28 | +5.38M | +71 | ++ | + | + | + |
| 101 | +complete-slice | +M007/S01 | +opus-4-6 | +Mar 30, 2026, 06:54 PM | +2m 30s | +$0.791 | +999.6k | +17 | ++ | + | + | + |
| 102 | +research-slice | +M007/S02 | +opus-4-6 | +Mar 30, 2026, 06:57 PM | +1m 29s | +$0.680 | +952.0k | +17 | ++ | + | + | + |
| 103 | +plan-slice | +M007/S02 | +opus-4-6 | +Mar 30, 2026, 06:58 PM | +47s | +$0.369 | +433.6k | +9 | ++ | + | + | + |
| 104 | +execute-task | +M007/S02/T01 | +opus-4-6 | +Mar 30, 2026, 06:59 PM | +7m 45s | +$3.67 | +5.95M | +72 | ++ | + | + | + |
| 105 | +complete-slice | +M007/S02 | +opus-4-6 | +Mar 30, 2026, 07:07 PM | +2m 59s | +$1.06 | +1.55M | +24 | ++ | + | + | + |
| 106 | +research-slice | +M007/S03 | +opus-4-6 | +Mar 30, 2026, 07:10 PM | +2m 29s | +$0.944 | +1.34M | +28 | ++ | + | + | + |
| 107 | +plan-slice | +M007/S03 | +opus-4-6 | +Mar 30, 2026, 07:12 PM | +2m 9s | +$0.644 | +724.3k | +17 | ++ | + | + | + |
| 108 | +execute-task | +M007/S03/T01 | +opus-4-6 | +Mar 30, 2026, 07:15 PM | +2m 39s | +$1.04 | +1.51M | +24 | ++ | + | + | + |
| 109 | +execute-task | +M007/S03/T02 | +opus-4-6 | +Mar 30, 2026, 07:17 PM | +6m 51s | +$1.90 | +3.05M | +53 | ++ | + | + | + |
| 110 | +complete-slice | +M007/S03 | +opus-4-6 | +Mar 30, 2026, 07:24 PM | +1m 58s | +$0.735 | +1.02M | +14 | ++ | + | + | + |
| 111 | +research-slice | +M007/S04 | +opus-4-6 | +Mar 30, 2026, 07:26 PM | +2m 25s | +$1.19 | +1.60M | +28 | ++ | + | + | + |
| 112 | +plan-slice | +M007/S04 | +opus-4-6 | +Mar 30, 2026, 07:29 PM | +1m 57s | +$0.650 | +830.7k | +11 | ++ | + | + | + |
| 113 | +execute-task | +M007/S04/T01 | +opus-4-6 | +Mar 30, 2026, 07:31 PM | +3m 8s | +$1.55 | +2.46M | +34 | ++ | + | + | + |
| 114 | +execute-task | +M007/S04/T02 | +opus-4-6 | +Mar 30, 2026, 07:34 PM | +2m 36s | +$1.29 | +2.04M | +26 | ++ | + | + | + |
| 115 | +complete-slice | +M007/S04 | +opus-4-6 | +Mar 30, 2026, 07:36 PM | +1m 6s | +$0.356 | +416.3k | +6 | ++ | + | + | + |
| 116 | +research-slice | +M007/S05 | +opus-4-6 | +Mar 30, 2026, 07:37 PM | +1m 34s | +$0.669 | +1.02M | +17 | ++ | + | + | + |
| 117 | +plan-slice | +M007/S05 | +opus-4-6 | +Mar 30, 2026, 07:39 PM | +37s | +$0.224 | +209.7k | +4 | ++ | + | + | + |
| 118 | +execute-task | +M007/S05/T01 | +opus-4-6 | +Mar 30, 2026, 07:40 PM | +1m 33s | +$0.726 | +1.16M | +16 | ++ | + | + | + |
| 119 | +complete-slice | +M007/S05 | +opus-4-6 | +Mar 30, 2026, 07:41 PM | +1m 0s | +$0.381 | +484.9k | +12 | ++ | + | + | + |
| 120 | +research-slice | +M007/S06 | +opus-4-6 | +Mar 30, 2026, 07:42 PM | +2m 8s | +$1.03 | +1.50M | +27 | ++ | + | + | + |
| 121 | +plan-slice | +M007/S06 | +opus-4-6 | +Mar 30, 2026, 07:44 PM | +1m 11s | +$0.493 | +647.7k | +9 | ++ | + | + | + |
| 122 | +execute-task | +M007/S06/T01 | +opus-4-6 | +Mar 30, 2026, 07:46 PM | +2m 26s | +$0.715 | +1.11M | +20 | ++ | + | + | + |
| 123 | +complete-slice | +M007/S06 | +opus-4-6 | +Mar 30, 2026, 07:48 PM | +50s | +$0.293 | +344.8k | +4 | ++ | + | + | + |
| 124 | +validate-milestone | +M007 | +opus-4-6 | +Mar 30, 2026, 07:49 PM | +1m 55s | +$0.700 | +955.3k | +19 | ++ | + | + | + |
| 125 | +complete-milestone | +M007 | +opus-4-6 | +Mar 30, 2026, 07:51 PM | +1m 54s | +$0.704 | +951.5k | +17 | ++ | + | + | + |
| 126 | +research-slice | +M008/S01 | +opus-4-6 | +Mar 31, 2026, 04:52 AM | +2m 57s | +$1.32 | +2.02M | +40 | ++ | + | + | + |
| 127 | +plan-slice | +M008/S01 | +opus-4-6 | +Mar 31, 2026, 04:55 AM | +2m 10s | +$0.975 | +1.38M | +31 | ++ | + | + | + |
| 128 | +execute-task | +M008/S01/T01 | +opus-4-6 | +Mar 31, 2026, 04:58 AM | +4m 45s | +$2.38 | +3.75M | +48 | ++ | + | + | + |
| 129 | +execute-task | +M008/S01/T02 | +opus-4-6 | +Mar 31, 2026, 05:02 AM | +1m 17s | +$0.628 | +888.9k | +12 | ++ | + | + | + |
| 130 | +complete-slice | +M008/S01 | +opus-4-6 | +Mar 31, 2026, 05:04 AM | +1m 22s | +$0.439 | +503.4k | +6 | ++ | + | + | + |
| 131 | +research-slice | +M008/S02 | +opus-4-6 | +Mar 31, 2026, 05:05 AM | +3m 29s | +$1.66 | +2.67M | +45 | ++ | + | + | + |
| 132 | +plan-slice | +M008/S02 | +opus-4-6 | +Mar 31, 2026, 05:08 AM | +1m 56s | +$0.828 | +1.16M | +29 | ++ | + | + | + |
| 133 | +execute-task | +M008/S02/T01 | +opus-4-6 | +Mar 31, 2026, 05:10 AM | +2m 21s | +$1.06 | +1.65M | +28 | ++ | + | + | + |
| 134 | +execute-task | +M008/S02/T02 | +opus-4-6 | +Mar 31, 2026, 05:13 AM | +1m 40s | +$0.761 | +1.16M | +20 | ++ | + | + | + |
| 135 | +complete-slice | +M008/S02 | +opus-4-6 | +Mar 31, 2026, 05:14 AM | +58s | +$0.325 | +353.1k | +6 | ++ | + | + | + |
| 136 | +research-slice | +M008/S03 | +opus-4-6 | +Mar 31, 2026, 05:15 AM | +2m 14s | +$1.18 | +1.81M | +32 | ++ | + | + | + |
| 137 | +plan-slice | +M008/S03 | +opus-4-6 | +Mar 31, 2026, 05:18 AM | +1m 22s | +$0.415 | +429.6k | +13 | ++ | + | + | + |
| 138 | +execute-task | +M008/S03/T01 | +opus-4-6 | +Mar 31, 2026, 05:19 AM | +4m 1s | +$2.14 | +3.44M | +41 | ++ | + | + | + |
| 139 | +execute-task | +M008/S03/T02 | +opus-4-6 | +Mar 31, 2026, 05:23 AM | +2m 39s | +$1.33 | +2.08M | +24 | ++ | + | + | + |
| 140 | +complete-slice | +M008/S03 | +opus-4-6 | +Mar 31, 2026, 05:26 AM | +1m 51s | +$0.776 | +1.17M | +16 | ++ | + | + | + |
| 141 | +validate-milestone | +M008 | +opus-4-6 | +Mar 31, 2026, 05:28 AM | +1m 37s | +$0.379 | +413.3k | +5 | ++ | + | + | + |
| 142 | +complete-milestone | +M008 | +opus-4-6 | +Mar 31, 2026, 05:29 AM | +1m 36s | +$0.564 | +771.7k | +14 | ++ | + | + | + |
Dependencies
+ +M001: Chrysopedia Foundation — Infrastructure, Pipeline Core, and Skeleton UI
+M002: M002: Chrysopedia Deployment — GitHub, ub01 Docker Stack, and Production Wiring
+M003: M003: Domain + DNS + Per-Stage LLM Model Routing
+M004: M004: UI Polish, Bug Fixes, Technique Page Redesign, and Article Versioning
+M005: M005: Pipeline Dashboard, Technique Page Redesign, Key Moment Cards
+M006: M006: Admin Nav, Pipeline Log Views, Commit SHA, Tag Polish, Topics Redesign, Footer
+M007: M007: Pipeline Transparency, Auto-Ingest, Admin UX Polish, and Mobile Fixes
+M008: M008: Credibility Debt Cleanup — Broken Links, Test Data, Jargon, Empty Metrics
+M009: Homepage & First Impression
+M010: Discovery, Navigation & Visual Identity
+Metrics
+ +Token breakdown
+ +Cost over time
+ +Cost by phase
+ + + + + + + +Tokens by phase
+ + + + + + + +Cost by slice
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Cost by model
+ +Duration by slice
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Slice timeline
+ +Health
+ +| Token profile | standard |
| Truncation rate | 0.0% per unit (0 total) |
| Continue-here rate | 0.0% per unit (0 total) |
| Tool calls | 3899 |
| Messages | 3190 assistant / 2 user |
Tier breakdown
+| Tier | Units | Cost | Tokens |
|---|---|---|---|
| unknown | +142 | $172.23 | +244.48M |
Changelog 32
+ +Homepage technique cards now show topic tag pills and key moment counts; creator detail pages show technique-count-by-topic instead of meaningless '0 views'.
+ +- Sync OpenAI/SQLAlchemy/Qdrant throughout Celery tasks — no async in worker context (D004)
- Embedding/Qdrant stage is non-blocking side-effect — failures don't break pipeline (D005)
- Stage 4 classification stored in Redis (24h TTL) due to missing KeyMoment columns
- Pipeline dispatch from ingest is best-effort; manual trigger returns 503 on Celery failure
- LLMClient retries once with JSON nudge on malformed LLM output before failing
6 files modified
+-
+
backend/schemas.py— Added key_moment_count: int = 0 to TechniquePageReadbackend/routers/techniques.py— Added correlated COUNT subquery for key moments in list_techniquesfrontend/src/api/public-client.ts— Added key_moment_count to TechniqueListItem interfacefrontend/src/pages/Home.tsx— Rendered topic_tags as pill badges and key_moment_count on homepage cardsfrontend/src/pages/CreatorDetail.tsx— Replaced view_count with topic-category breakdown from techniques arrayfrontend/src/App.css— Added .recent-card__moments styling for moment count display
+
Removed test data from Creators page, eliminated yellow jargon banner from search results, cleaned up footer version display, and bumped to v0.8.0.
+ +- Used NullPool for test engine to avoid asyncpg connection contention in pytest-asyncio
- Fixed _now() helper to return naive UTC datetimes for asyncpg TIMESTAMP WITHOUT TIME ZONE compatibility
- Used slugify helper inline in ingest.py rather than a shared utils module
- Set file_path to {creator_folder}/{source_file} for new SourceVideo records
7 files modified
+-
+
backend/models.py— Added hidden: Mapped[bool] column to Creator classbackend/routers/creators.py— Added Creator.hidden != True filter to list_creators() query and count queryalembic/versions/009_add_creator_hidden_flag.py— New migration: adds hidden column and marks testcreator as hiddenfrontend/src/pages/SearchResults.tsx— Removed fallback banner JSX and fallbackUsed statefrontend/src/App.css— Removed .search-fallback-banner CSS rulefrontend/src/components/AppFooter.tsx— Hide commit section when __GIT_COMMIT__ is 'dev'frontend/package.json— Version bumped from 0.1.0 to 0.8.0
+
Key moment search results now link to their parent technique page and scroll to the specific moment, instead of 404ing.
+ +- [object Object]
- env_file uses required: false so docker compose config validates on fresh clones
- POSTGRES_PASSWORD uses :-changeme default instead of :? to avoid config failures
- PostgreSQL exposed on host port 5433 to avoid conflicts with other projects
- SQLAlchemy relationship import aliased to sa_relationship to avoid column name clash
- Separate PostgreSQL enum type names to avoid collisions (key_moment_content_type vs content_type)
- Health check at /health performs real DB SELECT 1; lightweight /api/v1/health also available
- Whisper import deferred so --help works without openai-whisper installed
- Sample transcript uses realistic music production content for downstream pipeline testing
8 files modified
+-
+
backend/schemas.py— Added technique_page_slug: str = '' to SearchResultItembackend/search_service.py— Added technique_page_slug population in _enrich_results (semantic) and keyword_search (DB join)backend/pipeline/stages.py— Stage 6 now includes slug in technique page Qdrant dicts, technique_page_slug/technique_page_id in key moment dictsbackend/pipeline/qdrant_client.py— Updated payload structure documentation (no functional change — stages.py builds the dicts)backend/tests/test_search.py— 3 new keyword search tests for technique_page_slug; fixed ProcessingStatus seed data bugfrontend/src/api/public-client.ts— Added technique_page_slug to SearchResultItem interfacefrontend/src/pages/SearchResults.tsx— Key moment links now route to /techniques/{parent_slug}#km-{id} with re-search fallbackfrontend/src/pages/TechniquePage.tsx— Added km-{id} anchor IDs to key moment list items; added useEffect for hash-scroll on load
+
Added CSS flex-wrap, max-width constraints, and tighter mobile gaps to prevent horizontal overflow on ~412px viewports for technique pages and global content.
+ +- Vite define with JSON.stringify for build-time constant injection
- execSync for local git SHA with VITE_GIT_COMMIT env var fallback for Docker builds
- ARG+ENV pattern in Dockerfile.web matching existing API service pattern
- Read package.json via fs.readFileSync to avoid TS module resolution issues in Vite config
1 file modified
+-
+
frontend/src/App.css— Added flex-wrap on .technique-header__tags, new .technique-header__creator-genres rule, max-width + ellipsis on .version-switcher__select, tighter .app-main padding and .technique-header__meta gap at ≤640px
+
Fixed key moment card text overflow — long filenames truncate with ellipsis, titles wrap gracefully, no horizontal bleed from sidebar cards.
+ +- [object Object]
- [object Object]
- [object Object]
- [object Object]
- [object Object]
- [object Object]
- 300ms asyncio.wait_for timeout on both embedding and Qdrant calls
- Topics endpoint loads canonical_tags.yaml at request time and counts tag matches from DB
- Mocked SearchService at router dependency level for integration tests
- Duplicated request<T> helper in public-client.ts to avoid coupling public and admin API clients
1 file modified
+-
+
frontend/src/App.css— Added overflow: hidden, word-break: break-word, max-width: 100%, and min-width: 0 to technique-moment card CSS rules
+
Cleaned up AdminPipeline page with debug mode toggle, status filter pills, pruned dead UI, clearer labels, debug-aware trigger button, and review queue cross-links.
+ +- Redis mode toggle uses per-request get_redis() with aclose() — no connection pool (D007)
- API client uses bare fetch() with shared request() helper — no external HTTP library
- MomentDetail fetches full queue to find moment by ID since no single-moment GET endpoint exists
- Split creates new moment with '(split)' title suffix; merge combines summaries with double-newline separator
- Split dialog validates timestamp client-side before API call
3 files modified
+-
+
frontend/src/pages/AdminPipeline.tsx— Added DebugModeToggle, StatusFilter, renamed view toggle labels, removed dead UI, added debug indicator on trigger button, added review queue cross-linkfrontend/src/api/public-client.ts— Added fetchDebugMode() and setDebugMode() API client functionsfrontend/src/App.css— Added debug-toggle and moments-link CSS styles
+
Built and deployed a watchdog-based folder watcher service that auto-ingests transcript JSON files dropped into a monitored directory on ub01, replacing manual curl/upload for pipeline input.
+ +- Sync OpenAI/SQLAlchemy/Qdrant throughout Celery tasks — no async in worker context (D004)
- Embedding/Qdrant stage is non-blocking side-effect — failures don't break pipeline (D005)
- Stage 4 classification stored in Redis (24h TTL) due to missing KeyMoment columns
- Pipeline dispatch from ingest is best-effort; manual trigger returns 503 on Celery failure
- LLMClient retries once with JSON nudge on malformed LLM output before failing
3 files modified
+-
+
backend/watcher.py— New standalone folder watcher script using watchdog PollingObserverbackend/requirements.txt— Added watchdog>=4.0,<5.0 dependencydocker-compose.yml— Added chrysopedia-watcher service definition
+
Added DebugPayloadViewer component to the admin pipeline page — LLM call events now show collapsible System Prompt / User Prompt / Response sections with per-section clipboard copy and full JSON export.
+ +- Used NullPool for test engine to avoid asyncpg connection contention in pytest-asyncio
- Fixed _now() helper to return naive UTC datetimes for asyncpg TIMESTAMP WITHOUT TIME ZONE compatibility
- Used slugify helper inline in ingest.py rather than a shared utils module
- Set file_path to {creator_folder}/{source_file} for new SourceVideo records
3 files modified
+-
+
frontend/src/pages/AdminPipeline.tsx— Added DebugPayloadViewer component (collapsible sections, copy, export) and wired into llm_call event rowsfrontend/src/api/public-client.ts— Added system_prompt_text, user_prompt_text, response_text fields to PipelineEvent interfacefrontend/src/App.css— Added ~100 lines of debug-viewer CSS using var(--color-*) custom properties
+
Added debug mode toggle (Redis-backed) that captures full LLM system prompt, user prompt, and response text in pipeline_events, plus per-stage token summary endpoint.
+ +- [object Object]
- env_file uses required: false so docker compose config validates on fresh clones
- POSTGRES_PASSWORD uses :-changeme default instead of :? to avoid config failures
- PostgreSQL exposed on host port 5433 to avoid conflicts with other projects
- SQLAlchemy relationship import aliased to sa_relationship to avoid column name clash
- Separate PostgreSQL enum type names to avoid collisions (key_moment_content_type vs content_type)
- Health check at /health performs real DB SELECT 1; lightweight /api/v1/health also available
- Whisper import deferred so --help works without openai-whisper installed
- Sample transcript uses realistic music production content for downstream pipeline testing
6 files modified
+-
+
backend/models.py— Added system_prompt_text, user_prompt_text, response_text columns to PipelineEventbackend/config.py— Added debug_mode: bool = False to Settingsbackend/schemas.py— Added DebugModeResponse, DebugModeUpdate, TokenStageSummary, TokenSummaryResponse schemasbackend/routers/pipeline.py— Added debug-mode GET/PUT endpoints, token-summary endpoint, extended event listing responsealembic/versions/006_debug_columns.py— Migration adding 3 TEXT columns to pipeline_eventsbackend/pipeline/stages.py— Added _is_debug_mode(), extended _emit_event and _make_llm_callback for conditional I/O capture, updated 4 stage call sites
+
Added a persistent app footer showing version, build date, commit SHA link, and GitHub repo link — wired through Vite build-time constants with Docker ARG/ENV passthrough for production builds.
+ +- Vite define with JSON.stringify for build-time constant injection
- execSync for local git SHA with VITE_GIT_COMMIT env var fallback for Docker builds
- ARG+ENV pattern in Dockerfile.web matching existing API service pattern
- Read package.json via fs.readFileSync to avoid TS module resolution issues in Vite config
7 files modified
+-
+
frontend/src/components/AppFooter.tsx— New component displaying version, build date, commit SHA link, and GitHub repo linkfrontend/vite.config.ts— Added define block with __APP_VERSION__, __BUILD_DATE__, __GIT_COMMIT__ build-time constantsfrontend/src/App.tsx— Added AppFooter import and render at bottom of app layoutfrontend/src/App.css— Added flex-column layout with min-height:100vh to push footer to bottomfrontend/src/vite-env.d.ts— Added TypeScript declarations for build-time constantsdocker/Dockerfile.web— Added ARG VITE_GIT_COMMIT=dev and ENV VITE_GIT_COMMIT (on ub01)docker-compose.yml— Added VITE_GIT_COMMIT build arg to web service (on ub01)
+
Redesigned Topics browse page from vertical accordion to responsive 2-column card grid layout with 7 categories (added Music Theory) featuring colored accents, descriptions, summary stats, and expand/collapse sub-topic lists.
+ +- [object Object]
- [object Object]
- [object Object]
- [object Object]
- [object Object]
- [object Object]
- 300ms asyncio.wait_for timeout on both embedding and Qdrant calls
- Topics endpoint loads canonical_tags.yaml at request time and counts tag matches from DB
- Mocked SearchService at router dependency level for integration tests
- Duplicated request<T> helper in public-client.ts to avoid coupling public and admin API clients
3 files modified
+-
+
config/canonical_tags.yaml— Added Music Theory as 7th category with 8 sub-topics (harmony, chord progressions, scales, rhythm, time signatures, melody, counterpoint, song keys)frontend/src/pages/TopicsBrowse.tsx— Rewritten from vertical accordion to responsive 2-column card grid with colored accents, descriptions, summary stats, and expand/collapsefrontend/src/App.css— Added music-theory badge CSS custom properties and class; replaced .topics-list styles with .topics-grid/.topic-card card grid styles
+
Reordered technique page sidebar (plugins first), added prominent creator block with genre pills, and implemented per-category badge color system with 6 category-specific color pairs.
+ +- Redis mode toggle uses per-request get_redis() with aclose() — no connection pool (D007)
- API client uses bare fetch() with shared request() helper — no external HTTP library
- MomentDetail fetches full queue to find moment by ID since no single-moment GET endpoint exists
- Split creates new moment with '(split)' title suffix; merge combines summaries with double-newline separator
- Split dialog validates timestamp client-side before API call
2 files modified
+-
+
frontend/src/pages/TechniquePage.tsx— Moved plugins section to top of sidebar, added creator-block with genre pills between h1 and meta row, dynamic category badge class derivationfrontend/src/App.css— Added 12 per-category CSS custom properties, 6 .badge--cat-* classes, creator-block/creator-link/pill--genre-small styles, removed old .technique-header__creator rules
+
Pipeline now captures the git commit SHA at Docker build time and displays it in the technique page version metadata panel.
+ +- Sync OpenAI/SQLAlchemy/Qdrant throughout Celery tasks — no async in worker context (D004)
- Embedding/Qdrant stage is non-blocking side-effect — failures don't break pipeline (D005)
- Stage 4 classification stored in Redis (24h TTL) due to missing KeyMoment columns
- Pipeline dispatch from ingest is best-effort; manual trigger returns 503 on Celery failure
- LLMClient retries once with JSON nudge on malformed LLM output before failing
5 files modified
+-
+
docker/Dockerfile.api— Added GIT_COMMIT_SHA build arg and RUN echo to write SHA to /app/.git-commitdocker-compose.yml— Added GIT_COMMIT_SHA build arg to both chrysopedia-api and chrysopedia-worker servicesbackend/config.py— Added git_commit_sha field to Settings class with 'unknown' defaultbackend/pipeline/stages.py— Added _get_git_commit_sha() helper with 4-tier fallback; added git_commit_sha to _capture_pipeline_metadata()frontend/src/pages/TechniquePage.tsx— Added conditional Commit row to version metadata panel with 7-char abbreviated SHA
+
Added Head/Tail toggle to pipeline event log — Head shows oldest events first (asc), Tail shows newest (desc) — with backend `order` query parameter, segmented toggle UI, and preserved token count display.
+ +- Used NullPool for test engine to avoid asyncpg connection contention in pytest-asyncio
- Fixed _now() helper to return naive UTC datetimes for asyncpg TIMESTAMP WITHOUT TIME ZONE compatibility
- Used slugify helper inline in ingest.py rather than a shared utils module
- Set file_path to {creator_folder}/{source_file} for new SourceVideo records
4 files modified
+-
+
backend/routers/pipeline.py— Added order query parameter (asc/desc, default desc) to list_pipeline_events with validation and dynamic orderingfrontend/src/api/public-client.ts— Added order param to fetchPipelineEvents params type and URL builderfrontend/src/pages/AdminPipeline.tsx— Added viewMode state, Head/Tail segmented toggle, order param wiring, and pagination reset on mode switchfrontend/src/App.css— Added segmented toggle button CSS (.pipeline-events__view-toggle, .pipeline-events__view-btn)
+
Header nav consolidated: Home/Topics/Creators as flat links, Admin dropdown for Review/Reports/Pipeline, ModeToggle removed from header
+ +- [object Object]
- env_file uses required: false so docker compose config validates on fresh clones
- POSTGRES_PASSWORD uses :-changeme default instead of :? to avoid config failures
- PostgreSQL exposed on host port 5433 to avoid conflicts with other projects
- SQLAlchemy relationship import aliased to sa_relationship to avoid column name clash
- Separate PostgreSQL enum type names to avoid collisions (key_moment_content_type vs content_type)
- Health check at /health performs real DB SELECT 1; lightweight /api/v1/health also available
- Whisper import deferred so --help works without openai-whisper installed
- Sample transcript uses realistic music production content for downstream pipeline testing
3 files modified
+-
+
frontend/src/components/AdminDropdown.tsx— New component: dropdown trigger + menu with 3 admin links, click-outside/Escape close, ARIA attributesfrontend/src/App.tsx— Replaced 3 flat admin Links + ModeToggle with single AdminDropdown importfrontend/src/App.css— Appended BEM-style dropdown CSS using existing theme custom properties
+
Restructured key moment cards: title promoted to standalone h3 on its own line, metadata (source file, timestamp, content type badge) moved to a clean flex-row below.
+ +- Sync OpenAI/SQLAlchemy/Qdrant throughout Celery tasks — no async in worker context (D004)
- Embedding/Qdrant stage is non-blocking side-effect — failures don't break pipeline (D005)
- Stage 4 classification stored in Redis (24h TTL) due to missing KeyMoment columns
- Pipeline dispatch from ingest is best-effort; manual trigger returns 503 on Celery failure
- LLMClient retries once with JSON nudge on malformed LLM output before failing
2 files modified
+-
+
frontend/src/pages/TechniquePage.tsx— Extracted key moment title from __header flex row into standalone h3 element; renamed __header div to __metafrontend/src/App.css— Added __title block styles with h3 margin reset; added __meta flex styles (migrated from __header); removed dead __header class
+
Technique pages now display prose content (summary + study guide) in a left column and sidebar content (key moments, signal chains, plugins, related techniques) in a right column at desktop widths, collapsing to single column on mobile.
+ +- Used NullPool for test engine to avoid asyncpg connection contention in pytest-asyncio
- Fixed _now() helper to return naive UTC datetimes for asyncpg TIMESTAMP WITHOUT TIME ZONE compatibility
- Used slugify helper inline in ingest.py rather than a shared utils module
- Set file_path to {creator_folder}/{source_file} for new SourceVideo records
2 files modified
+-
+
frontend/src/App.css— Widened .technique-page max-width from 48rem to 64rem. Added .technique-columns CSS grid (1fr 22rem), .technique-columns__main, .technique-columns__sidebar with sticky positioning, and @media 768px breakpoint for single-column collapse.frontend/src/pages/TechniquePage.tsx— Wrapped content sections in .technique-columns grid. Summary + body sections in __main div. Key moments + signal chains + plugins + related techniques in __sidebar div.
+
Built a full pipeline management admin page at /admin/pipeline with video list, status monitoring, retrigger/revoke controls, event log with token usage, collapsible JSON responses, and live worker status.
+ +- [object Object]
- env_file uses required: false so docker compose config validates on fresh clones
- POSTGRES_PASSWORD uses :-changeme default instead of :? to avoid config failures
- PostgreSQL exposed on host port 5433 to avoid conflicts with other projects
- SQLAlchemy relationship import aliased to sa_relationship to avoid column name clash
- Separate PostgreSQL enum type names to avoid collisions (key_moment_content_type vs content_type)
- Health check at /health performs real DB SELECT 1; lightweight /api/v1/health also available
- Whisper import deferred so --help works without openai-whisper installed
- Sample transcript uses realistic music production content for downstream pipeline testing
9 files modified
+-
+
backend/pipeline/stages.py— Fixed _emit_event and _make_llm_callback syntax errors, replaced _get_session_factory() with _get_sync_session()backend/routers/pipeline.py— New router with 5 admin pipeline endpoints (videos, trigger, revoke, events, worker-status)backend/models.py— PipelineEvent model (previously added, verified working)backend/schemas.py— Pydantic schemas for pipeline admin responsesalembic/versions/004_pipeline_events.py— Migration creating pipeline_events table (previously added, verified at head)frontend/src/pages/AdminPipeline.tsx— New admin pipeline page with video table, event log, JSON viewer, worker statusfrontend/src/api/public-client.ts— API client functions for pipeline admin endpointsfrontend/src/App.tsx— Added /admin/pipeline route and nav linkfrontend/src/App.css— Themed CSS for pipeline admin page components
+
Added technique page version tracking with pipeline metadata capture, snapshot-on-write in stage 5, version list/detail API endpoints, and frontend version count display.
+ +- Redis mode toggle uses per-request get_redis() with aclose() — no connection pool (D007)
- API client uses bare fetch() with shared request() helper — no external HTTP library
- MomentDetail fetches full queue to find moment by ID since no single-moment GET endpoint exists
- Split creates new moment with '(split)' title suffix; merge combines summaries with double-newline separator
- Split dialog validates timestamp client-side before API call
8 files modified
+-
+
backend/models.py— Added TechniquePageVersion model with UUID PK, FK, version_number, content_snapshot (JSONB), pipeline_metadata (JSONB), created_at. Added versions relationship on TechniquePage.alembic/versions/002_technique_page_versions.py— New migration creating technique_page_versions table with composite unique index on (technique_page_id, version_number)backend/pipeline/stages.py— Added _capture_pipeline_metadata() helper and pre-overwrite snapshot logic in stage5_synthesisbackend/schemas.py— Added TechniquePageVersionSummary, TechniquePageVersionDetail, TechniquePageVersionListResponse schemas; added version_count to TechniquePageDetailbackend/routers/techniques.py— Added GET /{slug}/versions and GET /{slug}/versions/{version_number} endpoints; modified get_technique to include version_countbackend/tests/test_public_api.py— Added 6 integration tests for version endpointsfrontend/src/api/public-client.ts— Added version_count to TechniquePageDetail, TechniquePageVersionSummary/ListResponse interfaces, fetchTechniqueVersions functionfrontend/src/pages/TechniquePage.tsx— Conditional version count display in meta stats line
+
Technique detail pages now show meta stats, video filenames on key moments, and monospace signal chain flow blocks with arrow separators — matching the reference layout spec.
+ +- Sync OpenAI/SQLAlchemy/Qdrant throughout Celery tasks — no async in worker context (D004)
- Embedding/Qdrant stage is non-blocking side-effect — failures don't break pipeline (D005)
- Stage 4 classification stored in Redis (24h TTL) due to missing KeyMoment columns
- Pipeline dispatch from ingest is best-effort; manual trigger returns 503 on Celery failure
- LLMClient retries once with JSON nudge on malformed LLM output before failing
5 files modified
+-
+
backend/schemas.py— Added video_filename: str = '' to KeyMomentSummary schemabackend/routers/techniques.py— Chained selectinload for source_video; post-processing loop populates video_filenamefrontend/src/api/public-client.ts— Added video_filename: string to KeyMomentSummary TypeScript interfacefrontend/src/pages/TechniquePage.tsx— Added meta stats line, video filename display on moments, monospace signal chain flow blocksfrontend/src/App.css— Added CSS for technique-header__stats, technique-moment__source, technique-chain__flow/arrow/step classes
+
Replaced all 193 hex colors and 24 rgba values in App.css with 77 CSS custom properties establishing a dark theme with cyan accents, fixed mobile horizontal overflow, and updated HTML metadata.
+ +- Used NullPool for test engine to avoid asyncpg connection contention in pytest-asyncio
- Fixed _now() helper to return naive UTC datetimes for asyncpg TIMESTAMP WITHOUT TIME ZONE compatibility
- Used slugify helper inline in ingest.py rather than a shared utils module
- Set file_path to {creator_folder}/{source_file} for new SourceVideo records
2 files modified
+-
+
frontend/src/App.css— Added 77 CSS custom properties in :root block, replaced all 193 hex colors and 24 rgba values with var(--*) references, added html/body overflow-x:hidden, fixed mode-toggle label truncation, creator-row stats wrapping, and header flex-wrap for mobilefrontend/index.html— Changed title from 'Chrysopedia Admin' to 'Chrysopedia', added <meta name="theme-color" content="#0a0a12">
+
Fixed creators page (paginated response) and review detail (single-moment endpoint) — both working with real pipeline data
+ +- [object Object]
- env_file uses required: false so docker compose config validates on fresh clones
- POSTGRES_PASSWORD uses :-changeme default instead of :? to avoid config failures
- PostgreSQL exposed on host port 5433 to avoid conflicts with other projects
- SQLAlchemy relationship import aliased to sa_relationship to avoid column name clash
- Separate PostgreSQL enum type names to avoid collisions (key_moment_content_type vs content_type)
- Health check at /health performs real DB SELECT 1; lightweight /api/v1/health also available
- Whisper import deferred so --help works without openai-whisper installed
- Sample transcript uses realistic music production content for downstream pipeline testing
4 files modified
+-
+
backend/routers/creators.py— Returns paginated {items,total,offset,limit} wrapper instead of plain arraybackend/routers/review.py— Limit raised to 1000, added GET /moments/{moment_id} endpointfrontend/src/api/client.ts— Added fetchMoment() functionfrontend/src/pages/MomentDetail.tsx— Uses fetchMoment instead of full queue fetch
+
Per-stage LLM model/modality routing with think-tag stripping — 59 tests pass
+ +- Used NullPool for test engine to avoid asyncpg connection contention in pytest-asyncio
- Fixed _now() helper to return naive UTC datetimes for asyncpg TIMESTAMP WITHOUT TIME ZONE compatibility
- Used slugify helper inline in ingest.py rather than a shared utils module
- Set file_path to {creator_folder}/{source_file} for new SourceVideo records
5 files modified
+-
+
backend/config.py— Added 8 per-stage model/modality config fieldsbackend/pipeline/llm_client.py— Modality-aware complete() with strip_think_tags()backend/pipeline/stages.py— Stages 2-5 use _get_stage_config, pass modality/model_overridebackend/tests/test_pipeline.py— Added test_strip_think_tags with 7 cases.env.example— Documented per-stage LLM vars with modality comments
+
chrysopedia.com live with AdGuard DNS, nginx reverse proxy, and Certbot SSL
+ +- [object Object]
- env_file uses required: false so docker compose config validates on fresh clones
- POSTGRES_PASSWORD uses :-changeme default instead of :? to avoid config failures
- PostgreSQL exposed on host port 5433 to avoid conflicts with other projects
- SQLAlchemy relationship import aliased to sa_relationship to avoid column name clash
- Separate PostgreSQL enum type names to avoid collisions (key_moment_content_type vs content_type)
- Health check at /health performs real DB SELECT 1; lightweight /api/v1/health also available
- Whisper import deferred so --help works without openai-whisper installed
- Sample transcript uses realistic music production content for downstream pipeline testing
CLAUDE.md redirect and README deployment docs established
+ +- Sync OpenAI/SQLAlchemy/Qdrant throughout Celery tasks — no async in worker context (D004)
- Embedding/Qdrant stage is non-blocking side-effect — failures don't break pipeline (D005)
- Stage 4 classification stored in Redis (24h TTL) due to missing KeyMoment columns
- Pipeline dispatch from ingest is best-effort; manual trigger returns 503 on Celery failure
- LLMClient retries once with JSON nudge on malformed LLM output before failing
2 files modified
+-
+
CLAUDE.md— New file — redirects future development to ub01 canonical pathREADME.md— Added deployment section with service URLs and update workflow
+
Full 7-container Chrysopedia stack deployed and healthy on ub01 at port 8096
+ +- Used NullPool for test engine to avoid asyncpg connection contention in pytest-asyncio
- Fixed _now() helper to return naive UTC datetimes for asyncpg TIMESTAMP WITHOUT TIME ZONE compatibility
- Used slugify helper inline in ingest.py rather than a shared utils module
- Set file_path to {creator_folder}/{source_file} for new SourceVideo records
3 files modified
+-
+
docker-compose.yml— Healthcheck fixes for Ollama (ollama list), Qdrant (bash /dev/tcp), web (curl), worker (celery inspect ping)docker/Dockerfile.api— Added alembic.ini and alembic/ to Docker imagealembic/env.py— Added parent dir to sys.path for Docker layout compatibility
+
Fixed compose config (subnet, ports, Qdrant, Ollama), created private GitHub repo, pushed codebase
+ +- [object Object]
- env_file uses required: false so docker compose config validates on fresh clones
- POSTGRES_PASSWORD uses :-changeme default instead of :? to avoid config failures
- PostgreSQL exposed on host port 5433 to avoid conflicts with other projects
- SQLAlchemy relationship import aliased to sa_relationship to avoid column name clash
- Separate PostgreSQL enum type names to avoid collisions (key_moment_content_type vs content_type)
- Health check at /health performs real DB SELECT 1; lightweight /api/v1/health also available
- Whisper import deferred so --help works without openai-whisper installed
- Sample transcript uses realistic music production content for downstream pipeline testing
3 files modified
+-
+
docker-compose.yml— Corrected subnet, added Qdrant + Ollama services, web port 8096, EMBEDDING_API_URL.env.example— Updated LLM/embedding URLs for FYN DGX endpoint and Ollama containerdocker/Dockerfile.api— Added curl + HEALTHCHECK, copies prompts/ and config/ into image
+
Delivered the complete public-facing web UI: async search service with Qdrant+keyword fallback, landing page with debounced typeahead, technique page detail, creators browse (randomized default sort), topics browse (two-level hierarchy), and 18 integration tests — all 58 backend tests pass, frontend production build clean.
+ +- [object Object]
- [object Object]
- [object Object]
- [object Object]
- [object Object]
- [object Object]
- 300ms asyncio.wait_for timeout on both embedding and Qdrant calls
- Topics endpoint loads canonical_tags.yaml at request time and counts tag matches from DB
- Mocked SearchService at router dependency level for integration tests
- Duplicated request<T> helper in public-client.ts to avoid coupling public and admin API clients
18 files modified
+-
+
backend/search_service.py— New async SearchService class: embed_query (300ms timeout), search_qdrant, keyword_search (ILIKE), orchestrated search with fallbackbackend/schemas.py— Added SearchResultItem, SearchResponse, TechniquePageDetail, TopicCategory, TopicSubTopic, CreatorBrowseItem schemasbackend/routers/search.py— New router: GET /search with query/scope/limit params, SearchService instantiation, latency loggingbackend/routers/techniques.py— New router: GET /techniques (list with filters), GET /techniques/{slug} (detail with eager-loaded relations)backend/routers/topics.py— New router: GET /topics (category hierarchy from canonical_tags.yaml + DB counts), GET /topics/{category_slug}backend/routers/creators.py— Enhanced: sort=random|alpha|views, genre filter, technique_count/video_count correlated subqueriesbackend/main.py— Mounted search, techniques, topics routers at /api/v1backend/tests/test_search.py— 5 integration tests: search happy path, empty query, keyword fallback, scope filter, no resultsbackend/tests/test_public_api.py— 13 integration tests: techniques list/detail/404, topics hierarchy, creators sort/filter/detail/404/countsfrontend/src/api/public-client.ts— Typed API client with interfaces and 6 endpoint functions for all public routesfrontend/src/pages/Home.tsx— Landing page: auto-focus search, 300ms debounced typeahead, nav cards, recently addedfrontend/src/pages/SearchResults.tsx— Search results: URL param-driven, type-grouped display, fallback bannerfrontend/src/pages/TechniquePage.tsx— Full technique page: header/badges/prose/key moments/signal chains/plugins/related links, amber bannerfrontend/src/pages/CreatorsBrowse.tsx— Creators browse: randomized default sort, genre filter pills, name filter, sort togglefrontend/src/pages/CreatorDetail.tsx— Creator detail: info header + technique pages filtered by creator_slugfrontend/src/pages/TopicsBrowse.tsx— Topics browse: two-level expandable hierarchy with counts and filter inputfrontend/src/App.tsx— Added 6 public routes, updated navigation header with Chrysopedia brandingfrontend/src/App.css— ~500 lines added: search bar, typeahead, nav cards, technique page, browse pages, filter/sort controls
+
Delivered the complete review queue admin UI: 9 backend API endpoints with 24 integration tests, a React+Vite+TypeScript frontend with typed API client, and full admin pages for queue browsing, moment review/edit/split/merge, and review-vs-auto mode toggle.
+ +- Redis mode toggle uses per-request get_redis() with aclose() — no connection pool (D007)
- API client uses bare fetch() with shared request() helper — no external HTTP library
- MomentDetail fetches full queue to find moment by ID since no single-moment GET endpoint exists
- Split creates new moment with '(split)' title suffix; merge combines summaries with double-newline separator
- Split dialog validates timestamp client-side before API call
17 files modified
+-
+
backend/routers/review.py— New: 9 async review queue endpoints (354 lines)backend/schemas.py— Added 8 Pydantic schemas for review queue (ReviewQueueItem, ReviewQueueResponse, ReviewStatsResponse, MomentEditRequest, MomentSplitRequest, MomentMergeRequest, ReviewModeResponse, ReviewModeUpdate)backend/redis_client.py— New: async Redis client helper with get_redis()backend/main.py— Mounted review router under /api/v1backend/tests/test_review.py— New: 24 integration tests for review endpoints (495 lines)frontend/package.json— New: React 18 + Vite 6 + TypeScript 5.6 dependenciesfrontend/vite.config.ts— New: Vite config with React plugin and /api dev proxyfrontend/tsconfig.json— New: strict TypeScript configfrontend/index.html— New: Vite entry pointfrontend/src/main.tsx— New: React app entry with BrowserRouterfrontend/src/App.tsx— New: App shell with routes and nav headerfrontend/src/App.css— New: Comprehensive admin CSS (620 lines)frontend/src/api/client.ts— New: Typed API client with 9 functions and TypeScript interfaces (187 lines)frontend/src/pages/ReviewQueue.tsx— New: Queue list page with stats bar, filter tabs, pagination, mode togglefrontend/src/pages/MomentDetail.tsx— New: Moment detail page with approve/reject/edit/split/merge actions (458 lines)frontend/src/components/StatusBadge.tsx— New: Reusable status badge with color codingfrontend/src/components/ModeToggle.tsx— New: Review/auto mode toggle component
+
Built the complete 6-stage LLM extraction pipeline (segmentation → extraction → classification → synthesis → embedding) with Celery workers, sync SQLAlchemy, primary/fallback LLM endpoints, Qdrant vector indexing, configurable prompt templates, auto-dispatch from ingest, manual re-trigger API, and 10 integration tests — all 16 tests pass.
+ +- Sync OpenAI/SQLAlchemy/Qdrant throughout Celery tasks — no async in worker context (D004)
- Embedding/Qdrant stage is non-blocking side-effect — failures don't break pipeline (D005)
- Stage 4 classification stored in Redis (24h TTL) due to missing KeyMoment columns
- Pipeline dispatch from ingest is best-effort; manual trigger returns 503 on Celery failure
- LLMClient retries once with JSON nudge on malformed LLM output before failing
19 files modified
+-
+
backend/config.py— Extended Settings with 12 LLM/embedding/Qdrant/prompt config fieldsbackend/requirements.txt— Added openai, qdrant-client, pyyaml, psycopg2-binarybackend/worker.py— Created Celery app with Redis broker, imports pipeline.stagesbackend/pipeline/__init__.py— Created empty package initbackend/pipeline/schemas.py— 8 Pydantic models for pipeline stage I/Obackend/pipeline/llm_client.py— Sync LLMClient with primary/fallback logicbackend/pipeline/embedding_client.py— Sync EmbeddingClient for /v1/embeddingsbackend/pipeline/qdrant_client.py— QdrantManager with idempotent collection mgmt and metadata upsertsbackend/pipeline/stages.py— 6 Celery tasks: stages 2-6 + run_pipeline orchestratorbackend/routers/pipeline.py— POST /trigger/{video_id} manual re-trigger endpointbackend/routers/ingest.py— Added run_pipeline.delay() dispatch after ingest commitbackend/main.py— Mounted pipeline router under /api/v1prompts/stage2_segmentation.txt— LLM prompt for topic boundary detectionprompts/stage3_extraction.txt— LLM prompt for key moment extractionprompts/stage4_classification.txt— LLM prompt for canonical tag classificationprompts/stage5_synthesis.txt— LLM prompt for technique page synthesisbackend/tests/test_pipeline.py— 10 integration tests covering all pipeline stagesbackend/tests/fixtures/mock_llm_responses.py— Mock LLM response fixtures for all stagesbackend/tests/conftest.py— Added sync engine/session fixtures and pre_ingested_video fixture
+
Delivered POST /api/v1/ingest endpoint with creator auto-detection, SourceVideo upsert, TranscriptSegment bulk insert, raw JSON persistence, and 6 passing integration tests against real PostgreSQL.
+ +- Used NullPool for test engine to avoid asyncpg connection contention in pytest-asyncio
- Fixed _now() helper to return naive UTC datetimes for asyncpg TIMESTAMP WITHOUT TIME ZONE compatibility
- Used slugify helper inline in ingest.py rather than a shared utils module
- Set file_path to {creator_folder}/{source_file} for new SourceVideo records
9 files modified
+-
+
backend/routers/ingest.py— New file — POST /api/v1/ingest endpoint with creator auto-detection, SourceVideo upsert, TranscriptSegment bulk insert, raw JSON persistencebackend/schemas.py— Added TranscriptIngestResponse Pydantic model with 7 fieldsbackend/main.py— Mounted ingest router under /api/v1 prefixbackend/requirements.txt— Added python-multipart, pytest, pytest-asyncio, httpx dependenciesbackend/models.py— Fixed _now() to return naive UTC datetimes for asyncpg compatibilitybackend/tests/conftest.py— New file — async test fixtures: NullPool engine, ASGI client, sample transcript pathbackend/tests/test_ingest.py— New file — 6 integration tests for ingest endpointbackend/tests/fixtures/sample_transcript.json— New file — 5-segment sample transcript JSON fixturebackend/pytest.ini— New file — asyncio_mode = auto configuration
+
Delivered deployable Docker Compose infrastructure with PostgreSQL schema (7 tables), FastAPI skeleton API with CRUD endpoints, desktop Whisper transcription script, and sample transcript fixture.
+ +- [object Object]
- env_file uses required: false so docker compose config validates on fresh clones
- POSTGRES_PASSWORD uses :-changeme default instead of :? to avoid config failures
- PostgreSQL exposed on host port 5433 to avoid conflicts with other projects
- SQLAlchemy relationship import aliased to sa_relationship to avoid column name clash
- Separate PostgreSQL enum type names to avoid collisions (key_moment_content_type vs content_type)
- Health check at /health performs real DB SELECT 1; lightweight /api/v1/health also available
- Whisper import deferred so --help works without openai-whisper installed
- Sample transcript uses realistic music production content for downstream pipeline testing
25 files modified
+-
+
docker-compose.yml— Docker Compose project with 5 services (PostgreSQL 16, Redis 7, FastAPI API, Celery worker, React/nginx web).env.example— Template with all required environment variables and descriptionsdocker/Dockerfile.api— Multi-stage Dockerfile for FastAPI + Celery worker servicedocker/Dockerfile.web— Dockerfile for React app served via nginxdocker/nginx.conf— Nginx config for serving React SPA with API proxybackend/main.py— FastAPI app with lifespan, CORS, structured logging, router mountingbackend/models.py— SQLAlchemy async models for all 7 entities with enums, FKs, JSONBbackend/database.py— Async engine, session factory, declarative basebackend/schemas.py— Pydantic v2 schemas (Base/Create/Read) for all entitiesbackend/config.py— pydantic-settings config loading from .envbackend/routers/health.py— GET /health with DB connectivity checkbackend/routers/creators.py— GET /api/v1/creators (paginated), GET /api/v1/creators/{slug}backend/routers/videos.py— GET /api/v1/videos (paginated, optional creator filter)backend/requirements.txt— Python dependencies for FastAPI, SQLAlchemy, asyncpg, etc.alembic.ini— Alembic configuration pointing to async database URLalembic/env.py— Async Alembic migration runneralembic/versions/001_initial.py— Initial migration creating all 7 tables with constraintsalembic/script.py.mako— Alembic migration templatewhisper/transcribe.py— Desktop Whisper transcription script with CLI, batch mode, resumabilitywhisper/requirements.txt— Whisper script Python dependencieswhisper/README.md— Whisper script usage documentationconfig/canonical_tags.yaml— 6 topic categories and 13 genres for tag classificationREADME.md— Project README with architecture, setup, env vars, dev workflowtests/fixtures/sample_transcript.json— 5-segment sample transcript matching Whisper output formatfrontend/package.json— Placeholder React app package.json
+
Knowledge
+KNOWLEDGE.md exists but no entries parsed.
+Captures
+No captures recorded.
+Artifacts
+ +Missing changelogs 7
+| Milestone | Slice | Title |
|---|---|---|
| M009 | S01 | Homepage Hero & Value Proposition |
| M009 | S02 | About Page |
| M009 | S03 | Featured Content & Content Teasers |
| M010 | S01 | Dedicated Sub-Topic Pages |
| M010 | S02 | Related Techniques Cross-Linking |
| and 2 more | ||
Recently completed 32
+| Milestone | Slice | Title | Completed |
|---|---|---|---|
| M008 | S03 | Homepage Cards & Creator Metric Polish | Mar 31, 2026, 05:27 AM |
| M008 | S02 | Trust & Credibility Cleanup | Mar 31, 2026, 05:15 AM |
| M008 | S01 | Fix Key Moment Search Links | Mar 31, 2026, 05:05 AM |
| M007 | S06 | Mobile Viewport Overflow Fix — Technique Pages and Global Content | Mar 30, 2026, 07:49 PM |
| M007 | S05 | Key Moment Card Text Overflow Fix | Mar 30, 2026, 07:42 PM |
Planning
+ +| ID | Milestone | State | Context | Draft | Updated |
|---|---|---|---|---|---|
| M001 | +Chrysopedia Foundation — Infrastructure, Pipeline Core, and Skeleton UI | +undiscussed | ++ | + | + |
| M002 | +M002: Chrysopedia Deployment — GitHub, ub01 Docker Stack, and Production Wiring | +undiscussed | ++ | + | + |
| M003 | +M003: Domain + DNS + Per-Stage LLM Model Routing | +undiscussed | ++ | + | + |
| M004 | +M004: UI Polish, Bug Fixes, Technique Page Redesign, and Article Versioning | +undiscussed | ++ | + | + |
| M005 | +M005: Pipeline Dashboard, Technique Page Redesign, Key Moment Cards | +undiscussed | ++ | + | + |
| M006 | +M006: Admin Nav, Pipeline Log Views, Commit SHA, Tag Polish, Topics Redesign, Footer | +undiscussed | ++ | + | + |
| M007 | +M007: Pipeline Transparency, Auto-Ingest, Admin UX Polish, and Mobile Fixes | +undiscussed | ++ | + | + |
| M008 | +M008: Credibility Debt Cleanup — Broken Links, Test Data, Jargon, Empty Metrics | +undiscussed | ++ | + | + |
| M009 | +Homepage & First Impression | +undiscussed | ++ | + | + |
| M010 | +Discovery, Navigation & Visual Identity | +undiscussed | ++ | + | + |