/docs/generate 전수 감사 보고서
날짜: 2026-04-19 목적:
/docs/generate플로우·UX·에러·접근성·성능 전수 점검 후 개선 우선순위 도출 작성 기준: 코드베이스 현재 상태 (v0.81.0 · 커밋 feat/ocr-ops-panel 머지 시점) 이 보고서의 위상: 이후 슬라이스의 기준점. 개선이 진행되면 해당 항목에 취소선 + 머지 PR 번호 표기.
1. 아키텍처 매핑
/docs/generate
├─ page.tsx (AiDocGeneratePage · 코디네이터)
│ ├─ useDocGenerate() [훅 — 모든 상태·로직]
│ │ ├─ useEffectivePlan() — Pro 게이트
│ │ ├─ useSearchParams() — caseId · docType preselect
│ │ ├─ getAssistUsageAction() — AI 사용량
│ │ ├─ getRagContextForDocGenAction() — 사무실 기억 미리보기
│ │ ├─ executeAiAction() — reserve → RAG → generate (환불 자동)
│ │ ├─ generateLegalDoc() — Gemini 호출
│ │ └─ saveDocWithHtmlAction() — Firestore + Storage HTML 저장
│ ├─ DocStepIndicator — 3단계 진행 표시
│ ├─ DocStep1SelectType — 서류 종류 4 선택
│ ├─ DocStep2FillInfo — 폼 + 사무실 기억 미리보기 + 제외권
│ └─ DocStep3Preview — 초안 확인/편집/저장/복사/다운로드
상태 머신
step=1 (서류 선택) → step=2 (폼 입력) → handleGenerate → step=3 generating
→ handleUseTemplate → step=3 done (template)
step=3 generating → executeAiAction 결과:
- reserve_failed → showActionError + setStep(2)
- run_failed → logError + toast + setStep(2) (환불 여부 따라 문구)
- succeeded → setDraft + setStep(3) done
step=3 done → handleSave → saveDocWithHtmlAction → router.push("/docs")
step=3 error (현재는 이 상태 도달 경로 없음 — onRetry 로 done 복귀)
진입·이탈 지점
- 진입:
/docs/generate·/docs/generate?caseId=X&docType=Y·/cases/{id}/docs에서 "서류 생성" 클릭 후InlineDocGenerate모달 - 이탈:
handleSave성공 →/docs, 또는 페이지 닫기
2. 강점 (유지·확장 가치)
| # | 항목 | 근거 |
|---|---|---|
| S1 | 플랜 게이트 + readOnly 분기 | canUseAI = isPaidPlan && !readOnly. grace 기간 안내 명확 |
| S2 | AI 한도 임박 경고 | 90%+ 사용 시 amber 강조 |
| S3 | PII 사전 차단 (extraInstruction) | Zod RRN_PATTERN / CARD_PATTERN superRefine |
| S4 | 사무실 기억 미리보기 + 제외권 | Cycle 9 (# 205) |
| S5 | 환불 자동화 | executeAiAction 래퍼 (refunded 플래그 사용자 안내) |
| S6 | 사건 자동 입력 | CaseSelect → handleCaseSelect 로 10개 필드 일괄 주입 |
| S7 | 초안 복사·다운로드·편집·템플릿 분기 | Step 3 완성도 |
| S8 | 단계 인디케이터 · disabled 상태 시각화 | DocStepIndicator 피드백 |
3. 약점·개선 대상 리스트
P0 — 보안/오류 전달 (Critical)
P0-1. 에러 메시지 구체성 부족
step=3 error화면이 한 종류 — "서류 생성에 실패했습니다 / 네트워크 상태를 확인하고 다시 시도해주세요"- 실제로는 (a) reserve 실패(한도·readOnly·플래그 off) · (b) RAG 페치 실패 · (c) AI 호출 실패(retryable/non-retryable) · (d) 저장 실패(네트워크·권한·Zod) 등 4+ 원인 이 있는데 모두 동일 문구
- 현재 코드:
step=3 error경로가 이론상 존재하지만handleGenerate실패 시setStep(2)+ toast 로 처리 → Step 3 error UI 는 dead code 가능성 - 위험: 사용자가 왜 실패했는지 모르면 무의미한 재시도 반복 → 한도 소진·부정적 인상
P0-2. 저장 경로의 PII 검사 부재
DocGenerateInputSchema는extraInstructionPII 만 차단DocSaveInputSchema.finalText는 PII 검사 없음 — 사용자가 직접 편집한 draft 에 주민번호·계좌번호 포함 가능- Firestore
documents.content에 직접 저장 →/docs목록·DocPreviewModal에 노출 - 완화책: 사용자가 의도적으로 입력한 내용을 차단할 필요는 없으나
clientIdNumber등 이미 필드로 저장되는 값을 본문에서도 마스킹 할지 정책 결정 필요
H — UX 핵심 (High)
H-1. 접근성 (ARIA) 전반 부족
DocStep1SelectType카드(<button>) 에aria-pressed없음 — 선택 상태 스크린 리더 미전달DocStepIndicator에aria-current="step"없음DocStep3Previewgenerating 의 progress bar 에role="progressbar"+aria-valuenow/valuemin/valuemax없음- 성공·에러·면책 배너에
role="alert"/role="status"없음 → 상태 변화 감지 불가 - Step 3 편집
<textarea>에<label>연결 없음 (현재는 헤더 텍스트만) - 필드별
<label htmlFor>/<input id>명시 연결 없음
H-2. 가짜 로딩 진행률 (정직성 결여)
DocStep3Previewgenerating 단계의 progress 는 실제 진행과 무관한 random increment- "사건 정보 분석 중 / 법원 양식 적용 중 / 청구 취지·원인 작성 중 / 첨부 서류 목록 구성 중" — 정적 메시지, 실제 AI 단계와 대응 0
- 위험: 사용자가 신뢰를 잃을 수 있음 (예: 100% 가까이에서 오래 멈춤 → 실제는 처음부터 실패 중)
- 원칙 (
ai-pipeline.md): 사용자 트리거형 AI 는 투명한 상태 공개. 가짜 진행률은 원칙 위반
H-3. 편집 모드 저장/취소 분리 부재
DocStep3Preview편집 모드 진입 시draftstate 를 직접 수정 → 편집 중onBack누르면 변경 내용 유지된 채 Step 2 이동- "편집 취소 (변경 내용 버리기)" 옵션 없음
- Escape 키 편집 종료 없음
- 편집 모드 상태에서
onSave호출 가능 — 편집 확정 전 저장 위험
H-4. "다음 단계" 버튼 disabled 이유 안내 부재
canGenerate = baseRequired && (!needsIdNumber || hasIdNumber)- disabled 시 사용자에게는 클릭 불가만 보임. 왜 비활성인지 안내 없음
missingFields는 아래쪽 별도 배너로 표시하나 caseId 존재 조건 에서만 렌더 — caseId 없을 때는 무음
M — UX 보강 (Medium)
M-1. 저장 후 흐름 비효율
handleSave성공 →/docs로 이동 → 방금 저장한 문서를 목록에서 찾아야 함- 방금 생성한 결과를 확인하는 자연 경로:
/docs/{id}또는DocPreviewModal오픈 옵션 - 현재는 "저장했으니 목록 가세요" 수준
M-2. 필드 실시간 형식 검증 미흡
clientIdNumber는 자동 포맷 있음 ("000000-0000000")principal숫자만 허용은 Zod 에서만 — 입력 중 문자 허용됨 (onChange 미가공)interestRate"15%" 입력 가능 → Zod regex 에서만 잡힘 (submit 시점)- 입력 시점 실시간 피드백 없어 사용자가 끝에 가서야 실패 인지
M-3. Step 간 전환 시 포커스 관리 없음
- step=1 → 2 → 3 이동 시 DOM 교체만 발생 — 포커스는 "다음 단계" 버튼에 남음
- 스크린 리더 사용자가 Step 전환을 인지하려면 수동으로 화면 읽어야 함
M-4. 사건 자동 입력 후 수정 흔적 미표시
handleCaseSelect가 10개 필드를 자동 채움- 사용자가 필드 수정 시 "원본과 다름" 표시 없음 → 저장 시 원본 사건 레코드와 불일치 가능 (서류와 사건 데이터 drift)
L — 장기·리서치 (Low)
L-1. 브라우저 뒤로가기·URL Step 반영 부재
useSearchParams로 caseId·docType preselect 만. Step 변경은 state 만- 뒤로가기 시 페이지 전체 이탈 → 폼 데이터 유실
L-2. 자동 저장 없음
- 편집 중 페이지 이탈·새로고침 시 변경 내용 유실
- Cycle 2 의
saveDocContentAction은 사후 편집용. 생성 위저드의 draft 는 저장 전 휘발성
L-3. 테스트 커버리지
schemas.test.ts(Zod 경계) 있음constants.test.ts(헬퍼) 있음useDocGenerate훅 · Step1~3 컴포넌트 · 에러 경로 통합 테스트 없음- E2E 서류 생성 시나리오 커버리지 미확인 (
e2e/확인 필요)
4. 우선순위 매트릭스 + 실행 결과
| 우선순위 | 항목 | 결과 PR | 릴리스 | 상태 |
|---|---|---|---|---|
| H-1 | 접근성 ARIA | #220 | v0.82.1 | ✅ 완료 |
| P0-1 | 에러 메시지 구체화 | #221 | v0.83.0 | ✅ 완료 |
| H-2 | 가짜 로딩 정직화 | #222 | v0.84.0 | ✅ 완료 |
| H-3 | 편집 모드 저장/취소 분리 | #223 | v0.85.0 | ✅ 완료 |
| M-1 | 저장 후 흐름 개선 | #224 | v0.86.0 | ✅ 완료 |
| H-4 | canGenerate 이유 안내 | #225 | v0.87.0 | ✅ 완료 |
P0-2 (draft PII) 는 정책 결정 필요 — 사용자가 의도적으로 입력한 PII 를 차단해야 하는가? 변호사의 업무 특성상 채권자 주민번호는 법원 제출 서류에 필수 이므로 단순 차단은 불가능. 범위 밖 으로 분류, 차기 세션에서 정책 검토 후 결정.
M-24, L-13 은 장기·리서치 항목 — 이번 세션 범위 밖.
5. 금지 영역 (슬라이스 수행 시 건드리지 않음)
lib/ai/client.ts(Gemini 호출 로직)lib/ai/execute-ai-action.ts(reserve/refund 래퍼)app/(workspace)/docs/_actions/doc-generate-actions.ts::saveDocWithHtmlAction(Firestore 저장 비즈니스 로직)app/(workspace)/docs/_actions/ai-assist-actions.ts(한도 트랜잭션)
이유: 핵심 비즈니스 로직. 변경 시 실 변호사 업무에 직접 영향. 본 감사는 UX·에러 표시·접근성 범위 한정.
6. 다음 세션에서 다룰 항목 (이번 범위 밖)
- P0-2: 본문 PII 처리 정책 결정 (채권자 주민번호 필수 vs 마스킹)
- M-2: 입력 필드 실시간 검증 (interestRate · principal)
- M-4: 사건 자동 입력 후 수정 흔적 표시
- L-1: URL 에 Step 상태 반영
- L-2: draft 자동 저장
- L-3: E2E · 훅 테스트 커버리지 확장
7. 참고
- 정체성 문서: 사무실 기억
- AI 파이프라인: ai-pipeline
- AI 브리핑 원칙:
feedback-ai-briefing-principles(메모리)