Skip to content

[Refactor] - 통합 테스트 및 UI 개선#54

Merged
tmdcks1103 merged 34 commits into
mainfrom
50-refactor/web-app-intergration
May 28, 2026
Merged

[Refactor] - 통합 테스트 및 UI 개선#54
tmdcks1103 merged 34 commits into
mainfrom
50-refactor/web-app-intergration

Conversation

@tmdcks1103

@tmdcks1103 tmdcks1103 commented May 27, 2026

Copy link
Copy Markdown
Collaborator

✨ 주요 변경사항

  • 견적서 진단 · 가견적서 생성 · 분석 이력 페이지의 목 데이터를 제거하고 실제 API에 연동
  • 인증 스토어 초기화 타이밍 문제 해결
  • 기능 없는 UI 요소 정리
  • 공통 로딩 화면 컴포넌트 추출 및 UX 전면 개선

📝 작업 상세 내용

인증

  • AuthStoreInitializer: useEffect → useLayoutEffect 교체로 auth 스토어 초기화가 렌더 전에 완료되도록 수정 (깜빡임 방지)
  • (main)/layout.tsx: 서버에서 받은 유저 정보를 클라이언트 인증 스토어에 동기화

API

  • client.ts: beforeRetry 훅에서 401 이외의 에러는 즉시 throw하도록 수정 (불필요한 토큰 갱신 요청 방지)
  • analyze/api.ts: analyzeRisk에 userId 파라미터 추가 및 x-user-id 헤더 전송

견적서 진단

  • mapApiReport.ts: API 응답에서 status가 불분명이어도 title에 누락이 포함된 항목은 누락으로 재분류하는 inferItemStatus 로직 추가. 카운트 기준을 summary.chips(백엔드 집계) → 실제 매핑된 items 기준으로 변경해 정확도 향상
  • analysisStore.ts: API 오류 응답의 body.detail이 있으면 해당 메시지를 에러로 노출
  • 에러 화면에 다시 시도 버튼 추가
  • 결과 화면에서 기능 없는 PDF 다운로드 버튼 제거

가견적서 생성

  • Step4(공종 상세 입력) 제거 리팩토링 — 불필요한 단계 삭제로 플로우 단순화
  • 결과 화면에서 참조 데이터 없는 공정 컬럼 숨김 및 기능 없는 버튼 정리

분석 이력

  • 이력 목록 페이지 목 데이터 제거 및 실제 API 연동
  • 이력 테이블 업체 컬럼 제거 및 리스크 표시 세분화
  • 이력 상세 화면에서 DiagnosisDetailView로 진단 결과 표시

  • 최근 분석 이력을 최신순 10개로 제한
  • 기능 없는 요약 카드 화살표, 테이블 컬럼 정렬 화살표 제거

✅ 체크리스트

  • PR 본문에 Close #번호 추가
  • 불필요한 주석, 디버깅 코드 제거
  • 기능 테스트 및 정상 동작 확인
  • 커밋컨벤션 / 코드컨벤션 준수

📸 스크린샷 (선택)


🔍 기타 참고사항

  • 로딩 화면 팁 카드 개선 의도
    서비스 유입 사용자는 인테리어 시공 지식이 부족한 경우가 많습니다. 분석 대기 시간(최대 1~2분)을 단순 대기가 아닌 유용한 정보를 전달하는 시간으로 활용하기 위해 팁 카드를 주요 UI 요소로 격상했습니다.

  • mapApiReport 카운트 로직 변경
    기존에는 백엔드 summary.chips 값을 그대로 사용했으나, 실제 items를 순회해 집계하는 방식으로 변경했습니다. 이에 따라 analysisStore.test.ts의 목 데이터도 실제 items에 리스크 항목을 포함하도록 수정했습니다.


🔗 관련 이슈

Summary by CodeRabbit

릴리즈 노트

  • 새로운 기능

    • 통합 로딩 화면 UI 추가 및 애니메이션 개선
    • 견적 4단계 프로세스로 단순화
    • 공정 세부 설정 옵션 추가
    • 견적 검토 단계에 저장 기능 추가
  • 개선 사항

    • 진단 결과를 인사이트 형식으로 표시
    • 히스토리 테이블 재구성 (순번 추가, 위험도 표시 개선)
    • API 오류 메시지 정확도 향상
    • 참고 사례 데이터 조건부 표시
  • UI/UX 개선

    • 참조 데이터 없을 경우 테이블 레이아웃 최적화
    • 사용자 인증 컨텍스트 개선

Review Change Stack

tmdcks1103 added 20 commits May 26, 2026 11:44
mapApiReportToDiagnosisResult를 analysis/_lib/mapApiReport.ts로 추출하여
history/[id] 페이지에서 재사용 가능하게 함.
기존 인라인 style 숫자 3개 표시 → DiagnosisDetailView 컴포넌트로 교체.
… 초기화 타이밍 수정

useEffect는 브라우저 페인트 이후에 실행되어 첫 렌더 시 user가 null로 유지되는 문제가 있었음.
useLayoutEffect로 교체하여 페인트 전에 서버 유저를 auth 스토어에 동기화, 견적서 진단 '분석 시작' 버튼 비활성화 버그 수정.
@vercel

vercel Bot commented May 27, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
muneo-client-admin Ready Ready Preview, Comment May 28, 2026 5:49am
muneo-client-web Ready Ready Preview, Comment May 28, 2026 5:49am

@github-actions github-actions Bot changed the title 50 refactor/web app intergration [Refactor] - 통합 테스트 및 UI 개선 May 27, 2026
@github-actions github-actions Bot added the ♻️Refactor 코드 리팩토링 label May 27, 2026
@coderabbitai

coderabbitai Bot commented May 27, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@tmdcks1103, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 37 minutes and 8 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 42082951-806f-462a-8e31-f2497f92545f

📥 Commits

Reviewing files that changed from the base of the PR and between 81822e6 and 294bd70.

📒 Files selected for processing (1)
  • apps/web/src/app/(main)/_components/LoadingScreen/_hooks/useRotatingIndex.ts
📝 Walkthrough

Walkthrough

로딩 화면 중복 제거를 통해 코드 응집성을 높이고, 견적 플로우를 5→4단계로 단순화하며, History를 서버 렌더링으로 전환하고, API에 userId를 추가하며, UserProvider로 인증 아키텍처를 개선합니다. 불필요한 UI(PDF 버튼, 화살표)를 제거하고 인사이트 기반 리포팅으로 변경합니다.


Changes

LoadingScreen 컴포넌트 통합

Layer / File(s) Summary
공용 LoadingScreen 컴포넌트 및 스타일 작성
apps/web/src/app/(main)/_components/LoadingScreen/LoadingScreen.css.ts, LoadingScreen.tsx, _components/*.tsx, _hooks/*.ts, index.ts
Vanilla Extract 스타일(keyframes, 애니메이션), 4개 커스텀 훅(useRotatingIndex, useProgressSteps, useDelayedFlag, 미명시), 6개 하위 컴포넌트(Mascot, WorkingDots, RotatingMessage, ProgressBar, TipCard, LoadingFooter)를 정의하여 메시지 순환, 팁 카드, 프로그레스 바, 경고 표시, 취소 버튼을 한 곳에서 관리합니다. Props 기반으로 완전히 제어 가능합니다.
AnalysisLoadingScreen 마이그레이션
apps/web/src/app/(main)/analysis/_components/AnalysisLoadingScreen/AnalysisLoadingScreen.tsx
로컬 상태 관리 로직 제거, PROGRESS_STEPS 배열 추가, LoadingScreen 호출로 단순화. useEffect/useState 의존 제거.
EstimateLoadingScreen 마이그레이션
apps/web/src/app/(main)/estimate/_components/EstimateLoadingScreen/EstimateLoadingScreen.tsx
AnalysisLoadingScreen과 동일한 방식으로 리팩토링. Props 시그니처(region, processCount, onCancel) 유지, 내부 구현 단순화.

Estimate 견적 생성 플로우 단순화 (5단계→4단계)

Layer / File(s) Summary
Estimate 스텝 타입 및 상수 재정의
apps/web/src/app/(main)/estimate/_types/meta.ts, _store/estimateStore.ts
ESTIMATE_STEPS [1,2,3,4,5] → [1,2,3,4], STEP_LABELS Step4 "공정별 세부" → "견적 확인", MAX_STEP 5 → 4.
Step2ProcessSelection에 세부 설정 아코디언 통합
apps/web/src/app/(main)/estimate/_components/Step2ProcessSelection/Step2ProcessSelection.tsx, Step2ProcessSelection.css.ts, ProcessCheckbox/ProcessCheckbox.tsx, ProcessCheckbox.css.ts
Step 2에 세부 설정 섹션 컴포넌트(철거/도배/바닥/욕실/주방) 추가, SECTION_MAP으로 매핑, 아코디언 UI, fadeSlideDown 애니메이션, ProcessCheckbox hasDetail prop 추가, 검증 에러를 boolean → string으로 변경.
EstimatePage 단계 재배치 및 Step4ProcessDetail 제거
apps/web/src/app/(main)/estimate/page.tsx
STEP_COMPONENTS Step4: Step4ProcessDetail → EstimateResult, 로딩/에러 조건 currentStep === 5 → 4, 취소 버튼 goToStep(4) → (3), Step4ProcessDetail 파일 전체 삭제(CSS 219줄 포함).
StepIndicator, EstimateResult, 테스트 업데이트
apps/web/src/app/(main)/estimate/_components/StepIndicator/StepIndicator.tsx, EstimateResultView/EstimateResultView.tsx, Step5EstimateReview/Step5EstimateReview.tsx, _store/estimateStore.test.ts
StepIndicator wide 스타일 제거, EstimateResult 참조 데이터 조건부 렌더링(hasRefData), 총 배너 조건부 표시, 스타일 중복 제거(accordionTableHeaderBase, accordionRowContentBase), 테스트 범위 4 기준 갱신.
EstimateResult 저장 플로우 및 인증 개선
apps/web/src/app/(main)/estimate/_components/Step5EstimateReview/Step5EstimateReview.tsx
"PDF 다운로드" 버튼 → "저장하기" 버튼, useAuthStore → useUser() 전환, handleSave user 없음 시 saveError 설정, onEdit goToStep(4) → (3).

History 페이지 및 컴포넌트 구조 개선

Layer / File(s) Summary
History 타입 정의 및 모의 데이터 제거
apps/web/src/app/(main)/history/_types/history.types.ts, _mocks/history.mock.ts, _mocks/history-detail.mock.ts, _lib/historyFilter.test.ts
RiskStatus { type/label } → { missing/unclear: number }, HistoryRow id: number → string, vendor 필드 제거, 모의 데이터 파일 전부 삭제, 테스트 데이터 id 타입 변경.
HistoryPage 서버 렌더링 구현 및 데이터 변환
apps/web/src/app/(main)/history/page.tsx
async HistoryPage, getServerMe()/getServerEstimates()/getServerRiskDetections() 병렬 호출, buildHistoryRows로 HistoryRow[] 변환(risk는 mapApiReportToDiagnosisResult 결과 기반), createdAt 내림차순 정렬, rows props로 전달.
HistoryContent 및 HistoryTable 컴포넌트 업데이트
apps/web/src/app/(main)/history/_components/HistoryContent/HistoryContent.tsx, HistoryTable/HistoryTable.tsx, HistoryTable.css.ts
HistoryContent MOCK 제거, props rows 추가, HistoryTable onRowClick id: string, 순번 컬럼 추가(idx+1), RiskCell 카운트 기반 렌더링(둘 다 0이면 "—", 아니면 배지), colgroup/COLUMNS 재배치, vendor 셀 제거, 열 너비 조정.
History 상세 페이지 및 DiagnosisDetailView 통합
apps/web/src/app/(main)/history/[id]/page.tsx, _components/DiagnosisDetailView/DiagnosisDetailView.tsx
mapApiReportToDiagnosisResult import 추가, risk 렌더링을 DiagnosisDetailView 컴포넌트로 통합, PDF 버튼 제거.

API 인증 강화 및 UserProvider 도입

Layer / File(s) Summary
analyzeRisk API에 userId 헤더 추가 및 에러 처리 개선
apps/web/src/api/analyze/api.ts, _store/analysisStore.ts
analyzeRisk(formData, userId, signal?) 시그니처, 요청 헤더 x-user-id 추가, submitAnalysis userId 전달, API 에러 err.body.detail 우선 사용.
API 클라이언트 재시도 로직 강화
apps/web/src/api/client.ts
beforeRetry에서 error.status 체크, 401 아닌 경우 즉시 throw하여 토큰 갱신 회피.
mapApiReportToDiagnosisResult 로직 외부 모듈화
apps/web/src/app/(main)/analysis/_lib/mapApiReport.ts, _store/analysisStore.ts, _store/analysisStore.test.ts
RiskReport → DiagnosisResult 변환을 외부 모듈로 분리, inferItemStatus 추가(누락 상태 보정), ID 생성(si_ii), 날짜 정규화(YYYY.MM.DD), 카운트 집계, MOCK_REPORT items 다양화(4개 항목).
UserProvider 및 useUser 훅 도입
apps/web/src/app/(main)/_components/UserProvider/UserProvider.tsx, layout.tsx
UserProvider/useUser 훅 신규 추가, MainLayout에서 초기 user 전달, useAuthStore 의존 → useUser() 전환 (UploadPanel, EstimateResult, EstimateReview).

UI 요소 정리 및 스타일 정규화

Layer / File(s) Summary
DiagnosisReport 인사이트 UI 및 buildInsights 함수
apps/web/src/app/(main)/analysis/_components/DiagnosisReport/DiagnosisReport.tsx, DiagnosisReport.css.ts, _lib/buildInsights.ts
PDF 출력 버튼 제거, "진단 요약" → "종합 인사이트" UI 변경, buildInsights(missingCount, riskCount, insufficientCount) 함수로 3개 문장 생성(전체 요약, 최우선 액션, 계약 조언), summaryBox 관련 스타일 제거, insightBox 관련 스타일 신규 정의.
SummaryCard 화살표 아이콘 및 스타일 제거
apps/web/src/app/(main)/home/_components/SummaryCard/SummaryCard.tsx, SummaryCard.css.ts
ArrowIcon import/렌더링 제거, 카운트 표시 구조 변경, padding 28px 14px → 28px 28px 28px 14px, arrow/right 스타일 제거.
Home 및 History HistoryTable 헤더 UI 단순화
apps/web/src/app/(main)/home/_components/HistoryTable/HistoryTable.tsx, HistoryTable.css.ts, home/page.tsx
thInner 스타일 제거, 헤더 렌더링 아이콘 제거(컬럼명만), Home 이력 최대 10개 제한(slice(0,10)).
AnalysisPage 에러 재시도 UI 및 스타일
apps/web/src/app/(main)/analysis/page.tsx, page.css.ts
reset 액션 구독, 에러 렌더링 시 "다시 시도" 버튼 추가, loadingState flexDirection/gap 추가, retryButton 스타일 신규 정의.

Sequence Diagram(s)

sequenceDiagram
    participant App as Estimate Flow
    participant Store as estimateStore
    participant API as analyzeRisk API
    participant View as EstimateResult
    
    App->>Store: submitAnalysis(formData)
    Store->>API: analyzeRisk(formData, userId, signal)
    API->>API: 헤더: x-user-id
    alt API 성공
        API-->>Store: RiskDetectAnalyzeResponse
        Store->>Store: mapApiReportToDiagnosisResult
        Store->>View: result (DiagnosisResult)
        View->>View: buildInsights(counts)
        View-->>App: 종합 인사이트 렌더링
    else API 오류 (401 외)
        API-->>Store: 즉시 error throw
        Store->>Store: err.body.detail 우선
        Store-->>App: errorMessage
    end
Loading
sequenceDiagram
    participant Browser
    participant Server as getServerMe/getServerEstimates
    participant Client as HistoryPage (async)
    participant Component as HistoryContent
    
    Browser->>Client: /history 접근
    Client->>Server: getServerMe()
    Server-->>Client: user
    Client->>Server: Promise.all([getServerEstimates, getServerRiskDetections])
    Server-->>Client: estimates[], risks[]
    Client->>Client: buildHistoryRows(estimates, risks)
    Client->>Client: 정렬 (createdAt DESC)
    Client->>Component: rows={HistoryRow[]}
    Component-->>Browser: HistoryContent 렌더링
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes


Suggested labels

✅Test


Poem

다섯 단계를 넷으로 접고 🎯
로딩 화면 하나로 모았네 🎨
서버로 날린 히스토리 ☁️
userId 헤더에 담아 보내고 🔐
인사이트로 리포트 깔끔하게 📊

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch 50-refactor/web-app-intergration

@github-actions

Copy link
Copy Markdown

🔍 CI 검증 결과

검증 항목 web admin design-system
Type ✅ 통과 ✅ 통과 ✅ 통과
Prettier ✅ 통과 ✅ 통과 ✅ 통과
Lint ✅ 통과 ✅ 통과 ✅ 통과
tests ✅ 통과 ✅ 통과 ✅ 통과
Build ✅ 통과 ✅ 통과 ➖ 해당 없음

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 8

Caution

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

⚠️ Outside diff range comments (1)
apps/web/src/app/(main)/estimate/page.tsx (1)

35-58: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

취소 후에도 비동기 결과가 store에 반영되는 경쟁 상태를 막아주세요.

Line 79에서 취소로 이전 단계 이동해도, 이미 시작된 fetchEstimate가 완료되면 setEstimateResult/setGeneratingState가 실행됩니다. 취소 이후 stale 결과가 저장되어 다음 진입 시 재생성 로직이 건너뛰어질 수 있습니다.

수정 예시
   useEffect(() => {
+    let cancelled = false;
     if (currentStep !== 4 || estimateResult !== null || isGenerating || generateError !== null) {
       return;
     }
@@
     const fetchEstimate = async () => {
       setGeneratingState(true, null);
       try {
@@
-        setEstimateResult(result);
-        setGeneratingState(false, null);
+        if (cancelled) return;
+        setEstimateResult(result);
+        setGeneratingState(false, null);
       } catch {
-        setGeneratingState(false, '가견적 생성에 실패했습니다. 다시 시도해 주세요.');
+        if (cancelled) return;
+        setGeneratingState(false, '가견적 생성에 실패했습니다. 다시 시도해 주세요.');
       }
     };
 
     fetchEstimate();
+    return () => {
+      cancelled = true;
+    };
   }, [

Also applies to: 79-82, 100-103

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/app/`(main)/estimate/page.tsx around lines 35 - 58, The async
fetchEstimate can complete after the user navigates away and then write stale
results into the store (via setEstimateResult / setGeneratingState); prevent
this by tracking cancellation for that specific invocation and skipping state
updates when cancelled: inside the useEffect that declares fetchEstimate create
a local cancellation token (e.g., an AbortController or a boolean isCancelled
flag unique to this effect), pass it into the async flow (generateEstimate
promise) and, before calling setEstimateResult or setGeneratingState in both
success and catch branches, check the cancellation token and return early if
cancelled; also ensure you set the token to cancelled in the cleanup function of
the same useEffect so any in-flight fetchEstimate won’t mutate state after the
user changes currentStep.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/web/src/api/client.ts`:
- Around line 59-62: 현재 beforeRetry 핸들러에서 status가 undefined인 경우에도 refresh 경로로
빠지므로 네트워크/타임아웃 오류에 대해 refresh를 막아야 합니다; apps/web/src/api/client.ts의 beforeRetry
블록에서 현재의 if (status !== undefined && status !== 401) { throw error; } 를 변경해 상태
코드가 401일 때만 refresh를 허용하도록 바꾸세요 (예: if (status === undefined || status !== 401)
throw error; 또는 더 간단히 if (status !== 401) throw error;), 이렇게 하면 status가
undefined(네트워크/타임아웃)일 때 triggerRefresh()가 호출되지 않습니다.

In `@apps/web/src/app/`(main)/_components/LoadingScreen/LoadingScreen.css.ts:
- Around line 127-178: progressWrap and tipCard use fixed pixel widths causing
horizontal overflow on narrow viewports; change them to be responsive by
replacing the fixed width with width: '100%' and add maxWidth: '320px' for
progressWrap and maxWidth: '480px' for tipCard (and set boxSizing: 'border-box'
if not already) so they shrink to their parent on small screens while keeping
the intended max size on larger screens; update the style objects named
progressWrap and tipCard accordingly.

In `@apps/web/src/app/`(main)/_components/LoadingScreen/LoadingScreen.tsx:
- Around line 82-149: The loading UI is only visual—add accessibility semantics:
make the message region (where messages[msgIdx] or warningMessage render) an
ARIA live region (e.g., aria-live="polite" and aria-atomic="true") so screen
readers announce changes and keep using showWarning, warningMessage, msgIdx,
messages and msgExiting as the content source; mark the progress bar element
(styles.progressFill container or its parent progressWrap) with
role="progressbar" and provide aria-valuenow={progress.value}, aria-valuemin="0"
and aria-valuemax="100" (using progress.value from the progress object) and keep
the visual transition; hide purely decorative elements (the mascot SVG,
mascotShadow, and each working dot/ DOT_DELAYS mapping) from assistive tech via
aria-hidden="true" or role="presentation".

In `@apps/web/src/app/`(main)/analysis/_lib/mapApiReport.ts:
- Around line 4-5: The inferItemStatus function currently only reclassifies
items as '누락' when item.status === '불분명' and item.title.includes('누락'); update
inferItemStatus to also treat items with status '불분명' and an empty or
whitespace-only title as '누락' (e.g., trim the title and check for zero length)
so that ApiProcessItem entries with blank titles are counted/displayed as
missing.

In `@apps/web/src/app/`(main)/analysis/_store/analysisStore.test.ts:
- Around line 35-40: The test adds items with statuses including '누락', '불분명',
and '중복' but only asserts missingCount; update the same test to assert the other
derived counts as well: verify missingCount increments for status '누락',
riskCount (or whatever mapping is used) increments for '불분명', and
insufficientCount increments for '중복' using the same items array in the test;
locate the assertions around missingCount, riskCount, and insufficientCount in
the test file and add expectations for all three to prevent regression when the
status->count mapping changes.

In `@apps/web/src/app/`(main)/analysis/_store/analysisStore.ts:
- Around line 115-117: The code treats an empty string from (err as { body?: {
detail?: string } }).body?.detail as a valid message, causing a blank UI; update
the logic that builds apiDetail/errorMessage (referencing apiDetail,
errorMessage, and the set call) to treat empty or whitespace-only strings as
missing by trimming and checking length (>0) before using it, and fall back to
the default '분석 중 오류가 발생했습니다.' when the trimmed detail is empty or undefined.

In
`@apps/web/src/app/`(main)/estimate/_components/Step2ProcessSelection/Step2ProcessSelection.tsx:
- Around line 93-107: When wallpaperScope === '부분 도배' ensure the user cannot
proceed with an empty partialRooms: add a validation in the
Step2ProcessSelection component that checks data.wallpaperScope and
(data.partialRooms ?? []).length, show a validation error and/or disable the
next/submit action until at least one room is selected (update the UI around the
SelectButton group handling partialRooms); additionally, update mapToApiPayload
to either omit partialRooms or throw/return a validation error when
wallpaperScope is '부분 도배' but partialRooms is empty so invalid payloads are
never sent. Apply the same fix to the other identical block referenced (lines
~259-266).

In `@apps/web/src/app/`(main)/history/page.tsx:
- Around line 35-38: Replace direct access to summary.chips when building the
risk counts with the same aggregation used in the detail page: call or reuse
mapApiReportToDiagnosisResult on the report (e.g.,
mapApiReportToDiagnosisResult(item.result.report)) and pull the missing/unclear
counts from that mapped result instead of item.result.report.summary.chips
(update the risk object construction where missing/unclear are set).

---

Outside diff comments:
In `@apps/web/src/app/`(main)/estimate/page.tsx:
- Around line 35-58: The async fetchEstimate can complete after the user
navigates away and then write stale results into the store (via
setEstimateResult / setGeneratingState); prevent this by tracking cancellation
for that specific invocation and skipping state updates when cancelled: inside
the useEffect that declares fetchEstimate create a local cancellation token
(e.g., an AbortController or a boolean isCancelled flag unique to this effect),
pass it into the async flow (generateEstimate promise) and, before calling
setEstimateResult or setGeneratingState in both success and catch branches,
check the cancellation token and return early if cancelled; also ensure you set
the token to cancelled in the cleanup function of the same useEffect so any
in-flight fetchEstimate won’t mutate state after the user changes currentStep.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: f22652ab-114f-46ad-881e-90054325af7f

📥 Commits

Reviewing files that changed from the base of the PR and between a3253da and 3e472ef.

📒 Files selected for processing (46)
  • apps/web/src/api/analyze/api.ts
  • apps/web/src/api/client.ts
  • apps/web/src/app/(main)/_components/AuthStoreInitializer/AuthStoreInitializer.tsx
  • apps/web/src/app/(main)/_components/LoadingScreen/LoadingScreen.css.ts
  • apps/web/src/app/(main)/_components/LoadingScreen/LoadingScreen.tsx
  • apps/web/src/app/(main)/_components/LoadingScreen/index.ts
  • apps/web/src/app/(main)/analysis/_components/AnalysisLoadingScreen/AnalysisLoadingScreen.css.ts
  • apps/web/src/app/(main)/analysis/_components/AnalysisLoadingScreen/AnalysisLoadingScreen.tsx
  • apps/web/src/app/(main)/analysis/_components/DiagnosisReport/DiagnosisReport.tsx
  • apps/web/src/app/(main)/analysis/_lib/mapApiReport.ts
  • apps/web/src/app/(main)/analysis/_store/analysisStore.test.ts
  • apps/web/src/app/(main)/analysis/_store/analysisStore.ts
  • apps/web/src/app/(main)/analysis/page.css.ts
  • apps/web/src/app/(main)/analysis/page.tsx
  • apps/web/src/app/(main)/estimate/_components/EstimateLoadingScreen/EstimateLoadingScreen.css.ts
  • apps/web/src/app/(main)/estimate/_components/EstimateLoadingScreen/EstimateLoadingScreen.tsx
  • apps/web/src/app/(main)/estimate/_components/EstimateResultView/EstimateResultView.tsx
  • apps/web/src/app/(main)/estimate/_components/ProcessCheckbox/ProcessCheckbox.css.ts
  • apps/web/src/app/(main)/estimate/_components/ProcessCheckbox/ProcessCheckbox.tsx
  • apps/web/src/app/(main)/estimate/_components/Step2ProcessSelection/Step2ProcessSelection.css.ts
  • apps/web/src/app/(main)/estimate/_components/Step2ProcessSelection/Step2ProcessSelection.tsx
  • apps/web/src/app/(main)/estimate/_components/Step4ProcessDetail/Step4ProcessDetail.css.ts
  • apps/web/src/app/(main)/estimate/_components/Step4ProcessDetail/Step4ProcessDetail.tsx
  • apps/web/src/app/(main)/estimate/_components/Step5EstimateReview/Step5EstimateReview.css.ts
  • apps/web/src/app/(main)/estimate/_components/Step5EstimateReview/Step5EstimateReview.tsx
  • apps/web/src/app/(main)/estimate/_components/StepIndicator/StepIndicator.tsx
  • apps/web/src/app/(main)/estimate/_store/estimateStore.test.ts
  • apps/web/src/app/(main)/estimate/_store/estimateStore.ts
  • apps/web/src/app/(main)/estimate/_types/meta.ts
  • apps/web/src/app/(main)/estimate/page.tsx
  • apps/web/src/app/(main)/history/[id]/page.tsx
  • apps/web/src/app/(main)/history/_components/DiagnosisDetailView/DiagnosisDetailView.tsx
  • apps/web/src/app/(main)/history/_components/HistoryContent/HistoryContent.tsx
  • apps/web/src/app/(main)/history/_components/HistoryTable/HistoryTable.css.ts
  • apps/web/src/app/(main)/history/_components/HistoryTable/HistoryTable.tsx
  • apps/web/src/app/(main)/history/_lib/historyFilter.test.ts
  • apps/web/src/app/(main)/history/_mocks/history-detail.mock.ts
  • apps/web/src/app/(main)/history/_mocks/history.mock.ts
  • apps/web/src/app/(main)/history/_types/history.types.ts
  • apps/web/src/app/(main)/history/page.tsx
  • apps/web/src/app/(main)/home/_components/HistoryTable/HistoryTable.css.ts
  • apps/web/src/app/(main)/home/_components/HistoryTable/HistoryTable.tsx
  • apps/web/src/app/(main)/home/_components/SummaryCard/SummaryCard.css.ts
  • apps/web/src/app/(main)/home/_components/SummaryCard/SummaryCard.tsx
  • apps/web/src/app/(main)/home/page.tsx
  • apps/web/src/app/(main)/layout.tsx
💤 Files with no reviewable changes (9)
  • apps/web/src/app/(main)/history/_components/DiagnosisDetailView/DiagnosisDetailView.tsx
  • apps/web/src/app/(main)/history/_mocks/history-detail.mock.ts
  • apps/web/src/app/(main)/analysis/_components/DiagnosisReport/DiagnosisReport.tsx
  • apps/web/src/app/(main)/estimate/_components/EstimateLoadingScreen/EstimateLoadingScreen.css.ts
  • apps/web/src/app/(main)/analysis/_components/AnalysisLoadingScreen/AnalysisLoadingScreen.css.ts
  • apps/web/src/app/(main)/estimate/_components/Step4ProcessDetail/Step4ProcessDetail.tsx
  • apps/web/src/app/(main)/history/_mocks/history.mock.ts
  • apps/web/src/app/(main)/estimate/_components/Step4ProcessDetail/Step4ProcessDetail.css.ts
  • apps/web/src/app/(main)/home/_components/HistoryTable/HistoryTable.css.ts

Comment thread apps/web/src/api/client.ts
Comment thread apps/web/src/app/(main)/_components/LoadingScreen/LoadingScreen.tsx Outdated
Comment thread apps/web/src/app/(main)/analysis/_lib/mapApiReport.ts Outdated
Comment on lines +35 to +40
items: [
{ title: '일반 철거', description: '180만원', guide: '', status: '정상' as const },
{ title: '철거 폐기물 처리비', description: '누락된 항목', guide: '', status: '누락' as const },
{ title: '기타 비용', description: '불분명 항목', guide: '', status: '불분명' as const },
{ title: '인건비', description: '중복 항목', guide: '', status: '중복' as const },
],

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

확장한 상태 케이스에 대한 단정문을 같이 추가해 주세요.

목 데이터는 불분명/중복까지 늘렸는데, 핵심 검증은 아직 missingCount 위주라 매핑 회귀를 놓칠 수 있습니다. riskCount, insufficientCount도 동일 테스트에서 확인하는 게 좋습니다.

추가 단정 예시
       expect(state.diagnosisResult?.vendorLabel).toBe('A업체');
       expect(state.diagnosisResult?.missingCount).toBe(1);
+      expect(state.diagnosisResult?.riskCount).toBe(1);
+      expect(state.diagnosisResult?.insufficientCount).toBe(1);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/app/`(main)/analysis/_store/analysisStore.test.ts around lines
35 - 40, The test adds items with statuses including '누락', '불분명', and '중복' but
only asserts missingCount; update the same test to assert the other derived
counts as well: verify missingCount increments for status '누락', riskCount (or
whatever mapping is used) increments for '불분명', and insufficientCount increments
for '중복' using the same items array in the test; locate the assertions around
missingCount, riskCount, and insufficientCount in the test file and add
expectations for all three to prevent regression when the status->count mapping
changes.

Comment on lines +115 to +117
const apiDetail = (err as { body?: { detail?: string } }).body?.detail;
const errorMessage = apiDetail ?? '분석 중 오류가 발생했습니다.';
set({ error: errorMessage, loading: false });

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

detail이 빈 문자열일 때 에러 문구가 비어 보일 수 있습니다.

현재 apiDetail ?? ...''를 유효값으로 처리해서, 서버가 빈 detail을 보내면 UI에 빈 오류 문구가 표시됩니다. 공백/빈 문자열은 기본 메시지로 폴백하는 쪽이 안전합니다.

수정 예시
-      const apiDetail = (err as { body?: { detail?: string } }).body?.detail;
-      const errorMessage = apiDetail ?? '분석 중 오류가 발생했습니다.';
+      const apiDetail = (err as { body?: { detail?: string } }).body?.detail;
+      const errorMessage =
+        typeof apiDetail === 'string' && apiDetail.trim().length > 0
+          ? apiDetail
+          : '분석 중 오류가 발생했습니다.';
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/app/`(main)/analysis/_store/analysisStore.ts around lines 115 -
117, The code treats an empty string from (err as { body?: { detail?: string }
}).body?.detail as a valid message, causing a blank UI; update the logic that
builds apiDetail/errorMessage (referencing apiDetail, errorMessage, and the set
call) to treat empty or whitespace-only strings as missing by trimming and
checking length (>0) before using it, and fall back to the default '분석 중 오류가
발생했습니다.' when the trimmed detail is empty or undefined.

Comment thread apps/web/src/app/(main)/history/page.tsx Outdated

@KyeongJooni KyeongJooni left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

수고하셨습니다!! 코멘트 확인해주세요 :)

}, [user, setUser]);

return null;
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

현재 사용자 인증을 서버에서 직접 하고 있는데 zustand로 인증을 관리하는 이유가 있나요?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

UploadPanel, Step5EstimateReview에서 API 호출 시 user.id가 필요한데,
서버 전용 함수를 직접 호출할 수 없어서 AuthStoreInitializer가 user를 Zustand에 주입해주는 구조입니다.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

인증한정으로 user.id를 사용하는 거면 React Context (UserProvider) 로도 동일하게 server → client로 user 데이터를 흘려보낼 수 있고, 이쪽이 더 단순할 것 같습니다!!

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

아하 굳이 zudstand을 사용할 필요는 없었던 것 같네요! 반영하겠습니다🫡

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

불필요한 주석은 삭제해주세요!!

Comment on lines +86 to +122
<svg className={styles.mascotImg} viewBox="0 0 318 225" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M223.42 0C275.175 0 317.132 41.8155 317.132 93.3975C317.132 93.4476 317.131 93.4977 317.131 93.5479V179.104C316.852 186.528 317.902 197.168 310.961 207.93C304.019 218.692 291.293 222.535 279.723 206.008C277.366 202.641 271.45 200.284 267.768 206.008C258.127 220.998 240.388 221.766 229.59 206.008C226.864 202.031 219.692 201.636 216.863 206.008C207.413 220.613 190.254 221.766 178.684 206.008C175.985 201.012 169.732 200.628 166.344 206.008C158.118 219.076 145.519 220.614 136.649 209.083C132.065 202.396 130.395 197.783 129.708 187.276V93.3936H129.709C129.711 41.8134 171.666 9.71069e-05 223.42 0Z"
fill="#8455DF"
/>
<path
d="M223.813 28.9412C261.903 28.9412 292.729 57.9991 292.729 93.781C292.729 129.563 261.903 158.621 223.813 158.621C185.723 158.621 154.897 129.563 154.897 93.781C154.897 57.9991 185.723 28.9412 223.813 28.9412Z"
fill="white"
stroke="#8455DF"
/>
<rect
x="188.056"
y="95.0508"
width="16.7396"
height="30.5168"
rx="8.3698"
fill="#8455DF"
stroke="#8455DF"
/>
<rect
x="238.188"
y="95.0508"
width="16.7396"
height="30.5168"
rx="8.3698"
fill="#8455DF"
stroke="#8455DF"
/>
<path
d="M10.5085 127.034C12.4797 119.917 20.1856 114.827 24.049 111.845C36.6964 102.603 64.7902 82.015 72.2542 76.5606C79.7183 71.1061 84.3583 74.3151 85.8508 76.5606C86.9066 77.7862 90.494 82.8818 98.4228 94.1442L99.2493 95.3182C109.884 110.424 97.1264 112.066 85.8508 114.394C79.5814 115.689 80.3527 121.838 83.8235 127.219L114.457 172.128C116.959 176.179 120.7 186.762 109.662 194.097C99.2495 201.015 89.3928 193.328 85.1405 186.88C79.6263 178.518 67.1428 159.426 61.3218 149.953C54.201 138.366 52.6641 136.564 44.1022 141.44C33.304 147.59 35.6751 160.593 29.1858 161.522C22.6965 162.451 20.3332 159.889 14.3422 150.446C8.3513 141.002 8.61658 133.866 10.5085 127.034Z"
fill="#8455DF"
stroke="#8455DF"
/>
</svg>
</div>
<div className={styles.mascotShadow} />
</div>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

인라인 svg 말고 svg 파일을 만들어서 사용하는게 어떨까요?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

메시지 회전과 팁 회전이 동일 패턴 두 번 반복 되고 있는 것 같은데, useRotatingIndex(count, interval, exit) 커스텀 훅으로 추출하면 좋을 것 같아요!

LoadingScreen/
  LoadingScreen.tsx         ← 얇은 조립
  LoadingScreen.css.ts
  _components/
    Mascot.tsx              ← SVG 분리
    WorkingDots.tsx
    RotatingMessage.tsx     ← message + warning swap
    ProgressBar.tsx
    TipCard.tsx
    LoadingFooter.tsx
  _hooks/
    useRotatingIndex.ts     ← message/tip 공통 로직
    useProgressSteps.ts
    useDelayedFlag.ts       ← warning 노출

이런식으로 분리해서 로딩스크린 작성하면 좋을 것 같습니다! 😄

Comment on lines +45 to +79
useEffect(() => {
const timers = progressSteps.map(({ delay, value, duration, easing }) =>
setTimeout(() => setProgress({ value, duration, easing }), delay)
);
return () => timers.forEach(clearTimeout);
}, [progressSteps]);

useEffect(() => {
const id = setInterval(() => {
setMsgExiting(true);
const tid = setTimeout(() => {
setMsgIdx((i) => (i + 1) % messages.length);
setMsgExiting(false);
}, 270);
return () => clearTimeout(tid);
}, 4000);
return () => clearInterval(id);
}, [messages.length]);

useEffect(() => {
const id = setInterval(() => {
setTipExiting(true);
const tid = setTimeout(() => {
setTipIdx((i) => (i + 1) % tips.length);
setTipExiting(false);
}, 330);
return () => clearTimeout(tid);
}, 8000);
return () => clearInterval(id);
}, [tips.length]);

useEffect(() => {
const t = setTimeout(() => setShowWarning(true), warningDelayMs);
return () => clearTimeout(t);
}, [warningDelayMs]);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

SRP 원칙을 지켜서 UI 컴포넌트안에 있는 훅 핸들러들을 분리하는게 좋을 것 같습니다!!

@github-actions

Copy link
Copy Markdown

🔍 CI 검증 결과

검증 항목 web admin design-system
Type ✅ 통과 ✅ 통과 ✅ 통과
Prettier ✅ 통과 ✅ 통과 ✅ 통과
Lint ✅ 통과 ✅ 통과 ✅ 통과
tests ✅ 통과 ✅ 통과 ✅ 통과
Build ✅ 통과 ✅ 통과 ➖ 해당 없음

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
apps/web/src/api/client.ts (1)

59-61: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

beforeRetry에서 401 상태코드는 error.status가 아니라 HTTPError.response.status로 읽어야 합니다.

apps/web/src/api/client.tsbeforeRetry에서 const status = (error as { status?: number }).status;로 상태코드를 읽고 있어 401일 때도 statusundefined로 떨어져 곧바로 throw될 수 있습니다. ky의 HTTP 오류는 HTTPError로 전달되며 상태코드는 error.response.status에 있습니다.

수정 예시
-import ky, { type KyInstance } from 'ky';
+import ky, { HTTPError, type KyInstance } from 'ky';
@@
-          const status = (error as { status?: number }).status;
+          const status =
+            error instanceof HTTPError ? error.response.status : (error as { status?: number }).status;
           if (status !== 401) {
             throw error;
           }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/api/client.ts` around lines 59 - 61, In beforeRetry change how
the HTTP status is read: instead of reading (error as { status?: number
}).status, access the ky HTTPError response status (e.g. (error as
HTTPError).response.status or error.response?.status) so 401 is detected
correctly; update beforeRetry to import/recognize HTTPError (or use a safe
optional chain) and only rethrow when status !== 401, otherwise handle retry
logic as intended.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In `@apps/web/src/api/client.ts`:
- Around line 59-61: In beforeRetry change how the HTTP status is read: instead
of reading (error as { status?: number }).status, access the ky HTTPError
response status (e.g. (error as HTTPError).response.status or
error.response?.status) so 401 is detected correctly; update beforeRetry to
import/recognize HTTPError (or use a safe optional chain) and only rethrow when
status !== 401, otherwise handle retry logic as intended.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: f9233733-9320-4bfd-bd3b-8b62909043fb

📥 Commits

Reviewing files that changed from the base of the PR and between 3e472ef and 3da21c8.

📒 Files selected for processing (6)
  • apps/web/src/api/client.ts
  • apps/web/src/app/(main)/_components/LoadingScreen/LoadingScreen.css.ts
  • apps/web/src/app/(main)/_components/LoadingScreen/LoadingScreen.tsx
  • apps/web/src/app/(main)/analysis/_lib/mapApiReport.ts
  • apps/web/src/app/(main)/estimate/_components/Step2ProcessSelection/Step2ProcessSelection.tsx
  • apps/web/src/app/(main)/history/page.tsx

@github-actions

Copy link
Copy Markdown

🔍 CI 검증 결과

검증 항목 web admin design-system
Type ✅ 통과 ✅ 통과 ✅ 통과
Prettier ✅ 통과 ✅ 통과 ✅ 통과
Lint ✅ 통과 ✅ 통과 ✅ 통과
tests ✅ 통과 ✅ 통과 ✅ 통과
Build ✅ 통과 ✅ 통과 ➖ 해당 없음

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

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 (1)
apps/web/src/app/(main)/_components/LoadingScreen/LoadingScreen.tsx (1)

24-40: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

messages/tips 입력 방어 로직이 필요합니다.

Line 36, Line 38에서 배열을 그대로 전달하고 있어 빈 배열 입력 시 메시지/팁 렌더링이 깨질 수 있습니다(하위 컴포넌트 인덱싱/진행률 계산 영향). 공용 컴포넌트 진입점에서 기본값으로 정규화해 주세요.

수정 예시
 export const LoadingScreen = ({
   messages,
   tips,
   warningMessage,
   warningDelayMs,
   progressSteps,
   footerText,
   onCancel,
-}: Props) => (
-  <div className={styles.container}>
-    <Mascot />
-    <WorkingDots />
-    <RotatingMessage messages={messages} warningMessage={warningMessage} warningDelayMs={warningDelayMs} />
-    <ProgressBar steps={progressSteps} />
-    <TipCard tips={tips} />
-    <LoadingFooter footerText={footerText} onCancel={onCancel} />
-  </div>
-);
+}: Props) => {
+  const safeMessages = messages.length > 0 ? messages : ['처리 중입니다...'];
+  const safeTips = tips.length > 0 ? tips : ['잠시만 기다려 주세요.'];
+
+  return (
+    <div className={styles.container}>
+      <Mascot />
+      <WorkingDots />
+      <RotatingMessage messages={safeMessages} warningMessage={warningMessage} warningDelayMs={warningDelayMs} />
+      <ProgressBar steps={progressSteps} />
+      <TipCard tips={safeTips} />
+      <LoadingFooter footerText={footerText} onCancel={onCancel} />
+    </div>
+  );
+};
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/app/`(main)/_components/LoadingScreen/LoadingScreen.tsx around
lines 24 - 40, Normalize empty or undefined messages and tips at the
LoadingScreen entry: in the LoadingScreen component ensure the props messages
and tips are replaced with safe defaults (e.g., messages ?? [] or
messages?.length ? messages : [default] and likewise for tips) before passing
them to RotatingMessage and TipCard so downstream indexing/progress logic never
receives an empty/undefined array; update the LoadingScreen parameter handling
(Props destructuring) to compute safeMessages and safeTips and pass those to
RotatingMessage and TipCard respectively.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@apps/web/src/app/`(main)/_components/LoadingScreen/_components/ProgressBar.tsx:
- Line 18: The ProgressBar component currently hardcodes aria-label="분석 진행률",
which is incorrect for a shared component; update the ProgressBar (in
ProgressBar.tsx) to accept a prop (e.g., ariaLabel or label) with a sensible
default like "진행 상태" and replace the fixed aria-label with that prop so callers
(like the estimate flow) can pass a context-specific string; ensure the prop is
optional and typed in the component's props/interface and used where aria-label
is set.

In
`@apps/web/src/app/`(main)/_components/LoadingScreen/_hooks/useProgressSteps.ts:
- Line 32: The useEffect in useProgressSteps depends on the steps reference
(useEffect(..., [steps])) which assumes callers pass a stable array; add a short
comment in useProgressSteps stating the API expectation that callers (e.g.,
ProgressBar / LoadingScreen) must pass a stable/memoized steps array (such as
the PROGRESS_STEPS constant used by AnalysisLoadingScreen and
EstimateLoadingScreen) or wrap dynamic step arrays with useMemo to avoid timer
churn, and mention this expectation in the hook's docstring or adjacent comment
so future callers know to preserve reference stability.

In
`@apps/web/src/app/`(main)/_components/LoadingScreen/_hooks/useRotatingIndex.ts:
- Around line 29-39: The effect in useRotatingIndex creates setTimeout timers
(tid) inside the setInterval callback but never clears them because the return
inside the interval callback is ignored; fix by tracking timeout ids in a ref or
array (e.g., timeoutsRef) and clearing any pending timeout(s) when the effect
cleanup runs and before creating a new timeout; ensure you still
clearInterval(id) and clearTimeout for all stored tids (use clearTimeout on each
id) and keep using the existing symbols setInterval, setTimeout, id, tid,
exitDuration, interval, setExiting, setIndex, and count to locate and update the
logic.

---

Outside diff comments:
In `@apps/web/src/app/`(main)/_components/LoadingScreen/LoadingScreen.tsx:
- Around line 24-40: Normalize empty or undefined messages and tips at the
LoadingScreen entry: in the LoadingScreen component ensure the props messages
and tips are replaced with safe defaults (e.g., messages ?? [] or
messages?.length ? messages : [default] and likewise for tips) before passing
them to RotatingMessage and TipCard so downstream indexing/progress logic never
receives an empty/undefined array; update the LoadingScreen parameter handling
(Props destructuring) to compute safeMessages and safeTips and pass those to
RotatingMessage and TipCard respectively.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 518a3e02-0549-4126-8ac6-3d0ebcaef02f

📥 Commits

Reviewing files that changed from the base of the PR and between 3da21c8 and 81822e6.

📒 Files selected for processing (19)
  • apps/web/src/app/(main)/_components/LoadingScreen/LoadingScreen.css.ts
  • apps/web/src/app/(main)/_components/LoadingScreen/LoadingScreen.tsx
  • apps/web/src/app/(main)/_components/LoadingScreen/_components/LoadingFooter.tsx
  • apps/web/src/app/(main)/_components/LoadingScreen/_components/Mascot.tsx
  • apps/web/src/app/(main)/_components/LoadingScreen/_components/ProgressBar.tsx
  • apps/web/src/app/(main)/_components/LoadingScreen/_components/RotatingMessage.tsx
  • apps/web/src/app/(main)/_components/LoadingScreen/_components/TipCard.tsx
  • apps/web/src/app/(main)/_components/LoadingScreen/_components/WorkingDots.tsx
  • apps/web/src/app/(main)/_components/LoadingScreen/_hooks/useDelayedFlag.ts
  • apps/web/src/app/(main)/_components/LoadingScreen/_hooks/useProgressSteps.ts
  • apps/web/src/app/(main)/_components/LoadingScreen/_hooks/useRotatingIndex.ts
  • apps/web/src/app/(main)/_components/UserProvider/UserProvider.tsx
  • apps/web/src/app/(main)/analysis/_components/DiagnosisReport/DiagnosisReport.css.ts
  • apps/web/src/app/(main)/analysis/_components/DiagnosisReport/DiagnosisReport.tsx
  • apps/web/src/app/(main)/analysis/_components/UploadPanel/UploadPanel.tsx
  • apps/web/src/app/(main)/analysis/_lib/buildInsights.ts
  • apps/web/src/app/(main)/estimate/_components/Step5EstimateReview/Step5EstimateReview.tsx
  • apps/web/src/app/(main)/layout.tsx
  • apps/web/src/store/authStore.ts
💤 Files with no reviewable changes (1)
  • apps/web/src/store/authStore.ts

aria-valuenow={value}
aria-valuemin={0}
aria-valuemax={100}
aria-label="분석 진행률"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

공용 컴포넌트인데 aria-label이 분석 전용 문구로 고정되어 있습니다.

현재 라벨은 견적 플로우에서도 “분석 진행률”로 읽혀 문맥이 어긋납니다. 공용 문구(예: 진행 상태)로 바꾸거나 prop으로 주입해 주세요.

수정 예시
-      aria-label="분석 진행률"
+      aria-label="진행 상태"
📝 Committable suggestion

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

Suggested change
aria-label="분석 진행률"
aria-label="진행 상태"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@apps/web/src/app/`(main)/_components/LoadingScreen/_components/ProgressBar.tsx
at line 18, The ProgressBar component currently hardcodes aria-label="분석 진행률",
which is incorrect for a shared component; update the ProgressBar (in
ProgressBar.tsx) to accept a prop (e.g., ariaLabel or label) with a sensible
default like "진행 상태" and replace the fixed aria-label with that prop so callers
(like the estimate flow) can pass a context-specific string; ensure the prop is
optional and typed in the component's props/interface and used where aria-label
is set.

setTimeout(() => setProgress({ value, duration, easing }), delay)
);
return () => timers.forEach(clearTimeout);
}, [steps]);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: useProgressSteps 호출부에서 steps 전달 방식 확인

rg -nP -A5 -B2 'useProgressSteps\(' --type=ts --type=tsx

Repository: MaxedOut-Muneo/Muneo-Client

Length of output: 99


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# 1) 훅 구현 확인
HOOK_FILE="apps/web/src/app/(main)/_components/LoadingScreen/_hooks/useProgressSteps.ts"
echo "=== HOOK FILE (head) ==="
wc -l "$HOOK_FILE"
sed -n '1,200p' "$HOOK_FILE" | nl -ba

# 2) useProgressSteps 호출부 찾기 (tsx filetype은 rg 환경마다 다를 수 있어 -g로 안전하게)
echo "=== CALL SITES (useProgressSteps) ==="
rg -n "useProgressSteps\s*\(" -S apps/web/src --glob='*.ts' --glob='*.tsx' --glob='*.js' --glob='*.jsx'

# 3) 호출부의 steps 전달 방식(가능한 한 가까운 라인까지) 살펴보기
echo "=== CONTEXT AROUND CALL SITES ==="
# useProgressSteps( 근처에 steps를 넘기는 형태를 찾기 위해 steps= 혹은 steps 인자를 대략적으로 필터
rg -n "useProgressSteps\s*\(" -S apps/web/src --glob='*.ts' --glob='*.tsx' --glob='*.js' --glob='*.jsx' -o \
  | head -n 50 >/tmp/calls.txt || true

# 각 호출이 있는 파일/라인을 기준으로 주변 문맥 출력
while IFS=: read -r file line; do
  echo "---- $file:$line ----"
  sed -n "$((line-10)),$((line+20))p" "$file" | nl -ba
done < <(cut -d: -f1-2 /tmp/calls.txt 2>/dev/null | sort -u) || true

Repository: MaxedOut-Muneo/Muneo-Client

Length of output: 237


🏁 Script executed:

#!/bin/bash
set -euo pipefail

HOOK_FILE="apps/web/src/app/(main)/_components/LoadingScreen/_hooks/useProgressSteps.ts"
echo "=== HOOK FILE ==="
wc -l "$HOOK_FILE"
echo "=== HOOK CONTENT ==="
sed -n '1,120p' "$HOOK_FILE" | cat -n

echo "=== CALL SITES (useProgressSteps) ==="
rg -n "useProgressSteps\s*\(" apps/web/src --glob='*.ts' --glob='*.tsx' --glob='*.js' --glob='*.jsx' -S || true

Repository: MaxedOut-Muneo/Muneo-Client

Length of output: 1448


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="apps/web/src/app/(main)/_components/LoadingScreen/_components/ProgressBar.tsx"
echo "=== FILE ==="
wc -l "$FILE"
echo "=== CONTENT ==="
sed -n '1,200p' "$FILE" | cat -n

Repository: MaxedOut-Muneo/Muneo-Client

Length of output: 1011


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# ProgressBar 호출부 찾기
rg -n "ProgressBar\s*[^=]*\s*steps=" apps/web/src --glob='*.ts' --glob='*.tsx' --glob='*.js' --glob='*.jsx' -S || true

# 단순히 ProgressBar 사용 위치 전체 스캔(steps 인자 포함 여부 확인용)
rg -n "ProgressBar" apps/web/src --glob='*.ts' --glob='*.tsx' -S || true

Repository: MaxedOut-Muneo/Muneo-Client

Length of output: 555


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="apps/web/src/app/(main)/_components/LoadingScreen/LoadingScreen.tsx"
echo "=== FILE ==="
wc -l "$FILE"
echo "=== CONTENT ==="
sed -n '1,200p' "$FILE" | cat -n

Repository: MaxedOut-Muneo/Muneo-Client

Length of output: 1617


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# LoadingScreen 호출부 찾기
rg -n "<LoadingScreen" apps/web/src --glob='*.ts' --glob='*.tsx' --glob='*.js' --glob='*.jsx' -S || true

# progressSteps 전달하는 형태만 더 좁혀서 확인
rg -n "progressSteps\s*=" apps/web/src --glob='*.ts' --glob='*.tsx' --glob='*.js' --glob='*.jsx' -S || true

Repository: MaxedOut-Muneo/Muneo-Client

Length of output: 567


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILES=(
  "apps/web/src/app/(main)/analysis/_components/AnalysisLoadingScreen/AnalysisLoadingScreen.tsx"
  "apps/web/src/app/(main)/estimate/_components/EstimateLoadingScreen/EstimateLoadingScreen.tsx"
)

for f in "${FILES[@]}"; do
  echo "=== FILE: $f ==="
  wc -l "$f"
  sed -n '1,220p' "$f" | cat -n
  echo
done

Repository: MaxedOut-Muneo/Muneo-Client

Length of output: 4012


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== LoadingScreen usages ==="
rg -n "<LoadingScreen\b" apps/web/src --glob='*.ts' --glob='*.tsx' --glob='*.js' --glob='*.jsx' -S || true

echo
echo "=== progressSteps prop assignments ==="
rg -n "progressSteps\s*=" apps/web/src --glob='*.ts' --glob='*.tsx' --glob='*.js' --glob='*.jsx' -S || true

Repository: MaxedOut-Muneo/Muneo-Client

Length of output: 636


useProgressStepssteps 의존성은 현재 안정적이라 타이머 재생성 이슈 없음
useProgressSteps에서 useEffect(..., [steps])steps 참조에 의존하는 구조입니다. 다만 현재 useProgressSteps 호출부는 ProgressBar이고, LoadingScreen에서 전달되는 progressStepsPROGRESS_STEPS 모듈 상수로만 제공되어(AnalysisLoadingScreen/EstimateLoadingScreen) 렌더마다 새 배열이 만들어지지 않습니다.
향후 다른 곳에서 steps를 동적으로 생성해 넘길 경우에만 의존성 변경으로 타이머 churn이 생길 수 있으니, API 기대사항(참조 안정성/메모이즈)을 주석이나 문서로 남기는 정도를 권장합니다.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/app/`(main)/_components/LoadingScreen/_hooks/useProgressSteps.ts
at line 32, The useEffect in useProgressSteps depends on the steps reference
(useEffect(..., [steps])) which assumes callers pass a stable array; add a short
comment in useProgressSteps stating the API expectation that callers (e.g.,
ProgressBar / LoadingScreen) must pass a stable/memoized steps array (such as
the PROGRESS_STEPS constant used by AnalysisLoadingScreen and
EstimateLoadingScreen) or wrap dynamic step arrays with useMemo to avoid timer
churn, and mention this expectation in the hook's docstring or adjacent comment
so future callers know to preserve reference stability.

@github-actions

Copy link
Copy Markdown

🔍 CI 검증 결과

검증 항목 web admin design-system
Type ✅ 통과 ✅ 통과 ✅ 통과
Prettier ✅ 통과 ✅ 통과 ✅ 통과
Lint ✅ 통과 ✅ 통과 ✅ 통과
tests ✅ 통과 ✅ 통과 ✅ 통과
Build ✅ 통과 ✅ 통과 ➖ 해당 없음

@KyeongJooni KyeongJooni left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM 👍

@tmdcks1103
tmdcks1103 merged commit 0d99f4e into main May 28, 2026
14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

♻️Refactor 코드 리팩토링

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Refactor] #50 - 통합 테스트 및 UI 개선

2 participants