Skip to content

feat(cody): (+)Saved 보드 + Shop-this-look 공유 slug (gated) #634

Description

@thxforall

목적

cody recreation engine MVP 의 gated surface 2종 구현:

  1. (+)Saved Looks (app/cody/saved/page.tsx) — 로그인 필수, 유저가 저장한 룩 세트 보드
  2. Shop-this-look (app/cody/looks/[slug]/page.tsx) — 공개 읽기, 룩 생성자 gated 공유 slug

이 두 화면은 wireframes.md §2 (+)surface + mvp-scope.md §2 보조 surface 에서 정의된 피처이며, core 5화면(①②③④⑤) 을 완성한 후 도달 가능한 저장·공유 엔드 포인트다.

범위

화면

# 경로 (packages/web/) 역할 로그인 주요 컴포넌트
Saved app/cody/saved/page.tsx 유저의 저장된 룩 세트 보드 (gated) 필수 보드 그리드 · 빈 상태 안내
Shop-this-look app/cody/looks/[slug]/page.tsx 공개 공유 페이지 (SEO, Growth L2) 선택(뷰) / 필수(생성) 룩 요약 · "내 버전 만들기" CTA · 메타 정보

auth 경계

  • Saved:
    • 뷰: 로그인 필수 (비로그인 → gated modal)
    • DB: user-scoped (자신이 저장한 룩만)
  • Shop-this-look:
    • 뷰: 공개 (비로그인 허용, SEO 인입)
    • 생성/수정: 로그인 필수 (자신이 만든 룩만 공유 가능, 저작권 deferred)

데이터 모델

// User-scoped 저장 (cody_matches 와 별도)
type SavedLook = {
  id: string;                    // PK
  user_id: string;               // user-scoped
  recommended_look_id: string;   // ④ Recommended 의 look set 참조
  title: string;                 // "가을 출근룩"
  notes?: string;                // 유저 추가 메모
  slug: string;                  // Shop-this-look URL 용 (nullable — 공유 전까진 private)
  created_at: timestamp;
  updated_at: timestamp;
};

// Public-readable 공유 페이지
type PublicLookShare = {
  slug: string;                  // PK (SEO-friendly URL)
  creator_user_id: string;       // who created
  look_summary: {                // composition of ④ 룩 세트
    style_mood: string[];        // ["모던", "가을"]
    items: CandidateSKU[];       // from cody-match-stage2 §2.2
    total_price_krw: number;
    similarity_score: number;    // "유사도 88%"
    original_cody_image_url: string; // reference
  };
  created_at: timestamp;
  public_url: string;            // /cody/looks/[slug]
};

중요: cody_matches(§7.2) 는 공용 서비스 캐시이고, saved_looks 는 user-scoped 저장소 — 별도 테이블 필요. 저작권은 deferred(§5), 현재는 "자신이 만든 룩만 공유" 제약으로 skin-deep 처리.

화면별 AC

(+)Saved Looks · P6 저장

┌────────────────────────────┐
│  내 스타일 보드              │
│  ┌──────┐ ┌──────┐         │
│  │ 가을  │ │ 데일리│  …      │
│  │ 출근룩│ │ 캐주얼│         │
│  │ ▦▦▦  │ │ ▦▦▦  │         │
│  └──────┘ └──────┘         │
│  ── empty ──               │
│  저장한 룩이 없어요           │
│  [ 룩 추천받기 → ① ]         │
└────────────────────────────┘
  • 로그인 필수: 비로그인 진입 시 가입 모달(F6 참조)
  • 보드 그리드: 저장된 룩 세트 카드 목록 (3열 모바일 레이아웃)
    • 카드: 썸네일(원본 cody 또는 첫 아이템 이미지) · 제목 · 제작일
    • 탭: 각 카드 클릭 → ④ Recommended 흐름 재방문 또는 ⑤ Item Detail
  • empty 상태: 저장한 룩 0건 → "저장한 룩이 없어요" 안내 + "룩 추천받기" CTA(→ ①)
  • 저장 출처 기록: user_id + recommended_look_id 로 user-scoped
  • 공유 버튼(선택): 카드 우측 하단 "공유" → slug 생성(gated) → Shop-this-look URL 복사 모달

Shop-this-look · 공유 slug

┌────────────────────────────┐
│ 분해된 아이템 요약  (미니)   │  ← 원본 룩 맥락
│ [모던 가을 / 총 17만]       │
│                            │
│ ◇ 검색 결과 4건            │
│ ▸ ────────────────── ▸     │
│  [룩1] [룩2] [룩3] [룩4]   │
│                            │
│ [ 💡 내 예산으로 만들기 → ①] │  ← decoded 추천 동선
│ [ 공유하기 ]                │
└────────────────────────────┘
  • 공개 뷰: 비로그인 허용 (SEO growth loop L2, [[wireframes.md]] §2 (+)Shop-this-look)
  • slug URL: /cody/looks/:slug (SEO-friendly, 정적 페이지 스타일)
  • 룩 요약: 원본 스타일 무드 · 카테고리별 아이템 · 총 가격 · 유사도%
  • 검색 결과: 같은 스타일 근처의 다른 추천 룩 4–5건 (discovery)
  • CTA — "내 버전 만들기": → ① Upload (fresh analysis) 또는 바로 ③ Conditions(같은 style_formula 재사용 옵션)
  • 공유 메타: og:title · og:description · og:image (SNS 공유 카드)
  • 생성 gated: 로그인 필수 (자신이 생성한 룩만, 타인 룩 공유 불가)
  • 권한 체크: creator_user_id 일치 시만 수정/삭제 허용

Acceptance Criteria

  • 보드 조회 권한 검증: app/cody/saved 비로그인 → gated modal(가입/로그인) + 모달 close 시 홈으로 이동
  • 보드 데이터 쿼리: DB(saved_looks) user-scoped 조회, pagination 또는 limit 10
  • 저장된 룩 카드: 원본 이미지(or 첫 아이템) · 제목 · 제작일 · 공유 버튼 포함
  • empty 상태: saved_looks 0건 → "저장한 룩이 없어요" 메시지 + "룩 추천받기"(→ ①) CTA
  • 공유 slug 생성: Saved 카드 우측 "공유" → 로그인 확인 → slug = nanoid(6) 또는 유사 생성 → Shop-this-look URL 복사 버튼
  • slug 충돌 방지: unique constraint saved_looks.slug (공유 전까지 NULL 가능)
  • Shop-this-look 페이지 로드: /cody/looks/[slug] 공개(비로그인) · slug 유효성 검증 · 404 처리(deleted/invalid slug)
  • 룩 요약 표시: 원본 style 무드 · 아이템 목록(top/bottom/footwear/accessory 구분) · 총 가격 · 유사도% · 원본 이미지
  • "내 버전 만들기" CTA: → ① Upload 또는 (선택) ③ Conditions + style_formula 미리 설정해서 ④로 skip
  • 메타 데이터(SEO): <head> 에 og:title/description/image · twitter:card, 동적 생성 (next/head 또는 generateMetadata)
  • 검색 결과 fetch: 같은 style 근처 추천 룩 4–5건 (추천 ranking 재사용, 선택 사항 — F7 reconcile 가능)
  • 타임아웃 처리: Shop-this-look 로드 ≤ 3초, 데이터 미응답 → skeleton + "페이지를 찾을 수 없습니다" fallback
  • 저작권 제약 표시(선택): 공유 페이지 하단 작은 텍스트 "이 룩 세트는 [creator]가 decoded에서 만들었습니다. 저작권 정책은 [ToS]를 참조하세요."

핵심 파일

대상 경로 (packages/web/) 소유
Saved 페이지 app/cody/saved/page.tsx F9 신규
Shop-this-look 페이지 app/cody/looks/[slug]/page.tsx F9 신규
Saved 보드 컴포넌트 lib/components/cody/SavedBoard.tsx F9 신규
Shop-this-look 컴포넌트 lib/components/cody/PublicLookShare.tsx F9 신규
Cody store (F1 생성) lib/stores/codyStore.ts F1 기존, F9 extend (addSavedLook, removeSavedLook, getSavedLooks)
단일 adapter (F1 생성) lib/api/adapters/cody-match.ts F1 기존, F9 import only (new entry 없음)
DB migration 별도 infra 이슈로 분리 saved_looks 테이블+FK는 type:infra + human checkpoint 필요(operating contract). F9는 테이블 존재를 가정하고 FE만 — migration은 F9 범위 아님

금지 경로:

  • app/cody/screens, app/cody/detail, /recreate, app/(request), app/cody/[id]
  • ❌ 새 adapter 파일 (codyAnalysisAdapter.ts, useCodyMatch.ts, recommended/adapter.ts)
  • lib/components/vton/, /api/v1/vton 임포트

mock·real 경계

Contract 출처

Saved Looks 저장:

  • Input: F6(⑤ Item Detail) 에서 "룩 보드에 저장" 클릭
  • Output: DB saved_looks insert, 모달 "저장 완료" → Saved 보드로 이동 옵션
  • Mock: saveLook stub은 cody-match adapter seam과 분리lib/api/cody/saved.ts 별도 모듈에 둔다 (cody-match.ts/cody-match-mock.ts는 추천 contract 전용, 저장 로직 금지)

Shop-this-look 공유:

  • Input: Saved 보드 카드 "공유" 클릭
  • Output: slug 생성 + DB update(saved_looks.slug) + URL 복사 모달
  • Mock: slug 로컬 생성(nanoid(6)) + URL 조립(window.location.origin + '/cody/looks/' + slug)

공개 페이지 로드:

  • Input: /cody/looks/[slug] 진입
  • Output: DB saved_looks lookup (slug PK) → PublicLookShare 구성 → 렌더링
  • Mock: fixture saved_looks[] 배열로 slug 검색

Type mirror (cody-match-stage2 §2.1 재확인):

  • CodyAnalysis — mvp-scope §3 기준, items[].category ∈ {top, bottom, footwear, outerwear, accessory}
  • CandidateSKU — §2.2 전 필드: 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(§7.1) 소속, CandidateSKU 아님 — ⑤ Item Detail 에서 필요하면 catalog_items join
  • fit 분리: ShopperPreference 에 fit 필드 없음. UI 핏(over/slim/any)은 client-only, 백엔드 미전송

의존성

선행 작업

  • F1 (Upload + single adapter cody-match.ts): Saved/Shop-this-look 에서 recommended_look_id 참조
  • F6 (Item Detail): "룩 보드에 저장" CTA 가 Saved 진입 트리거

후행 작업

  • Saved/Shop-this-look 실패상태(미가입 저장 모달 · 404 · slug 충돌)는 F9 자체 소유 — F8 통합 QA 범위는 ①~⑤ 5화면 실패 8종만이며 F9는 F8 deps가 아님

Cross-cutting

  • Auth (기존 Kakao+Google+Apple): Saved gated 권한 체크
  • Metadata extraction (og_extractor): Shop-this-look og:image 등

제약·주의

  1. cody_matches 혼동 금지: cody_matches 는 raw_posts 당 서비스 캐시 (stage2 §7.2). 저장 은 별도 user-scoped saved_looks 테이블 에 만들 것.

  2. 권한 경계:

    • Saved: 로그인 필수 → user context 확인 후 WHERE user_id = $1 쿼리
    • Shop-this-look: 뷰는 공개, 생성/수정/삭제는 creator_user_id == current_user.id 체크
  3. VTON 미포함: lib/components/cody/ 어디도 lib/components/vton/, /api/v1/vton, vtonStore import 금지.

  4. Upload 재사용 금지: Saved → ① 로 갔을 때 기존 useImageUpload 쓰지 말 것 (requestStore 의존). ① 에서는 new cody upload 전용 hook 사용.

  5. slug 생성 정책:

    • 중복 방지: DB unique constraint
    • 길이: nanoid(6) 또는 custom(recommended 4-8char, 하이픈·언더스코어 허용)
    • 일회성 재생성 불가 (공유 링크 고정성 중요)
  6. Meta 동적화: generateMetadata (Next.js) 사용해 og:title=룩 제목, og:description=스타일 무드, og:image=원본 또는 첫 아이템 이미지. 정적 meta tag 금지.

  7. 빈 결과 처리:

    • Saved empty: "저장한 룩이 없어요" + "룩 추천받기" CTA(→ ①)
    • Shop-this-look 404: "이 룩 페이지를 찾을 수 없습니다" + "룩 추천받기" CTA(→ ①)
  8. 성능:

    • Saved 보드 pagination: limit 10–20 (무한스크롤 or 페이지 버튼)
    • Shop-this-look 로드: 데이터 ≤ 3초 (이상 timeout → fallback)
    • 검색 결과(선택): 캐시 재사용 (new API 콜 피하기)

참조

  • [[wireframes.md]] §2(+)Saved·(+)Shop-this-look · §4 빈 상태 카탈로그
  • [[mvp-scope.md]] §2(보조 surface) · §5(저작권 deferred)
  • [[cody-match-stage2.md]] §2.1(ShopperPreference, fit 필드 없음) · §2.2(CandidateSKU 전 필드) · §7.1(affiliate_url 은 catalog_items 소속)
  • [[CONTEXT.md]] Cody glossary — Style Formula · Analysis Quality · Shopper Preference(fit 아님) · Client-only Fit · VTON 분리
  • 기존 코드: F1(codyStore) · F6(gated modal) 참조 구조

Owner: raf (persona gated 확정 6/11)
Status: Ready for implementation
Estimate: 8–12 story points (Saved board + Shop-this-look + DB + auth + meta)

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