Skip to content

Conversation

@maehwasoo
Copy link
Member

@maehwasoo maehwasoo commented Dec 11, 2025

📌 Summary

  • close [fix] 가구 매핑 수정 및 스낵바 동작 개선 #393

  • 가구 매핑 정확도와 큐레이션 바텀시트(bottom sheet) 안정성을 함께 개선한 변경입니다.

  • Obj365 라벨→도메인 코드 매핑을 재정비하여 오탐/과매핑을 줄였습니다.

  • 감지, 카테고리, 상품 조회를 이미지/그룹 단위 캐시로 안정화해 중복 호출, 캐시 오염, UX 깜빡임을 줄였습니다.

  • 바텀시트의 상태 전이(특히 닫힘/숨김)를 명확히 해 선택 상태 해제 타이밍과 더보기 슬라이드에서의 동작을 개선했습니다.

  • 또한 마이페이지→결과 페이지 이동 시 감지 프리패치를 하고 네비게이션 state 전달로 첫 렌더가 빨라지도록 보강했습니다.

📄 Tasks

  • 가구 매핑 정확도 개선
    • Chair/Bench/Stool의 소파 코드 매핑을 비활성화하여 의자류가 1인 소파로 매핑되는 케이스를 제거했습니다.
    • Couch(Obj365 index: 50)에 대해 TWO_SEATER_SOFA뿐 아니라 SINGLE_SOFA도 함께 반환하도록 수정하였습니다.
    • Cabinet/Shelf 리파인(refine) 카테고리 타입을 가구 전체가 아닌 캐비닛 2차 분류 전용으로 명확히 분리했습니다.
    • Cabinet 리파인 결과를 개발 환경에서 확인하기 위한 디버그 로그를 추가했습니다.
  • 바텀시트 동작 및 UX 안정화
    • 큐레이션 시트 스냅 상태(CurationSnapState)에 hidden을 추가해 “완전 숨김” 상태를 공식 지원했습니다.
      • hidden → --base-y: 100%
      • snapState 타입을 스토어 타입으로 통일
      • body 스크롤 락 조건을 expanded|mid로 제한
    • 다중 이미지 결과(Variant A)에서 “이미지 더보기” 슬라이드에 진입하면 시트를 hidden으로 숨기고, 슬라이드 이탈 시 이전 스냅 상태로 복원하도록 했습니다.
      • 더보기 슬라이드(isImageMoreSlide)에서 setSnapState('hidden')
      • 슬라이드 이탈 시 restoreSheetSnapState()로 복원
    • 더보기 모달과 바텀시트 상태 복원을 함께 관리하도록 정리했습니다.
      • 모달 닫힘/취소 시 시트 상태 복원
      • “새로 만들기” 이동은 navigate(..., { replace: true })로 스택을 정리하고 시트는 collapsed로 수정
    • 핫스팟 선택 해제 시 선택 상태가 너무 빨리 초기화되어 리스트가 먼저 사라지는 문제를 해결했습니다.
  • 감지(inference) 결과 캐시 + 재사용 파이프라인 도입
    • 세션 단위 감지 캐시 저장소를 추가했습니다.
    • 감지 캐시를 안전하게 소비/갱신하기 위한 훅을 추가했습니다.
      • TTL: DETECTION_CACHE_TTL = 30분
      • imageUrl 불일치 또는 TTL 초과 시 캐시 무효 처리
      • 네비게이션으로 전달된 initialEntry가 더 최신이면 store에 반영
    • 결과 페이지 핫스팟 렌더링에서 감지 캐시를 연결했습니다.
      • 추론 완료 시 onInferenceComplete로 캐시에 저장(이후 detectedObjects 포함하여 한 번 더 저장)
    • 핫스팟 생성 훅이 캐시된 detections로도 파이프라인을 실행할 수 있도록 확장했습니다.
  • ONNX 모델 로딩 성능 개선: 영속 캐시(persistent cache) + 워밍업(warmup)
    • ONNX 모델 바이너리를 Cache Storage에 영속 저장하여 재진입 시 네트워크 비용을 줄였습니다.
    • generate 관련 라우트 진입 시 모델을 선로딩(warmup)하도록 확장했습니다.
  • 큐레이션(카테고리/상품) 쿼리 캐시 안정화: 그룹/서명 기반 키 도입
    • 카테고리 쿼리 키를 “감지 코드 배열”이 아니라 정규화된 감지 서명(signature) 기반으로 재설계해 캐시 충돌을 줄였습니다.
      • normalizedDetectedObjects(Set) + detectionSignature = sort().join(',')
      • queryKey 구조를 [KEY, { groupId, imageId, detectionSignature, codes }]로 통일
      • staleTime/gcTime 상향(카테고리: 15분/30분)
    • 상품 쿼리 키도 그룹/이미지/카테고리 식별자를 객체로 묶어 안정화했습니다.
      • queryKey 구조를 [KEY, { groupId, imageId, categoryId }]
      • staleTime/gcTime 상향(상품: 5분/30분)
    • 그룹 단위 진입(= houseId 단위)에서 초기 응답을 빠르게 재사용하기 위해 curation
      cache store를 추가했습니다.
      • groups[groupId].categories에 { response, detectedObjects, detectionSignature, updatedAt } 저장
      • groups[groupId].products[categoryId]에 { response, updatedAt } 저장
      • 그룹 캐시가 있고 detectionSignature가 동일하면 initialData로 hydrate
      • 쿼리 완료 시 그룹 캐시에 저장
    • 카테고리/상품 무효화(invalidate) 로직을 “정밀(predicate 기반)”로 변경하고, 그룹 캐시 스토어도 함께 비우도록 개선했습니다.
      • groupId 유무에 따라 GENERATE_FURNITURE_*_GROUP vs 일반 키 분기
      • 그룹 캐시도 같이 clear하여 “스토어/리액트쿼리 불일치”를 줄임
    • 카테고리별 상품 프리패치를 “순차”에서 “병렬”로 전환하되, dedupe 및 그룹 캐시 존재 시 skip하도록 했습니다.
      • queryClient.getQueryData로 이미 캐시된 쿼리 skip
      • useCurationCacheStore의 그룹 상품 캐시가 있으면 skip
      • 프리패치 queryKey도 [KEY, { groupId, imageId, categoryId }] 형태로 통일
    • 카테고리 로딩 UX를 개선하기 위해 필터 칩 스켈레톤을 추가했습니다.
  • 마이페이지 ↔ 결과 페이지 연동 및 체감 성능 개선
    • 마이페이지에서 생성 이미지 목록을 로드할 때 감지 프리패치를 수행하여 결과 페이지 첫 진입 속도를 개선했습니다.
      • 우선순위: 첫 이미지 immediate, 나머지 background
      • 동시성 제한: MAX_CONCURRENCY = 1 (세마포어)
      • 연속 호출 완화: PREFETCH_DELAY_MS = 120ms
      • CORS 대응: 기본 crossOrigin 로드 실패 시 loadCorsImage 활용
      • 저장: useDetectionCacheStore.setEntry로 processedDetections/hotspots/detectedObjects 적재
        • 목록 렌더 시 프리패치 스케줄
        • 이미지 로드 완료 시 idle 시간에 추가 프리패치(requestIdleCallback 우선)
    • 마이페이지 → 결과 페이지 이동 시 필요한 정보를 location.state로 전달하여 초기 렌더를 보강했습니다.
      • userProfile, initialHistory, cachedDetection를 state로 구성
      • 결과 상세 쿼리의 placeholderData 생성(createImageDetailPlaceholder)
      • location.state를 읽어 forwardedResult/userProfile/initialHistory/cachedDetection 지원
      • 마이페이지 상세 쿼리(useMyPageImageDetail)에 placeholderData를 적용
      • placeholder 상태에서는 슬라이드 번호를 스켈레톤으로 표시
      • userProfile이 전달되면 useMyPageUser 호출을 스킵하여 불필요한 사용자 정보 재조회 감소
    • 마이페이지 이미지 카드에서 skeleton 깜빡임을 줄이기 위해 “이미지 로드 완료 상태”를 sessionStorage로 기억하도록 변경했습니다.
  • 기타 UX/안정화
    • 결과 페이지 슬라이드 번호 영역에 로딩 스켈레톤을 추가했습니다.
    • 찜(zzim) 스낵바 중복 노출을 쿨타임으로 방지했습니다.
      • TOAST_COOLDOWN_MS = 1500 및 useRef 기반 가드
    • 이미지 생성 성공(정상/폴백) 시 마이페이지 이미지 목록 쿼리를 무효화하여 목록이 갱신되도록 했습니다.
    • 마이페이지에서 결과 페이지로 들어온 뒤 “뒤로가기” 동작을 실제 히스토리 스택 기반으로 안정화했습니다.
    • 로그아웃 시 세션 감지 캐시를 초기화하여 사용자 전환 시 잔여 데이터가 남지 않도록 했습니다.
    • 그룹 기반 쿼리 키 상수를 추가했습니다.
      • GENERATE_FURNITURE_CATEGORIES_GROUP
      • GENERATE_FURNITURE_PRODUCTS_GROUP

🔍 To Reviewer

  • 가구 매핑 확인
    • Chair/Bench/Stool이 더 이상 SINGLE_SOFA로 과매핑되지 않는지 확인 부탁드립니다.
    • Couch가 서버 허용 카테고리/칩 상황에 따라 2인/1인으로 자연스럽게 매칭되는지 확인 부탁드립니다.
  • 바텀시트 상태 전이/UX 확인
    • 핫스팟 선택 해제 시, 시트가 닫히는 도중에 리스트가 먼저 사라지지 않고 완전히 닫힌 뒤에만 선택 상태가 해제되는지 확인 부탁드립니다.
    • 다중 이미지(A)에서 마지막 “이미지 더보기” 슬라이드 진입 시 시트가 hidden으로 숨겨지고, 슬라이드 이탈 시 이전 스냅 상태로 정확히 복원되는지 확인 부탁드립니다.
    • 더보기 모달(overlay) 닫힘/확인 시 바텀시트 상태가 의도대로 복원/초기화되는지 확인 부탁드립니다.
  • 감지 캐시/프리패치 동작 확인
    • 마이페이지 → 결과 페이지 이동 시, 이미 프리패치된 이미지라면 ONNX 추론이 재실행되지 않고(또는 최소화되고) 핫스팟이 더 빠르게 노출되는지 확인 부탁드립니다.
    • 세션 감지 캐시 TTL(30분) 및 imageUrl 불일치 시 무효 처리 동작이 안전한지 확인 부탁드립니다.
  • 쿼리 캐시 안정성 확인
    • 카테고리/상품 쿼리키가 groupId + detectionSignature 기반으로 안정화되면서, 이미지 전환/핫스팟 변화 시 캐시가 섞이지 않는지 확인 부탁드립니다.
    • 카테고리 프리패치 병렬화 이후, 불필요한 중복 네트워크 요청이 발생하지 않는지(특히 group cache가 있을 때) 확인 부탁드립니다.
  • 저장소/캐시 리스크 확인
    • ONNX 모델을 Cache Storage(houme-onnx-models-v1)에 저장하므로, 동일 URL로 모델 파일이 교체되는 경우 캐시 무효화 전략(캐시명 bump 또는 URL 버스팅)이 필요한지 검토 부탁드립니다.
    • 감지 캐시가 sessionStorage에 적재되므로(이미지 수가 많은 계정) 저장 용량/성능 영향이 없는지 확인 부탁드립니다.
  • 회귀 체크
    • 로그아웃 후 재로그인 시 감지 캐시가 초기화되어 이전 사용자의 감지 데이터가 남지 않는지 확인 부탁드립니다.

📸 Screenshot

작업한 내용에 대한 스크린샷을 첨부해주세요.

@maehwasoo maehwasoo requested a review from a team as a code owner December 11, 2025 07:16
@coderabbitai
Copy link

coderabbitai bot commented Dec 11, 2025

📝 Walkthrough

Walkthrough

RootLayout에 ONNX 모델 워밍업 훅이 추가되고, 세션/영구 캐시 기반의 검출·모델 캐싱 및 프리패치가 도입되었습니다. 카테고리 타입이 FurnitureCategory에서 CabinetRefinementCategory로 리네임되었고, Result/MyPage 흐름에 detection·group 캐시 및 그룹 쿼리 키가 연동되었습니다.

Changes

Cohort / File(s) 변경 요약
레이아웃 / 라우팅
src/layout/RootLayout.tsx
GENERATE 관련 경로에서 ONNX 모델 사전 로드용 useGenerateWarmup 훅 추가 및 import 정리.
네비게이션 / 히스토리 유틸
src/pages/generate/GeneratePage.tsx, src/shared/utils/history.ts
getCanHistoryGoBack 유틸 추가 및 MyPage 유래 결과에서 뒤로가기/replace 분기 적용.
검출 캐시: 스토어·훅·프라임
src/pages/generate/stores/useDetectionCacheStore.ts, src/pages/generate/hooks/useDetectionCache.ts
Zustand 세션 퍼시스트 detection 캐시 추가, TTL(30분) 적용 useDetectionCache 훅 및 primeDetectionCacheEntry 도입.
프리패치(ONNX) 및 MyPage 연계
src/pages/mypage/hooks/useDetectionPrefetch.ts, src/pages/mypage/utils/resultNavigation.ts
ONNX 기반 사전 추론 훅 추가, MyPage→Result로 초기 히스토리·캐시 전달 상태 빌더 추가.
hotspot / inference 흐름 개선
src/pages/generate/hooks/useFurnitureHotspots.ts, src/pages/generate/pages/result/components/DetectionHotspots.tsx
CORS 이미지 로더, prefetchedDetections 경로(캐시 히트로 inference 우회), onInferenceComplete 콜백, render-metrics 중복 방지; DetectionHotspots에 cachedDetection/groupId 연동.
ONNX 모델 로더: 영구 캐시
src/pages/generate/hooks/useOnnxModel.ts
Cache Storage API 기반 모델 바이너리 영구 캐시(읽기/검증/쓰기/삭제) 도입 및 로드 진행률 재구성.
Result 페이지·이미지 컴포넌트 확장
src/pages/generate/pages/result/ResultPage.tsx, src/pages/generate/pages/result/components/GeneratedImgA.tsx, src/pages/generate/pages/result/components/GeneratedImgB.tsx, src/pages/generate/pages/result/components/GeneratedImg.css.ts
location.state로 userProfile/initialHistory/cachedDetection 전달, GeneratedImgA/B에 detectionCache/userProfile/isSlideCountLoading/groupId 전달, slideNumSkeleton 스켈레톤 추가.
카테고리 타입 리네임·정제 파이프라인
src/pages/generate/utils/cabinetRefinementCategories.ts, src/pages/generate/utils/refineFurnitureDetections.ts, src/pages/generate/constants/furnitureCategoryMapping.ts, src/pages/generate/hooks/furnitureHotspotState.ts
공개·내부 타입 및 상수명 일괄 변경(FurnitureCategory → CabinetRefinementCategory) 및 관련 시그니처·매핑 업데이트.
CurationSheet: 스냅 상태·그룹 캐시·UI
src/pages/generate/pages/result/curationSheet/CurationSheet.tsx, .../CurationSheetWrapper.tsx, .../CurationSheetWrapper.css.ts, src/pages/generate/stores/useCurationCacheStore.ts
CurationSnapState'hidden' 추가, 타입화된 snap 상태 적용, 그룹별 카테고리·제품 캐시 스토어 및 병렬 prefetch, snap 스타일 hidden variant 추가.
Curation UI 개선(토스트 쿨다운·스켈레톤)
src/pages/generate/pages/result/curationSheet/CardProductItem.tsx, src/pages/generate/pages/result/curationSheet/CurationSheet.css.ts
저장 성공 토스트 1500ms 쿨다운 추가, 필터 스켈레톤 칩 스타일·너비 프리셋 추가.
MyPage: 렌더·로드 상태·프리패치 연동
src/pages/mypage/MyPage.tsx, src/pages/mypage/components/card/cardCuration/CardCuration.tsx, src/pages/mypage/components/section/generatedImages/GeneratedImagesSection.tsx, src/pages/mypage/hooks/useMypage.ts
인라인 렌더로 로딩 처리 변경, GeneratedImagesSection에 userProfile 전달, CardCuration에 imageId/isLoaded/onImageLoad 추가, per-image 로드 상태(sessionStorage)와 detection prefetch 연계, React Query 훅명·옵션 정비.
유저 스토어 연동
src/store/useUserStore.ts
유저 clear 시 detection 캐시 초기화 호출 추가.
쿼리 키(그룹) 추가
src/shared/constants/queryKey.ts
그룹 기반 쿼리 키 GENERATE_FURNITURE_CATEGORIES_GROUP, GENERATE_FURNITURE_PRODUCTS_GROUP 추가.
스타일/작은 UI 익스포트
src/pages/generate/pages/result/components/GeneratedImg.css.ts, src/pages/generate/pages/result/curationSheet/CurationSheet.css.ts
slideNumSkeleton, filterSkeletonChip, filterSkeletonChipWidth 등 스켈레톤 스타일 추가.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

  • 추가 검토 권장 포인트:
    • src/pages/generate/hooks/useOnnxModel.ts — Cache Storage 읽기/검증/삭제 흐름, 비-이진(HTML) 응답 검증 및 실패 복구
    • src/pages/generate/stores/useDetectionCacheStore.ts · src/pages/generate/hooks/useDetectionCache.ts — TTL 만료 경계, 동시성(동시 저장/삭제) 시나리오, sessionStorage 직렬화/복구
    • src/pages/generate/hooks/useFurnitureHotspots.ts — prefetchedDetections 경로의 abort/cleanup, onInferenceComplete 타이밍과 부작용 안전성
    • src/pages/generate/pages/result/ResultPage.tsx — location.state 기반 초기 히스토리·placeholder 병합과 외부 fetch 병합 경계
    • 전역 타입 리네임(FurnitureCategory → CabinetRefinementCategory) 일관성(외부 노출/문서) 확인

Suggested labels

🔧 Fix, 🛠️ Refactor, 🤙 경호

Suggested reviewers

  • soyyyyy
  • earl9rey

Pre-merge checks

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 40.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed PR 제목은 [fix] 형식을 따르고 26자로 50자 이내 요구사항을 충족하며, 가구 매핑 수정과 스낵바 동작 개선이라는 실제 변경사항을 명확히 요약합니다.
Description check ✅ Passed PR 설명이 변경사항과 관련되어 있으며, 가구 매핑 수정, 바텀시트 안정화, 감지 캐시 파이프라인, ONNX 모델 성능 개선 등의 작업을 구체적으로 설명하고 있습니다.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link

빌드 결과

빌드 실패 ❌

@github-actions
Copy link

github-actions bot commented Dec 11, 2025

🎨 Storybook 빌드 완료!

📚 Storybook: https://686a831b8e000345a949970a-lhiaeknnef.chromatic.com/
🔍 Chromatic: https://www.chromatic.com/build?appId=686a831b8e000345a949970a&number=715

📊 빌드 정보

  • 빌드 상태: success
  • 테스트된 스토리: 3개
  • 변경된 컴포넌트: 3개

🔍 시각적 변경사항: 0개 발견

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 11

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/pages/mypage/hooks/useMypage.ts (1)

27-34: useMyPageImagesuseMyPageImageDetailcacheTime 옵션을 gcTime으로 변경해야 합니다

현재 프로젝트에서 사용 중인 @tanstack/react-query v5.81.5에서는 cacheTimegcTime으로 리네이밍되었습니다. 라인 32와 56의 cacheTime 옵션이 TypeScript 타입 에러를 유발합니다.

- cacheTime: 30 * 60 * 1000,
+ gcTime: 30 * 60 * 1000,

두 훅 모두 동일하게 수정하세요.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3c47b85 and d603052.

⛔ Files ignored due to path filters (1)
  • .gitignore is excluded by none and included by none
📒 Files selected for processing (28)
  • src/layout/RootLayout.tsx (1 hunks)
  • src/pages/generate/GeneratePage.tsx (2 hunks)
  • src/pages/generate/constants/furnitureCategoryMapping.ts (6 hunks)
  • src/pages/generate/hooks/furnitureHotspotState.ts (1 hunks)
  • src/pages/generate/hooks/useDetectionCache.ts (1 hunks)
  • src/pages/generate/hooks/useFurnitureCuration.ts (2 hunks)
  • src/pages/generate/hooks/useFurnitureHotspots.ts (9 hunks)
  • src/pages/generate/hooks/useOnnxModel.ts (2 hunks)
  • src/pages/generate/pages/result/ResultPage.tsx (6 hunks)
  • src/pages/generate/pages/result/components/DetectionHotspots.tsx (6 hunks)
  • src/pages/generate/pages/result/components/GeneratedImgA.tsx (7 hunks)
  • src/pages/generate/pages/result/components/GeneratedImgB.tsx (4 hunks)
  • src/pages/generate/pages/result/curationSheet/CardProductItem.tsx (4 hunks)
  • src/pages/generate/pages/result/curationSheet/CurationSheet.css.ts (1 hunks)
  • src/pages/generate/pages/result/curationSheet/CurationSheet.tsx (4 hunks)
  • src/pages/generate/pages/result/curationSheet/CurationSheetWrapper.css.ts (1 hunks)
  • src/pages/generate/pages/result/curationSheet/CurationSheetWrapper.tsx (3 hunks)
  • src/pages/generate/stores/useCurationStore.ts (1 hunks)
  • src/pages/generate/stores/useDetectionCacheStore.ts (1 hunks)
  • src/pages/generate/utils/cabinetRefinementCategories.ts (1 hunks)
  • src/pages/generate/utils/refineFurnitureDetections.ts (7 hunks)
  • src/pages/mypage/MyPage.tsx (3 hunks)
  • src/pages/mypage/components/card/cardCuration/CardCuration.tsx (2 hunks)
  • src/pages/mypage/components/section/generatedImages/GeneratedImagesSection.tsx (2 hunks)
  • src/pages/mypage/hooks/useMypage.ts (2 hunks)
  • src/pages/mypage/utils/resultNavigation.ts (1 hunks)
  • src/shared/utils/history.ts (1 hunks)
  • src/store/useUserStore.ts (2 hunks)
🧰 Additional context used
📓 Path-based instructions (10)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (README.md)

**/*.{ts,tsx}: Do not use var; prefer const and use let only when reassignment is required
Prefer destructuring assignment where appropriate
Use template literals for string composition instead of concatenation
Use clear, unabbreviated names for variables and functions (camelCase)
Use arrow functions for function definitions
Function names should be verb-based (get/create/check/handle/is/has/can) reflecting behavior
Types and interfaces must use PascalCase; do not use I/T/Type prefixes or suffixes (except generic T)
Props and State type names must be ComponentNameProps and ComponentNameState; API types use {Resource}Request/{Resource}Response
Use interface for component props/state and API request/response and object shapes; use type for unions, intersections, function signatures, and tuples
Use path aliases (e.g., @apis/user, @components/Button) instead of deep relative paths
When using TanStack Query, use QUERY_KEYS factory rather than hardcoded query keys

Files:

  • src/shared/utils/history.ts
  • src/pages/generate/hooks/furnitureHotspotState.ts
  • src/pages/generate/hooks/useFurnitureCuration.ts
  • src/pages/generate/stores/useCurationStore.ts
  • src/pages/generate/pages/result/curationSheet/CardProductItem.tsx
  • src/pages/generate/GeneratePage.tsx
  • src/pages/generate/pages/result/curationSheet/CurationSheetWrapper.css.ts
  • src/pages/generate/stores/useDetectionCacheStore.ts
  • src/pages/generate/utils/cabinetRefinementCategories.ts
  • src/pages/generate/pages/result/curationSheet/CurationSheet.css.ts
  • src/layout/RootLayout.tsx
  • src/pages/generate/hooks/useDetectionCache.ts
  • src/pages/mypage/MyPage.tsx
  • src/pages/generate/pages/result/components/GeneratedImgB.tsx
  • src/pages/generate/pages/result/curationSheet/CurationSheetWrapper.tsx
  • src/pages/mypage/components/card/cardCuration/CardCuration.tsx
  • src/pages/mypage/utils/resultNavigation.ts
  • src/pages/generate/constants/furnitureCategoryMapping.ts
  • src/pages/generate/hooks/useFurnitureHotspots.ts
  • src/store/useUserStore.ts
  • src/pages/generate/pages/result/components/DetectionHotspots.tsx
  • src/pages/generate/hooks/useOnnxModel.ts
  • src/pages/mypage/hooks/useMypage.ts
  • src/pages/generate/pages/result/components/GeneratedImgA.tsx
  • src/pages/generate/pages/result/curationSheet/CurationSheet.tsx
  • src/pages/generate/pages/result/ResultPage.tsx
  • src/pages/generate/utils/refineFurnitureDetections.ts
  • src/pages/mypage/components/section/generatedImages/GeneratedImagesSection.tsx
src/**/*.ts

⚙️ CodeRabbit configuration file

src/**/*.ts: TypeScript 파일 리뷰 시:

  • 타입/인터페이스는 PascalCase 사용, Type/I/T 접두사·접미사 금지 (제네릭 T 제외)
  • interface는 객체 타입, type은 union/별칭에 사용하는지 확인
  • API Response는 XXXResponse 네이밍 따르는지 확인
  • var 사용 금지, const 우선 사용 확인
  • type-only import 사용(import type), 불필요한 런타임 의존성 제거
  • satisfies 연산자 활용으로 정확한 타입 보장

Files:

  • src/shared/utils/history.ts
  • src/pages/generate/hooks/furnitureHotspotState.ts
  • src/pages/generate/hooks/useFurnitureCuration.ts
  • src/pages/generate/stores/useCurationStore.ts
  • src/pages/generate/pages/result/curationSheet/CurationSheetWrapper.css.ts
  • src/pages/generate/stores/useDetectionCacheStore.ts
  • src/pages/generate/utils/cabinetRefinementCategories.ts
  • src/pages/generate/pages/result/curationSheet/CurationSheet.css.ts
  • src/pages/generate/hooks/useDetectionCache.ts
  • src/pages/mypage/utils/resultNavigation.ts
  • src/pages/generate/constants/furnitureCategoryMapping.ts
  • src/pages/generate/hooks/useFurnitureHotspots.ts
  • src/store/useUserStore.ts
  • src/pages/generate/hooks/useOnnxModel.ts
  • src/pages/mypage/hooks/useMypage.ts
  • src/pages/generate/utils/refineFurnitureDetections.ts
src/**

⚙️ CodeRabbit configuration file

src/**: 전체 프로젝트 코드 리뷰 시:

기본 규칙

  • 모든 컴포넌트는 반드시 TypeScript로 작성
  • SVG는 ?react 쿼리를 통해 React 컴포넌트로 임포트
  • 스타일은 반드시 Vanilla Extract(.css.ts)로 작성
  • 절대 경로 임포트 사용 (@pages, @shared 등)
  • overlay-kit을 통한 모달 관리 패턴 준수

네이밍 컨벤션

  • 컴포넌트와 클래스는 PascalCase 사용
  • 폴더명은 camelCase 사용
  • 파일명은 camelCase 사용 (컴포넌트 파일 제외)
  • 변수와 함수는 camelCase 사용
  • 상수는 BIG_SNAKE_CASE 사용
  • 모든 타입/인터페이스는 PascalCase, Type/I/T 접두사·접미사 금지 (제네릭 T 제외)
  • Props 타입은 컴포넌트명 + Props 형식
  • API Response 타입은 XXXResponse 형식
  • 함수 네이밍: get/create/check/handle/is/has/can 접두사 활용
  • API 함수는 HTTP메서드 + 명사 형식 (getUserList, postComment)
  • TanStack Query 훅: use + 행위 + 대상 + Query/Mutation (예: useGetUserListQuery)
  • 커스텀 훅 파일명: use*.ts

보안 및 성능

  • 환경 변수는 반드시 import.meta.env 사용
  • API 키나 시크릿을 하드코딩하지 않음
  • React.memo, useMemo, useCallback의 적절한 사용
  • 큰 번들 사이즈 경고 (dynamic import 제안)
  • 템플릿 리터럴 사용 권장 (문자열 조합 시)
  • 구조 분해 할당 적극 활용

커밋 및 브랜치 패턴

  • 커밋 타입: feat, fix, docs, style, refactor, test, chore, design, comment, rename, remove
  • 브랜치 패턴: type/description/#issue-number 형식

Files:

  • src/shared/utils/history.ts
  • src/pages/generate/hooks/furnitureHotspotState.ts
  • src/pages/generate/hooks/useFurnitureCuration.ts
  • src/pages/generate/stores/useCurationStore.ts
  • src/pages/generate/pages/result/curationSheet/CardProductItem.tsx
  • src/pages/generate/GeneratePage.tsx
  • src/pages/generate/pages/result/curationSheet/CurationSheetWrapper.css.ts
  • src/pages/generate/stores/useDetectionCacheStore.ts
  • src/pages/generate/utils/cabinetRefinementCategories.ts
  • src/pages/generate/pages/result/curationSheet/CurationSheet.css.ts
  • src/layout/RootLayout.tsx
  • src/pages/generate/hooks/useDetectionCache.ts
  • src/pages/mypage/MyPage.tsx
  • src/pages/generate/pages/result/components/GeneratedImgB.tsx
  • src/pages/generate/pages/result/curationSheet/CurationSheetWrapper.tsx
  • src/pages/mypage/components/card/cardCuration/CardCuration.tsx
  • src/pages/mypage/utils/resultNavigation.ts
  • src/pages/generate/constants/furnitureCategoryMapping.ts
  • src/pages/generate/hooks/useFurnitureHotspots.ts
  • src/store/useUserStore.ts
  • src/pages/generate/pages/result/components/DetectionHotspots.tsx
  • src/pages/generate/hooks/useOnnxModel.ts
  • src/pages/mypage/hooks/useMypage.ts
  • src/pages/generate/pages/result/components/GeneratedImgA.tsx
  • src/pages/generate/pages/result/curationSheet/CurationSheet.tsx
  • src/pages/generate/pages/result/ResultPage.tsx
  • src/pages/generate/utils/refineFurnitureDetections.ts
  • src/pages/mypage/components/section/generatedImages/GeneratedImagesSection.tsx
src/**/hooks/**/*.ts

⚙️ CodeRabbit configuration file

src/**/hooks/**/*.ts: 커스텀 훅 리뷰 시:

  • 파일명/함수명은 use* 접두사 사용
  • 의존성 배열(exhaustive-deps) 준수, 참조 안정성 유지(함수/객체 메모이제이션)
  • 반환 값 일관성(tuple/object), 에러/로딩 상태 명확화
  • 제네릭 훅은 타입 매개변수 기본값 검토
  • TanStack Query 훅 네이밍: use + 행위 + 대상 + Query/Mutation (예: useGetUserListQuery)

Files:

  • src/pages/generate/hooks/furnitureHotspotState.ts
  • src/pages/generate/hooks/useFurnitureCuration.ts
  • src/pages/generate/hooks/useDetectionCache.ts
  • src/pages/generate/hooks/useFurnitureHotspots.ts
  • src/pages/generate/hooks/useOnnxModel.ts
  • src/pages/mypage/hooks/useMypage.ts
src/**/hooks/use*.ts

📄 CodeRabbit inference engine (README.md)

Custom hook files must be named with use prefix (use*.ts)

Files:

  • src/pages/generate/hooks/useFurnitureCuration.ts
  • src/pages/generate/hooks/useDetectionCache.ts
  • src/pages/generate/hooks/useFurnitureHotspots.ts
  • src/pages/generate/hooks/useOnnxModel.ts
  • src/pages/mypage/hooks/useMypage.ts
**/*.css.ts

📄 CodeRabbit inference engine (README.md)

Style files must use the Vanilla Extract convention with .css.ts extension (e.g., Button.css.ts)

Files:

  • src/pages/generate/pages/result/curationSheet/CurationSheetWrapper.css.ts
  • src/pages/generate/pages/result/curationSheet/CurationSheet.css.ts
src/**/*.css.ts

⚙️ CodeRabbit configuration file

src/**/*.css.ts: Vanilla Extract 스타일 파일 리뷰 시:

  • 디자인 토큰 활용 확인: colorVars 직접 사용, fontStyle() 함수를 통한 폰트 토큰 변환
  • fontStyle() 함수 사용법: ...fontStyle('토큰명') 형태로 스프레드 연산자와 함께 사용
  • recipe와 styleVariants 패턴의 적절한 사용
  • 반응형 스타일 구현 방식
  • 불필요한 중복 스타일 제거
  • 전역 스타일 남용 금지, 컴포넌트 범위 캡슐화

Files:

  • src/pages/generate/pages/result/curationSheet/CurationSheetWrapper.css.ts
  • src/pages/generate/pages/result/curationSheet/CurationSheet.css.ts
src/**/components/**/*.tsx

📄 CodeRabbit inference engine (README.md)

Component file names must use PascalCase and have .tsx extension (e.g., UserProfile.tsx, Button.tsx)

Files:

  • src/pages/generate/pages/result/components/GeneratedImgB.tsx
  • src/pages/mypage/components/card/cardCuration/CardCuration.tsx
  • src/pages/generate/pages/result/components/DetectionHotspots.tsx
  • src/pages/generate/pages/result/components/GeneratedImgA.tsx
  • src/pages/mypage/components/section/generatedImages/GeneratedImagesSection.tsx

⚙️ CodeRabbit configuration file

src/**/components/**/*.tsx: 컴포넌트 파일 리뷰 시:

  • 파일명이 PascalCase인지 확인
  • Props 타입이 '컴포넌트명Props' 형식인지 확인
  • 단일 export는 default export 권장(팀 가이드 우선 적용)
  • 이벤트 핸들러는 handle로 시작하는지 확인
  • 접근성 준수: ARIA 속성, label 연결, 키보드 포커스
  • 성능: memo/useMemo/useCallback 적절 사용
  • ref 전달 시 forwardRef 사용 및 displayName 지정

Files:

  • src/pages/generate/pages/result/components/GeneratedImgB.tsx
  • src/pages/mypage/components/card/cardCuration/CardCuration.tsx
  • src/pages/generate/pages/result/components/DetectionHotspots.tsx
  • src/pages/generate/pages/result/components/GeneratedImgA.tsx
  • src/pages/mypage/components/section/generatedImages/GeneratedImagesSection.tsx
src/pages/**/components/**

⚙️ CodeRabbit configuration file

src/pages/**/components/**: 페이지별 컴포넌트 리뷰 시:

  • 해당 페이지의 도메인 로직과 긴밀한 결합 확인
  • 재사용 가능한 컴포넌트는 shared로 이동 제안
  • 페이지 특화 타입과 상수의 적절한 활용 확인

Files:

  • src/pages/generate/pages/result/components/GeneratedImgB.tsx
  • src/pages/mypage/components/card/cardCuration/CardCuration.tsx
  • src/pages/generate/pages/result/components/DetectionHotspots.tsx
  • src/pages/generate/pages/result/components/GeneratedImgA.tsx
  • src/pages/mypage/components/section/generatedImages/GeneratedImagesSection.tsx
src/**/constants/**

⚙️ CodeRabbit configuration file

src/**/constants/**: 상수 파일 리뷰 시:

  • 모든 상수가 BIG_SNAKE_CASE로 작성되었는지 확인
  • export const 사용 확인
  • 의미있는 이름 사용 여부 확인
  • 리터럴은 as const로 타입 좁히기

Files:

  • src/pages/generate/constants/furnitureCategoryMapping.ts
🧠 Learnings (6)
📚 Learning: 2025-10-05T19:50:38.893Z
Learnt from: soyyyyy
Repo: TEAM-HOUME/HOUME-CLIENT PR: 328
File: src/pages/generate/pages/result/curationSheet/CurationSheet.tsx:76-87
Timestamp: 2025-10-05T19:50:38.893Z
Learning: In TypeScript/React projects using Zustand: when addressing performance issues related to state updates causing unnecessary re-renders, prefer splitting Zustand selectors over wrapping components with React.memo. The user soyyyyy prefers this approach as it addresses the root cause of re-renders at the state subscription level.

Applied to files:

  • src/pages/generate/pages/result/curationSheet/CardProductItem.tsx
  • src/pages/generate/hooks/useFurnitureHotspots.ts
📚 Learning: 2025-10-13T04:31:50.173Z
Learnt from: earl9rey
Repo: TEAM-HOUME/HOUME-CLIENT PR: 339
File: src/pages/mypage/components/history/HistorySection.tsx:57-61
Timestamp: 2025-10-13T04:31:50.173Z
Learning: src/pages/mypage/components/history/HistorySection.tsx 컴포넌트는 더 이상 뷰에서 사용되지 않는 데드 코드입니다. MyPage.tsx에서는 GeneratedImagesSection만 사용하고 있습니다.

Applied to files:

  • src/pages/generate/GeneratePage.tsx
  • src/pages/mypage/MyPage.tsx
  • src/pages/generate/pages/result/ResultPage.tsx
  • src/pages/mypage/components/section/generatedImages/GeneratedImagesSection.tsx
📚 Learning: 2025-09-11T03:55:38.235Z
Learnt from: maehwasoo
Repo: TEAM-HOUME/HOUME-CLIENT PR: 294
File: src/shared/hooks/useScrollToTop.ts:1-1
Timestamp: 2025-09-11T03:55:38.235Z
Learning: React의 스크롤 복원 훅에서 초기 렌더링 시 스크롤 점프 현상이 발생할 경우, useEffect 대신 isomorphic layout effect 패턴(클라이언트에서는 useLayoutEffect, SSR에서는 useEffect)을 사용하여 첫 페인트 플리커를 완화할 수 있습니다.

Applied to files:

  • src/layout/RootLayout.tsx
  • src/pages/generate/pages/result/curationSheet/CurationSheetWrapper.tsx
📚 Learning: 2025-07-14T15:15:29.763Z
Learnt from: soyyyyy
Repo: TEAM-HOUME/HOUME-CLIENT PR: 167
File: src/pages/onboarding/components/steps/step2/floorPlan.tsx:0-0
Timestamp: 2025-07-14T15:15:29.763Z
Learning: React에서 Bottom Sheet 애니메이션 처리 시 setTimeout 대신 useEffect를 사용하여 상태 변경 순서를 보장하는 패턴: openSheet 상태 변경 → useEffect 트리거 → isSheetOpen true 설정 → 애니메이션 시작

Applied to files:

  • src/pages/generate/pages/result/curationSheet/CurationSheetWrapper.tsx
  • src/pages/generate/pages/result/components/GeneratedImgA.tsx
📚 Learning: 2025-10-21T22:07:55.899Z
Learnt from: soyyyyy
Repo: TEAM-HOUME/HOUME-CLIENT PR: 341
File: src/pages/generate/pages/result/curationSheet/CurationSheet.tsx:63-68
Timestamp: 2025-10-21T22:07:55.899Z
Learning: In HOUME-CLIENT project: React keys for furniture product lists should use the stable `furnitureProductId` rather than the ephemeral `p.id` (recommendId). The recommendId changes per recommendation session, while furnitureProductId is a stable identifier for the product itself, enabling React to properly reconcile and reuse DOM elements across different recommendation sets.

Applied to files:

  • src/pages/generate/pages/result/components/DetectionHotspots.tsx
📚 Learning: 2025-07-16T04:24:52.968Z
Learnt from: soyyyyy
Repo: TEAM-HOUME/HOUME-CLIENT PR: 194
File: src/pages/generate/components/result/ResultPage.tsx:20-20
Timestamp: 2025-07-16T04:24:52.968Z
Learning: ResultPage 컴포넌트의 하드코딩된 imageId는 API 연동 작업 시 동적으로 수정될 예정입니다. 사용자는 API 연동과 함께 이러한 임시 값들을 처리하는 것을 선호합니다.

Applied to files:

  • src/pages/generate/pages/result/ResultPage.tsx
🧬 Code graph analysis (19)
src/pages/generate/hooks/furnitureHotspotState.ts (2)
src/pages/generate/utils/refineFurnitureDetections.ts (1)
  • CabinetRefinementCategory (10-10)
src/pages/generate/utils/cabinetRefinementCategories.ts (1)
  • CabinetRefinementCategory (1-7)
src/pages/generate/GeneratePage.tsx (1)
src/shared/utils/history.ts (1)
  • getCanHistoryGoBack (1-7)
src/pages/generate/stores/useDetectionCacheStore.ts (2)
src/pages/generate/types/detection.ts (1)
  • ProcessedDetections (22-25)
src/pages/generate/constants/furnitureCategoryMapping.ts (1)
  • FurnitureCategoryCode (25-25)
src/pages/generate/utils/cabinetRefinementCategories.ts (1)
src/pages/generate/utils/refineFurnitureDetections.ts (2)
  • CabinetRefinementCategory (10-10)
  • CABINET_REFINEMENT_CATEGORY_LABELS (9-9)
src/pages/generate/pages/result/curationSheet/CurationSheet.css.ts (2)
src/shared/styles/tokens/color.css.ts (1)
  • colorVars (3-36)
src/shared/styles/tokens/animation.css.ts (1)
  • animationTokens (26-41)
src/pages/generate/hooks/useDetectionCache.ts (3)
src/pages/generate/types/detection.ts (1)
  • ProcessedDetections (22-25)
src/pages/generate/constants/furnitureCategoryMapping.ts (1)
  • FurnitureCategoryCode (25-25)
src/pages/generate/stores/useDetectionCacheStore.ts (2)
  • DetectionCacheEntry (11-17)
  • useDetectionCacheStore (37-71)
src/pages/generate/pages/result/components/GeneratedImgB.tsx (1)
src/pages/generate/stores/useDetectionCacheStore.ts (1)
  • DetectionCacheEntry (11-17)
src/pages/generate/pages/result/curationSheet/CurationSheetWrapper.tsx (1)
src/pages/generate/stores/useCurationStore.ts (1)
  • CurationSnapState (10-10)
src/pages/mypage/utils/resultNavigation.ts (3)
src/pages/mypage/types/apis/MyPage.ts (4)
  • MyPageImageHistory (13-20)
  • MyPageUserData (4-8)
  • MyPageImageDetail (29-39)
  • MyPageImageDetailData (41-43)
src/pages/generate/stores/useDetectionCacheStore.ts (2)
  • DetectionCacheEntry (11-17)
  • useDetectionCacheStore (37-71)
src/pages/generate/hooks/useDetectionCache.ts (1)
  • primeDetectionCacheEntry (86-92)
src/pages/generate/constants/furnitureCategoryMapping.ts (2)
src/pages/generate/utils/refineFurnitureDetections.ts (1)
  • CabinetRefinementCategory (10-10)
src/pages/generate/utils/cabinetRefinementCategories.ts (1)
  • CabinetRefinementCategory (1-7)
src/pages/generate/hooks/useFurnitureHotspots.ts (2)
src/pages/generate/types/detection.ts (1)
  • ProcessedDetections (22-25)
src/pages/generate/hooks/furnitureHotspotState.ts (2)
  • FurnitureHotspot (7-15)
  • RenderMetrics (36-43)
src/store/useUserStore.ts (1)
src/pages/generate/stores/useDetectionCacheStore.ts (1)
  • useDetectionCacheStore (37-71)
src/pages/generate/pages/result/components/DetectionHotspots.tsx (5)
src/pages/generate/stores/useDetectionCacheStore.ts (1)
  • DetectionCacheEntry (11-17)
src/pages/generate/types/detection.ts (1)
  • ProcessedDetections (22-25)
src/pages/generate/hooks/useDetectionCache.ts (1)
  • useDetectionCache (24-84)
src/pages/generate/hooks/useFurnitureHotspots.ts (2)
  • FurnitureHotspot (41-41)
  • useFurnitureHotspots (116-510)
src/pages/generate/hooks/furnitureHotspotState.ts (1)
  • FurnitureHotspot (7-15)
src/pages/mypage/hooks/useMypage.ts (2)
src/pages/mypage/apis/mypage.ts (1)
  • getMyPageImageDetail (43-52)
src/shared/constants/queryKey.ts (1)
  • QUERY_KEY (1-16)
src/pages/generate/pages/result/components/GeneratedImgA.tsx (7)
src/pages/mypage/types/apis/MyPage.ts (1)
  • MyPageUserData (4-8)
src/pages/generate/stores/useDetectionCacheStore.ts (1)
  • DetectionCacheEntry (11-17)
src/pages/generate/hooks/useFurnitureCuration.ts (2)
  • useOpenCurationSheet (142-147)
  • useSheetSnapState (104-114)
src/pages/generate/stores/useCurationStore.ts (1)
  • CurationSnapState (10-10)
src/pages/mypage/hooks/useMypage.ts (1)
  • useMyPageUser (14-22)
src/pages/generate/utils/analytics.ts (3)
  • logResultImgClickBtnMoreImg (67-71)
  • logResultImgClickMoreModalBack (84-88)
  • logResultImgClickMoreModalMakeNew (101-107)
src/routes/paths.ts (1)
  • ROUTES (4-17)
src/pages/generate/pages/result/curationSheet/CurationSheet.tsx (3)
src/shared/apis/queryClient.ts (1)
  • queryClient (5-18)
src/shared/constants/queryKey.ts (1)
  • QUERY_KEY (1-16)
src/pages/generate/apis/furniture.ts (1)
  • getGeneratedImageProducts (26-34)
src/pages/generate/pages/result/ResultPage.tsx (6)
src/pages/mypage/types/apis/MyPage.ts (2)
  • MyPageImageHistory (13-20)
  • MyPageUserData (4-8)
src/pages/generate/types/generate.ts (3)
  • GenerateImageData (34-42)
  • GenerateImageAResponse (45-51)
  • GenerateImageBResponse (54-58)
src/pages/generate/stores/useDetectionCacheStore.ts (1)
  • DetectionCacheEntry (11-17)
src/pages/mypage/utils/resultNavigation.ts (1)
  • createImageDetailPlaceholder (50-54)
src/pages/generate/hooks/useGenerate.ts (1)
  • useGetResultDataQuery (66-75)
src/pages/mypage/hooks/useMypage.ts (1)
  • useMyPageImageDetail (48-59)
src/pages/generate/utils/refineFurnitureDetections.ts (1)
src/pages/generate/utils/cabinetRefinementCategories.ts (2)
  • CabinetRefinementCategory (1-7)
  • CABINET_REFINEMENT_CATEGORY_LABELS (20-29)
src/pages/mypage/components/section/generatedImages/GeneratedImagesSection.tsx (5)
src/pages/mypage/types/apis/MyPage.ts (2)
  • MyPageUserData (4-8)
  • MyPageImageHistory (13-20)
src/pages/mypage/hooks/useMypage.ts (1)
  • useMyPageImages (27-35)
src/pages/mypage/utils/analytics.ts (1)
  • logMyPageClickBtnImgCard (46-48)
src/pages/mypage/utils/resultNavigation.ts (1)
  • buildResultNavigationState (33-48)
src/routes/paths.ts (1)
  • ROUTES (4-17)
🪛 Biome (2.1.2)
src/pages/mypage/components/card/cardCuration/CardCuration.tsx

[error] 42-43: Static Elements should not be interactive.

To add interactivity such as a mouse or key event listener to a static element, give the element an appropriate role value.

(lint/a11y/noStaticElementInteractions)


[error] 42-43: Enforce to have the onClick mouse event with the onKeyUp, the onKeyDown, or the onKeyPress keyboard event.

Actions triggered using mouse events should have corresponding keyboard events to account for keyboard-only navigation.

(lint/a11y/useKeyWithClickEvents)

🪛 GitHub Actions: CI/CD Pipeline
src/pages/generate/stores/useDetectionCacheStore.ts

[error] 66-66: Type 'PersistStorage | undefined' is not assignable to type 'PersistStorage<{ images: Record<number, DetectionCacheEntry>; }> | undefined'.

🪛 GitHub Check: build
src/pages/generate/stores/useDetectionCacheStore.ts

[failure] 66-66:
Type 'PersistStorage | undefined' is not assignable to type 'PersistStorage<{ images: Record<number, DetectionCacheEntry>; }> | undefined'.

src/pages/mypage/hooks/useMypage.ts

[failure] 56-56:
No overload matches this call.


[failure] 32-32:
No overload matches this call.

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: chromatic
🔇 Additional comments (63)
src/pages/generate/pages/result/curationSheet/CardProductItem.tsx (1)

1-1: 스낵바 중복 방지 메커니즘이 잘 구현되었어요.

useRef를 활용한 쿨다운 패턴이 적절하며, 상수 네이밍(TOAST_COOLDOWN_MS)도 코딩 가이드라인(BIG_SNAKE_CASE)을 준수했어요. 빠른 연속 클릭 시 스낵바가 중복 노출되는 문제를 효과적으로 해결하는 방식이에요.

Also applies to: 30-31, 43-43

src/shared/utils/history.ts (1)

1-7: 구현이 깔끔하고 적절해요.

브라우저 환경 체크와 history.state null 가드가 잘 처리되어 있어요. idx 속성은 일부 라우터(react-router 등)에서 히스토리 위치를 추적하는 데 사용되므로 적절한 접근이에요.

다만, history.state의 타입이 any이므로 타입 안전성을 위해 아래와 같이 명시적 타입 가드를 고려해 볼 수 있어요:

 export const getCanHistoryGoBack = () => {
   if (typeof window === 'undefined') return false;
-  const historyState = window.history.state;
+  const historyState = window.history.state as { idx?: unknown } | null;
   if (!historyState) return false;
   const idx = typeof historyState.idx === 'number' ? historyState.idx : 0;
   return idx > 0;
 };
src/pages/generate/hooks/useFurnitureCuration.ts (2)

48-58: 캐싱 시간 증가가 적절해요.

카테고리 데이터의 staleTime을 15분으로 늘린 것은 성능 최적화에 도움이 돼요. 다만 queryKeydetectedObjects 배열이 포함되어 있어서, 배열 참조가 변경되면 캐시 미스가 발생할 수 있어요.

detectedObjects가 매 렌더링마다 새 배열 참조를 생성하지 않는지 확인이 필요해요. 만약 그렇다면 useMemo로 안정화하거나, 배열을 직렬화한 키를 사용하는 것을 고려해 주세요.


90-97: 제품 쿼리 캐싱도 적절해요.

staleTime 5분, gcTime 30분은 제품 데이터의 특성에 맞는 합리적인 설정이에요.

src/pages/generate/utils/cabinetRefinementCategories.ts (1)

1-33: 타입과 상수 정의가 잘 구성되어 있어요.

  • CabinetRefinementCategory 타입은 PascalCase 네이밍 컨벤션을 따르고 있어요.
  • as const satisfies 패턴을 사용해 타입 안전성과 리터럴 타입 추론을 모두 확보했어요.
  • CABINET_REFINEMENT_CATEGORY_SET은 빠른 조회를 위한 적절한 최적화예요.

코딩 가이드라인에 잘 맞는 구현이에요.

src/pages/generate/hooks/useFurnitureHotspots.ts (6)

33-39: 옵션 타입 정의가 적절해요.

FurnitureHotspotOptions 타입은 PascalCase 네이밍을 따르고, 선택적 속성으로 적절히 정의되어 있어요. 코딩 가이드라인에 맞아요.


43-57: 렌더 메트릭 비교 함수가 잘 구현되어 있어요.

불필요한 dispatch를 방지하기 위한 얕은 비교 함수예요. 모든 필드를 명시적으로 비교하는 것이 정확하고 안전해요.


136-142: 콜백 ref 패턴이 적절해요.

inferenceCompleteRef를 사용해 최신 콜백을 유지하면서 의존성 배열 증폭을 방지하는 패턴이에요. Zustand 셀렉터 분리를 선호하는 학습 내용과 일관된 성능 최적화 접근이에요.


294-305: 캐시 히트 경로 구현이 잘 되어 있어요.

prefetchedDetections가 있으면 추론을 건너뛰고 캐시된 결과를 사용하는 로직이 명확해요. inferenceCompleteRef.current를 통해 콜백도 올바르게 호출되고 있어요.


374-386: 의존성 배열이 완전해요.

prefetchedDetections가 의존성 배열에 올바르게 포함되어 있어 캐시 상태 변경 시 적절히 반응해요.


150-151: void _omitted 패턴에 대한 개선 제안이에요.

_omitted를 명시적으로 void로 처리하는 대신, 구조 분해 할당에서 언더스코어 prefix를 사용하면 린터가 자동으로 무시해요.

-    const { imageUrl: _omitted, ...restPayload } = payload ?? {};
-    void _omitted; // 이미지 URL은 로그에서 제외
+    const { imageUrl: _unusedUrl, ...restPayload } = payload ?? {};

또는 직접 rest 연산자만 사용하는 것도 가능해요:

const { imageUrl, ...restPayload } = payload ?? {};
// imageUrl은 사용하지 않음
⛔ Skipped due to learnings
Learnt from: CR
Repo: TEAM-HOUME/HOUME-CLIENT PR: 0
File: README.md:0-0
Timestamp: 2025-10-09T08:24:59.296Z
Learning: Applies to **/*.{ts,tsx} : Prefer destructuring assignment where appropriate
src/pages/generate/hooks/useOnnxModel.ts (4)

41-51: 캐시 스토리지 헬퍼 함수 구현 확인

Cache Storage API를 활용한 ONNX 모델 영속 캐싱이 잘 구현되어 있어요. hasCacheStorageSupport 함수가 브라우저 환경과 caches API 지원 여부를 정확히 확인하고 있고, resolveModelCacheKey가 상대 경로를 절대 URL로 변환하여 일관된 캐시 키를 보장해요.


67-84: 캐시 저장 시 ArrayBuffer 복제 확인

buffer.slice(0)으로 ArrayBuffer를 복제한 후 저장하는 것은 좋은 방어적 코딩이에요. 원본 버퍼가 다른 곳에서 사용되거나 detach될 가능성을 방지해요.


96-118: 모델 바이너리 검증 로직 우수

ensureModelBufferIsBinary가 HTML/오류 페이지를 감지하여 잘못된 캐시 데이터를 방지하고, fetchModelBinary가 Content-Type과 실제 콘텐츠를 이중 검증하는 방어적 구현이에요. CDN이나 서버가 오류 페이지를 반환하는 엣지 케이스를 잘 처리해요.


139-154: 캐시 통합 흐름 LGTM

캐시 읽기 → 검증 → 네트워크 폴백 흐름이 올바르게 구현되어 있어요. 캐시된 데이터가 유효하지 않으면 삭제 후 재다운로드하는 로직이 적절해요.

src/layout/RootLayout.tsx (1)

32-41: 경로 매칭 로직 LGTM

pathname === prefix || pathname.startsWith(\${prefix}/`)패턴이 정확한 경로 매칭과 하위 경로를 모두 처리해요.useEffect의존성 배열도[location.pathname]`으로 적절해요.

src/pages/generate/constants/furnitureCategoryMapping.ts (3)

7-7: 타입 임포트 변경 확인

FurnitureCategory에서 CabinetRefinementCategory로의 타입 변경이 import type 구문을 사용하여 올바르게 적용되었어요. 코딩 가이드라인의 type-only import 규칙을 준수해요.


97-106: 캐비닛 카테고리 매핑 LGTM

CABINET_CATEGORY_TO_CODE 매핑이 Partial<Record<CabinetRefinementCategory, FurnitureCategoryCode>>로 적절히 타입 지정되어 있어요. upperCabinet이 주석 처리되어 2차 cabinet 분류에서 비활성화된 것이 PR 목표와 일치해요.


79-94: OBJ365 매핑 주석 문서화 우수

각 OBJ365 인덱스에 대한 한글 주석이 매핑 의도를 명확히 설명해요. 비활성화된 매핑(Chair, Bench, Stool)도 주석으로 남겨두어 추후 참조가 용이해요.

src/pages/generate/utils/refineFurnitureDetections.ts (5)

1-10: 타입 임포트 및 재내보내기 LGTM

CabinetRefinementCategoryCABINET_REFINEMENT_CATEGORY_LABELS의 임포트 및 재내보내기가 올바르게 구성되어 있어요. 모듈 경계를 깔끔하게 유지하면서 필요한 타입을 외부에 노출해요.


51-58: RefinedFurnitureDetection 인터페이스 타입 일관성 확인

refinedLabel, probabilities, contributions 속성이 모두 CabinetRefinementCategory 타입으로 일관되게 업데이트되었어요.


285-296: 개발 환경 전용 디버그 로깅 확인

import.meta.env.DEV 조건으로 프로덕션 빌드에서는 로깅이 제외되어요. 디버깅에 유용한 정보(refinedLabel, confidence)를 포함하고 있어요.


458-469: 타입 단언 사용 적절

probabilitiescontributions 객체 초기화 시 타입 단언이 필요한 상황이에요. 모든 CabinetRefinementCategory 키가 초기화되어 런타임 안정성이 보장돼요.


510-519: 라벨 매핑 일관성 LGTM

CABINET_REFINEMENT_CATEGORY_LABELS[bestLabel].en을 통해 classNamerefinedLabelEn을 설정하는 방식이 일관되고 타입 안전해요.

src/pages/generate/hooks/furnitureHotspotState.ts (2)

4-4: 타입 임포트 경로 확인

CabinetRefinementCategory@pages/generate/utils/refineFurnitureDetections에서 임포트하고 있어요. 이 파일이 해당 타입을 재내보내기(re-export)하므로 유효하지만, 원본 소스인 cabinetRefinementCategories에서 직접 임포트하는 것도 고려해볼 수 있어요.


7-15: FurnitureHotspot 타입 업데이트 LGTM

refinedLabel 속성이 CabinetRefinementCategory로 업데이트되어 전체 코드베이스의 타입 일관성을 유지해요. 선택적 속성(?)이 올바르게 유지되어 있어요.

src/pages/generate/stores/useCurationStore.ts (1)

10-10: LGTM!

CurationSnapState 타입에 'hidden' 값이 추가되었어요. type을 사용한 union 정의가 코딩 가이드라인에 부합해요.

'hidden' 상태가 setSheetSnapState 액션에서 특별한 처리가 필요한지 확인해 주세요. 현재 'collapsed'만 명시적으로 처리하고 있어요 (Line 211).

src/pages/generate/pages/result/curationSheet/CurationSheetWrapper.css.ts (1)

51-53: LGTM!

snapStyleshidden 변형이 추가되었어요. --base-y: '100%'로 시트를 화면 아래로 완전히 숨기는 방식이 적절해요. 기존 collapsed/mid/expanded 패턴과 일관성 있게 구현되었어요.

src/pages/generate/GeneratePage.tsx (1)

28-34: LGTM!

히스토리 스택 존재 여부를 확인 후 네비게이션하는 방어적 패턴이 적절해요. 마이페이지에서 직접 접근 시 navigate(-1) 실패를 방지해요.

src/pages/generate/pages/result/curationSheet/CurationSheet.css.ts (1)

34-55: LGTM!

스켈레톤 칩 스타일이 디자인 토큰(colorVars, animationTokens)을 활용해 일관성 있게 구현되었어요. styleVariants로 너비 프리셋을 제공하는 패턴도 재사용성이 좋아요.

src/pages/mypage/MyPage.tsx (3)

65-66: LGTM!

null 병합 연산자와 옵셔널 체이닝을 활용한 안전한 기본값 처리가 적절해요.


89-89: 타입 일관성 확인 필요

GeneratedImagesSectionuserProfile prop이 MyPageUserData | null을 기대하는데, userDataMyPageUserData | undefined 타입일 수 있어요. 타입 불일치를 방지하기 위해 명시적으로 null로 변환하는 것을 권장해요.

<GeneratedImagesSection userProfile={userData ?? null} />

92-92: 로딩 UX 동작 확인 필요

이전에는 로딩 중일 때 전체 화면이 로딩 상태였지만, 현재는 페이지 구조가 먼저 렌더링되고 하단에 로딩 오버레이가 표시돼요. 이로 인해 userData가 로딩 중일 때 기본값("사용자", 0 크레딧)이 잠깐 보이다가 실제 데이터로 교체되는 깜빡임이 발생할 수 있어요.

의도된 UX 변경인지 확인이 필요해요. 만약 깜빡임을 방지하려면 early return 패턴 유지를 권장해요.

src/pages/generate/stores/useDetectionCacheStore.ts (1)

1-71: 캐시 설계 잘 구현되었어요

sessionStorage 기반 persist로 세션 단위 캐싱을 구현했고, SSR 환경 대응도 적절해요. TTL 관리는 useDetectionCache 훅에서 처리하는 구조가 명확해요.

src/pages/generate/pages/result/curationSheet/CurationSheet.tsx (3)

106-113: 상태 체크 로직 개선 확인

hidden 상태를 명시적으로 체크하여 시트 동작이 더 명확해졌어요.


124-139: 병렬 prefetch로 성능 개선

순차 프리패치에서 병렬 프리패치로 변경하여 초기 반응 속도가 개선되었어요. prefetchedRef로 중복 요청도 잘 방지하고 있어요.

다만 개별 prefetch의 staleTime: 30 * 1000(30초)이 전역 설정(5분)과 차이가 있어요. 의도된 차이인지 확인이 필요해요.


278-286: 스켈레톤 UI 개선

빈 카테고리 상태에서 스켈레톤 칩을 표시하여 로딩 UX가 개선되었고, aria-hidden으로 접근성도 고려했어요.

src/pages/generate/pages/result/components/GeneratedImgB.tsx (1)

32-32: 캐시 통합 패턴 적절해요

detectionCache prop을 통해 per-image 캐싱을 지원하도록 확장했고, null 안전 처리도 잘 되어 있어요. GeneratedImgA와 일관된 패턴이에요.

Also applies to: 39-39, 68-72, 81-81

src/pages/mypage/components/section/generatedImages/GeneratedImagesSection.tsx (3)

28-38: 로드 상태 추적 구현 우수해요

sessionStorage를 활용한 이미지 로드 상태 추적이 SSR guard와 에러 처리까지 적절히 구현되어 있어요.


56-65: 이미지 로드 핸들러 효율적이에요

useCallback과 중복 업데이트 방지 로직으로 성능이 최적화되어 있고, sessionStorage 동기화도 적절해요.


40-54: 네비게이션 상태 빌딩 패턴 일관성 있어요

buildResultNavigationState를 활용한 네비게이션 상태 구성이 체계적이고, userProfile ?? null 패턴으로 타입 일관성도 유지했어요.

src/pages/generate/pages/result/components/GeneratedImgA.tsx (4)

83-87: 조건부 데이터 페칭 효율적이에요

userProfile이 있을 때는 fetch를 건너뛰고, null 병합 연산자로 안전하게 크레딧을 파생하는 패턴이 효율적이에요.


122-152: 모달 상태 복원 로직 구현 우수해요

모달 닫힐 때 이전 snap state를 복원하는 로직이 잘 구현되어 있어요. useRef로 상태를 추적하고, closeModal 헬퍼로 조건부 복원을 제어하는 패턴이 깔끔해요. "이미지 새로 만들기" 시 복원을 건너뛰는 로직도 UX를 고려한 설계예요.


218-238: per-image 캐싱 패턴 일관성 있어요

각 이미지별로 캐시를 조회하여 DetectionHotspots에 전달하는 패턴이 GeneratedImgB와 일관되게 구현되어 있고, null 안전 처리도 적절해요.


130-136: snap state 저장 로직 적절해요

모달 열 때 현재 상태가 hidden이 아닐 때만 저장하여, hidden 상태가 복원되지 않도록 하는 가드 로직이 합리적이에요.

src/pages/generate/hooks/useDetectionCache.ts (3)

36-40: 만료 체크 로직 견고해요

imageUrl 불일치를 만료로 간주하여 이미지 변경을 감지하고, TTL 기반 시간 만료도 체크하는 이중 검증이 안전해요.


42-49: initialEntry 주입 로직 효율적이에요

updatedAt 비교로 최신 데이터만 반영하고, imageUrl 일치도 체크하여 불필요한 업데이트를 방지하는 로직이 효율적이에요.


86-92: 컴포넌트 외부 캐시 주입 패턴 적절해요

getState()를 활용해 React 컴포넌트 외부에서도 캐시를 주입할 수 있도록 한 헬퍼 함수가 유용해요. buildResultNavigationState에서 사용되는 패턴이 명확해요.

src/pages/mypage/utils/resultNavigation.ts (2)

33-48: 네비게이션 상태 빌딩 로직 체계적이에요

detection cache를 조회하고 존재하면 primeDetectionCacheEntry로 갱신하는 흐름이 명확해요. 네비게이션 상태를 일관되게 구성하는 유틸리티 함수가 잘 분리되어 있어요.


23-31: 스켈레톤 데이터 변환 로직 적절해요

MyPageImageHistoryMyPageImageDetail 스켈레톤으로 변환하는 헬퍼 함수가 명확하고, 기본값 설정도 합리적이에요.

Also applies to: 50-54

src/pages/generate/pages/result/ResultPage.tsx (3)

94-102: forwardedDetectionMap 메모이제이션 적절해요.

initialImageIdforwardedDetection이 변경될 때만 재계산되도록 useMemo를 잘 사용했어요.


115-121: 조건부 fetch 플래그 분리가 명확해요.

shouldFetchExternalResultshouldFetchMypageDetail로 fetch 조건을 분리해 가독성과 유지보수성이 좋아졌어요.


378-386: Props 전달은 올바르게 정의되어 있습니다.

두 컴포넌트 모두 필요한 props 인터페이스를 이미 제대로 정의하고 있습니다:

  • GeneratedImgA: userProfile?: MyPageUserData | null, detectionCache?: Record<number, DetectionCacheEntry> | null
  • GeneratedImgB: detectionCache?: Record<number, DetectionCacheEntry> | null

코드에서 전달하는 props 타입이 각 컴포넌트의 인터페이스와 정확히 일치하므로 추가 확인이 필요 없습니다.

src/pages/generate/pages/result/curationSheet/CurationSheetWrapper.tsx (4)

21-26: Props 타입이 CurationSnapState로 통일되어 좋아요.

store에서 정의된 타입을 재사용해 일관성이 향상됐어요. 코딩 가이드라인에 따라 Props 타입명도 적절해요.


77-85: mid 상태에서도 body 스크롤 잠금이 적용돼요.

기존에는 collapsed만 unlock이었는데, 이제 expandedmid 모두 lock이에요. hidden 상태는 unlock으로 처리되는 것이 맞는지 확인해주세요.


98-107: snapStyleshidden 키가 이미 정의되어 있어요.

CurationSnapState의 모든 값('collapsed' | 'mid' | 'expanded' | 'hidden')에 대응하는 키가 snapStyles 객체에 완전히 정의되어 있으므로 런타임 에러는 발생하지 않습니다.


37-66: 드래그 핸들러에서 'hidden' 상태를 처리하지 않아요.

CurationSnapState 타입에 'hidden' 상태가 정의되어 있고 실제로 GeneratedImgA.tsx에서 설정되지만, handleDragUphandleDragDown에서 이 상태에 대한 분기를 누락했어요. snapState'hidden'일 때 드래그해도 아무 반응이 없는데, 이 동작이 의도적인지 확인하고 필요하면 명시적으로 처리하세요.

src/pages/generate/pages/result/components/DetectionHotspots.tsx (5)

70-79: cachedDetection prop 추가가 적절해요.

선택적 prop으로 캐시된 detection을 전달받아 초기화에 활용하는 구조가 좋아요.


101-105: useDetectionCache 훅 사용이 적절해요.

imageId, imageUrl, initialEntry 옵션을 전달해 캐시를 관리하고, prefetchedDetectionssaveEntry를 반환받아 활용하는 패턴이 깔끔해요.


144-151: hotspotOptions 메모이제이션이 올바르게 적용됐어요.

의존성 배열에 prefetchedDetectionshandleInferenceComplete를 포함해 불필요한 훅 재실행을 방지해요.


135-142: prefetchedDetections 동기화와 imageUrl 변경 시 리셋 로직이 적절해요.

두 개의 분리된 useEffect로 각 책임을 명확히 했어요. imageUrl 변경 시 lastDetectionsRef를 초기화해 stale 데이터 사용을 방지해요.


229-236: useEffect 의존성 배열에 saveEntry 추가 시 안정성 확인 완료

saveEntryuseDetectionCache에서 useCallback으로 메모이제이션되어 있어 의존성 배열에 포함하는 것이 올바른 조치예요. 다만 saveEntry의 의존성(imageId, imageUrl)이 변경되면 함께 재생성되므로, 불필요한 재렌더링을 방지하려면 호출부의 imageId, imageUrl 변경 빈도도 고려해서 최적화하면 좋습니다.

src/pages/mypage/hooks/useMypage.ts (1)

14-21: 마이페이지 유저 쿼리의 staleTime/포커스 리패치 전략 확인 필요

useMyPageUser에 15분 staleTimerefetchOnWindowFocus: false가 추가되면서, 동일 세션 내에서 사용자 정보가 비교적 오래 캐시되고 포커스만으로는 자동 갱신되지 않게 됩니다.

  • 프로필 수정/로그아웃 등 “사용자 정보가 즉시 반영돼야 하는” 플로우에서 별도 invalidateQueries나 수동 refetch가 확실히 걸려 있는지 한 번 확인해 주세요.
  • 현재 options 타입이 { enabled?: boolean }로 제한돼 있어, 호출 측에서 staleTime 등을 조정할 수 없는데, 의도한 제약이라면 괜찮지만 추후 확장성을 고려하면 별도 옵션 타입(예: MyPageUserOptions)으로 분리해두는 것도 선택지입니다.

Comment on lines +29 to +42
function useGenerateWarmup() {
const location = useLocation();

useEffect(() => {
const pathname = location.pathname;
const shouldWarmup = GENERATE_WARMUP_PATHS.some(
(prefix) => pathname === prefix || pathname.startsWith(`${prefix}/`)
);

if (!shouldWarmup) return;

preloadONNXModel(OBJ365_MODEL_PATH).catch(() => undefined);
}, [location.pathname]);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

커스텀 훅 파일 분리 고려

코딩 가이드라인에 따르면 커스텀 훅 파일명은 use*.ts 형식이어야 해요. useGenerateWarmup 훅을 별도 파일(src/shared/hooks/useGenerateWarmup.ts)로 분리하면 재사용성과 테스트 용이성이 향상될 수 있어요. 현재 구현도 동작에는 문제없지만, 프로젝트 컨벤션 일관성을 위해 분리를 권장해요.

-function useGenerateWarmup() {
-  const location = useLocation();
-
-  useEffect(() => {
-    const pathname = location.pathname;
-    const shouldWarmup = GENERATE_WARMUP_PATHS.some(
-      (prefix) => pathname === prefix || pathname.startsWith(`${prefix}/`)
-    );
-
-    if (!shouldWarmup) return;
-
-    preloadONNXModel(OBJ365_MODEL_PATH).catch(() => undefined);
-  }, [location.pathname]);
-}
+// src/shared/hooks/useGenerateWarmup.ts 로 분리 권장

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In src/layout/RootLayout.tsx lines 29-42 the custom hook useGenerateWarmup
should be moved to its own file to follow the project convention for hooks
(use*.ts). Create src/shared/hooks/useGenerateWarmup.ts exporting the hook
(export default or named export), copy the implementation including the
useLocation, useEffect, GENERATE_WARMUP_PATHS and preloadONNXModel usage, keep
the dependency array as [location.pathname], add any needed imports/exports and
TypeScript types, then update RootLayout.tsx to import the hook from the new
path and remove the in-file definition.

Comment on lines +124 to +133
const handleInferenceComplete = useCallback(
(result: ProcessedDetections, latestHotspots: FurnitureHotspot[]) => {
lastDetectionsRef.current = result;
saveEntry({
processedDetections: result,
hotspots: latestHotspots,
});
},
[saveEntry]
);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

saveEntry가 두 곳에서 호출되어 중복 저장 가능성이 있어요.

handleInferenceComplete에서 inference 완료 시 저장하고, useEffect에서 hotspots 동기화 시에도 저장해요. 동일한 데이터가 두 번 저장될 수 있어요.

handleInferenceComplete에서는 processedDetections만 저장하고, detectedObjects가 포함된 최종 저장은 sync effect에서만 하도록 분리하거나, 중복 호출을 방지하는 조건을 추가하는 것이 좋아요:

  const handleInferenceComplete = useCallback(
    (result: ProcessedDetections, latestHotspots: FurnitureHotspot[]) => {
      lastDetectionsRef.current = result;
-     saveEntry({
-       processedDetections: result,
-       hotspots: latestHotspots,
-     });
+     // sync effect에서 detectedObjects 포함하여 최종 저장
    },
-   [saveEntry]
+   []
  );

Also applies to: 220-228

🤖 Prompt for AI Agents
In src/pages/generate/pages/result/components/DetectionHotspots.tsx around lines
124-133 (and also apply the same change for 220-228), handleInferenceComplete
currently calls saveEntry and duplicates saves with the useEffect that syncs
hotspots; change handleInferenceComplete to only update
lastDetectionsRef.current and call saveEntry with only processedDetections (no
hotspots) OR remove saveEntry there entirely and let the hotspot-syncing
useEffect perform the final save including detectedObjects; alternatively add a
simple dedupe guard (e.g., compare new processedDetections/hotspots to last
saved values and skip save if identical) so the same data is not saved twice.

Comment on lines +82 to +87
const now = Date.now();
if (now - toastCooldownRef.current < TOAST_COOLDOWN_MS) {
return; // 연속 클릭 시 스낵바 중복 방지
}
toastCooldownRef.current = now;
// 스낵바 중복 노출 방지 가드
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

쿨다운 로직이 정확하게 동작해요.

타임스탬프 비교와 조기 반환을 통해 중복 스낵바를 효과적으로 차단했어요. 87번 라인의 주석이 가드 로직(83-85번) 이후에 위치해 있는데, 83번 라인으로 이동하거나 84번 라인의 주석만으로도 충분할 수 있어요.

선택적으로 주석 위치를 조정할 수 있어요:

           if (!wasSaved && data.favorited) {
             const now = Date.now();
+            // 스낵바 중복 노출 방지 가드
             if (now - toastCooldownRef.current < TOAST_COOLDOWN_MS) {
               return; // 연속 클릭 시 스낵바 중복 방지
             }
             toastCooldownRef.current = now;
-            // 스낵바 중복 노출 방지 가드
             notify({
🤖 Prompt for AI Agents
In src/pages/generate/pages/result/curationSheet/CardProductItem.tsx around
lines 82 to 87, the cooldown guard comment is placed after the guard block; move
the comment up to immediately above line 83 (the start of the guard) or delete
the trailing comment and leave the inline comment on line 84 so the comment
directly documents the comparison-and-return logic that prevents duplicate
toasts.

(set) => ({
images: {},
setEntry: (imageId, payload) => {
if (!imageId) return;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

imageId 검증 로직 개선 권장

if (!imageId) 체크는 0을 falsy로 처리해요. 만약 imageId가 0부터 시작한다면 유효한 ID가 거부될 수 있어요. 더 명확한 검증을 권장해요.

-      if (!imageId) return;
+      if (imageId == null || imageId < 0) return;

또는 imageId가 1부터 시작하는 것이 보장된다면:

-      if (!imageId) return;
+      if (!imageId || imageId <= 0) return;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (!imageId) return;
if (imageId == null || imageId < 0) return;
🤖 Prompt for AI Agents
In src/pages/generate/stores/useDetectionCacheStore.ts around line 42, the
current check "if (!imageId) return;" treats 0 as falsy and may incorrectly
reject a valid imageId of 0; change the guard to explicitly check for
null/undefined (e.g., if (imageId == null) return; or if (imageId === undefined
|| imageId === null) return;) so 0 remains valid, or leave as-is only if you can
guarantee IDs start at 1 and document that assumption.

Comment on lines +27 to +31
useEffect(() => {
if (isLoaded && !localLoaded) {
setLocalLoaded(true);
}
}, [isLoaded, localLoaded]);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

상태 동기화 로직에 잠재적 문제가 있어요.

isLoaded prop이 true에서 false로 변경되는 경우(예: 다른 이미지로 교체 시) localLoaded가 초기화되지 않아요.

   useEffect(() => {
-    if (isLoaded && !localLoaded) {
-      setLocalLoaded(true);
-    }
+    setLocalLoaded(isLoaded);
   }, [isLoaded, localLoaded]);

또는 imageId가 변경될 때 상태를 리셋하는 방식도 고려해 주세요:

useEffect(() => {
  setLocalLoaded(isLoaded);
}, [imageId, isLoaded]);
🤖 Prompt for AI Agents
In src/pages/mypage/components/card/cardCuration/CardCuration.tsx around lines
27-31, the current effect only sets localLoaded to true when isLoaded becomes
true and never resets it when isLoaded flips back to false (or when the image
changes); replace the effect so localLoaded is directly synchronized with
isLoaded and/or reset when imageId changes — specifically, add an effect that
calls setLocalLoaded(isLoaded) with a dependency array of [imageId, isLoaded] so
localLoaded mirrors the incoming prop and resets on image change.

Comment on lines 42 to 51
<div className={styles.cardImage} onClick={onCurationClick}>
{/* 이미지 로드 완료 전에는 skeleton, 완료 시 실제 이미지 렌더링 */}
{!isLoaded && <div className={styles.skeleton} />}
{!localLoaded && <div className={styles.skeleton} />}
<img
src={imgSrc}
alt={imageUrl ? '생성된 이미지' : '기본 이미지'}
className={styles.image({ loaded: isLoaded })}
onLoad={() => setIsLoaded(true)}
className={styles.image({ loaded: localLoaded })}
onLoad={handleImageLoad}
crossOrigin="anonymous"
/>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

접근성 개선이 필요해요.

div 요소에 onClick 핸들러가 있지만 키보드 이벤트 핸들러와 적절한 role 속성이 없어요. 키보드 사용자가 이 요소와 상호작용할 수 없어요.

-      <div className={styles.cardImage} onClick={onCurationClick}>
+      <div
+        className={styles.cardImage}
+        onClick={onCurationClick}
+        onKeyDown={(e) => {
+          if (e.key === 'Enter' || e.key === ' ') {
+            e.preventDefault();
+            onCurationClick?.();
+          }
+        }}
+        role="button"
+        tabIndex={0}
+      >

또는 시맨틱하게 <button> 요소로 변경하는 것도 좋은 방법이에요.

🧰 Tools
🪛 Biome (2.1.2)

[error] 42-43: Static Elements should not be interactive.

To add interactivity such as a mouse or key event listener to a static element, give the element an appropriate role value.

(lint/a11y/noStaticElementInteractions)


[error] 42-43: Enforce to have the onClick mouse event with the onKeyUp, the onKeyDown, or the onKeyPress keyboard event.

Actions triggered using mouse events should have corresponding keyboard events to account for keyboard-only navigation.

(lint/a11y/useKeyWithClickEvents)

🤖 Prompt for AI Agents
In src/pages/mypage/components/card/cardCuration/CardCuration.tsx around lines
42 to 51, the div with onClick lacks keyboard accessibility and semantic role;
either replace the clickable div with a semantic <button> (preserving styles and
removing onKeyDown/onClick duplication) or keep the div but add role="button",
tabIndex={0}, and an onKeyDown handler that triggers onCurationClick for Enter
and Space keys; ensure aria-label or descriptive text is present if the image
has no visible caption and keep existing onClick and focus styles for keyboard
users.

Comment on lines 40 to 57
type ImageDetailResult = Awaited<ReturnType<typeof getMyPageImageDetail>>;

type ImageDetailOptions = {
enabled?: boolean;
initialData?: () => ImageDetailResult;
placeholderData?: () => ImageDetailResult;
};

export const useMyPageImageDetail = (
houseId: number,
options?: { enabled?: boolean }
options?: ImageDetailOptions
) => {
return useQuery({
queryKey: [QUERY_KEY.MYPAGE_IMAGE_DETAIL, houseId],
queryFn: () => getMyPageImageDetail(houseId),
staleTime: 0,
staleTime: 5 * 60 * 1000,
cacheTime: 30 * 60 * 1000,
...options,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat -n src/pages/mypage/hooks/useMypage.ts | head -70

Repository: TEAM-HOUME/HOUME-CLIENT

Length of output: 1785


🏁 Script executed:

find . -name "package.json" -type f | head -5 | xargs grep -l "react-query\|@tanstack/react-query" | head -1 | xargs cat | grep -A 2 "react-query\|@tanstack/react-query"

Repository: TEAM-HOUME/HOUME-CLIENT

Length of output: 235


useMyPageImageDetailuseMyPageImageDetailQuery로 함수명 변경하고, ImageDetailOptions 타입을 UseQueryOptions로 정렬해주세요

함수명이 컨벤션을 따르지 않습니다. 코딩 가이드라인에서 "TanStack Query 훅은 use + 행위 + 대상 + Query/Mutation 형식"을 명시하고 있으므로, Query 접미사를 추가해야 합니다. 동시에 ImageDetailOptions 타입도 UseQueryOptionsPick으로 정의하면 라이브러리 타입 변경에 견고해집니다:

-type ImageDetailResult = Awaited<ReturnType<typeof getMyPageImageDetail>>;
-
-type ImageDetailOptions = {
-  enabled?: boolean;
-  initialData?: () => ImageDetailResult;
-  placeholderData?: () => ImageDetailResult;
-};
-
-export const useMyPageImageDetail = (
+type ImageDetailResult = Awaited<ReturnType<typeof getMyPageImageDetail>>;
+
+type ImageDetailOptions = Pick<
+  UseQueryOptions<ImageDetailResult>,
+  'enabled' | 'initialData' | 'placeholderData'
+>;
+
+export const useMyPageImageDetailQuery = (

import 문에 타입 전용 import도 추가하세요:

import { useQuery } from '@tanstack/react-query';
import type { UseQueryOptions } from '@tanstack/react-query';

이렇게 하면 initialData/placeholderData가 값 또는 함수 형태를 모두 지원하게 되고, 라이브러리 업데이트 시 타입 호환성 문제가 줄어듭니다.

🧰 Tools
🪛 GitHub Check: build

[failure] 56-56:
No overload matches this call.

🤖 Prompt for AI Agents
In src/pages/mypage/hooks/useMypage.ts around lines 40 to 57, rename the
exported hook from useMyPageImageDetail to useMyPageImageDetailQuery to follow
TanStack Query naming convention, replace the custom ImageDetailOptions type
with the library's UseQueryOptions generic (e.g.,
UseQueryOptions<Awaited<ReturnType<typeof getMyPageImageDetail>>> or equivalent)
so initialData/placeholderData accept values or functions and remain robust to
library changes, and add a type-only import for UseQueryOptions from
'@tanstack/react-query' (import type { UseQueryOptions } from
'@tanstack/react-query') while keeping the existing useQuery import.

Comment on lines +49 to +50
// 세션 감지 캐시 초기화
useDetectionCacheStore.getState().clear();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

사용자 스토어와 감지 캐시 스토어 간 결합(coupling) 발생

clearUser 액션 내에서 다른 스토어(useDetectionCacheStore)의 상태를 직접 변경하고 있어요. 이는 스토어 간 암묵적 의존성을 만들어 유지보수성을 떨어뜨릴 수 있어요.

대안으로 아래 방식을 고려해 주세요:

  1. clearUser를 호출하는 컴포넌트/훅에서 두 스토어를 함께 초기화
  2. 별도의 useLogout 훅을 만들어 관련 스토어들을 일괄 초기화
🤖 Prompt for AI Agents
In src/store/useUserStore.ts around lines 49-50, remove the direct call to
useDetectionCacheStore.getState().clear() from inside clearUser to avoid
coupling stores; instead either (A) have the component/hook that calls clearUser
also call useDetectionCacheStore.getState().clear() so callers explicitly reset
both stores, or (B) create a new useLogout (or useClearAllStores) helper that
imports both stores and invokes clearUser plus detectionCache.clear() in one
place; implement one of these patterns and update callers to use it, and ensure
clearUser only touches its own state.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

♻️ Duplicate comments (4)
src/pages/mypage/hooks/useDetectionPrefetch.ts (1)

142-145: console.warn 대신 프로젝트 로깅 유틸리티 사용 권장

프로덕션 환경에서 구조화된 로깅을 위해 logFurniturePipelineEvent 같은 기존 로깅 유틸리티 사용을 권장해요.

src/pages/mypage/components/section/generatedImages/GeneratedImagesSection.tsx (1)

31-41: sessionStorage 키를 상수로 추출하는 것을 권장해요.

'mypage-image-loaded' 키가 Line 35와 Line 107에서 중복 사용되고 있어요. 상수로 추출하면 유지보수성이 향상돼요.

+const MYPAGE_IMAGE_LOADED_KEY = 'mypage-image-loaded';
+
 const GeneratedImagesSection = ({
   userProfile,
 }: GeneratedImagesSectionProps) => {
   // ...
   const [loadedImages, setLoadedImages] = useState<Record<number, boolean>>(
     () => {
       if (typeof window === 'undefined') return {};
       try {
-        const stored = sessionStorage.getItem('mypage-image-loaded');
+        const stored = sessionStorage.getItem(MYPAGE_IMAGE_LOADED_KEY);
src/pages/generate/pages/result/ResultPage.tsx (2)

143-162: 빌드 실패: mypageResult.histories 타입 오류를 수정해야 해요.

useMyPageImageDetail 훅에서 반환된 데이터의 타입이 histories 속성을 포함하지 않아 TypeScript 오류가 발생하고 있어요. 또한 Line 146에서 history 파라미터가 암묵적 any 타입을 가지고 있어요.

+ import type { MyPageImageDetailData } from '@/pages/mypage/types/apis/MyPage';

- const mypageResult = mypageDetailQuery.data;
+ const mypageResult = mypageDetailQuery.data as MyPageImageDetailData | undefined;

  if (isFromMypage && mypageResult && mypageResult.histories.length > 0) {
-   const allImageData = mypageResult.histories.map((history) => ({
-     imageId: history.imageId,
-     imageUrl: history.generatedImageUrl,
-     isMirror: false,
-     equilibrium: history.equilibrium,
-     houseForm: history.houseForm,
-     tagName: history.tasteTag,
-     name: history.name,
-   }));
+   const allImageData = mypageResult.histories.map((history) =>
+     toGenerateImageData(history)
+   );

toGenerateImageData 헬퍼를 재사용하면 중복 코드도 제거할 수 있어요. 단, toGenerateImageData에서 name: history.tasteTag로 설정하고 있는데, 여기서는 history.name을 사용하고 있어서 동작이 다를 수 있으니 확인이 필요해요.


83-92: 렌더 단계에서 result 변수 재할당 패턴이 있어요.

let result로 선언 후 조건에 따라 재할당하는 패턴은 렌더링 사이클마다 다른 참조를 생성할 수 있어요. useMemo를 사용해 결과를 안정적으로 계산하는 것이 좋아요.

- let result = locationState?.result;
+ const initialResult = useMemo(() => {
+   if (locationState?.result) return locationState.result;
+   if (initialHistory) {
+     return {
+       imageInfoResponses: [toGenerateImageData(initialHistory)],
+     } as UnifiedGenerateImageResult;
+   }
+   return null;
+ }, [locationState?.result, initialHistory]);
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ab8a123 and 1b88c95.

📒 Files selected for processing (11)
  • src/pages/generate/hooks/useFurnitureCuration.ts (4 hunks)
  • src/pages/generate/pages/result/ResultPage.tsx (7 hunks)
  • src/pages/generate/pages/result/components/DetectionHotspots.tsx (6 hunks)
  • src/pages/generate/pages/result/components/GeneratedImg.css.ts (2 hunks)
  • src/pages/generate/pages/result/components/GeneratedImgA.tsx (8 hunks)
  • src/pages/generate/pages/result/components/GeneratedImgB.tsx (4 hunks)
  • src/pages/generate/pages/result/curationSheet/CurationSheet.tsx (6 hunks)
  • src/pages/generate/stores/useCurationCacheStore.ts (1 hunks)
  • src/pages/mypage/components/section/generatedImages/GeneratedImagesSection.tsx (2 hunks)
  • src/pages/mypage/hooks/useDetectionPrefetch.ts (1 hunks)
  • src/shared/constants/queryKey.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (11)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (README.md)

**/*.{ts,tsx}: Do not use var; prefer const and use let only when reassignment is required
Prefer destructuring assignment where appropriate
Use template literals for string composition instead of concatenation
Use clear, unabbreviated names for variables and functions (camelCase)
Use arrow functions for function definitions
Function names should be verb-based (get/create/check/handle/is/has/can) reflecting behavior
Types and interfaces must use PascalCase; do not use I/T/Type prefixes or suffixes (except generic T)
Props and State type names must be ComponentNameProps and ComponentNameState; API types use {Resource}Request/{Resource}Response
Use interface for component props/state and API request/response and object shapes; use type for unions, intersections, function signatures, and tuples
Use path aliases (e.g., @apis/user, @components/Button) instead of deep relative paths
When using TanStack Query, use QUERY_KEYS factory rather than hardcoded query keys

Files:

  • src/shared/constants/queryKey.ts
  • src/pages/generate/pages/result/components/GeneratedImgA.tsx
  • src/pages/generate/pages/result/components/GeneratedImg.css.ts
  • src/pages/generate/stores/useCurationCacheStore.ts
  • src/pages/generate/pages/result/components/GeneratedImgB.tsx
  • src/pages/generate/pages/result/components/DetectionHotspots.tsx
  • src/pages/generate/pages/result/curationSheet/CurationSheet.tsx
  • src/pages/mypage/hooks/useDetectionPrefetch.ts
  • src/pages/generate/hooks/useFurnitureCuration.ts
  • src/pages/generate/pages/result/ResultPage.tsx
  • src/pages/mypage/components/section/generatedImages/GeneratedImagesSection.tsx
src/**/constants/queryKey.ts

📄 CodeRabbit inference engine (README.md)

Define and maintain QueryKey Factory in shared/constants/queryKey.ts using structured keys (e.g., USER.ALL/LIST/DETAIL)

Files:

  • src/shared/constants/queryKey.ts
src/**/constants/**

⚙️ CodeRabbit configuration file

src/**/constants/**: 상수 파일 리뷰 시:

  • 모든 상수가 BIG_SNAKE_CASE로 작성되었는지 확인
  • export const 사용 확인
  • 의미있는 이름 사용 여부 확인
  • 리터럴은 as const로 타입 좁히기

Files:

  • src/shared/constants/queryKey.ts
src/**/*.ts

⚙️ CodeRabbit configuration file

src/**/*.ts: TypeScript 파일 리뷰 시:

  • 타입/인터페이스는 PascalCase 사용, Type/I/T 접두사·접미사 금지 (제네릭 T 제외)
  • interface는 객체 타입, type은 union/별칭에 사용하는지 확인
  • API Response는 XXXResponse 네이밍 따르는지 확인
  • var 사용 금지, const 우선 사용 확인
  • type-only import 사용(import type), 불필요한 런타임 의존성 제거
  • satisfies 연산자 활용으로 정확한 타입 보장

Files:

  • src/shared/constants/queryKey.ts
  • src/pages/generate/pages/result/components/GeneratedImg.css.ts
  • src/pages/generate/stores/useCurationCacheStore.ts
  • src/pages/mypage/hooks/useDetectionPrefetch.ts
  • src/pages/generate/hooks/useFurnitureCuration.ts
src/**

⚙️ CodeRabbit configuration file

src/**: 전체 프로젝트 코드 리뷰 시:

기본 규칙

  • 모든 컴포넌트는 반드시 TypeScript로 작성
  • SVG는 ?react 쿼리를 통해 React 컴포넌트로 임포트
  • 스타일은 반드시 Vanilla Extract(.css.ts)로 작성
  • 절대 경로 임포트 사용 (@pages, @shared 등)
  • overlay-kit을 통한 모달 관리 패턴 준수

네이밍 컨벤션

  • 컴포넌트와 클래스는 PascalCase 사용
  • 폴더명은 camelCase 사용
  • 파일명은 camelCase 사용 (컴포넌트 파일 제외)
  • 변수와 함수는 camelCase 사용
  • 상수는 BIG_SNAKE_CASE 사용
  • 모든 타입/인터페이스는 PascalCase, Type/I/T 접두사·접미사 금지 (제네릭 T 제외)
  • Props 타입은 컴포넌트명 + Props 형식
  • API Response 타입은 XXXResponse 형식
  • 함수 네이밍: get/create/check/handle/is/has/can 접두사 활용
  • API 함수는 HTTP메서드 + 명사 형식 (getUserList, postComment)
  • TanStack Query 훅: use + 행위 + 대상 + Query/Mutation (예: useGetUserListQuery)
  • 커스텀 훅 파일명: use*.ts

보안 및 성능

  • 환경 변수는 반드시 import.meta.env 사용
  • API 키나 시크릿을 하드코딩하지 않음
  • React.memo, useMemo, useCallback의 적절한 사용
  • 큰 번들 사이즈 경고 (dynamic import 제안)
  • 템플릿 리터럴 사용 권장 (문자열 조합 시)
  • 구조 분해 할당 적극 활용

커밋 및 브랜치 패턴

  • 커밋 타입: feat, fix, docs, style, refactor, test, chore, design, comment, rename, remove
  • 브랜치 패턴: type/description/#issue-number 형식

Files:

  • src/shared/constants/queryKey.ts
  • src/pages/generate/pages/result/components/GeneratedImgA.tsx
  • src/pages/generate/pages/result/components/GeneratedImg.css.ts
  • src/pages/generate/stores/useCurationCacheStore.ts
  • src/pages/generate/pages/result/components/GeneratedImgB.tsx
  • src/pages/generate/pages/result/components/DetectionHotspots.tsx
  • src/pages/generate/pages/result/curationSheet/CurationSheet.tsx
  • src/pages/mypage/hooks/useDetectionPrefetch.ts
  • src/pages/generate/hooks/useFurnitureCuration.ts
  • src/pages/generate/pages/result/ResultPage.tsx
  • src/pages/mypage/components/section/generatedImages/GeneratedImagesSection.tsx
src/**/components/**/*.tsx

📄 CodeRabbit inference engine (README.md)

Component file names must use PascalCase and have .tsx extension (e.g., UserProfile.tsx, Button.tsx)

Files:

  • src/pages/generate/pages/result/components/GeneratedImgA.tsx
  • src/pages/generate/pages/result/components/GeneratedImgB.tsx
  • src/pages/generate/pages/result/components/DetectionHotspots.tsx
  • src/pages/mypage/components/section/generatedImages/GeneratedImagesSection.tsx

⚙️ CodeRabbit configuration file

src/**/components/**/*.tsx: 컴포넌트 파일 리뷰 시:

  • 파일명이 PascalCase인지 확인
  • Props 타입이 '컴포넌트명Props' 형식인지 확인
  • 단일 export는 default export 권장(팀 가이드 우선 적용)
  • 이벤트 핸들러는 handle로 시작하는지 확인
  • 접근성 준수: ARIA 속성, label 연결, 키보드 포커스
  • 성능: memo/useMemo/useCallback 적절 사용
  • ref 전달 시 forwardRef 사용 및 displayName 지정

Files:

  • src/pages/generate/pages/result/components/GeneratedImgA.tsx
  • src/pages/generate/pages/result/components/GeneratedImgB.tsx
  • src/pages/generate/pages/result/components/DetectionHotspots.tsx
  • src/pages/mypage/components/section/generatedImages/GeneratedImagesSection.tsx
src/pages/**/components/**

⚙️ CodeRabbit configuration file

src/pages/**/components/**: 페이지별 컴포넌트 리뷰 시:

  • 해당 페이지의 도메인 로직과 긴밀한 결합 확인
  • 재사용 가능한 컴포넌트는 shared로 이동 제안
  • 페이지 특화 타입과 상수의 적절한 활용 확인

Files:

  • src/pages/generate/pages/result/components/GeneratedImgA.tsx
  • src/pages/generate/pages/result/components/GeneratedImg.css.ts
  • src/pages/generate/pages/result/components/GeneratedImgB.tsx
  • src/pages/generate/pages/result/components/DetectionHotspots.tsx
  • src/pages/mypage/components/section/generatedImages/GeneratedImagesSection.tsx
**/*.css.ts

📄 CodeRabbit inference engine (README.md)

Style files must use the Vanilla Extract convention with .css.ts extension (e.g., Button.css.ts)

Files:

  • src/pages/generate/pages/result/components/GeneratedImg.css.ts
src/**/*.css.ts

⚙️ CodeRabbit configuration file

src/**/*.css.ts: Vanilla Extract 스타일 파일 리뷰 시:

  • 디자인 토큰 활용 확인: colorVars 직접 사용, fontStyle() 함수를 통한 폰트 토큰 변환
  • fontStyle() 함수 사용법: ...fontStyle('토큰명') 형태로 스프레드 연산자와 함께 사용
  • recipe와 styleVariants 패턴의 적절한 사용
  • 반응형 스타일 구현 방식
  • 불필요한 중복 스타일 제거
  • 전역 스타일 남용 금지, 컴포넌트 범위 캡슐화

Files:

  • src/pages/generate/pages/result/components/GeneratedImg.css.ts
src/**/hooks/use*.ts

📄 CodeRabbit inference engine (README.md)

Custom hook files must be named with use prefix (use*.ts)

Files:

  • src/pages/mypage/hooks/useDetectionPrefetch.ts
  • src/pages/generate/hooks/useFurnitureCuration.ts
src/**/hooks/**/*.ts

⚙️ CodeRabbit configuration file

src/**/hooks/**/*.ts: 커스텀 훅 리뷰 시:

  • 파일명/함수명은 use* 접두사 사용
  • 의존성 배열(exhaustive-deps) 준수, 참조 안정성 유지(함수/객체 메모이제이션)
  • 반환 값 일관성(tuple/object), 에러/로딩 상태 명확화
  • 제네릭 훅은 타입 매개변수 기본값 검토
  • TanStack Query 훅 네이밍: use + 행위 + 대상 + Query/Mutation (예: useGetUserListQuery)

Files:

  • src/pages/mypage/hooks/useDetectionPrefetch.ts
  • src/pages/generate/hooks/useFurnitureCuration.ts
🧠 Learnings (8)
📚 Learning: 2025-10-09T08:24:59.296Z
Learnt from: CR
Repo: TEAM-HOUME/HOUME-CLIENT PR: 0
File: README.md:0-0
Timestamp: 2025-10-09T08:24:59.296Z
Learning: Applies to src/**/constants/queryKey.ts : Define and maintain QueryKey Factory in shared/constants/queryKey.ts using structured keys (e.g., USER.ALL/LIST/DETAIL)

Applied to files:

  • src/shared/constants/queryKey.ts
📚 Learning: 2025-10-09T08:24:59.296Z
Learnt from: CR
Repo: TEAM-HOUME/HOUME-CLIENT PR: 0
File: README.md:0-0
Timestamp: 2025-10-09T08:24:59.296Z
Learning: Applies to **/*.{ts,tsx} : When using TanStack Query, use QUERY_KEYS factory rather than hardcoded query keys

Applied to files:

  • src/shared/constants/queryKey.ts
📚 Learning: 2025-07-14T15:15:29.763Z
Learnt from: soyyyyy
Repo: TEAM-HOUME/HOUME-CLIENT PR: 167
File: src/pages/onboarding/components/steps/step2/floorPlan.tsx:0-0
Timestamp: 2025-07-14T15:15:29.763Z
Learning: React에서 Bottom Sheet 애니메이션 처리 시 setTimeout 대신 useEffect를 사용하여 상태 변경 순서를 보장하는 패턴: openSheet 상태 변경 → useEffect 트리거 → isSheetOpen true 설정 → 애니메이션 시작

Applied to files:

  • src/pages/generate/pages/result/components/GeneratedImgA.tsx
📚 Learning: 2025-10-21T22:07:55.899Z
Learnt from: soyyyyy
Repo: TEAM-HOUME/HOUME-CLIENT PR: 341
File: src/pages/generate/pages/result/curationSheet/CurationSheet.tsx:63-68
Timestamp: 2025-10-21T22:07:55.899Z
Learning: In HOUME-CLIENT project: React keys for furniture product lists should use the stable `furnitureProductId` rather than the ephemeral `p.id` (recommendId). The recommendId changes per recommendation session, while furnitureProductId is a stable identifier for the product itself, enabling React to properly reconcile and reuse DOM elements across different recommendation sets.

Applied to files:

  • src/pages/generate/pages/result/components/DetectionHotspots.tsx
📚 Learning: 2025-10-09T08:24:59.296Z
Learnt from: CR
Repo: TEAM-HOUME/HOUME-CLIENT PR: 0
File: README.md:0-0
Timestamp: 2025-10-09T08:24:59.296Z
Learning: Applies to src/**/hooks/use*.ts : Custom hook files must be named with use prefix (use*.ts)

Applied to files:

  • src/pages/mypage/hooks/useDetectionPrefetch.ts
📚 Learning: 2025-10-13T04:31:50.173Z
Learnt from: earl9rey
Repo: TEAM-HOUME/HOUME-CLIENT PR: 339
File: src/pages/mypage/components/history/HistorySection.tsx:57-61
Timestamp: 2025-10-13T04:31:50.173Z
Learning: src/pages/mypage/components/history/HistorySection.tsx 컴포넌트는 더 이상 뷰에서 사용되지 않는 데드 코드입니다. MyPage.tsx에서는 GeneratedImagesSection만 사용하고 있습니다.

Applied to files:

  • src/pages/generate/pages/result/ResultPage.tsx
  • src/pages/mypage/components/section/generatedImages/GeneratedImagesSection.tsx
📚 Learning: 2025-10-05T19:50:38.893Z
Learnt from: soyyyyy
Repo: TEAM-HOUME/HOUME-CLIENT PR: 328
File: src/pages/generate/pages/result/curationSheet/CurationSheet.tsx:76-87
Timestamp: 2025-10-05T19:50:38.893Z
Learning: In TypeScript/React projects using Zustand: when addressing performance issues related to state updates causing unnecessary re-renders, prefer splitting Zustand selectors over wrapping components with React.memo. The user soyyyyy prefers this approach as it addresses the root cause of re-renders at the state subscription level.

Applied to files:

  • src/pages/generate/pages/result/ResultPage.tsx
📚 Learning: 2025-07-16T04:24:52.968Z
Learnt from: soyyyyy
Repo: TEAM-HOUME/HOUME-CLIENT PR: 194
File: src/pages/generate/components/result/ResultPage.tsx:20-20
Timestamp: 2025-07-16T04:24:52.968Z
Learning: ResultPage 컴포넌트의 하드코딩된 imageId는 API 연동 작업 시 동적으로 수정될 예정입니다. 사용자는 API 연동과 함께 이러한 임시 값들을 처리하는 것을 선호합니다.

Applied to files:

  • src/pages/generate/pages/result/ResultPage.tsx
🧬 Code graph analysis (8)
src/pages/generate/pages/result/components/GeneratedImg.css.ts (2)
src/shared/styles/tokens/color.css.ts (1)
  • colorVars (3-36)
src/shared/styles/tokens/animation.css.ts (1)
  • animationTokens (26-41)
src/pages/generate/stores/useCurationCacheStore.ts (2)
src/pages/generate/types/furniture.ts (2)
  • FurnitureCategoriesResponse (10-12)
  • FurnitureProductsInfoResponse (29-32)
src/pages/generate/constants/furnitureCategoryMapping.ts (1)
  • FurnitureCategoryCode (25-25)
src/pages/generate/pages/result/components/GeneratedImgB.tsx (1)
src/pages/generate/stores/useDetectionCacheStore.ts (1)
  • DetectionCacheEntry (11-17)
src/pages/generate/pages/result/components/DetectionHotspots.tsx (5)
src/pages/generate/stores/useDetectionCacheStore.ts (1)
  • DetectionCacheEntry (11-17)
src/pages/generate/hooks/useFurnitureCuration.ts (1)
  • useGeneratedCategoriesQuery (41-147)
src/pages/generate/hooks/useFurnitureHotspots.ts (2)
  • FurnitureHotspot (41-41)
  • useFurnitureHotspots (116-510)
src/pages/generate/types/detection.ts (1)
  • ProcessedDetections (22-25)
src/pages/generate/hooks/useDetectionCache.ts (1)
  • useDetectionCache (24-84)
src/pages/mypage/hooks/useDetectionPrefetch.ts (8)
src/pages/generate/hooks/useOnnxModel.ts (1)
  • useONNXModel (221-371)
src/pages/generate/constants/detection.ts (1)
  • OBJ365_MODEL_PATH (7-7)
src/pages/generate/stores/useDetectionCacheStore.ts (1)
  • useDetectionCacheStore (41-75)
src/pages/generate/types/detection.ts (1)
  • ProcessedDetections (22-25)
src/pages/generate/hooks/useFurnitureHotspots.ts (2)
  • FurnitureHotspot (41-41)
  • loadCorsImage (64-112)
src/pages/generate/constants/furnitureCategoryMapping.ts (1)
  • FurnitureCategoryCode (25-25)
src/pages/generate/hooks/furnitureHotspotPipeline.ts (1)
  • buildHotspotsPipeline (160-266)
src/pages/generate/utils/detectedObjectMapper.ts (2)
  • mapHotspotsToDetectedObjects (13-52)
  • filterAllowedDetectedObjects (55-78)
src/pages/generate/hooks/useFurnitureCuration.ts (7)
src/pages/generate/stores/useCurationStore.ts (1)
  • useCurationStore (83-255)
src/pages/generate/constants/furnitureCategoryMapping.ts (1)
  • FurnitureCategoryCode (25-25)
src/pages/generate/stores/useCurationCacheStore.ts (1)
  • useCurationCacheStore (52-143)
src/pages/generate/types/furniture.ts (2)
  • FurnitureCategoriesResponse (10-12)
  • FurnitureProductsInfoResponse (29-32)
src/shared/constants/queryKey.ts (1)
  • QUERY_KEY (1-18)
src/pages/generate/apis/furniture.ts (2)
  • getGeneratedImageCategories (14-23)
  • getGeneratedImageProducts (26-34)
src/shared/apis/queryClient.ts (1)
  • queryClient (5-18)
src/pages/generate/pages/result/ResultPage.tsx (7)
src/pages/mypage/types/apis/MyPage.ts (2)
  • MyPageImageHistory (13-20)
  • MyPageUserData (4-8)
src/pages/generate/types/generate.ts (3)
  • GenerateImageData (34-42)
  • GenerateImageAResponse (45-51)
  • GenerateImageBResponse (54-58)
src/pages/generate/stores/useDetectionCacheStore.ts (1)
  • DetectionCacheEntry (11-17)
src/pages/mypage/utils/resultNavigation.ts (1)
  • createImageDetailPlaceholder (50-54)
src/pages/generate/hooks/useGenerate.ts (1)
  • useGetResultDataQuery (66-75)
src/pages/mypage/hooks/useMypage.ts (1)
  • useMyPageImageDetail (66-77)
src/pages/generate/pages/result/curationSheet/CurationSheet.tsx (1)
  • CurationSheet (53-379)
src/pages/mypage/components/section/generatedImages/GeneratedImagesSection.tsx (7)
src/pages/mypage/types/apis/MyPage.ts (2)
  • MyPageUserData (4-8)
  • MyPageImageHistory (13-20)
src/pages/mypage/hooks/useMypage.ts (1)
  • useMyPageImages (45-54)
src/pages/mypage/hooks/useDetectionPrefetch.ts (1)
  • useDetectionPrefetch (57-193)
src/pages/mypage/utils/analytics.ts (1)
  • logMyPageClickBtnImgCard (46-48)
src/pages/mypage/utils/resultNavigation.ts (1)
  • buildResultNavigationState (33-48)
src/routes/paths.ts (1)
  • ROUTES (4-17)
src/pages/mypage/components/section/emptyState/EmptyStateSection.css.ts (1)
  • image (14-17)
🪛 GitHub Actions: CI/CD Pipeline
src/pages/generate/hooks/useFurnitureCuration.ts

[error] 80-80: TS2769: No overload matches this call.

🪛 GitHub Check: build
src/pages/generate/hooks/useFurnitureCuration.ts

[failure] 104-104:
Parameter 'data' implicitly has an 'any' type.


[failure] 80-80:
No overload matches this call.


[failure] 182-182:
Parameter 'data' implicitly has an 'any' type.


[failure] 163-163:
No overload matches this call.

src/pages/generate/pages/result/ResultPage.tsx

[failure] 146-146:
Parameter 'history' implicitly has an 'any' type.


[failure] 146-146:
Property 'histories' does not exist on type 'NonNullable<NoInfer>'.


[failure] 144-144:
Property 'histories' does not exist on type 'NonNullable<NoInfer>'.

🔇 Additional comments (28)
src/shared/constants/queryKey.ts (1)

8-10: LGTM!

그룹 기반 쿼리 키가 기존 패턴과 일관되게 추가되었어요. BIG_SNAKE_CASE 네이밍과 kebab-case 값 형식이 기존 키들과 잘 맞아요.

src/pages/generate/pages/result/components/GeneratedImg.css.ts (1)

62-76: LGTM!

스켈레톤 스타일이 디자인 토큰(colorVars, animationTokens)을 적절히 활용하고 있어요. borderRadius: 'inherit'로 부모 컴포넌트와의 일관성도 잘 유지돼요.

src/pages/generate/pages/result/components/GeneratedImgB.tsx (2)

32-34: LGTM!

detectionCachegroupId props가 적절한 타입과 함께 추가되었어요. optional props로 정의되어 기존 사용처에 영향 없이 점진적 적용이 가능해요.


70-73: LGTM!

cachedDetection 계산 로직이 안전하게 구현되었어요. optional chaining과 nullish coalescing으로 null 케이스를 잘 처리하고 있어요.

src/pages/generate/pages/result/curationSheet/CurationSheet.tsx (5)

49-53: LGTM!

CurationSheetProps 인터페이스가 네이밍 컨벤션을 따르고, groupId의 기본값 null 설정으로 하위 호환성을 유지해요.


61-68: LGTM!

hotspotsdetectedObjectsuseMemo를 적용해 불필요한 재계산을 방지했어요. 의존성 배열도 적절해요.


156-196: 병렬 프리패치 로직 잘 구현됨

카테고리별 프리패치를 병렬로 처리하고, prefetchedRefgroupProductCache 체크로 중복 요청을 방지하는 로직이 잘 구현되었어요. staleTime: 30 * 1000도 적절해요.


334-342: LGTM!

추론 중 스켈레톤 칩 렌더링이 aria-hidden으로 접근성을 고려하고 있어요. 단일 스켈레톤으로 UI를 단순화한 것도 좋은 UX 결정이에요.


137-145: LGTM!

snapState !== 'hidden' 조건 추가로 시트가 이미 숨겨진 상태에서 불필요한 상태 업데이트를 방지해요.

src/pages/generate/stores/useCurationCacheStore.ts (2)

10-15: 타입 정의가 잘 구성되어 있어요.

CategoryCacheEntry, ProductCacheEntry, GroupCache 타입들이 명확하게 분리되어 있고, updatedAt 타임스탬프로 캐시 갱신 시점을 추적할 수 있어요. 캐시 무효화(TTL) 로직이 필요하다면 이 updatedAt 값을 활용할 수 있어요.

Also applies to: 18-21, 23-26


52-143: Zustand 스토어 구현이 적절해요.

불변성을 유지하는 상태 업데이트 패턴이 잘 적용되어 있고, createDefaultGroup 헬퍼로 기본값 생성이 일관되게 처리돼요. 각 액션이 단일 책임을 가지고 있어 유지보수하기 좋아요.

src/pages/generate/pages/result/components/GeneratedImgA.tsx (4)

126-132: restoreSheetSnapState 함수가 적절하게 분리되어 있어요.

모달 종료 시 이전 snap 상태를 복원하는 로직이 컴포넌트 레벨에서 정의되어 재사용성이 좋아요. prevSnapStateRef.current'hidden'인 경우 'collapsed'로 폴백하는 방어 로직도 잘 적용되어 있어요.


143-156: closeModal 헬퍼가 overlay 콜백 내부에서 정의되어 있어요.

현재 구현은 동작하지만, closeModal이 렌더 콜백 내부에서 정의되어 매번 새로 생성돼요. overlay-kit 패턴에서는 이 방식이 일반적이므로 큰 문제는 아니지만, 복잡해지면 외부로 추출하는 것도 고려해볼 수 있어요.


209-223: 슬라이드 카운트 스켈레톤 렌더링이 잘 구현되어 있어요.

isSlideCountLoading 상태에 따라 조건부로 스켈레톤을 보여주고, aria-live="polite"aria-hidden 속성으로 접근성도 고려했어요.


232-253: 이미지별 캐시된 detection 전달 로직이 명확해요.

detectionCache에서 imageId를 키로 cachedDetection을 조회하고, DetectionHotspots에 전달하는 패턴이 직관적이에요. nullish coalescing(??)으로 안전하게 처리하고 있어요.

src/pages/mypage/components/section/generatedImages/GeneratedImagesSection.tsx (4)

55-79: scheduleDetectionPrefetch 헬퍼가 잘 설계되어 있어요.

requestIdleCallback 지원 여부를 확인하고, 미지원 시 setTimeout으로 폴백하는 패턴이 적절해요. immediate 옵션으로 우선순위 제어도 가능해요. Window 타입 확장도 올바르게 처리했어요.


81-99: handleViewResult가 전체 history 객체를 받도록 개선되었어요.

buildResultNavigationState를 활용해 네비게이션 상태를 구성하고, 네비게이션 직후 immediate 프리페치를 실행하는 것이 UX 향상에 도움이 돼요.


101-118: handleImageLoad 콜백이 적절하게 메모이제이션되어 있어요.

useCallback으로 감싸고 의존성 배열에 primaryImageIdscheduleDetectionPrefetch가 포함되어 있어요. sessionStorage 저장과 프리페치 트리거가 함께 처리되는 것이 효율적이에요.


44-53: 초기 프리페치 로직이 효율적으로 구현되어 있어요.

prefetchedImageIdsRef로 중복 프리페치를 방지하고, 첫 번째 이미지는 immediate 우선순위로, 나머지는 background로 처리하는 전략이 좋아요.

src/pages/generate/pages/result/components/DetectionHotspots.tsx (4)

129-138: saveEntry 호출이 두 곳에서 발생하지만, 의도된 설계로 보여요.

handleInferenceComplete에서는 inference 완료 직후 빠르게 저장하고, hotspots sync useEffect에서는 detectedObjects를 포함한 완전한 데이터를 저장해요. Line 232에서 lastDetectionsRef.current = null로 중복 저장을 방지하고 있어요.

다만, 첫 번째 저장에서 detectedObjects 없이 저장된 후 두 번째 저장 전에 컴포넌트가 언마운트되면 불완전한 캐시가 남을 수 있어요. 이 케이스가 문제가 된다면 첫 번째 저장을 제거하고 sync effect에서만 저장하는 것을 고려해보세요.

Also applies to: 225-233


98-101: useGeneratedCategoriesQuerygroupId 파라미터가 추가되었어요.

groupId를 첫 번째 파라미터로 전달하여 그룹 기반 캐싱을 지원해요. 관련 코드 스니펫(useFurnitureCuration.ts lines 40-146)과 일치하는 시그니처예요.


149-156: hotspotOptions 메모이제이션이 올바르게 적용되어 있어요.

useMemo로 옵션 객체를 메모이제이션하여 useFurnitureHotspots 훅의 불필요한 재실행을 방지해요. 의존성 배열에 prefetchedDetectionshandleInferenceComplete가 포함되어 있어요.


140-147: prefetchedDetections 동기화와 imageUrl 변경 시 리셋 로직이 적절해요.

두 useEffect가 각각 다른 역할을 수행해요: 하나는 프리페치된 데이터를 ref에 동기화하고, 다른 하나는 이미지 URL 변경 시 ref를 리셋해요. 이미지가 바뀔 때 이전 detection 데이터가 남지 않도록 하는 방어 로직이에요.

src/pages/generate/pages/result/ResultPage.tsx (4)

43-53: toGenerateImageData 헬퍼 함수가 잘 정의되어 있어요.

MyPageImageHistoryGenerateImageData로 변환하는 로직이 명확해요. 다만 Line 146-154에서 동일한 변환 로직이 인라인으로 중복되어 있으니 이 헬퍼를 재사용하면 좋겠어요.


94-102: forwardedDetectionMap 생성 로직이 적절해요.

useMemo로 메모이제이션되어 있고, forwardedDetectioninitialImageId가 모두 존재할 때만 맵을 생성해요. 의존성 배열이 올바르게 설정되어 있어요.


115-122: 데이터 페칭 조건 플래그가 명확하게 분리되어 있어요.

hasValidHouseId, shouldFetchExternalResult, shouldFetchMypageDetail 플래그로 각 시나리오별 데이터 소스를 명확하게 구분하고 있어요. detailPlaceholder로 마이페이지 경로에서 placeholder 데이터도 지원해요.


377-394: GeneratedImgAGeneratedImgB에 새 props가 전달되고 있어요.

userProfile, detectionCache, isSlideCountLoading, groupId가 적절하게 전달되고 있어요. forwardedDetectionMap ?? undefined 패턴으로 null을 undefined로 변환하여 optional prop과 호환되게 처리했어요.

src/pages/generate/hooks/useFurnitureCuration.ts (1)

218-260: 그룹 기반 캐시 무효화 로직이 잘 구현되었습니다.

groupId 유무에 따라 적절히 분기하여 캐시 클리어와 쿼리 무효화를 수행하고 있으며, 의존성 배열도 정확합니다.

detectedObjects,
detectionSignature,
}) => {
if (!groupId) return;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

!groupId 체크는 groupId === 0을 무효로 처리해요.

groupIdnumber 타입이므로 0이 유효한 ID일 수 있어요. !groupId는 0을 falsy로 처리하므로, 의도치 않게 유효한 그룹을 무시할 수 있어요.

- if (!groupId) return;
+ if (groupId == null) return;

Lines 80, 101, 117, 135에도 동일한 패턴이 적용되어 있어요.

🤖 Prompt for AI Agents
In src/pages/generate/stores/useCurationCacheStore.ts around lines 60 (and also
update the identical checks at lines 80, 101, 117, 135), the current guard uses
"if (!groupId) return;" which treats 0 as falsy and incorrectly rejects valid
groupId 0; replace these falsy checks with a null/undefined check (e.g., "if
(groupId == null) return;" or "if (groupId === undefined || groupId === null)
return;") so numeric 0 is allowed while still guarding against missing values.

Comment on lines +48 to +51
type PrefetchTask = {
imageId: number;
imageUrl: string;
};
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

객체 형태 타입은 interface로 정의 권장

코딩 가이드라인에 따르면 객체 형태는 interface를, union/별칭에는 type을 사용해요.

-type PrefetchTask = {
-  imageId: number;
-  imageUrl: string;
-};
+interface PrefetchTask {
+  imageId: number;
+  imageUrl: string;
+}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
type PrefetchTask = {
imageId: number;
imageUrl: string;
};
interface PrefetchTask {
imageId: number;
imageUrl: string;
}
🤖 Prompt for AI Agents
In src/pages/mypage/hooks/useDetectionPrefetch.ts around lines 48 to 51, the
PrefetchTask is declared with a `type` but the project guideline prefers
object-shaped declarations to use `interface`; change the declaration from a
`type` alias to an `interface` with the same properties (imageId: number;
imageUrl: string;) and update any local references if necessary to use the new
interface (no runtime behavior changes required).

Comment on lines +120 to +124
try {
targetImage = await loadImageElement(imageUrl);
} catch {
targetImage = await loadCorsImage(imageUrl);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

빈 catch 블록에 로깅 추가 고려

이미지 로드 실패 시 CORS 이미지 로더로 폴백하는 것은 좋지만, 디버깅을 위해 초기 실패를 로깅하면 문제 추적에 도움이 돼요.

         try {
           targetImage = await loadImageElement(imageUrl);
-        } catch {
+        } catch (loadError) {
+          // CORS 폴백으로 재시도 - 초기 실패는 예상 가능한 케이스
           targetImage = await loadCorsImage(imageUrl);
         }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
try {
targetImage = await loadImageElement(imageUrl);
} catch {
targetImage = await loadCorsImage(imageUrl);
}
try {
targetImage = await loadImageElement(imageUrl);
} catch (loadError) {
logFurniturePipelineEvent(
'image-load-failed-fallback-to-cors',
{ error: loadError instanceof Error ? loadError.message : String(loadError), imageUrl },
{ level: 'warn' }
);
targetImage = await loadCorsImage(imageUrl);
}
🤖 Prompt for AI Agents
In src/pages/mypage/hooks/useDetectionPrefetch.ts around lines 120-124, the
catch block that falls back from loadImageElement to loadCorsImage silently
swallows the initial error; update the catch to capture the caught error and log
it (using the existing logger or console with a clear message and the error),
then continue to call loadCorsImage so debugging info is preserved while
maintaining the fallback behavior.

@github-actions
Copy link

빌드 결과

빌드 실패 ❌

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (7)
src/pages/generate/stores/useDetectionCacheStore.ts (2)

45-58: imageId 검증 로직 개선 권장

if (!imageId) 체크는 0을 falsy로 처리해요. imageId가 1부터 시작하는 것이 보장된다면 현재 구현도 괜찮지만, 더 명시적인 검증을 권장해요.

-      if (!imageId) return;
+      if (imageId == null || imageId <= 0) return;

60-66: removeEntry에서 구조 분해 할당 사용 권장

delete 연산자 대신 구조 분해 할당을 사용하면 불변성 패턴이 더 명확해져요.

       removeEntry: (imageId) =>
         set((state) => {
-          const nextImages = { ...state.images };
-          delete nextImages[imageId];
-          return { images: nextImages };
+          const { [imageId]: _, ...nextImages } = state.images;
+          return { images: nextImages };
         }),
src/pages/mypage/hooks/useMypage.ts (2)

22-34: useMyPageUserQuery로 함수명 변경 필요

코딩 가이드라인에 따르면 TanStack Query 훅은 use + 행위 + 대상 + Query/Mutation 형식을 따라야 해요. useMyPageUseruseMyPageUserQuery로 변경해 주세요.

추가로, ...optionsstaleTime/refetchOnWindowFocus 앞에 있어서 호출자가 이 값들을 덮어쓸 수 없어요. 다른 훅들과 동작을 통일하려면 ...options를 마지막으로 이동하거나, 의도적인 차이라면 주석을 남겨주세요.

-export const useMyPageUser = (options?: UseMyPageUserOptions) => {
-  return useQuery<QueryResult<ReturnType<typeof getMyPageUser>>>({
+export const useMyPageUserQuery = (options?: UseMyPageUserOptions) => {
+  return useQuery<QueryResult<ReturnType<typeof getMyPageUser>>>({
     queryKey: [QUERY_KEY.MYPAGE_USER],
     queryFn: getMyPageUser,
-    ...options,
     staleTime: 15 * 60 * 1000,
     refetchOnWindowFocus: false,
+    ...options,
   });
 };

57-71: useMyPageImageDetailQuery로 함수명 변경 필요

TanStack Query 훅 네이밍 컨벤션에 따라 Query 접미사를 추가해 주세요.

-export const useMyPageImageDetail = (
+export const useMyPageImageDetailQuery = (
   houseId: number,
   options?: ImageDetailOptions
 ) => {
src/pages/generate/pages/result/ResultPage.tsx (1)

84-103: 렌더 단계에서 result 재할당 패턴 개선 필요

let result로 선언 후 조건에 따라 재할당하는 패턴은 예측하기 어려운 동작을 유발할 수 있어요. useMemo를 사용해 안정적으로 계산하는 것을 권장해요.

- let result = locationState?.result;
  const forwardedUserProfile = locationState?.userProfile ?? null;
  const initialHistory = locationState?.initialHistory ?? null;
  const forwardedDetection = locationState?.cachedDetection ?? null;

- if (!result && initialHistory) {
-   result = {
-     imageInfoResponses: [toGenerateImageData(initialHistory)],
-   } as UnifiedGenerateImageResult;
- }
+ const initialResult = useMemo(() => {
+   if (locationState?.result) return locationState.result;
+   if (initialHistory) {
+     return {
+       imageInfoResponses: [toGenerateImageData(initialHistory)],
+     } as UnifiedGenerateImageResult;
+   }
+   return null;
+ }, [locationState?.result, initialHistory]);
src/pages/generate/hooks/useFurnitureCuration.ts (2)

124-162: 빌드 실패: onSuccess는 React Query v5에서 제거되었어요

useQueryonSuccess 콜백은 React Query v5부터 제거되어 TypeScript 오버로드 매칭 에러(TS2769)가 발생해요. 캐시 저장 로직을 useEffect로 이동해야 해요.

  const query = useQuery<
    FurnitureCategoriesResponse,
    Error,
    FurnitureCategoriesResponse,
    CategoriesQueryKey
  >({
    queryKey: categoriesQueryKey,
    queryFn: () =>
      getGeneratedImageCategories(imageId!, normalizedDetectedObjects),
    enabled:
      Boolean(imageId) &&
      normalizedDetectedObjects.length > 0 &&
      !shouldSkipFetchWithCache,
    staleTime: 15 * 60 * 1000,
    gcTime: 30 * 60 * 1000,
    initialData:
      groupId && groupCategoriesEntry
        ? () => groupCategoriesEntry.response
        : undefined,
-   onSuccess: (data: FurnitureCategoriesResponse) => {
-     if (!groupId) return;
-     const existing =
-       useCurationCacheStore.getState().groups[groupId]?.categories ?? null;
-     if (
-       existing &&
-       existing.detectionSignature === detectionSignature &&
-       existing.response === data
-     ) {
-       return;
-     }
-     saveGroupCategories({
-       groupId,
-       response: data,
-       detectedObjects: normalizedDetectedObjects,
-       detectionSignature,
-     });
-   },
  });

+ useEffect(() => {
+   if (!groupId || !query.data || !query.isSuccess) return;
+   const existing =
+     useCurationCacheStore.getState().groups[groupId]?.categories ?? null;
+   if (
+     existing &&
+     existing.detectionSignature === detectionSignature &&
+     existing.response === query.data
+   ) {
+     return;
+   }
+   saveGroupCategories({
+     groupId,
+     response: query.data,
+     detectedObjects: normalizedDetectedObjects,
+     detectionSignature,
+   });
+ }, [groupId, query.data, query.isSuccess, detectionSignature, normalizedDetectedObjects, saveGroupCategories]);

218-251: 빌드 실패: onSuccess는 React Query v5에서 제거되었어요

마찬가지로 useGeneratedProductsQuery에서도 onSuccess 콜백을 useEffect로 이동해야 해요.

  const query = useQuery<
    FurnitureProductsInfoResponse,
    Error,
    FurnitureProductsInfoResponse,
    ProductsQueryKey
  >({
    queryKey: productsQueryKey,
    queryFn: () => getGeneratedImageProducts(imageId!, categoryId!),
    enabled:
      Boolean(imageId) && Boolean(categoryId) && !shouldSkipFetchWithCache,
    staleTime: 5 * 60 * 1000,
    gcTime: 30 * 60 * 1000,
    initialData:
      groupId && productCacheEntry
        ? () => productCacheEntry.response
        : undefined,
-   onSuccess: (data: FurnitureProductsInfoResponse) => {
-     if (!groupId || !categoryId) return;
-     const groupCache = useCurationCacheStore.getState().groups[groupId];
-     const existing = groupCache?.products[categoryId] ?? null;
-     if (existing?.response === data) {
-       return;
-     }
-     saveGroupProducts({
-       groupId,
-       categoryId,
-       response: data,
-     });
-   },
  });

+ useEffect(() => {
+   if (!groupId || !categoryId || !query.data || !query.isSuccess) return;
+   const groupCache = useCurationCacheStore.getState().groups[groupId];
+   const existing = groupCache?.products[categoryId] ?? null;
+   if (existing?.response === query.data) {
+     return;
+   }
+   saveGroupProducts({
+     groupId,
+     categoryId,
+     response: query.data,
+   });
+ }, [groupId, categoryId, query.data, query.isSuccess, saveGroupProducts]);

  return query;
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1b88c95 and 0604fba.

📒 Files selected for processing (4)
  • src/pages/generate/hooks/useFurnitureCuration.ts (5 hunks)
  • src/pages/generate/pages/result/ResultPage.tsx (7 hunks)
  • src/pages/generate/stores/useDetectionCacheStore.ts (1 hunks)
  • src/pages/mypage/hooks/useMypage.ts (2 hunks)
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (README.md)

**/*.{ts,tsx}: Do not use var; prefer const and use let only when reassignment is required
Prefer destructuring assignment where appropriate
Use template literals for string composition instead of concatenation
Use clear, unabbreviated names for variables and functions (camelCase)
Use arrow functions for function definitions
Function names should be verb-based (get/create/check/handle/is/has/can) reflecting behavior
Types and interfaces must use PascalCase; do not use I/T/Type prefixes or suffixes (except generic T)
Props and State type names must be ComponentNameProps and ComponentNameState; API types use {Resource}Request/{Resource}Response
Use interface for component props/state and API request/response and object shapes; use type for unions, intersections, function signatures, and tuples
Use path aliases (e.g., @apis/user, @components/Button) instead of deep relative paths
When using TanStack Query, use QUERY_KEYS factory rather than hardcoded query keys

Files:

  • src/pages/generate/stores/useDetectionCacheStore.ts
  • src/pages/mypage/hooks/useMypage.ts
  • src/pages/generate/hooks/useFurnitureCuration.ts
  • src/pages/generate/pages/result/ResultPage.tsx
src/**/*.ts

⚙️ CodeRabbit configuration file

src/**/*.ts: TypeScript 파일 리뷰 시:

  • 타입/인터페이스는 PascalCase 사용, Type/I/T 접두사·접미사 금지 (제네릭 T 제외)
  • interface는 객체 타입, type은 union/별칭에 사용하는지 확인
  • API Response는 XXXResponse 네이밍 따르는지 확인
  • var 사용 금지, const 우선 사용 확인
  • type-only import 사용(import type), 불필요한 런타임 의존성 제거
  • satisfies 연산자 활용으로 정확한 타입 보장

Files:

  • src/pages/generate/stores/useDetectionCacheStore.ts
  • src/pages/mypage/hooks/useMypage.ts
  • src/pages/generate/hooks/useFurnitureCuration.ts
src/**

⚙️ CodeRabbit configuration file

src/**: 전체 프로젝트 코드 리뷰 시:

기본 규칙

  • 모든 컴포넌트는 반드시 TypeScript로 작성
  • SVG는 ?react 쿼리를 통해 React 컴포넌트로 임포트
  • 스타일은 반드시 Vanilla Extract(.css.ts)로 작성
  • 절대 경로 임포트 사용 (@pages, @shared 등)
  • overlay-kit을 통한 모달 관리 패턴 준수

네이밍 컨벤션

  • 컴포넌트와 클래스는 PascalCase 사용
  • 폴더명은 camelCase 사용
  • 파일명은 camelCase 사용 (컴포넌트 파일 제외)
  • 변수와 함수는 camelCase 사용
  • 상수는 BIG_SNAKE_CASE 사용
  • 모든 타입/인터페이스는 PascalCase, Type/I/T 접두사·접미사 금지 (제네릭 T 제외)
  • Props 타입은 컴포넌트명 + Props 형식
  • API Response 타입은 XXXResponse 형식
  • 함수 네이밍: get/create/check/handle/is/has/can 접두사 활용
  • API 함수는 HTTP메서드 + 명사 형식 (getUserList, postComment)
  • TanStack Query 훅: use + 행위 + 대상 + Query/Mutation (예: useGetUserListQuery)
  • 커스텀 훅 파일명: use*.ts

보안 및 성능

  • 환경 변수는 반드시 import.meta.env 사용
  • API 키나 시크릿을 하드코딩하지 않음
  • React.memo, useMemo, useCallback의 적절한 사용
  • 큰 번들 사이즈 경고 (dynamic import 제안)
  • 템플릿 리터럴 사용 권장 (문자열 조합 시)
  • 구조 분해 할당 적극 활용

커밋 및 브랜치 패턴

  • 커밋 타입: feat, fix, docs, style, refactor, test, chore, design, comment, rename, remove
  • 브랜치 패턴: type/description/#issue-number 형식

Files:

  • src/pages/generate/stores/useDetectionCacheStore.ts
  • src/pages/mypage/hooks/useMypage.ts
  • src/pages/generate/hooks/useFurnitureCuration.ts
  • src/pages/generate/pages/result/ResultPage.tsx
src/**/hooks/use*.ts

📄 CodeRabbit inference engine (README.md)

Custom hook files must be named with use prefix (use*.ts)

Files:

  • src/pages/mypage/hooks/useMypage.ts
  • src/pages/generate/hooks/useFurnitureCuration.ts
src/**/hooks/**/*.ts

⚙️ CodeRabbit configuration file

src/**/hooks/**/*.ts: 커스텀 훅 리뷰 시:

  • 파일명/함수명은 use* 접두사 사용
  • 의존성 배열(exhaustive-deps) 준수, 참조 안정성 유지(함수/객체 메모이제이션)
  • 반환 값 일관성(tuple/object), 에러/로딩 상태 명확화
  • 제네릭 훅은 타입 매개변수 기본값 검토
  • TanStack Query 훅 네이밍: use + 행위 + 대상 + Query/Mutation (예: useGetUserListQuery)

Files:

  • src/pages/mypage/hooks/useMypage.ts
  • src/pages/generate/hooks/useFurnitureCuration.ts
🧠 Learnings (9)
📚 Learning: 2025-10-09T08:24:59.296Z
Learnt from: CR
Repo: TEAM-HOUME/HOUME-CLIENT PR: 0
File: README.md:0-0
Timestamp: 2025-10-09T08:24:59.296Z
Learning: Applies to src/**/hooks/use*.ts : Custom hook files must be named with use prefix (use*.ts)

Applied to files:

  • src/pages/mypage/hooks/useMypage.ts
📚 Learning: 2025-10-09T08:24:59.296Z
Learnt from: CR
Repo: TEAM-HOUME/HOUME-CLIENT PR: 0
File: README.md:0-0
Timestamp: 2025-10-09T08:24:59.296Z
Learning: Applies to src/**/hooks/use*{Query,Mutation}.ts : TanStack Query hooks must follow use + action + target + Query/Mutation naming (e.g., useGetUserListQuery, useCreateUserMutation)

Applied to files:

  • src/pages/mypage/hooks/useMypage.ts
📚 Learning: 2025-09-07T12:47:26.179Z
Learnt from: maehwasoo
Repo: TEAM-HOUME/HOUME-CLIENT PR: 291
File: src/pages/generate/hooks/useGenerate.ts:31-31
Timestamp: 2025-09-07T12:47:26.179Z
Learning: 프로젝트에서 React Query 훅은 Query 접미사를 붙이는 네이밍 컨벤션을 따릅니다. (예: useGetResultData → useGetResultDataQuery)

Applied to files:

  • src/pages/mypage/hooks/useMypage.ts
📚 Learning: 2025-10-09T08:24:59.296Z
Learnt from: CR
Repo: TEAM-HOUME/HOUME-CLIENT PR: 0
File: README.md:0-0
Timestamp: 2025-10-09T08:24:59.296Z
Learning: Applies to **/*.{ts,tsx} : When using TanStack Query, use QUERY_KEYS factory rather than hardcoded query keys

Applied to files:

  • src/pages/mypage/hooks/useMypage.ts
📚 Learning: 2025-10-09T08:24:59.296Z
Learnt from: CR
Repo: TEAM-HOUME/HOUME-CLIENT PR: 0
File: README.md:0-0
Timestamp: 2025-10-09T08:24:59.296Z
Learning: Applies to src/**/constants/queryKey.ts : Define and maintain QueryKey Factory in shared/constants/queryKey.ts using structured keys (e.g., USER.ALL/LIST/DETAIL)

Applied to files:

  • src/pages/mypage/hooks/useMypage.ts
📚 Learning: 2025-07-15T17:57:29.375Z
Learnt from: maehwasoo
Repo: TEAM-HOUME/HOUME-CLIENT PR: 180
File: src/pages/onboarding/hooks/useMoodBoardImage.hooks.ts:15-19
Timestamp: 2025-07-15T17:57:29.375Z
Learning: React Query의 queryKey에 사용되지 않는 파라미터를 포함하면 불필요한 캐시 분할이 발생합니다. queryKey는 실제로 쿼리 함수에서 사용되는 파라미터만 포함해야 합니다.

Applied to files:

  • src/pages/mypage/hooks/useMypage.ts
📚 Learning: 2025-10-05T19:50:38.893Z
Learnt from: soyyyyy
Repo: TEAM-HOUME/HOUME-CLIENT PR: 328
File: src/pages/generate/pages/result/curationSheet/CurationSheet.tsx:76-87
Timestamp: 2025-10-05T19:50:38.893Z
Learning: In TypeScript/React projects using Zustand: when addressing performance issues related to state updates causing unnecessary re-renders, prefer splitting Zustand selectors over wrapping components with React.memo. The user soyyyyy prefers this approach as it addresses the root cause of re-renders at the state subscription level.

Applied to files:

  • src/pages/generate/pages/result/ResultPage.tsx
📚 Learning: 2025-10-13T04:31:50.173Z
Learnt from: earl9rey
Repo: TEAM-HOUME/HOUME-CLIENT PR: 339
File: src/pages/mypage/components/history/HistorySection.tsx:57-61
Timestamp: 2025-10-13T04:31:50.173Z
Learning: src/pages/mypage/components/history/HistorySection.tsx 컴포넌트는 더 이상 뷰에서 사용되지 않는 데드 코드입니다. MyPage.tsx에서는 GeneratedImagesSection만 사용하고 있습니다.

Applied to files:

  • src/pages/generate/pages/result/ResultPage.tsx
📚 Learning: 2025-07-16T04:24:52.968Z
Learnt from: soyyyyy
Repo: TEAM-HOUME/HOUME-CLIENT PR: 194
File: src/pages/generate/components/result/ResultPage.tsx:20-20
Timestamp: 2025-07-16T04:24:52.968Z
Learning: ResultPage 컴포넌트의 하드코딩된 imageId는 API 연동 작업 시 동적으로 수정될 예정입니다. 사용자는 API 연동과 함께 이러한 임시 값들을 처리하는 것을 선호합니다.

Applied to files:

  • src/pages/generate/pages/result/ResultPage.tsx
🧬 Code graph analysis (4)
src/pages/generate/stores/useDetectionCacheStore.ts (3)
src/pages/generate/types/detection.ts (1)
  • ProcessedDetections (22-25)
src/pages/generate/hooks/useFurnitureHotspots.ts (1)
  • FurnitureHotspot (41-41)
src/pages/generate/constants/furnitureCategoryMapping.ts (1)
  • FurnitureCategoryCode (25-25)
src/pages/mypage/hooks/useMypage.ts (3)
src/pages/mypage/apis/mypage.ts (3)
  • getMyPageUser (15-22)
  • getMyPageImages (28-37)
  • getMyPageImageDetail (43-52)
src/shared/constants/queryKey.ts (1)
  • QUERY_KEY (1-18)
src/pages/mypage/types/apis/MyPage.ts (1)
  • MyPageImageDetailData (41-43)
src/pages/generate/hooks/useFurnitureCuration.ts (6)
src/pages/generate/constants/furnitureCategoryMapping.ts (1)
  • FurnitureCategoryCode (25-25)
src/shared/constants/queryKey.ts (1)
  • QUERY_KEY (1-18)
src/pages/generate/stores/useCurationStore.ts (1)
  • useCurationStore (83-255)
src/pages/generate/stores/useCurationCacheStore.ts (1)
  • useCurationCacheStore (52-143)
src/pages/generate/types/furniture.ts (2)
  • FurnitureCategoriesResponse (10-12)
  • FurnitureProductsInfoResponse (29-32)
src/pages/generate/apis/furniture.ts (2)
  • getGeneratedImageCategories (14-23)
  • getGeneratedImageProducts (26-34)
src/pages/generate/pages/result/ResultPage.tsx (5)
src/pages/mypage/types/apis/MyPage.ts (3)
  • MyPageImageHistory (13-20)
  • MyPageUserData (4-8)
  • MyPageImageDetail (29-39)
src/pages/generate/types/generate.ts (3)
  • GenerateImageData (34-42)
  • GenerateImageAResponse (45-51)
  • GenerateImageBResponse (54-58)
src/pages/generate/stores/useDetectionCacheStore.ts (1)
  • DetectionCacheEntry (11-17)
src/pages/mypage/utils/resultNavigation.ts (1)
  • createImageDetailPlaceholder (50-54)
src/pages/generate/hooks/useGenerate.ts (1)
  • useGetResultDataQuery (66-75)
🪛 GitHub Actions: CI/CD Pipeline
src/pages/generate/hooks/useFurnitureCuration.ts

[error] 124-124: TypeScript error TS2769: No overload matches this call.

🪛 GitHub Check: build
src/pages/generate/hooks/useFurnitureCuration.ts

[failure] 124-124:
No overload matches this call.


[failure] 218-218:
No overload matches this call.

🔇 Additional comments (12)
src/pages/generate/stores/useDetectionCacheStore.ts (3)

1-9: LGTM!

Zustand persist 미들웨어와 sessionStorage를 활용한 캐시 스토어 설정이 적절해요. SSR 안전성을 위한 typeof window !== 'undefined' 체크도 올바르게 구현되어 있어요.


11-30: LGTM!

인터페이스 정의가 명확하고 타입 네이밍 컨벤션(DetectionCacheEntry, DetectionCacheStore)을 잘 따르고 있어요. Pick을 활용한 payload 타입 정의도 적절해요.


32-39: LGTM!

DetectionCachePersistedState 타입을 별도로 정의하여 createJSONStorage에 전달함으로써 이전 타입 에러가 해결되었어요.

src/pages/mypage/hooks/useMypage.ts (1)

11-17: LGTM!

UseQueryBaseOptions 헬퍼 타입을 도입하여 각 훅의 옵션 타입을 일관되게 정의한 점이 좋아요. Omit을 활용해 queryKeyqueryFn을 제외한 것도 적절해요.

src/pages/generate/pages/result/ResultPage.tsx (5)

44-54: LGTM!

toGenerateImageData 헬퍼 함수가 MyPageImageHistoryGenerateImageData로 명확하게 변환해요. 필드 매핑이 올바르게 구현되어 있어요.


116-123: LGTM!

hasValidHouseId, shouldFetchExternalResult, shouldFetchMypageDetail 플래그를 사용한 세분화된 API 호출 제어가 좋아요. 불필요한 API 호출을 방지하고 데이터 흐름을 명확하게 해요.


133-144: LGTM!

useMyPageImageDetail 훅 사용과 placeholderData를 통한 초기 데이터 제공이 적절해요. isSlideCountLoading 상태 계산도 올바르게 구현되어 있어요.


168-179: LGTM!

historyById Map을 useMemo로 메모이제이션하여 O(1) 조회를 가능하게 한 점이 좋아요. 의존성 배열도 올바르게 설정되어 있어요.


386-402: LGTM!

GeneratedImgAGeneratedImgB에 새로운 props(userProfile, detectionCache, isSlideCountLoading, groupId)를 전달하는 구현이 적절해요. A/B 테스트 분기도 명확해요.

src/pages/generate/hooks/useFurnitureCuration.ts (3)

27-55: LGTM!

CategoriesQueryVariables, CategoriesQueryKey, ProductsQueryVariables, ProductsQueryKey 타입 정의가 명확해요. 쿼리 키 타입 안전성을 보장하는 좋은 패턴이에요.


70-111: LGTM!

그룹 수준 캐싱 로직이 잘 구현되어 있어요. shouldSkipFetchWithCache 플래그를 사용한 중복 요청 방지와 detectionSignature를 통한 캐시 유효성 검증이 적절해요.


271-312: LGTM!

useInvalidateCurationQueries 훅이 그룹 컨텍스트를 지원하도록 확장되었어요. clearGroupCategoriesclearGroupProduct를 활용한 캐시 무효화 로직이 적절해요.

Comment on lines 39 to 52
type UseMyPageImagesOptions = UseQueryBaseOptions<
QueryResult<ReturnType<typeof getMyPageImages>>
>;

export const useMyPageImages = (options?: UseMyPageImagesOptions) => {
return useQuery<QueryResult<ReturnType<typeof getMyPageImages>>>({
queryKey: [QUERY_KEY.MYPAGE_IMAGES],
queryFn: getMyPageImages,
staleTime: 0,
staleTime: 15 * 60 * 1000, // 15분 캐시
gcTime: 30 * 60 * 1000,
refetchOnWindowFocus: false,
...options,
});
};
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

useMyPageImagesQuery로 함수명 변경 필요

TanStack Query 훅 네이밍 컨벤션에 따라 Query 접미사를 추가해 주세요.

-export const useMyPageImages = (options?: UseMyPageImagesOptions) => {
+export const useMyPageImagesQuery = (options?: UseMyPageImagesOptions) => {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
type UseMyPageImagesOptions = UseQueryBaseOptions<
QueryResult<ReturnType<typeof getMyPageImages>>
>;
export const useMyPageImages = (options?: UseMyPageImagesOptions) => {
return useQuery<QueryResult<ReturnType<typeof getMyPageImages>>>({
queryKey: [QUERY_KEY.MYPAGE_IMAGES],
queryFn: getMyPageImages,
staleTime: 0,
staleTime: 15 * 60 * 1000, // 15분 캐시
gcTime: 30 * 60 * 1000,
refetchOnWindowFocus: false,
...options,
});
};
type UseMyPageImagesOptions = UseQueryBaseOptions<
QueryResult<ReturnType<typeof getMyPageImages>>
>;
export const useMyPageImagesQuery = (options?: UseMyPageImagesOptions) => {
return useQuery<QueryResult<ReturnType<typeof getMyPageImages>>>({
queryKey: [QUERY_KEY.MYPAGE_IMAGES],
queryFn: getMyPageImages,
staleTime: 15 * 60 * 1000, // 15분 캐시
gcTime: 30 * 60 * 1000,
refetchOnWindowFocus: false,
...options,
});
};
🤖 Prompt for AI Agents
In src/pages/mypage/hooks/useMypage.ts around lines 39 to 52, the exported hook
name should follow TanStack Query naming conventions: rename the function from
useMyPageImages to useMyPageImagesQuery; update the export and all internal
references/usages across the codebase (imports, tests, storybooks, and any
re-exports) to the new name, and adjust any related types or documentation
annotations if they reference the old name.

@github-actions
Copy link

빌드 결과

빌드 성공 🎊

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
src/pages/generate/hooks/useGenerate.ts (1)

235-249: 폴백 경로에서도 동일 캐시 무효화를 적용한 점은 좋지만, 중복 로직은 공통화하는 게 안전해 보입니다

폴백 이미지 생성 성공 시에도 resetGenerate · setNavigationData · setApiCompleted 후에 generateImageMYPAGE_IMAGES 쿼리를 모두 invalidation 해서, 메인/폴백 경로 간 상태 일관성이 잘 맞춰진 점은 좋습니다.

다만 현재는 다음 두 가지가 약간 아쉽습니다.

  1. 동일 invalidation 로직 중복

    • useGenerateImageApionSuccess와 여기 폴백 성공 useEffect에서 동일한 두 줄이 복제되어 있습니다.
    • 이후 관련 쿼리 키가 늘어나거나 이름이 바뀔 경우 한쪽만 수정되는 버그 리스크가 있으니, 작은 helper로 추출해 두는 걸 권장합니다.

    예를 들면 파일 상단(훅 바깥)에 다음과 같이 두고:

  • const invalidateGenerateQueries = () => {
  • queryClient.invalidateQueries({ queryKey: ['generateImage'] });
  • queryClient.invalidateQueries({ queryKey: [QUERY_KEY.MYPAGE_IMAGES] });
  • };
    
    두 군데에서 공통으로 호출하는 식입니다:
    
    ```diff
    
  •  queryClient.invalidateQueries({ queryKey: ['generateImage'] });
    
  •  queryClient.invalidateQueries({ queryKey: [QUERY_KEY.MYPAGE_IMAGES] });
    
  •  invalidateGenerateQueries();
    
    
    
  1. 'generateImage' 쿼리 키 상수화
    • 현재 'generateImage'는 문자열 리터럴로만 사용되고, 다른 쿼리 키는 QUERY_KEY 상수로 관리되고 있습니다.
    • 나중에 키 이름을 바꾸거나 관련 쿼리가 늘어날 때를 생각하면, 이 역시 QUERY_KEY.GENERATE_IMAGE 같은 형태로 상수화해 두는 편이 유지보수에 더 안전합니다. (이 부분은 별도 리팩토링 PR로 처리해도 됩니다.)

위 내용은 이 PR을 막는 수준의 이슈는 아니고, 중장기적인 유지보수성과 실수 방지를 위한 리팩토링 제안입니다. 팀 컨벤션과 일정에 맞춰 고려해 주세요. (As per coding guidelines.)

src/pages/generate/pages/result/curationSheet/CurationSheet.tsx (2)

152-195: 카테고리별 프리패치 로직이 캐시와 잘 통합되어 있습니다

dedupeKey로 중복 프리패치를 막고, 그룹 캐시(groupProductCache)와 쿼리 캐시(getQueryData)를 먼저 확인한 뒤에만 prefetchQuery를 호출하는 구조가 잘 짜여 있어서, 불필요한 네트워크 호출 없이 초기 반응 속도를 높이는 데 효과적입니다.
다만, 장기적으로는 ProductPrefetchQueryKey 타입을 useFurnitureCuration.ts의 제품 쿼리 키 타입(구조가 동일함)에 맞춰 공용 타입으로 빼 두면 쿼리 키 구조가 양쪽에서 드리프트할 위험을 더 줄일 수 있습니다.


252-279: 카테고리 에러/빈 상태에서도 스켈레톤 칩이 노출되어 UX가 다소 혼란스러울 수 있습니다

필터 영역이 categories.length === 0 조건만 보고 스켈레톤 칩을 렌더링하다 보니,

  • categoriesQuery.isLoading일 때뿐 아니라
  • categoriesQuery.isError이거나
  • 실제로 감지된 가구가 없어 빈 상태인 경우에도

상단에는 항상 “로딩 중처럼 보이는” 스켈레톤 칩이 하나 노출됩니다. 아래 renderProductSection에서는 에러/빈 상태 메시지를 정확히 보여주고 있어서, 상단·하단 상태 표현이 서로 달라 사용자 입장에서는 약간 혼란스러울 수 있습니다.

categoriesQuery.isLoading일 때만 스켈레톤 칩을 노출하고, 에러/빈 상태에서는 필터 영역을 비우거나 disabled 칩(예: “카테고리 없음”)을 렌더링하는 식으로 시각적 상태를 일치시키는 것을 고려해 보시면 좋겠습니다.

Also applies to: 334-342

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0604fba and 1cb72be.

📒 Files selected for processing (3)
  • src/pages/generate/hooks/useFurnitureCuration.ts (5 hunks)
  • src/pages/generate/hooks/useGenerate.ts (2 hunks)
  • src/pages/generate/pages/result/curationSheet/CurationSheet.tsx (6 hunks)
🧰 Additional context used
📓 Path-based instructions (5)
src/**/hooks/use*.ts

📄 CodeRabbit inference engine (README.md)

Custom hook files must be named with use prefix (use*.ts)

Files:

  • src/pages/generate/hooks/useGenerate.ts
  • src/pages/generate/hooks/useFurnitureCuration.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (README.md)

**/*.{ts,tsx}: Do not use var; prefer const and use let only when reassignment is required
Prefer destructuring assignment where appropriate
Use template literals for string composition instead of concatenation
Use clear, unabbreviated names for variables and functions (camelCase)
Use arrow functions for function definitions
Function names should be verb-based (get/create/check/handle/is/has/can) reflecting behavior
Types and interfaces must use PascalCase; do not use I/T/Type prefixes or suffixes (except generic T)
Props and State type names must be ComponentNameProps and ComponentNameState; API types use {Resource}Request/{Resource}Response
Use interface for component props/state and API request/response and object shapes; use type for unions, intersections, function signatures, and tuples
Use path aliases (e.g., @apis/user, @components/Button) instead of deep relative paths
When using TanStack Query, use QUERY_KEYS factory rather than hardcoded query keys

Files:

  • src/pages/generate/hooks/useGenerate.ts
  • src/pages/generate/hooks/useFurnitureCuration.ts
  • src/pages/generate/pages/result/curationSheet/CurationSheet.tsx
src/**/hooks/**/*.ts

⚙️ CodeRabbit configuration file

src/**/hooks/**/*.ts: 커스텀 훅 리뷰 시:

  • 파일명/함수명은 use* 접두사 사용
  • 의존성 배열(exhaustive-deps) 준수, 참조 안정성 유지(함수/객체 메모이제이션)
  • 반환 값 일관성(tuple/object), 에러/로딩 상태 명확화
  • 제네릭 훅은 타입 매개변수 기본값 검토
  • TanStack Query 훅 네이밍: use + 행위 + 대상 + Query/Mutation (예: useGetUserListQuery)

Files:

  • src/pages/generate/hooks/useGenerate.ts
  • src/pages/generate/hooks/useFurnitureCuration.ts
src/**/*.ts

⚙️ CodeRabbit configuration file

src/**/*.ts: TypeScript 파일 리뷰 시:

  • 타입/인터페이스는 PascalCase 사용, Type/I/T 접두사·접미사 금지 (제네릭 T 제외)
  • interface는 객체 타입, type은 union/별칭에 사용하는지 확인
  • API Response는 XXXResponse 네이밍 따르는지 확인
  • var 사용 금지, const 우선 사용 확인
  • type-only import 사용(import type), 불필요한 런타임 의존성 제거
  • satisfies 연산자 활용으로 정확한 타입 보장

Files:

  • src/pages/generate/hooks/useGenerate.ts
  • src/pages/generate/hooks/useFurnitureCuration.ts
src/**

⚙️ CodeRabbit configuration file

src/**: 전체 프로젝트 코드 리뷰 시:

기본 규칙

  • 모든 컴포넌트는 반드시 TypeScript로 작성
  • SVG는 ?react 쿼리를 통해 React 컴포넌트로 임포트
  • 스타일은 반드시 Vanilla Extract(.css.ts)로 작성
  • 절대 경로 임포트 사용 (@pages, @shared 등)
  • overlay-kit을 통한 모달 관리 패턴 준수

네이밍 컨벤션

  • 컴포넌트와 클래스는 PascalCase 사용
  • 폴더명은 camelCase 사용
  • 파일명은 camelCase 사용 (컴포넌트 파일 제외)
  • 변수와 함수는 camelCase 사용
  • 상수는 BIG_SNAKE_CASE 사용
  • 모든 타입/인터페이스는 PascalCase, Type/I/T 접두사·접미사 금지 (제네릭 T 제외)
  • Props 타입은 컴포넌트명 + Props 형식
  • API Response 타입은 XXXResponse 형식
  • 함수 네이밍: get/create/check/handle/is/has/can 접두사 활용
  • API 함수는 HTTP메서드 + 명사 형식 (getUserList, postComment)
  • TanStack Query 훅: use + 행위 + 대상 + Query/Mutation (예: useGetUserListQuery)
  • 커스텀 훅 파일명: use*.ts

보안 및 성능

  • 환경 변수는 반드시 import.meta.env 사용
  • API 키나 시크릿을 하드코딩하지 않음
  • React.memo, useMemo, useCallback의 적절한 사용
  • 큰 번들 사이즈 경고 (dynamic import 제안)
  • 템플릿 리터럴 사용 권장 (문자열 조합 시)
  • 구조 분해 할당 적극 활용

커밋 및 브랜치 패턴

  • 커밋 타입: feat, fix, docs, style, refactor, test, chore, design, comment, rename, remove
  • 브랜치 패턴: type/description/#issue-number 형식

Files:

  • src/pages/generate/hooks/useGenerate.ts
  • src/pages/generate/hooks/useFurnitureCuration.ts
  • src/pages/generate/pages/result/curationSheet/CurationSheet.tsx
🧠 Learnings (2)
📚 Learning: 2025-07-15T17:57:29.375Z
Learnt from: maehwasoo
Repo: TEAM-HOUME/HOUME-CLIENT PR: 180
File: src/pages/onboarding/hooks/useMoodBoardImage.hooks.ts:15-19
Timestamp: 2025-07-15T17:57:29.375Z
Learning: React Query의 queryKey에 사용되지 않는 파라미터를 포함하면 불필요한 캐시 분할이 발생합니다. queryKey는 실제로 쿼리 함수에서 사용되는 파라미터만 포함해야 합니다.

Applied to files:

  • src/pages/generate/hooks/useGenerate.ts
📚 Learning: 2025-10-21T22:07:55.899Z
Learnt from: soyyyyy
Repo: TEAM-HOUME/HOUME-CLIENT PR: 341
File: src/pages/generate/pages/result/curationSheet/CurationSheet.tsx:63-68
Timestamp: 2025-10-21T22:07:55.899Z
Learning: In HOUME-CLIENT project: React keys for furniture product lists should use the stable `furnitureProductId` rather than the ephemeral `p.id` (recommendId). The recommendId changes per recommendation session, while furnitureProductId is a stable identifier for the product itself, enabling React to properly reconcile and reuse DOM elements across different recommendation sets.

Applied to files:

  • src/pages/generate/hooks/useFurnitureCuration.ts
🧬 Code graph analysis (2)
src/pages/generate/hooks/useGenerate.ts (2)
src/shared/apis/queryClient.ts (1)
  • queryClient (5-18)
src/shared/constants/queryKey.ts (1)
  • QUERY_KEY (1-18)
src/pages/generate/pages/result/curationSheet/CurationSheet.tsx (7)
src/pages/generate/hooks/useFurnitureCuration.ts (2)
  • useGeneratedCategoriesQuery (70-188)
  • useGeneratedProductsQuery (190-251)
src/pages/generate/stores/useCurationStore.ts (1)
  • useCurationStore (83-255)
src/pages/generate/stores/useCurationCacheStore.ts (1)
  • useCurationCacheStore (52-143)
src/shared/constants/queryKey.ts (1)
  • QUERY_KEY (1-18)
src/shared/apis/queryClient.ts (1)
  • queryClient (5-18)
src/pages/generate/types/furniture.ts (1)
  • FurnitureProductsInfoResponse (29-32)
src/pages/generate/apis/furniture.ts (1)
  • getGeneratedImageProducts (26-34)
🔇 Additional comments (3)
src/pages/generate/hooks/useGenerate.ts (1)

170-184: MyPage 이미지 캐시 무효화 추가 방향이 목적에 잘 맞습니다

이미지 생성 성공 시 QUERY_KEY.MYPAGE_IMAGES까지 invalidateQueries 해 주어서, 마이페이지 이미지 목록이 생성 결과와 더 잘 동기화될 것으로 보입니다. 이 변경 자체는 괜찮아 보입니다.

다만 다음 두 가지는 한 번만 점검해 주세요.

  1. 실제 MyPage 이미지 리스트 쿼리의 queryKey 형태 확인

    • 해당 쿼리가 queryKey: [QUERY_KEY.MYPAGE_IMAGES] 또는 [QUERY_KEY.MYPAGE_IMAGES, ...] 형태로 정의되어 있는지 확인해 주세요.
    • 만약 전혀 다른 문자열/배열로 정의되어 있다면, 이번 invalidation이 기대한 쿼리를 건드리지 못할 수 있습니다.
  2. MYPAGE_IMAGES 관련 추가 파라미터 여부

    • 페이징·필터 등으로 page, sort 같은 인자를 key에 붙였다면(예: [QUERY_KEY.MYPAGE_IMAGES, page]), 지금처럼 prefix만 넘기는 방식이면 전부 함께 무효화되므로 의도에 부합하는지 한 번 생각해 볼 만 합니다 (전체 새로고침 vs 특정 페이지만 무효화).

추가로, queryKey에 불필요한 파라미터를 넣지 않고 베이스 키만 사용한 점은 이전에 논의된 “불필요한 캐시 분할 방지” 전략과 잘 맞습니다. 기존 learnings를 반영한 올바른 방향으로 보입니다. (Based on learnings.)

src/pages/generate/pages/result/curationSheet/CurationSheet.tsx (1)

137-145: activeImageId 해제 시 시트를 자동으로 접는 조건 분기 구조가 명확합니다

activeImageId === null이면서 스냅 상태가 collapsed/hidden이 아닐 때만 collapsed로 강제하는 분기가 잘 분리되어 있어서, 이미지 선택 해제 시 시트가 과하게 깜빡이지 않고 자연스럽게 접히도록 제어되고 있습니다. 현재 로직 그대로 유지해도 무방해 보입니다.

src/pages/generate/hooks/useFurnitureCuration.ts (1)

27-55: 쿼리 키 타입 별도 정의로 타입 안전성이 좋아졌습니다

CategoriesQueryKey / ProductsQueryKey를 별도 타입으로 정의하고 useQuery<…, CategoriesQueryKey> 제네릭에 넘겨 준 덕분에, 쿼리 키 구조 변경 시 타입 레벨에서 바로 이탈을 잡아 줄 수 있어 유지보수성이 좋아졌습니다. TanStack Query v5의 제네릭 시그니처와도 잘 맞는 구성이라, 이후 invalidate 로직을 보완할 때도 도움이 될 것 같습니다.

Comment on lines 70 to 135
export const useGeneratedCategoriesQuery = (
groupId: number | null,
imageId: number | null
) => {
const selectCategory = useCurationStore((state) => state.selectCategory);
const imageState = useCurationStore((state) =>
imageId !== null ? (state.images[imageId] ?? null) : null
);

const detectedObjects = imageState?.detectedObjects ?? [];
const detectedObjects = useMemo<FurnitureCategoryCode[]>(
() => imageState?.detectedObjects ?? [],
[imageState?.detectedObjects]
);
const selectedCategoryId = imageState?.selectedCategoryId ?? null;

const query = useQuery<FurnitureCategoriesResponse>({
queryKey: [
QUERY_KEY.GENERATE_FURNITURE_CATEGORIES,
const normalizedDetectedObjects = useMemo<FurnitureCategoryCode[]>(
() => Array.from(new Set(detectedObjects)),
[detectedObjects]
);
const detectionSignature = useMemo(
() => normalizedDetectedObjects.slice().sort().join(','),
[normalizedDetectedObjects]
);

const groupCategoriesEntry = useCurationCacheStore((state) =>
groupId ? (state.groups[groupId]?.categories ?? null) : null
);
const saveGroupCategories = useCurationCacheStore(
(state) => state.saveCategories
);
const canUseGroupInitialData =
Boolean(groupId) &&
Boolean(groupCategoriesEntry) &&
groupCategoriesEntry?.detectionSignature === detectionSignature;

const categoriesQueryKey: CategoriesQueryKey = [
groupId
? QUERY_KEY.GENERATE_FURNITURE_CATEGORIES_GROUP
: QUERY_KEY.GENERATE_FURNITURE_CATEGORIES,
{
groupId,
imageId,
detectedObjects,
],
queryFn: () => getGeneratedImageCategories(imageId!, detectedObjects),
enabled: Boolean(imageId) && detectedObjects.length > 0,
staleTime: 60 * 1000,
gcTime: 5 * 60 * 1000,
detectionSignature,
codes: normalizedDetectedObjects,
},
];

const initialCategoriesResponse = canUseGroupInitialData
? groupCategoriesEntry!.response
: undefined;

const query = useQuery<
FurnitureCategoriesResponse,
Error,
FurnitureCategoriesResponse,
CategoriesQueryKey
>({
// queryKey에 이미지/감지값 전체를 직접 포함해 의존성 유지
queryKey: categoriesQueryKey,
queryFn: () =>
getGeneratedImageCategories(imageId!, normalizedDetectedObjects),
enabled: Boolean(imageId) && normalizedDetectedObjects.length > 0,
staleTime: 15 * 60 * 1000,
gcTime: 30 * 60 * 1000,
...(initialCategoriesResponse
? { initialData: initialCategoriesResponse }
: {}),
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

그룹 캐시가 있어도 항상 재요청을 보내는 구조가 의도인지 한 번 확인이 필요합니다

현재 구현을 보면:

  • 카테고리 쿼리

    • canUseGroupInitialDatainitialData 여부만 결정하고,
    • enabledBoolean(imageId) && normalizedDetectedObjects.length > 0만 보고 있습니다.
      → 즉, 그룹 캐시(detectionSignature까지 일치)가 있어도 네트워크 요청은 항상 한 번 더 나갑니다.
  • 상품 쿼리

    • productCacheEntry도 마찬가지로 initialData에만 쓰이고,
    • enabledBoolean(imageId) && Boolean(categoryId)만 보고 있어서, 그룹 캐시가 있을 때도 매번 재요청이 발생합니다.

이게 “그룹 캐시는 초기 뷰만 빠르게 만들고, 항상 최신 서버 데이터로 동기화한다”는 의도라면 문제 없지만, “동일 detectionSignature/카테고리 조합에 대해서는 네트워크 부하를 줄이고 싶다”는 요구였다면 enabled 또는 별도 플래그에 !canUseGroupInitialData/!productCacheEntry 조건을 섞는 편이 맞을 수 있습니다.

또한 캐시 저장용 useEffect에서도 query.isSuccess 대신 query.data 존재만 보고 있어, 이론상 “초기 데이터만 있는 상태”와 “실제 fetch 성공 데이터”를 동일하게 취급하게 됩니다. 현재 동작에는 큰 문제는 없지만, 성공 상태만 저장하려면 query.isSuccess를 추가로 체크하는 것도 고려해 볼 수 있습니다.

마지막으로, 이 파일에서도 groupId/categoryId를 여러 곳에서 truthy 체크(if (groupId), groupId && categoryId)로 사용하고 있어, 0 값이 들어올 경우 버그 가능성이 있습니다. CurationSheet와 동일하게 !== null / === null 비교로 통일하는 편이 더 안전합니다.

Also applies to: 138-162, 190-233, 235-249

🤖 Prompt for AI Agents
In src/pages/generate/hooks/useFurnitureCuration.ts around lines 70-135 (and
similarly apply to 138-162, 190-233, 235-249): the current logic uses group
cache only as initialData while always enabling network fetches and uses truthy
checks for IDs; to fix, change the query enabled conditions to also respect
cache presence (e.g., enabled: Boolean(imageId) &&
normalizedDetectedObjects.length > 0 && !canUseGroupInitialData) so no fetch
occurs when valid group cache matches, do the same for product queries (use
!productCacheEntry), update any effects that persist cache to check
query.isSuccess rather than only query.data before saving, and replace truthy ID
checks (if (groupId)/if (categoryId)) with explicit null checks (groupId !==
null / categoryId !== null) to avoid 0 being treated as missing.

@maehwasoo maehwasoo self-assigned this Dec 11, 2025
@github-actions
Copy link

빌드 결과

빌드 실패 ❌

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/pages/mypage/components/history/HistorySection.tsx (1)

10-76: 데드 코드 제거 필요

HistorySection 컴포넌트는 코드베이스 어디에도 임포트되지 않고 있습니다. MyPage.tsxGeneratedImagesSection, ProfileSection, SavedItemsSection만 사용하고 있으므로, 이 파일과 HistorySection.css.ts는 제거하는 것이 좋습니다.

♻️ Duplicate comments (6)
src/pages/mypage/hooks/useDetectionPrefetch.ts (3)

50-53: 객체 형태 타입은 interface로 정의 권장

코딩 가이드라인에 따르면 객체 형태는 interface를, union/별칭에는 type을 사용해요.

-type PrefetchTask = {
-  imageId: number;
-  imageUrl: string;
-};
+interface PrefetchTask {
+  imageId: number;
+  imageUrl: string;
+}

122-126: 빈 catch 블록에 로깅 추가 고려

이미지 로드 실패 시 CORS 폴백으로 재시도하는 것은 좋지만, 초기 실패를 로깅하면 디버깅에 도움이 돼요.

         try {
           targetImage = await loadImageElement(imageUrl);
-        } catch {
+        } catch (loadError) {
+          // CORS 폴백으로 재시도 - 초기 실패는 예상 가능한 케이스
           targetImage = await loadCorsImage(imageUrl);
         }

144-147: console.warn 대신 로깅 유틸리티 사용 권장

프로덕션 환경에서 console.warn은 디버깅에 적합하지 않을 수 있어요. 프로젝트의 logFurniturePipelineEvent 같은 로깅 유틸리티 사용을 권장해요.

+import {
+  logFurniturePipelineEvent,
+} from '@pages/generate/utils/furniturePipelineMonitor';

// ...

-          console.warn('감지 프리페치 실패', inferenceError);
+          logFurniturePipelineEvent(
+            'detection-prefetch-failed',
+            { error: inferenceError instanceof Error ? inferenceError.message : String(inferenceError) },
+            { level: 'warn' }
+          );
src/pages/mypage/hooks/useMypage.ts (2)

26-33: useMyPageUser에서만 options 우선순위가 다른 점 정리 필요

useMyPageUser에서는 ...optionsstaleTime/refetchOnWindowFocus 앞에 있어서 호출자가 이 값들을 덮어쓸 수 없어요. 반면 다른 훅들은 ...options가 마지막에 있어서 덮어쓸 수 있어요. 의도된 차이라면 주석으로 이유를 남겨 주세요.

   return useQuery<QueryResult<ReturnType<typeof getMyPageUser>>>({
     queryKey: [QUERY_KEY.MYPAGE_USER],
     queryFn: getMyPageUser,
-    ...options,
     staleTime: 15 * 60 * 1000,
     refetchOnWindowFocus: false,
+    ...options,
   });

60-70: useMyPageImageDetailuseMyPageImageDetailQuery로 함수명 변경 필요

TanStack Query 훅 네이밍 컨벤션(use + 행위 + 대상 + Query/Mutation)에 따라 Query 접미사를 추가해 주세요.

-export const useMyPageImageDetail = (
+export const useMyPageImageDetailQuery = (
   houseId: number,
   options?: ImageDetailOptions
 ) => {
src/pages/mypage/components/section/generatedImages/GeneratedImagesSection.tsx (1)

20-42: Props 인터페이스와 SSR 가드가 잘 구현되어 있어요.

GeneratedImagesSectionProps 네이밍이 컴포넌트명+Props 형식을 따르고 있어요. typeof window === 'undefined' 가드로 SSR 환경을 안전하게 처리하고 있네요.

sessionStorage 키 'mypage-image-loaded'가 Line 35와 Line 107에서 중복 사용되고 있어요. 상수로 추출하면 유지보수성이 향상돼요.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1cb72be and ec60ce1.

📒 Files selected for processing (8)
  • src/pages/generate/hooks/useFurnitureCuration.ts (5 hunks)
  • src/pages/generate/pages/result/ResultPage.tsx (7 hunks)
  • src/pages/generate/pages/result/components/GeneratedImgA.tsx (8 hunks)
  • src/pages/generate/pages/result/curationSheet/CurationSheet.tsx (6 hunks)
  • src/pages/mypage/components/history/HistorySection.tsx (1 hunks)
  • src/pages/mypage/components/section/generatedImages/GeneratedImagesSection.tsx (2 hunks)
  • src/pages/mypage/hooks/useDetectionPrefetch.ts (1 hunks)
  • src/pages/mypage/hooks/useMypage.ts (2 hunks)
🧰 Additional context used
📓 Path-based instructions (6)
src/pages/**

📄 CodeRabbit inference engine (README.md)

Organize code using a feature-based structure within pages/ directory, grouping page-specific logic (components, constants, apis, types, utils, hooks) together by domain

Files:

  • src/pages/generate/pages/result/ResultPage.tsx
  • src/pages/mypage/hooks/useDetectionPrefetch.ts
  • src/pages/generate/pages/result/components/GeneratedImgA.tsx
  • src/pages/mypage/components/section/generatedImages/GeneratedImagesSection.tsx
  • src/pages/mypage/components/history/HistorySection.tsx
  • src/pages/generate/pages/result/curationSheet/CurationSheet.tsx
  • src/pages/mypage/hooks/useMypage.ts
  • src/pages/generate/hooks/useFurnitureCuration.ts
src/**

⚙️ CodeRabbit configuration file

src/**: 전체 프로젝트 코드 리뷰 시:

기본 규칙

  • 모든 컴포넌트는 반드시 TypeScript로 작성
  • SVG는 ?react 쿼리를 통해 React 컴포넌트로 임포트
  • 스타일은 반드시 Vanilla Extract(.css.ts)로 작성
  • 절대 경로 임포트 사용 (@pages, @shared 등)
  • overlay-kit을 통한 모달 관리 패턴 준수

네이밍 컨벤션

  • 컴포넌트와 클래스는 PascalCase 사용
  • 폴더명은 camelCase 사용
  • 파일명은 camelCase 사용 (컴포넌트 파일 제외)
  • 변수와 함수는 camelCase 사용
  • 상수는 BIG_SNAKE_CASE 사용
  • 모든 타입/인터페이스는 PascalCase, Type/I/T 접두사·접미사 금지 (제네릭 T 제외)
  • Props 타입은 컴포넌트명 + Props 형식
  • API Response 타입은 XXXResponse 형식
  • 함수 네이밍: get/create/check/handle/is/has/can 접두사 활용
  • API 함수는 HTTP메서드 + 명사 형식 (getUserList, postComment)
  • TanStack Query 훅: use + 행위 + 대상 + Query/Mutation (예: useGetUserListQuery)
  • 커스텀 훅 파일명: use*.ts

보안 및 성능

  • 환경 변수는 반드시 import.meta.env 사용
  • API 키나 시크릿을 하드코딩하지 않음
  • React.memo, useMemo, useCallback의 적절한 사용
  • 큰 번들 사이즈 경고 (dynamic import 제안)
  • 템플릿 리터럴 사용 권장 (문자열 조합 시)
  • 구조 분해 할당 적극 활용

커밋 및 브랜치 패턴

  • 커밋 타입: feat, fix, docs, style, refactor, test, chore, design, comment, rename, remove
  • 브랜치 패턴: type/description/#issue-number 형식

Files:

  • src/pages/generate/pages/result/ResultPage.tsx
  • src/pages/mypage/hooks/useDetectionPrefetch.ts
  • src/pages/generate/pages/result/components/GeneratedImgA.tsx
  • src/pages/mypage/components/section/generatedImages/GeneratedImagesSection.tsx
  • src/pages/mypage/components/history/HistorySection.tsx
  • src/pages/generate/pages/result/curationSheet/CurationSheet.tsx
  • src/pages/mypage/hooks/useMypage.ts
  • src/pages/generate/hooks/useFurnitureCuration.ts
src/**/hooks/**/*.ts

⚙️ CodeRabbit configuration file

src/**/hooks/**/*.ts: 커스텀 훅 리뷰 시:

  • 파일명/함수명은 use* 접두사 사용
  • 의존성 배열(exhaustive-deps) 준수, 참조 안정성 유지(함수/객체 메모이제이션)
  • 반환 값 일관성(tuple/object), 에러/로딩 상태 명확화
  • 제네릭 훅은 타입 매개변수 기본값 검토
  • TanStack Query 훅 네이밍: use + 행위 + 대상 + Query/Mutation (예: useGetUserListQuery)

Files:

  • src/pages/mypage/hooks/useDetectionPrefetch.ts
  • src/pages/mypage/hooks/useMypage.ts
  • src/pages/generate/hooks/useFurnitureCuration.ts
src/**/*.ts

⚙️ CodeRabbit configuration file

src/**/*.ts: TypeScript 파일 리뷰 시:

  • 타입/인터페이스는 PascalCase 사용, Type/I/T 접두사·접미사 금지 (제네릭 T 제외)
  • interface는 객체 타입, type은 union/별칭에 사용하는지 확인
  • API Response는 XXXResponse 네이밍 따르는지 확인
  • var 사용 금지, const 우선 사용 확인
  • type-only import 사용(import type), 불필요한 런타임 의존성 제거
  • satisfies 연산자 활용으로 정확한 타입 보장

Files:

  • src/pages/mypage/hooks/useDetectionPrefetch.ts
  • src/pages/mypage/hooks/useMypage.ts
  • src/pages/generate/hooks/useFurnitureCuration.ts
src/**/components/**/*.tsx

⚙️ CodeRabbit configuration file

src/**/components/**/*.tsx: 컴포넌트 파일 리뷰 시:

  • 파일명이 PascalCase인지 확인
  • Props 타입이 '컴포넌트명Props' 형식인지 확인
  • 단일 export는 default export 권장(팀 가이드 우선 적용)
  • 이벤트 핸들러는 handle로 시작하는지 확인
  • 접근성 준수: ARIA 속성, label 연결, 키보드 포커스
  • 성능: memo/useMemo/useCallback 적절 사용
  • ref 전달 시 forwardRef 사용 및 displayName 지정

Files:

  • src/pages/generate/pages/result/components/GeneratedImgA.tsx
  • src/pages/mypage/components/section/generatedImages/GeneratedImagesSection.tsx
  • src/pages/mypage/components/history/HistorySection.tsx
src/pages/**/components/**

⚙️ CodeRabbit configuration file

src/pages/**/components/**: 페이지별 컴포넌트 리뷰 시:

  • 해당 페이지의 도메인 로직과 긴밀한 결합 확인
  • 재사용 가능한 컴포넌트는 shared로 이동 제안
  • 페이지 특화 타입과 상수의 적절한 활용 확인

Files:

  • src/pages/generate/pages/result/components/GeneratedImgA.tsx
  • src/pages/mypage/components/section/generatedImages/GeneratedImagesSection.tsx
  • src/pages/mypage/components/history/HistorySection.tsx
🧠 Learnings (7)
📚 Learning: 2025-10-05T19:50:38.893Z
Learnt from: soyyyyy
Repo: TEAM-HOUME/HOUME-CLIENT PR: 328
File: src/pages/generate/pages/result/curationSheet/CurationSheet.tsx:76-87
Timestamp: 2025-10-05T19:50:38.893Z
Learning: In TypeScript/React projects using Zustand: when addressing performance issues related to state updates causing unnecessary re-renders, prefer splitting Zustand selectors over wrapping components with React.memo. The user soyyyyy prefers this approach as it addresses the root cause of re-renders at the state subscription level.

Applied to files:

  • src/pages/generate/pages/result/ResultPage.tsx
📚 Learning: 2025-10-13T04:31:50.173Z
Learnt from: earl9rey
Repo: TEAM-HOUME/HOUME-CLIENT PR: 339
File: src/pages/mypage/components/history/HistorySection.tsx:57-61
Timestamp: 2025-10-13T04:31:50.173Z
Learning: src/pages/mypage/components/history/HistorySection.tsx 컴포넌트는 더 이상 뷰에서 사용되지 않는 데드 코드입니다. MyPage.tsx에서는 GeneratedImagesSection만 사용하고 있습니다.

Applied to files:

  • src/pages/generate/pages/result/ResultPage.tsx
  • src/pages/mypage/components/section/generatedImages/GeneratedImagesSection.tsx
  • src/pages/mypage/components/history/HistorySection.tsx
📚 Learning: 2025-07-16T04:24:52.968Z
Learnt from: soyyyyy
Repo: TEAM-HOUME/HOUME-CLIENT PR: 194
File: src/pages/generate/components/result/ResultPage.tsx:20-20
Timestamp: 2025-07-16T04:24:52.968Z
Learning: ResultPage 컴포넌트의 하드코딩된 imageId는 API 연동 작업 시 동적으로 수정될 예정입니다. 사용자는 API 연동과 함께 이러한 임시 값들을 처리하는 것을 선호합니다.

Applied to files:

  • src/pages/generate/pages/result/ResultPage.tsx
📚 Learning: 2025-07-14T15:15:29.763Z
Learnt from: soyyyyy
Repo: TEAM-HOUME/HOUME-CLIENT PR: 167
File: src/pages/onboarding/components/steps/step2/floorPlan.tsx:0-0
Timestamp: 2025-07-14T15:15:29.763Z
Learning: React에서 Bottom Sheet 애니메이션 처리 시 setTimeout 대신 useEffect를 사용하여 상태 변경 순서를 보장하는 패턴: openSheet 상태 변경 → useEffect 트리거 → isSheetOpen true 설정 → 애니메이션 시작

Applied to files:

  • src/pages/generate/pages/result/components/GeneratedImgA.tsx
📚 Learning: 2025-09-07T12:47:26.179Z
Learnt from: maehwasoo
Repo: TEAM-HOUME/HOUME-CLIENT PR: 291
File: src/pages/generate/hooks/useGenerate.ts:31-31
Timestamp: 2025-09-07T12:47:26.179Z
Learning: 프로젝트에서 React Query 훅은 Query 접미사를 붙이는 네이밍 컨벤션을 따릅니다. (예: useGetResultData → useGetResultDataQuery)

Applied to files:

  • src/pages/mypage/hooks/useMypage.ts
📚 Learning: 2025-07-15T17:57:29.375Z
Learnt from: maehwasoo
Repo: TEAM-HOUME/HOUME-CLIENT PR: 180
File: src/pages/onboarding/hooks/useMoodBoardImage.hooks.ts:15-19
Timestamp: 2025-07-15T17:57:29.375Z
Learning: React Query의 queryKey에 사용되지 않는 파라미터를 포함하면 불필요한 캐시 분할이 발생합니다. queryKey는 실제로 쿼리 함수에서 사용되는 파라미터만 포함해야 합니다.

Applied to files:

  • src/pages/mypage/hooks/useMypage.ts
  • src/pages/generate/hooks/useFurnitureCuration.ts
📚 Learning: 2025-10-21T22:07:55.899Z
Learnt from: soyyyyy
Repo: TEAM-HOUME/HOUME-CLIENT PR: 341
File: src/pages/generate/pages/result/curationSheet/CurationSheet.tsx:63-68
Timestamp: 2025-10-21T22:07:55.899Z
Learning: In HOUME-CLIENT project: React keys for furniture product lists should use the stable `furnitureProductId` rather than the ephemeral `p.id` (recommendId). The recommendId changes per recommendation session, while furnitureProductId is a stable identifier for the product itself, enabling React to properly reconcile and reuse DOM elements across different recommendation sets.

Applied to files:

  • src/pages/generate/hooks/useFurnitureCuration.ts
🧬 Code graph analysis (5)
src/pages/mypage/hooks/useDetectionPrefetch.ts (8)
src/pages/generate/hooks/useOnnxModel.ts (1)
  • useONNXModel (221-371)
src/pages/generate/constants/detection.ts (1)
  • OBJ365_MODEL_PATH (7-7)
src/pages/generate/stores/useDetectionCacheStore.ts (1)
  • useDetectionCacheStore (41-75)
src/pages/generate/types/detection.ts (1)
  • ProcessedDetections (22-25)
src/pages/generate/hooks/useFurnitureHotspots.ts (2)
  • FurnitureHotspot (41-41)
  • loadCorsImage (64-112)
src/pages/generate/constants/furnitureCategoryMapping.ts (1)
  • FurnitureCategoryCode (25-25)
src/pages/generate/hooks/furnitureHotspotPipeline.ts (1)
  • buildHotspotsPipeline (160-266)
src/pages/generate/utils/detectedObjectMapper.ts (2)
  • mapHotspotsToDetectedObjects (13-52)
  • filterAllowedDetectedObjects (55-78)
src/pages/generate/pages/result/components/GeneratedImgA.tsx (6)
src/pages/mypage/types/apis/MyPage.ts (1)
  • MyPageUserData (4-8)
src/pages/generate/stores/useDetectionCacheStore.ts (1)
  • DetectionCacheEntry (11-17)
src/pages/generate/hooks/useFurnitureCuration.ts (2)
  • useOpenCurationSheet (355-360)
  • useSheetSnapState (260-270)
src/pages/generate/stores/useCurationStore.ts (1)
  • CurationSnapState (10-10)
src/pages/mypage/hooks/useMypage.ts (1)
  • useMyPageUser (26-34)
src/routes/paths.ts (1)
  • ROUTES (4-17)
src/pages/mypage/components/history/HistorySection.tsx (1)
src/pages/mypage/hooks/useMypage.ts (1)
  • useMyPageImagesQuery (43-52)
src/pages/generate/pages/result/curationSheet/CurationSheet.tsx (7)
src/pages/generate/hooks/useFurnitureCuration.ts (4)
  • useActiveImageId (66-67)
  • useActiveImageCurationState (256-257)
  • useGeneratedCategoriesQuery (70-188)
  • useGeneratedProductsQuery (190-253)
src/pages/generate/stores/useCurationStore.ts (1)
  • useCurationStore (83-255)
src/pages/generate/stores/useCurationCacheStore.ts (1)
  • useCurationCacheStore (52-143)
src/shared/constants/queryKey.ts (1)
  • QUERY_KEY (1-18)
src/shared/apis/queryClient.ts (1)
  • queryClient (5-18)
src/pages/generate/types/furniture.ts (1)
  • FurnitureProductsInfoResponse (29-32)
src/pages/generate/apis/furniture.ts (1)
  • getGeneratedImageProducts (26-34)
src/pages/mypage/hooks/useMypage.ts (3)
src/pages/mypage/apis/mypage.ts (3)
  • getMyPageUser (15-22)
  • getMyPageImages (28-37)
  • getMyPageImageDetail (43-52)
src/shared/constants/queryKey.ts (1)
  • QUERY_KEY (1-18)
src/pages/mypage/types/apis/MyPage.ts (1)
  • MyPageImageDetailData (41-43)
🪛 GitHub Actions: CI/CD Pipeline
src/pages/generate/hooks/useFurnitureCuration.ts

[error] 324-324: TypeScript error TS2345: Argument of type 'number | undefined' is not assignable to parameter of type 'number'.

🪛 GitHub Check: build
src/pages/generate/hooks/useFurnitureCuration.ts

[failure] 324-324:
Argument of type 'number | undefined' is not assignable to parameter of type 'number'.

🔇 Additional comments (34)
src/pages/mypage/hooks/useDetectionPrefetch.ts (1)

59-65: LGTM - 훅 구조와 상태 관리 패턴이 적절해요

pendingRef, queueRef, drainingRef를 사용한 중복 프리페치 방지 및 큐 관리 로직이 잘 구현되어 있어요. useCallback으로 함수 참조 안정성도 유지하고 있어요.

src/pages/mypage/components/history/HistorySection.tsx (1)

8-12: 훅 이름 변경 LGTM

useMyPageImagesQuery로 변경하여 TanStack Query 네이밍 컨벤션(use + 행위 + 대상 + Query)을 준수해요.

src/pages/mypage/hooks/useMypage.ts (2)

13-17: 타입 정의 개선 LGTM

QueryResult<T>UseQueryBaseOptions<TData>를 도입하여 훅 옵션 타입을 일관되게 정의한 것이 좋아요. Omit을 사용해 queryKeyqueryFn을 제외한 것도 적절해요.


43-52: useMyPageImagesQuery 네이밍 컨벤션 준수 확인

훅 이름이 TanStack Query 네이밍 컨벤션을 따르고 있어요. staleTime, gcTime 설정도 적절해요.

src/pages/generate/pages/result/components/GeneratedImgA.tsx (5)

137-153: 시트 상태 복원 로직 LGTM

isImageMoreSlide 상태에 따라 시트를 숨기고 복원하는 로직이 잘 구현되어 있어요. useCallback으로 restoreSheetSnapState를 메모이제이션하고, ref를 사용해 이전 상태를 추적하는 패턴이 적절해요.


88-92: 조건부 쿼리 활성화 패턴 적절

userProfile이 이미 제공되면 useMyPageUser 쿼리를 비활성화하여 불필요한 API 호출을 방지해요. 폴백 체인(userProfile?.CreditCount ?? fetchedUserData?.CreditCount ?? 0)도 안전하게 처리되어 있어요.


226-240: 슬라이드 카운트 스켈레톤 접근성 개선

aria-live="polite"aria-hidden="true"를 적절히 사용하여 스크린 리더 접근성을 고려한 것이 좋아요.


160-173: closeModal 헬퍼 함수 가독성 향상

모달 닫기 시 시트 상태 복원 옵션을 명확하게 분리한 것이 좋아요. restoreSnap 옵션으로 의도를 명확히 전달하고 있어요.


249-270: 캐시된 감지 결과 전달 LGTM

detectionCache에서 imageId로 캐시된 감지 결과를 조회하여 DetectionHotspots에 전달하는 로직이 적절해요. groupId도 함께 전달하여 그룹 기반 캐싱을 지원해요.

src/pages/generate/pages/result/curationSheet/CurationSheet.tsx (6)

49-53: CurationSheetProps interface 정의 LGTM

groupId?: number | null 타입 정의가 명확하고, 기본값 null을 컴포넌트에서 설정한 것이 적절해요.


61-68: useMemo로 참조 안정성 확보 LGTM

hotspotsdetectedObjectsuseMemo로 감싸서 불필요한 리렌더링을 방지한 것이 좋아요. 의존성 배열도 적절해요.


137-145: 시트 자동 접힘 조건 개선

snapState !== 'hidden' 조건을 추가하여 이미 숨겨진 상태에서 불필요하게 collapsed로 변경되는 것을 방지한 것이 좋아요.


338-346: 스켈레톤 칩 접근성 개선

aria-hidden 속성을 사용하여 스크린 리더에서 스켈레톤을 숨긴 것이 적절해요. 단일 long 너비 스켈레톤으로 단순화한 것도 좋아요.


92-94: groupId 진위값 체크 패턴 확인

groupId !== null 체크를 사용하고 있어서 groupId0인 경우에도 올바르게 처리돼요. 이전 리뷰에서 지적된 부분이 반영된 것으로 보여요.


156-200: 프리패치 로직 개선 LGTM

그룹 캐시(groupProductCache)를 활용하여 이미 캐시된 카테고리는 프리패치를 건너뛰는 최적화가 잘 되어 있어요. prefetchedRef로 중복 프리패치도 방지하고 있어요.

한 가지 개선 제안: dedupeKey 생성 시 groupId ?? activeImageId 패턴을 사용하고 있는데, groupId0인 경우 activeImageId로 폴백될 수 있어요. groupId !== null ? groupId : activeImageId로 명시적으로 처리하면 더 안전해요.

-      const dedupeKey = `${groupId ?? activeImageId}:${category.id}`;
+      const dedupeKey = `${groupId !== null ? groupId : activeImageId}:${category.id}`;

Likely an incorrect or invalid review comment.

src/pages/mypage/components/section/generatedImages/GeneratedImagesSection.tsx (6)

1-18: LGTM! import 구조와 컴포넌트 구성이 적절해요.

필요한 React 훅들과 페이지별 유틸리티, 타입들이 잘 정리되어 있어요. type import를 사용해 런타임 의존성을 줄인 점이 좋아요.


44-53: 프리페치 로직이 효율적으로 구현되어 있어요.

prefetchedImageIdsRef를 사용해 중복 프리페치를 방지하고, 첫 번째 이미지에 immediate 우선순위를 부여하는 것이 UX 관점에서 적절해요.


55-79: requestIdleCallback 폴백 패턴이 잘 구현되어 있어요.

브라우저 지원 여부를 확인하고 setTimeout(0)으로 폴백하는 방식이 적절해요. SSR 환경에서는 즉시 실행하는 가드도 올바르게 처리되어 있네요.


81-99: 네비게이션과 프리페치 연동이 적절해요.

buildResultNavigationState를 통해 상태를 구성하고, 네비게이션 직후 immediate 프리페치를 실행하는 흐름이 사용자 경험을 개선해요.


101-118: 이미지 로드 핸들러가 효율적으로 구현되어 있어요.

이미 로드된 이미지에 대해 early return으로 불필요한 상태 업데이트를 방지하고, primaryImageId와 비교해 첫 번째 이미지에만 즉시 프리페치를 실행하는 최적화가 좋아요.


135-151: 렌더링 로직이 깔끔하게 구현되어 있어요.

imageId를 key로 사용하고, CardCuration에 필요한 props를 명확하게 전달하고 있어요. onCurationClick에서 전체 history 객체를 전달하는 것이 handleViewResult와 잘 연동되네요.

src/pages/generate/pages/result/ResultPage.tsx (8)

6-11: 타입 import가 올바르게 사용되어 있어요.

import type을 사용해 런타임 의존성 없이 타입만 가져오고 있어요. 코딩 가이드라인에 맞는 패턴이에요.


44-54: toGenerateImageData 헬퍼 함수가 명확하게 정의되어 있어요.

MyPageImageHistoryGenerateImageData로 변환하는 로직이 간결해요. nametagName 모두 history.tasteTag를 사용하는데, 의도된 것인지 확인이 필요할 수 있어요.


75-98: location state 처리와 detection map 생성이 적절해요.

forwardedDetectionMapuseMemo로 안정적으로 생성하고, null 체크가 정확해요. 의존성 배열도 올바르게 설정되어 있어요.


110-119: 조건부 플래그들이 명확하게 정의되어 있어요.

hasValidHouseId, hasInitialResult, shouldFetchExternalResult, shouldFetchMypageDetail 플래그들이 데이터 페칭 조건을 명확하게 표현하고 있어요.


142-180: resolvedResult useMemo가 과거 리뷰 피드백을 반영해 잘 구현되어 있어요.

데이터 소스 우선순위(mypageHistories → forwardedResult → initialHistory → apiResult)가 명확하고, 의존성 배열도 정확해요. 렌더링 안정성이 개선되었어요.


183-194: O(1) 조회를 위한 Map 생성이 적절해요.

historyById Map을 통해 currentLikeStatecurrentFactorId 계산 시 반복적인 배열 탐색을 피할 수 있어요. 조건부 생성으로 불필요한 메모리 할당도 방지하고 있네요.


401-417: 그룹 기반 캐싱 props가 자식 컴포넌트에 올바르게 전달되고 있어요.

userProfile, detectionCache, isSlideCountLoading, groupId 등 새로운 props가 GeneratedImgAGeneratedImgB에 적절하게 전달되고 있어요. ?? undefined로 null을 undefined로 변환하는 것도 올바른 처리예요.


517-517: CurationSheetgroupId prop 전달이 적절해요.

그룹 기반 캐싱을 위한 groupId prop이 CurationSheet에 전달되어 캐시 무효화와 조회가 올바르게 동작할 수 있어요.

src/pages/generate/hooks/useFurnitureCuration.ts (5)

27-54: 그룹 기반 쿼리 키 타입들이 잘 정의되어 있어요.

CategoriesQueryVariables, ProductsQueryVariables 타입과 해당 쿼리 키 타입들이 명확하게 정의되어 있어요. 이를 통해 쿼리 무효화 시 타입 안전성을 확보할 수 있어요.


70-136: 카테고리 쿼리 훅이 그룹 기반 캐싱을 잘 지원해요.

detectionSignature 생성을 위한 정규화와 정렬이 일관된 캐시 키를 보장해요. canUseGroupInitialData 조건으로 그룹 캐시 사용 여부를 명확히 구분하고 있어요.


138-162: 캐시 저장 로직이 useEffect로 올바르게 구현되어 있어요.

React Query v5에서 제거된 onSuccess 대신 useEffect를 사용해 캐시를 저장하고 있어요. 기존 캐시와의 중복 체크로 불필요한 저장을 방지하는 것도 좋아요.


190-253: 상품 쿼리 훅이 카테고리 쿼리와 일관된 패턴으로 구현되어 있어요.

그룹 캐시 조회와 저장 로직이 카테고리 쿼리와 동일한 패턴을 따르고 있어 유지보수성이 좋아요.


283-316: Predicate 기반 쿼리 무효화가 잘 구현되어 있어요.

과거 리뷰에서 지적된 쿼리 키 구조 불일치 문제가 해결되었어요. predicate 함수를 사용해 그룹/이미지 기반 쿼리를 정밀하게 무효화하고 있어요.

Comment on lines +94 to +97
const rawDetectedObjects = mapHotspotsToDetectedObjects(
pipeline.hotspots
);
const detectedObjects = filterAllowedDetectedObjects(rawDetectedObjects, {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cd src/pages/mypage && find . -name "*.ts" -o -name "*.tsx" | head -20

Repository: TEAM-HOUME/HOUME-CLIENT

Length of output: 984


🏁 Script executed:

# mapHotspotsToDetectedObjects 함수 정의 찾기
rg "mapHotspotsToDetectedObjects" --type ts -A 20

Repository: TEAM-HOUME/HOUME-CLIENT

Length of output: 9275


🏁 Script executed:

# filterAllowedDetectedObjects 함수 정의 찾기
rg "filterAllowedDetectedObjects" --type ts -B 2 -A 10

Repository: TEAM-HOUME/HOUME-CLIENT

Length of output: 6817


🏁 Script executed:

# useDetectionPrefetch.ts 전체 내용 확인
wc -l src/pages/mypage/hooks/useDetectionPrefetch.ts

Repository: TEAM-HOUME/HOUME-CLIENT

Length of output: 117


🏁 Script executed:

# mapHotspotsToDetectedObjects의 전체 구현 확인
sed -n '1,100p' src/pages/generate/utils/detectedObjectMapper.ts

Repository: TEAM-HOUME/HOUME-CLIENT

Length of output: 2239


🏁 Script executed:

# filterAllowedDetectedObjects의 구현 확인
sed -n '40,80p' src/pages/generate/utils/detectedObjectMapper.ts

Repository: TEAM-HOUME/HOUME-CLIENT

Length of output: 1020


mapHotspotsToDetectedObjects 반환값은 이미 필터링되어 있으므로 중복 호출 제거 필요

mapHotspotsToDetectedObjects 함수가 내부적으로 filterAllowedDetectedObjects를 호출하고 필터링된 배열을 반환합니다. 94-97 라인에서 반환값을 다시 필터링하면 불필요한 중복 처리가 발생하며, 로깅도 중복됩니다.

개선: 94-97 라인의 filterAllowedDetectedObjects 호출을 제거하고 rawDetectedObjects를 직접 사용하거나, 변수명을 detectedObjects로 변경하세요. 또는 mapHotspotsToDetectedObjects가 필터링 없이 raw 결과를 반환하도록 구조를 변경하는 방식도 검토하세요 (DetectionHotspots.tsx도 동일 패턴이므로 일관성 확인 필요).

🤖 Prompt for AI Agents
In src/pages/mypage/hooks/useDetectionPrefetch.ts around lines 94-97 the code
redundantly calls filterAllowedDetectedObjects on the result of
mapHotspotsToDetectedObjects even though that function already returns a
filtered array; remove the duplicate filter call and either use
rawDetectedObjects directly (rename it to detectedObjects) or assign
rawDetectedObjects to detectedObjects and drop the extra filter, and then update
any downstream references; also review DetectionHotspots.tsx for the same
pattern and make the naming/behavior consistent across both files.

@github-actions
Copy link

빌드 결과

빌드 실패 ❌

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (8)
src/pages/mypage/hooks/useDetectionPrefetch.ts (4)

50-53: 객체 형태 타입은 interface로 정의 권장

코딩 가이드라인에 따르면 객체 형태는 interface를, union/별칭에는 type을 사용해요.

-type PrefetchTask = {
-  imageId: number;
-  imageUrl: string;
-};
+interface PrefetchTask {
+  imageId: number;
+  imageUrl: string;
+}

94-101: filterAllowedDetectedObjects 중복 호출 제거 필요

mapHotspotsToDetectedObjects 함수가 내부적으로 filterAllowedDetectedObjects를 호출하고 필터링된 배열을 반환해요. 94-101 라인에서 반환값을 다시 필터링하면 불필요한 중복 처리와 로깅이 발생해요.

      const pipeline = buildHotspotsPipeline(targetImage, processed);
-      const rawDetectedObjects = mapHotspotsToDetectedObjects(
+      const detectedObjects = mapHotspotsToDetectedObjects(
        pipeline.hotspots
      );
-      const detectedObjects = filterAllowedDetectedObjects(rawDetectedObjects, {
-        stage: 'prefetch-detection',
-        imageId,
-        hotspotCount: pipeline.hotspots.length,
-      });

      storeDetections(imageId, imageUrl, processed, {
        hotspots: pipeline.hotspots,
        detectedObjects,
      });

144-147: console.warn 대신 로깅 유틸리티 사용 권장

프로덕션 환경에서 console.warn은 디버깅에 적합하지 않아요. 프로젝트의 다른 파일에서 사용하는 logFurniturePipelineEvent 같은 로깅 유틸리티를 사용하는 것을 권장해요.


122-126: 빈 catch 블록에 로깅 추가 고려

이미지 로드 실패 시 CORS 이미지 로더로 폴백하는 것은 좋지만, 디버깅을 위해 초기 실패를 로깅하면 문제 추적에 도움이 돼요.

        try {
          targetImage = await loadImageElement(imageUrl);
-        } catch {
+        } catch (loadError) {
+          // CORS 폴백으로 재시도 - 초기 실패는 예상 가능한 케이스
          targetImage = await loadCorsImage(imageUrl);
        }
src/pages/mypage/hooks/useMypage.ts (2)

28-35: useMyPageUser에서 options 우선순위 불일치

useMyPageImagesQueryuseMyPageImageDetail...options가 마지막에 위치해 호출자가 기본값을 덮어쓸 수 있지만, useMyPageUser...optionsstaleTime/refetchOnWindowFocus 앞에 위치해 덮어쓸 수 없어요.

의도적인 차이라면 주석으로 이유를 남기고, 아니라면 통일해 주세요:

  return useQuery<QueryResult<ReturnType<typeof getMyPageUser>>>({
    queryKey: [QUERY_KEY.MYPAGE_USER],
    queryFn: getMyPageUser,
-    ...options,
    staleTime: 15 * 60 * 1000,
    refetchOnWindowFocus: false,
+    ...options,
  });

67-77: useMyPageImageDetailuseMyPageImageDetailQuery로 함수명 변경 필요

TanStack Query 훅 네이밍 컨벤션에 따라 Query 접미사를 추가해 주세요:

-export const useMyPageImageDetail = (
+export const useMyPageImageDetailQuery = (
  houseId: number,
  options?: ImageDetailOptions
) => {
src/pages/mypage/components/section/generatedImages/GeneratedImagesSection.tsx (1)

35-45: sessionStorage 키를 상수로 추출하면 유지보수성이 향상돼요.

'mypage-image-loaded' 키가 Line 39와 Line 121에서 중복 사용되고 있어요. 상수로 추출해서 관리하는 것을 권장해요.

+const MYPAGE_IMAGE_LOADED_KEY = 'mypage-image-loaded';
+
 const GeneratedImagesSection = ({
   userProfile,
 }: GeneratedImagesSectionProps) => {
   // ...
   const [loadedImages, setLoadedImages] = useState<Record<number, boolean>>(
     () => {
       if (typeof window === 'undefined') return {};
       try {
-        const stored = sessionStorage.getItem('mypage-image-loaded');
+        const stored = sessionStorage.getItem(MYPAGE_IMAGE_LOADED_KEY);
         return stored ? (JSON.parse(stored) as Record<number, boolean>) : {};
src/pages/generate/hooks/useFurnitureCuration.ts (1)

340-370: 빌드 실패: categoryId 타입 오류를 수정해야 해요.

Line 346에서 categoryIdnumber | null | undefined 타입인데 clearGroupProductnumber 타입을 기대해요. categoryId?: number | null로 선언되어 있어서 undefined일 수 있어요.

       invalidateProducts: (
         groupId: number | null,
         imageId: number | null,
         categoryId?: number | null
       ) => {
-        if (groupId !== null && categoryId !== null) {
+        if (groupId !== null && categoryId != null) {
           clearGroupProduct(groupId, categoryId);
         }

!== null 대신 != null을 사용하면 nullundefined 모두 체크할 수 있어요. 또는 명시적으로 categoryId !== null && categoryId !== undefined로 작성해도 돼요.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ec60ce1 and ab0fe38.

📒 Files selected for processing (8)
  • src/pages/generate/hooks/useFurnitureCuration.ts (5 hunks)
  • src/pages/generate/pages/result/ResultPage.tsx (10 hunks)
  • src/pages/generate/pages/result/components/GeneratedImgA.tsx (8 hunks)
  • src/pages/generate/pages/result/curationSheet/CurationSheet.tsx (8 hunks)
  • src/pages/mypage/components/history/HistorySection.tsx (1 hunks)
  • src/pages/mypage/components/section/generatedImages/GeneratedImagesSection.tsx (2 hunks)
  • src/pages/mypage/hooks/useDetectionPrefetch.ts (1 hunks)
  • src/pages/mypage/hooks/useMypage.ts (2 hunks)
🧰 Additional context used
📓 Path-based instructions (6)
src/pages/**

📄 CodeRabbit inference engine (README.md)

Organize code using a feature-based structure within pages/ directory, grouping page-specific logic (components, constants, apis, types, utils, hooks) together by domain

Files:

  • src/pages/generate/pages/result/curationSheet/CurationSheet.tsx
  • src/pages/mypage/components/section/generatedImages/GeneratedImagesSection.tsx
  • src/pages/generate/pages/result/components/GeneratedImgA.tsx
  • src/pages/mypage/hooks/useDetectionPrefetch.ts
  • src/pages/generate/pages/result/ResultPage.tsx
  • src/pages/generate/hooks/useFurnitureCuration.ts
  • src/pages/mypage/hooks/useMypage.ts
  • src/pages/mypage/components/history/HistorySection.tsx
src/**

⚙️ CodeRabbit configuration file

src/**: 전체 프로젝트 코드 리뷰 시:

기본 규칙

  • 모든 컴포넌트는 반드시 TypeScript로 작성
  • SVG는 ?react 쿼리를 통해 React 컴포넌트로 임포트
  • 스타일은 반드시 Vanilla Extract(.css.ts)로 작성
  • 절대 경로 임포트 사용 (@pages, @shared 등)
  • overlay-kit을 통한 모달 관리 패턴 준수

네이밍 컨벤션

  • 컴포넌트와 클래스는 PascalCase 사용
  • 폴더명은 camelCase 사용
  • 파일명은 camelCase 사용 (컴포넌트 파일 제외)
  • 변수와 함수는 camelCase 사용
  • 상수는 BIG_SNAKE_CASE 사용
  • 모든 타입/인터페이스는 PascalCase, Type/I/T 접두사·접미사 금지 (제네릭 T 제외)
  • Props 타입은 컴포넌트명 + Props 형식
  • API Response 타입은 XXXResponse 형식
  • 함수 네이밍: get/create/check/handle/is/has/can 접두사 활용
  • API 함수는 HTTP메서드 + 명사 형식 (getUserList, postComment)
  • TanStack Query 훅: use + 행위 + 대상 + Query/Mutation (예: useGetUserListQuery)
  • 커스텀 훅 파일명: use*.ts

보안 및 성능

  • 환경 변수는 반드시 import.meta.env 사용
  • API 키나 시크릿을 하드코딩하지 않음
  • React.memo, useMemo, useCallback의 적절한 사용
  • 큰 번들 사이즈 경고 (dynamic import 제안)
  • 템플릿 리터럴 사용 권장 (문자열 조합 시)
  • 구조 분해 할당 적극 활용

커밋 및 브랜치 패턴

  • 커밋 타입: feat, fix, docs, style, refactor, test, chore, design, comment, rename, remove
  • 브랜치 패턴: type/description/#issue-number 형식

Files:

  • src/pages/generate/pages/result/curationSheet/CurationSheet.tsx
  • src/pages/mypage/components/section/generatedImages/GeneratedImagesSection.tsx
  • src/pages/generate/pages/result/components/GeneratedImgA.tsx
  • src/pages/mypage/hooks/useDetectionPrefetch.ts
  • src/pages/generate/pages/result/ResultPage.tsx
  • src/pages/generate/hooks/useFurnitureCuration.ts
  • src/pages/mypage/hooks/useMypage.ts
  • src/pages/mypage/components/history/HistorySection.tsx
src/**/components/**/*.tsx

⚙️ CodeRabbit configuration file

src/**/components/**/*.tsx: 컴포넌트 파일 리뷰 시:

  • 파일명이 PascalCase인지 확인
  • Props 타입이 '컴포넌트명Props' 형식인지 확인
  • 단일 export는 default export 권장(팀 가이드 우선 적용)
  • 이벤트 핸들러는 handle로 시작하는지 확인
  • 접근성 준수: ARIA 속성, label 연결, 키보드 포커스
  • 성능: memo/useMemo/useCallback 적절 사용
  • ref 전달 시 forwardRef 사용 및 displayName 지정

Files:

  • src/pages/mypage/components/section/generatedImages/GeneratedImagesSection.tsx
  • src/pages/generate/pages/result/components/GeneratedImgA.tsx
  • src/pages/mypage/components/history/HistorySection.tsx
src/pages/**/components/**

⚙️ CodeRabbit configuration file

src/pages/**/components/**: 페이지별 컴포넌트 리뷰 시:

  • 해당 페이지의 도메인 로직과 긴밀한 결합 확인
  • 재사용 가능한 컴포넌트는 shared로 이동 제안
  • 페이지 특화 타입과 상수의 적절한 활용 확인

Files:

  • src/pages/mypage/components/section/generatedImages/GeneratedImagesSection.tsx
  • src/pages/generate/pages/result/components/GeneratedImgA.tsx
  • src/pages/mypage/components/history/HistorySection.tsx
src/**/hooks/**/*.ts

⚙️ CodeRabbit configuration file

src/**/hooks/**/*.ts: 커스텀 훅 리뷰 시:

  • 파일명/함수명은 use* 접두사 사용
  • 의존성 배열(exhaustive-deps) 준수, 참조 안정성 유지(함수/객체 메모이제이션)
  • 반환 값 일관성(tuple/object), 에러/로딩 상태 명확화
  • 제네릭 훅은 타입 매개변수 기본값 검토
  • TanStack Query 훅 네이밍: use + 행위 + 대상 + Query/Mutation (예: useGetUserListQuery)

Files:

  • src/pages/mypage/hooks/useDetectionPrefetch.ts
  • src/pages/generate/hooks/useFurnitureCuration.ts
  • src/pages/mypage/hooks/useMypage.ts
src/**/*.ts

⚙️ CodeRabbit configuration file

src/**/*.ts: TypeScript 파일 리뷰 시:

  • 타입/인터페이스는 PascalCase 사용, Type/I/T 접두사·접미사 금지 (제네릭 T 제외)
  • interface는 객체 타입, type은 union/별칭에 사용하는지 확인
  • API Response는 XXXResponse 네이밍 따르는지 확인
  • var 사용 금지, const 우선 사용 확인
  • type-only import 사용(import type), 불필요한 런타임 의존성 제거
  • satisfies 연산자 활용으로 정확한 타입 보장

Files:

  • src/pages/mypage/hooks/useDetectionPrefetch.ts
  • src/pages/generate/hooks/useFurnitureCuration.ts
  • src/pages/mypage/hooks/useMypage.ts
🧠 Learnings (7)
📚 Learning: 2025-10-13T04:31:50.173Z
Learnt from: earl9rey
Repo: TEAM-HOUME/HOUME-CLIENT PR: 339
File: src/pages/mypage/components/history/HistorySection.tsx:57-61
Timestamp: 2025-10-13T04:31:50.173Z
Learning: src/pages/mypage/components/history/HistorySection.tsx 컴포넌트는 더 이상 뷰에서 사용되지 않는 데드 코드입니다. MyPage.tsx에서는 GeneratedImagesSection만 사용하고 있습니다.

Applied to files:

  • src/pages/mypage/components/section/generatedImages/GeneratedImagesSection.tsx
  • src/pages/generate/pages/result/ResultPage.tsx
  • src/pages/mypage/components/history/HistorySection.tsx
📚 Learning: 2025-07-14T15:15:29.763Z
Learnt from: soyyyyy
Repo: TEAM-HOUME/HOUME-CLIENT PR: 167
File: src/pages/onboarding/components/steps/step2/floorPlan.tsx:0-0
Timestamp: 2025-07-14T15:15:29.763Z
Learning: React에서 Bottom Sheet 애니메이션 처리 시 setTimeout 대신 useEffect를 사용하여 상태 변경 순서를 보장하는 패턴: openSheet 상태 변경 → useEffect 트리거 → isSheetOpen true 설정 → 애니메이션 시작

Applied to files:

  • src/pages/generate/pages/result/components/GeneratedImgA.tsx
📚 Learning: 2025-10-05T19:50:38.893Z
Learnt from: soyyyyy
Repo: TEAM-HOUME/HOUME-CLIENT PR: 328
File: src/pages/generate/pages/result/curationSheet/CurationSheet.tsx:76-87
Timestamp: 2025-10-05T19:50:38.893Z
Learning: In TypeScript/React projects using Zustand: when addressing performance issues related to state updates causing unnecessary re-renders, prefer splitting Zustand selectors over wrapping components with React.memo. The user soyyyyy prefers this approach as it addresses the root cause of re-renders at the state subscription level.

Applied to files:

  • src/pages/generate/pages/result/ResultPage.tsx
📚 Learning: 2025-07-16T04:24:52.968Z
Learnt from: soyyyyy
Repo: TEAM-HOUME/HOUME-CLIENT PR: 194
File: src/pages/generate/components/result/ResultPage.tsx:20-20
Timestamp: 2025-07-16T04:24:52.968Z
Learning: ResultPage 컴포넌트의 하드코딩된 imageId는 API 연동 작업 시 동적으로 수정될 예정입니다. 사용자는 API 연동과 함께 이러한 임시 값들을 처리하는 것을 선호합니다.

Applied to files:

  • src/pages/generate/pages/result/ResultPage.tsx
📚 Learning: 2025-07-15T17:57:29.375Z
Learnt from: maehwasoo
Repo: TEAM-HOUME/HOUME-CLIENT PR: 180
File: src/pages/onboarding/hooks/useMoodBoardImage.hooks.ts:15-19
Timestamp: 2025-07-15T17:57:29.375Z
Learning: React Query의 queryKey에 사용되지 않는 파라미터를 포함하면 불필요한 캐시 분할이 발생합니다. queryKey는 실제로 쿼리 함수에서 사용되는 파라미터만 포함해야 합니다.

Applied to files:

  • src/pages/generate/hooks/useFurnitureCuration.ts
  • src/pages/mypage/hooks/useMypage.ts
📚 Learning: 2025-10-21T22:07:55.899Z
Learnt from: soyyyyy
Repo: TEAM-HOUME/HOUME-CLIENT PR: 341
File: src/pages/generate/pages/result/curationSheet/CurationSheet.tsx:63-68
Timestamp: 2025-10-21T22:07:55.899Z
Learning: In HOUME-CLIENT project: React keys for furniture product lists should use the stable `furnitureProductId` rather than the ephemeral `p.id` (recommendId). The recommendId changes per recommendation session, while furnitureProductId is a stable identifier for the product itself, enabling React to properly reconcile and reuse DOM elements across different recommendation sets.

Applied to files:

  • src/pages/generate/hooks/useFurnitureCuration.ts
📚 Learning: 2025-09-07T12:47:26.179Z
Learnt from: maehwasoo
Repo: TEAM-HOUME/HOUME-CLIENT PR: 291
File: src/pages/generate/hooks/useGenerate.ts:31-31
Timestamp: 2025-09-07T12:47:26.179Z
Learning: 프로젝트에서 React Query 훅은 Query 접미사를 붙이는 네이밍 컨벤션을 따릅니다. (예: useGetResultData → useGetResultDataQuery)

Applied to files:

  • src/pages/mypage/hooks/useMypage.ts
🧬 Code graph analysis (4)
src/pages/generate/pages/result/ResultPage.tsx (7)
src/pages/mypage/types/apis/MyPage.ts (3)
  • MyPageImageHistory (13-20)
  • MyPageUserData (4-8)
  • MyPageImageDetail (29-39)
src/pages/generate/types/generate.ts (3)
  • GenerateImageData (34-42)
  • GenerateImageAResponse (45-51)
  • GenerateImageBResponse (54-58)
src/pages/generate/stores/useDetectionCacheStore.ts (1)
  • DetectionCacheEntry (11-17)
src/pages/mypage/utils/resultNavigation.ts (1)
  • createImageDetailPlaceholder (50-54)
src/pages/generate/hooks/useGenerate.ts (1)
  • useGetResultDataQuery (66-75)
src/pages/mypage/hooks/useMypage.ts (1)
  • useMyPageImageDetail (67-78)
src/pages/generate/pages/result/curationSheet/CurationSheet.tsx (1)
  • CurationSheet (58-397)
src/pages/generate/hooks/useFurnitureCuration.ts (6)
src/pages/generate/constants/furnitureCategoryMapping.ts (1)
  • FurnitureCategoryCode (25-25)
src/shared/constants/queryKey.ts (1)
  • QUERY_KEY (1-18)
src/pages/generate/stores/useCurationStore.ts (2)
  • useCurationStore (83-255)
  • selectActiveImageState (258-262)
src/pages/generate/stores/useCurationCacheStore.ts (1)
  • useCurationCacheStore (52-143)
src/pages/generate/types/furniture.ts (2)
  • FurnitureCategoriesResponse (10-12)
  • FurnitureProductsInfoResponse (29-32)
src/pages/generate/apis/furniture.ts (2)
  • getGeneratedImageCategories (14-23)
  • getGeneratedImageProducts (26-34)
src/pages/mypage/hooks/useMypage.ts (3)
src/pages/mypage/apis/mypage.ts (3)
  • getMyPageUser (15-22)
  • getMyPageImages (28-37)
  • getMyPageImageDetail (43-52)
src/shared/constants/queryKey.ts (1)
  • QUERY_KEY (1-18)
src/pages/mypage/types/apis/MyPage.ts (1)
  • MyPageImageDetailData (41-43)
src/pages/mypage/components/history/HistorySection.tsx (1)
src/pages/mypage/hooks/useMypage.ts (1)
  • useMyPageImagesQuery (47-56)
🪛 GitHub Actions: CI/CD Pipeline
src/pages/generate/hooks/useFurnitureCuration.ts

[error] 346-346: TS2345: Argument of type 'number | undefined' is not assignable to parameter of type 'number'. Build failed during 'pnpm run build' (tsc -b).

🪛 GitHub Check: build
src/pages/generate/hooks/useFurnitureCuration.ts

[failure] 346-346:
Argument of type 'number | undefined' is not assignable to parameter of type 'number'.

🔇 Additional comments (32)
src/pages/mypage/components/history/HistorySection.tsx (1)

8-16: 훅 네이밍 컨벤션 준수 확인 - LGTM

useMyPageImagesQuery로 변경한 부분은 TanStack Query 훅 네이밍 컨벤션(use + 행위 + 대상 + Query)을 잘 따르고 있어요.

다만, 기존 learnings에 따르면 이 컴포넌트는 MyPage.tsx에서 사용되지 않는 데드 코드예요. GeneratedImagesSection만 사용 중이라 이 파일을 유지할 필요가 있는지 검토해 주세요. Based on learnings.

src/pages/mypage/hooks/useDetectionPrefetch.ts (1)

59-65: 훅 구조 및 상태 관리 - LGTM

useRef를 활용한 pending/queue 상태 관리와 draining 플래그 패턴이 적절해요. PREFETCH_DELAY_MS 상수 추출도 잘 되어 있어요.

src/pages/generate/pages/result/components/GeneratedImgA.tsx (6)

61-65: Props 인터페이스 확장 - LGTM

새로운 props (userProfile, detectionCache, isSlideCountLoading, groupId)가 적절히 추가되었고, 옵셔널 타입 처리가 잘 되어 있어요.


93-97: 조건부 데이터 페칭 패턴 - LGTM

enabled: !userProfile로 불필요한 API 호출을 방지하고, nullish coalescing 체인으로 creditCount를 안전하게 도출하는 패턴이 좋아요.


134-158: 시트 상태 관리 로직 - LGTM

이미지 더보기 슬라이드에서 시트 상태를 숨기고 복원하는 로직이 잘 구현되어 있어요. prevSnapStateRefisSheetHiddenByImageMoreRef를 활용한 상태 추적이 적절해요.


169-181: 모달 닫기 헬퍼 함수 패턴 - LGTM

closeModal 헬퍼가 restoreSnap 옵션으로 시트 상태 복원 여부를 제어하고, afterClose 콜백으로 후속 작업을 처리하는 패턴이 깔끔해요.


234-248: 슬라이드 카운트 로딩 UI - LGTM

aria-live="polite"로 접근성을 고려하고, 스켈레톤에 aria-hidden="true"role="presentation"을 적용한 것이 좋아요.


257-278: 캐시된 감지 결과 연동 - LGTM

detectionCache에서 imageId로 캐시를 조회하고 DetectionHotspots에 전달하는 패턴이 적절해요.

src/pages/generate/pages/result/curationSheet/CurationSheet.tsx (5)

66-73: Memoization 적용 - LGTM

hotspotsdetectedObjectsuseMemo로 메모이제이션하여 참조 안정성을 확보한 것이 좋아요.


161-205: 그룹 기반 프리패치 로직 - LGTM

카테고리별 프리패치를 병렬로 처리하고, dedupeKey로 중복을 방지하며, groupProductCache 체크로 불필요한 fetch를 방지하는 로직이 잘 구현되어 있어요. prefetchedRef.current.add(dedupeKey)를 비동기 호출 전에 수행해 race condition을 방지한 점도 좋아요.


142-150: 시트 접힘 조건 개선 - LGTM

snapState !== 'collapsed' && snapState !== 'hidden' 조건으로 이미 접힌 상태거나 숨김 상태일 때 불필요한 상태 변경을 방지해요.


352-361: 카테고리 스켈레톤 렌더링 - LGTM

카테고리가 없을 때 단일 스켈레톤 칩을 렌더링하고, FILTER_SKELETON_WIDTH 상수로 길이를 관리하는 패턴이 적절해요.


49-58: CurationSheetProps 인터페이스 및 기본값 - LGTM

groupId prop이 number | null 타입으로 정의되고 기본값 null이 설정되어 있습니다. 컴포넌트 전체에서 groupId 관련 null 체크가 명시적으로 적용되었습니다(groupId !== null 비교, 정확한 nullish coalescing 사용). 이전 리뷰의 지적사항이 잘 반영되었습니다.

src/pages/mypage/hooks/useMypage.ts (2)

13-17: 재사용 가능한 타입 헬퍼 정의 - LGTM

QueryResult<T>UseQueryBaseOptions<TData> 타입 헬퍼가 훅 전반에서 일관되게 사용되어 유지보수성이 좋아요.


47-55: useMyPageImagesQuery 네이밍 및 캐시 설정 - LGTM

TanStack Query 훅 네이밍 컨벤션(use + 행위 + 대상 + Query)을 따르고, 캐시 설정(staleTime: 15분, gcTime: 30분)이 적절해요.

src/pages/mypage/components/section/generatedImages/GeneratedImagesSection.tsx (6)

1-2: useCallback을 import하지만 useRefuseState도 함께 사용하고 있어요.

import 구조가 적절하게 구성되어 있고, 필요한 React 훅들이 모두 포함되어 있어요.


20-30: Props 인터페이스와 컴포넌트 시그니처가 코딩 가이드라인을 준수해요.

GeneratedImagesSectionProps 형식으로 Props 타입이 정의되어 있고, 컴포넌트가 해당 props를 받아 사용하고 있어요.


48-57: 프리패치 로직이 잘 구성되어 있어요.

첫 번째 이미지는 immediate 우선순위로, 나머지는 background로 처리하는 전략이 적절해요. prefetchedImageIdsRef로 중복 프리패치도 방지하고 있어요.


63-87: requestIdleCallback 폴백 처리가 잘 되어 있어요.

SSR 환경 가드와 requestIdleCallback 미지원 브라우저를 위한 setTimeout 폴백이 적절하게 구현되어 있어요.


115-132: 이미지 로드 핸들러가 적절하게 구현되어 있어요.

상태 업데이트와 sessionStorage 동기화가 잘 처리되어 있고, primary 이미지에 대한 immediate 프리패치 로직도 적절해요.


153-160: CardCuration에 새로운 props가 잘 전달되고 있어요.

imageId, isLoaded, onImageLoad props가 추가되어 이미지별 로드 상태 추적이 가능해졌어요.

src/pages/generate/pages/result/ResultPage.tsx (5)

86-108: location.state 타입 확장과 detection 캐시 맵 생성이 적절해요.

useMemo를 사용해 forwardedDetectionMap을 안정적으로 계산하고 있고, null 체크도 잘 되어 있어요.


153-191: 이전 리뷰에서 지적된 result 재할당 문제가 useMemo로 잘 해결되었어요.

resolvedResultuseMemo로 계산하여 렌더 사이클 간 참조 안정성을 확보했어요. 우선순위도 명확해요:

  1. mypage 히스토리 → 2. forwarded result → 3. initialHistory → 4. API result

194-205: historyById Map을 통한 O(1) 조회 최적화가 적절해요.

useMemo로 Map을 생성하여 currentLikeStatecurrentFactorId 계산 시 효율적인 조회가 가능해요.


423-440: GeneratedImgA/B에 새로운 props가 잘 전달되고 있어요.

userProfile, detectionCache, isSlideCountLoading, groupId props가 추가되어 결과 페이지의 데이터 흐름이 개선되었어요.


540-540: CurationSheet에 groupId가 전달되고 있어요.

그룹 단위 캐싱 전략과 연동되어 일관성 있는 데이터 관리가 가능해요.

src/pages/generate/hooks/useFurnitureCuration.ts (6)

27-54: 쿼리 키 타입 정의가 잘 구성되어 있어요.

CategoriesQueryVariables, ProductsQueryVariables 인터페이스와 해당 쿼리 키 타입이 명확하게 정의되어 있어요. 이를 통해 predicate 기반 무효화에서 타입 안전성이 보장돼요.


79-145: 그룹 캐시 통합과 쿼리 키 구조가 잘 설계되어 있어요.

detectionSignature를 쿼리 키에 포함시켜 캐시를 정밀하게 관리하고, initialData로 그룹 캐시를 hydrate하는 패턴이 적절해요. 이전 리뷰에서 지적된 onSuccess 제거도 반영되었어요.


147-171: 캐시 저장용 useEffect가 적절하게 구현되어 있어요.

React Query v5에서 제거된 onSuccess 대신 useEffect를 사용하여 캐시 저장 로직을 처리하고 있어요. 중복 저장 방지 로직도 포함되어 있어요.


203-265: 상품 쿼리도 동일한 패턴으로 그룹 캐시와 통합되어 있어요.

카테고리 쿼리와 일관된 방식으로 구현되어 유지보수성이 좋아요.


305-338: predicate 기반 쿼리 무효화가 이전 리뷰 피드백을 잘 반영했어요.

쿼리 키 구조와 정확히 매칭되는 predicate 함수를 사용하여 그룹/이미지별 정밀 무효화가 가능해요.


102-111: 그룹 캐시 초기 데이터 사용 조건이 명확해요.

detectionSignature까지 일치해야 초기 데이터를 사용하는 조건이 적절해요. 다만 이전 리뷰에서 언급된 것처럼 "그룹 캐시가 있어도 네트워크 요청이 나가는 것"이 의도된 동작인지 확인이 필요해요.

Comment on lines +44 to +59
/**
* 마이페이지 히스토리 데이터를 결과 페이지 이미지 포맷으로 변환
* @param history 마이페이지 히스토리 객체(history item)
* @returns 결과 페이지에서 사용하는 이미지 데이터(generate image data)
*/
const toGenerateImageData = (
history: MyPageImageHistory
): GenerateImageData => ({
imageId: history.imageId,
imageUrl: history.generatedImageUrl,
isMirror: false,
equilibrium: history.equilibrium,
houseForm: history.houseForm,
tagName: history.tasteTag,
name: history.tasteTag,
});
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# MyPageImageHistory 타입 정의 찾기
rg "type MyPageImageHistory|interface MyPageImageHistory" -A 10

Repository: TEAM-HOUME/HOUME-CLIENT

Length of output: 767


🏁 Script executed:

# GenerateImageData 타입 정의 찾기
rg "type GenerateImageData|interface GenerateImageData" -A 10

Repository: TEAM-HOUME/HOUME-CLIENT

Length of output: 693


🏁 Script executed:

# toGenerateImageData 함수 사용 패턴 찾기
rg "toGenerateImageData" -B 2 -A 2

Repository: TEAM-HOUME/HOUME-CLIENT

Length of output: 838


🏁 Script executed:

# name 필드 사용 패턴 확인
rg "\.name" src/pages/generate --type=ts -B 1 -A 1 | head -40

Repository: TEAM-HOUME/HOUME-CLIENT

Length of output: 1654


🏁 Script executed:

# tagName 필드 사용 패턴 확인
rg "\.tagName|tagName:" src/pages/generate --type=ts -B 1 -A 1 | head -40

Repository: TEAM-HOUME/HOUME-CLIENT

Length of output: 715


🏁 Script executed:

# 전체 MyPageImageHistory 타입 정의 확인
rg "interface MyPageImageHistory" -A 20

Repository: TEAM-HOUME/HOUME-CLIENT

Length of output: 1358


🏁 Script executed:

# 실제로 history.name이 어디서 사용되는지 확인
rg "history\.name" src/pages/generate --type=ts -B 3 -A 3

Repository: TEAM-HOUME/HOUME-CLIENT

Length of output: 581


🏁 Script executed:

# MyPageImageDetail 타입 정의 확인 (언급된 경우)
rg "interface MyPageImageDetail|type MyPageImageDetail" -A 15

Repository: TEAM-HOUME/HOUME-CLIENT

Length of output: 1098


toGenerateImageData 함수의 타입 불일치 문제 수정 필요

함수 파라미터는 MyPageImageHistory 타입으로 지정되어 있지만, 이 타입에는 name 필드가 없습니다. 반면 name 필드는 MyPageImageDetail 타입에만 존재합니다. 현재 코드는 history.tasteTagname에 할당하고 있는데, 이는 실제 데이터 구조와 맞지 않습니다.

파라미터 타입을 MyPageImageDetail로 수정하거나, 함수 사용처에서 전달하는 데이터 타입을 명확히 하세요.

🤖 Prompt for AI Agents
In src/pages/generate/pages/result/ResultPage.tsx around lines 44 to 59, the
toGenerateImageData function accepts a MyPageImageHistory but maps a name field
that only exists on MyPageImageDetail; change the parameter type to
MyPageImageDetail (or, if callers truly pass MyPageImageHistory, remove/derive
the name field appropriately) so the function signature matches the actual
object shape and update any call sites or imports accordingly to use the
corrected type.

Comment on lines +92 to 110
const handleViewResult = (history: MyPageImageHistory) => {
const { houseId } = history;
logMyPageClickBtnImgCard();
const navigationState = buildResultNavigationState({
history,
userProfile: userProfile ?? null,
});
const params = new URLSearchParams({
from: 'mypage',
houseId: String(houseId),
});
navigate(`${ROUTES.GENERATE_RESULT}?${params.toString()}`);
navigate(`${ROUTES.GENERATE_RESULT}?${params.toString()}`, {
state: navigationState,
});
// 네비게이션 직후 우선순위 감지 프리페치 실행
scheduleDetectionPrefetch(history.imageId, history.generatedImageUrl, {
immediate: true,
});
};
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# 파일 위치 및 구조 확인
find . -type f -name "*.ts*" | grep -E "(GeneratedImagesSection|scheduleDetectionPrefetch)" | head -20

Repository: TEAM-HOUME/HOUME-CLIENT

Length of output: 231


🏁 Script executed:

#!/bin/bash
# scheduleDetectionPrefetch 함수 정의 찾기
rg -n "scheduleDetectionPrefetch|const scheduleDetectionPrefetch|function scheduleDetectionPrefetch" -A 5 -B 1

Repository: TEAM-HOUME/HOUME-CLIENT

Length of output: 2956


🏁 Script executed:

#!/bin/bash
# navigate 후 prefetch 호출 패턴 찾기
rg -n "navigate.*\)" -A 3 | grep -E "(prefetch|scheduleDetection)" -B 3

Repository: TEAM-HOUME/HOUME-CLIENT

Length of output: 634


🏁 Script executed:

#!/bin/bash
# GeneratedImagesSection.tsx 파일 확인
find . -type f -name "GeneratedImagesSection.tsx" -exec head -150 {} \;

Repository: TEAM-HOUME/HOUME-CLIENT

Length of output: 4711


🏁 Script executed:

#!/bin/bash
# useDetectionPrefetch 훅 찾기
find . -type f -name "*useDetectionPrefetch*" -o -name "*detection*" | grep -E "\.(ts|tsx)$"

Repository: TEAM-HOUME/HOUME-CLIENT

Length of output: 199


🏁 Script executed:

#!/bin/bash
# useDetectionPrefetch 구현 확인
rg -n "useDetectionPrefetch|prefetchDetection" -A 10 -B 2 | head -100

Repository: TEAM-HOUME/HOUME-CLIENT

Length of output: 10280


🏁 Script executed:

#!/bin/bash
# 코드베이스에서 navigate 직후 다른 작업 패턴 찾기
rg -n "navigate\(" -A 5 | grep -E "(prefetch|setTimeout|schedule)" -B 5 | head -80

Repository: TEAM-HOUME/HOUME-CLIENT

Length of output: 1021


🏁 Script executed:

#!/bin/bash
# useDetectionPrefetch.ts 전체 파일 읽기
wc -l src/pages/mypage/hooks/useDetectionPrefetch.ts

Repository: TEAM-HOUME/HOUME-CLIENT

Length of output: 117


🏁 Script executed:

#!/bin/bash
# useDetectionPrefetch.ts 핵심 부분 확인 (executePrefetch와 scheduleBackgroundPrefetch)
cat -n src/pages/mypage/hooks/useDetectionPrefetch.ts

Repository: TEAM-HOUME/HOUME-CLIENT

Length of output: 7488


🏁 Script executed:

#!/bin/bash
# 결과 페이지에서 이미지 프리패치를 하고 있는지 확인
find . -type f -name "*GENERATE_RESULT*" -o -name "*result*" | grep -E "(pages|components)" | grep -v node_modules

Repository: TEAM-HOUME/HOUME-CLIENT

Length of output: 145


🏁 Script executed:

#!/bin/bash
# generate result 페이지 찾기
fd -t f "result" src/pages --type f | head -20

Repository: TEAM-HOUME/HOUME-CLIENT

Length of output: 206


🏁 Script executed:

#!/bin/bash
# ROUTES.GENERATE_RESULT 경로 확인
rg -n "GENERATE_RESULT" -A 2 -B 2 | head -50

Repository: TEAM-HOUME/HOUME-CLIENT

Length of output: 2352


🏁 Script executed:

#!/bin/bash
# ResultPage.tsx 파일 확인
head -150 src/pages/generate/pages/result/ResultPage.tsx

Repository: TEAM-HOUME/HOUME-CLIENT

Length of output: 5561


🏁 Script executed:

#!/bin/bash
# ResultPage.tsx 전체 라인 수 확인
wc -l src/pages/generate/pages/result/ResultPage.tsx

Repository: TEAM-HOUME/HOUME-CLIENT

Length of output: 117


🏁 Script executed:

#!/bin/bash
# ResultPage.tsx에서 prefetch 관련 로직 확인
rg -n "prefetch|useDetection|scheduleDetection" src/pages/generate/pages/result/ResultPage.tsx

Repository: TEAM-HOUME/HOUME-CLIENT

Length of output: 159


🏁 Script executed:

#!/bin/bash
# ResultPage에서 useLocation state 사용 패턴 및 프리패치 관련 확인
grep -n "forwardedDetection\|cachedDetection\|prefetch" src/pages/generate/pages/result/ResultPage.tsx

Repository: TEAM-HOUME/HOUME-CLIENT

Length of output: 540


🏁 Script executed:

#!/bin/bash
# 마이페이지에서 buildResultNavigationState로 전달하는 데이터 확인
cat -n src/pages/mypage/utils/resultNavigation.ts

Repository: TEAM-HOUME/HOUME-CLIENT

Length of output: 2002


🏁 Script executed:

#!/bin/bash
# primeDetectionCacheEntry 함수 확인
rg -n "primeDetectionCacheEntry" -A 10 -B 2

Repository: TEAM-HOUME/HOUME-CLIENT

Length of output: 2508


🏁 Script executed:

#!/bin/bash
# 결론: 전체 플로우 정리 위해 GeneratedImagesSection의 전체 handleViewResult 맥락 재확인
cat -n src/pages/mypage/components/section/generatedImages/GeneratedImagesSection.tsx | sed -n '92,111p'

Repository: TEAM-HOUME/HOUME-CLIENT

Length of output: 804


네비게이션 직후 비동기 프리페치 작업은 취소될 위험이 있어요.

navigate() 호출 후 scheduleDetectionPrefetch(..., { immediate: true })를 실행하는데, executePrefetch는 이미지 로드와 ONNX 모델 추론을 수행하는 비동기 작업이에요. 페이지 전환 중에 컴포넌트가 언마운트되면서 작업이 중단되거나 미완료된 상태에서 메모리 누수가 발생할 수 있어요.

더 안정적인 방법은:

  1. buildResultNavigationState에서 현재 캐시된 감지 데이터를 이미 navigation state에 포함하고 있으므로, 결과 페이지 마운트 시점에 필요한 데이터를 프리페치하기
  2. 또는 navigate() 전에 프리페치를 완료하고 결과를 state에 포함시키기

현재 navigate 직후 prefetch는 타이밍상 결과 페이지에서 활용되지 않을 가능성이 높아요.

🤖 Prompt for AI Agents
In
src/pages/mypage/components/section/generatedImages/GeneratedImagesSection.tsx
around lines 92-110, the current pattern calls scheduleDetectionPrefetch
immediately after navigate which risks the async image/model prefetch being
cancelled during route transition; either move the prefetch logic to the result
page so it runs on mount (using detection data already included in
buildResultNavigationState) or, if you must prefetch here, await the prefetch
before calling navigate and attach the prefetch results to the navigation state
so the result page can use them without relying on a cancelled background task;
implement one of these approaches and remove the navigate-then-fire-and-forget
prefetch pattern.

@github-actions
Copy link

빌드 결과

빌드 성공 🎊

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

♻️ Duplicate comments (3)
src/pages/generate/hooks/useFurnitureCuration.ts (3)

147-171: 캐시 저장 useEffect에 query.isSuccess 체크를 추가하면 더 명확해요.

현재는 query.data만 확인하고 있어서, initialData가 있을 때도 effect가 실행돼요. 물론 equality 체크(lines 152-157)가 있어서 중복 저장은 막히지만, query.isSuccess를 추가로 확인하면 "실제로 fetch가 성공했을 때만 저장한다"는 의도가 더 명확해져요.

다음과 같이 개선할 수 있어요:

  useEffect(() => {
    if (groupId === null) return;
-   if (!query.data) return;
+   if (!query.data || !query.isSuccess) return;
    const existing =
      useCurationCacheStore.getState().groups[groupId]?.categories ?? null;
    if (
      existing &&
      existing.detectionSignature === detectionSignature &&
      existing.response === query.data
    ) {
      return;
    }
    saveGroupCategories({
      groupId,
      response: query.data,
      detectedObjects: normalizedDetectedObjects,
      detectionSignature,
    });
- }, [
+ }, [
    groupId,
    query.data,
+   query.isSuccess,
    detectionSignature,
    normalizedDetectedObjects,
    saveGroupCategories,
  ]);

250-263: 상품 캐시 저장 effect에도 query.isSuccess 체크가 있으면 좋아요.

카테고리 쿼리와 동일한 패턴으로, query.isSuccess를 체크하면 fetch 성공 시에만 캐시에 저장한다는 의도가 명확해져요.

  useEffect(() => {
    if (groupId === null || categoryId === null) return;
-   if (!query.data) return;
+   if (!query.data || !query.isSuccess) return;
    const groupCache = useCurationCacheStore.getState().groups[groupId];
    const existing = groupCache?.products[categoryId] ?? null;
    if (existing?.response === query.data) {
      return;
    }
    saveGroupProducts({
      groupId,
      categoryId,
      response: query.data,
    });
- }, [groupId, categoryId, query.data, saveGroupProducts]);
+ }, [groupId, categoryId, query.data, query.isSuccess, saveGroupProducts]);

305-339: invalidation predicate의 타입 단언을 더 안전하게 개선할 수 있어요.

Lines 314-317과 328-331에서 query.queryKeyas [unknown, Partial<CategoriesQueryVariables>]로 단언하고 있어요. unknown 타입을 사용하는 대신, 쿼리 키 상수의 실제 타입을 활용하면 더 타입 안전하게 작성할 수 있어요.

다음과 같이 개선할 수 있어요:

          predicate: (query) => {
-           const [key, variables] = query.queryKey as [
-             unknown,
-             Partial<CategoriesQueryVariables>,
-           ];
+           if (query.queryKey.length !== 2) return false;
+           const [key, variables] = query.queryKey;
+           if (typeof variables !== 'object' || variables === null) return false;
+           const typed = variables as Partial<CategoriesQueryVariables>;
            return (
              key === QUERY_KEY.GENERATE_FURNITURE_CATEGORIES_GROUP &&
-             variables?.groupId === groupId
+             typed.groupId === groupId
            );
          },

참고: 앞서 지적한 queryKey 구조 변경을 적용하면 이 invalidation 로직도 함께 수정해야 해요.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ab0fe38 and d781917.

📒 Files selected for processing (1)
  • src/pages/generate/hooks/useFurnitureCuration.ts (5 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
src/pages/**

📄 CodeRabbit inference engine (README.md)

Organize code using a feature-based structure within pages/ directory, grouping page-specific logic (components, constants, apis, types, utils, hooks) together by domain

Files:

  • src/pages/generate/hooks/useFurnitureCuration.ts
src/**/hooks/**/*.ts

⚙️ CodeRabbit configuration file

src/**/hooks/**/*.ts: 커스텀 훅 리뷰 시:

  • 파일명/함수명은 use* 접두사 사용
  • 의존성 배열(exhaustive-deps) 준수, 참조 안정성 유지(함수/객체 메모이제이션)
  • 반환 값 일관성(tuple/object), 에러/로딩 상태 명확화
  • 제네릭 훅은 타입 매개변수 기본값 검토
  • TanStack Query 훅 네이밍: use + 행위 + 대상 + Query/Mutation (예: useGetUserListQuery)

Files:

  • src/pages/generate/hooks/useFurnitureCuration.ts
src/**/*.ts

⚙️ CodeRabbit configuration file

src/**/*.ts: TypeScript 파일 리뷰 시:

  • 타입/인터페이스는 PascalCase 사용, Type/I/T 접두사·접미사 금지 (제네릭 T 제외)
  • interface는 객체 타입, type은 union/별칭에 사용하는지 확인
  • API Response는 XXXResponse 네이밍 따르는지 확인
  • var 사용 금지, const 우선 사용 확인
  • type-only import 사용(import type), 불필요한 런타임 의존성 제거
  • satisfies 연산자 활용으로 정확한 타입 보장

Files:

  • src/pages/generate/hooks/useFurnitureCuration.ts
src/**

⚙️ CodeRabbit configuration file

src/**: 전체 프로젝트 코드 리뷰 시:

기본 규칙

  • 모든 컴포넌트는 반드시 TypeScript로 작성
  • SVG는 ?react 쿼리를 통해 React 컴포넌트로 임포트
  • 스타일은 반드시 Vanilla Extract(.css.ts)로 작성
  • 절대 경로 임포트 사용 (@pages, @shared 등)
  • overlay-kit을 통한 모달 관리 패턴 준수

네이밍 컨벤션

  • 컴포넌트와 클래스는 PascalCase 사용
  • 폴더명은 camelCase 사용
  • 파일명은 camelCase 사용 (컴포넌트 파일 제외)
  • 변수와 함수는 camelCase 사용
  • 상수는 BIG_SNAKE_CASE 사용
  • 모든 타입/인터페이스는 PascalCase, Type/I/T 접두사·접미사 금지 (제네릭 T 제외)
  • Props 타입은 컴포넌트명 + Props 형식
  • API Response 타입은 XXXResponse 형식
  • 함수 네이밍: get/create/check/handle/is/has/can 접두사 활용
  • API 함수는 HTTP메서드 + 명사 형식 (getUserList, postComment)
  • TanStack Query 훅: use + 행위 + 대상 + Query/Mutation (예: useGetUserListQuery)
  • 커스텀 훅 파일명: use*.ts

보안 및 성능

  • 환경 변수는 반드시 import.meta.env 사용
  • API 키나 시크릿을 하드코딩하지 않음
  • React.memo, useMemo, useCallback의 적절한 사용
  • 큰 번들 사이즈 경고 (dynamic import 제안)
  • 템플릿 리터럴 사용 권장 (문자열 조합 시)
  • 구조 분해 할당 적극 활용

커밋 및 브랜치 패턴

  • 커밋 타입: feat, fix, docs, style, refactor, test, chore, design, comment, rename, remove
  • 브랜치 패턴: type/description/#issue-number 형식

Files:

  • src/pages/generate/hooks/useFurnitureCuration.ts
🧠 Learnings (2)
📚 Learning: 2025-10-21T22:07:55.899Z
Learnt from: soyyyyy
Repo: TEAM-HOUME/HOUME-CLIENT PR: 341
File: src/pages/generate/pages/result/curationSheet/CurationSheet.tsx:63-68
Timestamp: 2025-10-21T22:07:55.899Z
Learning: In HOUME-CLIENT project: React keys for furniture product lists should use the stable `furnitureProductId` rather than the ephemeral `p.id` (recommendId). The recommendId changes per recommendation session, while furnitureProductId is a stable identifier for the product itself, enabling React to properly reconcile and reuse DOM elements across different recommendation sets.

Applied to files:

  • src/pages/generate/hooks/useFurnitureCuration.ts
📚 Learning: 2025-07-15T17:57:29.375Z
Learnt from: maehwasoo
Repo: TEAM-HOUME/HOUME-CLIENT PR: 180
File: src/pages/onboarding/hooks/useMoodBoardImage.hooks.ts:15-19
Timestamp: 2025-07-15T17:57:29.375Z
Learning: React Query의 queryKey에 사용되지 않는 파라미터를 포함하면 불필요한 캐시 분할이 발생합니다. queryKey는 실제로 쿼리 함수에서 사용되는 파라미터만 포함해야 합니다.

Applied to files:

  • src/pages/generate/hooks/useFurnitureCuration.ts
🔇 Additional comments (2)
src/pages/generate/hooks/useFurnitureCuration.ts (2)

60-66: 헬퍼 훅들이 잘 구성되어 있어요.

  • useFurnitureDashboardQuery는 명확한 staleTime 설정과 함께 기본적인 쿼리 패턴을 잘 따르고 있어요.
  • useActiveImageId, useActiveImageCurationState 같은 셀렉터 훅들은 간결하고 명확해요.
  • useSheetSnapStateuseMemo로 setter와 state를 묶어 불필요한 리렌더를 방지하고 있어요.
  • useOpenCurationSheet는 깔끔한 헬퍼 추상화예요.

커스텀 훅 네이밍 규칙(use* 접두사)과 반환 값 패턴이 일관적으로 잘 적용되어 있어요.

Also applies to: 71-72, 271-272, 278-288, 379-384


27-54: These type definitions are correctly left unexported. CategoriesQueryVariables, CategoriesQueryKey, ProductsQueryVariables, and ProductsQueryKey are internal implementation details of the hook file used only within useFurnitureCuration.ts to structure React Query operations. They follow the codebase pattern where internal types remain unexported unless needed by external consumers.

Likely an incorrect or invalid review comment.

Comment on lines +113 to +123
const categoriesQueryKey: CategoriesQueryKey = [
groupId !== null
? QUERY_KEY.GENERATE_FURNITURE_CATEGORIES_GROUP
: QUERY_KEY.GENERATE_FURNITURE_CATEGORIES,
{
groupId,
imageId,
detectedObjects,
],
queryFn: () => getGeneratedImageCategories(imageId!, detectedObjects),
enabled: Boolean(imageId) && detectedObjects.length > 0,
staleTime: 60 * 1000,
gcTime: 5 * 60 * 1000,
detectionSignature,
codes: normalizedDetectedObjects,
},
];
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

queryKey에 detectionSignaturecodes가 중복돼요.

detectionSignaturenormalizedDetectedObjects(즉, codes)를 정렬하고 join한 문자열이에요. 두 값은 완전히 동일한 정보를 표현하므로, queryKey에 둘 다 포함할 필요는 없어요. codes 배열만으로도 React Query가 올바르게 캐시를 구분할 수 있어요.

다음과 같이 detectionSignature를 queryKey에서 제거할 수 있어요:

  const categoriesQueryKey: CategoriesQueryKey = [
    groupId !== null
      ? QUERY_KEY.GENERATE_FURNITURE_CATEGORIES_GROUP
      : QUERY_KEY.GENERATE_FURNITURE_CATEGORIES,
    {
      groupId,
      imageId,
-     detectionSignature,
      codes: normalizedDetectedObjects,
    },
  ];

단, detectionSignature는 group cache 비교용으로 여전히 필요하므로 변수 자체는 유지해야 해요.

🤖 Prompt for AI Agents
In src/pages/generate/hooks/useFurnitureCuration.ts around lines 113 to 123, the
queryKey currently includes both detectionSignature and
normalizedDetectedObjects (codes) which duplicate the same information; remove
detectionSignature from the queryKey array and keep only the
codes/normalizedDetectedObjects there so React Query caching is correct, but do
not delete the detectionSignature variable since it’s still used elsewhere for
group cache comparisons.

Comment on lines +113 to +144
const categoriesQueryKey: CategoriesQueryKey = [
groupId !== null
? QUERY_KEY.GENERATE_FURNITURE_CATEGORIES_GROUP
: QUERY_KEY.GENERATE_FURNITURE_CATEGORIES,
{
groupId,
imageId,
detectedObjects,
],
queryFn: () => getGeneratedImageCategories(imageId!, detectedObjects),
enabled: Boolean(imageId) && detectedObjects.length > 0,
staleTime: 60 * 1000,
gcTime: 5 * 60 * 1000,
detectionSignature,
codes: normalizedDetectedObjects,
},
];

const initialCategoriesResponse = canUseGroupInitialData
? groupCategoriesEntry!.response
: undefined;

const query = useQuery<
FurnitureCategoriesResponse,
Error,
FurnitureCategoriesResponse,
CategoriesQueryKey
>({
// queryKey에 이미지/감지값 전체를 직접 포함해 의존성 유지
queryKey: categoriesQueryKey,
queryFn: () =>
getGeneratedImageCategories(imageId!, normalizedDetectedObjects),
enabled: Boolean(imageId) && normalizedDetectedObjects.length > 0,
staleTime: 15 * 60 * 1000,
gcTime: 30 * 60 * 1000,
...(initialCategoriesResponse
? { initialData: initialCategoriesResponse }
: {}),
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

queryKey에 queryFn에서 사용하지 않는 groupId가 포함되어 불필요한 캐시 분할이 발생해요.

현재 queryKey에 groupId를 포함하고 있지만, 실제 queryFn(lines 137-138)은 imageIdnormalizedDetectedObjects만 사용해요. 이로 인해 동일한 이미지와 감지 객체를 가진 요청이 서로 다른 그룹에서 올 경우 별도의 캐시 엔트리를 생성하게 되어, 동일한 API 응답을 중복으로 캐싱하게 돼요.

groupId는 캐시 저장(lines 147-171)에만 필요하므로, queryKey에서 제거하고 React Query 캐시는 imageId와 감지 객체만으로 공유하도록 개선할 수 있어요.

Based on learnings: React Query의 queryKey에 사용되지 않는 파라미터를 포함하면 불필요한 캐시 분할이 발생합니다.

다음 diff를 적용하여 수정하세요:

  const categoriesQueryKey: CategoriesQueryKey = [
-   groupId !== null
-     ? QUERY_KEY.GENERATE_FURNITURE_CATEGORIES_GROUP
-     : QUERY_KEY.GENERATE_FURNITURE_CATEGORIES,
+   QUERY_KEY.GENERATE_FURNITURE_CATEGORIES,
    {
-     groupId,
      imageId,
      detectionSignature,
      codes: normalizedDetectedObjects,
    },
  ];

그리고 타입 정의도 업데이트하세요:

-type CategoriesQueryKey = readonly [
-  (
-    | typeof QUERY_KEY.GENERATE_FURNITURE_CATEGORIES_GROUP
-    | typeof QUERY_KEY.GENERATE_FURNITURE_CATEGORIES
-  ),
-  CategoriesQueryVariables,
-];
+type CategoriesQueryKey = readonly [
+  typeof QUERY_KEY.GENERATE_FURNITURE_CATEGORIES,
+  Omit<CategoriesQueryVariables, 'groupId'>,
+];

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +217 to 248
const productsQueryKey: ProductsQueryKey = [
groupId !== null
? QUERY_KEY.GENERATE_FURNITURE_PRODUCTS_GROUP
: QUERY_KEY.GENERATE_FURNITURE_PRODUCTS,
{
groupId,
imageId,
categoryId,
},
];

const initialProductsResponse =
groupId !== null && productCacheEntry
? productCacheEntry.response
: undefined;

const query = useQuery<
FurnitureProductsInfoResponse,
Error,
FurnitureProductsInfoResponse,
ProductsQueryKey
>({
// queryKey에 그룹/이미지/카테고리 식별자를 직접 배치
queryKey: productsQueryKey,
queryFn: () => getGeneratedImageProducts(imageId!, categoryId!),
enabled: Boolean(imageId) && Boolean(categoryId),
staleTime: 30 * 1000,
gcTime: 5 * 60 * 1000,
enabled: Boolean(imageId) && categoryId !== null,
staleTime: 5 * 60 * 1000,
gcTime: 30 * 60 * 1000,
...(initialProductsResponse
? { initialData: initialProductsResponse }
: {}),
});
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

상품 쿼리에서도 groupId가 queryKey에 불필요하게 포함되어 있어요.

카테고리 쿼리와 동일한 패턴의 문제예요. queryFn(line 241)은 imageIdcategoryId만 사용하지만 queryKey에는 groupId가 포함되어 있어요. 동일한 이미지·카테고리 조합이 다른 그룹에서 요청될 때마다 별도의 캐시 엔트리가 생성돼요.

Based on learnings: React Query의 queryKey에 사용되지 않는 파라미터를 포함하면 불필요한 캐시 분할이 발생합니다.

다음 diff를 적용하여 수정하세요:

  const productsQueryKey: ProductsQueryKey = [
-   groupId !== null
-     ? QUERY_KEY.GENERATE_FURNITURE_PRODUCTS_GROUP
-     : QUERY_KEY.GENERATE_FURNITURE_PRODUCTS,
+   QUERY_KEY.GENERATE_FURNITURE_PRODUCTS,
    {
-     groupId,
      imageId,
      categoryId,
    },
  ];
🤖 Prompt for AI Agents
In src/pages/generate/hooks/useFurnitureCuration.ts around lines 217-248, the
products queryKey incorrectly includes groupId causing unnecessary cache
fragmentation; update the productsQueryKey to use the non-group key
(QUERY_KEY.GENERATE_FURNITURE_PRODUCTS) and remove groupId from the params
object so the key only contains imageId and categoryId (matching what queryFn
actually uses), keeping the existing initialProductsResponse logic and types
intact.

@github-actions
Copy link

빌드 결과

빌드 성공 🎊

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (3)
src/pages/mypage/hooks/useDetectionPrefetch.ts (3)

44-55: PrefetchTaskinterface로 바꾸는 게 가이드라인에 맞아요.
현재 객체 형태 타입을 type으로 선언하고 있어요. (기존 리뷰 코멘트와 동일)

 type PrefetchPriority = 'immediate' | 'background';

 export interface DetectionPrefetchOptions {
   priority?: PrefetchPriority;
 }

-type PrefetchTask = {
+interface PrefetchTask {
   imageId: number;
   imageUrl: string;
-};
+}

124-148: mapHotspotsToDetectedObjects 결과를 다시 filterAllowedDetectedObjects로 필터링하는 중복을 제거해야 합니다.
mapHotspotsToDetectedObjects가 내부에서 이미 allowed 필터 + 로깅을 수행하는 구조라, 현재는 중복 필터/중복 로그 가능성이 큽니다. (기존 리뷰 코멘트와 동일)

     ) => {
       const pipeline = buildHotspotsPipeline(targetImage, processed);
-      const rawDetectedObjects = mapHotspotsToDetectedObjects(
-        pipeline.hotspots
-      );
-      const detectedObjects = filterAllowedDetectedObjects(rawDetectedObjects, {
-        stage: 'prefetch-detection',
-        imageId,
-        hotspotCount: pipeline.hotspots.length,
-      });
+      const detectedObjects = mapHotspotsToDetectedObjects(pipeline.hotspots);

       storeDetections(imageId, imageUrl, processed, {
         hotspots: pipeline.hotspots,
         detectedObjects,
       });
     },

149-191: 프로덕션 로깅/디버깅 관점에서 console.warn 및 “빈 catch”는 정리 필요합니다.

  • console.warn → 프로젝트 로깅 유틸(예: logFurniturePipelineEvent) 권장 (기존 리뷰 코멘트와 동일)
  • loadImageElement 실패 후 loadCorsImage로 폴백하는 catch가 에러를 삼켜서 원인 추적이 어려워요 (기존 리뷰 코멘트와 동일)
-        try {
-          targetImage = await loadImageElement(imageUrl);
-        } catch {
+        try {
+          targetImage = await loadImageElement(imageUrl);
+        } catch (loadError) {
+          // TODO: 프로젝트 로깅 유틸로 로깅 권장 (원인 추적)
           targetImage = await loadCorsImage(imageUrl);
         }
...
-          console.warn('감지 프리페치 실패', inferenceError);
+          // TODO: console.warn 대신 로깅 유틸 사용 권장
...
-      } catch (unexpectedError) {
-        console.warn('감지 프리페치 예외', unexpectedError);
+      } catch (unexpectedError) {
+        // TODO: console.warn 대신 로깅 유틸 사용 권장
       } finally {
HOUME-CLIENT 코드베이스에서 logFurniturePipelineEvent(또는 이에 준하는 로깅 유틸)의 표준 사용 패턴(이벤트명/페이로드/level 옵션)과 권장 import 경로는 무엇인가요?
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d781917 and 69c698d.

📒 Files selected for processing (1)
  • src/pages/mypage/hooks/useDetectionPrefetch.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
src/**/hooks/**/*.ts

⚙️ CodeRabbit configuration file

src/**/hooks/**/*.ts: 커스텀 훅 리뷰 시:

  • 파일명/함수명은 use* 접두사 사용
  • 의존성 배열(exhaustive-deps) 준수, 참조 안정성 유지(함수/객체 메모이제이션)
  • 반환 값 일관성(tuple/object), 에러/로딩 상태 명확화
  • 제네릭 훅은 타입 매개변수 기본값 검토
  • TanStack Query 훅 네이밍: use + 행위 + 대상 + Query/Mutation (예: useGetUserListQuery)

Files:

  • src/pages/mypage/hooks/useDetectionPrefetch.ts
src/**/*.ts

⚙️ CodeRabbit configuration file

src/**/*.ts: TypeScript 파일 리뷰 시:

  • 타입/인터페이스는 PascalCase 사용, Type/I/T 접두사·접미사 금지 (제네릭 T 제외)
  • interface는 객체 타입, type은 union/별칭에 사용하는지 확인
  • API Response는 XXXResponse 네이밍 따르는지 확인
  • var 사용 금지, const 우선 사용 확인
  • type-only import 사용(import type), 불필요한 런타임 의존성 제거
  • satisfies 연산자 활용으로 정확한 타입 보장

Files:

  • src/pages/mypage/hooks/useDetectionPrefetch.ts
src/**

⚙️ CodeRabbit configuration file

src/**: 전체 프로젝트 코드 리뷰 시:

기본 규칙

  • 모든 컴포넌트는 반드시 TypeScript로 작성
  • SVG는 ?react 쿼리를 통해 React 컴포넌트로 임포트
  • 스타일은 반드시 Vanilla Extract(.css.ts)로 작성
  • 절대 경로 임포트 사용 (@pages, @shared 등)
  • overlay-kit을 통한 모달 관리 패턴 준수

네이밍 컨벤션

  • 컴포넌트와 클래스는 PascalCase 사용
  • 폴더명은 camelCase 사용
  • 파일명은 camelCase 사용 (컴포넌트 파일 제외)
  • 변수와 함수는 camelCase 사용
  • 상수는 BIG_SNAKE_CASE 사용
  • 모든 타입/인터페이스는 PascalCase, Type/I/T 접두사·접미사 금지 (제네릭 T 제외)
  • Props 타입은 컴포넌트명 + Props 형식
  • API Response 타입은 XXXResponse 형식
  • 함수 네이밍: get/create/check/handle/is/has/can 접두사 활용
  • API 함수는 HTTP메서드 + 명사 형식 (getUserList, postComment)
  • TanStack Query 훅: use + 행위 + 대상 + Query/Mutation (예: useGetUserListQuery)
  • 커스텀 훅 파일명: use*.ts

보안 및 성능

  • 환경 변수는 반드시 import.meta.env 사용
  • API 키나 시크릿을 하드코딩하지 않음
  • React.memo, useMemo, useCallback의 적절한 사용
  • 큰 번들 사이즈 경고 (dynamic import 제안)
  • 템플릿 리터럴 사용 권장 (문자열 조합 시)
  • 구조 분해 할당 적극 활용

커밋 및 브랜치 패턴

  • 커밋 타입: feat, fix, docs, style, refactor, test, chore, design, comment, rename, remove
  • 브랜치 패턴: type/description/#issue-number 형식

Files:

  • src/pages/mypage/hooks/useDetectionPrefetch.ts
🧬 Code graph analysis (1)
src/pages/mypage/hooks/useDetectionPrefetch.ts (8)
src/pages/generate/hooks/useOnnxModel.ts (1)
  • useONNXModel (221-371)
src/pages/generate/constants/detection.ts (1)
  • OBJ365_MODEL_PATH (7-7)
src/pages/generate/stores/useDetectionCacheStore.ts (1)
  • useDetectionCacheStore (41-75)
src/pages/generate/types/detection.ts (1)
  • ProcessedDetections (22-25)
src/pages/generate/hooks/useFurnitureHotspots.ts (2)
  • FurnitureHotspot (41-41)
  • loadCorsImage (64-112)
src/pages/generate/constants/furnitureCategoryMapping.ts (1)
  • FurnitureCategoryCode (25-25)
src/pages/generate/hooks/furnitureHotspotPipeline.ts (1)
  • buildHotspotsPipeline (160-266)
src/pages/generate/utils/detectedObjectMapper.ts (2)
  • mapHotspotsToDetectedObjects (13-52)
  • filterAllowedDetectedObjects (55-78)

Comment on lines +70 to +103
// 세마포어 슬롯 확보
const acquireSlot = useCallback(async () => {
if (activeCountRef.current < MAX_CONCURRENCY) {
activeCountRef.current += 1;
return;
}
await new Promise<void>((resolve) => {
waitersRef.current.push(resolve);
});
activeCountRef.current += 1;
}, []);

// 세마포어 슬롯 반환
const releaseSlot = useCallback(() => {
activeCountRef.current = Math.max(0, activeCountRef.current - 1);
const next = waitersRef.current.shift();
if (next) {
next();
}
}, []);

// 공통 실행 래퍼: 동시 실행 상한을 2개로 제한
const runWithSemaphore = useCallback(
async (task: () => Promise<void>) => {
await acquireSlot();
try {
await task();
} finally {
releaseSlot();
}
},
[acquireSlot, releaseSlot]
);

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

세마포어 주석이 구현과 불일치합니다(MAX_CONCURRENCY=1인데 “2개”로 표기).
오해 소지가 커서 주석 또는 상수를 일치시키는 게 좋아요.

-  // 공통 실행 래퍼: 동시 실행 상한을 2개로 제한
+  // 공통 실행 래퍼: 동시 실행 상한을 MAX_CONCURRENCY로 제한
🤖 Prompt for AI Agents
In src/pages/mypage/hooks/useDetectionPrefetch.ts around lines 70 to 103 the
comment "공통 실행 래퍼: 동시 실행 상한을 2개로 제한" disagrees with the implementation which
uses MAX_CONCURRENCY=1; update them to match by either changing the comment to
reflect the actual limit (e.g., "동시 실행 상한을 1개로 제한") or, if the intent was to
allow two concurrent tasks, change the MAX_CONCURRENCY constant to 2 and ensure
tests/usage expect that; keep implementation and comment consistent.

Comment on lines +193 to +213
// 백그라운드 큐를 순차로 소모해 모델 호출 폭주 방지
const drainQueue = useCallback(async () => {
if (drainingRef.current) return;
drainingRef.current = true;
try {
const jobs: Promise<void>[] = [];
while (queueRef.current.length > 0) {
const task = queueRef.current.shift();
if (!task) continue;
jobs.push(
runWithSemaphore(async () => {
await executePrefetch(task.imageId, task.imageUrl);
await sleep(PREFETCH_DELAY_MS); // 감지 모델 연속 호출 완화
})
);
}
await Promise.all(jobs);
} finally {
drainingRef.current = false;
}
}, [executePrefetch, runWithSemaphore]);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

drainQueue는 draining 중 추가된 작업을 “놓칠” 수 있어요(재현 가능).
현재 구조는 (1) drainingRef로 재진입을 막고 (2) while에서 작업을 한 번에 jobs로 뽑아둔 뒤 Promise.all을 기다려서, draining 도중 enqueue된 항목이 다음 drain 트리거 없이 큐에 남을 수 있습니다.

아래처럼 “순차 처리 + draining 종료 시 큐 재확인” 형태가 안전합니다(현재 MAX_CONCURRENCY=1과도 일치):

   const drainQueue = useCallback(async () => {
     if (drainingRef.current) return;
     drainingRef.current = true;
     try {
-      const jobs: Promise<void>[] = [];
-      while (queueRef.current.length > 0) {
-        const task = queueRef.current.shift();
-        if (!task) continue;
-        jobs.push(
-          runWithSemaphore(async () => {
-            await executePrefetch(task.imageId, task.imageUrl);
-            await sleep(PREFETCH_DELAY_MS); // 감지 모델 연속 호출 완화
-          })
-        );
-      }
-      await Promise.all(jobs);
+      while (queueRef.current.length > 0) {
+        const task = queueRef.current.shift();
+        if (!task) continue;
+        await runWithSemaphore(async () => {
+          await executePrefetch(task.imageId, task.imageUrl);
+          await sleep(PREFETCH_DELAY_MS); // 감지 모델 연속 호출 완화
+        });
+      }
     } finally {
       drainingRef.current = false;
+      // draining 중 추가된 작업 처리 보장
+      if (queueRef.current.length > 0) {
+        void drainQueue();
+      }
     }
   }, [executePrefetch, runWithSemaphore]);
🤖 Prompt for AI Agents
In src/pages/mypage/hooks/useDetectionPrefetch.ts around lines 193 to 213,
drainQueue can miss tasks enqueued while it is running; change the logic to keep
draining until the queue is truly empty by looping: set drainingRef.current =
true, then do { while (queueRef.current.length > 0) { shift one task and run it
(respecting runWithSemaphore/sleep) } } while (queueRef.current.length > 0);
finally set drainingRef.current = false. This ensures sequential processing and
rechecks the queue after finishing so tasks added during draining are not left
behind.

@maehwasoo maehwasoo added 🤙 경호 웹 36기 엄경호 🔧 Fix 버그 수정 관련 labels Jan 4, 2026
@maehwasoo
Copy link
Member Author

@codex review this PR

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 69c698d934

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +151 to +155
if (!imageId || !imageUrl) return;
if (pendingRef.current.has(imageId)) return;
const cached = useDetectionCacheStore.getState().images[imageId];
if (cached) return;
if (isLoading || error) return;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Queue prefetch when model is still loading

The new prefetcher drops work whenever the ONNX model is still loading: executePrefetch returns early on isLoading, and no retry is scheduled. On MyPage, GeneratedImagesSection marks each image as “prefetched” before calling prefetchDetection, so if the model hasn’t finished loading yet (common on first visit), those images are never re‑queued and the detection cache never fills. This defeats the intended prefetch behavior and forces full inference on the result page. Consider queueing these requests until isLoading flips false or retrying after load completes.

Useful? React with 👍 / 👎.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@codex Any other feedback on the code?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Below is feedback focused on the area you referenced (src/pages/mypage/hooks/useDetectionPrefetch.ts around line 155). I inspected it with nl -ba src/pages/mypage/hooks/useDetectionPrefetch.ts | sed -n '120,220p' and cross-checked mapHotspotsToDetectedObjects with nl -ba src/pages/generate/utils/detectedObjectMapper.ts | sed -n '1,160p'.

Feedback

  1. Potential false-negative on imageId guard (!imageId)

    • In executePrefetch, the guard if (!imageId || !imageUrl) return; will treat 0 as invalid. If imageId can be 0, this will skip valid work. Consider a nullish check instead (e.g., imageId == null) to preserve 0 as valid. src/pages/mypage/hooks/useDetectionPrefetch.tsL149-L156
  2. Duplicate filtering/logging in processAndStore

  3. console.warn usage in error paths

  4. Type alias vs interface for object shape

If you want, I can apply these changes directly and include any additional guardrails (e.g., imageId nullish checks across both immediate and background paths).

View task →

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🔧 Fix 버그 수정 관련 🤙 경호 웹 36기 엄경호

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[fix] 가구 매핑 수정 및 스낵바 동작 개선

2 participants