Skip to content

FEATURE : QA 결과에 따른 ui 및 api 수정 및 리팩토링#43

Merged
kim-subsub merged 27 commits into
developfrom
feat/42
May 14, 2026
Merged

FEATURE : QA 결과에 따른 ui 및 api 수정 및 리팩토링#43
kim-subsub merged 27 commits into
developfrom
feat/42

Conversation

@kim-subsub

@kim-subsub kim-subsub commented May 14, 2026

Copy link
Copy Markdown
Contributor

Summary

closed #42

QR 분석 결과 처리 흐름의 안정성, 보안성, 유지보수성을 높이기 위해 스캔 응답 정규화, 상태 전환, 로딩/SSE 처리, 리포트 데이터 조립 로직을 단계적으로 리팩토링했습니다.
또한 README와 컨벤션/로드맵 문서를 현재 구조에 맞게 최신화했습니다.

Tasks

  • CI에서 타입 오류를 잡을 수 있도록 typecheck 스크립트 및 검증 흐름 보강
  • 비 URL 실행 스킴 보안 정책 정리 및 테스트 추가
  • 스캔 URL, 위험도, 스키마, 웹 여부 판정 공통 유틸 분리
  • scanSessionStore 상태 전환 로직을 순수 함수로 분리
  • 로딩/SSE 결과 라우팅, 상세 조회 retry, polling 정책 분리
  • 리포트 데이터 조립 로직을 컨텍스트/평판/도메인/서버 정보 빌더로 분리
  • route-level lazy loading 및 vendor chunk 분리 상태 확인
  • README, convention, refactor-roadmap 문서 최신화
  • pnpm format, pnpm lint, pnpm test, pnpm typecheck, pnpm security:check, pnpm build 통과

Summary by CodeRabbit

릴리스 노트

  • New Features

    • 리포트 페이지에서 도메인 신고 건수 및 생성일 정보 표시
    • 테스트용 QR 코드 생성 기능 추가
  • Bug Fixes

    • 로딩 상세 분석 폴링 로직 개선 및 모듈화
    • 외부 링크 오픈 시 보안 검증 강화
  • Documentation

    • README 및 개발 가이드 확장 (프로젝트 구조, 스크립트, 환경 설정)
    • 리팩토링 로드맵 문서 추가
  • Tests

    • 로딩, 리포트, 결과 페이지 관련 단위 테스트 다수 추가
  • Chores

    • TypeScript 타입 체크 CI 통합
    • QR 코드 라이브러리 의존성 추가

@vercel

vercel Bot commented May 14, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
veri-q Ready Ready Preview, Comment May 14, 2026 2:08pm

@coderabbitai

coderabbitai Bot commented May 14, 2026

Copy link
Copy Markdown

Warning

Rate limit exceeded

@kim-subsub has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 36 minutes and 28 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, 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 have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 4e5bb6e7-d9f3-4456-b677-82241800279c

📥 Commits

Reviewing files that changed from the base of the PR and between 4ebd620 and 4b79030.

📒 Files selected for processing (8)
  • README.md
  • src/pages/Loading/hooks/useLoadingPage.ts
  • src/pages/Loading/lib/loadingDetailResolution.test.ts
  • src/pages/Loading/lib/loadingDetailResolution.ts
  • src/pages/Report/constants/threatText.ts
  • src/shared/lib/scan-session/ensureScanDetail.ts
  • src/shared/lib/scan-session/scanSessionErrors.ts
  • src/shared/lib/scan-session/scanUrlResolution.test.ts

워크스루

이 PR은 보고서 페이지의 평판 데이터 구조를 신고 건수/도메인 생성일 중심으로 개선하고, 스캔 결과 정규화 및 라우팅 시스템을 도입하며, 로딩/결과 페이지의 상세 분석 제어 로직을 모듈화합니다. 또한 Non-URL 실행 보안을 강화하고 프로젝트 문서를 업데이트합니다.

변경 사항

보고서 페이지 데이터 구조 및 표현 개선

층 / 파일 설명
위협 탐지 카탈로그 확장
src/pages/Report/constants/threatText.ts, src/pages/Report/constants/riskDetectionCatalog.ts, src/pages/Report/constants/riskDetectionCatalog.test.ts
threatTextCatalog에 단축 URL, 의심 쿼리/경로/프래그먼트, 악성코드/피싱/인증서 관련 185개 항목이 추가되고, normalizeRiskType이 더 다양한 특수 문자를 제거하도록 확장되며, 백엔드 위협 코드 매핑이 테스트됩니다.
보고서 데이터 컨텍스트 및 세션 소스
src/pages/Report/lib/reportPageDataContext.ts, src/pages/Report/lib/reportPageDataContext.test.ts
getReportSessionSources가 세션의 분석/최종/스캔/히스토리 소스를 우선순위로 수집하고, createReportPageDataContext가 리스크 레벨/URL/도메인/평판/서버정보 레코드를 통합하여 ReportPageDataContext를 구성합니다.
평판/도메인/서버정보 빌더
src/pages/Report/lib/reportReputationData.ts, src/pages/Report/lib/reportDomainData.ts, src/pages/Report/lib/reportServerInfoData.ts, src/pages/Report/lib/toReportPageData.test.ts
buildReportReputation이 reportCount/domainAgeText/providerStatusText를 구성하고, buildReportDomainComparison이 officialUrl/suspiciousUrl/summary를 생성하며, buildReportServerInfo와 resolveReportServerInfoRecords가 인증서 유효성/유효기간을 매핑합니다. 테스트는 요약 메트릭 우선 사용과 누락 시 폴백을 검증합니다.
보고서 페이지 통합 및 UI 업데이트
src/pages/Report/lib/toReportPageData.ts, src/pages/Report/ReportPage.tsx, src/pages/Report/types/reportPage.types.ts, src/pages/Report/styles/reportPage.css.ts
toReportPageData가 createReportPageDataContext와 buildReport* 빌더를 호출하도록 단순화되고, ReportPage는 reportCount 기반으로 배지 톤을 결정하며, ReportReputationSummary 타입이 reportCount/domainAgeText를 포함하도록 변경되고, 태블릿 그리드가 3열에서 2열로 조정됩니다.

스캔 결과 정규화 및 라우팅 시스템

층 / 파일 설명
스캔 식별자 및 결과 기본 정규화
src/shared/lib/scan-session/scanIdentity.ts, src/shared/lib/scan-session/scanResultNormalization.ts, src/shared/lib/scan-session/scanIdentity.test.ts, src/shared/lib/scan-session/scanResultNormalization.test.ts
resolveScanIdentifier가 scanId/scan_id/id를 추출하고, normalizeScanResult/resolveScanDecodedUrl/resolveScanSchemeType/resolveScanIsUrl/resolveScanRiskLevel이 URL/스킴/위험도를 정규화하며, 테스트는 필드 우선순위와 스코어 우선 사용을 검증합니다.
스캔 URL 해석 및 결과 라우팅
src/shared/lib/scan-session/scanUrlResolution.ts, src/shared/lib/scan-session/scanResultRoute.ts, src/shared/lib/scan-session/scanUrlResolution.test.ts, src/shared/lib/scan-session/scanResultRoute.test.ts
resolveScanUrls가 sources/decodedUrl/historyUrl로부터 scannedUrl/destinationUrl/previewUrl을 도출하고, buildScanResultHref가 라우트와 URL로 href를 생성하며, resolveScanResultRoute가 최종 라우트/href를 결정합니다. 테스트는 리다이렉트 URL 구분과 스코어 기반 라우팅을 검증합니다.
스캔 세션 상태 전이 및 저장소
src/shared/store/scanSessionTransitions.ts, src/shared/store/scanSessionStore.ts, src/shared/store/scanSessionTransitions.test.ts
buildAnalysisDetailPatch/buildFinalResultPatch/buildHistorySelectionPatch/buildScanResponsePatch가 각 단계별 입력을 정규화하여 ScanSessionPatch를 생성하고, scanSessionStore가 이 빌더들을 호출하여 상태를 일관되게 갱신합니다. 테스트는 URL 필드 누락 시 유지와 스킴 정규화를 검증합니다.

로딩 페이지 상세 분석 제어 시스템

층 / 파일 설명
상세 분석 해결 및 재시도
src/pages/Loading/lib/loadingDetailResolution.ts, src/pages/Loading/lib/loadingDetailResolution.test.ts
resolveLoadingDetailWithRetry가 ensureDetail()을 DETAIL_RETRY_MAX만큼 반복 시도하며, 404는 지연 후 재시도하고, SCAN_SESSION_REQUIRED는 session-required로 해결하며, 한도 초과 시 timeout을 반환합니다. 테스트는 즉시 성공, 404 재시도, 세션 필수, 비-404 에러, 타임아웃 케이스를 검증합니다.
상세 분석 폴링 및 진행도 판별
src/pages/Loading/lib/loadingDetailPolling.ts, src/pages/Loading/lib/loadingProgressResolution.ts, src/pages/Loading/lib/loadingDetailPolling.test.ts, src/pages/Loading/lib/loadingProgressResolution.test.ts
startLoadingDetailPolling이 DETAIL_POLL_START_DELAY_MS 후 canPoll/resolveDetail을 호출하고, DETAIL_POLL_MAX_ATTEMPTS 초과 시 onTimeout을 호출하며, shouldResolveDetailAfterTerminalProgress가 completed+터미널 스텝 여부를 판별합니다. 테스트는 지연 호출, 시도 소진, dispose, canPoll=false를 검증합니다.
로딩 페이지 훅 통합
src/pages/Loading/hooks/useLoadingPage.ts
openResultRouteForCurrentSession이 resolveScanResultRoute 호출로 단순화되고, resolveDetailAndOpenResult가 resolveLoadingDetailWithRetry를 호출하며, 폴링이 startLoadingDetailPolling으로 위임되고, 진행 이벤트 처리가 shouldResolveDetailAfterTerminalProgress로 변경됩니다. pollAttemptCountRef와 내부 타이머 로직이 제거됩니다.

결과 페이지 위험도 표현 및 URL 통합

층 / 파일 설명
결과 페이지 데이터 구조 및 URL 해석
src/pages/Result/types/resultPage.types.ts, src/pages/Result/lib/toResultPageData.ts, src/pages/Result/lib/toResultPageData.test.ts
ResultPageData에 riskLevel: ResultTone 필드가 추가되고, toResultPageData가 resolveScanUrls(decodedUrl/historyUrl)로 previewUrl/siteName/siteUrl/visitUrl을 매핑합니다. 테스트는 safe 매핑, critical 산출, 리다이렉트 URL 구분을 검증합니다.
결과 페이지 렌더링 및 사용자 인터랙션
src/pages/Result/ResultPage.tsx, src/pages/Result/hooks/useResultPage.ts, src/pages/Result/ui/ResultStatusPage.tsx, src/pages/Result/api/createResultPageFetcher.ts, src/pages/Result/api/createResultPageFetcher.test.ts
ResultPage가 resultData.detailUnavailable/resultData.riskLevel 기반으로 표시를 결정하고, useResultPage가 handleVisit/handleReport에서 tone 대신 resultData.riskLevel을 사용하며, ResultStatusPage가 riskLevel 필드를 포함합니다. createDetailUnavailableResultPageData가 riskLevel: 'warning'을 추가합니다.

Non-URL 실행 보안 강화

층 / 파일 설명
Non-URL 액션 실행 계획 및 확인
src/pages/ResultNonUrl/lib/resolveNonUrlActionExecution.ts, src/pages/ResultNonUrl/constants/nonUrlActionText.ts, src/pages/ResultNonUrl/lib/resolveNonUrlActionExecution.test.ts
resolveNonUrlActionExecution이 confirmationTitle/confirmationContent를 포함하도록 확장되고, allowedDeepLinkSchemeNames/allowedCryptoSchemeNames 허용 목록 검증을 추가하며, normalizeHttpUrl이 isSafeExternalUrl로 안전성을 검증합니다. 테스트는 URL 정규화, 스킴 정리, 스킴 허용, 스크립트 차단을 검증합니다.
외부 링크 안전성 검증
src/shared/lib/browser/openExternalLink.ts, src/shared/lib/browser/openExternalLink.test.ts
openExternalLink가 반환 타입을 boolean으로 변경하고, isSafeExternalUrl로 프로토콜과 임베디드 크리덴셜을 검증하며, 안전 시 window.open('_blank', 'noopener,noreferrer') 호출 후 true 반환, 불안전 시 false 반환합니다.
Non-URL 페이지 UI 및 모달 통합
src/pages/ResultNonUrl/ResultNonUrlPage.tsx, src/pages/ResultNonUrl/styles/resultNonUrlPage.css.ts
ResultNonUrlPage가 resolveNonUrlActionExecution 결과를 unsupported/open/navigate로 분기하여 피드백/모달을 설정하고, nonUrlActionLabelByType으로 라벨을 직접 결정하며, 프리뷰 섹션/상태/스타일이 제거됩니다.

QR 스캔 페이지 라우팅 통합

층 / 파일 설명
스캔 결과 라우팅 호출자 업데이트
src/pages/QRScan/hooks/useQRScanPage.ts
useQRScanPage가 isNonWebScanResponse를 normalizeScanResult 기반으로 변경하고, nonUrlResultRoute를 사용하며, handleOpenRecentScanResult에서 resolveScanResultRouteByRiskLevel 또는 nonUrlResultRoute를 선택하여 buildScanResultHref로 href를 생성합니다.

히스토리 위험도 톤 결정

층 / 파일 설명
히스토리 항목 위험도 톤
src/features/scan-history/lib/historyItemAccess.ts, src/features/scan-history/lib/historyItemAccess.test.ts
pickHistoryRiskLevel이 resolveResultToneFromSource(source)를 호출하여 score 기반 위험도를 계산하고, historyRiskLevelKeys 상수가 제거됩니다. 테스트는 score 우선 사용을 검증합니다.

프로젝트 문서 및 CI/빌드 설정

층 / 파일 설명
프로젝트 구조 및 협업 규칙 문서
README.md, docs/convention.md, docs/refactor-roadmap.md
README가 "Veri-Q Client" 중심으로 재구성되어 FSD-lite 구조, 환경 파일, Backend Proxy, Scripts, Test QR Code 사용법을 추가합니다. docs/convention.md가 Import Order/File Naming/Page Logic/State/Security/Tests/Accessibility/Routing 섹션으로 재구성되고, docs/refactor-roadmap.md가 로드맵/검증/차기 후보를 포함합니다.
CI/빌드 설정 및 QR 생성 스크립트
.github/workflows/ci.yml, package.json, .gitignore, .secretlintignore, src/test-code/generate-qr-png.mjs
.github/workflows/ci.yml에 typecheck 단계가 추가되고, package.json에 pnpm.overrides (fast-uri), scripts (typecheck/test-code), devDependencies (qrcode)가 추가되며, .gitignore와 .secretlintignore에 src/test-code/generated-qr/ 경로가 추가되고, generate-qr-png.mjs가 CLI URL 입력으로 QR PNG를 생성합니다.

@kim-subsub kim-subsub self-assigned this May 14, 2026

@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: 2

🧹 Nitpick comments (5)
src/pages/Loading/lib/loadingDetailResolution.ts (2)

64-67: ⚡ Quick win

Promise 동작이 반환 타입과 일치하지 않습니다.

함수가 Promise<LoadingDetailResolutionStatus>를 반환하지만, 404가 아닌 에러의 경우 Line 66에서 예외를 다시 던집니다. 이는 함수가 resolve(상태 반환)와 reject(예외 던지기) 두 가지 방식으로 종료될 수 있음을 의미하며, 반환 타입만으로는 이를 알 수 없습니다.

다음 중 하나를 고려하세요:

  1. 모든 에러를 잡아서 적절한 상태로 반환 (예: 'error' 상태 추가)
  2. JSDoc 또는 주석으로 rethrow 동작을 명시적으로 문서화
  3. 반환 타입을 변경하여 에러 가능성을 표현
🤖 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 `@src/pages/Loading/lib/loadingDetailResolution.ts` around lines 64 - 67, The
function currently declares Promise<LoadingDetailResolutionStatus> but rethrows
non-404 errors (see isApiError check and throw error), causing inconsistent
behavior; instead of rethrowing in the non-404 branch, call onFailure with the
error message (using error instanceof Error ? error.message :
DETAIL_RESOLUTION_ERROR_MESSAGE) and return a LoadingDetailResolutionStatus
value representing failure (e.g., 'error'); update the branch around
isApiError()/onFailure to swallow the exception and return that error status so
the function always resolves to LoadingDetailResolutionStatus rather than
rejecting.

60-62: ⚡ Quick win

에러 메시지 문자열 기반 체크는 취약합니다.

Line 60에서 error.message === 'SCAN_SESSION_REQUIRED'로 특수 에러를 식별하는데, 이는 문자열 기반 비교로 오타나 메시지 변경에 취약합니다.

커스텀 에러 클래스를 사용하는 것을 권장합니다:

🔧 제안하는 개선 방안

새로운 에러 클래스 정의:

export class ScanSessionRequiredError extends Error {
  constructor() {
    super('SCAN_SESSION_REQUIRED');
    this.name = 'ScanSessionRequiredError';
  }
}

그리고 체크 로직을 다음과 같이 변경:

-      if (error instanceof Error && error.message === 'SCAN_SESSION_REQUIRED') {
+      if (error instanceof ScanSessionRequiredError) {
         return 'session-required';
       }
🤖 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 `@src/pages/Loading/lib/loadingDetailResolution.ts` around lines 60 - 62,
Replace the brittle string comparison in loadingDetailResolution.ts (the block
checking error.message === 'SCAN_SESSION_REQUIRED') with a proper custom error
class check: define a ScanSessionRequiredError extends Error (name
'ScanSessionRequiredError' and message 'SCAN_SESSION_REQUIRED'), update any
places that currently throw that string to throw new ScanSessionRequiredError(),
and change the conditional in the function (the one using error instanceof
Error) to use error instanceof ScanSessionRequiredError so the special-case
branch reliably detects the condition.
src/shared/lib/scan-session/scanUrlResolution.test.ts (1)

5-55: ⚡ Quick win

테스트 커버리지를 개선하면 좋습니다.

현재 테스트는 주요 시나리오(리다이렉트, 세션 디코딩, 히스토리 폴백)를 잘 다루고 있습니다. 그러나 모든 소스가 실패하고 빈 문자열로 폴백되는 엣지 케이스에 대한 테스트가 없습니다.

다음과 같은 테스트 케이스 추가를 고려하세요:

it('returns empty string when all URL resolution attempts fail', () => {
  expect(
    resolveScanUrls({
      sources: [{ unrelatedField: 'value' }],
    }),
  ).toMatchObject({
    scannedUrl: '',
    destinationUrl: '',
    originalUrl: '',
  });
});
🤖 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 `@src/shared/lib/scan-session/scanUrlResolution.test.ts` around lines 5 - 55,
Add a new unit test for resolveScanUrls to cover the edge case where no URL
fields are present on any source and all resolution attempts fail: create a test
(e.g. "returns empty string when all URL resolution attempts fail") that calls
resolveScanUrls({ sources: [{ unrelatedField: 'value' }] }) and asserts that
scannedUrl, destinationUrl, and originalUrl are all empty strings; this ensures
resolveScanUrls' fallback-to-empty-string behavior is covered alongside the
existing redirect/decoded/history tests.
src/pages/Loading/hooks/useLoadingPage.ts (2)

159-176: 💤 Low value

onFinal의 후속 분기들이 모두 같은 조기 종료로 귀결됩니다.

!hasResolvedRiskLevel 블록의 try/catch 이후 세 개의 return은 모두 동일 결과(함수 종료)를 가집니다. isResolved/detailResolutionFailedRef 검사 결과에 따른 후속 동작 차이가 없다면 가독성/노이즈 측면에서 단순화하는 편이 안전합니다.

♻️ 제안 diff
     onFinal: async (payload) => {
       setFinalResult(payload);

       const hasResolvedRiskLevel = resolveResultToneFromSources([payload], null) !== null;

       if (!hasResolvedRiskLevel) {
-        let isResolved = false;
-
         try {
-          isResolved = await resolveDetailAndOpenResult();
+          await resolveDetailAndOpenResult();
         } catch {
-          return;
+          // resolveDetailAndOpenResult 내부 onFailure 가 알림/플래그 갱신을 담당
         }
-
-        if (isResolved) {
-          return;
-        }
-
-        if (detailResolutionFailedRef.current) {
-          return;
-        }
-
         return;
       }

만약 향후 isResolved/detailResolutionFailedRef 별 후속 분기가 의도되어 placeholder로 남긴 것이라면 TODO 주석으로 의도를 남겨두는 편이 좋습니다.

🤖 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 `@src/pages/Loading/hooks/useLoadingPage.ts` around lines 159 - 176, The code
in useLoadingPage surrounding hasResolvedRiskLevel contains three successive
early returns after calling resolveDetailAndOpenResult (the isResolved check,
the detailResolutionFailedRef check, and a final return) which are redundant;
simplify by collapsing them into one early exit path—either remove the redundant
isResolved/detailResolutionFailedRef checks and directly return when
!hasResolvedRiskLevel after the try/catch, or if those checks are placeholders
for future branching, add a clear TODO comment above the
isResolved/detailResolutionFailedRef checks noting intended future behavior;
locate the logic in the same block that calls resolveDetailAndOpenResult and
references isResolved, hasResolvedRiskLevel, and detailResolutionFailedRef to
apply the change.

32-34: ⚡ Quick win

결과 페이지 진입을 풀-페이지 리로드로 전환한 점을 확인하고, 불필요한 조건문을 정리하세요.

  • Line 33: useNavigate(line 62의 / 리다이렉트)와 달리 결과 페이지 진입만 window.location.assign으로 풀-페이지 네비게이션을 강제합니다. SPA 내부 상태 손실, 메모리·HTTP 캐시 초기화, 진행 중인 zustand 영속화 flush 타이밍에 영향이 있을 수 있으니, 의도(예: 결과 라우트 lazy chunk/스토어 초기화 필수)를 문서화하거나 TanStack Router의 navigate API로 통일하는 것을 검토하세요.

  • Lines 168-176 (onFinal 콜백 내): resolveDetailAndOpenResult() 호출 후 3개의 조건부 return이 모두 같은 결과(함수 종료)를 가집니다. 각 조건이 표현하는 의도(resolved 여부, 실패 여부, 미활성 상태)를 명확히 하거나, 공통 로직으로 단순화하는 것을 권장합니다.

🤖 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 `@src/pages/Loading/hooks/useLoadingPage.ts` around lines 32 - 34, The
openResultRouteForCurrentSession function currently forces a full-page reload
via
window.location.assign(resolveScanResultRoute(getScanSessionSnapshot()).href);
change this to use your SPA router's navigate API (e.g., TanStack Router's
navigate) or, if a full reload is truly required, add an inline comment
documenting why (lazy chunk/store init requirement) so intent is explicit;
replace the direct location.assign call with
navigate(resolveScanResultRoute(getScanSessionSnapshot())) or equivalent. Also,
inside the onFinal callback where resolveDetailAndOpenResult() is called,
collapse the three identical conditional returns into a single clear guard (or
make each branch's intent explicit with separate boolean-named checks) so the
logic either returns once on any failure/inactive/resolved case or documents
distinct outcomes, referencing resolveDetailAndOpenResult and onFinal to locate
the code to simplify.
🤖 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 `@README.md`:
- Line 47: Update the README sentence so it accurately states that the
postinstall script runs during installation and creates .env.local and
.env.server.local from the example files (i.e., change the current wording that
suggests the opposite conditional to an explicit statement like: "During
installation the postinstall script runs and creates .env.local and
.env.server.local from the example files").

In `@src/pages/Report/constants/threatText.ts`:
- Around line 181-188: The entry with englishLabel 'SHORTENED URL' whose names
array includes ['SHORTENED_URL', 'shortened_url', 'short_url', 'url_shortener']
conflicts with an earlier mapping that also uses 'shortened_url', 'short_url',
and 'url_shortener', causing one to overwrite the other; fix by consolidating
aliases so each alias only appears once across the list—either remove the
duplicate aliases from this 'SHORTENED URL' entry and add any missing aliases to
the intended canonical entry, or merge the two entries into a single canonical
entry (keeping title '단축 URL 감지' and description/risk) and ensure the final
names array contains the unique set of aliases.

---

Nitpick comments:
In `@src/pages/Loading/hooks/useLoadingPage.ts`:
- Around line 159-176: The code in useLoadingPage surrounding
hasResolvedRiskLevel contains three successive early returns after calling
resolveDetailAndOpenResult (the isResolved check, the detailResolutionFailedRef
check, and a final return) which are redundant; simplify by collapsing them into
one early exit path—either remove the redundant
isResolved/detailResolutionFailedRef checks and directly return when
!hasResolvedRiskLevel after the try/catch, or if those checks are placeholders
for future branching, add a clear TODO comment above the
isResolved/detailResolutionFailedRef checks noting intended future behavior;
locate the logic in the same block that calls resolveDetailAndOpenResult and
references isResolved, hasResolvedRiskLevel, and detailResolutionFailedRef to
apply the change.
- Around line 32-34: The openResultRouteForCurrentSession function currently
forces a full-page reload via
window.location.assign(resolveScanResultRoute(getScanSessionSnapshot()).href);
change this to use your SPA router's navigate API (e.g., TanStack Router's
navigate) or, if a full reload is truly required, add an inline comment
documenting why (lazy chunk/store init requirement) so intent is explicit;
replace the direct location.assign call with
navigate(resolveScanResultRoute(getScanSessionSnapshot())) or equivalent. Also,
inside the onFinal callback where resolveDetailAndOpenResult() is called,
collapse the three identical conditional returns into a single clear guard (or
make each branch's intent explicit with separate boolean-named checks) so the
logic either returns once on any failure/inactive/resolved case or documents
distinct outcomes, referencing resolveDetailAndOpenResult and onFinal to locate
the code to simplify.

In `@src/pages/Loading/lib/loadingDetailResolution.ts`:
- Around line 64-67: The function currently declares
Promise<LoadingDetailResolutionStatus> but rethrows non-404 errors (see
isApiError check and throw error), causing inconsistent behavior; instead of
rethrowing in the non-404 branch, call onFailure with the error message (using
error instanceof Error ? error.message : DETAIL_RESOLUTION_ERROR_MESSAGE) and
return a LoadingDetailResolutionStatus value representing failure (e.g.,
'error'); update the branch around isApiError()/onFailure to swallow the
exception and return that error status so the function always resolves to
LoadingDetailResolutionStatus rather than rejecting.
- Around line 60-62: Replace the brittle string comparison in
loadingDetailResolution.ts (the block checking error.message ===
'SCAN_SESSION_REQUIRED') with a proper custom error class check: define a
ScanSessionRequiredError extends Error (name 'ScanSessionRequiredError' and
message 'SCAN_SESSION_REQUIRED'), update any places that currently throw that
string to throw new ScanSessionRequiredError(), and change the conditional in
the function (the one using error instanceof Error) to use error instanceof
ScanSessionRequiredError so the special-case branch reliably detects the
condition.

In `@src/shared/lib/scan-session/scanUrlResolution.test.ts`:
- Around line 5-55: Add a new unit test for resolveScanUrls to cover the edge
case where no URL fields are present on any source and all resolution attempts
fail: create a test (e.g. "returns empty string when all URL resolution attempts
fail") that calls resolveScanUrls({ sources: [{ unrelatedField: 'value' }] })
and asserts that scannedUrl, destinationUrl, and originalUrl are all empty
strings; this ensures resolveScanUrls' fallback-to-empty-string behavior is
covered alongside the existing redirect/decoded/history tests.
🪄 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: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 531ef085-70f3-485b-b220-53bbeb5a40c5

📥 Commits

Reviewing files that changed from the base of the PR and between 25768ae and 4ebd620.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (60)
  • .github/workflows/ci.yml
  • .gitignore
  • .secretlintignore
  • README.md
  • docs/convention.md
  • docs/refactor-roadmap.md
  • package.json
  • src/features/scan-history/lib/historyItemAccess.test.ts
  • src/features/scan-history/lib/historyItemAccess.ts
  • src/pages/Loading/hooks/useLoadingPage.ts
  • src/pages/Loading/lib/loadingDetailPolling.test.ts
  • src/pages/Loading/lib/loadingDetailPolling.ts
  • src/pages/Loading/lib/loadingDetailResolution.test.ts
  • src/pages/Loading/lib/loadingDetailResolution.ts
  • src/pages/Loading/lib/loadingProgressResolution.test.ts
  • src/pages/Loading/lib/loadingProgressResolution.ts
  • src/pages/QRScan/hooks/useQRScanPage.ts
  • src/pages/Report/ReportPage.tsx
  • src/pages/Report/constants/riskDetectionCatalog.test.ts
  • src/pages/Report/constants/riskDetectionCatalog.ts
  • src/pages/Report/constants/threatText.ts
  • src/pages/Report/lib/reportDomainData.test.ts
  • src/pages/Report/lib/reportDomainData.ts
  • src/pages/Report/lib/reportPageDataContext.test.ts
  • src/pages/Report/lib/reportPageDataContext.ts
  • src/pages/Report/lib/reportReputationData.test.ts
  • src/pages/Report/lib/reportReputationData.ts
  • src/pages/Report/lib/reportServerInfoData.test.ts
  • src/pages/Report/lib/reportServerInfoData.ts
  • src/pages/Report/lib/toReportPageData.test.ts
  • src/pages/Report/lib/toReportPageData.ts
  • src/pages/Report/styles/reportPage.css.ts
  • src/pages/Report/types/reportPage.types.ts
  • src/pages/Result/ResultPage.tsx
  • src/pages/Result/api/createResultPageFetcher.test.ts
  • src/pages/Result/api/createResultPageFetcher.ts
  • src/pages/Result/hooks/useResultPage.ts
  • src/pages/Result/lib/toResultPageData.test.ts
  • src/pages/Result/lib/toResultPageData.ts
  • src/pages/Result/types/resultPage.types.ts
  • src/pages/Result/ui/ResultStatusPage.tsx
  • src/pages/ResultNonUrl/ResultNonUrlPage.tsx
  • src/pages/ResultNonUrl/constants/nonUrlActionText.ts
  • src/pages/ResultNonUrl/lib/resolveNonUrlActionExecution.test.ts
  • src/pages/ResultNonUrl/lib/resolveNonUrlActionExecution.ts
  • src/pages/ResultNonUrl/styles/resultNonUrlPage.css.ts
  • src/shared/lib/browser/openExternalLink.test.ts
  • src/shared/lib/browser/openExternalLink.ts
  • src/shared/lib/scan-session/scanIdentity.test.ts
  • src/shared/lib/scan-session/scanIdentity.ts
  • src/shared/lib/scan-session/scanResultNormalization.test.ts
  • src/shared/lib/scan-session/scanResultNormalization.ts
  • src/shared/lib/scan-session/scanResultRoute.test.ts
  • src/shared/lib/scan-session/scanResultRoute.ts
  • src/shared/lib/scan-session/scanUrlResolution.test.ts
  • src/shared/lib/scan-session/scanUrlResolution.ts
  • src/shared/store/scanSessionStore.ts
  • src/shared/store/scanSessionTransitions.test.ts
  • src/shared/store/scanSessionTransitions.ts
  • src/test-code/generate-qr-png.mjs
💤 Files with no reviewable changes (2)
  • src/pages/ResultNonUrl/styles/resultNonUrlPage.css.ts
  • src/pages/ResultNonUrl/constants/nonUrlActionText.ts

Comment thread README.md Outdated
Comment thread src/pages/Report/constants/threatText.ts
@kim-subsub kim-subsub merged commit 62726c8 into develop May 14, 2026
8 checks passed
@kim-subsub kim-subsub deleted the feat/42 branch May 14, 2026 14:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature]: QA 결과 미구현 화면 구현

1 participant