problem category 입력 UI 회수 (Sprint 151 미연결 갭)
요약Sprint 177에서 이월한 계획(plan 템플릿 잔여 · 블로그 cross-check 추가 차원)을 착수했으나, 탐색 결과 둘 다 막다른 길임이 드러나 방향을 전환했다.
목표
- Sprint 177에서 이월한 계획(plan 템플릿 잔여 · 블로그 cross-check 추가 차원)을 착수했으나, 탐색 결과 둘 다 막다른 길임이 드러나 방향을 전환했다.
- plan 템플릿 잔여: PR 템플릿(
.github/pull_request_template.md)·스크립트(check-adr-index-count.mjs)에 이미 구현 완료 → 작업 거리 없음. - 블로그 cross-check 추가 차원(reference-style 링크): 블로그 전체에서 사용 0건, frontmatter도 20개 글 균일 → 사용되지 않는 패턴을 위한 방어 게이트라 실효성 낮음(Sprint 177 교훈 "결정론적 검증 가능 + false-positive 회피 1순위"에 비춰 우선순위 하).
- plan 템플릿 잔여: PR 템플릿(
- 대신 이월 시드 잔재 중 명확한 기능 갭인 problem category 입력 UI를 회수. 정석 사이클(단일 작업 브랜치 + PR + Squash merge + Critic) 준수.
결정
D1. 단순 UI 추가가 아니라 "절반 구현" 기능 갭 회수 (사용자 승인)
Sprint 151에서 SQL Kit 지원을 위해 ProblemCategory(ALGORITHM/SQL)를 백엔드 entity·DTO·service·DB 마이그레이션 + frontend API 타입까지 깔았으나, frontend create/edit 폼이 category를 전송하는 경로가 누락되어 있었다. 백엔드는 dto.category ?? ProblemCategory.ALGORITHM(problem.service.ts:67)로 입력에만 의존하고 sourceUrl 기반 자동 추론 로직이 없으며, 검색 훅(useProgrammersSearch)·programmersApi 결과에도 category가 없다. 결과적으로 모든 신규/수정 문제가 ALGORITHM으로 저장되어 SQL 문제를 등록할 방법이 없는 상태였다. 본 스프린트는 이 미연결 갭을 회수한다.
D2. category는 순수 수동 선택 — difficulty의 "검색 적용 시 disabled" 패턴 미적용
difficulty는 BOJ/Programmers 검색 결과가 채우므로 검색 적용 시 disabled된다. category는 검색이 채우지 않고 자동 추론도 없으므로 항상 활성인 수동 select(disabled={isSubmitting}만)로 두고 기본값 ALGORITHM을 가진다. 검색 패턴을 무비판적으로 복제하지 않고 데이터 흐름의 실제 차이에 맞춘다.
D3. native <select> 재사용 + i18n 라벨 — components/ui 신규 생성 없음
difficulty/status가 쓰는 native <select> + selectClass(problem-form-utils.ts) 패턴을 그대로 재사용해 components/ui/ 신규 컴포넌트 생성을 피한다(Palette 가이드 회피, _base.md 규칙). 라벨은 difficulty(고유명사 상수)·status(기존 한국어 하드코딩 기술부채)와 달리 신규 코드이므로 **i18n(problems.form.category.*, ko/en)**으로 영문 환경까지 정석 지원한다. 상수 SSOT는 PROBLEM_CATEGORIES(constants.ts), Zod는 z.enum(PROBLEM_CATEGORIES)로 타입 안전.
구현
PR #309 20295e7 — problem 생성/수정 category 입력 UI (9파일 +94/-2)
lib/constants.ts:PROBLEM_CATEGORIES(SSOT 튜플) +ProblemCategory파생 타입.lib/schemas/problem.ts:problemCreateSchema에category: z.enum(PROBLEM_CATEGORIES)추가..default()는 z.input/z.output 타입 분리로 RHF resolver와 충돌해 required enum 유지.create/page.tsx: defaultValues/reset에category: 'ALGORITHM'+ category select + onSubmitdata.category전송.edit/page.tsx:EditFormState.category+ load 시data.category ?? 'ALGORITHM'prefill + select + diff 기반 전송.messages/{ko,en}/problems.json:categoryLabel+category.ALGORITHM/SQL.- 테스트: create(렌더+기본값 ALGORITHM), edit(prefill SQL), schema(enum 유효/무효), constants mock에
PROBLEM_CATEGORIES추가.
Critic 사이클
codex review --base main 1라운드 (session 019e453e-d1bd-78b3-806b-224e3fb9e60a): 0건 — "category support를 create/edit 폼·번역·상수·스키마 검증에 추가하며 영향 흐름에 명확한 회귀나 breakage를 도입하지 않음". 머지 가능.
검증
로컬
tsc --noEmit통과.- ESLint 신규 경고 0 (기존 플랫폼 토글 인라인 style만 baseline).
- jest 1361 통과 / 0 실패.
- 커버리지 게이트: Lines 86.46%(≥83), Branches 77.49%(≥71), JEST_EXIT=0.
CI
- PR #309 "CI — Test, Build & Push"
conclusion: success(전 job green).
결과
- 머지: origin/main
06ebcc2→20295e7(PR #309 squash merge, 작업 브랜치 삭제). - 순변경: frontend 9파일 +94/-2 (상수/스키마/create·edit page/i18n/테스트).
신규 패턴
- "절반 구현" 기능 갭 회수: 백엔드·타입·DB가 한 기능을 위해 갖춰졌어도 frontend 전송 경로 한 줄이 빠지면 기능 전체가 silent하게 무력화(전부 기본값 저장)된다. API 타입에 필드가 존재한다고 실제 전송된다는 보장은 없다 — 데이터 흐름 양 끝(폼 → DTO → service)을 추적해야 갭이 보인다.
- 패턴 재사용 시 데이터 흐름 차이 확인: 인접 필드(difficulty)의 UI 패턴을 재사용하되, disabled 조건처럼 데이터 출처(검색 자동 채움 vs 수동)가 다른 부분은 복제하지 않고 분기한다.