feat(editorial): thumbnail regen button + pipeline error surfacing#526
Merged
Conversation
Admin 매거진 draft 페이지에서 썸네일 단독 재생성 트리거 + 파이프라인 에러
가시화. abc34c2f-... 케이스 같이 generate_thumbnail 노드가 실패해도 status=
draft 로 마감되어 admin 이 원인을 못 봤던 결함 해결.
ai-server
- inbound.proto: RegenThumbnail RPC (article_id, hint) 추가, pb2 재생성
- regen_thumbnail_service.py: ARQ task — layout 로드 → generate_thumbnail
단독 호출 → 결과/에러를 editorial_articles + editorial_article_events 에
기록 (실패해도 thumbnail_url 보존)
- metadata_servicer: RegenThumbnail handler — 즉시 enqueue 후 응답
- worker: regen_thumbnail_job 등록
- generate_thumbnail._build_prompt: 옵션 hint 파라미터 추가, state 의
regen_hint 픽업해서 'ADDITIONAL DIRECTION FROM REVIEWER' 블록 주입
- publish_node: graph state 의 error_log 를 editorial_articles.error_log
jsonb 컬럼에 append (기존 누락 버그 fix — soft-fail 정책은 보존)
api-server
- proto/ai.proto sync + decoded_ai_grpc/client.rs regen_thumbnail wrapper
- POST /api/v1/admin/editorial-articles/{id}/regen-thumbnail 핸들러
- status guard: draft / failed 만 허용
- editorial_article_events INSERT (step=regen_thumbnail, note=requested
+ hint snippet) → gRPC enqueue
web
- useRegenThumbnail mutation hook + proxy route
- ArticleErrors 컴포넌트: error_log jsonb 배열을 빨간 배너로 렌더
- drafts/[id]/page.tsx: 썸네일 헤더에 ⟳ 아이콘 버튼 + 클릭 시 인라인
textarea (선택 hint) + 재생성 트리거. 메인 컬럼 상단에 ArticleErrors 배치
Verification
- ai-server: 실패 경로 E2E 통과 (error_log append + event INSERT + hint
capture), success 경로 SQL 통과 (OpenAI 빌링 한도로 실 호출 검증은 추후)
- api-server: cargo check + fmt --check 통과
- web: typecheck 신규 에러 0건
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
4 tasks
cocoyoon
added a commit
that referenced
this pull request
May 14, 2026
* feat(raw_posts): StarStyle.com adapter (#466) (#481) * feat(raw_posts): StarStyle.com adapter (#466) WordPress SSR fashion 사이트(starstyle.com) 어댑터 추가. wots(#465) 인프라 재사용 — discovery_target='raw_posts' 글로벌 피드 모델, PrelabeledData 로 vision 단계 우회. 핵심 차이: per-item 사진 URL 이 없어 thumbnail_url=None → items_thumbnail processor 가 spots bbox 로 hero crop fallback path 사용. brand/title 분리는 보수적으로 product 통째로 (verify 단계 admin 처리). - adapters/_starstyle_html.py: parse_post / parse_sitemap / decode_skimresources (skim affiliate 디코드) - adapters/starstyle.py: thin httpx wrapper, fetch + sitemap discover - scripts/backfill_starstyle_posts.py: sitemap → JSONL streaming → PostgREST bulk INSERT (wots 미러) - service.rs: parse_starstyle_source — slug-sp{ID} 형식 검증 - admin UI: PLATFORM_FILTERS / PLATFORM_TABS / DiscoveryPipelineCard / URL builder - migration: pipeline_settings starstyle row (모든 cycle OFF) - 24 단위 테스트 추가 (parser + adapter + prelabeled round-trip) Closes #466. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(starstyle): force HTTPS on og:image URL (#466) starstyle 의 og:image 가 http:// 로 노출되는데 admin 은 HTTPS 라 mixed-content 로 이미지가 차단되던 문제. CDN 자체는 HTTPS 정상이라 파서 단계에서 https 강제 변환. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore: retrigger CI with bump label * fix(starstyle-backfill): mirror hero images to R2 before INSERT (#466) (#484) starstyle 의 og:image 는 admin (HTTPS) 에서 직접 hotlink 가 막힌다: - HTTPS 요청 → 301 redirect to HTTP → mixed-content 차단 - HTTP 요청 → User-Agent / Referer 가 비어 있으면 403 백필 시점에 boto3 로 upstream 이미지 다운로드 → R2 (RAW_POSTS_R2_BUCKET) 에 ``starstyle/{shard}/{external_id}.jpg`` 키로 업로드하고, raw_posts.image_url 을 R2 public URL 로 저장한다. admin 은 R2 직접 fetch (HTTPS clean, hotlink 없음) → Vercel image-proxy 우회. - HEAD 로 R2 객체 존재 확인 → 재실행 시 중복 업로드 skip (idempotent) - 다운로드/업로드 실패 시 upstream URL 로 fallback (image_url 그대로) - RAW_POSTS_R2_* env 미설정 시 mirror skip + warning - PostData @DataClass(frozen=True) → frozen 제거 (image_url 교체 위해) 검증: 500 row 재백필, 479/500 R2 mirrored. 샘플 R2 URL HTTPS 200 확인. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(raw_posts): R2 cleanup on delete + storage rename (#466) (#486) * release: starstyle R2 mirror fix (#466) (#485) * feat(raw_posts): StarStyle.com adapter (#466) (#481) * feat(raw_posts): StarStyle.com adapter (#466) WordPress SSR fashion 사이트(starstyle.com) 어댑터 추가. wots(#465) 인프라 재사용 — discovery_target='raw_posts' 글로벌 피드 모델, PrelabeledData 로 vision 단계 우회. 핵심 차이: per-item 사진 URL 이 없어 thumbnail_url=None → items_thumbnail processor 가 spots bbox 로 hero crop fallback path 사용. brand/title 분리는 보수적으로 product 통째로 (verify 단계 admin 처리). - adapters/_starstyle_html.py: parse_post / parse_sitemap / decode_skimresources (skim affiliate 디코드) - adapters/starstyle.py: thin httpx wrapper, fetch + sitemap discover - scripts/backfill_starstyle_posts.py: sitemap → JSONL streaming → PostgREST bulk INSERT (wots 미러) - service.rs: parse_starstyle_source — slug-sp{ID} 형식 검증 - admin UI: PLATFORM_FILTERS / PLATFORM_TABS / DiscoveryPipelineCard / URL builder - migration: pipeline_settings starstyle row (모든 cycle OFF) - 24 단위 테스트 추가 (parser + adapter + prelabeled round-trip) Closes #466. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(starstyle): force HTTPS on og:image URL (#466) starstyle 의 og:image 가 http:// 로 노출되는데 admin 은 HTTPS 라 mixed-content 로 이미지가 차단되던 문제. CDN 자체는 HTTPS 정상이라 파서 단계에서 https 강제 변환. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore: retrigger CI with bump label * fix(starstyle-backfill): mirror hero images to R2 before INSERT (#466) (#484) starstyle 의 og:image 는 admin (HTTPS) 에서 직접 hotlink 가 막힌다: - HTTPS 요청 → 301 redirect to HTTP → mixed-content 차단 - HTTP 요청 → User-Agent / Referer 가 비어 있으면 403 백필 시점에 boto3 로 upstream 이미지 다운로드 → R2 (RAW_POSTS_R2_BUCKET) 에 ``starstyle/{shard}/{external_id}.jpg`` 키로 업로드하고, raw_posts.image_url 을 R2 public URL 로 저장한다. admin 은 R2 직접 fetch (HTTPS clean, hotlink 없음) → Vercel image-proxy 우회. - HEAD 로 R2 객체 존재 확인 → 재실행 시 중복 업로드 skip (idempotent) - 다운로드/업로드 실패 시 upstream URL 로 fallback (image_url 그대로) - RAW_POSTS_R2_* env 미설정 시 mirror skip + warning - PostData @DataClass(frozen=True) → frozen 제거 (image_url 교체 위해) 검증: 500 row 재백필, 479/500 R2 mirrored. 샘플 R2 URL HTTPS 200 확인. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore(release): backend bump ai=1.5.1 api=0.10.1 [skip ci] * feat(raw_posts): R2 cleanup on raw_post delete (#466) raw_post 삭제 시 image_url 이 raw_posts R2 객체이고 verified posts 가 같은 URL 을 참조하지 않으면 R2 객체도 같이 삭제. verify 후에는 posts. image_url 가 raw_post.image_url 그대로 복사되므로 (#333) — verified post 가 참조 중이면 보존해야 깨지지 않는다. 또한 storage 명명을 db 패턴(db / assets_db) 에 맞춤: - storage_client → operation_storage - 신규 RAW_POSTS_R2_* → assets_storage - AppConfig.storage / assets_storage, AppState.operation_storage / assets_storage 일관 매핑. config: - StorageConfig 두 개 (storage / assets_storage), 별도 R2 버킷 wiring - RAW_POSTS_R2_{ACCOUNT_ID, ACCESS_KEY_ID, SECRET_ACCESS_KEY, BUCKET, PUBLIC_URL} service::delete_item: - 시그니처 (assets_db, prod_db, storage, public_url, id) — 4개의 DI - raw_post.image_url 추출 → DELETE 진행 - extract_assets_r2_key 로 R2 key 추출 (public_url prefix strip) - posts.image_url COUNT — 0 이면 storage.delete(key) 호출 - 실패는 best-effort warn (이미 raw_post 삭제는 성공) 테스트: 4개 단위 (key 추출 - prefix/trailing slash/other domain/empty url). solutions/tests.rs 의 AdminSolutionListQuery 빌드에 has_url=None 추가 (unrelated 사전 컴파일 에러 — 본 PR 의 cargo test --lib 통과 위해 보강). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Co-authored-by: decoded-ci <ci@decodedcorp.com> * feat(web): add vton item and post selection * fix(web): use local supabase fallback for posts * fix(web): serve local vton seed images * fix(web): seed local post images for vton qa * docs: design profile tries detail modal * feat(web): add try detail snapshots * fix(raw_posts): preserve vision log thumbnails (#490) Store the judged image URL with vision filter audit rows so Instagram skip decisions can still render admin thumbnails after raw_posts cleanup. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(web): remove missing supabase client export (#491) Co-authored-by: Cursor <cursoragent@cursor.com> * feat(verify): R2 relocate raw → operation at verify time (#466) (#492) 작업 1 (마이그레이션, 1080 row) 후속 — 신규 verify 데이터도 동일한 single-source 컨벤션 (r2.decoded.style/posts/{post_id} / r2.decoded.style/items/{solution_id}) 유지하도록 verify_raw_post 흐름 수정. 기존: raw_post.image_url (assets R2 dev pub URL) 그대로 posts.image_url 복사 → 운영 DB 가 dev 도메인 URL 들고 있는 버그. 새 흐름: 1. prod_txn 진입 전 R2 copy: - post_id 사전 생성 → assets_storage.download(raw_key) → operation_storage.upload("posts/{post_id}") - 각 visible item: solution_id 사전 생성 → items thumbnail 동일 패턴 2. prod_txn: create_post_from_raw / create_solution_for_verify 에 미리 생성한 UUID + 새 operation URL 전달 (시그니처에 명시 인자 추가) 3. assets_txn: raw_post.image_url 도 operation URL 로 갱신 (단일 진실 소스) 4. 두 트랜잭션 commit 후 raw 객체 best-effort delete (warn 만, orphan 허용) 코드 변경: - StorageClient trait 에 `download(key) -> (bytes, Option<content_type>)` 추가 - CloudflareR2Client GetObject 구현, NotFound 매핑 - DummyStorageClient stub 구현 - 신규 `raw_posts/relocate.rs` — relocate_raw_to_operation helper + 4 단위 테스트 - extract_assets_r2_key → pub(crate) 격상 (relocate 가 재사용) - create_post_from_raw 시그니처: post_id, image_url 명시 인자 (내부 Uuid::new_v4 제거) - create_solution_for_verify 시그니처: solution_id 명시 인자 - verify_raw_post: prod_txn 전 R2 copy 단계 + raw cleanup post-commit 실패 처리: - copy 도중 실패 → BadRequest/NotFound/ExternalService Err, prod_txn 미시작 (DB 부작용 0) - 부분 성공 (hero copy OK / item N copy fail) → op bucket 에 hero orphan 잔존 (lifecycle rule / 별도 GC out of scope) - raw delete 실패 → warn 로깅만, verify 자체는 성공 유지 테스트: - 4 단위 (relocate happy/fallback/non-raw-rejection/not-found-propagation) - 24 회귀 통과 (parse_source_identifier_*, verify_*) Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(wiki): remove unknown tags (profile, vton) from profile-tries spec (#494) Wiki lint 가 docs/wiki/schema/tags.md 에 등록 안 된 태그 거부. api / ui 만 남겨 dev→main 머지 차단 해제. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(cost-tracking): Gemini API per-call cost tracking + admin dashboard (#496) * feat(cost-tracking): Gemini API per-call cost tracking + admin dashboard ai-server 의 Gemini 호출 (raw_post 6 + editorial 7) 모두를 호출 시점에 DB 적립 + 어드민 대시보드에서 일/step/model/pipeline 별 spend 가시화. 핵심 결정: - 단가는 코드 하드코딩 없음 — `gemini_pricing` DB 테이블 SOT (SCD-2, effective_from/to). 어드민 UI 에서 inline upsert. - per-call `pricing_snapshot` 보존 — 단가 변경 후에도 과거 cost 재현 가능. - fire-and-forget recorder — 추적이 본 파이프라인 latency/실패에 영향 0. - 실패도 row 적립 (ok=false, error_class) — 안전·정책 차단, quota, parse_error, timeout, no_pricing 등 가시화. - 5분 in-process pricing cache — 어드민 변경 후 최악 5분 stale 허용. DB (`supabase-assets/migrations/20260514140000_gemini_cost_tracking.sql`): - gemini_pricing (SCD-2, 12 seed row — Pro / Flash / Flash-Lite / Flash-Image / grounding) - gemini_usage_events (per-call, BRIN + composite index 시간순) - gemini_spend_daily (view, 대시보드 read-side) ai-server (`src/services/cost_tracking/`): - `track_call(step, model, extract, coro)` — coroutine wrapper. usage_metadata 자동 추출 + estimator (token × DB 단가 × cached 우대) + recorder INSERT. - raw_post scheduler / post_editorial service 에서 `set_context()` 1회 호출 → 이후 모든 Gemini 호출이 raw_post_id / post_id / pipeline 자동 적립. - 13 call site wrap: items/subject/spots/nano_banana(hero+thumbnail)/url_grounded/url_filter (raw_post 6), design_spec/image_analysis/item_search/news_research/editorial/review/celeb_search (editorial 7). api-server (`src/domains/admin/gemini_cost.rs`): - GET /spend/{daily,by-step,by-model,by-pipeline,today,top-raw-posts} - GET / POST /pricing (POST 가 기존 active row close + 새 row insert TX) - DELETE /pricing/{id} (retire) web (`/admin/gemini-cost`): - KPI row (Today / Yesterday / 7d / 30d) - Daily spend AreaChart (7/14/30d period selector) - Spend by step / model / pipeline (가로 Bar) - Top expensive raw_posts (drill-down → /admin/raw-posts/[id]) - Pricing editor (inline upsert + history toggle + retire) - /admin home 에 `<GeminiCostMini>` 카드 + sidebar 새 "Observability" 그룹 검증: - migration syntax check (local DB BEGIN; ... ROLLBACK;) - e2e unit test (scripts/cost_tracking_e2e_test.py) — Pro/Flash/Image/cached/ failure/no-pricing 6 case 모두 단가 계산 정확 (1000 in × \$1.25/M + 500 out × \$10/M = \$0.006250 등 모두 ✓) - 실측 (scripts/cost_probe.py) — 운영 raw_post 1건 풀 파이프라인: fixed \$0.041 + per-item \$0.075 = 3 items \$0.268, 5 items 추정 ~\$0.42 - ai-server import / api-server cargo check / web typecheck + lint 모두 clean Out of scope (별도 트랙): - Cloud Billing Catalog API 자동 sync (Vertex 이전 후) - daily threshold alert (Slack/텔레그램) - 비용 hot path 최적화 (thumbnail/grounded 감축) — 데이터 적립 후 결정 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * test(cost-tracking): real-parser wrap verification script 기존 cost_tracking_e2e_test.py 는 `track_call` 을 직접 호출 (mock coroutine). 이 스크립트는 실제 `SubjectParser` 클래스를 import + 호출 → 그 내부에 박힌 wrap 코드 라인이 작동해서 DB row 가 적립되는지 검증한다. 검증 결과 (운영 Pinterest 이미지 1장, ~$0.0005): step=subject_parser, model=gemini-2.5-flash, pipeline=raw_post, prompt_tokens=813, completion_tokens=98, cost_usd=$0.000489, pricing_snapshot={input_token: 0.0000003, output_token: 0.0000025} → wrap 코드 경로 + context 자동 전파 + 단가 cache lookup + INSERT 모두 실제로 작동함을 확인. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(content-studio): AI-powered content generation pipeline (#498) * feat(content-studio): add admin content studio * feat(content-studio): add llm generation and research design * docs(content-studio): clarify research panel contract * docs(content-studio): add research panel implementation plan * Add content studio research workflow * docs(content-studio): add asset and short-form plan * feat(content-studio): add asset and short-form schemas * feat(content-studio): add asset planning helpers * feat(content-studio): add asset plan api * feat(content-studio): add asset and short-form panels * feat(content-studio): add OpenAI image generation with editorial prompts - Add /api/v1/content/assets/images route (admin-only) that runs OpenAI Image API per asset prompt and uploads results to Supabase Storage. - Switch buildAssetPlan prompts to format-specific editorial / Vogue composition guides (4:5 cover, 9:16 spread, 1:1 portrait, 16:9 thumbnail, 2:3 lookbook). Inject artist + wardrobe references and forbid on-image text or logos. - Add content-studio-assets public storage bucket migration. - AssetPanel: Generate Images button, image preview rendering, Download link per generated asset, failure list surface. * feat(content-studio): inject research keywords + expand allowed domains - buildAssetPlan now passes researchRun into image prompts when useResearchInCopy is on. Prompts cite trend topics from insights and adapt the scene cue based on packet.context (photoshoot, stage, runway, street, studio). - Strengthen FORMAT_COMPOSITION away from solo-portrait bias toward full magazine-spread layouts with environment, props, and intentional negative space for typography. - Expand DEFAULT_ALLOWED_DOMAINS so common Firecrawl results (instagram, pinterest, medium, dazed, i-d, business-of-fashion, etc.) pass the policy filter instead of returning "No allowed research sources were found." Configurable via CONTENT_STUDIO_RESEARCH_ALLOWED_DOMAINS. * feat(content-studio): image-to-image edits + clickable insight evidence Adds OpenAI image-edit endpoint support so generated images preserve the look of the source post. - openai-client: new fetchImageBytesFromOpenAIEdit posts multipart to /v1/images/edits with up to 4 reference images. generateAssetImage now routes to edit endpoint when referenceImageUrls are passed, falling back to plain generation on failure. - /api/v1/content/assets/images route accepts { plan, packet, useReferenceImages }. When useReferenceImages is true, packet sourceImage and detectedItem thumbnails are forwarded as references. - AssetPanel: "Use post images (image-to-image)" toggle (default on) alongside the Generate Images button. - ResearchPanel: insight cards now render evidenceRefs as clickable domain chips linking to the source URL. * feat(content-studio): bake headline into generated image (Instagram cover style) * fix(content-studio): research falls back to top web results if no whitelisted source matches * fix(content-studio): align asset test assertion with editorial prompt structure The image prompt now uses packet.hook (concept) instead of packet.title, matching the editorial magazine prompt format introduced in b55dfb8. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * feat(admin): verify observability — stats page + daily-digest nag (#499) `users.is_admin=true` 사용자별 raw_post verify 카운트 추적 + 어드민 UI + daily-digest 텔레그램 메시지에 통합 (어제 0건 admin 에 부드러운 독촉). ## api-server (`/api/v1/admin/verify-stats`) - cross-DB join — operation DB users.is_admin + assets DB raw_posts.verified_by/at - Returns per-admin (today / yesterday / 7d / 30d) + totals + nag_admin_ids - 어제 0건 → needs_nag=true ## web (`/admin/verify-stats` + home) - 페이지: KPI cards + per-admin table + nag banner - /admin home 작은 카드 (오늘 / 어제 / 7d / nag count) — nag 있으면 amber border - Sidebar Observability 그룹에 항목 추가 ## daily-digest (Telegram cron) - `.github/workflows/daily-digest.yml` — postgresql-client + 2 read-only DSN secret (ASSETS_DATABASE_URL_RO, OPERATION_DATABASE_URL_RO; 없으면 silent skip) - `daily-digest.sh` — psql 2회 (admin list + verified_at GROUP BY) → jq zip → DATA_JSON 에 `admin_verify_stats` / `admin_verify_totals` → Claude Haiku prompt 에 "needs_nag 인 admin 에 부드러운 독촉 톤" 지시 → BODY 에 deterministic verify table fallback (LLM 누락 시에도⚠️ 표시) ## 검증 - api-server `cargo check` + `cargo fmt --check` ✓ - web `bun run lint` ✓ (verify-stats 신규 파일 clean) - daily-digest 스크립트 `bash -n` syntax ✓ + jq zip 로직 fixture test ✓ ## 필요한 후속 작업 - 두 read-only DSN secret 을 GH repo secrets 에 추가 (없으면 verify 섹션만 silent skip, 기존 daily-digest 정상 작동) - 운영팀: read-only DB user 생성 권장 (admin 권한 불필요) Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(content-studio): DB persistence for local dev parity (#510) * feat(seed): add editorial/magazine sample data for local dev post_magazines 2건 + posts 연결로 supabase db reset 시 홈 EditorialMagazine/EditorialCarousel에 샘플 데이터가 표시됨. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(content-studio): add Supabase migration for content_packets/variants tables Local dev parity — mirrors SeaORM m20260507_000001 so tables exist without Rust API when SKIP_DB_MIGRATIONS=1. Includes seed data with one packet and three channel variants (instagram, youtube, x). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * chore(types): regenerate Supabase types with content_packets/variants Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(content-studio): Supabase direct CRUD for local dev without Rust API Add db.ts helper (upsert/list/get packets, upsert variants, update variant status) and wire all content API routes to use Supabase as fallback when API_BASE_URL is absent or upstream returns 5xx. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(content-studio): lower maxDuration to 60s for Vercel Hobby plan limit Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(types): add content_packets/variants types without full typegen Manual type addition to avoid overwriting PRD-only tables (item, post, image) that local dev schema lacks. Replaces reverted full typegen. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(content-studio): destructure auth for proper type narrowing Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(content-studio): destructure auth in POST/GET packets for type narrowing Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(content-studio): destructure admin in approve/reject/generate-variants Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: cast JSONB fields to Json type for Supabase type compatibility Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * feat(editorial): thumbnail regen button + pipeline error surfacing (#526) Admin 매거진 draft 페이지에서 썸네일 단독 재생성 트리거 + 파이프라인 에러 가시화. abc34c2f-... 케이스 같이 generate_thumbnail 노드가 실패해도 status= draft 로 마감되어 admin 이 원인을 못 봤던 결함 해결. ai-server - inbound.proto: RegenThumbnail RPC (article_id, hint) 추가, pb2 재생성 - regen_thumbnail_service.py: ARQ task — layout 로드 → generate_thumbnail 단독 호출 → 결과/에러를 editorial_articles + editorial_article_events 에 기록 (실패해도 thumbnail_url 보존) - metadata_servicer: RegenThumbnail handler — 즉시 enqueue 후 응답 - worker: regen_thumbnail_job 등록 - generate_thumbnail._build_prompt: 옵션 hint 파라미터 추가, state 의 regen_hint 픽업해서 'ADDITIONAL DIRECTION FROM REVIEWER' 블록 주입 - publish_node: graph state 의 error_log 를 editorial_articles.error_log jsonb 컬럼에 append (기존 누락 버그 fix — soft-fail 정책은 보존) api-server - proto/ai.proto sync + decoded_ai_grpc/client.rs regen_thumbnail wrapper - POST /api/v1/admin/editorial-articles/{id}/regen-thumbnail 핸들러 - status guard: draft / failed 만 허용 - editorial_article_events INSERT (step=regen_thumbnail, note=requested + hint snippet) → gRPC enqueue web - useRegenThumbnail mutation hook + proxy route - ArticleErrors 컴포넌트: error_log jsonb 배열을 빨간 배너로 렌더 - drafts/[id]/page.tsx: 썸네일 헤더에 ⟳ 아이콘 버튼 + 클릭 시 인라인 textarea (선택 hint) + 재생성 트리거. 메인 컬럼 상단에 ArticleErrors 배치 Verification - ai-server: 실패 경로 E2E 통과 (error_log append + event INSERT + hint capture), success 경로 SQL 통과 (OpenAI 빌링 한도로 실 호출 검증은 추후) - api-server: cargo check + fmt --check 통과 - web: typecheck 신규 에러 0건 Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore(dx): dev-reset PRD seed + pre-push lint fixes (#525) * chore(dx): dev-reset auto-seeds from PRD when PRD_DB_URL available - seed-from-prod.sh: add --yes/-y flag for non-interactive mode - seed-from-prod.sh: delete editorial_pipeline_settings before import to avoid duplicate key error - Justfile dev-reset: auto-detect PRD_DB_URL from packages/web/.env.local - Justfile dev-reset: graceful fallback when decoded-backend network is absent Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(web): resolve pre-existing lint errors (prettier + eslint) Auto-fix prettier formatting across admin pages and add eslint-disable for untyped Supabase entity table access. Attach cause to firecrawl timeout error. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * chore(web): fix prettier format + add .next.bak to prettierignore Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(api-server): resolve pre-existing clippy errors - Remove duplicate has_url fields in solutions/tests.rs - Fix unnecessary cast in gemini_cost.rs - Allow clippy::disallowed_methods for json!() macro in content_studio Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(api-server): resolve remaining clippy errors for pre-push hook - Allow disallowed_methods for json!() macro in raw_posts handlers - Use next_back() instead of last() on DoubleEndedIterator - Fix doc_lazy_continuation in solutions/spots service - Allow too_many_arguments for create_solution_for_verify - Allow type_complexity for test-only InMemoryStorage Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(api-server): allow too_many_arguments for create_post_from_raw Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(api-server): resolve final clippy errors (println + too_many_arguments) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(api-server): skip dotenvy in test mode to prevent .env.dev interference Config tests that verify default values were failing because dotenvy reloaded .env.dev inside from_env(), overriding remove_var calls. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(api-server): ignore RUSTSEC-2026-0104 for AWS SDK legacy TLS chain Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * chore(release): backend bump ai=1.6.0 api=0.11.0 [skip ci] * fix(daily-digest): use single quotes for UUID array literals in psql (#511) #509 의 verify-stats 섹션이 SQL 오류로 실패. ARRAY["..."] 는 PostgreSQL 에서 identifier (column) 로 해석되어 ARRAY['...']::uuid[] 가 정답. jq 의 join 구분자도 "," → ',' 로 변경. counts_json 빈 문자열 fallback 추가. --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Co-authored-by: decoded-ci <ci@decodedcorp.com> Co-authored-by: thxforall <113906780+thxforall@users.noreply.github.com> Co-authored-by: philippe <rhkr9693@gmail.com> Co-authored-by: Cursor <cursoragent@cursor.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
매거진 draft
abc34c2f-...의 썸네일이 텍스트/워드마크 없이 생성된 케이스를 추적하다 발견한 두 결함을 한 PR 로 해결.generate_thumbnail노드가 실패해도 graph state 의error_log가publish_node에서 DB 에 안 써지고 통째로 버려졌음. 그 결과 status=draft 로 마감되어 매니저가 원인을 못 봤음.What's in
ai-server
inbound.proto:RegenThumbnail (article_id, hint)RPC 추가 + pb2 재생성regen_thumbnail_service.py(신규): ARQ task —editorial_articles.layout_json로드 →generate_thumbnail_node단독 호출 → 결과/에러 persist. 실패 시thumbnail_url보존.metadata_servicer.RegenThumbnail: 즉시 enqueue 후 응답worker:regen_thumbnail_job등록generate_thumbnail._build_prompt: 옵션hint파라미터 — state 의regen_hint가 있으면 "ADDITIONAL DIRECTION FROM REVIEWER" 블록을 프롬프트 최상단에 주입 (패턴:editorial_article_chat/tools.py._build_regenerate_prompt)publish_nodeUPDATE 에error_log컬럼 누적 추가 — graph state 의error_log가 비어있지 않으면 jsonb 배열에 append (soft-fail 정책 유지)api-server
proto/ai.protosync +decoded_ai_grpc::client::regen_thumbnailwrapperPOST /api/v1/admin/editorial-articles/{id}/regen-thumbnail핸들러:draft/failed만 허용 (그 외 400)editorial_article_eventsINSERT (step='regen_thumbnail', note='requested' + hint snippet) → gRPC enqueue → 202 응답web
useRegenThumbnailmutation hook +/api/admin/editorial-articles/[id]/regen-thumbnailproxy routeArticleErrors.tsx(신규):error_logjsonb 배열을 빨간 배너로 표시drafts/[id]/page.tsx: "Thumbnail preview" 헤더에 ⟳ 아이콘 버튼 → 클릭 시 인라인 textarea (선택 hint) + 재생성. 메인 컬럼 상단에<ArticleErrors>배치.Test plan
_persist_success): thumbnail_url UPDATE + event note='ok (hint: ...)'cargo check+cargo fmt --checkbilling_hard_limit_reached로 차단)abc34c2f-...에 hint 없이 / 있이 각각 재생성해서 출력 비교Out of scope
🤖 Generated with Claude Code