Skip to content

Refactor/#131 pdf 미리보기 페이지 넘어가는 단위 수정 - #132

Merged
leeleeleeleejun merged 7 commits into
devfrom
refactor/#131
Jul 30, 2026
Merged

Refactor/#131 pdf 미리보기 페이지 넘어가는 단위 수정#132
leeleeleeleejun merged 7 commits into
devfrom
refactor/#131

Conversation

@leeleeleeleejun

@leeleeleeleejun leeleeleeleejun commented Jul 30, 2026

Copy link
Copy Markdown
Member

#️⃣연관된 이슈

📝작업 내용

1. PDF 섹션이 페이지를 넘어갈 때 '섹션 제목'이 고립되던 문제

PDF가 A4 여러 장으로 나뉠 때, 미리보기의 2단(제목 좌 / 항목 우) 레이아웃 특성상 섹션이 페이지 경계에서 나뉘면 제목만 페이지 맨 아래에 홀로 남는(고립) 현상이 있었습니다.
섹션 전체를 wrap={false}로 묶으면, A4 한 장을 넘는 큰 섹션이 다음 장으로 못 가고 겹쳐서 렌더되는 문제가 있었습니다
(쪼갤 수 없는 블록이 페이지보다 크면 배치 불가).

아이템 단위로만 분리되지 않도록 하면

  1. 섹션 제목과 아이템영역이 분리됨
Image

섹션(카테고리) 단위로 분리되지 않도록 하면

  1. 현재 페이지에 다 들어가지 못하면 섹션 전체가 다음 페이지로 넘어가 큰 공백이 생김
Image
  1. 쪼갤 수 없는 블록(하나의 섹션)이 페이지보다 크면 배치 불가
    (하나의 섹션이 하나의 페이지를 넘어가서 필수적으로 절단이 필요하지만 절단되지않아서 겹쳐서 나옴)
image

해결방법
섹션을 [제목 + 첫 항목](→ wrap={false}로 묶음) + [빈 열 + 나머지 항목] 두 줄로 재구성했습니다.

  • Children.toArray로 첫 항목과 나머지를 분리
  • 묶는 단위가 "제목 + 항목 하나"뿐이라 항상 한 페이지에 들어가 겹치지 않고, 제목이 첫 항목과 항상 함께 이동해 고립되지 않음
  • 나머지 항목은 Item 단위로 페이지를 이어감
image

2. 이력서 완료 후 상세로 이동해도 최신 내용(PDF)이 바로 안 보이던 문제

완료 뮤테이션 onSuccessinvalidateQueriesawait 없이 실행돼, 상세 refetch가 끝나기 전에 페이지가 이동해 stale 캐시(수정 전 데이터) 가 보였습니다.
onSuccessasync로 바꿔 상세 무효화만 await 처리. 이동 시점에 최신 데이터가 보장됩니다

스크린샷 (선택)

💬리뷰 요구사항(선택)

리뷰어가 특별히 봐주었으면 하는 부분이 있다면 작성해주세요

Summary by CodeRabbit

  • 버그 수정
    • 이력서 수정 후 상세 정보가 최신 상태로 안정적으로 반영되도록 개선했습니다.
    • PDF 다운로드 시 섹션 제목과 첫 항목이 페이지 경계에서 분리되지 않도록 레이아웃을 개선했습니다.
    • 긴 섹션의 항목이 더 일관된 간격과 구조로 표시됩니다.

- 제목과 첫 항목을 묶어 페이지 분리 시 제목 고립 방지 처리 추가
- Children.toArray를 활용한 항목 분리 및 렌더링 로직 최적화
- `onSuccess` 내부 쿼리 무효화 호출 시 비동기 처리로 변경
- 업데이트된 이력서 정보 및 목록, 카운트 데이터를 최신 상태로 유지하도록 개선
@leeleeleeleejun
leeleeleeleejun requested a review from optshj July 30, 2026 04:52
@leeleeleeleejun leeleeleeleejun self-assigned this Jul 30, 2026
@leeleeleeleejun leeleeleeleejun linked an issue Jul 30, 2026 that may be closed by this pull request
1 task
@vercel

vercel Bot commented Jul 30, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
28th-web-team-1-fe Ready Ready Preview Jul 30, 2026 5:59am

@coderabbitai

coderabbitai Bot commented Jul 30, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@leeleeleeleejun, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 50 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

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.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 94f7b387-137b-49af-9909-76e372e068e9

📥 Commits

Reviewing files that changed from the base of the PR and between c0f860a and a372229.

📒 Files selected for processing (5)
  • src/features/resume_pdf_download/lib/generateResumePdf.tsx
  • src/features/resume_pdf_download/ui/ResumeDownloadButton.tsx
  • src/views/resume_detail/ui/ResumeDetailPage.tsx
  • src/views/resume_edit/ui/form/FormInput.tsx
  • src/views/resume_edit/ui/form/FormTextarea.tsx

Walkthrough

이 변경은 이력서 수정 성공 시 상세 쿼리 무효화를 await하도록 조정하고, PDF 섹션 렌더링을 제목·첫 항목과 후속 항목 행으로 분리해 페이지 경계 처리를 변경합니다.

Changes

이력서 캐시 무효화

Layer / File(s) Summary
상세 쿼리 무효화 순서 조정
src/entities/resume/model/resume.mutations.ts
useUpdateResume의 성공 콜백을 비동기로 변경하고 상세 쿼리 무효화를 await한 뒤 후속 무효화를 실행합니다.

이력서 PDF 섹션 레이아웃

Layer / File(s) Summary
섹션 행 구조 및 페이지 경계 처리
src/features/resume_pdf_download/ui/ResumePdfDocument.tsx
섹션을 세로로 쌓고, 제목·첫 항목과 후속 항목을 별도 가로 행으로 렌더링하며, 첫 행에 wrap={false}를 적용합니다.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related PRs

Suggested labels: ✨ Feature

Suggested reviewers: optshj

Poem

당근처럼 단단해진 캐시의 길,
토끼는 상세 갱신을 기다려요.
PDF 줄마다 제목을 꼭 안고,
첫 항목과 함께 페이지를 건너요.
깡충, 더 고른 이력서가 되었답니다!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed PDF 미리보기의 페이지 분할 단위 수정이라는 핵심 변경을 잘 요약한 제목입니다.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
⚔️ Resolve merge conflicts 💡
  • Resolve merge conflict in branch refactor/#131
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor/#131

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@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

🤖 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 `@src/entities/resume/model/resume.mutations.ts`:
- Around line 46-49: Update the onSuccess callback to await the list and count
invalidations alongside the detail invalidation, using Promise.all for
resumeKeys.lists() and resumeKeys.counts(workspaceId) so the mutation does not
complete until all refetches finish.

In `@src/features/resume_pdf_download/ui/ResumePdfDocument.tsx`:
- Around line 80-87: Remove the first-child wrapping from the SectionRow
rendering in ResumePdfDocument, so the title is not grouped with the entire
first content block under wrap={false}. Keep long ResumeItem text and skillGrid
content splittable across pages, and implement title orphan prevention through
the existing type/unit rendering or minPresenceAhead mechanism instead.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 8ff1bda7-4444-4361-b1d6-7ee37b15f6b8

📥 Commits

Reviewing files that changed from the base of the PR and between daa3708 and c0f860a.

📒 Files selected for processing (2)
  • src/entities/resume/model/resume.mutations.ts
  • src/features/resume_pdf_download/ui/ResumePdfDocument.tsx

Comment on lines +46 to 49
onSuccess: async () => {
await queryClient.invalidateQueries({ queryKey: resumeKeys.detail(workspaceId, resumeId) })
queryClient.invalidateQueries({ queryKey: resumeKeys.lists() })
queryClient.invalidateQueries({ queryKey: resumeKeys.counts(workspaceId) })

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Locate file =="
git ls-files | rg '(^|/)resume\.mutations\.ts$' || true

echo "== File excerpt =="
cat -n src/entities/resume/model/resume.mutations.ts | sed -n '1,120p'

echo "== Resolvable package info =="
for f in package.json pnpm-lock.yaml package-lock.json yarn.lock; do
  if [ -f "$f" ]; then
    echo "--- $f ---"
    if [ "$f" = "package.json" ]; then
      node - <<'JS'
const fs=require('fs');
const p=JSON.parse(fs.readFileSync('package.json','utf8'));
console.log(JSON.stringify({packageManager:p.packageManager, dependencies:p.dependencies, devDependencies:p.devDependencies}, null, 2));
JS
    else
      rg -n "`@tanstack/query-core`|invalidateQueries|`@tanstack`" "$f" | head -80 || true
    fi
  fi
done

echo "== Query usage =="
rg -n "invalidateQueries|resumeKeys|resumeId|workspaceId|onSuccess" src/entities/resume/model/resume.mutations.ts src -g '*.ts' -g '*.tsx' | head -200

Repository: YAPP-Github/28th-Web-Team-1-FE

Length of output: 30472


🌐 Web query:

TanStack Query v5 queryClient invalidateQueries returns Promise useMutation documentation

💡 Result:

In TanStack Query v5, the queryClient.invalidateQueries method returns a Promise [1][2]. This Promise resolves once the invalidation process—which includes marking the matching queries as stale and potentially refetching active queries—has completed [1][3][4]. Awaiting this Promise is particularly useful when used within mutation callbacks like onSuccess [5][6]. By returning (or awaiting) this Promise inside the mutation lifecycle, you ensure that the mutation remains in a pending state (e.g., isPending remains true) until the invalidated queries have finished refetching [5][6]. Key points regarding this behavior: - Purpose of the Promise: It allows you to synchronize your code with the completion of the background refetching triggered by the invalidation [7][4]. - Mutation Integration: You can use async/await within useMutation callbacks (such as onSuccess) to wait for invalidations to finish before the mutation is considered fully settled [5][6]. - Granularity: If invalidating multiple queries, you can wrap multiple queryClient.invalidateQueries calls in Promise.all to await them all simultaneously [5][6]. Example usage within useMutation: const mutation = useMutation({ mutationFn: addTodo, onSuccess: async => { // Wait for the query to be invalidated and refetched await queryClient.invalidateQueries({ queryKey: ['todos'] }); }, });

Citations:


목록·카운트 무효화도 모두 await 처리해 주세요.

queryClient.invalidateQueries()는 TanStack Query v5에서 Promise를 반환하므로, 목록·카운트 무효화도 Promise.all로 병렬 await해야 mutation 성공의 refetch 완료 시점이 보장됩니다.

       await queryClient.invalidateQueries({ queryKey: resumeKeys.detail(workspaceId, resumeId) })
-      queryClient.invalidateQueries({ queryKey: resumeKeys.lists() })
-      queryClient.invalidateQueries({ queryKey: resumeKeys.counts(workspaceId) })
+      await Promise.all([
+        queryClient.invalidateQueries({ queryKey: resumeKeys.lists() }),
+        queryClient.invalidateQueries({ queryKey: resumeKeys.counts(workspaceId) })
+      ])
📝 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
onSuccess: async () => {
await queryClient.invalidateQueries({ queryKey: resumeKeys.detail(workspaceId, resumeId) })
queryClient.invalidateQueries({ queryKey: resumeKeys.lists() })
queryClient.invalidateQueries({ queryKey: resumeKeys.counts(workspaceId) })
onSuccess: async () => {
await queryClient.invalidateQueries({ queryKey: resumeKeys.detail(workspaceId, resumeId) })
await Promise.all([
queryClient.invalidateQueries({ queryKey: resumeKeys.lists() }),
queryClient.invalidateQueries({ queryKey: resumeKeys.counts(workspaceId) })
])
🤖 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/entities/resume/model/resume.mutations.ts` around lines 46 - 49, Update
the onSuccess callback to await the list and count invalidations alongside the
detail invalidation, using Promise.all for resumeKeys.lists() and
resumeKeys.counts(workspaceId) so the mutation does not complete until all
refetches finish.

Comment on lines +80 to +87
const [first, ...rest] = Children.toArray(children)
return (
// '제목 + 첫 항목'을 wrap={false}로 묶어, 페이지 경계에서 제목만 홀로 남는 분리를 막는다(묶음은 항목 하나 크기라
// 항상 한 페이지에 들어감 → 겹침 없음). 나머지 항목은 자유롭게 나뉘어(빈 좌측 열로 정렬 유지) 큰 섹션도 이어진다.
<View style={styles.section}>
<View style={styles.sectionLine} wrap={false}>
<Text style={styles.sectionTitle}>{title}</Text>
<View style={styles.sectionBody}>{first}</View>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Locate file =="
fd -a 'ResumePdfDocument\.tsx$' . || true

echo "== File excerpt =="
if [ -f src/features/resume_pdf_download/ui/ResumePdfDocument.tsx ]; then
  wc -l src/features/resume_pdf_download/ui/ResumePdfDocument.tsx
  sed -n '1,180p' src/features/resume_pdf_download/ui/ResumePdfDocument.tsx | cat -n
fi

echo "== Relevant occurrences =="
rg -n "FirstElement|Children.toArray|wrap=\{false\}|SkillGrid|skillGrid|ResumeItem|`@react-pdf/renderer`" src/features/resume_pdf_download -S || true

Repository: YAPP-Github/28th-Web-Team-1-FE

Length of output: 12033


🌐 Web query:

@react-pdf/renderer 2 wrap false prevents component from being split across pages

💡 Result:

In @react-pdf/renderer, setting the wrap={false} prop on a component (such as View) instructs the engine to attempt to render the component as a single, unbreakable block [1][2]. If the component fits within the remaining space on the current page, it will be rendered there [1]. If it does not fit, it will be moved to the beginning of the next page [1]. However, there are significant technical limitations and known behaviors associated with this prop: 1. Potential for Overflow: If the content of a wrap={false} component is larger than the available height of a single page, the component will not automatically split across multiple pages [3][4]. Instead, it may overflow off the bottom of the page [4] or, in some cases, cause rendering issues or process hangs [5]. 2. Not a "Break When Needed" Feature: wrap={false} is intended to keep elements together, not to force a component to start on a new page if it would otherwise split, while still allowing it to wrap if it is too large [3][6]. Requests for a feature like breakWhenNeeded—which would trigger a page break only if an element doesn't fit, but allow it to span pages if it is larger than a single page—have been discussed in the project's issue tracker as a distinct requirement from the current behavior [3][6]. For scenarios where you need to prevent splitting but avoid the pitfalls of wrap={false} on very large content, common community workarounds include: * Using the break prop to manually force page breaks before specific components when necessary [1][7]. * Breaking down large components into smaller, individual units that are each set to wrap={false} [5]. * Keeping headers or parent containers "fixed" so that they persist across page breaks when their children are allowed to wrap [1][3].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== ResumePdfDocument tail =="
sed -n '175,230p' src/features/resume_pdf_download/ui/ResumePdfDocument.tsx | cat -n

echo "== package dependencies =="
if [ -f package.json ]; then
  node - <<'JS'
const fs = require('fs')
const pkg = JSON.parse(fs.readFileSync('package.json', 'utf8'))
for (const sec of ['dependencies','devDependencies']) {
  if (!pkg[sec]) continue
  for (const [k,v] of Object.entries(pkg[sec])) {
    if (k === '`@react-pdf/renderer`' || k === 'react-pdf') console.log(`${k}: ${v}`)
  }
}
JS
fi

echo "== tests related to pdf/download =="
fd -i '.*pdf.*|.*download.*|.*resume.*' src test tests __tests__ 2>/dev/null | head -80 || true
rg -n "ResumePdfDocument|CoreSkill|CORE_SKILL|wrap=\{false\}|minPresenceAhead|Page" src test tests 2>/dev/null || true

Repository: YAPP-Github/28th-Web-Team-1-FE

Length of output: 27744


첫 자녀 단위로 wrap={false}를 적용하지 마세요.

SectionRow가 렌더한 첫 번째 React child까지 wrap={false}로 묶으니 CORE_SKILL의 전체 텍스트 열 하나와, SKILL의 전체 skillGrid를 모두 단일 비분할 블록으로 만듭니다. 더 큰 문제는 ResumeItem 전체와 skillGrid 같은 긴 블록이 남은 페이지 높이보다 클 수 있는데도 두 번째 페이지로만 미뤄지고 실제 문단/스킬 단위는 여러 페이지로 분할되지 않습니다. 제목 고아 방지 규칙은 타입별/단위별 렌더링이나 minPresenceAhead로 분리하고, 첫 항목과 스킬 그리드에는 wrap={false}를 적용하지 마세요.

🤖 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/features/resume_pdf_download/ui/ResumePdfDocument.tsx` around lines 80 -
87, Remove the first-child wrapping from the SectionRow rendering in
ResumePdfDocument, so the title is not grouped with the entire first content
block under wrap={false}. Keep long ResumeItem text and skillGrid content
splittable across pages, and implement title orphan prevention through the
existing type/unit rendering or minPresenceAhead mechanism instead.

Source: MCP tools

@optshj optshj left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

첫 섹션의 내용이 너무 길면
첫번째 사진의 예시처럼 동일하게 큰 공백이 생기는건가요?

@leeleeleeleejun

leeleeleeleejun commented Jul 30, 2026

Copy link
Copy Markdown
Member Author

첫 섹션의 내용이 너무 길면 첫번째 사진의 예시처럼 동일하게 큰 공백이 생기는건가요?

현재 페이지 남은 공간에 제목은 들어가나 내용이 다 들어가지 못하면 제목과 내용이 분리되는 문제가 발생했습니다
섹션을 제목 + 첫 항목을 묶음으로 구성해 분리되지 않도록 했습니다! (4번째 사진)

@optshj

optshj commented Jul 30, 2026

Copy link
Copy Markdown
Member

첫 섹션의 내용이 너무 길면 첫번째 사진의 예시처럼 동일하게 큰 공백이 생기는건가요?

현재 페이지에 제목은 들어가나 내용이 다 들어가지 못하면 제목과 내용이 분리되는 문제가 발생합니다
섹션을 제목 + 첫 항목을 묶음으로 구성해 분리되지 않도록 했습니다! (4번째 사진)

아하아하 알겠습니다~~

- 다운로드 완료 시 사용자에게 성공 메시지 표시
@leeleeleeleejun
leeleeleeleejun merged commit d063ee0 into dev Jul 30, 2026
3 checks passed
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.

[Refactor] pdf 미리보기 페이지 넘어가는 단위 수정

2 participants