15 KiB
15 KiB
| schema_version | sid | started_at | ended_at | user_request | mode |
|---|---|---|---|---|---|
| 2 | 20260618-145152 | 2026-06-18T14:51:52+09:00 | 2026-06-18T14:58:00+09:00 | W3-2 댓글/리뷰 고도화 — 인터뷰 주도 디테일 빌드. W3-2 코어(2026-06-18 구현·검증 완료) 유지하며 세세한 디테일을 사용자 인터뷰로 누적. 바로 설계·구현 금지. 질문 시드 카탈로그(A 일관성 / B 목록규모 / C UX)를 한 축씩 AskUserQuestion 으로 물어 결정 누적 → design-advisor → 구현 → L1 회귀 31건 + 신규 → L3 스모크. | interview-first (orchestrator 직접 인터뷰, requirements-advisor 대체) |
Advisor Invocation Decision Log
- advisor: requirements-advisor decision: skip rationale: '카탈로그가 요구 분해를 이미 제공 — orchestrator 가 AskUserQuestion 인터뷰로 직접 스코프 확정(task 명시)' checked_at: 2026-06-18T14:52:00+09:00
- advisor: design-advisor decision: deferred-to-next-session rationale: '인터뷰 완료·FINAL SPEC 사용자 확정. 단 사용자 지시로 구현은 다음 세션 → design-advisor 도 다음 세션 진입. 선결: W2-3 freeze 근거 확인' checked_at: 2026-06-18T15:10:00+09:00
- advisor: retrospective-advisor decision: skip rationale: '코드 변경 0줄·구현 미시작 인터뷰 전용 세션. 회고 입력(구현/검증 결과) 부재 → 구현 세션 종료 시 수행' checked_at: 2026-06-18T15:10:00+09:00
Summary
인터뷰 완료 — 카탈로그 전 축(A1·A2·A3 / B1·B2·B3 / 리뷰정책4 / C1~C6) 결정 누적 완료.
사용자 지시로 이 세션은 결정·기록만, 구현은 다음 세션. 코드 변경 0줄(Serena/grep 탐색만).
다음 세션: 새 sid + resumed_from: 20260618-145152, 아래 FINAL SPEC 으로 design-advisor 진입.
⚠️ 다음 세션 선결·고위험 항목
- C3 = W2-3 평점 집계 동결 해제: 설계 진입 전 W2-3 freeze 근거(ADR/문서) 필독. §6 파괴적 게이트 인접 — orchestrator 사용자 재확인. 회귀 검증 필수
- 다축 평점 = 대규모 신규: game_review_axes 테이블 + 6축 입력 UI + overall 자동평균/수동덮어쓰기 + game_review_stats 뷰(축별 평균). 작업량·테스트 비중 큼
- 입력 부담: 리뷰 제출 = 6축 필수 + 본문 10자 — UX 마찰 인지
FINAL SPEC (✅ 사용자 최종확인 완료 2026-06-18 — 다음 세션 design-advisor 입력)
A. 일관성
- A1 댓글 응답 =
commentView단일 통일(POST·PUT·list 전부). POST는 insert 후 재조회(createReview 패턴) - A2 작성자명 = 하이브리드
COALESCE(u.display_name, c.nickname)+ LEFT JOIN. user_id 있으면 현재닉(개명반영), NULL이면 스냅샷(QG-2 해결). createComment nickname 스냅샷 저장 유지- 탈퇴자: 댓글 보존(LEFT JOIN) / 리뷰 현행 제외 유지(INNER JOIN). 탈퇴자 댓글 이름 = "(탈퇴한 사용자)" 마스킹:
CASE WHEN u.id IS NULL THEN c.nickname WHEN u.is_delete THEN '(탈퇴한 사용자)' ELSE u.display_name END
- 탈퇴자: 댓글 보존(LEFT JOIN) / 리뷰 현행 제외 유지(INNER JOIN). 탈퇴자 댓글 이름 = "(탈퇴한 사용자)" 마스킹:
- A3 댓글 edited+updatedAt 추가. game_comments.updated_at 멱등 ALTER + 매퍼/모델 갱신. editGameComment 시 updated_at 갱신, nickname 미덮어씀
B. 목록 규모
- B1 페이지네이션 offset/limit + "더보기", 20건/페이지. 정렬 토글: 댓글(오래된순 기본/최신순), 리뷰(최신순 기본/별점 높은순/낮은순, tiebreaker rating·created_at·id). sort 화이트리스트 enum→고정 SQL(${} 금지)
- B2 리뷰 본문 필수 + 최소 10자(별점만 제출 불가). 댓글은 현행(200자 상한, 최소 없음)
- B3 본문 정규화 = 제어문자 제거 + 외곽 trim(댓글·리뷰 공통). 내부 연속공백·줄바꿈 보존
리뷰 정책
- 게임당 1회 유지(ux 유니크), 수정 무제한 유지(edited 마커)
- 다축 평점: overall rating 유지(W2-3 집계용) + game_review_axes 신규 테이블(review_id FK, axis_key enum, score). 축 6개 몰입성·창의성·조작성·완성도·사운드·비주얼, 각 1~5 정수 전 축 필수. UI: overall = 축 평균 자동계산(정수 반올림)되다 유저 직접선택 시 덮어쓰기(수동 우선, 출처 구분 플래그 검토)
- 다축 시각화 = 육각형 레이더 차트(hexagon):
- 위치 = 요약 + 개별 리뷰 둘 다 (요약=game_review_stats 6축 평균 육각형 / 각 리뷰 카드=그 리뷰 6축 육각형)
- 렌더 = 인라인 SVG 자체 구현 (polygon points 계산, 기존 JSP 인라인 SVG idiom·빌드 없음·의존성 0). 차트 라이브러리/CDN 배제
- 입력은 6개 별점 위젯 유지(C6) — 육각형은 표시 전용(레이더 드래그 입력 a11y 제외)
- a11y: SVG 시각요소엔 텍스트 대체 필수 — 축별 점수 aria-label 또는 인접 텍스트/표(스크린리더). 폴리곤만으로 끝내지 말 것
- 개별 리뷰 카드 육각형은 컴팩트 사이즈(페이지당 20건 DOM 고려)
- 다축 시각화 = 육각형 레이더 차트(hexagon):
- 신고·숨김 보류(W1 운영자 role 선행)
C. UX (game-detail.jsp)
- C1 submit 잠금 + 로딩 상태(제출 중 disabled)
- C2 상대시각 "n분 전" + title 절대시각(fmtDate 확장)
- C3 ⚠️ W2-3 동결 해제 + game_review_stats 뷰(game_id, avg_rating, review_count + 6축 평균). 표시 = 소수1자리 + 반올림 별 + 0개 "아직 평가 없음". client 계산 폐기(페이지네이션 정합)
- C4 실시간 글자수 카운터(123/200, 리뷰 최소10자 안내). 댓글·리뷰 둘 다
- C5 삭제 = 완전 소멸 유지(현행)
- C6 별점 위젯 완전 radiogroup 패턴(roving tabindex + 화살표/Home/End), 다축 6위젯 적용
Grounded Baseline (evidence)
- GameCommentController 응답 3종 불일치:
- POST createComment flat: status,message,commentId,gameId,authorName,userId,content (createdAt/edited 없음)
- PUT updateComment 부분: status,message,commentId,content
- list commentView: commentId,gameId,authorName,userId,content,createdAt (edited 없음)
- GameReviewController 이미 통일: POST/PUT/list/get 전부 reviewView 사용
- reviewView: reviewId,gameId,authorName,userId,rating,body,edited,createdAt,updatedAt
- 댓글 작성자명 = nickname 스냅샷 (createComment: session displayName 저장)
- GameCommentData 필드: id,gameId,userId,nickname,content,createdAt,deletedAt — updatedAt 없음 → A3(댓글 수정됨 마커)는 game_comments.updated_at ALTER 필요 (무료 아님)
- createReview: rating 필수, body trimToEmpty 후 length 검사만 → 빈 본문 리뷰 허용 (B2)
- trimToNull/trimToEmpty = 외곽 trim 만; 내부 연속공백/제어문자 미처리 (B3)
- DDL game-reviews-ddl.sql: game_reviews(updated_at 보유), game_comments user_id nullable(레거시 보존)
- W2-3 평점 집계 스키마 동결 / QG-2 레거시 user_id NULL 댓글 미결
- A2 작성자명 메커니즘 분기 (신규 발견):
- 리뷰 =
JOIN users u, u.display_name AS authorName(GameReviewsMapper) → 항상 현재 닉네임(live)u.is_delete IS NOT TRUE조건 → 탈퇴자 리뷰는 목록서 제외됨
- 댓글 =
nickname컬럼 스냅샷(작성 시점 고정, GameCommentsMapper listGameComments:36) → 탈퇴자 댓글도 표시 - 댓글 매퍼에 미사용
updateGameComment(nickname 갱신 가능) 존재 (:81) — A2/A3 설계 시 활용 여지 - QG-2 결합: 레거시 user_id NULL 댓글은 JOIN 불가 → live-join 채택 시 스냅샷 fallback 필수
- 리뷰 =
Decisions (누적)
- A3 댓글 수정됨 마커: edited+updatedAt 추가 (리뷰 대칭). game_comments.updated_at 멱등 ALTER + 매퍼 갱신 필요. [2026-06-18]
- A1 응답 스키마: commentView 단일 통일 — POST·PUT·list 전부 commentView 반환(commentId,gameId,authorName,userId,content,createdAt,edited,updatedAt). POST는 insert 후 재조회(createReview 패턴). 프론트 렌더 1함수. [2026-06-18]
Invocations
[]
user_signals
positive: [] negative: []
verified_by_me
(없음 — 코드 변경 0줄, 인터뷰만 수행. L1/L2/log-scan 비대상)
needs_user_verification
- 다음 세션: 인터뷰 재개(아래 어젠다) → 결정 충분 시 design-advisor → 구현 → L1 회귀 31건+신규 → L3 스모크.
graph_refresh
skip: 코드 변경 0줄 (graph-refresh-checker 비대상)
Next-session agenda (인터뷰 재개 지점)
결정 잠금 (재확인만)
- A1 = commentView 단일 통일 (POST·PUT·list 전부 commentView; POST insert 후 재조회)
- A3 = 댓글 edited+updatedAt 추가 (game_comments.updated_at 멱등 ALTER + 매퍼/모델 갱신)
- 종속: A1 통일 시 commentView 가 edited/updatedAt 자동 포함 → 두 결정 한 묶음 구현
- A2 = 하이브리드 작성자명:
COALESCE(u.display_name, c.nickname)+ LEFT JOIN users (GameCommentsMapper getGameComment/listGameComments 둘 다)- user_id 있으면 현재 닉네임(개명 반영, 리뷰와 일관) / NULL이면 스냅샷 nickname → QG-2 자동 해결
- createComment 의 nickname 스냅샷 저장은 유지(fallback 안전망). 미사용 updateGameComment(:81)는 edit 시 nickname 덮어쓰지 않도록 주의
- A2-탈퇴자 = 댓글 보존 / 리뷰 현행(제외) 유지: 댓글 LEFT JOIN(is_delete 무필터) → 탈퇴자 댓글 표시. 리뷰 INNER JOIN+
u.is_delete IS NOT TRUE그대로 → 리뷰 스코프·31건 테스트 무영향- A2-탈퇴자-이름 = "(탈퇴한 사용자)" 마스킹. 댓글 SQL:
CASE WHEN u.id IS NULL THEN c.nickname WHEN u.is_delete THEN '(탈퇴한 사용자)' ELSE u.display_name END AS authorName(LEFT JOIN). 탈퇴 플래그 노출 불필요 — 이름만 대체
- A2-탈퇴자-이름 = "(탈퇴한 사용자)" 마스킹. 댓글 SQL:
- B1 = 페이지네이션 도입 + 기본 정렬 현행 유지 (댓글 오래된순 ASC / 리뷰 최신순 DESC 기본). 전건 반환 폐기 → offset/limit 또는 cursor (방식·크기·정렬옵션은 후속 질문)
- B1-방식 = offset/limit + "더보기" 버튼, 페이지당 20건. page/offset 쿼리 파라미터
- B1-정렬 = 댓글·리뷰 모두 정렬 토글:
- 댓글: 오래된순(기본)/최신순
- 리뷰: 최신순(기본)/별점 높은순/낮은순 — 별점순 정렬은 tiebreaker 필요(rating DESC/ASC, created_at DESC, id DESC)
- API sort 파라미터 신설 + 프론트 정렬 컨트롤. 매퍼 정렬절 동적화(단
${}금지 — sort 화이트리스트 enum→고정 SQL 분기로 #{} 안전 유지)
- B3 = 제어문자 제거 + 외곽 trim (댓글 content·리뷰 body 공통). 탭/개행 외 제어문자 strip, 내부 연속공백·줄바꿈은 보존. textContent 렌더라 보안 아닌 저장 위생 목적
- B2 = 리뷰 본문 필수 + 최소 10자 (별점만 제출 불가). createReview/updateReview body 검증 추가. 댓글은 별도(200자 상한만, 최소 없음 — 변경 안 함)
- 확장 리뷰 정책 4영역:
- ① 게임당 1회 제한 = 1회 유지(현행) — ux_game_reviews_game_user_active 유지, 무결성 우선
- ② 수정 제한 = 무제한 수정 유지(현행) — edited 마커로 투명성
- ③ 별점 척도 = 다축 평점 도입. 세부:
- overall rating 유지 + 축 별도 (W2-3 집계 동결 안 건드림 — 집계는 game_reviews.rating 그대로). 축은 game_review_axes 신규 테이블
- UI 동작: overall 별점 = 축 점수 평균으로 자동계산·표시되다가, 유저가 overall 을 직접 선택하면 그 값으로 강제 덮어쓰기 (수동 우선)
- 설계 미결: 자동평균→정수 rating 반올림 정책(C3 평균 별점 정책과 연동) / rating 수동·자동 출처 구분 필요(예 is_rating_manual 플래그?)
- 축 6개: 몰입성, 창의성, 조작성, 완성도, 사운드, 비주얼
- 축 척도 = 1~5 정수, 전 축 필수 (overall 과 동일, 자동평균 결정적)
- 스키마 = game_review_axes 테이블 (review_id FK + axis_key + score, 리뷰당 6행). axis_key enum 6종. DDL 신규 테이블 + FK + 인덱스(review_id)
- 다축 평점 = 풀스펙 확정. 입력 부담(6축 필수 + 본문 10자)은 인지하고 진행
- ④ 신고·숨김 = 도입 보류(W1 선행) — 운영자 role 활성 후 신고+숨김 함께. 현 세션 스코프 제외
- 확장 리뷰 정책 4영역:
미결 — 다음 세션 질문 순서 (1~2축씩, AskUserQuestion, 추천 맨앞+근거+트레이드오프)
- C UX (game-detail.jsp) — JSP grounding 완료:
- C1 연타 방지 = submit 잠금 + 로딩 상태 (제출 중 disabled, 응답 후 해제). 댓글 중복 POST 차단
- C2 시간 표시 = 상대시각 "n분 전" + title 속성에 절대시각 (fmtDate:1202 대체/확장)
- C3 평균 별점 = ⚠️ W2-3 동결 해제 + 집계 컬럼/뷰 신설 (사용자 informed 선택, 고위험 — 회귀 검증 필수). 표시 = 소수1자리 + 반올림 별 + 0개는 "아직 평가 없음" 문구
- 모순 해소: client 계산은 B1 페이지네이션과 충돌 → 서버 집계로 전환 확정
- 다음 세션 설계 선결조건: W2-3 freeze 근거(ADR/문서) 먼저 읽고 무엇이 왜 동결됐는지 파악 후 해제. §6 파괴적 게이트 인접 — orchestrator 사용자 확인 경로
- 리뷰별 overall 자동평균(축→overall)은 rating 정수라 정수 반올림 / 전체 평균 표시는 소수1자리 (반올림 컨텍스트 2종 구분)
- 집계 방식 = 뷰(VIEW) 기반 — game_review_stats 뷰(game_id, avg_rating, review_count + 6축 평균). 저장 컬럼 비신설(파생 객체만) → 동결 해제 중 회귀 표면 최소
- 축별 평균 = overall + 6축 평균 모두 요약 표시. UI에 축별 막대/점수. 뷰가 축별 AVG(game_review_axes JOIN) 포함
- C4 글자수 카운터 = 실시간 카운터 (123/200 형식, 리뷰는 최소 10자 미달 안내). 댓글·리뷰 textarea 둘 다 + (다축 도입 시 6축 위젯 입력 상태도 고려)
- C5 삭제 표시 = 완전 소멸 유지(현행) — soft-delete + is_delete 필터. 댓글 스레드 없어 tombstone 불필요
- C6 접근성 = 완전 radiogroup 패턴 — roving tabindex(위젯당 단일 탭정지) + ←→↑↓·Home/End 별점 이동, aria 유지. 다축 6위젯 동일 적용. 현 위젯은 화살표 핸들러 없음(grep 확인) — 신규 추가
- grounding 근거: 별점위젯:1043(role=radiogroup/radio+aria 기구현), 평균:1034, 댓글ta:1080(max200), 리뷰ta:1052(max1000), 제출버튼:538(disabled 없음)
인터뷰 종료 후 (task 명시)
- 누적 결정 최종 확인 → 2. design-advisor: 파일 소유권 맵 + 멱등 ALTER DDL → 3. 구현 → L1 31건 회귀+신규 → L3 스모크 → 4. docs/changes/ 이력 기록
open_items
.atp/work-session/20260618-145152/report.md(신규, untracked) — 세션 기록. 커밋은 사용자 지시 시 (CLAUDE.md 커밋 정책: 명시 요청 시만).- src/ 변경 0건 — 구현 미시작.