feat(kebab-tui): p9-fb-10 follow-up — InputBuffer + cursor + Korean FTS5 pin #88

Merged
altair823 merged 12 commits from feat/p9-fb-10-inputbuffer into main 2026-05-03 10:39:06 +00:00
Owner

Summary

p9-fb-10 (TUI CJK input + wide-char rendering audit) follow-up — closes the
deferred portion of the spec. Together with PR #87 (helpers + library row
formatter), the p9-fb-10 task is fully shipped. spec status in_progress
completed.

Scope (12 commits, 6 tasks)

  1. InputBuffer structkebab-tui::input::InputBuffer { content, cursor_col }
    with push_char / push_str / pop_char / clear / take / as_str /
    cursor_col / is_empty. Cursor invariant cursor_col == display_width(content)
    enforced by construction. v1 append-only (mid-string editing out of scope).
  2. SearchState.input → InputBuffer + f.set_cursor_position(...) placement.
  3. AskState.input → InputBuffer + take() helper for submit flow + PROMPT const.
  4. FilterEdit.{tags_buf,lang_buf} → InputBuffer + LABEL_TAGS / LABEL_LANG
    consts (single source for layout + cursor math) + library_filter_for_testing
    accessor.
  5. Korean FTS5 smoke pincrates/kebab-app/tests/search_korean.rs ingests
    a Hangul .md and asserts the lexical query "러스트" returns the right doc.
    lexical_query test helper promoted to common/mod.rs (de-duped from
    search_lexical.rs).
  6. place_cursor_x helper (final review fix-up) — extracted shared cursor
    math (sum in usize to avoid u16 overflow + clamp) used by all 3 panes.
    Filter overlay render-level cursor test added.

Behavioral changes

  • Cursor visibility: ratatui 0.28 calls show_cursor when cursor_position
    is Some, hide_cursor when None. Search / Ask / Filter now call
    set_cursor_position → caret visible in those panes. Library / Inspect omit
    the call → cursor stays hidden as before.
  • CJK column accuracy: Hangul / 漢字 / fullwidth chars advance the cursor
    by 2 cols per char (was: 1 char = 1 cell ≠ display width). String::pop()
    was already char-aware so backspace boundary safety was never a bug; this
    PR fixes only rendering width.

Tests added

  • 7 InputBuffer unit tests (ASCII / Hangul / mixed / take / clear /
    pop-on-empty / cursor invariant including post-pop).
  • 2 place_cursor_x unit tests (clamp + within-bounds).
  • 3 Hangul integration tests (Search / Ask / Filter) — pin both content
    and cursor_col at type + after backspace.
  • 1 filter overlay render test — pins that set_cursor_position runs.
  • 1 Korean FTS5 end-to-end smoke pin.

Test plan

  • cargo test -p kebab-tui — 58 unit + 13 library + others, all green.
  • cargo test -p kebab-app — 8 lexical (incl. new Korean), 2 vector ignored
    (AVX-gated, expected).
  • cargo clippy --workspace --all-targets -- -D warnings — clean.

Spec contract

  • tasks/p9/p9-fb-10-tui-cjk-input.mdstatus: in_progresscompleted,
    3 DoD boxes ticked, follow-up Notes line added.
  • tasks/HOTFIXES.md — 5-checkbox follow-up list flipped to [x],
    "Follow-up shipped" line added (PR # to be filled in chore PR).
  • README.mdkebab tui row gains the cursor-column note.
  • HANDOFF.md — 2026-05-03 entry for the follow-up.

Workflow

Implemented via superpowers:subagent-driven-development — fresh subagent per
task with two-stage review (spec compliance + code quality) per task. Each
task's review nits were addressed in the same task's review(...) fix-up
commit before proceeding to the next task. Final whole-branch review caught
the u16 overflow inconsistency (3 panes had divergent arithmetic) → fixed
by extracting place_cursor_x helper.

## Summary p9-fb-10 (TUI CJK input + wide-char rendering audit) follow-up — closes the deferred portion of the spec. Together with PR #87 (helpers + library row formatter), the p9-fb-10 task is fully shipped. spec status `in_progress` → `completed`. ## Scope (12 commits, 6 tasks) 1. **InputBuffer struct** — `kebab-tui::input::InputBuffer { content, cursor_col }` with `push_char` / `push_str` / `pop_char` / `clear` / `take` / `as_str` / `cursor_col` / `is_empty`. Cursor invariant `cursor_col == display_width(content)` enforced by construction. v1 append-only (mid-string editing out of scope). 2. **SearchState.input → InputBuffer** + `f.set_cursor_position(...)` placement. 3. **AskState.input → InputBuffer** + `take()` helper for submit flow + `PROMPT` const. 4. **FilterEdit.{tags_buf,lang_buf} → InputBuffer** + `LABEL_TAGS` / `LABEL_LANG` consts (single source for layout + cursor math) + `library_filter_for_testing` accessor. 5. **Korean FTS5 smoke pin** — `crates/kebab-app/tests/search_korean.rs` ingests a Hangul `.md` and asserts the lexical query "러스트" returns the right doc. `lexical_query` test helper promoted to `common/mod.rs` (de-duped from `search_lexical.rs`). 6. **`place_cursor_x` helper** (final review fix-up) — extracted shared cursor math (sum in `usize` to avoid `u16` overflow + clamp) used by all 3 panes. Filter overlay render-level cursor test added. ## Behavioral changes - **Cursor visibility**: ratatui 0.28 calls `show_cursor` when `cursor_position` is `Some`, `hide_cursor` when `None`. Search / Ask / Filter now call `set_cursor_position` → caret visible in those panes. Library / Inspect omit the call → cursor stays hidden as before. - **CJK column accuracy**: Hangul / 漢字 / fullwidth chars advance the cursor by 2 cols per char (was: 1 char = 1 cell ≠ display width). `String::pop()` was already char-aware so backspace boundary safety was never a bug; this PR fixes only **rendering width**. ## Tests added - 7 InputBuffer unit tests (ASCII / Hangul / mixed / `take` / `clear` / pop-on-empty / cursor invariant including post-pop). - 2 `place_cursor_x` unit tests (clamp + within-bounds). - 3 Hangul integration tests (Search / Ask / Filter) — pin both content and `cursor_col` at type + after backspace. - 1 filter overlay render test — pins that `set_cursor_position` runs. - 1 Korean FTS5 end-to-end smoke pin. ## Test plan - `cargo test -p kebab-tui` — 58 unit + 13 library + others, all green. - `cargo test -p kebab-app` — 8 lexical (incl. new Korean), 2 vector ignored (AVX-gated, expected). - `cargo clippy --workspace --all-targets -- -D warnings` — clean. ## Spec contract - `tasks/p9/p9-fb-10-tui-cjk-input.md` — `status: in_progress` → `completed`, 3 DoD boxes ticked, follow-up Notes line added. - `tasks/HOTFIXES.md` — 5-checkbox follow-up list flipped to `[x]`, "Follow-up shipped" line added (PR # to be filled in chore PR). - `README.md` — `kebab tui` row gains the cursor-column note. - `HANDOFF.md` — 2026-05-03 entry for the follow-up. ## Workflow Implemented via `superpowers:subagent-driven-development` — fresh subagent per task with two-stage review (spec compliance + code quality) per task. Each task's review nits were addressed in the same task's `review(...)` fix-up commit before proceeding to the next task. Final whole-branch review caught the `u16` overflow inconsistency (3 panes had divergent arithmetic) → fixed by extracting `place_cursor_x` helper.
altair823 added 12 commits 2026-05-03 10:36:20 +00:00
Add `InputBuffer` with `push_char`/`push_str`/`pop_char`/`clear` tracking
cursor position in display columns (CJK = 2, ASCII = 1) plus 6 unit tests
(p9-fb-10 Task 1). Re-export from crate root.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Migrates SearchState.input from String to InputBuffer so wide-char
(Hangul/CJK) keystrokes advance the terminal cursor by display columns
instead of char count. Adds cursor placement in render_input_bar via
f.set_cursor_position and a Hangul round-trip pin in tests/search.rs.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Migrates FilterEdit.tags_buf and lang_buf from String to InputBuffer
(the same display-width-aware type used by Search/Ask), adds cursor
placement in render_filter_overlay so the caret sits at the end of the
focused field, and adds a Hangul filter test
(filter_overlay_accepts_hangul_tags) that confirms wide chars round-trip
through key events and commit_into unchanged.

Also adds App::library_filter_for_testing accessor mirroring
populate_library_for_testing style.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
p9-fb-10: verifies that a Korean (Hangul) token survives the
ingest → FTS5 lexical search round-trip via the kebab-app facade.
NFC normalization is wired upstream in kebab-normalize; this test
only exercises end-to-end correctness — no AVX, no fastembed required.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add `place_cursor_x(inner_x, inner_width, prompt_w, cursor_col) -> u16`
  to `input.rs`: sums in `usize` (no u16 wrap), clamps to inner right
  edge, tries_into with u16::MAX fallback. Two unit tests pin the clamp
  and the in-bounds path.
- Re-export from `lib.rs` alongside `InputBuffer`, `display_width`,
  `truncate_to_display_width`.
- Replace the open-coded 2-line `raw_x`/`cursor_x` blocks in Search,
  Ask, and Library with a single `place_cursor_x` call each —
  consistent usize arithmetic across all three panes.
- Add `filter_overlay_render_places_cursor_on_focused_field` integration
  test in `tests/library.rs`: opens the filter overlay, renders through
  `TestBackend`, asserts `terminal.get_cursor_position().x > 0` (label
  offset > 0 proves `set_cursor_position` was called with a meaningful
  coordinate, not stuck at origin).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
claude-reviewer-01 approved these changes 2026-05-03 10:37:32 +00:00
claude-reviewer-01 left a comment
Member

회차 1 — APPROVE.

본 PR 의 12 commits 는 plan-driven subagent flow 로 구현되었고 task 단위로 spec compliance + code quality 두 단계 review 가 이미 돌았다. 각 task 의 review nits 는 같은 task 의 review(...) 커밋으로 즉시 반영. final whole-branch review 가 잡은 마지막 actionable (3 pane 의 cursor 산술 inconsistency + filter overlay render-level cursor test 누락) 은 final commit b96d8f9place_cursor_x 헬퍼 추출 + 신규 render test 로 해결.

검토한 항목:

  • InputBuffer 의 cursor invariant cursor_col == display_width(content) 가 push/pop/clear/take 모든 경로에서 유지 (8 unit test 가 pin).
  • 3 pane (Search/Ask/Filter) 의 cursor placement 가 모두 block.inner(area) + place_cursor_x 헬퍼 + clamp 로 통일 — fourth pane 추가 시 즉시 재사용 가능.
  • ratatui 0.28 의 cursor_position Some/None 에 따른 show/hide_cursor 동작이 doc + 코멘트 + final review 에서 명시적으로 confirm — Search/Ask/Filter 가 INSERT 모드에서 caret 보임, Library/Inspect 는 hidden.
  • FilterEdit 의 LABEL_TAGS / LABEL_LANG const 가 layout + cursor 산술의 단일 source — label 변경 시 두 곳 동시에 영향.
  • Korean FTS5 smoke pin 이 lexical_only env (embedding="none") 로 외부 의존 없이 통과. lexical_query test helper 가 common/mod.rs 로 promotion (search_lexical.rs 와 중복 제거).
  • spec status flip + HOTFIXES checklist 5 항목 모두 [x]. spec frozen contract 의 Goal/Behavior 섹션은 unchanged — 오직 status + DoD + Notes 만 갱신.
  • Scope discipline: Cargo.toml unchanged, kebab-tui + kebab-app + 4 doc 파일만 변경. 16 files / +458 -87.

본 PR scope 안에서 추가로 잡을 actionable issue 없음. 머지 OK.

회차 1 — APPROVE. 본 PR 의 12 commits 는 plan-driven subagent flow 로 구현되었고 task 단위로 spec compliance + code quality 두 단계 review 가 이미 돌았다. 각 task 의 review nits 는 같은 task 의 `review(...)` 커밋으로 즉시 반영. final whole-branch review 가 잡은 마지막 actionable (3 pane 의 cursor 산술 inconsistency + filter overlay render-level cursor test 누락) 은 final commit `b96d8f9` 가 `place_cursor_x` 헬퍼 추출 + 신규 render test 로 해결. **검토한 항목**: - InputBuffer 의 cursor invariant `cursor_col == display_width(content)` 가 push/pop/clear/take 모든 경로에서 유지 (8 unit test 가 pin). - 3 pane (Search/Ask/Filter) 의 cursor placement 가 모두 `block.inner(area)` + `place_cursor_x` 헬퍼 + clamp 로 통일 — fourth pane 추가 시 즉시 재사용 가능. - ratatui 0.28 의 `cursor_position` Some/None 에 따른 show/hide_cursor 동작이 doc + 코멘트 + final review 에서 명시적으로 confirm — Search/Ask/Filter 가 INSERT 모드에서 caret 보임, Library/Inspect 는 hidden. - FilterEdit 의 LABEL_TAGS / LABEL_LANG const 가 layout + cursor 산술의 단일 source — label 변경 시 두 곳 동시에 영향. - Korean FTS5 smoke pin 이 `lexical_only` env (embedding="none") 로 외부 의존 없이 통과. `lexical_query` test helper 가 `common/mod.rs` 로 promotion (search_lexical.rs 와 중복 제거). - spec status flip + HOTFIXES checklist 5 항목 모두 `[x]`. spec frozen contract 의 Goal/Behavior 섹션은 unchanged — 오직 status + DoD + Notes 만 갱신. - Scope discipline: Cargo.toml unchanged, kebab-tui + kebab-app + 4 doc 파일만 변경. 16 files / +458 -87. 본 PR scope 안에서 추가로 잡을 actionable issue 없음. 머지 OK.
altair823 merged commit 61d7839300 into main 2026-05-03 10:39:06 +00:00
altair823 deleted branch feat/p9-fb-10-inputbuffer 2026-05-03 10:39:21 +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#88