release: develop 운영 배포 준비#682
Conversation
Why: 레슨 질문 모달에서 업로드 공개 URL을 imageKeys로 보내 백엔드가 잘못된 R2 key를 재서명하던 문제를 막기 위함. Constraint: origin/develop 기반 클린브랜치에서 레슨 QnA 이미지 key 변환만 수정하고 API 계약은 변경하지 않음. Tested: yarn eslint src/app/(class-lesson)/class/vibe-intro/lesson/[id]/_components/lesson-qna-submission-modal.tsx --fix Tested: yarn biome format --write src/app/(class-lesson)/class/vibe-intro/lesson/[id]/_components/lesson-qna-submission-modal.tsx Tested: yarn typecheck Tested: git diff --check Co-authored-by: OmX <omx@oh-my-codex.dev>
class E2E 테스트 추가 및 버그 수정
Co-authored-by: OmX <omx@oh-my-codex.dev>
Co-authored-by: OmX <omx@oh-my-codex.dev>
Co-authored-by: OmX <omx@oh-my-codex.dev>
Co-authored-by: OmX <omx@oh-my-codex.dev>
…y-20260516 QnA 이미지 키 전송 오류 수정
Co-authored-by: OmX <omx@oh-my-codex.dev>
…-notion-paste fix: 노션 복붙 본문 유실 방지
…teRequest 타입 추가 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…화 범위 보완 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
프론트 레포를 운영 릴리즈 기록 SSOT로 두고 main prod 배포 및 backend-prod-deployed dispatch 기반 release record 자동 기록을 추가합니다.\n\n검증:\n- lint\n- prettier\n- typecheck\n- build\n- storybook\n- e2e\n- security\n- validate-release-records\n- Chromatic
Co-authored-by: OmX <omx@oh-my-codex.dev>
PR #613의 develop -> main 병합 전 충돌을 해소하기 위해 origin/main 변경사항을 develop에 반영합니다. 커리큘럼 드로어 배지 E2E는 develop의 scoped locator 방식을 유지해 Playwright strict mode 중복 매칭을 방지합니다. Constraint: PR #613은 develop에서 main으로 올리는 운영 반영 PR이므로 develop에 main 최신 변경사항을 먼저 흡수한다 Rejected: page-global or locator 복원 | strict mode에서 배지 컨테이너와 아이콘이 동시에 매칭될 수 있음 Confidence: high Scope-risk: moderate Tested: yarn eslint e2e/class/curriculum-drawer.spec.ts Tested: git diff --check Related: PR #613 Co-authored-by: OmX <omx@oh-my-codex.dev>
Notion 등 복합 HTML 붙여넣기에서 텍스트/표/이미지 순서를 보존하고, 실제 클립보드 payload 확인을 위한 어드민 진단 페이지를 추가합니다.
공용 MarkdownEditor의 복합 붙여넣기에서 표를 마크다운 표로 보존하고 Notion attachment 이미지는 위치 안내 문구로 남깁니다.
Co-authored-by: OmX <omx@oh-my-codex.dev>
Co-authored-by: OmX <omx@oh-my-codex.dev>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
slug 독립성을 위해 코스 공용 에셋을 vibe-intro 하위에서 class/ 공통 경로로 이동. curriculum SVG 10개 + 루트 에셋 5개 이동. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…be-intro → class/ 경로 이동 slug 독립성을 위해 모든 클래스 컴포넌트를 vibe-intro 하위에서 class/ 공통 경로로 이동. builder-feed-utils 유틸 분리 및 admin/builder-profile 동적 slug 참조 수정. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
(class-lesson)/class/vibe-intro/lesson/[id] → /class/[slug]/lesson/[id] (landing)/class/vibe-intro/** → /class/[slug]/** [id]/page.tsx를 [slug]/page.tsx로 통합 (동일 레벨 동적 세그먼트 충돌 해결) (service)/payment: courseSlug 기반 폴백 처리 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… slug 가드 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… 데드 코드 제거 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
결제 대기 페이지의 링크 href가 /class-payment-management로 변경되었으나 E2E 테스트는 구 URL /my-page?tab=payment를 기대하고 있어 CI 실패. 테스트 assertion을 실제 렌더링 경로와 일치하도록 수정. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
마이 페이지 탭 UI 및 기능 구현
- shouldBlink 가드를 !isProgressLoading → progress !== undefined 로 교체 (쿼리 disabled 상태를 로딩 완료로 오인하던 문제 해결) - 모달 제목 whitespace-nowrap 제거, 설명 line-clamp-3 추가로 높이 고정
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
레슨 목록 UX 버그 수정
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Option bonus 건너뛰기 기능은 백엔드 isOption 필드 미지원으로 아직 구현 불가 — 해당 테스트 2개를 test.skip으로 처리해 CI 블로킹 해제 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
유료 사용자(PAID)에게 백엔드가 항상 isAccessible=true를 반환하므로 기존 `!accessible` 조건이 항상 false → 자물쇠 아이콘 미표시. status === 'LOCKED' 조건으로 변경하고 lockReason도 accessible 기준으로 단순화. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
완료된 레슨 도장 텍스트 invisible 수정
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
잠긴 레슨 도장 클릭 차단 및 툴팁 통일
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
클래스 학습 레이아웃 탭 배너 UI 구현
어드민 알림톡 템플릿/로그/dry-run 화면을 추가합니다.
마이 클래스 알림톡 시간 설정을 B-02/B-03 API 상태와 연결합니다.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Warning Rate limit exceeded
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 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 configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (18)
📝 WalkthroughWalkthroughLarge feature-rich PR introducing Admin Alerttalk notification management system, course plan management UI, lesson batch update/Notion ZIP import workflows, payment checkout refactoring to move preparation logic into form component, markdown rendering pipeline improvements, comprehensive operations documentation for release management, and E2E test coverage for payment flows. ChangesOperations & Release Management Documentation
Admin Features & Course Management
Frontend Features: Payment, Notifications, Content & Testing
Estimated code review effort🎯 4 (Complex) | ⏱️ ~75 minutes Possibly related PRs
Poem
✨ Finishing Touches🧪 Generate unit tests (beta)
|
There was a problem hiding this comment.
Actionable comments posted: 12
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
src/app/(landing)/class/[slug]/(learning)/feed/write/page.tsx (1)
206-210:⚠️ Potential issue | 🟡 Minor | ⚡ Quick win편집 모드에서 뒤로가기 라벨과 실제 이동 경로가 불일치합니다.
Line 206은 항상 홈 피드로 이동하지만, 편집 모드의 라벨(Line 210)은 “피드로 돌아가기”라서 상세로 복귀하는 의미로 읽힙니다. 라벨/경로를 동일한 의도로 맞춰주세요.
패치 제안 (기존 분기 재사용)
<Link - href={`/class/${slug}/home?tab=feed`} + href={backHref} className="inline-flex items-center gap-125 font-designer-14m text-gray-800" >🤖 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/app/`(landing)/class/[slug]/(learning)/feed/write/page.tsx around lines 206 - 210, The back link currently always navigates to `/class/${slug}/home?tab=feed` while the displayed text (backLabel) changes for edit mode; update the link generation so the href matches the intent of backLabel by reusing the same branch/logic that sets backLabel (e.g., the edit-mode check or variable such as backLabel/backHref or isEditing) — ensure the anchor's href is the detail-return path when in edit mode and the home feed path otherwise so the visual label and navigation target are consistent.src/components/pages/class/payment/course-summary-section.tsx (1)
20-24:⚠️ Potential issue | 🟠 Major | ⚡ Quick win
thumbnailUrl렌더링 전 Next/Image 호스트 허용목록 검증 추가 필요현재
thumbnailUrl이 truthy이면 바로unoptimized포함<Image src={thumbnailUrl} />를 렌더링하고 있는데,unoptimized는 이미지 최적화만 건너뛸 뿐next.config.ts의images.remotePatterns허용목록 검증을 우회하지 않습니다. 허용되지 않은 도메인/경로면next/image에서Invalid src prop(400) 런타임 에러로 이미지가 깨질 수 있습니다.next.config.ts의remotePatterns에 맞춰 공통으로 호스트/경로를 사전 검증한 뒤, 미허용이면 렌더링을 중단(또는 placeholder 대체)하도록 로직을 보완하세요. (src/components/pages/class/payment/course-summary-section.tsx, 20-30)🤖 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/components/pages/class/payment/course-summary-section.tsx` around lines 20 - 24, The Image is rendered whenever thumbnailUrl is truthy, but unoptimized does not bypass next/image remotePatterns checks so unauthorized hosts cause runtime errors; update the CourseSummarySection rendering logic that checks thumbnailUrl to first validate the URL against your next.config.ts images.remotePatterns (or an equivalent helper like isRemotePatternAllowed(remotePatterns, url)) by parsing thumbnailUrl (new URL(thumbnailUrl)) and matching host/path patterns, and then only render the <Image ... src={thumbnailUrl} /> when the helper returns allowed; if not allowed, render a safe placeholder or null to avoid the Invalid src prop error.
🧹 Nitpick comments (3)
src/hooks/queries/notification/use-notification-setting.ts (1)
39-43: 💤 Low value
setQueryData이후 즉시invalidateQueries호출 재검토
setQueryData로 캐시를 즉시 업데이트한 직후invalidateQueries를 호출하면 방금 설정한 데이터가 stale로 표시되어 백그라운드 refetch가 트리거됩니다. 일반적으로 mutation 응답으로 최신 데이터를 받았다면setQueryData만으로 충분합니다.서버에서 추가 변환/처리가 있어 다시 fetch가 필요한 경우가 아니라면,
invalidateQueries호출을 제거하는 것을 고려해보세요.♻️ 제안하는 수정
onSuccess: async (data) => { queryClient.setQueryData(NOTIFICATION_SETTING_QUERY_KEY, data); - await queryClient.invalidateQueries({ - queryKey: NOTIFICATION_SETTING_QUERY_KEY, - }); },🤖 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/hooks/queries/notification/use-notification-setting.ts` around lines 39 - 43, In onSuccess for the notification setting mutation, calling queryClient.invalidateQueries immediately after queryClient.setQueryData with NOTIFICATION_SETTING_QUERY_KEY marks the freshly written cache as stale and triggers an unnecessary refetch; remove the subsequent queryClient.invalidateQueries call (or only call it conditionally when the server requires a fresh fetch) so that setQueryData alone updates the cache without an immediate background refetch.src/app/(class-lesson)/class/[slug]/lesson/[id]/page.tsx (1)
202-205: ⚡ Quick win임의 Tailwind 값을 토큰으로 치환해 주세요.
max-w-[1236px],h-[calc(100vh-var(--spacing-800))],min-h-[964px]는 이 경로에서 금지된 arbitrary value라서global.css의 프로젝트 토큰/유틸리티로 바꾸는 편이 맞습니다. As per coding guidelines "src/app/**/*.{tsx,jsx}: Do not use arbitrary Tailwind values in className attributes" and "src/**/*.{ts,tsx,css}: No Tailwind arbitrary values ... use project custom tokens fromglobal.css".Also applies to: 247-249
🤖 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/app/`(class-lesson)/class/[slug]/lesson/[id]/page.tsx around lines 202 - 205, Replace the arbitrary Tailwind values used in the JSX className attributes with project tokens from global.css: change max-w-[1236px] in the container div (currently "mx-auto w-full max-w-[1236px] px-300") to the corresponding max-width token (or add one in global.css if missing), replace h-[calc(100vh-var(--spacing-800))] in the left column div ("min-w-0 flex-1 flex flex-col h-[calc(100vh-var(--spacing-800))]") with a height token or a utility that uses the existing --spacing-800 token, and swap min-h-[964px] (the other occurrence referenced at 247-249) for a min-height token; if tokens do not exist yet, add appropriately named custom properties/utilities in global.css (e.g., --container-max-w-1236, --lesson-left-height, --lesson-min-h-964) and then use the corresponding classNames instead of arbitrary bracket values.e2e/payment/cancel.spec.ts (1)
161-174: ⚡ Quick win테스트 이름과 달리 cancel API 미호출 검증이 없습니다.
현재는 모달 닫힘만 확인해서, 회귀로 POST
/cancel이 호출돼도 테스트가 통과할 수 있습니다.🔧 제안 수정
test('"아니오" 클릭 → 모달 닫힘, cancel API 미호출', async ({ page }) => { + let cancelCalled = false; + await page.route(/\/payments\/\d+\/cancel$/, async (route) => { + if (route.request().method() === 'POST') { + cancelCalled = true; + } + await route.continue(); + }); + await gotoPaymentManagement(page); await page.getByRole('button', { name: '결제 취소' }).first().click(); await expect( page.getByText('해당 스터디의 결제를 취소하시겠습니까?'), @@ await expect( page.getByText('해당 스터디의 결제를 취소하시겠습니까?'), ).not.toBeVisible({ timeout: 3000 }); + expect(cancelCalled).toBe(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 `@e2e/payment/cancel.spec.ts` around lines 161 - 174, The test currently only checks the modal closed but not that the cancel API wasn't called; before invoking page.getByRole('button', { name: '결제 취소' }).first().click() use Playwright request interception (e.g., page.route or page.on('request')) to watch for POST requests to the cancel endpoint (match '**/cancel' or '/cancel') and record a flag or fail immediately if such a request occurs, then after clicking the '아니오' button assert that the flag is false (or that no matching request was observed) so the test verifies the cancel API was not called; update the test named '"아니오" 클릭 → 모달 닫힘, cancel API 미호출' and use gotoPaymentManagement(page) as before.
🤖 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 `@docs/ops/release-record-shared-contract.md`:
- Around line 55-63: 현재 문서의 release_id 패턴(prod-YYYYMMDD-HHmm)은 분 단위까지만 포함해 동일 분
내 연속 배포나 재시도로 충돌 가능성이 있으므로, release_id 규격을 수정해 고해상도 타임스탬프 또는 증분 식별자를 추가하세요; 예를
들어 prod-YYYYMMDD-HHmmss(초 포함) 또는 prod-YYYYMMDD-HHmm-<NN>(연속 배포 카운터), 또는
prod-YYYYMMDD-HHmm-<uniquesuffix>(밀리초 또는 짧은 해시) 같은 형태를 명시하고 release_id 관련 문서와
파일명 사용 규칙(예: release_id) 모두에 이 변경을 반영하십시오.
In `@src/app/`(admin)/admin/alerttalk/delivery-logs/[jobId]/page.tsx:
- Line 10: The page directly converts jobId via Number(jobId) without validating
it; update the page component to validate the route param (e.g., ensure
parseInt/Number yields a finite integer and >0) and call next/navigation's
notFound() when the value is invalid, then pass the validated numeric id into
AdminAlerttalkDeliveryLogDetailPageClient (replace Number(jobId) with the
validated variable); reference the route param name jobId and the component
AdminAlerttalkDeliveryLogDetailPageClient and use notFound() on invalid input.
In `@src/app/`(landing)/class/page.tsx:
- Line 470: The current expression course.learnerLabel.replace(/^\d+/, '') fails
for thousand-separated numbers like "1,234명"; update the replacement to strip
leading digits and commas and any surrounding whitespace instead, e.g. use
course.learnerLabel.replace(/^[\d,]+\s*/, '').trim() (or equivalent) where the
JSX renders course.learnerLabel so the numeric prefix including commas is
removed without leaving a leading comma.
In `@src/components/admin/courses/admin-course-plan-management.tsx`:
- Around line 206-210: The createPlanMutation.mutateAsync and the other
mutateAsync call (e.g., updatePlanMutation.mutateAsync) can leave unhandled
Promise rejections; wrap each await createPlanMutation.mutateAsync({ courseId,
request }) and the analogous mutateAsync call in a try/catch block, perform
setCreateForm(emptyPlanForm) (or other success-side state updates) only on
success inside the try, and handle the error inside catch (e.g., show/forward
the existing onError toast or rethrow if the caller needs it) so no rejection
escapes the calling function.
- Around line 386-387: The select onChange handlers in
admin-course-plan-management are using bare "as" type assertions (e.g.,
onChange('isActive', event.target.value as 'true' | 'false')), which is unsafe;
replace them with a runtime guard + fallback: read event.target.value into a
const, check if it equals 'true' or 'false' (e.g., const v = event.target.value;
const safe = v === 'true' ? 'true' : v === 'false' ? 'false' : 'false';) and
pass that safe value to onChange; apply the same guarded pattern for the other
boolean-like select handler (e.g., for 'isAllowApply') so no bare "as"
assertions remain.
In `@src/components/admin/courses/admin-lesson-detail-page-client.tsx`:
- Around line 271-275: The onSuccess handler updates local state (setLessonForm,
setHydratedLessonSnapshot via toAdminLessonForm/toAdminLessonHydrationSnapshot)
but leaves the canonical lessonDetailQuery.data stale, so the component's
rehydrate effect (the effect that reads lessonDetailQuery.data and resets
lessonForm) will overwrite the newly imported content; fix by also updating the
query cache or refetching before setting local state: use the query client to
setQueryData for the lesson detail query key to the new lessonDetail (or call
lessonDetailQuery.refetch and wait for it), then call setLessonForm and
setHydratedLessonSnapshot so the rehydrate effect sees the up-to-date snapshot
and won’t revert the form.
In `@src/components/admin/courses/admin-lesson-management-page-client.tsx`:
- Around line 530-532: The early return that checks "if (!file ||
isLessonFormLocked || !editingLessonId) { return; }" should provide user
feedback when the required editingLessonId is missing; update the guard to
detect missing editingLessonId and call the existing toast/notification utility
(the same pattern used elsewhere in this component) to show a clear error
message like "Unable to save: no lesson selected" before returning, leaving the
current behavior for file or isLessonFormLocked unchanged and referencing the
same variables (file, isLessonFormLocked, editingLessonId) to locate the check.
In `@src/components/admin/courses/admin-notion-zip-action-card.tsx`:
- Around line 60-62: The list item keys can collide because key={bullet} is not
unique for duplicate text; update the bullets.map call in
admin-notion-zip-action-card.tsx to include the index fallback (e.g.,
bullets.map((bullet, i) => ...)) and construct a unique key using both the
bullet and index (for example `${bullet}-${i}`) when rendering the <li> to
guarantee uniqueness.
In `@src/features/admin/alerttalk/api/admin-alerttalk-api.ts`:
- Around line 74-84: The URL path is built using templateKey directly in
testSendAdminAlerttalkTemplate, which breaks if templateKey contains characters
like /, ?, #; wrap templateKey with encodeURIComponent before interpolating into
the path used in the axiosInstanceV5.post call (i.e., replace ${templateKey}
with the encoded value) so the path segment is safely encoded when sending the
request.
In `@src/features/admin/alerttalk/ui/admin-alerttalk-pages.tsx`:
- Around line 845-849: The default 'now' value is computed with toISOString()
(UTC) which misaligns with the local `datetime-local` input; replace the `now`
assignment with a local-time formatter that produces "YYYY-MM-DDTHH:mm" using
Date#getFullYear, getMonth, getDate, getHours, and getMinutes with zero-padding
so the `at: at || now` passed into mutation.mutate uses local time; update the
`now` constant in admin-alerttalk-pages.tsx accordingly (referencing the
existing `now` variable and the mutation.mutate call).
In `@src/features/admin/course-management/model/admin-course-markdown.ts`:
- Around line 141-153: 현재 분기에서 shouldRenderMarkdownAsMarkdown(normalizedContent)
및 recoverMarkdownTextFromTextOnlyHtml(normalizedContent) 경로가
renderMarkdownToHtml(...) 결과를 바로 반환해 stripEditorBreakingAttributes(...)를 통과하지 않아
금지 속성이 남을 수 있습니다; renderMarkdownToHtml(normalizedContent) 와
renderMarkdownToHtml(recoveredMarkdown) 호출 결과를 반환하기 전에 반드시
stripEditorBreakingAttributes(...)로 정리하도록 변경하고, isHtmlContent(normalizedContent)
체크 후 반환되는 normalizedContent 경로도 동일한 정리(필요하면 적용)를 거치게 하세요.
In `@src/features/admin/course-management/model/admin-lesson-form-mapper.ts`:
- Around line 21-34: toAdminLessonHydrationSnapshot currently hardcodes
description to an empty string causing mismatch with the form initialiser (which
uses lesson.description ?? ''). Update the snapshot mapping in
toAdminLessonHydrationSnapshot so description uses the same rule
(lesson.description ?? '') and keep the rest of the fields (e.g. content via
normalizeAdminCourseMarkdownContent) unchanged so change detection stays
consistent.
---
Outside diff comments:
In `@src/app/`(landing)/class/[slug]/(learning)/feed/write/page.tsx:
- Around line 206-210: The back link currently always navigates to
`/class/${slug}/home?tab=feed` while the displayed text (backLabel) changes for
edit mode; update the link generation so the href matches the intent of
backLabel by reusing the same branch/logic that sets backLabel (e.g., the
edit-mode check or variable such as backLabel/backHref or isEditing) — ensure
the anchor's href is the detail-return path when in edit mode and the home feed
path otherwise so the visual label and navigation target are consistent.
In `@src/components/pages/class/payment/course-summary-section.tsx`:
- Around line 20-24: The Image is rendered whenever thumbnailUrl is truthy, but
unoptimized does not bypass next/image remotePatterns checks so unauthorized
hosts cause runtime errors; update the CourseSummarySection rendering logic that
checks thumbnailUrl to first validate the URL against your next.config.ts
images.remotePatterns (or an equivalent helper like
isRemotePatternAllowed(remotePatterns, url)) by parsing thumbnailUrl (new
URL(thumbnailUrl)) and matching host/path patterns, and then only render the
<Image ... src={thumbnailUrl} /> when the helper returns allowed; if not
allowed, render a safe placeholder or null to avoid the Invalid src prop error.
---
Nitpick comments:
In `@e2e/payment/cancel.spec.ts`:
- Around line 161-174: The test currently only checks the modal closed but not
that the cancel API wasn't called; before invoking page.getByRole('button', {
name: '결제 취소' }).first().click() use Playwright request interception (e.g.,
page.route or page.on('request')) to watch for POST requests to the cancel
endpoint (match '**/cancel' or '/cancel') and record a flag or fail immediately
if such a request occurs, then after clicking the '아니오' button assert that the
flag is false (or that no matching request was observed) so the test verifies
the cancel API was not called; update the test named '"아니오" 클릭 → 모달 닫힘, cancel
API 미호출' and use gotoPaymentManagement(page) as before.
In `@src/app/`(class-lesson)/class/[slug]/lesson/[id]/page.tsx:
- Around line 202-205: Replace the arbitrary Tailwind values used in the JSX
className attributes with project tokens from global.css: change max-w-[1236px]
in the container div (currently "mx-auto w-full max-w-[1236px] px-300") to the
corresponding max-width token (or add one in global.css if missing), replace
h-[calc(100vh-var(--spacing-800))] in the left column div ("min-w-0 flex-1 flex
flex-col h-[calc(100vh-var(--spacing-800))]") with a height token or a utility
that uses the existing --spacing-800 token, and swap min-h-[964px] (the other
occurrence referenced at 247-249) for a min-height token; if tokens do not exist
yet, add appropriately named custom properties/utilities in global.css (e.g.,
--container-max-w-1236, --lesson-left-height, --lesson-min-h-964) and then use
the corresponding classNames instead of arbitrary bracket values.
In `@src/hooks/queries/notification/use-notification-setting.ts`:
- Around line 39-43: In onSuccess for the notification setting mutation, calling
queryClient.invalidateQueries immediately after queryClient.setQueryData with
NOTIFICATION_SETTING_QUERY_KEY marks the freshly written cache as stale and
triggers an unnecessary refetch; remove the subsequent
queryClient.invalidateQueries call (or only call it conditionally when the
server requires a fresh fetch) so that setQueryData alone updates the cache
without an immediate background refetch.
🪄 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: 4f9f21e5-18aa-4015-b77a-3cfb2dbca78b
⛔ Files ignored due to path filters (11)
public/class/detail/audit/node-142-3114.pngis excluded by!**/*.pngpublic/class/detail/audit/node-210-2643.pngis excluded by!**/*.pngpublic/class/detail/audit/node-225-4596.pngis excluded by!**/*.pngpublic/class/detail/audit/node-306-2830.pngis excluded by!**/*.pngpublic/class/detail/audit/node-33-1925.pngis excluded by!**/*.pngpublic/class/detail/audit/node-771-23642.pngis excluded by!**/*.pngpublic/class/detail/audit/node-865-33747.pngis excluded by!**/*.pngpublic/class/detail/audit/node-865-34214.pngis excluded by!**/*.pngpublic/class/detail/class-detail-figma-ref.pngis excluded by!**/*.pngpublic/class/vibe-intro/feed-more-menu.pngis excluded by!**/*.pngpublic/class/vibe-intro/learning-map-modal-ref.pngis excluded by!**/*.png
📒 Files selected for processing (66)
docs/2026-03-15-login-fail-fix/AUTH_STAGED_CHANGE_BREAKDOWN.mddocs/2026-03-15-login-fail-fix/FRONTEND_OAUTH_REDIRECTION_CONTRACT.mddocs/2026-03-15-login-fail-fix/MIDDLEWARE_AUTH_REFACTORING_NEXT_STEPS.mddocs/2026-03-15-login-fail-fix/MIDDLEWARE_ROUTE_POLICY_GUIDE.mddocs/2026-03-15-login-fail-fix/PR_AUTH_REFACTORING_OVERVIEW.mddocs/2026-03-15-login-fail-fix/SOCIAL_LOGIN_REFACTORING_PLAN.mddocs/2026-03-26-markdown-editor/COMMON_MARKDOWN_EDITOR_USAGE.mddocs/auth-proposition/AUTH_LOGICAL_PROPOSITIONS_200.mddocs/auth-proposition/AUTH_PROPOSITION_121_150_AUDIT.mddocs/auth-proposition/AUTH_PROPOSITION_151_180_AUDIT.mddocs/auth-proposition/AUTH_PROPOSITION_181_200_AUDIT.mddocs/auth-proposition/AUTH_PROPOSITION_1_30_AUDIT.mddocs/auth-proposition/AUTH_PROPOSITION_31_60_AUDIT.mddocs/auth-proposition/AUTH_PROPOSITION_61_90_AUDIT.mddocs/auth-proposition/AUTH_PROPOSITION_91_120_AUDIT.mddocs/ops/onboarding.mddocs/ops/release-record-shared-contract.mddocs/ops/version-management.mde2e/class/builder-feed.spec.tse2e/class/payment-success.spec.tse2e/payment/cancel.spec.tse2e/payment/refund.spec.tse2e/support/study-helpers.tsskills_context/SHARED/zeroone-version-management.mdsrc/api/client/axios.tssrc/app/(admin)/admin/alerttalk/delivery-logs/[jobId]/page.tsxsrc/app/(admin)/admin/alerttalk/delivery-logs/page.tsxsrc/app/(admin)/admin/alerttalk/schedules/dry-run/page.tsxsrc/app/(admin)/admin/alerttalk/templates/page.tsxsrc/app/(class-lesson)/class/[slug]/lesson/[id]/_components/lesson-review-form.tsxsrc/app/(class-lesson)/class/[slug]/lesson/[id]/page.tsxsrc/app/(landing)/class/[slug]/(learning)/feed/write/page.tsxsrc/app/(landing)/class/[slug]/(learning)/payment/page.tsxsrc/app/(landing)/class/page.tsxsrc/app/(service)/(my)/my-class/_components/learning-notification-modal.tsxsrc/app/(service)/(my)/my-class/page.tsxsrc/app/global.csssrc/components/admin/courses/admin-course-detail-page-client.tsxsrc/components/admin/courses/admin-course-management-page-client.tsxsrc/components/admin/courses/admin-course-plan-management.tsxsrc/components/admin/courses/admin-lesson-detail-page-client.tsxsrc/components/admin/courses/admin-lesson-management-page-client.tsxsrc/components/admin/courses/admin-notion-zip-action-card.tsxsrc/components/admin/courses/admin-notion-zip-import-button.tsxsrc/components/common/layout/sidebar/admin-sidebar.tsxsrc/components/common/ui/editor/markdown-content.tsxsrc/components/common/ui/editor/markdown-editor.tsxsrc/components/common/ui/rich-text/markdown-content-core.tsxsrc/components/pages/class/payment/checkout-form.tsxsrc/components/pages/class/payment/course-summary-section.tsxsrc/components/pages/class/roadmap-tab.tsxsrc/features/admin/alerttalk/api/admin-alerttalk-api.tssrc/features/admin/alerttalk/model/admin-alerttalk-contract.tssrc/features/admin/alerttalk/model/use-admin-alerttalk-query.tssrc/features/admin/alerttalk/ui/admin-alerttalk-pages.tsxsrc/features/admin/course-management/api/admin-course-management-api.test.tssrc/features/admin/course-management/api/admin-course-management-api.tssrc/features/admin/course-management/model/admin-course-management-contract.tssrc/features/admin/course-management/model/admin-course-markdown.test.tssrc/features/admin/course-management/model/admin-course-markdown.tssrc/features/admin/course-management/model/admin-lesson-form-mapper.tssrc/features/admin/course-management/model/use-admin-course-management-query.tssrc/hooks/queries/notification/use-notification-setting.tssrc/types/api/course.types.tssrc/utils/markdown-rendering-utils.test.tssrc/utils/markdown-rendering-utils.ts
💤 Files with no reviewable changes (10)
- docs/2026-03-15-login-fail-fix/MIDDLEWARE_ROUTE_POLICY_GUIDE.md
- docs/2026-03-15-login-fail-fix/AUTH_STAGED_CHANGE_BREAKDOWN.md
- docs/2026-03-15-login-fail-fix/MIDDLEWARE_AUTH_REFACTORING_NEXT_STEPS.md
- src/app/(class-lesson)/class/[slug]/lesson/[id]/_components/lesson-review-form.tsx
- docs/2026-03-15-login-fail-fix/SOCIAL_LOGIN_REFACTORING_PLAN.md
- docs/2026-03-26-markdown-editor/COMMON_MARKDOWN_EDITOR_USAGE.md
- docs/2026-03-15-login-fail-fix/FRONTEND_OAUTH_REDIRECTION_CONTRACT.md
- docs/2026-03-15-login-fail-fix/PR_AUTH_REFACTORING_OVERVIEW.md
- src/app/global.css
- src/components/admin/courses/admin-course-management-page-client.tsx
요약
develop에 누적된 프론트 작업을main운영 배포 후보 브랜치로 병합합니다.release:minor운영/호환성 확인
origin/main확인:12a58142137dadmin/alerttalk존재 확인:AdminAlerttalkController에서/api/v5/admin/alerttalk및 templates, delivery-logs, dry-run, test-send 엔드포인트 확인.releases/삭제/스테일 기록 방지: 이번 병합에서 release YAML 삭제 없음.검증
yarn lint:fix✅ 0 errors / 257 warnings(existing)yarn prettier:fix✅ Biome format 완료; 기존.codex/skills/clarify.mdsymlink warning 1건yarn typecheck✅tsc --noEmitnode --check scripts/release/generate-prod-release-record.mjs✅node --check scripts/release/generate-backend-prod-release-record.mjs✅node --check scripts/release/resolve-prod-release-intent.mjs✅node scripts/release/validate-release-record.mjs releases✅Validated 21 release record(s).머지 전 체크
release:minorreleases/prod-*.yaml생성 및 production health 확인Summary by CodeRabbit
릴리즈 노트
새로운 기능
개선 사항
문서