feat(kebab-tui): P9-4 Inspect pane #46

Merged
altair823 merged 2 commits from feat/p9-4-tui-inspect into main 2026-05-02 16:00:54 +00:00
Owner

요약

Library Enter / Search i 가 활성화. Inspect 패널이 Doc 또는 Chunk 단일 view 로 metadata / provenance / blocks (doc) 또는 spans / text / embeddings (chunk) 6 section 을 collapsible 로 표시. Esc/q 로 originating pane (Library 또는 Search) 으로 복귀.

핵심 결정

  • InspectTarget enum: Doc(DocumentId) | Chunk(ChunkId). Library Enter → Doc, Search i → Chunk.
  • enter_inspect(state, target, return_to) helper: Library / Search 공통 entry point. return_to: Pane 가 Esc 시 원래 pane 으로 돌려보냄.
  • Lazy fetch: needs_fetch flag → run-loop idle tick refresh_inspectinspect_doc_with_config / inspect_chunk_with_config.
  • Collapse simplification: c 가 모든 section 일괄 toggle (v1, spec § "focus 기반 selective collapse 는 P+").
  • Pretty JSON metadata: metadata.userserde_json::to_string_pretty 로 indented 표시.

Spec deviation (HOTFIXES 2026-05-02 P9-4)

  1. render_inspect<B: Backend> generic 제거 (P9-1/2/3 와 동일).
  2. Search 패널에 i 키 추가 — spec 명시했지만 P9-2 머지 시점에 빠져 있던 entry. P9-4 가 retroactive 추가.
  3. c 일괄 collapse — spec 의 "focus 기반 selective" 는 P+ enhancement.

테스트 (12개, tests/inspect.rs)

  • Esc → return_to / q 도 동일
  • j/k scroll bounds (saturating)
  • PgUp/PgDn ±10
  • c 일괄 toggle (collapse all ↔ expand all)
  • no target hint render
  • loading message render
  • doc view header + metadata + provenance + blocks 모두 표시
  • doc view collapse 가 section body 숨김 검증
  • chunk view text + block_ids + heading_path joined + spans
  • no slot → SwitchPane(Library)
  • enter_inspect helper 가 target / return_to / needs_fetch 설정

Docs (sync rule)

  • README: TUI 행 "4 패널" + Quick start 코멘트.
  • HANDOFF: 한 줄 요약 + Phase status (P9 3/5 → 4/5) + deviation 한 줄.
  • HOTFIXES: P9-4 entry (3 deviation 명시).
  • tasks/p9/p9-4: status completed.

검증

  • cargo test -p kebab-tui 51 passed (10 library + 15 search + 14 ask + 12 inspect)
  • cargo clippy --workspace --all-targets -- -D warnings clean
  • cargo build --release -p kebab-cli clean

Test plan

  • 단위 12건 통과
  • clippy -D warnings
  • HOTFIXES + README + HANDOFF + spec status sync
  • 사용자 manual smoke: kebab tui → Library Enter → Inspect doc → c collapse → Esc → Search → 결과 → i → Inspect chunk → Esc

다음

P9 phase 4/5 완료. P9-5 (desktop tauri) 만 남음.

## 요약 Library Enter / Search `i` 가 활성화. Inspect 패널이 Doc 또는 Chunk 단일 view 로 metadata / provenance / blocks (doc) 또는 spans / text / embeddings (chunk) 6 section 을 collapsible 로 표시. Esc/q 로 originating pane (Library 또는 Search) 으로 복귀. ## 핵심 결정 - **`InspectTarget` enum**: `Doc(DocumentId) | Chunk(ChunkId)`. Library Enter → Doc, Search `i` → Chunk. - **`enter_inspect(state, target, return_to)` helper**: Library / Search 공통 entry point. `return_to: Pane` 가 Esc 시 원래 pane 으로 돌려보냄. - **Lazy fetch**: `needs_fetch` flag → run-loop idle tick `refresh_inspect` → `inspect_doc_with_config` / `inspect_chunk_with_config`. - **Collapse simplification**: `c` 가 모든 section 일괄 toggle (v1, spec § \"focus 기반 selective collapse 는 P+\"). - **Pretty JSON metadata**: `metadata.user` 는 `serde_json::to_string_pretty` 로 indented 표시. ## Spec deviation (HOTFIXES `2026-05-02 P9-4`) 1. `render_inspect<B: Backend>` generic 제거 (P9-1/2/3 와 동일). 2. Search 패널에 `i` 키 추가 — spec 명시했지만 P9-2 머지 시점에 빠져 있던 entry. P9-4 가 retroactive 추가. 3. `c` 일괄 collapse — spec 의 \"focus 기반 selective\" 는 P+ enhancement. ## 테스트 (12개, `tests/inspect.rs`) - Esc → return_to / q 도 동일 - j/k scroll bounds (saturating) - PgUp/PgDn ±10 - c 일괄 toggle (collapse all ↔ expand all) - no target hint render - loading message render - doc view header + metadata + provenance + blocks 모두 표시 - doc view collapse 가 section body 숨김 검증 - chunk view text + block_ids + heading_path joined + spans - no slot → SwitchPane(Library) - enter_inspect helper 가 target / return_to / needs_fetch 설정 ## Docs (sync rule) - README: TUI 행 \"4 패널\" + Quick start 코멘트. - HANDOFF: 한 줄 요약 + Phase status (P9 3/5 → 4/5) + deviation 한 줄. - HOTFIXES: P9-4 entry (3 deviation 명시). - tasks/p9/p9-4: status `completed`. ## 검증 - `cargo test -p kebab-tui` 51 passed (10 library + 15 search + 14 ask + 12 inspect) - `cargo clippy --workspace --all-targets -- -D warnings` clean - `cargo build --release -p kebab-cli` clean ## Test plan - [x] 단위 12건 통과 - [x] clippy `-D warnings` - [x] HOTFIXES + README + HANDOFF + spec status sync - [ ] 사용자 manual smoke: `kebab tui` → Library Enter → Inspect doc → c collapse → Esc → Search → 결과 → `i` → Inspect chunk → Esc ## 다음 P9 phase 4/5 완료. P9-5 (desktop tauri) 만 남음.
altair823 added 1 commit 2026-05-02 15:41:41 +00:00
Library Enter / Search 'i' 가 Inspect 진입. Doc 또는 Chunk 단일 view 로
metadata / provenance / blocks (doc) 또는 spans / text / embeddings (chunk)
6 section 을 collapsible 로 표시. Esc/q 로 originating pane 으로 복귀.

핵심:
- InspectTarget enum (`Doc(DocumentId) | Chunk(ChunkId)`).
- InspectState 본체 (`app.rs`) — target / doc / chunk / collapsed
  HashSet / scroll / return_to / needs_fetch / loading.
- `src/inspect.rs`:
  - `render_inspect` — target 종류별 render_doc / render_chunk 분기,
    section header 가 collapse marker (▾/▸) 표시. metadata.user JSON
    pretty-printed.
  - `handle_key_inspect`: j/k / Down/Up scroll. PageDown/PageUp 10 row.
    c = toggle all sections (v1 simplification). Esc/q = SwitchPane(return_to).
  - `enter_inspect(state, target, return_to)` helper — Library 와 Search
    공통 entry point.
  - run-loop hook `refresh_inspect` — needs_fetch 면 lazy
    inspect_doc_with_config / inspect_chunk_with_config.
- run.rs: Pane::Inspect arm 이 handle_key_inspect + render_inspect.
  Idle tick 마다 refresh_inspect. SwitchPane(Inspect) lazy init.
- Library: Enter 가 enter_inspect(Doc(selected)) 호출 후 SwitchPane.
- Search: 'i' (plain modifier) 가 enter_inspect(Chunk(selected_hit))
  호출 후 SwitchPane. typing 'i' (\"instance\") 와 충돌 가드.

테스트 12개 (`tests/inspect.rs`, TestBackend) — Esc 가 return_to 사용
/ q 도 동작 / j/k scroll bounds / PgUp PgDn ±10 / c 일괄 toggle / no
target hint / loading / doc view header+metadata+provenance+blocks /
collapse hides body / chunk view text+block_ids / no slot →
SwitchPane(Library) / enter_inspect helper sets fields.

Spec deviation (HOTFIXES `2026-05-02 P9-4`):
- `render_inspect<B: Backend>` generic 제거 (P9-1/2/3 와 동일).
- Search `i` 키 추가 (P9-2 spec 에 없었음, P9-4 retroactive 추가).
- `c` 일괄 collapse — spec 의 \"focus 기반 selective collapse\" 는 P+.

Docs (sync rule):
- README: TUI 행 \"4 패널\" + Quick start 코멘트.
- HANDOFF: 한 줄 요약 + Phase status (P9 3/5 → 4/5) + deviation 한 줄.
- HOTFIXES: P9-4 entry.
- tasks/p9/p9-4 status: completed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
claude-reviewer-01 requested changes 2026-05-02 15:42:52 +00:00
claude-reviewer-01 left a comment
Member

회차 1 — collapse 일관성 issue 2건. blocks/embeddings 의 count 라인이 collapse 검사 밖에서 push 되어 collapsed 상태에서 부분만 사라짐. fix: section header 에 inline count (예: ) 후 body 만 collapse 검사 안. 그 외 suggestion 1 (scroll bound — follow-up scope) + 칭찬 (enter_inspect helper, NLL 코멘트, Search i 컨벤션 일관, HOTFIXES retroactive 명시).

회차 1 — collapse 일관성 issue 2건. blocks/embeddings 의 count 라인이 collapse 검사 밖에서 push 되어 collapsed 상태에서 부분만 사라짐. fix: section header 에 inline count (예: ) 후 body 만 collapse 검사 안. 그 외 suggestion 1 (scroll bound — follow-up scope) + 칭찬 (enter_inspect helper, NLL 코멘트, Search i 컨벤션 일관, HOTFIXES retroactive 명시).
@@ -0,0 +323,4 @@
};
format!("Paragraph: {trimmed}")
}
Block::Quote(q) => format!("Quote: {} chars", q.text.len()),

(issue / collapse 작동 안 함) blocks 섹션에서 count = N 표시가 collapse 검사 에 무조건 push 됨 — collapsed 상태에서 count 가 그대로 보임. 다른 섹션 (metadata / provenance) 은 collapse 검사 안에 모든 line 이 들어가 있어 일관성 깨짐.

Why: 사용자 입장에서 c 누르면 6 섹션 일괄 collapse 되는데 blocks 의 count 라인만 사라지지 않음. snapshot 테스트가 collapse 후 "section header still visible" 만 검증하고 "count line hidden" 은 검증 안 해서 issue 가 회귀 grid 에 안 잡힘.

How to apply: count = N 라인을 collapse 검사 안으로 옮기거나, count 를 section header 옆에 붙임 (예: ▾ blocks (N)). 후자가 정보 dense 하면서 collapse 의도 그대로.

lines.push(Line::from(Span::styled(
    format!("{marker} blocks ({})", doc.blocks.len()),
    ...
)));
if !s.collapsed.contains(SECTION_BLOCKS) {
    for (i, b) in doc.blocks.iter().take(preview_n).enumerate() { ... }
}

회귀 테스트도 "collapsed 상태에서 block describe 가 사라짐" 검증 추가.

(issue / collapse 작동 안 함) `blocks` 섹션에서 `count = N` 표시가 collapse 검사 *전* 에 무조건 push 됨 — collapsed 상태에서 count 가 그대로 보임. 다른 섹션 (metadata / provenance) 은 collapse 검사 안에 모든 line 이 들어가 있어 일관성 깨짐. Why: 사용자 입장에서 `c` 누르면 6 섹션 일괄 collapse 되는데 blocks 의 count 라인만 사라지지 않음. snapshot 테스트가 collapse 후 "section header still visible" 만 검증하고 "count line hidden" 은 검증 안 해서 issue 가 회귀 grid 에 안 잡힘. How to apply: `count = N` 라인을 collapse 검사 안으로 옮기거나, count 를 section header 옆에 붙임 (예: `▾ blocks (N)`). 후자가 정보 dense 하면서 collapse 의도 그대로. ```rust lines.push(Line::from(Span::styled( format!("{marker} blocks ({})", doc.blocks.len()), ... ))); if !s.collapsed.contains(SECTION_BLOCKS) { for (i, b) in doc.blocks.iter().take(preview_n).enumerate() { ... } } ``` 회귀 테스트도 "collapsed 상태에서 block describe 가 사라짐" 검증 추가.
@@ -0,0 +407,4 @@
// simplify by toggling all sections").
toggle_all_sections(s);
KeyOutcome::Continue
}

(issue / collapse 작동 안 함) 같은 패턴 — block_ids = N 라인이 collapse 검사 에 있지만 (embedding records not loaded — out of v1 scope) 라인 자체가 항상 collapse 안에서만 보이고 헤더는 별개. 그런데 위 issue 와 일관성 위해 한번에 정리: SECTION_EMBEDDINGS 도 같은 패턴 (header 에 카운트 inline) 이면 모든 섹션이 동일 모양.

How to apply: ▾ embeddings (block_ids=2) 같은 inline count. body 는 "(records not loaded)" + each block_id list 만.

(issue / collapse 작동 안 함) 같은 패턴 — `block_ids = N` 라인이 collapse 검사 *안* 에 있지만 `(embedding records not loaded — out of v1 scope)` 라인 자체가 항상 collapse 안에서만 보이고 헤더는 별개. 그런데 위 issue 와 일관성 위해 한번에 정리: SECTION_EMBEDDINGS 도 같은 패턴 (header 에 카운트 inline) 이면 모든 섹션이 동일 모양. How to apply: `▾ embeddings (block_ids=2)` 같은 inline count. body 는 "(records not loaded)" + each block_id list 만.

(suggestion / scroll 한계) scroll = scroll.saturating_add(1) 가 위로는 무한 스크롤 가능 — 빈 영역이 본문 위로 끝없이 올라감. 사용자가 j 만 누르고 있으면 scroll 값이 큰 정수로 이르고 다시 위로 스크롤 못 함 (k 가 1씩 빼므로 시간 걸림). 이 패널은 텍스트 길이가 가변이라 max scroll 계산이 까다롭지만, 적어도 build_doc_lines / build_chunk_lines 의 line 수를 미리 캐시 + max bound 검사 하면 UX 안정.

Why: scroll 이 u16::MAX 까지 갔다가 사용자가 "왜 위��� 안 가지" 의문 발생 → 무한 PgUp 으로 escape 해야 함.

How to apply (선택): InspectState 에 total_lines: u16 캐시 → render 직전 build 에서 갱신 → j arm 이 if scroll < total_lines.saturating_sub(viewport_h) { scroll += 1 }. 본 PR 에서는 scope 외 — follow-up 가능.

(suggestion / scroll 한계) `scroll = scroll.saturating_add(1)` 가 위로는 무한 스크롤 가능 — 빈 영역이 본문 위로 끝없이 올라감. 사용자가 j 만 누르고 있으면 scroll 값이 큰 정수로 이르고 다시 위로 스크롤 못 함 (k 가 1씩 빼므로 시간 걸림). 이 패널은 텍스트 길이가 가변이라 max scroll 계산이 까다롭지만, 적어도 build_doc_lines / build_chunk_lines 의 line 수를 미리 캐시 + max bound 검사 하면 UX 안정. Why: scroll 이 `u16::MAX` 까지 갔다가 사용자가 "왜 위��� 안 가지" 의문 발생 → 무한 PgUp 으로 escape 해야 함. How to apply (선택): InspectState 에 `total_lines: u16` 캐시 → render 직전 build 에서 갱신 → `j` arm 이 `if scroll < total_lines.saturating_sub(viewport_h) { scroll += 1 }`. 본 PR 에서는 scope 외 — follow-up 가능.

(칭찬) enter_inspect helper 가 Library / Search 두 호출자에서 같은 진입 path 를 쓰도록 만든 게 좋습니다. return_to: Pane 가 Esc 시 정확히 원래 pane 으로 돌려보내고, needs_fetch = true 가 run-loop idle tick 에 위임 — 호출자가 fetch 를 직접 트리거 안 함. 미래 P9-5 desktop 가 동일 helper 재사용 가능.

(칭찬) `enter_inspect` helper 가 Library / Search 두 호출자에서 같은 진입 path 를 쓰도록 만든 게 좋습니다. `return_to: Pane` 가 Esc 시 정확히 원래 pane 으로 돌려보내고, `needs_fetch = true` 가 run-loop idle tick 에 위임 — 호출자가 fetch 를 직접 트리거 안 함. 미래 P9-5 desktop 가 동일 helper 재사용 가능.
@@ -282,0 +287,4 @@
// we can re-borrow `state` mutably for the inspect-side
// mutation below.
let target = crate::app::InspectTarget::Doc(doc_id);
crate::inspect::enter_inspect(state, target, Pane::Library);

(칭찬) Library Enter 에서 enter_inspect 호출 + SwitchPane(Inspect). NLL 코멘트 가 "왜 inner 별도 drop 없이 state 재차용 가능한지" 명시 — 미래 reader 가 borrow checker 마술이라고 오해할 위험 차단. 한 줄짜리 코멘트 가 큰 가치.

(칭찬) Library Enter 에서 `enter_inspect` 호출 + SwitchPane(Inspect). NLL 코멘트 가 "왜 inner 별도 drop 없이 state 재차용 가능한지" 명시 — 미래 reader 가 borrow checker 마술이라고 오해할 위험 차단. 한 줄짜리 코멘트 가 큰 가치.
@@ -184,0 +193,4 @@
if s.hits.is_empty() {
None
} else {
Some(s.hits[s.selected_hit].chunk_id.clone())

(칭찬) i 키 가 g 키 와 같은 "plain modifier only" 컨벤션 적용. 사용자가 "instance" / "interface" 같은 검색어 입력 시 첫 'i' 가 input 으로 가게 — P9-2 의 SHIFT/none 분리 패턴 일관 유지.

(칭찬) `i` 키 가 `g` 키 와 같은 "plain modifier only" 컨벤션 적용. 사용자가 "instance" / "interface" 같은 검색어 입력 시 첫 'i' 가 input 으로 가게 — P9-2 의 SHIFT/none 분리 패턴 일관 유지.
@@ -17,0 +19,4 @@
**Discovered**: P9-4 implementation start.
**Symptom 1 (cosmetic)**: Same shape as P9-1/2/3 — `tasks/p9/p9-4-tui-inspect.md` § Public surface declares `render_inspect<B: ratatui::backend::Backend>(...)`. ratatui 0.28's `Frame` is backend-agnostic; the generic is unused.

(칭찬) HOTFIXES entry 가 spec 의 3 deviation 을 분리 명시 — generic / Search i 누락 / collapse simplification. 특히 Symptom 2 (Search 가 P9-2 머지 시점에 i 미포함) 는 이전 task 의 spec 누락이 retroactively 잡힌 케이스 — "누가 빠뜨렸는지" 가 아니라 "누가 채워 넣었는지" 로 표현 정직.

(칭찬) HOTFIXES entry 가 spec 의 3 deviation 을 분리 명시 — generic / Search `i` 누락 / collapse simplification. 특히 Symptom 2 (Search 가 P9-2 머지 시점에 `i` 미포함) 는 이전 task 의 spec 누락이 retroactively 잡힌 케이스 — "누가 빠뜨렸는지" 가 아니라 "누가 채워 넣었는지" 로 표현 정직.
altair823 added 1 commit 2026-05-02 15:44:17 +00:00
blocks / embeddings 섹션의 count 라인이 collapse 검사 *밖* 에서 push
되어 collapsed 상태에서 부분만 사라지던 일관성 깨짐. fix: count 를
section header 에 inline 으로 (`▾ blocks (N)`, `▾ embeddings (N)`),
body 만 collapse 검사 안. 새 helper `push_section_header_with_count`
가 둘 다 통일.

회귀 테스트 보강:
- doc_view_collapse_hides_section_body: collapsed 상태에서 \"blocks (2)\"
  inline count 표시 + \"Heading L1\" body 숨김 검증.
- chunk_view_renders_text_and_block_ids: \"embeddings (2)\" inline
  count 검증.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
claude-reviewer-01 approved these changes 2026-05-02 15:44:42 +00:00
claude-reviewer-01 left a comment
Member

회차 2 — 회차 1 지적 1건 (collapse 일관성) 반영. blocks/embeddings 의 count 가 section header inline 으로 이동, body 만 collapse 검사. 회귀 테스트 보강. 51 tests pass + clippy clean. 머지 가능. P9 phase 4/5 완성, P9-5 desktop 만 남음.

회차 2 — 회차 1 지적 1건 (collapse 일관성) 반영. blocks/embeddings 의 count 가 section header inline 으로 이동, body 만 collapse 검사. 회귀 테스트 보강. 51 tests pass + clippy clean. 머지 가능. P9 phase 4/5 완성, P9-5 desktop 만 남음.
@@ -0,0 +329,4 @@
}
}
fn describe_block(b: &Block) -> String {

(칭찬) blocks 섹션 의 count 가 section header 에 inline. 사용자가 collapsed 상태에서 "이 doc 가 몇 개 block 이 있나" 답을 헤더에서 즉시 얻음 — collapse 의 의도 (스캔 없이 overview) 정확히 충족.

(칭찬) blocks 섹션 의 count 가 section header 에 inline. 사용자가 collapsed 상태에서 "이 doc 가 몇 개 block 이 있나" 답을 헤더에서 즉시 얻음 — collapse 의 의도 (스캔 없이 overview) 정확히 충족.
@@ -0,0 +477,4 @@
InspectTarget::Doc(doc_id) => {
let result = kebab_app::inspect_doc_with_config(cfg, &doc_id);
let s = state.inspect.as_mut().unwrap();
s.loading = false;

(칭찬) push_section_header_with_count helper 가 두 use case (count 있음 / 없음) 를 한 함수로 통합. metadata / provenance / spans / text 는 count 없이 (None), blocks / embeddings 는 count inline. 호출 site 모두 readable + 일관.

(칭찬) `push_section_header_with_count` helper 가 두 use case (count 있음 / 없음) 를 한 함수로 통합. metadata / provenance / spans / text 는 count 없이 (None), blocks / embeddings 는 count inline. 호출 site 모두 readable + 일관.
@@ -0,0 +242,4 @@
assert!(rendered.contains("Test Doc"), "title rendered");
assert!(rendered.contains("notes/test.md"), "doc_path rendered");
assert!(rendered.contains("test-parser"), "parser_version rendered");
assert!(rendered.contains("metadata"), "metadata section visible");

(칭찬) collapse 회귀 테스트가 (1) "blocks (2)" inline count 표시 (2) Heading L1 body 숨김 (3) provenance kb-source-fs 숨김 세 invariant 모두 검증. 미래에 누군가 count 라인을 다시 collapse 검사 밖으로 옮기면 즉시 빨개짐.

(칭찬) collapse 회귀 테스트가 (1) "blocks (2)" inline count 표시 (2) Heading L1 body 숨김 (3) provenance kb-source-fs 숨김 세 invariant 모두 검증. 미래에 누군가 count 라인을 다시 collapse 검사 밖으로 옮기면 즉시 빨개짐.
altair823 merged commit e0b2ae4db6 into main 2026-05-02 16:00:54 +00:00
altair823 deleted branch feat/p9-4-tui-inspect 2026-05-02 16:00:55 +00:00
Sign in to join this conversation.
No Reviewers
No Label
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: altair823-org/kebab#46