-
Notifications
You must be signed in to change notification settings - Fork 2
[fix] 가구 매핑 수정 및 스낵바 동작 개선 #395
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Conversation
📝 WalkthroughWalkthroughRootLayout에 ONNX 모델 워밍업 훅이 추가되고, 세션/영구 캐시 기반의 검출·모델 캐싱 및 프리패치가 도입되었습니다. 카테고리 타입이 FurnitureCategory에서 CabinetRefinementCategory로 리네임되었고, Result/MyPage 흐름에 detection·group 캐시 및 그룹 쿼리 키가 연동되었습니다. Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes
Suggested labels
Suggested reviewers
Pre-merge checks❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
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. Comment |
빌드 결과빌드 실패 ❌ |
🎨 Storybook 빌드 완료!📚 Storybook: https://686a831b8e000345a949970a-lhiaeknnef.chromatic.com/ 📊 빌드 정보
|
There was a problem hiding this 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:useMyPageImages와useMyPageImageDetail의cacheTime옵션을gcTime으로 변경해야 합니다현재 프로젝트에서 사용 중인
@tanstack/react-queryv5.81.5에서는cacheTime이gcTime으로 리네이밍되었습니다. 라인 32와 56의cacheTime옵션이 TypeScript 타입 에러를 유발합니다.- cacheTime: 30 * 60 * 1000, + gcTime: 30 * 60 * 1000,두 훅 모두 동일하게 수정하세요.
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
⛔ Files ignored due to path filters (1)
.gitignoreis 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.tssrc/pages/generate/hooks/furnitureHotspotState.tssrc/pages/generate/hooks/useFurnitureCuration.tssrc/pages/generate/stores/useCurationStore.tssrc/pages/generate/pages/result/curationSheet/CardProductItem.tsxsrc/pages/generate/GeneratePage.tsxsrc/pages/generate/pages/result/curationSheet/CurationSheetWrapper.css.tssrc/pages/generate/stores/useDetectionCacheStore.tssrc/pages/generate/utils/cabinetRefinementCategories.tssrc/pages/generate/pages/result/curationSheet/CurationSheet.css.tssrc/layout/RootLayout.tsxsrc/pages/generate/hooks/useDetectionCache.tssrc/pages/mypage/MyPage.tsxsrc/pages/generate/pages/result/components/GeneratedImgB.tsxsrc/pages/generate/pages/result/curationSheet/CurationSheetWrapper.tsxsrc/pages/mypage/components/card/cardCuration/CardCuration.tsxsrc/pages/mypage/utils/resultNavigation.tssrc/pages/generate/constants/furnitureCategoryMapping.tssrc/pages/generate/hooks/useFurnitureHotspots.tssrc/store/useUserStore.tssrc/pages/generate/pages/result/components/DetectionHotspots.tsxsrc/pages/generate/hooks/useOnnxModel.tssrc/pages/mypage/hooks/useMypage.tssrc/pages/generate/pages/result/components/GeneratedImgA.tsxsrc/pages/generate/pages/result/curationSheet/CurationSheet.tsxsrc/pages/generate/pages/result/ResultPage.tsxsrc/pages/generate/utils/refineFurnitureDetections.tssrc/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.tssrc/pages/generate/hooks/furnitureHotspotState.tssrc/pages/generate/hooks/useFurnitureCuration.tssrc/pages/generate/stores/useCurationStore.tssrc/pages/generate/pages/result/curationSheet/CurationSheetWrapper.css.tssrc/pages/generate/stores/useDetectionCacheStore.tssrc/pages/generate/utils/cabinetRefinementCategories.tssrc/pages/generate/pages/result/curationSheet/CurationSheet.css.tssrc/pages/generate/hooks/useDetectionCache.tssrc/pages/mypage/utils/resultNavigation.tssrc/pages/generate/constants/furnitureCategoryMapping.tssrc/pages/generate/hooks/useFurnitureHotspots.tssrc/store/useUserStore.tssrc/pages/generate/hooks/useOnnxModel.tssrc/pages/mypage/hooks/useMypage.tssrc/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.tssrc/pages/generate/hooks/furnitureHotspotState.tssrc/pages/generate/hooks/useFurnitureCuration.tssrc/pages/generate/stores/useCurationStore.tssrc/pages/generate/pages/result/curationSheet/CardProductItem.tsxsrc/pages/generate/GeneratePage.tsxsrc/pages/generate/pages/result/curationSheet/CurationSheetWrapper.css.tssrc/pages/generate/stores/useDetectionCacheStore.tssrc/pages/generate/utils/cabinetRefinementCategories.tssrc/pages/generate/pages/result/curationSheet/CurationSheet.css.tssrc/layout/RootLayout.tsxsrc/pages/generate/hooks/useDetectionCache.tssrc/pages/mypage/MyPage.tsxsrc/pages/generate/pages/result/components/GeneratedImgB.tsxsrc/pages/generate/pages/result/curationSheet/CurationSheetWrapper.tsxsrc/pages/mypage/components/card/cardCuration/CardCuration.tsxsrc/pages/mypage/utils/resultNavigation.tssrc/pages/generate/constants/furnitureCategoryMapping.tssrc/pages/generate/hooks/useFurnitureHotspots.tssrc/store/useUserStore.tssrc/pages/generate/pages/result/components/DetectionHotspots.tsxsrc/pages/generate/hooks/useOnnxModel.tssrc/pages/mypage/hooks/useMypage.tssrc/pages/generate/pages/result/components/GeneratedImgA.tsxsrc/pages/generate/pages/result/curationSheet/CurationSheet.tsxsrc/pages/generate/pages/result/ResultPage.tsxsrc/pages/generate/utils/refineFurnitureDetections.tssrc/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.tssrc/pages/generate/hooks/useFurnitureCuration.tssrc/pages/generate/hooks/useDetectionCache.tssrc/pages/generate/hooks/useFurnitureHotspots.tssrc/pages/generate/hooks/useOnnxModel.tssrc/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.tssrc/pages/generate/hooks/useDetectionCache.tssrc/pages/generate/hooks/useFurnitureHotspots.tssrc/pages/generate/hooks/useOnnxModel.tssrc/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.tssrc/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.tssrc/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.tsxsrc/pages/mypage/components/card/cardCuration/CardCuration.tsxsrc/pages/generate/pages/result/components/DetectionHotspots.tsxsrc/pages/generate/pages/result/components/GeneratedImgA.tsxsrc/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.tsxsrc/pages/mypage/components/card/cardCuration/CardCuration.tsxsrc/pages/generate/pages/result/components/DetectionHotspots.tsxsrc/pages/generate/pages/result/components/GeneratedImgA.tsxsrc/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.tsxsrc/pages/mypage/components/card/cardCuration/CardCuration.tsxsrc/pages/generate/pages/result/components/DetectionHotspots.tsxsrc/pages/generate/pages/result/components/GeneratedImgA.tsxsrc/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.tsxsrc/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.tsxsrc/pages/mypage/MyPage.tsxsrc/pages/generate/pages/result/ResultPage.tsxsrc/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.tsxsrc/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.tsxsrc/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.statenull 가드가 잘 처리되어 있어요.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분으로 늘린 것은 성능 최적화에 도움이 돼요. 다만queryKey에detectedObjects배열이 포함되어 있어서, 배열 참조가 변경되면 캐시 미스가 발생할 수 있어요.
detectedObjects가 매 렌더링마다 새 배열 참조를 생성하지 않는지 확인이 필요해요. 만약 그렇다면useMemo로 안정화하거나, 배열을 직렬화한 키를 사용하는 것을 고려해 주세요.
90-97: 제품 쿼리 캐싱도 적절해요.
staleTime5분,gcTime30분은 제품 데이터의 특성에 맞는 합리적인 설정이에요.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 appropriatesrc/pages/generate/hooks/useOnnxModel.ts (4)
41-51: 캐시 스토리지 헬퍼 함수 구현 확인Cache Storage API를 활용한 ONNX 모델 영속 캐싱이 잘 구현되어 있어요.
hasCacheStorageSupport함수가 브라우저 환경과cachesAPI 지원 여부를 정확히 확인하고 있고,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
CabinetRefinementCategory와CABINET_REFINEMENT_CATEGORY_LABELS의 임포트 및 재내보내기가 올바르게 구성되어 있어요. 모듈 경계를 깔끔하게 유지하면서 필요한 타입을 외부에 노출해요.
51-58: RefinedFurnitureDetection 인터페이스 타입 일관성 확인
refinedLabel,probabilities,contributions속성이 모두CabinetRefinementCategory타입으로 일관되게 업데이트되었어요.
285-296: 개발 환경 전용 디버그 로깅 확인
import.meta.env.DEV조건으로 프로덕션 빌드에서는 로깅이 제외되어요. 디버깅에 유용한 정보(refinedLabel, confidence)를 포함하고 있어요.
458-469: 타입 단언 사용 적절
probabilities와contributions객체 초기화 시 타입 단언이 필요한 상황이에요. 모든CabinetRefinementCategory키가 초기화되어 런타임 안정성이 보장돼요.
510-519: 라벨 매핑 일관성 LGTM
CABINET_REFINEMENT_CATEGORY_LABELS[bestLabel].en을 통해className과refinedLabelEn을 설정하는 방식이 일관되고 타입 안전해요.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!
snapStyles에hidden변형이 추가되었어요.--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: 타입 일관성 확인 필요
GeneratedImagesSection의userProfileprop이MyPageUserData | null을 기대하는데,userData는MyPageUserData | 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: 캐시 통합 패턴 적절해요
detectionCacheprop을 통해 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: 스켈레톤 데이터 변환 로직 적절해요
MyPageImageHistory를MyPageImageDetail스켈레톤으로 변환하는 헬퍼 함수가 명확하고, 기본값 설정도 합리적이에요.Also applies to: 50-54
src/pages/generate/pages/result/ResultPage.tsx (3)
94-102:forwardedDetectionMap메모이제이션 적절해요.
initialImageId와forwardedDetection이 변경될 때만 재계산되도록useMemo를 잘 사용했어요.
115-121: 조건부 fetch 플래그 분리가 명확해요.
shouldFetchExternalResult와shouldFetchMypageDetail로 fetch 조건을 분리해 가독성과 유지보수성이 좋아졌어요.
378-386: Props 전달은 올바르게 정의되어 있습니다.두 컴포넌트 모두 필요한 props 인터페이스를 이미 제대로 정의하고 있습니다:
GeneratedImgA:userProfile?: MyPageUserData | null,detectionCache?: Record<number, DetectionCacheEntry> | nullGeneratedImgB: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이었는데, 이제expanded와mid모두 lock이에요.hidden상태는 unlock으로 처리되는 것이 맞는지 확인해주세요.
98-107:snapStyles에hidden키가 이미 정의되어 있어요.
CurationSnapState의 모든 값('collapsed' | 'mid' | 'expanded' | 'hidden')에 대응하는 키가snapStyles객체에 완전히 정의되어 있으므로 런타임 에러는 발생하지 않습니다.
37-66: 드래그 핸들러에서'hidden'상태를 처리하지 않아요.
CurationSnapState타입에'hidden'상태가 정의되어 있고 실제로GeneratedImgA.tsx에서 설정되지만,handleDragUp과handleDragDown에서 이 상태에 대한 분기를 누락했어요.snapState가'hidden'일 때 드래그해도 아무 반응이 없는데, 이 동작이 의도적인지 확인하고 필요하면 명시적으로 처리하세요.src/pages/generate/pages/result/components/DetectionHotspots.tsx (5)
70-79:cachedDetectionprop 추가가 적절해요.선택적 prop으로 캐시된 detection을 전달받아 초기화에 활용하는 구조가 좋아요.
101-105:useDetectionCache훅 사용이 적절해요.
imageId,imageUrl,initialEntry옵션을 전달해 캐시를 관리하고,prefetchedDetections와saveEntry를 반환받아 활용하는 패턴이 깔끔해요.
144-151:hotspotOptions메모이제이션이 올바르게 적용됐어요.의존성 배열에
prefetchedDetections와handleInferenceComplete를 포함해 불필요한 훅 재실행을 방지해요.
135-142:prefetchedDetections동기화와imageUrl변경 시 리셋 로직이 적절해요.두 개의 분리된
useEffect로 각 책임을 명확히 했어요.imageUrl변경 시lastDetectionsRef를 초기화해 stale 데이터 사용을 방지해요.
229-236:useEffect의존성 배열에saveEntry추가 시 안정성 확인 완료
saveEntry가useDetectionCache에서useCallback으로 메모이제이션되어 있어 의존성 배열에 포함하는 것이 올바른 조치예요. 다만saveEntry의 의존성(imageId,imageUrl)이 변경되면 함께 재생성되므로, 불필요한 재렌더링을 방지하려면 호출부의imageId,imageUrl변경 빈도도 고려해서 최적화하면 좋습니다.src/pages/mypage/hooks/useMypage.ts (1)
14-21: 마이페이지 유저 쿼리의 staleTime/포커스 리패치 전략 확인 필요
useMyPageUser에 15분staleTime과refetchOnWindowFocus: false가 추가되면서, 동일 세션 내에서 사용자 정보가 비교적 오래 캐시되고 포커스만으로는 자동 갱신되지 않게 됩니다.
- 프로필 수정/로그아웃 등 “사용자 정보가 즉시 반영돼야 하는” 플로우에서 별도
invalidateQueries나 수동refetch가 확실히 걸려 있는지 한 번 확인해 주세요.- 현재
options타입이{ enabled?: boolean }로 제한돼 있어, 호출 측에서 staleTime 등을 조정할 수 없는데, 의도한 제약이라면 괜찮지만 추후 확장성을 고려하면 별도 옵션 타입(예:MyPageUserOptions)으로 분리해두는 것도 선택지입니다.
| 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]); | ||
| } |
There was a problem hiding this comment.
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.
| const handleInferenceComplete = useCallback( | ||
| (result: ProcessedDetections, latestHotspots: FurnitureHotspot[]) => { | ||
| lastDetectionsRef.current = result; | ||
| saveEntry({ | ||
| processedDetections: result, | ||
| hotspots: latestHotspots, | ||
| }); | ||
| }, | ||
| [saveEntry] | ||
| ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| const now = Date.now(); | ||
| if (now - toastCooldownRef.current < TOAST_COOLDOWN_MS) { | ||
| return; // 연속 클릭 시 스낵바 중복 방지 | ||
| } | ||
| toastCooldownRef.current = now; | ||
| // 스낵바 중복 노출 방지 가드 |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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.
| 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.
| useEffect(() => { | ||
| if (isLoaded && !localLoaded) { | ||
| setLocalLoaded(true); | ||
| } | ||
| }, [isLoaded, localLoaded]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
상태 동기화 로직에 잠재적 문제가 있어요.
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.
| <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" | ||
| /> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
접근성 개선이 필요해요.
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.
src/pages/mypage/hooks/useMypage.ts
Outdated
| 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, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
cat -n src/pages/mypage/hooks/useMypage.ts | head -70Repository: 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
useMyPageImageDetail → useMyPageImageDetailQuery로 함수명 변경하고, ImageDetailOptions 타입을 UseQueryOptions로 정렬해주세요
함수명이 컨벤션을 따르지 않습니다. 코딩 가이드라인에서 "TanStack Query 훅은 use + 행위 + 대상 + Query/Mutation 형식"을 명시하고 있으므로, Query 접미사를 추가해야 합니다. 동시에 ImageDetailOptions 타입도 UseQueryOptions의 Pick으로 정의하면 라이브러리 타입 변경에 견고해집니다:
-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.
| // 세션 감지 캐시 초기화 | ||
| useDetectionCacheStore.getState().clear(); |
There was a problem hiding this comment.
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)의 상태를 직접 변경하고 있어요. 이는 스토어 간 암묵적 의존성을 만들어 유지보수성을 떨어뜨릴 수 있어요.
대안으로 아래 방식을 고려해 주세요:
clearUser를 호출하는 컴포넌트/훅에서 두 스토어를 함께 초기화- 별도의
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.
There was a problem hiding this 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
📒 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.tssrc/pages/generate/pages/result/components/GeneratedImgA.tsxsrc/pages/generate/pages/result/components/GeneratedImg.css.tssrc/pages/generate/stores/useCurationCacheStore.tssrc/pages/generate/pages/result/components/GeneratedImgB.tsxsrc/pages/generate/pages/result/components/DetectionHotspots.tsxsrc/pages/generate/pages/result/curationSheet/CurationSheet.tsxsrc/pages/mypage/hooks/useDetectionPrefetch.tssrc/pages/generate/hooks/useFurnitureCuration.tssrc/pages/generate/pages/result/ResultPage.tsxsrc/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.tssrc/pages/generate/pages/result/components/GeneratedImg.css.tssrc/pages/generate/stores/useCurationCacheStore.tssrc/pages/mypage/hooks/useDetectionPrefetch.tssrc/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.tssrc/pages/generate/pages/result/components/GeneratedImgA.tsxsrc/pages/generate/pages/result/components/GeneratedImg.css.tssrc/pages/generate/stores/useCurationCacheStore.tssrc/pages/generate/pages/result/components/GeneratedImgB.tsxsrc/pages/generate/pages/result/components/DetectionHotspots.tsxsrc/pages/generate/pages/result/curationSheet/CurationSheet.tsxsrc/pages/mypage/hooks/useDetectionPrefetch.tssrc/pages/generate/hooks/useFurnitureCuration.tssrc/pages/generate/pages/result/ResultPage.tsxsrc/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.tsxsrc/pages/generate/pages/result/components/GeneratedImgB.tsxsrc/pages/generate/pages/result/components/DetectionHotspots.tsxsrc/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.tsxsrc/pages/generate/pages/result/components/GeneratedImgB.tsxsrc/pages/generate/pages/result/components/DetectionHotspots.tsxsrc/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.tsxsrc/pages/generate/pages/result/components/GeneratedImg.css.tssrc/pages/generate/pages/result/components/GeneratedImgB.tsxsrc/pages/generate/pages/result/components/DetectionHotspots.tsxsrc/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.tssrc/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.tssrc/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.tsxsrc/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!
detectionCache와groupIdprops가 적절한 타입과 함께 추가되었어요. 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!
hotspots와detectedObjects에useMemo를 적용해 불필요한 재계산을 방지했어요. 의존성 배열도 적절해요.
156-196: 병렬 프리패치 로직 잘 구현됨카테고리별 프리패치를 병렬로 처리하고,
prefetchedRef와groupProductCache체크로 중복 요청을 방지하는 로직이 잘 구현되었어요.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으로 감싸고 의존성 배열에primaryImageId와scheduleDetectionPrefetch가 포함되어 있어요. 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:useGeneratedCategoriesQuery에groupId파라미터가 추가되었어요.
groupId를 첫 번째 파라미터로 전달하여 그룹 기반 캐싱을 지원해요. 관련 코드 스니펫(useFurnitureCuration.ts lines 40-146)과 일치하는 시그니처예요.
149-156:hotspotOptions메모이제이션이 올바르게 적용되어 있어요.
useMemo로 옵션 객체를 메모이제이션하여useFurnitureHotspots훅의 불필요한 재실행을 방지해요. 의존성 배열에prefetchedDetections와handleInferenceComplete가 포함되어 있어요.
140-147:prefetchedDetections동기화와imageUrl변경 시 리셋 로직이 적절해요.두 useEffect가 각각 다른 역할을 수행해요: 하나는 프리페치된 데이터를 ref에 동기화하고, 다른 하나는 이미지 URL 변경 시 ref를 리셋해요. 이미지가 바뀔 때 이전 detection 데이터가 남지 않도록 하는 방어 로직이에요.
src/pages/generate/pages/result/ResultPage.tsx (4)
43-53:toGenerateImageData헬퍼 함수가 잘 정의되어 있어요.
MyPageImageHistory를GenerateImageData로 변환하는 로직이 명확해요. 다만 Line 146-154에서 동일한 변환 로직이 인라인으로 중복되어 있으니 이 헬퍼를 재사용하면 좋겠어요.
94-102:forwardedDetectionMap생성 로직이 적절해요.
useMemo로 메모이제이션되어 있고,forwardedDetection과initialImageId가 모두 존재할 때만 맵을 생성해요. 의존성 배열이 올바르게 설정되어 있어요.
115-122: 데이터 페칭 조건 플래그가 명확하게 분리되어 있어요.
hasValidHouseId,shouldFetchExternalResult,shouldFetchMypageDetail플래그로 각 시나리오별 데이터 소스를 명확하게 구분하고 있어요.detailPlaceholder로 마이페이지 경로에서 placeholder 데이터도 지원해요.
377-394:GeneratedImgA와GeneratedImgB에 새 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; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
!groupId 체크는 groupId === 0을 무효로 처리해요.
groupId가 number 타입이므로 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.
| type PrefetchTask = { | ||
| imageId: number; | ||
| imageUrl: string; | ||
| }; |
There was a problem hiding this comment.
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.
| 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).
| try { | ||
| targetImage = await loadImageElement(imageUrl); | ||
| } catch { | ||
| targetImage = await loadCorsImage(imageUrl); | ||
| } |
There was a problem hiding this comment.
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.
| 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.
빌드 결과빌드 실패 ❌ |
There was a problem hiding this 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형식을 따라야 해요.useMyPageUser→useMyPageUserQuery로 변경해 주세요.추가로,
...options가staleTime/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에서 제거되었어요
useQuery의onSuccess콜백은 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
📒 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.tssrc/pages/mypage/hooks/useMypage.tssrc/pages/generate/hooks/useFurnitureCuration.tssrc/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.tssrc/pages/mypage/hooks/useMypage.tssrc/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.tssrc/pages/mypage/hooks/useMypage.tssrc/pages/generate/hooks/useFurnitureCuration.tssrc/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.tssrc/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.tssrc/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을 활용해queryKey와queryFn을 제외한 것도 적절해요.src/pages/generate/pages/result/ResultPage.tsx (5)
44-54: LGTM!
toGenerateImageData헬퍼 함수가MyPageImageHistory를GenerateImageData로 명확하게 변환해요. 필드 매핑이 올바르게 구현되어 있어요.
116-123: LGTM!
hasValidHouseId,shouldFetchExternalResult,shouldFetchMypageDetail플래그를 사용한 세분화된 API 호출 제어가 좋아요. 불필요한 API 호출을 방지하고 데이터 흐름을 명확하게 해요.
133-144: LGTM!
useMyPageImageDetail훅 사용과placeholderData를 통한 초기 데이터 제공이 적절해요.isSlideCountLoading상태 계산도 올바르게 구현되어 있어요.
168-179: LGTM!
historyByIdMap을useMemo로 메모이제이션하여 O(1) 조회를 가능하게 한 점이 좋아요. 의존성 배열도 올바르게 설정되어 있어요.
386-402: LGTM!
GeneratedImgA와GeneratedImgB에 새로운 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훅이 그룹 컨텍스트를 지원하도록 확장되었어요.clearGroupCategories와clearGroupProduct를 활용한 캐시 무효화 로직이 적절해요.
| 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, | ||
| }); | ||
| }; |
There was a problem hiding this comment.
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.
| 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.
빌드 결과빌드 성공 🎊 |
There was a problem hiding this 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후에generateImage및MYPAGE_IMAGES쿼리를 모두 invalidation 해서, 메인/폴백 경로 간 상태 일관성이 잘 맞춰진 점은 좋습니다.다만 현재는 다음 두 가지가 약간 아쉽습니다.
동일 invalidation 로직 중복
useGenerateImageApi의onSuccess와 여기 폴백 성공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();
'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
📒 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.tssrc/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.tssrc/pages/generate/hooks/useFurnitureCuration.tssrc/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.tssrc/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.tssrc/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.tssrc/pages/generate/hooks/useFurnitureCuration.tssrc/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해 주어서, 마이페이지 이미지 목록이 생성 결과와 더 잘 동기화될 것으로 보입니다. 이 변경 자체는 괜찮아 보입니다.다만 다음 두 가지는 한 번만 점검해 주세요.
실제 MyPage 이미지 리스트 쿼리의 queryKey 형태 확인
- 해당 쿼리가
queryKey: [QUERY_KEY.MYPAGE_IMAGES]또는[QUERY_KEY.MYPAGE_IMAGES, ...]형태로 정의되어 있는지 확인해 주세요.- 만약 전혀 다른 문자열/배열로 정의되어 있다면, 이번 invalidation이 기대한 쿼리를 건드리지 못할 수 있습니다.
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 로직을 보완할 때도 도움이 될 것 같습니다.
| 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 } | ||
| : {}), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
그룹 캐시가 있어도 항상 재요청을 보내는 구조가 의도인지 한 번 확인이 필요합니다
현재 구현을 보면:
-
카테고리 쿼리
canUseGroupInitialData는initialData여부만 결정하고,enabled는Boolean(imageId) && normalizedDetectedObjects.length > 0만 보고 있습니다.
→ 즉, 그룹 캐시(detectionSignature까지 일치)가 있어도 네트워크 요청은 항상 한 번 더 나갑니다.
-
상품 쿼리
productCacheEntry도 마찬가지로initialData에만 쓰이고,enabled는Boolean(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.
빌드 결과빌드 실패 ❌ |
There was a problem hiding this 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.tsx는GeneratedImagesSection,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에서는...options가staleTime/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:useMyPageImageDetail→useMyPageImageDetailQuery로 함수명 변경 필요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
📒 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.tsxsrc/pages/mypage/hooks/useDetectionPrefetch.tssrc/pages/generate/pages/result/components/GeneratedImgA.tsxsrc/pages/mypage/components/section/generatedImages/GeneratedImagesSection.tsxsrc/pages/mypage/components/history/HistorySection.tsxsrc/pages/generate/pages/result/curationSheet/CurationSheet.tsxsrc/pages/mypage/hooks/useMypage.tssrc/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.tsxsrc/pages/mypage/hooks/useDetectionPrefetch.tssrc/pages/generate/pages/result/components/GeneratedImgA.tsxsrc/pages/mypage/components/section/generatedImages/GeneratedImagesSection.tsxsrc/pages/mypage/components/history/HistorySection.tsxsrc/pages/generate/pages/result/curationSheet/CurationSheet.tsxsrc/pages/mypage/hooks/useMypage.tssrc/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.tssrc/pages/mypage/hooks/useMypage.tssrc/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.tssrc/pages/mypage/hooks/useMypage.tssrc/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.tsxsrc/pages/mypage/components/section/generatedImages/GeneratedImagesSection.tsxsrc/pages/mypage/components/history/HistorySection.tsx
src/pages/**/components/**
⚙️ CodeRabbit configuration file
src/pages/**/components/**: 페이지별 컴포넌트 리뷰 시:
- 해당 페이지의 도메인 로직과 긴밀한 결합 확인
- 재사용 가능한 컴포넌트는 shared로 이동 제안
- 페이지 특화 타입과 상수의 적절한 활용 확인
Files:
src/pages/generate/pages/result/components/GeneratedImgA.tsxsrc/pages/mypage/components/section/generatedImages/GeneratedImagesSection.tsxsrc/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.tsxsrc/pages/mypage/components/section/generatedImages/GeneratedImagesSection.tsxsrc/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.tssrc/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을 사용해queryKey와queryFn을 제외한 것도 적절해요.
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:CurationSheetPropsinterface 정의 LGTM
groupId?: number | null타입 정의가 명확하고, 기본값null을 컴포넌트에서 설정한 것이 적절해요.
61-68:useMemo로 참조 안정성 확보 LGTM
hotspots와detectedObjects를useMemo로 감싸서 불필요한 리렌더링을 방지한 것이 좋아요. 의존성 배열도 적절해요.
137-145: 시트 자동 접힘 조건 개선
snapState !== 'hidden'조건을 추가하여 이미 숨겨진 상태에서 불필요하게collapsed로 변경되는 것을 방지한 것이 좋아요.
338-346: 스켈레톤 칩 접근성 개선
aria-hidden속성을 사용하여 스크린 리더에서 스켈레톤을 숨긴 것이 적절해요. 단일long너비 스켈레톤으로 단순화한 것도 좋아요.
92-94:groupId진위값 체크 패턴 확인
groupId !== null체크를 사용하고 있어서groupId가0인 경우에도 올바르게 처리돼요. 이전 리뷰에서 지적된 부분이 반영된 것으로 보여요.
156-200: 프리패치 로직 개선 LGTM그룹 캐시(
groupProductCache)를 활용하여 이미 캐시된 카테고리는 프리패치를 건너뛰는 최적화가 잘 되어 있어요.prefetchedRef로 중복 프리패치도 방지하고 있어요.한 가지 개선 제안:
dedupeKey생성 시groupId ?? activeImageId패턴을 사용하고 있는데,groupId가0인 경우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 훅들과 페이지별 유틸리티, 타입들이 잘 정리되어 있어요.
typeimport를 사용해 런타임 의존성을 줄인 점이 좋아요.
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헬퍼 함수가 명확하게 정의되어 있어요.
MyPageImageHistory를GenerateImageData로 변환하는 로직이 간결해요.name과tagName모두history.tasteTag를 사용하는데, 의도된 것인지 확인이 필요할 수 있어요.
75-98: location state 처리와 detection map 생성이 적절해요.
forwardedDetectionMap을useMemo로 안정적으로 생성하고, null 체크가 정확해요. 의존성 배열도 올바르게 설정되어 있어요.
110-119: 조건부 플래그들이 명확하게 정의되어 있어요.
hasValidHouseId,hasInitialResult,shouldFetchExternalResult,shouldFetchMypageDetail플래그들이 데이터 페칭 조건을 명확하게 표현하고 있어요.
142-180:resolvedResultuseMemo가 과거 리뷰 피드백을 반영해 잘 구현되어 있어요.데이터 소스 우선순위(mypageHistories → forwardedResult → initialHistory → apiResult)가 명확하고, 의존성 배열도 정확해요. 렌더링 안정성이 개선되었어요.
183-194: O(1) 조회를 위한 Map 생성이 적절해요.
historyByIdMap을 통해currentLikeState와currentFactorId계산 시 반복적인 배열 탐색을 피할 수 있어요. 조건부 생성으로 불필요한 메모리 할당도 방지하고 있네요.
401-417: 그룹 기반 캐싱 props가 자식 컴포넌트에 올바르게 전달되고 있어요.
userProfile,detectionCache,isSlideCountLoading,groupId등 새로운 props가GeneratedImgA와GeneratedImgB에 적절하게 전달되고 있어요.?? undefined로 null을 undefined로 변환하는 것도 올바른 처리예요.
517-517:CurationSheet에groupIdprop 전달이 적절해요.그룹 기반 캐싱을 위한
groupIdprop이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함수를 사용해 그룹/이미지 기반 쿼리를 정밀하게 무효화하고 있어요.
| const rawDetectedObjects = mapHotspotsToDetectedObjects( | ||
| pipeline.hotspots | ||
| ); | ||
| const detectedObjects = filterAllowedDetectedObjects(rawDetectedObjects, { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
cd src/pages/mypage && find . -name "*.ts" -o -name "*.tsx" | head -20Repository: TEAM-HOUME/HOUME-CLIENT
Length of output: 984
🏁 Script executed:
# mapHotspotsToDetectedObjects 함수 정의 찾기
rg "mapHotspotsToDetectedObjects" --type ts -A 20Repository: TEAM-HOUME/HOUME-CLIENT
Length of output: 9275
🏁 Script executed:
# filterAllowedDetectedObjects 함수 정의 찾기
rg "filterAllowedDetectedObjects" --type ts -B 2 -A 10Repository: TEAM-HOUME/HOUME-CLIENT
Length of output: 6817
🏁 Script executed:
# useDetectionPrefetch.ts 전체 내용 확인
wc -l src/pages/mypage/hooks/useDetectionPrefetch.tsRepository: TEAM-HOUME/HOUME-CLIENT
Length of output: 117
🏁 Script executed:
# mapHotspotsToDetectedObjects의 전체 구현 확인
sed -n '1,100p' src/pages/generate/utils/detectedObjectMapper.tsRepository: TEAM-HOUME/HOUME-CLIENT
Length of output: 2239
🏁 Script executed:
# filterAllowedDetectedObjects의 구현 확인
sed -n '40,80p' src/pages/generate/utils/detectedObjectMapper.tsRepository: 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.
빌드 결과빌드 실패 ❌ |
There was a problem hiding this 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 우선순위 불일치
useMyPageImagesQuery와useMyPageImageDetail은...options가 마지막에 위치해 호출자가 기본값을 덮어쓸 수 있지만,useMyPageUser는...options가staleTime/refetchOnWindowFocus앞에 위치해 덮어쓸 수 없어요.의도적인 차이라면 주석으로 이유를 남기고, 아니라면 통일해 주세요:
return useQuery<QueryResult<ReturnType<typeof getMyPageUser>>>({ queryKey: [QUERY_KEY.MYPAGE_USER], queryFn: getMyPageUser, - ...options, staleTime: 15 * 60 * 1000, refetchOnWindowFocus: false, + ...options, });
67-77:useMyPageImageDetail→useMyPageImageDetailQuery로 함수명 변경 필요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에서
categoryId가number | null | undefined타입인데clearGroupProduct는number타입을 기대해요.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을 사용하면null과undefined모두 체크할 수 있어요. 또는 명시적으로categoryId !== null && categoryId !== undefined로 작성해도 돼요.
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
📒 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.tsxsrc/pages/mypage/components/section/generatedImages/GeneratedImagesSection.tsxsrc/pages/generate/pages/result/components/GeneratedImgA.tsxsrc/pages/mypage/hooks/useDetectionPrefetch.tssrc/pages/generate/pages/result/ResultPage.tsxsrc/pages/generate/hooks/useFurnitureCuration.tssrc/pages/mypage/hooks/useMypage.tssrc/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.tsxsrc/pages/mypage/components/section/generatedImages/GeneratedImagesSection.tsxsrc/pages/generate/pages/result/components/GeneratedImgA.tsxsrc/pages/mypage/hooks/useDetectionPrefetch.tssrc/pages/generate/pages/result/ResultPage.tsxsrc/pages/generate/hooks/useFurnitureCuration.tssrc/pages/mypage/hooks/useMypage.tssrc/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.tsxsrc/pages/generate/pages/result/components/GeneratedImgA.tsxsrc/pages/mypage/components/history/HistorySection.tsx
src/pages/**/components/**
⚙️ CodeRabbit configuration file
src/pages/**/components/**: 페이지별 컴포넌트 리뷰 시:
- 해당 페이지의 도메인 로직과 긴밀한 결합 확인
- 재사용 가능한 컴포넌트는 shared로 이동 제안
- 페이지 특화 타입과 상수의 적절한 활용 확인
Files:
src/pages/mypage/components/section/generatedImages/GeneratedImagesSection.tsxsrc/pages/generate/pages/result/components/GeneratedImgA.tsxsrc/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.tssrc/pages/generate/hooks/useFurnitureCuration.tssrc/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.tssrc/pages/generate/hooks/useFurnitureCuration.tssrc/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.tsxsrc/pages/generate/pages/result/ResultPage.tsxsrc/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.tssrc/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이미지 더보기 슬라이드에서 시트 상태를 숨기고 복원하는 로직이 잘 구현되어 있어요.
prevSnapStateRef와isSheetHiddenByImageMoreRef를 활용한 상태 추적이 적절해요.
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
hotspots와detectedObjects를useMemo로 메모이제이션하여 참조 안정성을 확보한 것이 좋아요.
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
groupIdprop이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네이밍 및 캐시 설정 - LGTMTanStack Query 훅 네이밍 컨벤션(
use + 행위 + 대상 + Query)을 따르고, 캐시 설정(staleTime: 15분,gcTime: 30분)이 적절해요.src/pages/mypage/components/section/generatedImages/GeneratedImagesSection.tsx (6)
1-2:useCallback을 import하지만useRef와useState도 함께 사용하고 있어요.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,onImageLoadprops가 추가되어 이미지별 로드 상태 추적이 가능해졌어요.src/pages/generate/pages/result/ResultPage.tsx (5)
86-108: location.state 타입 확장과 detection 캐시 맵 생성이 적절해요.
useMemo를 사용해forwardedDetectionMap을 안정적으로 계산하고 있고, null 체크도 잘 되어 있어요.
153-191: 이전 리뷰에서 지적된result재할당 문제가useMemo로 잘 해결되었어요.
resolvedResult를useMemo로 계산하여 렌더 사이클 간 참조 안정성을 확보했어요. 우선순위도 명확해요:
- mypage 히스토리 → 2. forwarded result → 3. initialHistory → 4. API result
194-205:historyByIdMap을 통한 O(1) 조회 최적화가 적절해요.
useMemo로 Map을 생성하여currentLikeState와currentFactorId계산 시 효율적인 조회가 가능해요.
423-440: GeneratedImgA/B에 새로운 props가 잘 전달되고 있어요.
userProfile,detectionCache,isSlideCountLoading,groupIdprops가 추가되어 결과 페이지의 데이터 흐름이 개선되었어요.
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까지 일치해야 초기 데이터를 사용하는 조건이 적절해요. 다만 이전 리뷰에서 언급된 것처럼 "그룹 캐시가 있어도 네트워크 요청이 나가는 것"이 의도된 동작인지 확인이 필요해요.
| /** | ||
| * 마이페이지 히스토리 데이터를 결과 페이지 이미지 포맷으로 변환 | ||
| * @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, | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
# MyPageImageHistory 타입 정의 찾기
rg "type MyPageImageHistory|interface MyPageImageHistory" -A 10Repository: TEAM-HOUME/HOUME-CLIENT
Length of output: 767
🏁 Script executed:
# GenerateImageData 타입 정의 찾기
rg "type GenerateImageData|interface GenerateImageData" -A 10Repository: TEAM-HOUME/HOUME-CLIENT
Length of output: 693
🏁 Script executed:
# toGenerateImageData 함수 사용 패턴 찾기
rg "toGenerateImageData" -B 2 -A 2Repository: TEAM-HOUME/HOUME-CLIENT
Length of output: 838
🏁 Script executed:
# name 필드 사용 패턴 확인
rg "\.name" src/pages/generate --type=ts -B 1 -A 1 | head -40Repository: TEAM-HOUME/HOUME-CLIENT
Length of output: 1654
🏁 Script executed:
# tagName 필드 사용 패턴 확인
rg "\.tagName|tagName:" src/pages/generate --type=ts -B 1 -A 1 | head -40Repository: TEAM-HOUME/HOUME-CLIENT
Length of output: 715
🏁 Script executed:
# 전체 MyPageImageHistory 타입 정의 확인
rg "interface MyPageImageHistory" -A 20Repository: TEAM-HOUME/HOUME-CLIENT
Length of output: 1358
🏁 Script executed:
# 실제로 history.name이 어디서 사용되는지 확인
rg "history\.name" src/pages/generate --type=ts -B 3 -A 3Repository: TEAM-HOUME/HOUME-CLIENT
Length of output: 581
🏁 Script executed:
# MyPageImageDetail 타입 정의 확인 (언급된 경우)
rg "interface MyPageImageDetail|type MyPageImageDetail" -A 15Repository: TEAM-HOUME/HOUME-CLIENT
Length of output: 1098
toGenerateImageData 함수의 타입 불일치 문제 수정 필요
함수 파라미터는 MyPageImageHistory 타입으로 지정되어 있지만, 이 타입에는 name 필드가 없습니다. 반면 name 필드는 MyPageImageDetail 타입에만 존재합니다. 현재 코드는 history.tasteTag를 name에 할당하고 있는데, 이는 실제 데이터 구조와 맞지 않습니다.
파라미터 타입을 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.
| 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, | ||
| }); | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# 파일 위치 및 구조 확인
find . -type f -name "*.ts*" | grep -E "(GeneratedImagesSection|scheduleDetectionPrefetch)" | head -20Repository: TEAM-HOUME/HOUME-CLIENT
Length of output: 231
🏁 Script executed:
#!/bin/bash
# scheduleDetectionPrefetch 함수 정의 찾기
rg -n "scheduleDetectionPrefetch|const scheduleDetectionPrefetch|function scheduleDetectionPrefetch" -A 5 -B 1Repository: TEAM-HOUME/HOUME-CLIENT
Length of output: 2956
🏁 Script executed:
#!/bin/bash
# navigate 후 prefetch 호출 패턴 찾기
rg -n "navigate.*\)" -A 3 | grep -E "(prefetch|scheduleDetection)" -B 3Repository: 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 -100Repository: 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 -80Repository: TEAM-HOUME/HOUME-CLIENT
Length of output: 1021
🏁 Script executed:
#!/bin/bash
# useDetectionPrefetch.ts 전체 파일 읽기
wc -l src/pages/mypage/hooks/useDetectionPrefetch.tsRepository: TEAM-HOUME/HOUME-CLIENT
Length of output: 117
🏁 Script executed:
#!/bin/bash
# useDetectionPrefetch.ts 핵심 부분 확인 (executePrefetch와 scheduleBackgroundPrefetch)
cat -n src/pages/mypage/hooks/useDetectionPrefetch.tsRepository: 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_modulesRepository: TEAM-HOUME/HOUME-CLIENT
Length of output: 145
🏁 Script executed:
#!/bin/bash
# generate result 페이지 찾기
fd -t f "result" src/pages --type f | head -20Repository: TEAM-HOUME/HOUME-CLIENT
Length of output: 206
🏁 Script executed:
#!/bin/bash
# ROUTES.GENERATE_RESULT 경로 확인
rg -n "GENERATE_RESULT" -A 2 -B 2 | head -50Repository: TEAM-HOUME/HOUME-CLIENT
Length of output: 2352
🏁 Script executed:
#!/bin/bash
# ResultPage.tsx 파일 확인
head -150 src/pages/generate/pages/result/ResultPage.tsxRepository: TEAM-HOUME/HOUME-CLIENT
Length of output: 5561
🏁 Script executed:
#!/bin/bash
# ResultPage.tsx 전체 라인 수 확인
wc -l src/pages/generate/pages/result/ResultPage.tsxRepository: 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.tsxRepository: 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.tsxRepository: TEAM-HOUME/HOUME-CLIENT
Length of output: 540
🏁 Script executed:
#!/bin/bash
# 마이페이지에서 buildResultNavigationState로 전달하는 데이터 확인
cat -n src/pages/mypage/utils/resultNavigation.tsRepository: TEAM-HOUME/HOUME-CLIENT
Length of output: 2002
🏁 Script executed:
#!/bin/bash
# primeDetectionCacheEntry 함수 확인
rg -n "primeDetectionCacheEntry" -A 10 -B 2Repository: 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 모델 추론을 수행하는 비동기 작업이에요. 페이지 전환 중에 컴포넌트가 언마운트되면서 작업이 중단되거나 미완료된 상태에서 메모리 누수가 발생할 수 있어요.
더 안정적인 방법은:
buildResultNavigationState에서 현재 캐시된 감지 데이터를 이미 navigation state에 포함하고 있으므로, 결과 페이지 마운트 시점에 필요한 데이터를 프리페치하기- 또는 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.
빌드 결과빌드 성공 🎊 |
There was a problem hiding this 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.queryKey를as [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
📒 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같은 셀렉터 훅들은 간결하고 명확해요.useSheetSnapState는useMemo로 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, andProductsQueryKeyare internal implementation details of the hook file used only withinuseFurnitureCuration.tsto 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.
| 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, | ||
| }, | ||
| ]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
queryKey에 detectionSignature와 codes가 중복돼요.
detectionSignature는 normalizedDetectedObjects(즉, 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.
| 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 } | ||
| : {}), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
queryKey에 queryFn에서 사용하지 않는 groupId가 포함되어 불필요한 캐시 분할이 발생해요.
현재 queryKey에 groupId를 포함하고 있지만, 실제 queryFn(lines 137-138)은 imageId와 normalizedDetectedObjects만 사용해요. 이로 인해 동일한 이미지와 감지 객체를 가진 요청이 서로 다른 그룹에서 올 경우 별도의 캐시 엔트리를 생성하게 되어, 동일한 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.
| 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 } | ||
| : {}), | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
상품 쿼리에서도 groupId가 queryKey에 불필요하게 포함되어 있어요.
카테고리 쿼리와 동일한 패턴의 문제예요. queryFn(line 241)은 imageId와 categoryId만 사용하지만 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.
빌드 결과빌드 성공 🎊 |
There was a problem hiding this 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:PrefetchTask는interface로 바꾸는 게 가이드라인에 맞아요.
현재 객체 형태 타입을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
📒 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)
| // 세마포어 슬롯 확보 | ||
| 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] | ||
| ); | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
세마포어 주석이 구현과 불일치합니다(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.
| // 백그라운드 큐를 순차로 소모해 모델 호출 폭주 방지 | ||
| 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]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
|
@codex review this PR |
There was a problem hiding this 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".
| if (!imageId || !imageUrl) return; | ||
| if (pendingRef.current.has(imageId)) return; | ||
| const cached = useDetectionCacheStore.getState().images[imageId]; | ||
| if (cached) return; | ||
| if (isLoading || error) return; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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 👍 / 👎.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
-
Potential false-negative on
imageIdguard (!imageId)- In
executePrefetch, the guardif (!imageId || !imageUrl) return;will treat0as invalid. IfimageIdcan be0, this will skip valid work. Consider a nullish check instead (e.g.,imageId == null) to preserve0as valid. src/pages/mypage/hooks/useDetectionPrefetch.tsL149-L156
- In
-
Duplicate filtering/logging in
processAndStoremapHotspotsToDetectedObjectsalready callsfilterAllowedDetectedObjectsand logs the filtered codes before returning the allowed set. InprocessAndStore, you callfilterAllowedDetectedObjectsagain on that result, which may double-filter and double-log. You can likely rely onmapHotspotsToDetectedObjectsdirectly and remove the extra filtering stage to avoid redundant work/logging. src/pages/mypage/hooks/useDetectionPrefetch.tsL124-L140 src/pages/generate/utils/detectedObjectMapper.tsL13-L77
-
console.warnusage in error paths- The inference error path uses
console.warnfor both inference failures and unexpected errors. The rest of the furniture pipeline uses structured logging vialogFurniturePipelineEventindetectedObjectMapper.ts; consider using that here as well for consistency and production-grade observability. src/pages/mypage/hooks/useDetectionPrefetch.tsL171-L186 src/pages/generate/utils/detectedObjectMapper.tsL7-L77 - Related: the
loadImageElementfailure path silently falls back toloadCorsImagewithout logging. That might be okay, but a structured warning would make CORS/mixed-origin failures easier to trace. src/pages/mypage/hooks/useDetectionPrefetch.tsL159-L165
- The inference error path uses
-
Type alias vs interface for object shape
PrefetchTaskis an object-shapedtype. Your codebase guidance prefersinterfacefor object shapes. Consider switching it tointerface PrefetchTaskto align with that convention. src/pages/mypage/hooks/useDetectionPrefetch.tsL45-L53
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).
📌 Summary
close [fix] 가구 매핑 수정 및 스낵바 동작 개선 #393
가구 매핑 정확도와 큐레이션 바텀시트(bottom sheet) 안정성을 함께 개선한 변경입니다.
Obj365 라벨→도메인 코드 매핑을 재정비하여 오탐/과매핑을 줄였습니다.
감지, 카테고리, 상품 조회를 이미지/그룹 단위 캐시로 안정화해 중복 호출, 캐시 오염, UX 깜빡임을 줄였습니다.
바텀시트의 상태 전이(특히 닫힘/숨김)를 명확히 해 선택 상태 해제 타이밍과 더보기 슬라이드에서의 동작을 개선했습니다.
또한 마이페이지→결과 페이지 이동 시 감지 프리패치를 하고 네비게이션 state 전달로 첫 렌더가 빨라지도록 보강했습니다.
📄 Tasks
cache store를 추가했습니다.
🔍 To Reviewer
📸 Screenshot
작업한 내용에 대한 스크린샷을 첨부해주세요.