Skip to content

release: develop 운영 배포 준비#682

Open
Hyeonjun0527 wants to merge 150 commits into
mainfrom
release/develop-to-main-20260523
Open

release: develop 운영 배포 준비#682
Hyeonjun0527 wants to merge 150 commits into
mainfrom
release/develop-to-main-20260523

Conversation

@Hyeonjun0527
Copy link
Copy Markdown
Member

@Hyeonjun0527 Hyeonjun0527 commented May 22, 2026

요약

  • develop에 누적된 프론트 작업을 main 운영 배포 후보 브랜치로 병합합니다.
  • 포함 범위: admin/alerttalk 화면, 관리자 코스/레슨 관리 개선, 결제/클래스/마크다운 렌더링 누적 변경, 운영 버전관리 문서/검증 스크립트 보존.
  • release intent: release:minor

운영/호환성 확인

  • 백엔드 origin/main 확인: 12a58142137d
  • admin/alerttalk 존재 확인: AdminAlerttalkController에서 /api/v5/admin/alerttalk 및 templates, delivery-logs, dry-run, test-send 엔드포인트 확인.
  • 최신 프론트 release record는 아직 기존 backend image/commit을 가리키므로, 실제 merge/deploy 전에는 운영 백엔드 상태가 위 API 세트를 포함하는지 또는 backend release record/current-state가 갱신됐는지 최종 확인 필요.
  • releases/ 삭제/스테일 기록 방지: 이번 병합에서 release YAML 삭제 없음.

검증

  • yarn lint:fix ✅ 0 errors / 257 warnings(existing)
  • yarn prettier:fix ✅ Biome format 완료; 기존 .codex/skills/clarify.md symlink warning 1건
  • yarn typechecktsc --noEmit
  • node --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 releasesValidated 21 release record(s).

머지 전 체크

  • required CI 전체 통과 확인
  • PR label이 정확히 1개 release intent인지 확인: release:minor
  • 운영 배포 시 백엔드 admin/alerttalk API 포함 상태 확인
  • deploy-prod 완료 후 신규 releases/prod-*.yaml 생성 및 production health 확인

Summary by CodeRabbit

릴리즈 노트

  • 새로운 기능

    • 관리자용 알림톡 관리 기능 추가: 템플릿 관리, 전송 로그 조회, 배치 검증
    • 과정 플랜 관리 기능 추가
    • Notion ZIP 파일을 통한 레슨 컨텐츠 대량 가져오기 지원
    • 결제 환불/취소 UI 및 기능 개선
    • 알림 설정 저장 중 상태 표시 개선
  • 개선 사항

    • 결제 준비 프로세스 간소화
    • 레슨 페이지 스크롤 영역 재구성
  • 문서

    • 운영 배포 및 버전 관리 문서 추가

Hyeonjun0527 and others added 30 commits May 16, 2026 22:12
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>
HA-SEUNG-JEONG and others added 19 commits May 22, 2026 13:04
결제 대기 페이지의 링크 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 추가로 높이 고정
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 상태와 연결합니다.
@vercel
Copy link
Copy Markdown

vercel Bot commented May 22, 2026

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

Project Deployment Actions Updated (UTC)
study-platform-client-dev Ready Ready Preview, Comment May 22, 2026 9:54pm

@Hyeonjun0527 Hyeonjun0527 added the release:minor Minor production release label May 22, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 22, 2026

Warning

Rate limit exceeded

@Hyeonjun0527 has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 1 minute and 49 seconds before requesting another review.

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 @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

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 configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 17a0aee9-4d76-4a2a-8db0-6b336f830b95

📥 Commits

Reviewing files that changed from the base of the PR and between 7fb3d43 and 3c3f279.

📒 Files selected for processing (18)
  • .github/workflows/deploy-prod.yml
  • docs/ops/release-record-shared-contract.md
  • docs/ops/version-management.md
  • e2e/group-study/create.spec.ts
  • e2e/payment/cancel.spec.ts
  • e2e/payment/refund.spec.ts
  • scripts/release/generate-backend-prod-release-record.mjs
  • scripts/release/resolve-prod-release-intent.mjs
  • scripts/release/validate-release-record.mjs
  • src/app/(admin)/admin/alerttalk/delivery-logs/[jobId]/page.tsx
  • src/app/(landing)/class/page.tsx
  • src/components/admin/courses/admin-course-plan-management.tsx
  • src/components/admin/courses/admin-notion-zip-action-card.tsx
  • src/features/admin/alerttalk/api/admin-alerttalk-api.ts
  • src/features/admin/alerttalk/ui/admin-alerttalk-pages.tsx
  • src/features/admin/course-management/model/admin-course-markdown.test.ts
  • src/features/admin/course-management/model/admin-course-markdown.ts
  • src/features/admin/course-management/model/admin-lesson-form-mapper.ts
📝 Walkthrough

Walkthrough

Large 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.

Changes

Operations & Release Management Documentation

Layer / File(s) Summary
Operations documentation and release management guides
docs/ops/onboarding.md, docs/ops/release-record-shared-contract.md, docs/ops/version-management.md, skills_context/SHARED/zeroone-version-management.md
Adds procedures for backend/frontend production deployment, defines shared payload contracts between systems for release tracking, specifies release ID/image tag rules, bootstrap mode handling, rollback procedures, and AI agent automation workflows for version management. Removes obsolete auth refactoring docs.

Admin Features & Course Management

Layer / File(s) Summary
Admin Alerttalk API & data contracts
src/features/admin/alerttalk/api/admin-alerttalk-api.ts, src/features/admin/alerttalk/model/admin-alerttalk-contract.ts
Introduces Alerttalk management API functions (template list/sync/test-send, delivery log list/detail/retry, schedule dry-run) using v5 axios with response unwrapping; defines status enums, request/response types for templates, delivery logs, retry operations.
Admin Alerttalk React Query integration
src/features/admin/alerttalk/model/use-admin-alerttalk-query.ts
Implements query hooks for templates/delivery-logs/log-details and mutation hooks for sync/test-send/retry/dry-run with cache invalidation patterns; adds error message helpers for Notion ZIP import by statusCode.
Admin Alerttalk UI pages
src/features/admin/alerttalk/ui/admin-alerttalk-pages.tsx
Four client-side pages: template approval management with filter/sync/test modals; delivery log list with filtering; delivery log detail with retry modal; schedule dry-run batch testing. Includes summary cards, data tables, and result previews.
Course plan management
src/components/admin/courses/admin-course-plan-management.tsx, src/components/admin/courses/admin-course-detail-page-client.tsx
New component for creating/editing/deactivating course plans with form validation, item parsing, datetime handling, number validation; integrates into course detail page edit mode.
Course plan API & queries
src/features/admin/course-management/api/admin-course-management-api.ts, src/features/admin/course-management/model/use-admin-course-management-query.ts, src/features/admin/course-management/model/admin-course-management-contract.ts
CRUD operations for course plans; updates course form contract to replace price fields with metadata fields; adds batch lesson import/update response types; implements React Query hooks with error handling.
Notion ZIP components & lesson management
src/components/admin/courses/admin-notion-zip-action-card.tsx, src/components/admin/courses/admin-notion-zip-import-button.tsx, src/components/admin/courses/admin-lesson-detail-page-client.tsx, src/components/admin/courses/admin-lesson-management-page-client.tsx
UI components for ZIP file selection; lesson management page with per-lesson dirty-state tracking, bulk save handler, draft hydration from server/localStorage/in-memory forms; detail page with single-lesson ZIP import for content replacement.
Lesson form mapping utilities
src/features/admin/course-management/model/admin-lesson-form-mapper.ts, src/features/admin/course-management/model/admin-course-markdown.ts, src/features/admin/course-management/model/admin-course-markdown.test.ts
Mappers convert lesson detail responses to form values with content normalization; utilities for HTML attribute stripping with SSR-compatible regex implementation; test coverage for markdown normalization edge cases.

Frontend Features: Payment, Notifications, Content & Testing

Layer / File(s) Summary
Markdown rendering improvements
src/utils/markdown-rendering-utils.ts, src/utils/markdown-rendering-utils.test.ts, src/components/common/ui/editor/markdown-content.tsx, src/components/common/ui/rich-text/markdown-content-core.tsx
New HTML/markdown classification function distinguishing renderable markdown from HTML; updates rendering pipeline to apply normalization, removes emoticon replacement, rejects emoticon URIs in sanitization config; adds test coverage for code fence and HTML detection.
Payment checkout refactoring
src/components/pages/class/payment/checkout-form.tsx, src/app/(landing)/class/[slug]/(learning)/payment/page.tsx, src/types/api/course.types.ts
Moves payment preparation into checkout form component calling mutateAsync internally with courseId; removes external paymentData dependency; extends CoursePaymentPrepareRequest with buyer info fields; page simplifies to course detail query.
Payment page UI & course card improvements
src/components/pages/class/payment/course-summary-section.tsx, src/app/(landing)/class/page.tsx, src/components/pages/class/roadmap-tab.tsx
Removes image host whitelist validation; CourseCard displays learner label without leading digits; RoadmapTab uses slug-based dynamic SVG paths instead of static paths.
Notification settings enhancement
src/hooks/queries/notification/use-notification-setting.ts, src/app/(service)/(my)/my-class/page.tsx, src/app/(service)/(my)/my-class/_components/learning-notification-modal.tsx
Exports notification setting interfaces and query key constant; mutation syncs cache before invalidation; modal displays loading/error states before time values; form updates pass hour/minute with proper type checking.
Lesson page layout refactoring
src/app/(class-lesson)/class/[slug]/lesson/[id]/page.tsx
Restructures page layout with fixed header/tabs and left-column container scroll; places lesson content and review form inside scrollable container; keeps right sidebar fixed and independent.
E2E payment flow tests
e2e/class/payment-success.spec.ts, e2e/payment/cancel.spec.ts, e2e/payment/refund.spec.ts
Comprehensive test suites for payment success scenarios (card/virtual account/error), cancellation flow across payment states, and refund request UX; all use auth cookie injection and API response mocking.
E2E feed test simplification
e2e/class/builder-feed.spec.ts, e2e/support/study-helpers.ts
Simplifies feed mocking from pathname-based routing to URL string matching; removes networkidle waits; adds member profile mocking for study creation flows; adjusts element visibility timeouts.
Admin sidebar & routing
src/components/common/layout/sidebar/admin-sidebar.tsx, src/app/(admin)/admin/alerttalk/templates/page.tsx, src/app/(admin)/admin/alerttalk/delivery-logs/page.tsx, src/app/(admin)/admin/alerttalk/delivery-logs/[jobId]/page.tsx, src/app/(admin)/admin/alerttalk/schedules/dry-run/page.tsx
Adds alerttalk navigation to admin sidebar; creates four new page routes for alerttalk features delegating to client components.
API infrastructure
src/api/client/axios.ts, src/features/admin/course-management/api/admin-course-management-api.test.ts
Multipart v5 axios instance for file uploads with same auth interceptors; extends API tests to verify FormData field mapping for single/batch Notion ZIP imports.
Minor fixes & cleanup
src/app/(landing)/class/[slug]/(learning)/feed/write/page.tsx, src/app/(class-lesson)/class/[slug]/lesson/[id]/_components/lesson-review-form.tsx, src/app/global.css
Feed write page back link always navigates to feed tab; removes backend constraint comment from lesson review; removes disabled-state cursor styling.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

Poem

🐰 Admin pages bloom like spring clover,
Alerts and plans now under cover!
Markdown renders, payments flow free,
Lessons batch-save with harmony 🎯
From ops docs to test suites so bright,
ZERO-ONE ships features just right! ✨

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch release/develop-to-main-20260523

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

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.tsimages.remotePatterns 허용목록 검증을 우회하지 않습니다. 허용되지 않은 도메인/경로면 next/image에서 Invalid src prop(400) 런타임 에러로 이미지가 깨질 수 있습니다. next.config.tsremotePatterns에 맞춰 공통으로 호스트/경로를 사전 검증한 뒤, 미허용이면 렌더링을 중단(또는 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 from global.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

📥 Commits

Reviewing files that changed from the base of the PR and between fae5d19 and 7fb3d43.

⛔ Files ignored due to path filters (11)
  • public/class/detail/audit/node-142-3114.png is excluded by !**/*.png
  • public/class/detail/audit/node-210-2643.png is excluded by !**/*.png
  • public/class/detail/audit/node-225-4596.png is excluded by !**/*.png
  • public/class/detail/audit/node-306-2830.png is excluded by !**/*.png
  • public/class/detail/audit/node-33-1925.png is excluded by !**/*.png
  • public/class/detail/audit/node-771-23642.png is excluded by !**/*.png
  • public/class/detail/audit/node-865-33747.png is excluded by !**/*.png
  • public/class/detail/audit/node-865-34214.png is excluded by !**/*.png
  • public/class/detail/class-detail-figma-ref.png is excluded by !**/*.png
  • public/class/vibe-intro/feed-more-menu.png is excluded by !**/*.png
  • public/class/vibe-intro/learning-map-modal-ref.png is excluded by !**/*.png
📒 Files selected for processing (66)
  • docs/2026-03-15-login-fail-fix/AUTH_STAGED_CHANGE_BREAKDOWN.md
  • docs/2026-03-15-login-fail-fix/FRONTEND_OAUTH_REDIRECTION_CONTRACT.md
  • docs/2026-03-15-login-fail-fix/MIDDLEWARE_AUTH_REFACTORING_NEXT_STEPS.md
  • docs/2026-03-15-login-fail-fix/MIDDLEWARE_ROUTE_POLICY_GUIDE.md
  • docs/2026-03-15-login-fail-fix/PR_AUTH_REFACTORING_OVERVIEW.md
  • docs/2026-03-15-login-fail-fix/SOCIAL_LOGIN_REFACTORING_PLAN.md
  • docs/2026-03-26-markdown-editor/COMMON_MARKDOWN_EDITOR_USAGE.md
  • docs/auth-proposition/AUTH_LOGICAL_PROPOSITIONS_200.md
  • docs/auth-proposition/AUTH_PROPOSITION_121_150_AUDIT.md
  • docs/auth-proposition/AUTH_PROPOSITION_151_180_AUDIT.md
  • docs/auth-proposition/AUTH_PROPOSITION_181_200_AUDIT.md
  • docs/auth-proposition/AUTH_PROPOSITION_1_30_AUDIT.md
  • docs/auth-proposition/AUTH_PROPOSITION_31_60_AUDIT.md
  • docs/auth-proposition/AUTH_PROPOSITION_61_90_AUDIT.md
  • docs/auth-proposition/AUTH_PROPOSITION_91_120_AUDIT.md
  • docs/ops/onboarding.md
  • docs/ops/release-record-shared-contract.md
  • docs/ops/version-management.md
  • e2e/class/builder-feed.spec.ts
  • e2e/class/payment-success.spec.ts
  • e2e/payment/cancel.spec.ts
  • e2e/payment/refund.spec.ts
  • e2e/support/study-helpers.ts
  • skills_context/SHARED/zeroone-version-management.md
  • src/api/client/axios.ts
  • src/app/(admin)/admin/alerttalk/delivery-logs/[jobId]/page.tsx
  • src/app/(admin)/admin/alerttalk/delivery-logs/page.tsx
  • src/app/(admin)/admin/alerttalk/schedules/dry-run/page.tsx
  • src/app/(admin)/admin/alerttalk/templates/page.tsx
  • src/app/(class-lesson)/class/[slug]/lesson/[id]/_components/lesson-review-form.tsx
  • src/app/(class-lesson)/class/[slug]/lesson/[id]/page.tsx
  • src/app/(landing)/class/[slug]/(learning)/feed/write/page.tsx
  • src/app/(landing)/class/[slug]/(learning)/payment/page.tsx
  • src/app/(landing)/class/page.tsx
  • src/app/(service)/(my)/my-class/_components/learning-notification-modal.tsx
  • src/app/(service)/(my)/my-class/page.tsx
  • src/app/global.css
  • src/components/admin/courses/admin-course-detail-page-client.tsx
  • src/components/admin/courses/admin-course-management-page-client.tsx
  • src/components/admin/courses/admin-course-plan-management.tsx
  • src/components/admin/courses/admin-lesson-detail-page-client.tsx
  • src/components/admin/courses/admin-lesson-management-page-client.tsx
  • src/components/admin/courses/admin-notion-zip-action-card.tsx
  • src/components/admin/courses/admin-notion-zip-import-button.tsx
  • src/components/common/layout/sidebar/admin-sidebar.tsx
  • src/components/common/ui/editor/markdown-content.tsx
  • src/components/common/ui/editor/markdown-editor.tsx
  • src/components/common/ui/rich-text/markdown-content-core.tsx
  • src/components/pages/class/payment/checkout-form.tsx
  • src/components/pages/class/payment/course-summary-section.tsx
  • src/components/pages/class/roadmap-tab.tsx
  • src/features/admin/alerttalk/api/admin-alerttalk-api.ts
  • src/features/admin/alerttalk/model/admin-alerttalk-contract.ts
  • src/features/admin/alerttalk/model/use-admin-alerttalk-query.ts
  • src/features/admin/alerttalk/ui/admin-alerttalk-pages.tsx
  • src/features/admin/course-management/api/admin-course-management-api.test.ts
  • src/features/admin/course-management/api/admin-course-management-api.ts
  • src/features/admin/course-management/model/admin-course-management-contract.ts
  • src/features/admin/course-management/model/admin-course-markdown.test.ts
  • src/features/admin/course-management/model/admin-course-markdown.ts
  • src/features/admin/course-management/model/admin-lesson-form-mapper.ts
  • src/features/admin/course-management/model/use-admin-course-management-query.ts
  • src/hooks/queries/notification/use-notification-setting.ts
  • src/types/api/course.types.ts
  • src/utils/markdown-rendering-utils.test.ts
  • src/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

Comment thread docs/ops/release-record-shared-contract.md
Comment thread src/app/(admin)/admin/alerttalk/delivery-logs/[jobId]/page.tsx Outdated
Comment thread src/app/(landing)/class/page.tsx Outdated
Comment thread src/components/admin/courses/admin-course-plan-management.tsx Outdated
Comment thread src/components/admin/courses/admin-course-plan-management.tsx Outdated
Comment thread src/components/admin/courses/admin-notion-zip-action-card.tsx Outdated
Comment thread src/features/admin/alerttalk/api/admin-alerttalk-api.ts
Comment thread src/features/admin/alerttalk/ui/admin-alerttalk-pages.tsx Outdated
Comment thread src/features/admin/course-management/model/admin-course-markdown.ts
@Hyeonjun0527 Hyeonjun0527 self-assigned this May 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

release:minor Minor production release

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants