# v0.3 — Operational UX Track Handover

> **Status (2026-05-17)**: PR-O1~O6 **머지 완료**. §4 의 PR 표 참조.
> 잔여: PR-O6b (frontend + i18n, 별 PR), PR-O7 (드래그+연결, 별
> 사이클 후보). 자세한 session 결산은
> `v0.3.x-session-2026-05-17-closure.md`.
>
> **Active handover** (2026-05-14~). 사이클 8 (Knowledge Cascade) closure
> + 사이클 11 (Cognitive Layer, PR #275 open) 신설 직후 신설.
>
> 사이클 12. **운영 UX / 라이브 사용성 차단** 트랙. cognitive layer 와
> 별도로 진행 (시점이 다르고 작업 단위가 작음).

---

## 0. 한 줄 요약

라이브 사용 중 발견된 6 항목의 버그 / UX 결손을 7 개 PR 로 분리해서
해결. **이번 핸드오버 머지 후 사용자 지시 따라 PR 1 부터 순차 진행**.
실제 코드 변경은 지시 후 시작.

---

## 1. 입력 — 2026-05-14 사용자 피드백 6건

1. **N-3 새 세션 첫 인사 회귀** — PR #257 (`e7819d8`) 의 fix 가 부분적,
   라이브에서 여전히 재현. 새 세션이 직전 세션의 분석을 끌어옴.
   페르소나 / 사용자 정보는 기억되어야 하지만 대화 내용은 격리되어야 함.
2. **제안 칩 연관성 + 클릭 버튼 미생성** — chat 본문에만 텍스트로
   나오는 케이스. 사용자 추측 (서버측 생성 제안만 클릭 칩화) **정확**.
3. **장기기억 저장 진행 애니메이션 부재** — chip 클릭 후 응답까지
   spinner 없어서 사용자가 처리 중인지 모름.
4. **비로그인 사용자 격리** — 일상 챗 + 웹검색만 허용, 내부자료 답변
   / 다른 페이지 / 다른 기능 엄격 차단, 대화 말미에 로그인 안내.
5. **그래프 다각적 연결 부재** — 같은 entity (예: US Army) 가 다른
   분야 doc 에서 등장해도 cross-domain weak edge 형성 안 됨. 사용자
   분석 검토 결과 **사용자 판단 정확**. 두 문제 (entity dedup 약함 +
   cross-doc inferred weak edge 미형성).
6. **그래프 노드/에디터 UX**:
   - (a) 노드 클릭 시 "요약 불러오지 못했습니다. 에러 403" 항상 발생
   - (b) 엔티티 노드 자체 (name / type / aliases / summary) 편집 미지원
   - (c) 노드 드래그 / 클릭 연결 편집 미지원
   - (d) 비개발자 직관성 (영문 type 명, 어휘) 개선 필요

---

## 2. 사용자 결정 4건 (2026-05-14)

이 트랙 진입 전에 명시적으로 채택:

| # | 항목 | 결정 | 의미 |
|---|---|---|---|
| 1 | 1+2+3 묶음 PR 여부 | **B = 3 PR 분리** | 검토 쉬움 우선 |
| 2 | 비로그인 격리 방식 | **B = 기존 `external` role 의 feature matrix 좁히기** | 새 role 신설 없이 정합 |
| 3 | 사이클 8 라이브 검증과의 순서 | **B = 이번 fix 묶음 먼저** | 라이브 사용 차단이 검증보다 시급 |
| 4 | 본 트랙이 cognitive layer 와 분리되는가 | **A = 분리, 별 사이클** | 작업 시점 / 단위 다름 |

---

## 3. 항목별 원인 분석 (코드 레벨)

### ① N-3 새 세션 회귀
- PR #257 이 `continuity directive` 게이트를 `hist_ctx` 로 좁힘 ✓
- **그러나 `long_ctx` 자체가 system prompt 의 배경 정보로 여전히 들어감**
  — directive 없어도 LLM 이 알아서 prior 분석을 끌어오는 케이스 존재
- 또는 클라이언트 측 `session_id` 가 새 세션에서도 재사용 (localStorage)

### ② 제안 칩
- `frontend/static/chat.js:1517` `extractNextActionSuggestions(answerText)`
- LLM 응답 tail 600자에서 정규식 매칭 (`SUGGESTION_PATTERNS`)
- threshold ≥2 매칭 시 chip 변환 + 본문에서 절단
- 못 잡는 케이스: 자연어 제안 ("혹시 ~궁금하신가요?"), 1개만 매칭

### ③ 진행 애니메이션
- `chat.js:1570 approveWikiSave` 가 즉시 disable 만 하고 spinner 없음
- 웹 검색 자체도 진행률 미표시

### ④ 비로그인 격리
- `verify_api_key(system api_key)` → role=`employee` 로 resolve
- 즉 비로그인이 employee 권한으로 통과 가능
- 사용자 요구: 일상 챗 + 웹검색 OK, 내부자료 차단, 다른 페이지 차단

### ⑤ 그래프 다각적 연결
- (a) `_find_existing_entity_id` 가 `normalized_name` + `aliases` 만 매칭
  → "US Army" vs "미군" 분리됨
- (b) 같은 entity 가 두 doc 에 등장하면 sources 배열에 두 doc_id 가
  쌓이지만, 다른 entity 들 사이 "공통 reference" 기반 weak edge 는
  명시적으로 안 만들어짐

### ⑥-a 노드 클릭 403
- `frontend/static/graph.js:585` 의 fetch 가 **Authorization 헤더 누락**
- `/admin/entities/{entity_id}` 는 `admin.data` feature gate → JWT 필요
- snapshot fetch (graph.js:151) 는 정상 Bearer 보내는데 entity summary 만 미포함

### ⑥-b/c/d 그래프 에디터
- 현재 graph editor (PR #271/#273) 는 **relation** 만 편집
- 노드 attributes 편집은 wiki_editor.py 가 chat 기반으로만
- 드래그는 ForceGraph3D 의 force 시뮬 위치만 변경 (영구 저장 X)
- 클릭 연결은 미구현
- 직관성: 영문 type 명, role 라벨 등 노출

---

## 4. PR 7개 — 우선순위 + 명세 + 머지 상태

### Closure summary (2026-05-17)

| # | 항목 | PR | 상태 |
|---|---|---|---|
| PR-O1 | ⑥-a 노드 클릭 403 | #277 | ✓ merged |
| PR-O2 | ② 제안 칩 NL 패턴 | #279 | ✓ merged |
| PR-O3 | ③ 저장 chip spinner | #280 | ✓ merged |
| PR-O4 | ① N-3 long_ctx 격리 | #291 | ✓ merged |
| PR-O5 | ④ external 격리 | #292 | ✓ merged |
| PR-O6 | ⑥-b 노드 편집 backend | #294 | ✓ merged |
| PR-O6b | ⑥-b/d frontend + i18n | — | **pending** |
| PR-O7 | ⑥-c 드래그 + 클릭 연결 | — | **deferred** (별 사이클) |

상세 명세는 아래 그대로 보존 — 각 PR 의 의도 / scope 가 future
reference 로 유용. 머지된 항목은 별표 (★).

### 🔴 PR-O1 — 노드 클릭 403 micro-fix ★
- **항목**: ⑥-a
- **변경**: `frontend/static/graph.js:585` 의 fetch 에
  `headers: { 'Authorization': 'Bearer ' + token }` 추가 (1 line)
- **테스트**: 기존 test_graph_w2_node_summary 그대로 통과
- **bench**: 불필요 (UI fix)
- **규모**: micro (~5 lines)

### 🔴 PR-O2 — 제안 칩 정규식 보강
- **항목**: ② Fix-A
- **변경**:
  - `SUGGESTION_PATTERNS` 에 "혹시 ~궁금하신가요?" / "~에 대해 더
    알고 싶으시면" / "관련된 질문으로는" 등 자연어 패턴 추가
  - threshold `>=2` → `>=1` (단일 매칭도 chip 화)
- **테스트**: `tests/test_chat_ux_n4_n5.py` 확장
- **규모**: 소 (~30 lines + 2 신규 test)

### 🔴 PR-O3 — 장기기억 저장 진행 spinner
- **항목**: ③
- **변경**:
  - `chat.js:1570 approveWikiSave` 에서 chip 즉시 spinner 로 교체
  - tokens.css 의 `--accent` (mint) 로 회전 애니메이션
  - 응답 도착 시 spinner → ✓ checkmark / ✗ cross 전환
  - 웹 검색 자체 진행률 표시는 별 PR (out of scope)
- **테스트**: JS 단위 테스트 미작성 — 라이브 검증 가이드만
- **규모**: 소 (~50 lines CSS + JS)

### 🟠 PR-O4 — N-3 진짜 fix (long_ctx 격리)
- **항목**: ①
- **변경**:
  - `core/memory/store.py` 의 long_ctx 추출 함수가 새 세션 첫 turn
    (`hist_ctx == ""`) 에서 **persona / user info 만** 추출. 대화 내용
    (이전 분석 / 답변) 은 제외
  - 또는 `core/reasoning/modes.py` 에서 `long_ctx` 를 system prompt
    의 "사용자 프로필" 섹션과 "이전 분석" 섹션으로 분리하고, 첫 turn
    에서는 "이전 분석" 만 비활성화
  - 클라이언트 측 `session_id` 발급 검토 (chat.js 의 새 세션 버튼)
- **테스트**: `tests/test_conversation_continuity.py` 확장 — 새 세션
  첫 turn 에서 prior session 의 답변 단어가 LLM context 에 없음 확인
- **규모**: 중 (~100 lines + 새 test)
- **CLAUDE.md rule #2**: reasoning 변경 — STEP 7 bench 필요 (회귀 0)

### 🟠 PR-O5 — 비로그인 격리 (external feature matrix)
- **항목**: ④
- **변경**:
  - feature catalog 에 `query.internal_rag` 신설 (vector + graph
    retrieval 게이트). default: admin + manager + employee
    (**external 차단**)
  - `query.web_search` (이미 있음) external 통과 유지
  - `core/reasoning/engine.py` 가 `query.internal_rag` 거부 받으면
    web + small-talk 만으로 동작
  - 비로그인 (= system api_key 의 employee role) 도 같은 gate 적용
    하기 위해 employee → external 다운그레이드 옵션 검토. 또는
    JWT 없으면 강제로 role=external 처리
  - chat 답변 말미에 i18n key `chat.login_prompt_guest` 자동 append
    (external role 일 때만)
  - HTML 페이지 (`/workspace`, `/admin/*`) external 차단 — 기존 admin
    게이트 + 새 `pages.workspace` feature gate
- **테스트**: `tests/test_external_role_isolation.py` 신규
- **bench**: 회귀 0 확인
- **규모**: 중 (~150 lines + 2 신규 test)

### 🟠 PR-O6 — 노드 편집 + 직관성 일괄
- **항목**: ⑥-b + ⑥-d
- **변경**:
  - `core/graph_editor.py` 에 `update_node_attributes(entity_id, patch)`
    함수 신규 — name 외 type/aliases/summary 편집 (entity_id 는 immutable)
  - 신규 endpoint `PUT /admin/graph/node` (admin.data + JAMES_GRAPH_EDIT
    게이트)
  - frontend: 노드 클릭 → 기존 edge modal 위에 "Edit node" 토글, 같은
    pattern
  - i18n keys 한글화 강화 (영문 type 명 옆 한글 병기). modal 어휘 단순
    화 ("관계 종류" / "출처 가중치" 등). hover tooltip 추가
- **테스트**: `tests/test_phase_e_graph_editor.py` 확장 (node 편집 분기)
- **bench**: 회귀 0 확인
- **규모**: 중 (~250 lines + 5~6 신규 test)

### 🟡 PR-O7 — 노드 드래그 + 클릭 연결 (대규모, 별 사이클 후보)
- **항목**: ⑥-c
- **변경**:
  - ForceGraph3D `onNodeDrag` 후 위치 영구 저장 (entity frontmatter
    에 `graph_x/y/z` 신규 필드, admin 만 trigger)
  - 클릭 연결 모드: edit-mode 안에서 노드 1 클릭 → 노드 2 클릭 →
    relation 생성 modal
  - 직관성: 미니 도움말 oneliner
- **규모**: 대 (~500 lines + UX 신규 테스트)
- **권장**: 본 사이클 외 별 사이클 (사이클 13?) — 우선순위 낮음

---

## 5. 사이클 11 (Cognitive Layer) 과의 boundary

본 트랙은 **현재 라이브 사용성 차단 회복** 이 목적.
사이클 11 의 4 phase 와 다른 차원이지만, 일부 항목은 자연 흡수됨:

| 본 트랙 항목 | 사이클 11 와의 연관 |
|---|---|
| ② Fix-B (server-side suggestion) — **본 트랙 out of scope** | Cog Phase 2 (Cognitive Middleware) 의 일부로 이동 — 별 PR (PR-O2 의 Fix-A 만으로 일단 사용자 경험 회복) |
| ⑤ (a) Entity dedup | Cog Phase 1 의 `alias_pump` 와 사이클 10 다국어 호환에 흡수 |
| ⑤ (b) Cross-doc weak edge | Cog Phase 1 의 `infer_weak_edges` 에 흡수 |

즉 ⑤ 두 항목은 **본 트랙에서 PR 만들지 않고** cognitive layer
Phase 1 PR-1/PR-2 진입 시 같이 처리. 본 트랙 PR 은 1~7 만.

---

## 6. CLAUDE.md gate 정합

| Rule | 본 트랙에서의 적용 |
|---|---|
| #1 도메인 분화 금지 | 본 트랙은 platform layer. 도메인 무관 |
| #2 bench numbers | PR-O4 (reasoning), PR-O5 (retrieval 분기 변경), PR-O6 (graph 편집) 은 STEP 7 회귀 0 확인 필수 |
| #3 자가-진화 게이트 | 무관 |
| #4 architecture 변경 | 새 trust zone 추가 없음. PR-O5 의 feature 신설은 catalog 변경만 (architecture-labelled 불필요) |
| #5 20 KB 모듈 | PR-O6 가 `core/graph_editor.py` 를 부풀릴 수 있음 — 분할 검토 |

---

## 7. 진행 순서 권장

```
PR-O1 (403 fix)            ← 즉시. ⑥-a 라이브 사용성 회복
PR-O2 (suggestion regex)   ← 즉시. ② Fix-A
PR-O3 (save spinner)       ← 즉시. ③
─────── 위 3 PR = "Quick fix 묶음" — 1~2 시간 ───────
PR-O4 (N-3 long_ctx)       ← 사용자 차단 회복
PR-O5 (external 격리)      ← 보안 정합
PR-O6 (노드 편집 + 직관성) ← Phase E follow-up
─────── 사이클 12 본 트랙 closure ───────
PR-O7 (drag + 클릭 연결)   ← 별 사이클 (선택)
```

병행 가능한 사이클 11 의 Phase 1 (Reranker, Query rewriter, ⑤ 흡수)
이 시작되면, 본 트랙의 ⑤ 항목 자동 closure.

---

## 8. 사용자 액션 대기

1. 본 핸드오버 검토 + 추가 피드백 (있다면)
2. PR-O1 진입 결정 시 알려주세요. 그 후 PR 7건 순차 진행
3. 사이클 8 라이브 검증 (#273/#274) 은 본 트랙 PR-O3 closure 이후
   여유 있을 때 (결정 #3 = B 에 따라 본 트랙 우선)

---

## 9. 한국어 요약

라이브 사용 중 발견된 6 결손을 7 PR 로 분리해서 차근차근 해결.
사용자 결정 4 건 (분리 PR / external 좁히기 / fix 먼저 / 별 트랙)
반영. 코드 작업은 사용자 지시 후 시작. cognitive layer 트랙 (사이클
11, PR #275) 과 병행 가능.
