fix: 9월 1주차 QA#233
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Walkthroughaxios 클라이언트에서 Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor U as 사용자
participant F as Feed.tsx
participant T as waitForToken
participant L as loadTotalFeeds / loadMyFeeds
participant A as apiClient (axios)
participant S as 서버
U->>F: 피드 화면 진입/탭 전환
F->>T: 토큰 대기
T-->>F: 완료
note over F: 로컬스토리지 토큰 확인 및 조기 반환 로직 제거
F->>L: 활성 탭에 따른 로더 호출
L->>A: 요청 전송
A->>A: 요청 인터셉터(Authorization 설정, withCredentials 미사용)
A->>S: API 호출
S-->>A: 응답
A-->>L: 데이터 반환
L-->>F: 렌더링 데이터 전달
sequenceDiagram
autonumber
participant C as 호출자
participant A as apiClient
participant I as 요청 인터셉터
participant S as 서버
C->>A: axios 요청
A->>I: 인터셉트
I-->>A: 토큰 존재 시 Authorization 헤더 설정
note right of I: withCredentials 설정 제거됨
A->>S: 요청 전송
S-->>A: 응답
A-->>C: 응답 반환
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Suggested labels
Pre-merge checks (2 passed, 1 inconclusive)❌ Failed checks (1 inconclusive)
✅ Passed checks (2 passed)
Poem
Tip 👮 Agentic pre-merge checks are now available in preview!Pro plan users can now enable pre-merge checks in their settings to enforce checklists before merging PRs.
Please see the documentation for more information. Example: reviews:
pre_merge_checks:
custom_checks:
- name: "Undocumented Breaking Changes"
mode: "warning"
instructions: |
Pass/fail criteria: All breaking changes to public APIs, CLI flags, environment variables, configuration keys, database schemas, or HTTP/GraphQL endpoints must be documented in the "Breaking Change" section of the PR description and in CHANGELOG.md. Exclude purely internal or private changes (e.g., code not exported from package entry points or explicitly marked as internal).Please share your feedback with us on this Discord post. ✨ Finishing Touches
🧪 Generate unit tests
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 |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (3)
src/pages/mypage/SavePage.tsx (1)
356-362: 패딩 축소에 따른 터치 타깃 축소 가능성 점검BookItem 패딩을 12px로 축소하면 우측 Save 아이콘(24x24)의 실제 탭 가능한 영역이 작아져(권장 최소 44px) 오터치가 늘 수 있습니다. 디자인 의도 유지하면서 아이콘의 히트 에어리어만 확장하는 방안을 고려해주세요(예: SaveIcon에 padding 추가 또는 ::after로 클릭 영역 확장).
예시(선택 적용, 변경 범위 외 참고용):
const SaveIcon = styled.div` width: 24px; height: 24px; padding: 10px; /* 히트 에어리어 44x44 근접 */ margin: -10px; /* 레이아웃 영향 최소화 */ cursor: pointer; `;src/components/feed/UserProfileItem.tsx (2)
10-21: prop 이름 변경 OK — 기본 아바타 기본값 지정 제안네트워크 오류/누락 대비해 profileImageUrl에 기본값(플레이스홀더)을 두면 UX가 안정적입니다.
-const UserProfileItem = ({ - profileImageUrl, +const UserProfileItem = ({ + profileImageUrl = defaultAvatar, nickname,참고: 상단에 기본 아바타 import 추가 필요
import defaultAvatar from '@/assets/common/defaultAvatar.png';
62-62: img 접근성/성능 개선: alt/width/height/lazy 및 폴백 적용대체 텍스트와 고정 크기, 지연 로딩을 추가하고 onError 폴백을 연결하는 것을 권장합니다.
- <img src={profileImageUrl} /> + <img + src={profileImageUrl} + alt={`${nickname}님의 프로필 이미지`} + width={36} + height={36} + loading="lazy" + decoding="async" + onError={(e) => { (e.currentTarget as HTMLImageElement).src = defaultAvatar; }} + />
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (7)
src/api/index.ts(0 hunks)src/api/users/getUsers.ts(1 hunks)src/components/feed/UserProfileItem.tsx(2 hunks)src/data/userData.ts(4 hunks)src/pages/feed/Feed.tsx(0 hunks)src/pages/mypage/SavePage.tsx(1 hunks)src/types/user.ts(1 hunks)
💤 Files with no reviewable changes (2)
- src/api/index.ts
- src/pages/feed/Feed.tsx
🔇 Additional comments (2)
src/data/userData.ts (1)
5-5: 목데이터 키(profileImageUrl)로의 일관된 변경 확인모든 항목이 새 키로 정리되어 있어 타입과 컴포넌트 사용과의 정합성이 맞습니다. 별도 이슈 없습니다.
Also applies to: 14-14, 23-23, 32-32
src/api/users/getUsers.ts (1)
3-10: 백엔드 반환 필드(profileImageUrl) 일치 여부 확인
src/api/users/getUsers.ts에profileImgUrl사용 흔적이 없으므로, 백엔드가 실제로profileImageUrl을 반환하는지 확인해 주세요. 여전히 레거시profileImgUrl을 반환한다면, 하위호환 맵핑(응답 변환 또는 인터페이스 선택적 필드 추가) 로 정규화 처리해야 합니다.
| export interface UserProfileItemProps { | ||
| profileImgUrl: string; | ||
| profileImageUrl: string; | ||
| nickname: string; |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
프로필 이미지 필드명 변경 LGTM — 잔존 참조 스캔 권장
UI/타입 일치가 핵심이라서 레거시 키(profileImgUrl) 잔존 여부를 전체 코드베이스에서 한 번 스캔해 주세요.
🏁 Script executed:
#!/bin/bash
# 레거시 키 사용 흔적 확인
rg -nP '\bprofileImgUrl\b' -C2 --hidden -g '!**/node_modules/**'
# 혹시 문자열 키로 남아있는 경우까지
rg -nP '"profileImgUrl"\s*:' -C2 --hidden -g '!**/node_modules/**'Length of output: 517
src/pages/feed/FollowerListPage.tsx:129 — 프로필 이미지 prop 이름 수정 필요
여전히 profileImgUrl={user.profileImageUrl}로 전달되고 있으니 profileImageUrl로 변경하세요.
🤖 Prompt for AI Agents
In src/types/user.ts around lines 3-5, the UserProfileItemProps defines
profileImageUrl but the component usage in src/pages/feed/FollowerListPage.tsx
(around line 129) still passes profileImgUrl; update the prop passed to
profileImageUrl={user.profileImageUrl}. Search for any other usages of
profileImgUrl and rename them to profileImageUrl to match the type definition
and ensure props align.
There was a problem hiding this comment.
Actionable comments posted: 0
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (4)
src/pages/feed/FollowerListPage.tsx (4)
2-2: 무한 스크롤 중 중복 API 호출 가능성 — useRef로 동시 요청 가드 필요(1/4: import)
useCallback/스크롤 리스너가loading의 오래된 값을 캡처합니다. 동시 호출 방지를useRef로 처리하세요.-import { useState, useEffect, useCallback } from 'react'; +import { useState, useEffect, useCallback, useRef } from 'react';
17-24: 무한 스크롤 동시 요청 가드 추가(2/4: ref 선언)렌더링과 무관한 진행 상태는 ref로 추적하는 것이 안전합니다.
const [isLast, setIsLast] = useState(false); + // 동시 요청 가드 (state 대신 ref 사용해 최신 값 유지) + const isFetchingRef = useRef(false);
31-36: 로딩 가드 교체 및 finally 복원(3/4: 가드/해제 시점 수정)
if (loading) return;는 stale 클로저로 무력화될 수 있습니다. ref 기반 가드로 교체하고 try/finally에서 ref를 정확히 토글하세요.- async (cursor?: string) => { - if (loading) return; + async (cursor?: string) => { + if (isFetchingRef.current) return; try { - setLoading(true); + setLoading(true); + isFetchingRef.current = true; setError(null); let response; @@ } finally { - setLoading(false); + setLoading(false); + isFetchingRef.current = false; }Also applies to: 87-89
95-101: 스크롤 가드에 ref 반영(4/4: 중복 트리거 방지)진행 중일 때 추가 호출을 막습니다.
- if (loading || isLast || error || retryCount >= 3 || !nextCursor) { + if (loading || isFetchingRef.current || isLast || error || retryCount >= 3 || !nextCursor) { console.log('스크롤 요청 차단:', { loading, isLast, error, retryCount, nextCursor }); return; }
🧹 Nitpick comments (2)
src/pages/feed/FollowerListPage.tsx (2)
60-62: 콘솔 로그는 프로덕션에서 억제디버그 로그는 개발 환경에서만 출력되도록 가드하는 편이 좋습니다.
- console.log('API 응답:', response); - console.log('추출된 사용자 데이터:', userData); + if (process.env.NODE_ENV !== 'production') { + console.log('API 응답:', response); + console.log('추출된 사용자 데이터:', userData); + }
123-123: 접근성: 아이콘 대체 텍스트 추가아이콘 img에 alt를 지정해 스크린리더 친화도를 개선하세요.
- <TitleHeader leftIcon={<img src={leftArrow} />} onLeftClick={handleBackClick} title={title} /> + <TitleHeader leftIcon={<img src={leftArrow} alt="뒤로가기" />} onLeftClick={handleBackClick} title={title} />
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (1)
src/pages/feed/FollowerListPage.tsx(1 hunks)
🔇 Additional comments (1)
src/pages/feed/FollowerListPage.tsx (1)
127-135: 프로필 이미지 prop 리네임 일관성 확인됨
profileImgUrl사용 흔적이 제거되었고,FollowData및 관련 타입·API 응답 모두profileImageUrl로 정상 정의·매핑되어 있습니다.
…to chore/minor-updates
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (6)
package.json (1)
12-13: LAN 노출 스크립트 보안·안전성 보완 제안
--host 0.0.0.0로 개발 서버가 동일 네트워크 모든 단말에 노출됩니다. 의도된 사용이라면 좋습니다만, 다음 옵션을 함께 두는 걸 권장합니다:
- 고정 포트 실패 시 종료:
--strictPort- 필요 시만 실행하도록 분리(이미 dev:lan로 분리된 점은 👍)
예시 수정:
- "dev:lan": "vite --host 0.0.0.0 --port 5173" + "dev:lan": "vite --host 0.0.0.0 --port 5173 --strictPort"또한 조직 정책상 공용/카페 와이파이 사용을 금지하는 안내를 README에 한줄 추가하면 사고 예방에 도움이 됩니다.
index.html (4)
19-27: touchstart 리스너는 passive: true로
preventDefault를 호출하지 않으므로passive: true가 성능 경고를 줄이고 스크롤 지연 가능성을 낮춥니다.- document.addEventListener( + document.addEventListener( 'touchstart', function (e) { startX = e.touches[0].clientX; startY = e.touches[0].clientY; }, - { passive: false }, + { passive: true }, );
48-55: touchend도 passive: true 권장기본 동작을 막지 않으므로 passive로 바꿔도 안전합니다.
- { passive: false }, + { passive: true },
57-64: touchcancel도 passive: true 권장동일한 이유로
passive: true가 적절합니다.- { passive: false }, + { passive: true },
117-125: --vvh 업데이트 이벤트 추가 제안(키보드/주소바 변화 대응 강화)주소바 축소/확대·가상키보드 등장 시
visualViewport의scroll/resize, 윈도우orientationchange에 반응하게 해두면 더 견고합니다. 또한DOMContentLoaded시점 보장도 추천.function applyVv() { const vv = window.visualViewport; const h = vv ? vv.height : window.innerHeight; document.documentElement.style.setProperty('--vvh', `${h}px`); } - applyVv(); - (window.visualViewport || window).addEventListener('resize', applyVv); + // 초기화 + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', applyVv, { once: true }); + } else { + applyVv(); + } + // 변화 대응 + const vv = window.visualViewport; + vv?.addEventListener('resize', applyVv); + vv?.addEventListener('scroll', applyVv, { passive: true }); + window.addEventListener('resize', applyVv); + window.addEventListener('orientationchange', applyVv);src/components/common/Modal/MoreMenu.tsx (1)
54-71: inset: 0와 height 동시 지정은 중복/혼동 유발 — 하나로 통일
position: fixed에서top과bottom이 동시에 지정되면height는 무시됩니다. 현재inset: 0때문에 아래의height: 100vh/100dvh/var(--vvh)는 실효성이 없습니다. 의도(동적 뷰포트 대응)가--vvh라면,bottom을 제거하고 height를 살리는 쪽이 명확합니다. 또한width: 100vw는inset: 0과 중복으로 일부 브라우저에서 수평 스크롤을 유발할 수 있습니다.정리 예시:
- inset: 0; + top: 0; + left: 0; + right: 0; @@ - height: 100vh; - @supports (height: 100dvh) { - height: 100dvh; - } - /* JS 폴백 변수 */ - height: var(--vvh, 100dvh); + /* 동적 뷰포트 높이(visualViewport → --vvh, 미지원시 100dvh) */ + height: var(--vvh, 100dvh); @@ - width: 100vw; + /* inset-inline으로 폭은 이미 고정됨: width 제거 */
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- Jira integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (4)
index.html(4 hunks)package.json(1 hunks)src/components/common/Modal/MoreMenu.tsx(2 hunks)src/components/feed/FollowList.tsx(1 hunks)
✅ Files skipped from review due to trivial changes (1)
- src/components/feed/FollowList.tsx
🔇 Additional comments (2)
index.html (1)
28-46: [index_scripts]#!/bin/bash # index.html 경로 확인 INDEX_PATH=$(fd index.html | head -n 1) echo "index.html 경로: $INDEX_PATH" # touchmove 이벤트 리스너 위치 출력 rg -n "touchmove" -C3 "$INDEX_PATH"src/components/common/Modal/MoreMenu.tsx (1)
86-86: 안전영역 패딩 추가는 👍하단 홈바 겹침 방지에 효과적입니다. 유지 추천합니다.
| { passive: false }, | ||
| ); | ||
|
|
||
| // 추가적인 제스처 차단 |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
iOS 전용 gesture 차단은 핀치 줌까지 막아 접근성 이슈 유발*
gesturestart/change/end에서 preventDefault()를 호출하면 iOS Safari에서 핀치 줌이 차단되어 WCAG 확대 요건을 침해할 수 있습니다. 필요 구간(예: 풀스크린 모달 활성화 시)에만 조건부로 차단하거나, 전역 차단을 제거하는 방향을 권장합니다.
옵션 A(전역 제거):
- document.addEventListener(
- 'gesturestart',
- function (e) {
- e.preventDefault();
- },
- { passive: false },
- );
- document.addEventListener(
- 'gesturechange',
- function (e) {
- e.preventDefault();
- },
- { passive: false },
- );
- document.addEventListener(
- 'gestureend',
- function (e) {
- e.preventDefault();
- },
- { passive: false },
- );옵션 B(조건부 차단):
- function (e) {
- e.preventDefault();
- },
+ function (e) {
+ // 예: 모달/드로어가 열렸을 때만 차단
+ if (document.documentElement.hasAttribute('data-block-gestures')) {
+ e.preventDefault();
+ }
+ },Also applies to: 68-89
아래 스크립트로 index.html에 gesturestart/gesturechange/gestureend 이벤트 리스너와 preventDefault() 호출이 실제로 존재하는지 확인해 주세요.
🏁 Script executed:
#!/bin/bash
# index.html에서 gesture 이벤트 리스너 위치 확인
rg -nP "addEventListener\(\s*['\"]gesture(start|change|end)['\"]" -C2 index.html
# index.html에서 preventDefault() 호출 위치 확인
rg -n "preventDefault" -C2 index.htmlLength of output: 582
iOS Safari 전역 gesture 이벤트 차단으로 핀치 줌이 막혀 WCAG 확대 요건 위배
index.html 68~89줄의 gesturestart/change/end 리스너 전부에서 전역 e.preventDefault() 호출을 제거하고, 풀스크린 모달 등 실제 필요 시점에만 다음과 같이 조건부로 차단하도록 수정하세요.
- 전역 제거
해당 범위 전체 리스너 삭제 - 조건부 적용
function onGesture(e) { if (document.documentElement.hasAttribute('data-block-gestures')) { e.preventDefault(); } } // … document.addEventListener('gesturestart', onGesture, { passive: false }); document.addEventListener('gesturechange', onGesture, { passive: false }); document.addEventListener('gestureend', onGesture, { passive: false });
🤖 Prompt for AI Agents
In index.html around line 66 (originally 68–89), the global gesture event
listeners call e.preventDefault() unconditionally which blocks iOS Safari
pinch-zoom and violates WCAG; remove those global listeners and instead add a
single shared onGesture handler that only calls e.preventDefault() when a page
state indicates gestures must be blocked (for example when
document.documentElement has a data-block-gestures attribute), then register
that handler for gesturestart/gesturechange/gestureend with { passive: false }
so the prevention works when needed.
| <meta property="og:description" content="커뮤니티형 독서 기록 플랫폼. 띱. THIP." /> | ||
| <meta property="og:image" content="https://thip.co.kr/assets/thumbnail.jpeg" /> | ||
| <meta property="og:url" content="https://thip.co.kr" /> | ||
| <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" /> |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
viewport 메타 태그 중복 정의 정리
<meta name="viewport" ...>가 두 번 선언되어 있습니다. 하나만 남기고 통합하세요. 아래처럼 viewport-fit=cover를 포함한 한 줄로 정리하는 걸 권장합니다.
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+ <!-- 단일 선언 권장: viewport-fit 포함 -->
+ <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />그리고 위 변경과 함께 기존 중복 라인(초반의 viewport 메타, 현재 128라인)은 제거해주세요.
🏁 Script executed:
#!/bin/bash
rg -n "<meta name=\"viewport\"" index.htmlLength of output: 179
viewport 메타 태그 중복 제거 및 통합
- index.html 128라인의
<meta name="viewport" content="width=device-width, initial-scale=1.0" />삭제 - 134라인의
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />한 줄로 유지
🤖 Prompt for AI Agents
In index.html around lines 128 and 134, remove the duplicate viewport meta at
line 128 (<meta name="viewport" content="width=device-width, initial-scale=1.0"
/>) and keep the single unified tag at line 134 (<meta name="viewport"
content="width=device-width, initial-scale=1, viewport-fit=cover" />); ensure
only the line-134 meta remains in the head so there is no duplicate viewport
meta tag.
#️⃣연관된 이슈
없음
📝작업 내용
💬리뷰 요구사항
없음
Summary by CodeRabbit