fix: 위시리스트 뒤로가기 시 스크롤 위치 복원 - #401
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (2)
🚧 Files skipped from review as they are similar to previous changes (2)
📝 WalkthroughWalkthrough위시리스트 카드의 위치 앵커를 저장하고 상세 복귀 시 카드 오프셋을 기준으로 스크롤을 복원합니다. 루트 스크롤 컨테이너를 식별 가능하게 만들고, 삭제 성공 시 이전 페이지로 이동합니다. Changes위시리스트 탐색 상태
Estimated code review effort: 3 (Moderate) | ~20 minutes Possibly related PRs
Sequence Diagram(s)sequenceDiagram
participant WishlistList
participant WishGrid
participant WishDetail
participant wishScroll
participant ScrollContainer
WishGrid->>wishScroll: 카드 wishId와 오프셋 저장
WishGrid->>WishDetail: 상품 상세로 이동
WishDetail->>WishlistList: router.back()으로 복귀
WishlistList->>wishScroll: 저장된 앵커 조회
wishScroll->>WishlistList: wishId와 오프셋 반환
WishlistList->>ScrollContainer: 카드 오프셋 기준 scrollTop 보정
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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: 2
🧹 Nitpick comments (2)
apps/web/src/app/layout.tsx (1)
70-73: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win루트 콘텐츠 컨테이너를
<main>으로 변경하세요.이 요소는 모든 페이지의 주 콘텐츠와 스크롤 영역을 감싸지만 현재 일반
<div>입니다.As per coding guidelines, "컨테이너에는 semantic tag인
<main>을 사용한다."🤖 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/layout.tsx` around lines 70 - 73, In the root content container identified by SCROLL_CONTAINER_ID, replace the wrapping div with a semantic main element while preserving its id, className, and existing children.Source: Coding guidelines
apps/web/src/app/archive/wish/_components/WishlistList.tsx (1)
9-9: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win상위 디렉터리의 훅은 절대 경로로 import하세요.
../_hooks/useScrollRestoration은 같은 디렉터리 밖의 상대 경로입니다.@/app/archive/wish/_hooks/useScrollRestoration로 변경하세요.As per coding guidelines, "relative imports only for files in the same directory."
🤖 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/archive/wish/_components/WishlistList.tsx` at line 9, Update the useScrollRestoration import in WishlistList.tsx to use the absolute alias path `@/app/archive/wish/_hooks/useScrollRestoration` instead of the parent-directory relative path, while preserving the imported symbol and behavior.Source: Coding guidelines
🤖 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/archive/wish/_hooks/useScrollRestoration.ts`:
- Around line 45-67: Update the scroll restoration logic around the saved value
and restore callback so that when no stored scroll position exists, the
container’s scrollTop is explicitly reset to 0. Preserve the existing
saved-position retry behavior for saved values greater than zero, using the same
container and restoration state flow.
In `@apps/web/src/app/archive/wish/`[id]/_hooks/useDeleteWish.ts:
- Line 19: Update the post-delete navigation in the delete-success flow of
useDeleteWish: call router.back() only when the wish detail was entered from the
wishlist, and otherwise navigate to ROUTES.WISHLIST with router.replace().
Preserve the existing success behavior while preventing users from remaining on
the deleted detail page when no usable history exists.
---
Nitpick comments:
In `@apps/web/src/app/archive/wish/_components/WishlistList.tsx`:
- Line 9: Update the useScrollRestoration import in WishlistList.tsx to use the
absolute alias path `@/app/archive/wish/_hooks/useScrollRestoration` instead of
the parent-directory relative path, while preserving the imported symbol and
behavior.
In `@apps/web/src/app/layout.tsx`:
- Around line 70-73: In the root content container identified by
SCROLL_CONTAINER_ID, replace the wrapping div with a semantic main element while
preserving its id, className, and existing children.
🪄 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 Plus
Run ID: 00ae67fa-b47e-4d6b-b2b8-772f39e30964
📒 Files selected for processing (5)
apps/web/src/app/archive/wish/[id]/_hooks/useDeleteWish.tsapps/web/src/app/archive/wish/_components/WishlistList.tsxapps/web/src/app/archive/wish/_hooks/useScrollRestoration.tsapps/web/src/app/layout.tsxapps/web/src/consts/layout.ts
|
Discord 스레드 연동용 메타데이터입니다. discord-pr-bot 워크플로가 자동 생성하며, 수정·삭제하면 PR 과 Discord 알림 연동이 끊깁니다. |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (2)
apps/web/src/app/archive/wish/_components/wish-grid/index.tsx (1)
30-35: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win클릭 가능한 Link에
cursor-pointer를 추가하세요.두 카드 Link 모두 클릭 요소이지만 포인터 커서가 없습니다.
수정 예시
<Link href={ROUTES.WISH_EDIT(item.id)} + className="cursor-pointer" key={item.id}As per coding guidelines, "Add
cursor-pointerto clickable elements unless a shared Button component already provides it through cva."Also applies to: 73-78
🤖 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/archive/wish/_components/wish-grid/index.tsx` around lines 30 - 35, 두 카드의 Link 요소에 cursor-pointer 클래스를 추가하세요. `ROUTES.WISH_EDIT`를 사용하는 Link와 두 번째 카드 Link 모두에 적용하고, 기존 스타일 클래스와 클릭 동작은 유지하세요.Source: Coding guidelines
apps/web/src/app/archive/wish/_hooks/useScrollRestoration.ts (1)
3-9: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win프로젝트 모듈 import를
@/절대 경로로 통일하세요.
apps/web/src/app/archive/wish/_hooks/useScrollRestoration.ts#L3-L9:../_utils/wishScroll을@/app/archive/wish/_utils/wishScroll로 변경하세요.apps/web/src/app/archive/wish/_components/wish-grid/index.tsx#L9-L9:../../_utils/wishScroll을@/app/archive/wish/_utils/wishScroll로 변경하세요.As per coding guidelines, "Use
@/*absolute imports for project modules and relative imports only for files in the same directory."🤖 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/archive/wish/_hooks/useScrollRestoration.ts` around lines 3 - 9, Replace the relative wishScroll imports with the `@/` absolute alias in useScrollRestoration.ts and wish-grid/index.tsx: use `@/app/archive/wish/_utils/wishScroll` at both affected sites.Source: Coding guidelines
🤖 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/archive/wish/_components/wish-grid/index.tsx`:
- Around line 21-23: Update handleCardClick to skip saveWishScroll when the
anchor is opened with Ctrl, Cmd, Shift, or Alt, while preserving the existing
save behavior for normal clicks.
---
Nitpick comments:
In `@apps/web/src/app/archive/wish/_components/wish-grid/index.tsx`:
- Around line 30-35: 두 카드의 Link 요소에 cursor-pointer 클래스를 추가하세요.
`ROUTES.WISH_EDIT`를 사용하는 Link와 두 번째 카드 Link 모두에 적용하고, 기존 스타일 클래스와 클릭 동작은 유지하세요.
In `@apps/web/src/app/archive/wish/_hooks/useScrollRestoration.ts`:
- Around line 3-9: Replace the relative wishScroll imports with the `@/` absolute
alias in useScrollRestoration.ts and wish-grid/index.tsx: use
`@/app/archive/wish/_utils/wishScroll` at both affected sites.
🪄 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 Plus
Run ID: 39760991-15f3-46d6-8c17-325693acb507
📒 Files selected for processing (4)
apps/web/src/app/archive/wish/_components/wish-grid/index.tsxapps/web/src/app/archive/wish/_hooks/useScrollRestoration.tsapps/web/src/app/archive/wish/_utils/wishScroll.tsapps/web/src/app/layout.tsx
There was a problem hiding this comment.
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/archive/wish/_components/wish-grid/index.tsx (1)
33-40: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win카드 Link에
cursor-pointer를 추가해 주세요.두 카드 Link가 클릭 가능하지만
cursor-pointer가 없어 저장소 UI 규칙을 위반합니다. 두 Link에 동일한 클래스를 추가해 주세요.수정 예시
<Link href={ROUTES.WISH_EDIT(item.id)} key={item.id} data-wish-id={item.id} + className="cursor-pointer" onClick={event => handleCardClick(event, item.id)} >Also applies to: 76-89
🤖 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/archive/wish/_components/wish-grid/index.tsx` around lines 33 - 40, Update both card Link elements in the wish grid, including the Link wrapping WishFailedCard and the corresponding Link at the referenced second section, to include the shared cursor-pointer class. Keep their existing href, key, data attributes, and click handlers unchanged.Source: Coding guidelines
🤖 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.
Outside diff comments:
In `@apps/web/src/app/archive/wish/_components/wish-grid/index.tsx`:
- Around line 33-40: Update both card Link elements in the wish grid, including
the Link wrapping WishFailedCard and the corresponding Link at the referenced
second section, to include the shared cursor-pointer class. Keep their existing
href, key, data attributes, and click handlers unchanged.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 82ad5173-db8e-401a-9046-d748d761e0f8
📒 Files selected for processing (1)
apps/web/src/app/archive/wish/_components/wish-grid/index.tsx
iOdiO89
left a comment
There was a problem hiding this comment.
내 토너먼트 페이지에도 useScrollRestoration 훅 적용하면 어떨까
data-wish-id 같은 위시 종속적인 키값들만 일반화하면 될 것 같아
ux 고려 짱짱
| @@ -0,0 +1,64 @@ | |||
| import { SCROLL_CONTAINER_ID } from '@/consts/layout'; | |||
|
|
|||
| const STORAGE_KEY_PREFIX = 'piki.wishScroll.'; | |||
| <div | ||
| className={`pointer-events-none absolute top-0 right-0 left-0 z-[11] aspect-[201/166] bg-black/20 transition-opacity duration-200 ${isSelected ? 'opacity-100' : 'opacity-0'}`} | ||
| /> | ||
| <span className="pointer-events-none absolute top-3 left-3 z-[12] block size-5"> |
There was a problem hiding this comment.
| <div | |
| className={`pointer-events-none absolute top-0 right-0 left-0 z-[11] aspect-[201/166] bg-black/20 transition-opacity duration-200 ${isSelected ? 'opacity-100' : 'opacity-0'}`} | |
| /> | |
| <span className="pointer-events-none absolute top-3 left-3 z-[12] block size-5"> | |
| <div | |
| style={{ zIndex: Z_INDEX.BASE_IMAGE + 1 }} | |
| className={`pointer-events-none absolute top-0 right-0 left-0 aspect-[201/166] bg-black/20 transition-opacity duration-200 ${isSelected ? 'opacity-100' : 'opacity-0'}`} | |
| /> | |
| <span | |
| style={{ zIndex: Z_INDEX.BASE_IMAGE + 2 }} | |
| className="pointer-events-none absolute top-3 left-3 block size-5" | |
| > |
이거 zindex만 이렇게 바꿔줄 수 있을까!?
작업 요약
작업 세부 내용
위시리스트를 아래로 스크롤해 상품을 클릭한 뒤 뒤로가기를 하면, 항상 리스트 최상단으로 돌아와 보던 위치를 다시 찾아 내려가야 하는 불편이 있었습니다.
이는 현재 구조가
html/body가 모두overflow-hidden이고, 실제 스크롤은 루트 레이아웃의max-w-120 overflow-y-autodiv 이기 때문에 발생하는 문제였습니다. (Next.js 의 기본 스크롤 복원은 window기준으로 동작하기 때문에 복원이 이루어지지 않음)픽셀 오프셋 vs 상품 기준
scrollTop 값을 그대로 저장하면 목록이 변하지 않는 경우에는 정확하지만, 위시 목록은 항목이 자주 변경되는 경로가 많습니다.
WishProcessingCard→WishCard로 카드 종류가 전환됨카드 하나가 약 290px 이라 위쪽에서 항목 하나만 바뀌어도 픽셀 기준은 한 줄씩 어긋납니다.
이에 반해 상품 기준은 클릭한 상품 id 를 기준점으로 삼으면 목록이 어떻게 바뀌어도 그 상품 위치로 돌아갑니다.
스크롤 컨테이너 식별자 추가 —
apps/web/src/app/layout.tsx,apps/web/src/consts/layout.tsid부여 (SCROLL_CONTAINER_ID상수로 관리)스크롤 저장·키 로직 —
apps/web/src/app/archive/wish/_utils/wishScroll.ts(신규)history.state에 식별자를 심어 같은 entry 로 돌아왔는지로 판별.저장값이 있으면 = 이 entry 에서 상세로 떠난 적 있음 = 뒤로/앞으로 복귀
떨어진 거리 를 기록
읽는 시점에 지우면 StrictMode 이중 마운트에서 첫 마운트가 값을 소비한 뒤 복원을 못 끝내고
언마운트될 때 유실되므로, 성공 시점에만 지움
sessionStorage.setItem은try/catch— 할당량 초과·프라이빗 모드 대응스크롤 복원 훅 —
apps/web/src/app/archive/wish/_hooks/useScrollRestoration.tsuseLayoutEffect에서 기준 카드를 찾아, 저장 시점과 같은 위치에 오도록 스크롤을 상대 조정도달하거나 1초가 지날 때까지
requestAnimationFrame으로 재시도최상단에서 시작
기준점·저장 시점 연결 —
apps/web/src/app/archive/wish/_components/wish-grid/index.tsxdata-wish-id부여 (복원 시 기준 카드 조회용)saveWishScroll호출목록 복귀 방식 변경 —
apps/web/src/app/archive/wish/[id]/_hooks/useDeleteWish.ts위시 삭제 후
router.replace(ROUTES.WISHLIST)→router.back().새 history entry 를 쌓지 않고 원래 entry 로 돌아가야 복원이 동작
스크린샷
2026-07-29.11.12.59.mov
2026-07-29.11.36.08.mov
왜
popstate로는 판별할 수 없었나처음에는
popstate이벤트를 모듈 스코프 플래그에 기록해두고, 마운트 시점에 그 플래그로 "뒤로가기로 진입했는지"를 구분했습니다. (popstate가 새 라우트 마운트보다 먼저 발생한다는 전제)하지만
/archive/wish를 새로고침한 뒤에는 정상 동작하는데, 홈에서 탭바로 이동해 들어오면 항상 최상단으로 초기화되었습니다.-> 저장값(
saved: 797)은 멀쩡히 읽혔지만 플래그가 아직false라 복원 분기를 타지 못함원인: 리스너 등록 순서
같은
window에 등록된 리스너는 등록 순서대로 호출됩니다. 그리고 Next 라우터의popstate핸들러는 그 안에서 라우터 상태를 갱신하고 React 가 동기로 커밋까지 끝냅니다./archive/wish하드 로드->
popstate기반 판별은 코드 스플리팅 결과에 동작이 좌우되는 구조(리스너를 더 일찍 등록하도록 옮기더라도 청크 분할 방식이 바뀌면 다시 깨질 수 있어 순서에 의존하지 않는 방법이 필요했음)
대안:
history.statehistory.state는 브라우저가popstate핸들러를 호출하기 전에 이미 해당 entry 의 값으로 복원해둡니다. 리스너 등록 순서와 무관하게, 마운트 시점에 읽으면 정확함연관 이슈
closes #392
Summary by CodeRabbit
요약(릴리스 노트)