Skip to content

fix: 사건 생성 플로우 개선 및 UI/성능 수정 - #107

Merged
evenif99 merged 7 commits into
devfrom
fix/ai-prompt-ja
Jun 22, 2026
Merged

fix: 사건 생성 플로우 개선 및 UI/성능 수정#107
evenif99 merged 7 commits into
devfrom
fix/ai-prompt-ja

Conversation

@juahcheon

@juahcheon juahcheon commented Jun 22, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • 홈에서 방 생성만 하고, /rooms/[roomId]/statement 페이지에서 진술 작성 후 POST /api/disputes 호출로 플로우 분리
  • POST /api/disputes에서 AI 추출로 제목 자동 설정 (새 사건 하드코딩 제거)
  • RSC 성능 개선: getCachedSessiongetServerSession(DB 조회) -> JWT decode(로컬 파싱)로 교체
  • 중복 API 호출 제거: JudgmentResult, JudgmentTypeResult에서 각각 useJudgment 호출하던 것을 page에서 단일 호출 후 prop 전달
  • dispute.status === 'closed' 진입 시 판결 결과 없음 모달 오작동 수정 (isJudged 조건 사용)

Changes

  • src/app/(page)/rooms/[roomId]/statement/page.tsx 신규 사건 작성 페이지
    • 취소: keepalive fetch로 방 삭제 후 즉시 홈 이동 (await 제거로 3s 블로킹 해소)
    • 진술저장: spinner 오버레이 + 멘트 순환 (나쁜 말 검열 중 -> 내용 저장 중 -> AI 분석 중 -> 조금만 기다려주세요)
  • src/app/api/disputes/route.ts: content 파라미터 추가, 모더레이션 + AI 추출 병렬 처리, 트랜잭션으로 dispute + participant + statement 원자적 생성
  • src/components/home/NewCaseButton.tsx: POST /api/disputes 제거, 방 생성 후 statement 페이지로 이동
  • src/app/(page)/disputes/[id]/page.tsx: 판결 중 멘트 순환 추가 (5개, 2.5s 간격)
  • src/app/(page)/disputes/[id]/DisputePage.module.scss: statementCardEditable hover border 제거
  • src/lib/auth/getSession.ts: JWT decode 기반으로 교체

Test plan

  • 홈 -> 새 사건 -> 카테고리 선택 -> /rooms/[roomId]/statement 진입 확인
  • 취소 버튼 클릭 시 방 삭제 + 즉시 홈 이동 확인
  • 진술저장 클릭 시 spinner 멘트 순환 확인
  • 진술저장 완료 후 /disputes/[id] 이동 확인
  • 판결받기 클릭 시 멘트 순환 확인
  • status === closed 사건 진입 시 판결 결과 없음 모달 미노출 확인
  • 사건 카드 hover 시 border 색상 변경 없음 확인

Generated with Claude Code

Summary by CodeRabbit

Release Notes

  • New Features

    • 사건작성 전용 페이지 추가(카테고리 선택, MBTI 선택, 내용 입력)
    • 판결 진행 중 단계별 문구가 순차적으로 교체되는 로딩 메시지 제공
  • Improvements

    • 판결 결과 표시가 더 안정적으로 업데이트되며, AI 기반 인사이트 노출 방식이 개선됨
    • 개인 정보 포함 가능성 감지 시 경고 모달로 사전 안내
    • 사건 생성 흐름이 간소화되어 방 생성 후 바로 작성 화면으로 이동
  • UI/UX

    • 사건작성 화면에 모달/저장 중 화면 등 스타일 전반 추가
    • 카드 hover 시 테두리 표시 동작이 조정됨

juahcheon and others added 5 commits June 22, 2026 15:02
- aFault/bFault에서 'A가/B가' 알파벳 지칭 제거, 실제 사용자 이름 사용
- mbtiNote 별도 필드 제거, aFault/bFault 내용에 MBTI 인사이트 통합
- judge route에서 user nickname/name 조회 후 프롬프트에 전달

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- detailCode 영어 원문 의미 기준으로 구체적 유형 선택하도록 지시 추가
- expectation_mismatch 등 포괄적 유형은 최후 수단으로만 선택하도록 제한
- SOLO/DUO 프롬프트 모두 동일하게 적용

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- JudgmentResult, JudgmentTypeResult에서 useJudgment 제거, page.tsx에서 props로 전달
- 탭 전환 시 불필요한 API 재호출 방지
- closed 상태에서 판결 결과 없음 모달 버그 수정 (isJudged 조건 적용)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- 홈에서 방 생성만 하고, 진술저장 클릭 시 dispute 생성 + AI 추출 원자적 처리
- POST /api/disputes: title 하드코딩 제거, content 받아 AI 추출 후 제목 자동 설정
- /rooms/[roomId]/statement 페이지 신규 추가 (취소/진술저장 버튼)
- 취소 버튼: keepalive fetch로 방 삭제 후 즉시 홈 이동 (3s 블로킹 제거)
- 진술저장 중 spinner 오버레이 + 멘트 순환 표시
- 판결받기 중 멘트 단일→순환 (5개, 2.5s 간격)
- statementCardEditable hover 시 border 색상 변경 제거
- getCachedSession: getServerSession → JWT decode로 교체 (DB 조회 제거)
- JudgmentResult/JudgmentTypeResult: useJudgment 중복 호출 → prop으로 수신
- dispute status judged 일 때만 judgment fetch 활성화 (closed 오작동 수정)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@vercel

vercel Bot commented Jun 22, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
talky-owl Ready Ready Preview, Comment Jun 22, 2026 8:42am

@coderabbitai

coderabbitai Bot commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: 0eb9a422-cb18-40b5-b40a-b1515908149d

📥 Commits

Reviewing files that changed from the base of the PR and between 32ff0fa and 7d854b1.

📒 Files selected for processing (5)
  • src/app/api/disputes/[id]/judge/route.ts
  • src/app/api/disputes/route.ts
  • src/components/judgement/JudgmentResult.tsx
  • src/lib/ai/judgment.ts
  • src/lib/auth/getSession.ts

📝 Walkthrough

Walkthrough

사건 작성 흐름을 NewCaseButton → StatementPage(신규) 경로로 재라우팅하고, POST /api/disputes에 AI 메타 추출·모더레이션 파이프라인을 추가했다. AI 판결 스키마에서 mbtiNote를 제거하고 nameA/nameB 기반 치환을 도입했으며, JudgmentResult·JudgmentTypeResult를 props 직접 수신 방식으로 전환했다. getCachedSession도 JWT 직접 디코딩 방식으로 교체됐다.

Changes

Statement 제출 및 AI 판결 파이프라인 전면 개편

Layer / File(s) Summary
AI 판결 입력·출력 스키마 및 프롬프트 개편
src/lib/ai/judgment.ts
JudgmentResult에서 mbtiNote가 제거되어 aFault/bFault/aSuggestedLine/bSuggestedLine 중심으로 재편됐다. 솔로·듀오 프롬프트 템플릿에서 하드코딩 알파벳 지칭이 {nameA}/{nameB} 치환 방식으로 변경되고, 반환 매핑에서 mbtiNote 관련 로직이 제거됐다.
POST /api/disputes AI 메타 추출·모더레이션 파이프라인
src/app/api/disputes/route.ts
createDisputeSchemacontent 필수 검증으로 변경됐다. moderateContentextractDisputeMetaPromise.allSettled로 병렬 실행하고 타임아웃/파싱/일반 실패를 세분화된 에러 코드로 매핑한다. 트랜잭션에서 statusWAITING_OPPONENT, moderationStatus·moderationLog가 조건부 생성되며, 응답에 hasPersonalInfo201이 추가됐다.
judge route 참가자 이름 전달
src/app/api/disputes/[id]/judge/route.ts
prisma.user.findMany select에 nickname·name이 추가됐다. 조회 결과를 userMap으로 구성해 참가자 A/B의 nickname 또는 name으로 nameA/nameB를 계산해 generateAiJudgment에 전달하며, rawResponseundefined로 고정됐다.
신규 StatementPage 구현 및 라우팅 변경
src/app/(page)/rooms/[roomId]/statement/page.tsx, src/app/(page)/rooms/[roomId]/statement/StatementPage.module.scss, src/components/home/NewCaseButton.tsx
rooms/[roomId]/statement/page.tsx가 신규 추가됐다. category/MBTI 초기화, 저장 메시지 로테이션, handleCancel/handleSave 핸들러, AI 에러 코드 분기, 개인정보 감지 모달 분기를 구현한다. StatementPage.module.scss는 페이지·카테고리·모달·저장 화면 스타일을 정의하고, NewCaseButton은 방 생성 후 statement 페이지로 바로 라우팅하도록 변경됐다.
JudgmentResult·JudgmentTypeResult props 기반 전환 및 DisputePage 판결 UI 갱신
src/components/judgement/JudgmentResult.tsx, src/components/judgement/JudgmentTypeResult.tsx, src/app/(page)/disputes/[id]/page.tsx, src/app/(page)/disputes/[id]/DisputePage.module.scss
JudgmentResult·JudgmentTypeResultdisputeId 훅 호출에서 judgment·participants 직접 수신 방식으로 변경됐다. DisputePage에서 JUDGING_MESSAGES 배열 로테이션, 판결 fetch 조건을 judged 단일 상태로 한정, judgment 미존재 시 빈 결과 문구 분기가 추가됐다. &:hover 스타일이 제거됐다.
getCachedSession JWT 직접 디코딩 방식 교체
src/lib/auth/getSession.ts
getCachedSessiongetServerSession(authOptions) 위임에서 세션 쿠키 직접 읽기 + decode 검증 방식으로 재작성됐다. 토큰 없거나 디코딩 실패 시 null을 반환한다.

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant NewCaseButton
  participant RoomsAPI as POST /api/rooms
  participant StatementPage
  participant DisputesAPI as POST /api/disputes
  participant extractDisputeMeta
  participant moderateContent
  participant Router

  User->>NewCaseButton: 카테고리 선택
  NewCaseButton->>RoomsAPI: POST /api/rooms
  RoomsAPI-->>NewCaseButton: { roomId }
  NewCaseButton->>Router: /rooms/:roomId/statement?category=...

  User->>StatementPage: 진술 작성 후 저장
  StatementPage->>DisputesAPI: POST { roomId, categoryGroup, content }
  DisputesAPI->>moderateContent: content 모더레이션 (병렬)
  DisputesAPI->>extractDisputeMeta: title/summary 추출 (타임아웃 병렬)
  moderateContent-->>DisputesAPI: 결과 (fail open)
  extractDisputeMeta-->>DisputesAPI: 메타 or 에러코드
  DisputesAPI-->>StatementPage: 201 { dispute, hasPersonalInfo }
  alt hasPersonalInfo
    StatementPage->>User: 개인정보 경고 모달
  else
    StatementPage->>Router: /disputes/:id
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • I5-Project/TALKY-OWL#73: NewCaseButton에서 방 생성 후 statement 페이지로 이동하는 라우팅 흐름을 도입한 PR으로, 이번 PR의 StatementPage 신규 구현 및 NewCaseButton 라우팅 변경과 직접 연결됩니다.
  • I5-Project/TALKY-OWL#96: src/lib/ai/judgment.ts의 AI 판결 프롬프트·JSON 스키마를 함께 수정하는 PR으로, mbtiNote 제거 및 판결 결과 필드 재편 내용이 이번 PR과 직접 맞닿아 있습니다.
  • I5-Project/TALKY-OWL#104: JudgmentResult/JudgmentTypeResult의 참가자 이름·아바타 렌더링을 수정한 PR으로, 이번 PR의 두 컴포넌트 props 기반 전환(judgment·participants 직접 수신)과 코드 레벨에서 직접 연관됩니다.

Suggested reviewers

  • evenif99

Poem

🐰 토끼가 진술서를 꼭 쥐고 뛰어가네,
AI가 문구를 {nameA}로 바꿔 말하고,
모더레이션 병렬로 쌩쌩 달리며,
mbtiNote는 풀밭에 남겨두고 훌쩍!
판결 결과는 props로 뚝딱 전달되니,
🥕 당근처럼 201 응답이 달콤하구나~

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 27.27% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ❓ Inconclusive PR 설명에서 주요 변경사항과 테스트 계획이 있으나, 필수 템플릿의 여러 섹션이 누락되었습니다. 작업 내용, 담당 영역, 관련 Issue, 변경사항, 테스트 결과 체크리스트, 작업 범위/보안/DB 변경 확인, 스크린샷 등 템플릿 섹션을 완성해 주세요.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed 제목이 사건 생성 플로우 개선과 UI/성능 수정을 포함하는 주요 변경사항을 종합적으로 설명하고 있습니다.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/ai-prompt-ja

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 9

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/app/api/disputes/route.ts (1)

12-18: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

서버에서도 진술 공백과 길이를 제한하세요.

현재 min(1)은 공백 문자열과 과도하게 큰 본문을 통과시켜 Gemini 호출/DB 저장 비용을 키울 수 있습니다. 페이지의 Textarea가 1000자로 제한되므로 API 스키마도 같은 상한을 강제하는 편이 안전합니다.

수정 예시
+const MAX_STATEMENT_LENGTH = 1000
+
 const createDisputeSchema = z.object({
   roomId: z.string().uuid('유효하지 않은 방 ID입니다.'),
   categoryGroup: z.enum(VALID_CATEGORY_GROUPS, {
     errorMap: () => ({ message: '카테고리는 romance, family, friend, work 중 하나여야 합니다.' }),
   }),
-  content: z.string().min(1, '진술 내용을 입력해주세요.'),
+  content: z
+    .string()
+    .trim()
+    .min(1, '진술 내용을 입력해주세요.')
+    .max(MAX_STATEMENT_LENGTH, `진술 내용은 ${MAX_STATEMENT_LENGTH}자 이내로 입력해주세요.`),
   sourceConversationId: z.string().uuid('유효하지 않은 대화 ID입니다.').optional(),
 })
🤖 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/api/disputes/route.ts` around lines 12 - 18, The content field
validation in createDisputeSchema currently only enforces a minimum length of 1
character, which allows whitespace-only strings and excessively long content to
pass through, potentially increasing costs for Gemini API calls and database
storage. Update the content field validation to both trim whitespace (to reject
strings with only spaces) and enforce a maximum length of 1000 characters to
match the UI constraints, ensuring consistent validation between client and
server.
🤖 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/app/`(page)/disputes/[id]/page.tsx:
- Around line 115-123: The condition determining when to fetch judgment data
only checks for `dispute.status === 'judged'`, but the judgment tab should be
visible and data should be fetched for both `judged` and `closed` dispute
statuses. Update the `isJudged` condition to include both completed states so
that the `useJudgment` hook is triggered whenever the dispute status is either
`judged` or `closed`, not just when it is `judged` alone.

In `@src/app/`(page)/rooms/[roomId]/statement/page.tsx:
- Around line 87-91: The POST request to /api/disputes endpoint in the fetch
call is missing the MBTI value in the request body. Identify where the MBTI
state or input value is being captured in the component, and add it to the JSON
body being sent alongside the existing roomId, categoryGroup (from category),
and content properties. This fix should be applied consistently at all locations
where the /api/disputes POST request is made (including the location at lines
155-160 mentioned in the comment).

In `@src/app/`(page)/rooms/[roomId]/statement/StatementPage.module.scss:
- Around line 55-58: The stylelint rule `declaration-empty-line-before` requires
a blank line before property declarations that follow an `@include` statement.
In the .categoryLabel class, add a blank line between the `@include
m.text-caption;` statement and the `font-weight` property declaration. Apply the
same fix to the similar issue mentioned at lines 104-106 where an `@include`
statement should be separated from following properties by a blank line.

In `@src/app/api/disputes/route.ts`:
- Around line 187-190: The duplicate check for disputes using findFirst on
roomId (around line 189) is separated from the actual dispute creation logic
(around lines 270-281), creating a race condition where concurrent requests with
the same roomId could both pass validation and create duplicates. To fix this,
wrap the entire duplicate check and creation flow in a serialized Prisma
transaction to make it atomic, or alternatively add a unique constraint on the
roomId column in the dispute table and use upsert instead of the separate
findFirst check followed by create, then handle the P2002 unique constraint
violation error appropriately to ensure idempotency and prevent duplicate
dispute records.
- Around line 212-221: The moderateContent call in the Promise.allSettled array
lacks a timeout wrapper, unlike extractDisputeMeta which is protected by a
Promise.race with a META_TIMEOUT_MS timeout. Apply the same timeout pattern to
the moderateContent call by wrapping it with Promise.race and a timeout promise,
ensuring both calls have consistent timeout protection. Consider creating a
common timeout helper function to wrap both moderateContent and
extractDisputeMeta to avoid duplication and to ensure proper cleanup of timers
on successful completion to prevent memory leaks.
- Around line 224-239: The moderation failure and blocking logs are not
following security guidelines. For the console.error call in the
moderationResult.status !== 'fulfilled' branch, replace logging the full
moderationResult.reason with only safe metadata like confidenceScore and
modelName that won't expose original text or personal information. Additionally,
add a logging statement before the return in the moderation.isBlocked branch to
record the blocking action with the same safe metadata approach, ensuring that
original content and personal data are never logged as per the coding
guidelines.

In `@src/lib/ai/judgment.ts`:
- Around line 234-252: The nameA and nameB variables are assigned default values
of 'A' and 'B' when not provided, which conflicts with an instruction
prohibiting alphabet references in the prompt. Additionally, if the input names
contain template placeholders like {statementA} or newline characters, the
subsequent replaceAll() calls will corrupt the prompt structure. Normalize nameA
and nameB by sanitizing them to remove or escape newlines and template
placeholders before they are used in the replaceAll() operations with
JUDGMENT_PROMPT_SOLO and JUDGMENT_PROMPT_DUO.

In `@src/lib/auth/getSession.ts`:
- Line 14: The decode function call in getSession.ts uses a non-null assertion
on process.env.NEXTAUTH_SECRET which can lead to unpredictable runtime errors if
the environment variable is not set. Add explicit validation before calling
decode to check if NEXTAUTH_SECRET exists, and throw a clear, descriptive error
immediately if it is not defined. This provides fail-fast behavior with a
meaningful error message instead of relying on the non-null assertion operator.
- Around line 17-25: The user.id property in the Session object returned from
the getSession function is being set to decoded.sub without validation, which
results in undefined when the JWT sub claim is missing. Add validation before
constructing the return object to ensure decoded.sub exists and is not
undefined; if the sub claim is missing, the function should either throw an
error or return early to prevent downstream code from receiving a Session with
an undefined user.id that could cause unexpected behavior in authorization
checks and database queries.

---

Outside diff comments:
In `@src/app/api/disputes/route.ts`:
- Around line 12-18: The content field validation in createDisputeSchema
currently only enforces a minimum length of 1 character, which allows
whitespace-only strings and excessively long content to pass through,
potentially increasing costs for Gemini API calls and database storage. Update
the content field validation to both trim whitespace (to reject strings with
only spaces) and enforce a maximum length of 1000 characters to match the UI
constraints, ensuring consistent validation between client and server.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: d6c7797e-dc38-4a2d-ae2f-254d603e68cf

📥 Commits

Reviewing files that changed from the base of the PR and between b35583b and 32ff0fa.

📒 Files selected for processing (11)
  • src/app/(page)/disputes/[id]/DisputePage.module.scss
  • src/app/(page)/disputes/[id]/page.tsx
  • src/app/(page)/rooms/[roomId]/statement/StatementPage.module.scss
  • src/app/(page)/rooms/[roomId]/statement/page.tsx
  • src/app/api/disputes/[id]/judge/route.ts
  • src/app/api/disputes/route.ts
  • src/components/home/NewCaseButton.tsx
  • src/components/judgement/JudgmentResult.tsx
  • src/components/judgement/JudgmentTypeResult.tsx
  • src/lib/ai/judgment.ts
  • src/lib/auth/getSession.ts
💤 Files with no reviewable changes (1)
  • src/app/(page)/disputes/[id]/DisputePage.module.scss

Comment on lines 115 to +123
// 판결 완료/종료 상태일 때만 fetch — 불필요한 API 호출 방지
const isJudged = !!dispute && dispute.status === 'judged';

const {
data: judgment,
isLoading: judgmentLoading,
isError: judgmentError,
error: judgmentErrorData,
} = useJudgment(id, isCompleted);
} = useJudgment(id, isJudged);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

closed 상태에서 판결 결과 조회가 막히는 회귀가 있습니다.

판결 탭은 judged/closed 모두에서 노출되는데, 현재 조회 조건이 status === 'judged'로 제한되어 closed 사건은 useJudgment가 실행되지 않습니다. 완료 상태 전체와 조회 조건을 일치시켜야 합니다.

제안 패치 예시
- // 판결 완료/종료 상태일 때만 fetch — 불필요한 API 호출 방지
- const isJudged = !!dispute && dispute.status === 'judged';
+ // 판결 완료/종료 상태에서 결과 조회
+ const shouldFetchJudgment =
+   !!dispute && (dispute.status === 'judged' || dispute.status === 'closed');

 ...
- } = useJudgment(id, isJudged);
+ } = useJudgment(id, shouldFetchJudgment);
📝 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
// 판결 완료/종료 상태일 때만 fetch — 불필요한 API 호출 방지
const isJudged = !!dispute && dispute.status === 'judged';
const {
data: judgment,
isLoading: judgmentLoading,
isError: judgmentError,
error: judgmentErrorData,
} = useJudgment(id, isCompleted);
} = useJudgment(id, isJudged);
// 판결 완료/종료 상태에서 결과 조회
const shouldFetchJudgment =
!!dispute && (dispute.status === 'judged' || dispute.status === 'closed');
const {
data: judgment,
isLoading: judgmentLoading,
isError: judgmentError,
error: judgmentErrorData,
} = useJudgment(id, shouldFetchJudgment);
🤖 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/`(page)/disputes/[id]/page.tsx around lines 115 - 123, The condition
determining when to fetch judgment data only checks for `dispute.status ===
'judged'`, but the judgment tab should be visible and data should be fetched for
both `judged` and `closed` dispute statuses. Update the `isJudged` condition to
include both completed states so that the `useJudgment` hook is triggered
whenever the dispute status is either `judged` or `closed`, not just when it is
`judged` alone.

Comment on lines +87 to +91
const res = await fetch('/api/disputes', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ roomId, categoryGroup: category, content }),
})

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

MBTI 선택값이 저장 요청에 반영되지 않습니다.

MBTI를 수정할 수 있는 UI가 있는데 POST /api/disputes 요청 본문에는 mbti가 없어 사용자 입력이 저장되지 않습니다. 저장 대상이면 API 스키마와 함께 요청에 포함하고, 저장 대상이 아니면 이 입력 UI를 제거하는 편이 맞습니다.

제안 패치 예시
- body: JSON.stringify({ roomId, categoryGroup: category, content }),
+ body: JSON.stringify({
+   roomId,
+   categoryGroup: category,
+   content,
+   mbti: mbti || null,
+ }),

Also applies to: 155-160

🤖 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/`(page)/rooms/[roomId]/statement/page.tsx around lines 87 - 91, The
POST request to /api/disputes endpoint in the fetch call is missing the MBTI
value in the request body. Identify where the MBTI state or input value is being
captured in the component, and add it to the JSON body being sent alongside the
existing roomId, categoryGroup (from category), and content properties. This fix
should be applied consistently at all locations where the /api/disputes POST
request is made (including the location at lines 155-160 mentioned in the
comment).

Comment on lines +55 to +58
.categoryLabel {
@include m.text-caption;
font-weight: v.$font-weight-bold;
color: var(--text-primary);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Stylelint 공백 규칙을 맞춰 주세요.

Line 57, Line 106에서 @include 뒤 선언 전에 빈 줄이 없어 declaration-empty-line-before가 실패합니다.

수정 예시
 .categoryLabel {
   `@include` m.text-caption;
+
   font-weight: v.$font-weight-bold;
   color: var(--text-primary);
   white-space: nowrap;
 }
@@
 .modalText {
   `@include` m.text-body-m;
+
   color: var(--text-primary);
   margin: 0;
   text-align: center;
 }

Also applies to: 104-106

🧰 Tools
🪛 Stylelint (17.13.0)

[error] 57-57: Expected empty line before declaration (declaration-empty-line-before)

(declaration-empty-line-before)

🤖 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/`(page)/rooms/[roomId]/statement/StatementPage.module.scss around
lines 55 - 58, The stylelint rule `declaration-empty-line-before` requires a
blank line before property declarations that follow an `@include` statement. In
the .categoryLabel class, add a blank line between the `@include
m.text-caption;` statement and the `font-weight` property declaration. Apply the
same fix to the similar issue mentioned at lines 104-106 where an `@include`
statement should be separated from following properties by a blank line.

Source: Linters/SAST tools

Comment thread src/app/api/disputes/route.ts Outdated
Comment thread src/app/api/disputes/route.ts Outdated
Comment thread src/app/api/disputes/route.ts
Comment thread src/lib/ai/judgment.ts Outdated

if (!token) return null

const decoded = await decode({ token, secret: process.env.NEXTAUTH_SECRET! })

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

NEXTAUTH_SECRET 환경변수 미설정 시 런타임 에러 발생

non-null assertion(!)으로 인해 NEXTAUTH_SECRET이 미설정된 환경에서 decode 호출 시 예측하기 어려운 에러가 발생할 수 있습니다. 명시적 검증으로 빠른 실패(fail-fast)와 명확한 에러 메시지를 제공하세요.

🛡️ 제안: 환경변수 검증 추가
+ const secret = process.env.NEXTAUTH_SECRET
+ if (!secret) {
+   console.error('NEXTAUTH_SECRET is not configured')
+   return null
+ }
+
- const decoded = await decode({ token, secret: process.env.NEXTAUTH_SECRET! })
+ const decoded = await decode({ token, secret })
📝 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
const decoded = await decode({ token, secret: process.env.NEXTAUTH_SECRET! })
const secret = process.env.NEXTAUTH_SECRET
if (!secret) {
console.error('NEXTAUTH_SECRET is not configured')
return null
}
const decoded = await decode({ token, secret })
🤖 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/lib/auth/getSession.ts` at line 14, The decode function call in
getSession.ts uses a non-null assertion on process.env.NEXTAUTH_SECRET which can
lead to unpredictable runtime errors if the environment variable is not set. Add
explicit validation before calling decode to check if NEXTAUTH_SECRET exists,
and throw a clear, descriptive error immediately if it is not defined. This
provides fail-fast behavior with a meaningful error message instead of relying
on the non-null assertion operator.

Comment thread src/lib/auth/getSession.ts
- disputes POST: 중복 체크를 Serializable 트랜잭션 내부로 이동 (race condition 방지)
- disputes POST: moderateContent에도 8s 타임아웃 적용 (withTimeout 공통 헬퍼)
- disputes POST: 모더레이션 실패/차단 로그에서 원문 제거, 안전한 메타데이터만 기록
- judgment.ts: AI 프롬프트에서 실명 제거, A/B 레이블 고정 (표시 시 user.name으로 치환)
- judge/route.ts: generateAiJudgment 호출에서 nameA/nameB 제거
- getSession.ts: decoded.sub 없을 시 null 반환으로 undefined user.id 방지

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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.

2 participants