---
task_id: task-2414
type: context
scope: task
created: 2026-05-03
updated: 2026-05-03
status: in-progress
---

# 맥락 노트: task-2414

**task**: task-2414

---

## Root Cause 분석 (있는 그대로 체크)

### 진단 절차

1. **백엔드 직접 curl 테스트** (3 doc 모두):
   - `curl http://localhost:8000/api/three-docs/task/task-2405/plan` → HTTP 200 + 정상 content
   - `curl http://localhost:8000/api/three-docs/task/task-2405/context-notes` → HTTP 200 + 정상 content
   - `curl http://localhost:8000/api/three-docs/task/task-2405/checklist` → HTTP 200 + 정상 content
   - **결론**: 백엔드는 정상. URL 인코딩/whitelist/path resolve 모두 OK.

2. **프론트엔드 Playwright 검증**:
   - task-2417, task-2405에서 3 탭 모두 정상 표시 확인
   - `.md-render` htmlLength 측정: plan 4449, context-notes 5476, checklist 4211 (모두 정상)

3. **코드 정밀 분석** — `dashboard/components/ArchiveView.js` line 97-99:
   ```js
   fetch(`/api/three-docs/${type}/${id}/${activeDocTab}`)
       .then(r => r.ok ? r.json() : null)
       .then(data => setDocContent(data?.content || (data?.error ? `*${data.error}*` : '')))
   ```
   - **silent blank 패턴 발견**: 백엔드가 404/400 반환 시 `r.ok=false` → `null` 전달 → `data?.content`도 `data?.error`도 undefined → **`setDocContent('')` → 빈 화면**.

### 회장이 본 시나리오 (가능성)

A. **system 단독 .md 파일** (`memory/specs/anu-guide.md` 등):
   - 백엔드: `if doc_name != "plan"` → 404 반환 (line 3106-3108)
   - 프론트: r.ok=false → null → silent blank
   - **확률: 매우 높음**. 회장은 시스템 탭에서 단독 .md 파일을 클릭했을 가능성.

B. **task/project에서 context-notes.md 또는 checklist.md만 누락된 경우**:
   - 백엔드: 404 + `expected_path` 반환
   - 프론트: silent blank
   - **확률: 중간**.

C. **단순 templated 빈 내용**:
   - 200 OK + content (frontmatter + 빈 헤더) → 시각적으로 거의 비어 보임
   - **확률: 낮음** (시각적으로 짧을 뿐 진짜 blank는 아님)

### 결정 — Fix 범위

- **Fix 1 (필수)**: ArchiveView.js fetch 응답 처리 — `r.json()`을 status에 무관하게 호출 + 명확한 에러 메시지
- **Fix 2 (강력 권장)**: 카드 그리드의 has_X 뱃지가 회색(=false)이면 해당 탭을 disabled + tooltip "이 문서는 존재하지 않습니다"
- **Fix 3 (회귀)**: 단위 테스트 — 404 응답이 JSON body 포함 확인

### 대안과 기각 이유

- **대안 A**: 백엔드를 200 + 빈 content로 변경 → 기각. 의미적으로 잘못됨 (파일 없음=404가 표준).
- **대안 B**: r.ok=false 시 `*불러오기 실패*` 통일 → 기각. expected_path 같은 진단 정보 손실.
- **선택**: r.json() 항상 호출 + status별 분기. 사용자 친화적 메시지 + 진단 정보 보존.

## 참조 자료

- 회장 명시: `memory/tasks/task-2414.md` (회장 명시 2026-05-03)
- 백엔드 코드: `dashboard/routes_get.py` line 2855-3153
- 프론트 코드: `dashboard/components/ArchiveView.js` line 93-102, 320-345
- task-2410 commit: `af9ddd9b` (기능 도입), `f46e6d07` (테스트/L1)

## 주의사항

- forbidden 절대 준수: dashboard/server.py, dashboard/data_loader.py, dispatch.py 등 21개 금지 파일 수정 금지
- ★ Edit 직후 grep 검증 필수
- ★ L1 스모크테스트: 실제 서버 재시작 + Playwright 스크린샷 (blank 없음 확인)
- task-2410 plan 탭 정상 보존 (회귀 0)
- 단독 .md 파일 (system 타입)에서 plan만 표시 + context/checklist 탭 disabled 동작 검증

## 3 Step Why 검증

- **1st Why**: 왜 fetch 응답 처리 fix가 필요한가?
  → A: 백엔드 404가 silent blank로 변환되어 회장이 "blank임"으로 인식. 진단 정보를 사용자에게 보여줘야 함.
- **2nd Why**: 왜 이 접근(r.json() 항상 + status 분기)이 최선인가?
  → B: 백엔드는 모든 에러 응답에 `{"error": "..."}` JSON body를 반환. r.json()을 무조건 호출하여 진단 정보 활용 가능. UX 개선 + 디버깅 용이.
- **3rd Why**: 왜 has_X=false 탭 disabled가 메시지 표시보다 나은가?
  → C: 사용자가 존재하지 않는 문서를 클릭하는 행위 자체를 막는 것이 더 명확한 UX. has_X 신호는 이미 백엔드가 list API에서 반환 중이므로 활용. 클릭 후 메시지 표시는 한 번 더 인지 부담을 줌.
- **A-B-C 일관성**: ✅ "silent blank → 응답 처리 강화 → UX 사전 차단" 일관됨
