도그푸딩 item 9 — TUI Ask 답변 본문이 raw `**bold**` / `# Title` /
` ```code``` ` 그대로 보여 가독성 떨어지던 문제 해소. pulldown-cmark
파싱 → ratatui Span/Line 변환.
## 핵심 변경
- **`kebab-tui::markdown::render(text, &Theme) -> Vec<Line<'static>>`**
신규. pulldown-cmark = "0.13" (이미 kebab-parse-md 가 사용 중인
버전) 위에 build.
inline:
- `**bold**` / `__bold__` → `Modifier::BOLD`
- `*italic*` / `_italic_` → `Modifier::ITALIC`
- `~~strike~~` → `Modifier::CROSSED_OUT`
- `` `code` `` → `Role::Hint` (DIM 스타일 — 터미널 호환성 위해 bg
color 보다 안전)
- `[text](url)` → `Role::CitationMarker` + `Modifier::UNDERLINED`
block:
- heading H1/H2 → `Role::Heading` (Cyan + BOLD), H3-H6 → `Role::Title`
(White + BOLD)
- bullet list `-`/`*` → `- ` + 깊이별 indent
- ordered list `1.` → 실제 번호 prefix + indent
- fenced code block ``` ``` ``` → ` ` indented + `Role::Hint`
- blockquote `>` → 좌측 `▎` bar (중첩 시 반복) + `Role::Hint`
- table `| col |` → `| col1 | col2 |` 식 줄, `|` separator 색 강조
- horizontal rule `---` → `─` × 40
- **streaming 안전성**: 매 frame 재 parse 가 spec — pulldown
토크나이저가 µs/KB 라 비용 무시. unterminated `**` (사용자가 한창
입력 중인 inline 가 닫히기 전) 은 pulldown 이 Text 로 처리 →
literal `**` 그대로 표시 (글자 누락 X).
- **`ask::push_turn_lines` 통합**: grounded 답변에서만 markdown
렌더 사용. refusal turn (`Role::Warning` override) 와 streaming
turn (`Role::Hint`) 은 raw 로 두어 role color 시그널이 markdown
스타일에 묻히지 않도록. body line 들은 ` ` indent 로 transcript
에서 답변 본문 시각 구분.
- **CLI `kebab ask` 출력은 raw markdown** — 터미널 호환성 + pipe
처리 시 안정성 위해 (ANSI escape 없이 plain text).
## 테스트 (markdown.rs 14 unit)
- empty input → 빈 라인 1 줄 (caller scroll/measure 안전)
- plain text → 단일 라인 + paragraph blank
- bold / italic / strikethrough / inline code → 해당 modifier 검증
- link → UNDERLINED 검증
- heading H1 → BOLD 텍스트 span
- bullet list `-` / numbered list `1./2.` → prefix 검증
- code fence body → 줄별 ` ` indent 보존
- blockquote → `▎` prefix
- 2x2 table → `|`-separated 줄 검증
- unterminated `**` → 글자 누락 없음 (streaming 안전성 회귀 방지)
- composite (heading + para + list + code) → 문서 순서 보존
기존 75 TUI 테스트 + 신규 14 markdown = 89 통과. clippy clean.
## 문서
- README `kebab tui` 행에 markdown 렌더 안내 + CLI 는 raw 명시
- HANDOFF: 2026-05-03 entry
- spec status planned → in_progress
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
46 lines
1.7 KiB
Rust
46 lines
1.7 KiB
Rust
//! `kebab-tui` — Ratatui shell + Library pane (P9-1).
|
|
//!
|
|
//! Per design §8 module boundary: UI crates may only touch the
|
|
//! `kebab-app` facade. The store / search / embed / llm / rag layers
|
|
//! stay invisible behind it. P9-1 establishes the shell (App loop,
|
|
//! key dispatch, error popup, raw-mode panic guard) plus the Library
|
|
//! pane. P9-2/3/4 plug into the same `App` struct via the
|
|
//! `Option<*State>` slot pattern (parallel-safety: their sub-state
|
|
//! types start as `pub struct *State;` opaque forward declarations
|
|
//! and only their authoring crate fills the body).
|
|
//!
|
|
//! Per report §16.2 (TUI epic), design §1 (UX scenes), design §3.7
|
|
//! (`SearchHit` / `DocSummary`).
|
|
|
|
mod app;
|
|
mod ask;
|
|
mod editor;
|
|
mod error_popup;
|
|
mod ingest_progress;
|
|
mod inspect;
|
|
mod library;
|
|
mod markdown;
|
|
mod run;
|
|
mod search;
|
|
mod terminal;
|
|
mod theme;
|
|
|
|
pub use theme::{Palette, Role, Theme};
|
|
pub use app::{
|
|
App, AskState, IngestState, InspectState, InspectTarget, KeyOutcome, LibraryState, Pane,
|
|
SearchState, TERMINAL_LINE_HOLD_SECS,
|
|
};
|
|
pub use ask::{handle_key_ask, render_ask};
|
|
pub use error_popup::{ErrorOverlay, render_error_overlay};
|
|
pub use ingest_progress::{
|
|
cancel_running_ingest, drain_progress, ready_to_clear, start_ingest, status_line,
|
|
};
|
|
pub use inspect::{enter_inspect, handle_key_inspect, render_inspect};
|
|
pub use library::{handle_key_library, render_library};
|
|
// `editor::with_external_program` and `search::jump_to_citation`
|
|
// stay `pub(crate)` — they take the internal `TuiTerminal` handle,
|
|
// which is intentionally module-private (its `Drop` lifecycle is the
|
|
// only safe constructor path for raw mode + alt-screen). External
|
|
// callers stage editor spawns via `App.pending_editor` instead.
|
|
pub use search::{build_jump_command, handle_key_search, render_search};
|