SEOJing 학습 UX 중간 점검 묶음#31
Conversation
|
Warning Review limit reached
More reviews will be available in 6 minutes and 17 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 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 configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (7)
Walkthrough이 PR은 블로그 기사에 TTS 오디오 플레이어와 섹션별 질문 기능을 추가하고, 프레젠테이션 뷰의 전체화면 캔버스 모드를 구현하며, AdSense 준비도 검토 문서를 포함합니다. ChangesBlog Audio & Section QA Features
Presentation View Enhancements
Documentation and Content Updates
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 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 |
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
seojing | 7f68dd0 | Commit Preview URL Branch Preview URL |
Jun 08 2026, 06:15 AM |
|
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
|
추가 반영했습니다.
검증:
원격 체크:
|
|
추가 피드백 반영했습니다.
검증:
원격 체크:
|
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (3)
apps/web/src/widgets/post-qa/SectionQaPrompts.tsx (1)
64-65: 💤 Low value스크롤 이벤트 리스너가 불필요할 수 있음
프롬프트의 절대 위치는 스크롤 중에 변경되지 않으므로
scroll이벤트 리스너는 불필요할 수 있습니다.resize와ResizeObserver만으로 충분합니다.♻️ 제안된 수정
measure(); window.addEventListener("resize", measure); - window.addEventListener("scroll", measure, { passive: true }); const resizeObserver = typeof ResizeObserver === "undefined" ? null : new ResizeObserver(() => measure()); resizeObserver?.observe(article); return () => { window.removeEventListener("resize", measure); - window.removeEventListener("scroll", measure); resizeObserver?.disconnect(); };Also applies to: 74-75
🤖 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/widgets/post-qa/SectionQaPrompts.tsx` around lines 64 - 65, Remove the unnecessary window "scroll" listener in SectionQaPrompts.tsx: the absolute position of the prompt doesn't change on scroll so keep the resize handling (window.addEventListener("resize", measure)) and the existing ResizeObserver logic, and remove any lines that add window.addEventListener("scroll", measure, { passive: true }) (also update the matching removal in cleanup if present). Apply this change for both occurrences referenced (the lines around the first addEventListener and the second pair at 74-75) so only resize/ResizeObserver drive calls to measure.apps/web/src/widgets/blog-audio-player/BlogAudioPlayer.tsx (1)
161-168: ⚖️ Poor tradeoff
timeupdate이벤트 핸들러의 과도한 localStorage 쓰기 주의
handleTimeUpdate는timeupdate이벤트마다 호출되며, 재생 중에는 초당 여러 번 발생할 수 있습니다. 매번localStorage.setItem을 호출하면 성능 문제가 발생할 수 있습니다.권장 사항: 쓰기를 throttle하거나 (예: 5초마다) debounce 처리를 추가하세요.
⚡ Throttle 적용 예시
+ const lastSaveRef = useRef(0); + const handleTimeUpdate = useCallback(() => { const audio = audioRef.current; if (!audio || !selectedArtifact) return; + const now = performance.now(); + if (now - lastSaveRef.current < 5000) return; // 5초마다만 저장 + lastSaveRef.current = now; writeStoredString( positionKey(storageBaseKey, selectedArtifact), String(Math.floor(audio.currentTime)), ); }, [selectedArtifact, storageBaseKey]);🤖 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/widgets/blog-audio-player/BlogAudioPlayer.tsx` around lines 161 - 168, The handleTimeUpdate function is writing to storage on every timeupdate event (via writeStoredString(positionKey(...))), which can cause performance issues; modify handleTimeUpdate (and any setup that attaches it to audioRef) to throttle/debounce storage writes — e.g., track the lastSavedTime (module or ref-level variable) and only call writeStoredString when Math.floor(audio.currentTime) differs from lastSavedTime by a threshold (e.g., 5 seconds) or use a throttle utility (lodash.throttle) to limit writes to once every N seconds; keep references to selectedArtifact, storageBaseKey, positionKey and writeStoredString unchanged, just gate the calls to writeStoredString accordingly.apps/web/src/widgets/presentation/PresentationView.tsx (1)
433-436: ⚡ Quick win조건부 체크가 중복됩니다.
이미
else브랜치(덱 모드)에 있으므로isMobile || !isFullscreenCanvas조건을 다시 체크할 필요가 없습니다. 항상undefined를 반환하도록 단순화할 수 있습니다.♻️ 단순화 제안
<div ref={slideContentRef} className="mx-auto w-full max-w-5xl overflow-hidden" - style={ - isMobile || !isFullscreenCanvas - ? undefined - : { zoom: pcScale } - } />덱 모드에서는 zoom을 적용하지 않으므로 style 속성 자체를 제거해도 됩니다.
🤖 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/widgets/presentation/PresentationView.tsx` around lines 433 - 436, The JSX in PresentationView redundantly re-checks isMobile || !isFullscreenCanvas inside the deck-mode else branch to decide the style/zoom (pcScale); remove the redundant conditional and always return undefined for that style (or simply omit the style prop in deck mode) so zoom: pcScale is not applied in deck mode; update the JSX expression that currently uses "isMobile || !isFullscreenCanvas ? undefined : { zoom: pcScale }" to a single undefined (or remove the prop) when in the deck-mode branch.
🤖 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/scripts/insert-content-image.mjs`:
- Around line 13-20: The allowedExtensions Set currently includes ".svg" which
exposes XSS/JS vectors; remove ".svg" from allowedExtensions and require an
explicit opt-in (e.g., a new allowSvg boolean/CLI flag) or run a
sanitizeSvg(svgContent) validation step before accepting SVG uploads; update the
code paths that read allowedExtensions and any upload handler/validation logic
(referencing allowedExtensions and the upload processing function in
insert-content-image.mjs) to reject SVG by default and only accept after the
explicit flag or after sanitizeSvg passes.
---
Nitpick comments:
In `@apps/web/src/widgets/blog-audio-player/BlogAudioPlayer.tsx`:
- Around line 161-168: The handleTimeUpdate function is writing to storage on
every timeupdate event (via writeStoredString(positionKey(...))), which can
cause performance issues; modify handleTimeUpdate (and any setup that attaches
it to audioRef) to throttle/debounce storage writes — e.g., track the
lastSavedTime (module or ref-level variable) and only call writeStoredString
when Math.floor(audio.currentTime) differs from lastSavedTime by a threshold
(e.g., 5 seconds) or use a throttle utility (lodash.throttle) to limit writes to
once every N seconds; keep references to selectedArtifact, storageBaseKey,
positionKey and writeStoredString unchanged, just gate the calls to
writeStoredString accordingly.
In `@apps/web/src/widgets/post-qa/SectionQaPrompts.tsx`:
- Around line 64-65: Remove the unnecessary window "scroll" listener in
SectionQaPrompts.tsx: the absolute position of the prompt doesn't change on
scroll so keep the resize handling (window.addEventListener("resize", measure))
and the existing ResizeObserver logic, and remove any lines that add
window.addEventListener("scroll", measure, { passive: true }) (also update the
matching removal in cleanup if present). Apply this change for both occurrences
referenced (the lines around the first addEventListener and the second pair at
74-75) so only resize/ResizeObserver drive calls to measure.
In `@apps/web/src/widgets/presentation/PresentationView.tsx`:
- Around line 433-436: The JSX in PresentationView redundantly re-checks
isMobile || !isFullscreenCanvas inside the deck-mode else branch to decide the
style/zoom (pcScale); remove the redundant conditional and always return
undefined for that style (or simply omit the style prop in deck mode) so zoom:
pcScale is not applied in deck mode; update the JSX expression that currently
uses "isMobile || !isFullscreenCanvas ? undefined : { zoom: pcScale }" to a
single undefined (or remove the prop) when in the deck-mode branch.
🪄 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: CHILL
Plan: Pro
Run ID: 9df8e326-89c6-45d5-9a10-fd8e8d4b6357
⛔ Files ignored due to path filters (1)
apps/web/public/images/content/seojing/presentation-scale-stabilization/viewport-units-comparison-01.svgis excluded by!**/*.svg
📒 Files selected for processing (15)
apps/web/content/SEOJing/presentation-scale-stabilization.mdxapps/web/scripts/insert-content-image.mjsapps/web/src/app/blog/[...slug]/page.tsxapps/web/src/widgets/article-analytics/useArticleAnalytics.test.tsxapps/web/src/widgets/article-analytics/useArticleAnalytics.tsapps/web/src/widgets/blog-audio-player/BlogAudioPlayer.test.tsxapps/web/src/widgets/blog-audio-player/BlogAudioPlayer.tsxapps/web/src/widgets/post-qa/PostQaPanel.tsxapps/web/src/widgets/post-qa/SectionQaPrompts.tsxapps/web/src/widgets/post-qa/index.tsapps/web/src/widgets/presentation/PresentationView.tsxapps/web/src/widgets/presentation/presentation.utils.test.tsapps/web/src/widgets/presentation/presentation.utils.tsdocs/seojing-adsense-readiness-review.mddocs/seojing-content-asset-policy.md
TTS / 질문 UX 후속 반영 완료요청 주신 UX 피드백과 이후 CodeRabbit 후속 리뷰를 반영했습니다. 반영 내용
검증
최종 head: |
섹션 질문 / 오디오 UX 추가 조정 완료추가 피드백 반영했습니다. 반영 내용
검증
Commit: |
섹션 질문 hover / 오디오 도킹 위치 재조정추가 피드백 반영했습니다. 변경
확인
Commit: |
이번 PR에서 정리한 것
게이트웨이 재시작으로 티켓 연쇄가 끊긴 상태라서, 로컬에 남아 있던 작업을 한 번 PR 단위로 묶었습니다. 큰 방향은 “글을 읽는 방식”을 조금 더 학습 도구답게 만드는 중간 저장입니다.
1. 블로그 오디오 플레이어
TTS artifact manifest가 있는 글에서는 본문 아래에 오디오 플레이어가 뜨도록 붙였습니다.
/tts-artifacts/<slug>/manifest.json에서 읽습니다.여기서 중요한 점은 “오디오를 별도 기능”으로 두기보다, 글을 다시 읽는 다른 입구로 붙였다는 점입니다.
2. TTS 재생 이벤트를 analytics에 연결
오디오 플레이어에서 발생하는 이벤트를 기존 article analytics 흐름으로 흘려보내도록 연결했습니다.
단, 원문 질문/답변 같은 민감한 raw text를 보내지 않았던 것처럼, 여기서도 privacy-safe한 요약 필드만 보냅니다. 즉 “무엇을 얼마나 들었는지”는 보되, 사용자의 사적인 입력을 수집하는 방향은 아닙니다.
3. 프레젠테이션 모드 회귀 복구
처음 PR에 넣었던 데스크톱 deck/card형 목차 레이아웃은 기존 프레젠테이션 모드의 핵심 동작을 깨뜨렸습니다. 화면 전체를 쓰지 못하고, 슬라이드 분할 기준과 실제 표시 영역이 어긋났습니다.
그래서 이 PR에서는 그 레이아웃 변경을 되돌리고, 기존처럼 화면 전체를 슬라이드 표시 영역으로 쓰도록 복구했습니다.
프레젠테이션 모드는 발표/훑어보기의 기본기가 먼저라서, 목차형 deck UX는 나중에 별도 설계로 다시 다루는 편이 맞습니다.
4. 콘텐츠 이미지 워크플로우 보강
presentation-scale-stabilization글에 viewport 단위 비교 SVG를 추가했고, 이미지 삽입 도구도 SVG를 허용하도록 바꿨습니다.100vh,svh,dvh차이를 설명하는 다이어그램 추가ArticleImage템플릿 추가insert-content-image.mjs에서.svg허용5. AdSense readiness 중간 점검 문서
AdSense를 바로 붙이는 단계가 아니라, 먼저 무엇이 막히는지 확인한 문서를 추가했습니다.
현재 판정은 간단합니다.
/privacy, 쿠키 고지,ads.txt, 실제 analytics collect surface는 아직 정리 필요어디까지 왔는지
이번 PR까지 합치면 SEOJing 고도화 흐름은 대략 여기까지 왔습니다.
아직 다음 단계로 남은 건 privacy route, 광고 실험 계획, 포트폴리오 케이스 스터디 쪽입니다.
확인한 것
pnpm exec vitest run src/widgets/presentation/presentation.utils.test.tspnpm exec vitest run src/widgets/blog-audio-player/BlogAudioPlayer.test.tsx src/widgets/article-analytics/useArticleAnalytics.test.tsx src/widgets/presentation/presentation.utils.test.tspnpm run lint(apps/web)pnpm run build(apps/web)pnpm format:checkpnpm test:coveragegit diff --checkvinext start에서/blog/okayJing/workflow/smart-reviewer-behavior프레젠테이션 모드가 fixed full viewport를 덮고, 16:9 카드 축소 레이아웃이 제거된 것을 확인Summary by CodeRabbit
릴리스 노트
New Features
Documentation
Tests