목적
코디 재현 엔진(Cody Recreation Engine) consumer MVP의 기초를 마련하기 위해 app/cody/ 라우트 도메인과 전역 상태 관리 인프라를 구축한다. 이 이슈는 모든 후속 feature(F2~F6)가 의존할 단일 adapter 모듈 과 canonical 타입 정의 를 정의하며, 화면별 구현 전에 경로·계약·타입을 lock-in 한다.
범위
① 라우트 스캐폴딩 (packages/web/app/cody/)
page.tsx — Upload 진입점
analysis/page.tsx — Analysis 결과 화면
conditions/page.tsx — Conditions 설정 화면
recommended/page.tsx — Recommended Looks 카드 리스트
item/[skuHash]/page.tsx — Item Detail 화면
saved/page.tsx — Saved Looks 보드 (gated)
looks/[slug]/page.tsx — Shop-this-look 공유 페이지 (public read)
② codyStore 구축
위치 : packages/web/lib/stores/codyStore.ts
패턴 : requestStore와 동일한 Zustand 패턴 mirror
flow state: currentStep, codyAnalysis, shopperPreference 등
UI state: isLoading, error, success 상태
actions: setStep, setCodyAnalysis, updatePreference 등
책임 : 5화면(①②③④⑤) 흐름 상태 관리, 저장/복구 기능(localStorage) 포함
③ 단일 adapter 모듈 (F1만 생성, 이후는 import만)
핵심: F1이 유일하게 생성하는 파일. F3/F5/F6/F9는 여기서만 import.
금지 항목 (이 이슈에서 생성 금지):
codyAnalysisAdapter.ts, recommendedLooksAdapter.ts, useCodyMatch.ts, CodyEngineAdapter 등 추가 adapter 파일 절대 금지
각 화면(F2~F6)이 자체 adapter 모듈 생성 금지 — 모두 cody-match.ts import만
④ 부품 재사용
재사용 : DropZone, compressImage, uploadImage utility (기존 코드)
금지 : useImageUpload hook 절대 금지 (내부에서 requestStore + startDetection() 호출 → 옛 detection flow 트리거)
F2에서 새로운 cody upload hook 작성 (codyStore 기반)
⑤ VTON 분리 확인 (smoke test AC)
app/cody/* 및 lib/components/cody/* 는 lib/components/vton, lib/stores/vtonStore, /api/v1/vton 에서 import 0건
/cody/* 전체 경로가 VTON modal/state에 미의존 확인
화면 규격 (wireframes.md §1~§2 기준)
① Upload (P1) — app/cody/page.tsx
역할: 코디 스샷 진입점
컴포넌트: drag-drop + file picker + 카메라 + URL + 검색/둘러보기 보조 버튼
상태: default / uploading ⟳ / error(비패션·저해상도)
전환: 성공 → ②, E3 진입 → ④, E2 진입 → ④
비로그인 가능
② Analysis (P2·P3) — app/cody/analysis/page.tsx
역할: AI 분해 결과 + 스타일 공식 제시
데이터: style{mood[], season[], color_palette[], silhouette[]}, items[], style_formula{must_keep[], replaceable[]}
상태: loading ⟳ / low-confidence (AnalysisQuality 배지) / default
실패: confidence 낮음 → "다시 분석 / 다른 사진" 유도
비로그인 가능
③ Conditions (P4 선택) — app/cody/conditions/page.tsx
역할: 예산·쇼핑몰·핏·성별 설정 (전부 default 있음, 선택)
주의 : fit('오버'/'슬림'/'무관')은 ClientOnlyFitPreference (FE-only, backend ShopperPreference 아님)
상태: default(skippable)만 존재
비로그인 가능
④ Recommended (P5, E2·E3 진입점) — app/cody/recommended/page.tsx
역할: 룩 세트 3~5개 카드(저가/중가/무드정확) + 피드백(👍/👎)
상태: loading / 예산초과 / 빈결과(0건) / 품절auto-swap / affiliate미커버 / default
실패: 위 모두 F5가 소유
비로그인 가능(뷰)
⑤ Item Detail (P6) — app/cody/item/[skuHash]/page.tsx
역할: 아이템 가격·브랜드·유사도·대체이유·구매링크
상태: default / 링크끊김 / 미가입저장(gated)
실패: 링크 끊김 + 미가입 → F6가 소유
비로그인 구매링크 가능, 저장만 gated
(+) Saved (gated) — app/cody/saved/page.tsx
역할: 로그인 유저의 스타일 보드
상태: default(보드 그리드) / empty
실패: empty → 추천 동선
auth : 가입 필수
(+) Shop-this-look (public read) — app/cody/looks/[slug]/page.tsx
역할: SEO 인입 채널 + 공유 링크
상태: public read / 생성은 gated
auth : 뷰 공개, 링크 생성 가입 필요
Acceptance Criteria (검증가능)
라우트 생성 : app/cody/page.tsx, app/cody/analysis/page.tsx, app/cody/conditions/page.tsx, app/cody/recommended/page.tsx, app/cody/item/[skuHash]/page.tsx, app/cody/saved/page.tsx, app/cody/looks/[slug]/page.tsx 7개 파일 생성 (내용은 placeholder OK, 모듈 export 확인)
codyStore 생성 : lib/stores/codyStore.ts 생성, Zustand 패턴, 다음 상태 정의:
currentStep: 'upload'|'analysis'|'conditions'|'recommended'|'item_detail'
codyAnalysis: CodyAnalysis | null
shopperPreference: ShopperPreference | null
isLoading: boolean, error: string | null, success: boolean
actions: setStep, setCodyAnalysis, updatePreference, reset 등
localStorage persist (선택사항 but 권장)
단일 adapter 파일 생성 : lib/api/adapters/cody-match.ts에 다음 타입 정의 (cody-match-stage2 §2 정확히):
type CodyAnalysis = {
items : Array < { category , color , fit , role , importance } > ;
style : { mood [ ] , color_palette [ ] , silhouette [ ] } ;
style_formula: { must_keep [ ] , replaceable [ ] , core_combination ?, substitute_axis ?[ ] } ;
}
type ShopperPreference = {
budget_krw : number ;
retailer_scope : 'kr' | 'global' | 'both' ;
country : string ;
gender : 'male' | 'female' | 'unisex' ;
excluded_brands ?: string [ ] ;
preferred_retailers ?: string [ ] ;
}
type CandidateSKU = {
sku_signature_hash , brand , product , color , category , price_krw ,
primary_image_url , product_url , source_domain , retailer_scope ,
confidence , reason , relaxation_level , image_verification
}
type CodyMatchResult = {
raw_post_id , preference_variant , matched_at , per_role , coverage ,
relaxation_summary , cost_usd , cache_hit
}
type AnalysisQuality = { confidence : number } // FE-only, cody_analysis 아님
type ClientOnlyFitPreference = 'over' | 'slim' | 'any'
entry function: matchCody(analysis, pref): Promise<CodyMatchResult>
mock adapter 생성 : lib/api/adapters/cody-match-mock.ts
CodyAnalysis, ShopperPreference, CodyMatchResult full fixture 포함
모든 필드 present (선택필드도 명시적 초기값)
F7 reconcile 체크리스트용
금지 파일 0개 : 다른 adapter 파일 없음 (find로 확인)
# cody adapter는 cody-match.ts + cody-match-mock.ts 2개만. 기존 non-cody adapter(postDetailToImageDetail.ts 등)는 무관
ls packages/web/lib/api/adapters/cody-match* .ts | wc -l # 결과 2
ls packages/web/lib/api/adapters/cody* [Aa]dapter* .ts 2> /dev/null | wc -l # 결과 0 (codyAnalysisAdapter 등 금지)
부품 재사용 확인 :
VTON 분리 smoke test :
grep -r " vtonStore\|VtonModal\|/api/v1/vton" packages/web/app/cody packages/web/lib/components/cody
# 결과 0 (import 없음)
타입 미러 정확성 : cody-match.ts의 모든 필드가 cody-match-stage2 §2.2 CandidateSKU mock 에서 present
confidence는 per-SKU 매칭점수 (cody_analysis.confidence 아님 확인)
affiliate_url은 catalog_items 소속 (CandidateSKU 아님 확인)
핵심 파일
용도
경로 (packages/web/)
① Upload
app/cody/page.tsx
② Analysis
app/cody/analysis/page.tsx
③ Conditions
app/cody/conditions/page.tsx
④ Recommended
app/cody/recommended/page.tsx
⑤ Item Detail
app/cody/item/[skuHash]/page.tsx
(+) Saved
app/cody/saved/page.tsx
(+) Shop-this-look
app/cody/looks/[slug]/page.tsx
flow store
lib/stores/codyStore.ts
단일 adapter (F1만 생성)
lib/api/adapters/cody-match.ts (types+entry)
mock fixture (F7용)
lib/api/adapters/cody-match-mock.ts
화면 컴포넌트
lib/components/cody/*
mock·real 경계
mock 파일 : lib/api/adapters/cody-match-mock.ts
출처: cody-match-stage2 §2.2 + mvp-scope §3 스키마
용도: F7 (Mock→Real Reconcile) 의 fixture 비교 체크리스트
AC: 모든 필드 full-form 으로 present (선택필드 포함), mvp-scope §3 예시와 일치
real adapter : cody-match.ts entry function
F2~F6 각 화면이 import하는 contract
1차 구현은 mock만. F3/F4/F5/F6 구현 시 real로 대체(backward-compatible import)
의존성
Zustand (기존)
기존 upload utility: DropZone, compressImage, uploadImage
cody-match-stage2 문서 (타입 정의 출처)
mvp-scope (schema 출처)
제약·주의
경로 규칙 (절대 규칙)
❌ 금지: /recreate, app/request, app/(request), app/(consumer), app/cody/screens, app/cody/detail
✅ 허용: 위 표의 정확한 경로만
타입 mirror (누락·발명 금지)
CodyAnalysis의 모든 필드: items[], style{mood[], season[], color_palette[], silhouette[]}, style_formula{must_keep[], replaceable[], core_combination?, substitute_axis?[]}
ShopperPreference: budget_krw, retailer_scope, country, gender, excluded_brands, preferred_retailers (fit 필드 절대 금지, ClientOnlyFitPreference는 FE-only)
CandidateSKU: sku_signature_hash, brand, product, color, category, price_krw, primary_image_url, product_url, source_domain, retailer_scope, confidence, reason, relaxation_level, image_verification (affiliate_url은 catalog_items 소속)
confidence 혼동 금지: CandidateSKU.confidence(매칭점수) ≠ AnalysisQuality.confidence(분석품질)
실패상태 소유권 (각 화면이 자기 것 소유)
① Upload: 비패션·저해상도 → F2가 구현 아님, F2의 업로드 후 validation에서 표시
② Analysis: low-confidence → F3이 소유
④ Recommended: 예산초과·빈결과·품절·미커버·👎 → F5가 소유
⑤ Item Detail: 링크끊김·미가입저장 → F6이 소유
F8 = 위 실패상태 8종 모두 F2~F6에 구현됐는지 검증 (구현 아님, QA만)
VTON 분리 (testable AC)
cody는 vtonStore, VtonModal, /api/v1/vton에서 import 0건
/cody/* 라우트가 VTON 없이 독립 동작 검증
페르소나 & owner
owner : raf (확정 6/11)
Primary=가설 (Aspirational Style Buyer)
구현에서 페르소나 제외, 골격만 안정
참조
wireframes.md : §1(화면 지도) §2(화면별 spec) §3(entry) §4(실패 카탈로그)
mvp-scope.md : §1(golden path) §2(5화면) §3(data schema) §4(비기능)
user-scenarios.md : §1(파이프라인) §3(entry) §4(실패)
cody-match-stage2.md : §2.1(ShopperPreference input) §2.2(CandidateSKU/CodyMatchResult output)
CONTEXT.md : Cody glossary (Composition Variant / Preference Variant / Shopper Preference / Client-only Fit / Analysis Quality)
다음 단계
이 F1 이슈 완료 후:
F2 (Upload impl): codyStore + new upload hook (useImageUpload 금지, DropZone/compressImage 재사용)
F3 (Analysis impl): codyStore + Analysis render
F4~F6 (나머지 화면): cody-match.ts import → layout/logic 구현
F7 (Mock→Real Reconcile): cody-match-mock.ts fixture와 real adapter 비교 체크리스트
F8 (Integration QA): 8종 실패상태 통합 검증
목적
코디 재현 엔진(Cody Recreation Engine) consumer MVP의 기초를 마련하기 위해 app/cody/ 라우트 도메인과 전역 상태 관리 인프라를 구축한다. 이 이슈는 모든 후속 feature(F2~F6)가 의존할 단일 adapter 모듈과 canonical 타입 정의를 정의하며, 화면별 구현 전에 경로·계약·타입을 lock-in 한다.
범위
① 라우트 스캐폴딩 (packages/web/app/cody/)
page.tsx— Upload 진입점analysis/page.tsx— Analysis 결과 화면conditions/page.tsx— Conditions 설정 화면recommended/page.tsx— Recommended Looks 카드 리스트item/[skuHash]/page.tsx— Item Detail 화면saved/page.tsx— Saved Looks 보드 (gated)looks/[slug]/page.tsx— Shop-this-look 공유 페이지 (public read)② codyStore 구축
packages/web/lib/stores/codyStore.ts③ 단일 adapter 모듈 (F1만 생성, 이후는 import만)
핵심: F1이 유일하게 생성하는 파일. F3/F5/F6/F9는 여기서만 import.
lib/api/adapters/cody-match.ts(types + entry function)CodyAnalysis,ShopperPreference,CodyMatchResult,CandidateSKU등ClientOnlyFitPreference = 'over'|'slim'|'any'(FE mock state)AnalysisQuality(FE-only 배지 상태, cody_analysis에 없음)async matchCody(codyAnalysis, preference) → CodyMatchResultlib/api/adapters/cody-match-mock.ts(mock implementation)금지 항목 (이 이슈에서 생성 금지):
codyAnalysisAdapter.ts,recommendedLooksAdapter.ts,useCodyMatch.ts,CodyEngineAdapter등 추가 adapter 파일 절대 금지④ 부품 재사용
DropZone,compressImage,uploadImageutility (기존 코드)useImageUploadhook 절대 금지 (내부에서 requestStore + startDetection() 호출 → 옛 detection flow 트리거)⑤ VTON 분리 확인 (smoke test AC)
app/cody/*및lib/components/cody/*는lib/components/vton,lib/stores/vtonStore,/api/v1/vton에서 import 0건/cody/*전체 경로가 VTON modal/state에 미의존 확인화면 규격 (wireframes.md §1~§2 기준)
① Upload (P1) — app/cody/page.tsx
② Analysis (P2·P3) — app/cody/analysis/page.tsx
③ Conditions (P4 선택) — app/cody/conditions/page.tsx
④ Recommended (P5, E2·E3 진입점) — app/cody/recommended/page.tsx
⑤ Item Detail (P6) — app/cody/item/[skuHash]/page.tsx
(+) Saved (gated) — app/cody/saved/page.tsx
(+) Shop-this-look (public read) — app/cody/looks/[slug]/page.tsx
Acceptance Criteria (검증가능)
라우트 생성:
app/cody/page.tsx,app/cody/analysis/page.tsx,app/cody/conditions/page.tsx,app/cody/recommended/page.tsx,app/cody/item/[skuHash]/page.tsx,app/cody/saved/page.tsx,app/cody/looks/[slug]/page.tsx7개 파일 생성 (내용은 placeholder OK, 모듈 export 확인)codyStore 생성:
lib/stores/codyStore.ts생성, Zustand 패턴, 다음 상태 정의:currentStep: 'upload'|'analysis'|'conditions'|'recommended'|'item_detail'codyAnalysis: CodyAnalysis | nullshopperPreference: ShopperPreference | nullisLoading: boolean,error: string | null,success: booleansetStep,setCodyAnalysis,updatePreference,reset등단일 adapter 파일 생성:
lib/api/adapters/cody-match.ts에 다음 타입 정의 (cody-match-stage2 §2 정확히):matchCody(analysis, pref): Promise<CodyMatchResult>mock adapter 생성:
lib/api/adapters/cody-match-mock.ts금지 파일 0개: 다른 adapter 파일 없음 (find로 확인)
부품 재사용 확인:
useImageUpload사용 안 함 (grep 확인)DropZone,compressImage재사용 확인 (import 존재)VTON 분리 smoke test:
타입 미러 정확성: cody-match.ts의 모든 필드가 cody-match-stage2 §2.2
CandidateSKUmock 에서 present핵심 파일
mock·real 경계
mock 파일:
lib/api/adapters/cody-match-mock.tsreal adapter: cody-match.ts entry function
의존성
제약·주의
경로 규칙 (절대 규칙)
/recreate,app/request,app/(request),app/(consumer),app/cody/screens,app/cody/detail타입 mirror (누락·발명 금지)
실패상태 소유권 (각 화면이 자기 것 소유)
VTON 분리 (testable AC)
/cody/*라우트가 VTON 없이 독립 동작 검증페르소나 & owner
참조
다음 단계
이 F1 이슈 완료 후: