feat(tui): Ask conversation transcript UI (p9-fb-16) #62
Reference in New Issue
Block a user
Delete Branch "feat/p9-fb-16-tui-conv"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
도그푸딩 항목 13 (꼬리 물기) 의 TUI surface. AskState 재설계 + transcript layout + ask_with_history wiring + Ctrl-L reset. p9-fb-15 (#60) 의 RAG facade 위에 build.
변경
kebab-tui::app::AskStatekebab-tui::askspawn_ask_worker: 첫 submit 에conversation_id = Some(make_conversation_id())자동 생성 (timestamp-based:conv_<unix_nanos_hex>).input→current_question,inputclear.history = turns.clone(),turn_index = turns.len() as u32. worker 가kebab-app::ask_with_config(facade) 호출, opts 에 history/conversation_id/turn_index 모두 채움 → RagPipeline::ask_with_history 까지 thread.poll_worker: Answer 받으면 새 Turn 만들어turns.push,last_answer도 보존. UI 는 다음 frame 부터 새 turn 보여줌.handle_key_ask:Ctrl-L가 turns + conversation_id + last_answer + partial + current_question + scroll 모두 초기화 (in-flight worker 는 그대로 finish, 결과는 새 conv 의 stale turn 으로 silently 폐기).render_answer: 완료 turns chronological 출력 + in-flight turn (있으면) 가장 아래. Q/A 라벨 색상 구분 (Q cyan bold, A green bold), in-flight answer 는 ▍ cursor + dim. transcript title 에 turn count.Test
ctrl_l_clears_conversation_state— Ctrl-L 가 모든 conversation field 초기화.render_transcript_shows_completed_turns_in_order— Q1 < Q2 chronological.render_streaming_inflight_turn_appears_below_completed_turns— 완료 turn 다음에 ▍ cursor 가진 in-flight turn.README + HANDOFF
TUI 행에 multi-turn 동작 추가 ("Q1/A1, Q2/A2 transcript 누적, 다음 질문이 이전 턴을 history 로 받아 답변. Ctrl-L 로 새 conversation 시작").
후속
in_progress→completed.--session/--repl) 가 같은 facade 위에 build.Multi-turn ask pane. AskState 가 turns: Vec<Turn> + current_question + conversation_id + last_answer 로 재설계. answer area 가 transcript 형식 (Q1/A1, Q2/A2, ...) 로 갈음, 매 Enter 가 이전 turns 를 history 로 worker 에 전달 — RagPipeline::ask_with_history 호출. 신규 (kebab-tui::app): - AskState 에 turns / current_question / conversation_id / last_answer 4 field 추가. 기존 answer field 제거 (last_answer 가 갈음). 신규 (kebab-tui::ask): - spawn_ask_worker: 첫 submit 시 conversation_id 자동 생성 (conv_<unix_nanos_hex>), input → current_question, input clear. history = turns.clone(), turn_index = turns.len(). worker 가 ask_with_history 호출 (kebab-app facade 가 _cancellable 통해 RagPipeline::ask_with_history 까지 thread). - poll_worker: Answer 받으면 Turn { question: current_question, answer, citations, created_at } 만들어 turns 에 push, last_answer 도 보존. - handle_key_ask: Ctrl-L 가 turns + conversation_id 초기화 (in-flight worker 는 그대로 finish — 결과는 새 conversation 의 stale turn 으로 silently 폐기, 사용자 의도와 일치). - render_answer: 모든 completed turns + (있으면) in-flight turn chronological 출력. Q/A 라벨 색상 구분 (Q cyan bold, A green bold). in-flight answer 는 ▍ cursor + dim. transcript title 에 turn count. - render_status / render_citations_or_explain: s.last_answer 사용. Test: - 17 PASS (3 신규: ctrl_l_clears_conversation_state / render_transcript_shows_completed_turns_in_order / render_streaming_inflight_turn_appears_below_completed_turns). - 기존 14 회귀 0 (기존 s.answer → s.last_answer + Turn fixture push). README + HANDOFF: TUI 행에 multi-turn 동작 추가. spec status planned → in_progress. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>회차 1 — UX 회귀 1건 (transcript refusal yellow 손실) + race risk 1건 (Ctrl-L 이 thread/rx clear 안 함 → 새 conv 에 stale turn 끼임) + 칭찬 2건. fix: push_turn_lines 가 last turn 색상 분기 + Ctrl-L 분기에 thread=None/rx=None/streaming=false 추가.
(칭찬)
AskState의 4 신규 field (turns/current_question/conversation_id/last_answer) 모두 doc comment 가 lifecycle 명시 — 각 field 가 언제 set / clear 되는지. 미래 reader 가 "turns 는 언제 push?" / "current_question 은 언제 None?" 의문 코드 옆에서 즉시 답.Defaultderive 유지 (모두 Vec::new / None 기본값) — 기존 caller (App::new 의ask: None) 변경 0.@@ -96,1 +129,4 @@out.push(Line::from(vec![Span::styled(q_label,Style::default()(칭찬) transcript title format
transcript (N turn{s})가 user-visible state 노출 — 사용자 한눈에 "지금 N 번째 follow-up" 인지 파악. 영어 plural 정확 처리 (turnvsturns). pre-submission 빈 상태는 plain "transcript" 로 fallback —(0 turns)같은 noise 없음.(UX 회귀 / refusal style 손실) 이전 single-shot
s.answer렌더링은 refusal (grounded=false) turn 을 yellow text 로 표시 → 사용자가 sourced 답변과 거절 즉시 구분. 새 transcript 는 모든 turn 동일 default style — refusal 도 normal 답변과 시각 구분 0.Why: 도그푸딩 항목 13 의 multi-turn 요구는 transcript 누적 + history wiring — 시각 구분을 jeo 변경 의도 X. 이전 yellow refusal 색상 contract (P9-3 의 design choice) 가 silently dropped — 회귀.
How to apply:
push_turn_lines에grounded: bool인자 추가 (또는Turn구조체 안에 grounded 정보가 없으니 caller 가 알 수 있는 방식 — last_answer.grounded 와 turn 의 매칭). 가장 단순:Turnstruct 자체에는 grounded 없음 (kebab-core).권장:
last_answer.grounded == false일 때 마지막 turn 의 A 라인만 yellow. 또는 전체 turn yellow (강한 시각 — 사용자 친화). 가장 작은 surface: 마지막 turn 만.@@ -203,3 +257,4 @@}(KeyCode::Esc, _) => {// Best-effort cancellation per spec — worker keeps running// but its result is dropped. Detach by clearing rx /(race / Ctrl-L 후 stale turn) Ctrl-L 가 turns/conversation_id 등 6 field 초기화 하지만
thread/rx/streaming은 그대로 둠. in-flight worker 가 다음 frame 에 finish → poll_worker 가 새 Turn 만들어 빈 turns 에 push. 결과: 새 conversation 의 첫 turn 이 사실 이전 conversation 의 답변 (질문 current_question 도 clear 됐는데 worker 가 받은 query 는 여전히 old). 사용자에게 confusing.Why: spec p9-fb-16 의 "Ctrl-L 가 turns + conversation_id 초기화 (in-flight worker 는 그대로 finish, 결과는 새 conversation 의 stale turn 으로 silently 폐기)" — "silently 폐기" 가 핵심이지만 현재 구현은 폐기 안 됨, 새 conversation 으로 들어감.
How to apply: Ctrl-L 분기에
s.thread = None; s.rx = None; s.streaming = false;추가. JoinHandle Drop onNone할당 = detach (기존 P9-3 Esc 핸들러와 같은 패턴). worker thread 는 background 에서 계속 돌아 SQLiteanswers에 commit (정상 — "실패한 turn" 흔적 보존), TUI 는 결과 무시.회차 2 — 회차 1 actionable 2건 (refusal yellow / Ctrl-L race) 정확히 반영. 18 PASS (1 신규: refusal yellow buffer cell 검사). APPROVE.