feat(kebab-tui): p9-fb-10 partial — CJK width helpers + render audit #87
Reference in New Issue
Block a user
Delete Branch "feat/p9-fb-10-cjk"
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
p9-fb-10 (TUI CJK input + wide-char rendering audit) partial ship.
kebab-tui::input::{display_width, truncate_to_display_width}helper모듈 신규 —
unicode-width위에서 column-단위 width 계산 (ASCII=1,Hangul/CJK/fullwidth=2, combining=0) + char-boundary 안전 truncate.
library.rs의 중복truncate_to_display_widthprivate fn 제거 — 모든pane 이 단일 source 호출.
Scope
crates/kebab-tui/src/input.rs신규 (162 lines, helper + 9 unit tests)crates/kebab-tui/src/lib.rsmod input;+pub use등록crates/kebab-tui/src/library.rs중복 helper 제거 →crate::input::truncate_to_display_width호출crates/kebab-tui/tests/library.rsKorean + Japanese fixture render audit (TestBackend 80×20)tasks/p9/p9-fb-10-tui-cjk-input.mdstatusplanned→in_progresstasks/HOTFIXES.mdInputBuffer struct deferral 사유 + IME composing out-of-scope 기록Deferred
spec 의
InputBufferstruct (cursor 가 column 단위 wide-char width 추적) 는follow-up — Ask/Search/Editor pane 의 String + cursor 일괄 마이그레이션이
회귀 표면이 커서 helper 만 먼저 머지. 백스페이스는 모든 pane 이 이미
String::pop()사용 (char-aware) → byte-boundary 안전성은 helper 없이도확보된 상태였고, 본 PR 의 helper 는 rendering width 만 정정.
crossterm 0.28 이 native IME composing surface 미노출 — finalized jamo /
composed glyph 가
KeyCode::Char(c)로만 도달. preedit handling out ofscope (spec 도 명시).
Test plan
cargo test -p kebab-tui— 25 + 9 + ... full suite greencargo clippy -p kebab-tui --all-targets -- -D warningsclean`kebab-tui::input::{display_width, truncate_to_display_width}` 신규. unicode-width 위에서 column-단위 width 계산 (ASCII=1, Hangul/CJK/ fullwidth=2, combining=0) + char-boundary 안전 truncate. library.rs 의 중복 `truncate_to_display_width` private fn 제거 — 단일 source 로 통일. 9 unit tests + 1 integration render test (Korean + Japanese fixture, TestBackend 80×20). spec 의 `InputBuffer` struct 도입은 follow-up — Ask/Search/Editor pane 의 String + cursor 일괄 마이그레이션이 회귀 표면이 커서 helper 만 먼저 머지. backspace 는 모든 pane 이 이미 `String::pop()` 사용 → byte- boundary 안전성 확보. crossterm 0.28 가 native IME composing 미노출 → preedit handling out of scope. spec status `planned` → `in_progress`. HOTFIXES.md 에 InputBuffer struct deferral 사유 기록.회차 1 — 핵심 actionable 1건 + nit 2건.
핵심: library.rs
format_doc_row의{title:<title_w$}padding 이 std::fmt 의 char-count 기반이라 wide char title 에서 column drift. 본 PR 이 truncate 는 정확히 잡지만 padding 은 안 잡아 — 같은 PR 에 같이 가는 게 일관 (CJK column 정확성 = 본 PR 의 stated goal).nit: input.rs 테스트 코멘트의
= wait잔재 + HOTFIXES 의 후속 체크리스트가 owner 없이 떠 있음.@@ -0,0 +97,4 @@/// p9-fb-10: mixed ASCII + Hangul sums correctly.#[test]fn mixed_ascii_hangul_width() {// "kb-한글" = 2 ASCII + 1 dash + 2 Hangul × 2 = 5 + 4 = waitnit (cosmetic): 테스트 코멘트에 디버깅 흔적 남음 —
// "kb-한글" = 2 ASCII + 1 dash + 2 Hangul × 2 = 5 + 4 = wait의= wait가 thinking-out-loud 잔재. 의도는 다음 줄의 정정 ("k" 1 + "b" 1 + ...= 7) 이지만 코멘트 한 줄로 충분:// "kb-한글" = k(1) + b(1) + -(1) + 한(2) + 글(2) = 7.Padding bug — pre-existing, surfaced by 본 PR 의 CJK 머지:
format!("{title:<title_w$}", ...)는 std::fmt 의 width 가 char count 기반이라 wide char 가 들어가면 padding 이 모자라. 예: title=러스트로 만드는 지식 베이스(13 chars / 25 cols), title_w=30 → fmt 는 30-13=17 spaces 추가 → 총 13+17=30 chars 지만 display 는 25+17=42 cols 가 되어 columns 가 밀림. 본 PR 의 truncate_to_display_width 로 title.width() <= title_w 는 보장되지만 padding 후의 over-width 는 별개. 수정안: pad 를 명시적으로 계산 —let pad = title_w.saturating_sub(crate::input::display_width(&title)); let title = format!("{title}{:width$}", "", width = pad);후 format string 에서{title}만 (no<title_w$>). 같은 pane 의tags:<12/updated_short:<10도 동일 — tags 에 한글이 들어가면 (이번 PR test fixture한글tag) column drift. 본 PR scope 가 helper + 단일 source 통합이므로 padding 수정은 follow-up commit (또는 별 PR) 으로 splitting 할 만하지만, 기왕 CJK 머지하는 김에 같은 PR 에 끼우는 게 사용자 입장 일관.@@ -17,0 +38,4 @@handling 은 out-of-scope (spec 도 "not in scope" 로 명시).**후속 spec issue**: InputBuffer 도입 시 (a) 모든 pane 의 input string을 InputBuffer 로 교체, (b) cursor render 가 wide-char 위에서 columnnit:
**후속 spec issue**: ...절이 actionable owner 없이 떠 있음. (a)/(b)/(c) 가 InputBuffer follow-up PR 에 들어갈 항목이라는 건 명확하지만, 어디에 적힐지 (별 spec issue 파일? 본 spec 의 DoD 갱신?) 가 모호. 제안:tasks/p9/p9-fb-10-tui-cjk-input.md의## Notes절 안에### Follow-up checklist로 옮기거나, 같은 HOTFIXES 항목 내에**후속 PR 체크리스트**:로 명시. 현재 상태로는 사람이 spec 만 봐도 다음 작업 파악 가능해야 한다는 frozen-spec 원칙에 살짝 어긋남.회차 2 — 회차 1 actionable 모두 반영됨 (format_doc_row padding 정확화 + 코멘트 cleanup + HOTFIXES checklist owner 명시). 신규 nit 2 건만 — regression hardening + maintainability.
본 회차 actionable: 직접 unit test (format_doc_row 의 column drift 회귀 catch) + tags column width const 추출.
@@ -193,43 +193,33 @@ pub(crate) fn format_doc_row(d: &DocSummary, title_w: usize) -> String {} else {d.tags.join(",")};let tags = truncate_to_display_width(&tags, 12);nit: tags column width
12가 두 군데 반복 (line 196 truncate_to_display_width 인자 + line 20912usize.saturating_sub). 한 곳에서 바꾸고 다른 곳을 잊으면 column drift 재발.const TAGS_COL_W: usize = 12;으로 빼면 maintainability ↑. title_w 는 area.width 에서 동적 계산되니 그대로 두고, tags 만.@@ -205,0 +206,4 @@// from `display_width`, then concatenate — the truncate above// already guarantees `display_width(title) <= title_w`.let title_pad = title_w.saturating_sub(display_width(&title));let tags_pad = 12usize.saturating_sub(display_width(&tags));nit (regression hardening): format_doc_row 의 padding 수정 자체에 대한 직접 unit test 가 없음 — render 테스트 (
library_renders_korean_titles_without_overflow) 가 글자 presence 만 확인하지 column alignment 는 안 확인. 즉 누군가<title_w$>로 되돌려도 render test 는 통과. 제안:format_doc_row(&doc_with_hangul_title, 20)직접 호출 →display_width(&row) == expected_total_colsassert. 그러면 한글 padding 회귀가 unit-level 에서 잡힘. 본 PR 의tests/library.rs끝에 추가하거나 새format_doc_row_*그룹으로 묶기.회차 3 — 회차 2 actionable 모두 깔끔히 반영됨.
TAGS_COL_Wconst 추출로 truncate / pad drift 가능성 제거.format_doc_row직접 unit test 2 건 (Hangul title 30 cols → 59, Hangul tag 20 cols → 49) 가 column drift 회귀를 unit-level 에서 catch.<title_w$>으로 되돌리는 변경이 즉시 빨간 줄.본 PR scope (CJK helper + library padding + helper 단일 source 통합 + render audit) 모두 일관되게 closed. spec 의 InputBuffer struct 도입은 HOTFIXES 의 5-항목 체크리스트로 owner 명시 — 다음 PR 가 그대로 picking up 가능. 머지 OK.