Skip to content

feat(cody): 라우트 스캐폴딩 + codyStore + 단일 adapter/타입 모듈 + 부품 재사용 #626

Description

@thxforall

목적

코디 재현 엔진(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.

  • lib/api/adapters/cody-match.ts (types + entry function)

    • cody-match-stage2 §2.1~§2.2의 모든 타입 정의 (누락/발명 금지)
    • CodyAnalysis, ShopperPreference, CodyMatchResult, CandidateSKU
    • client-side only fit preference 타입: ClientOnlyFitPreference = 'over'|'slim'|'any' (FE mock state)
    • analysis quality 타입: AnalysisQuality (FE-only 배지 상태, cody_analysis에 없음)
    • entry function: async matchCody(codyAnalysis, preference) → CodyMatchResult
    • 모든 필드 정의 기준: cody-match-stage2 §2.1(Input) + §2.2(Output) + mvp-scope §3(schema)
  • lib/api/adapters/cody-match-mock.ts (mock implementation)

    • F7 (Mock→Real Reconcile) 체크리스트를 위한 fixture
    • cody-match.ts의 모든 타입을 canonical full-form으로 사용
    • 예: 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 전 필드 포함
    • 실제 구현은 없음 (F7 reconcile 시에만 real 실장)

금지 항목 (이 이슈에서 생성 금지):

  • 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 등 금지)
  • 부품 재사용 확인:

    • F2 upload가 useImageUpload 사용 안 함 (grep 확인)
    • DropZone, compressImage 재사용 확인 (import 존재)
  • 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종 실패상태 통합 검증

Metadata

Metadata

Assignees

No one assigned

    Labels

    codyCody Recreation Engine MVPenhancementNew feature or requestfrontend프론트엔드

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    Status
    Todo

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions