feat(kebab-tui): P9-1 Ratatui shell + Library pane #42

Merged
altair823 merged 2 commits from feat/p9-1-tui-library into main 2026-05-02 13:37:20 +00:00
Owner

요약

새 crate kebab-tui 가 Ratatui 기반 TUI shell + Library pane 을 제공. §8 facade rule 따라 kebab-app 만 import. p9-2/3/4 가 search/ask/inspect pane 을 같은 App 구조체의 Option<*State> slot 으로 plug-in 하는 parallel-safety contract 확립.

핵심 결정

  • App 구조체: config + focus + library + 3 Option sub-state. p9-1 외 다른 task 가 App 정의 손대지 않음.
  • Pane / KeyOutcome enum: 5 pane + Continue/Quit/SwitchPane/Refresh.
  • LibraryState: 내부 LibraryStateInnerpub(crate) — docs / list_state / filter / filter_edit / needs_refresh / loading / pending_g.
  • Filter overlay: tags_any (CSV) + lang, Tab cycle, Enter apply→Refresh, Esc cancel.
  • Error overlay: anyhow chain 캡쳐 → popup (Clear + 빨간 border + 어떤 키든 dismiss).
  • 터미널 lifecycle: TuiTerminal::enter 가 enable_raw_mode + AltScreen, Drop 이 종료 시 (panic 포함) restore.
  • 동기 facade: kebab-app::list_docs_with_config 를 main thread 에서 호출, brief hang 수용. 비동기는 v1 scope 밖.
  • Korean/wide-char 너비: unicode-width 로 truncation.

키 매핑 (Library pane)

j/k/Down/Up 이동 · gg 맨 위 · G 맨 아래 · f 필터 · / Search · ? Ask · Enter Inspect (선택 doc) · q/Esc 종료. error overlay 활성 시 어떤 키든 dismiss.

CLI

kebab tui 서브커맨드 추가. --config 받아 App::new(config) + run. spec DoD 의 "kebab tui launches and shows Library on a real terminal (manual smoke)" 항목.

테스트 (10건, tests/library.rs)

  • 빈 library render (panic 0)
  • 3-doc render → 모든 title 표시
  • q / Esc quit
  • / → Pane::Search 전환
  • ? → Pane::Ask 전환
  • Enter (빈 list) → Continue
  • Enter (3-doc) → Pane::Inspect
  • j 3 step → clamp at last
  • f 필터 overlay → 입력 → Enter → Refresh

TestBackend 로 hermetic 렌더. test seam: App::populate_library_for_testing (#[doc(hidden)]).

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

  1. render_library<B: Backend> generic 제거 — ratatui 0.28 의 Frame 이 backend-agnostic. unused generic 은 clippy -D warnings 거부.
  2. populate_library_for_testing 테스트 seam 추가 — pub(crate) inner 가 integration test 에서 안 보이므로. official UI API 아님 (#[doc(hidden)]).

검증

  • cargo test -p kebab-tui 10 passed
  • cargo clippy --workspace --all-targets -- -D warnings clean
  • cargo build --release -p kebab-cli clean → kebab tui--help 에 노출
  • 실제 TUI launch 는 manual smoke (TTY 필요)

다음 task (out of scope)

  • P9-2 (search pane), P9-3 (ask pane), P9-4 (inspect pane) — 같은 App 의 sub-state slot 채움
  • P9-5 (desktop tauri) — 별 분기

Test plan

  • 단위 10건 통과
  • clippy -D warnings
  • release binary 에 tui 서브커맨드 노출
  • HOTFIXES entry
  • 사용자 manual smoke: real terminal 에서 kebab tui 실행 + 키 동작 확인
## 요약 새 crate `kebab-tui` 가 Ratatui 기반 TUI shell + Library pane 을 제공. §8 facade rule 따라 `kebab-app` 만 import. p9-2/3/4 가 search/ask/inspect pane 을 같은 `App` 구조체의 `Option<*State>` slot 으로 plug-in 하는 parallel-safety contract 확립. ## 핵심 결정 - **`App`** 구조체: config + focus + library + 3 Option sub-state. p9-1 외 다른 task 가 App 정의 손대지 않음. - **`Pane` / `KeyOutcome`** enum: 5 pane + Continue/Quit/SwitchPane/Refresh. - **`LibraryState`**: 내부 `LibraryStateInner` 가 `pub(crate)` — docs / list_state / filter / filter_edit / needs_refresh / loading / pending_g. - **Filter overlay**: tags_any (CSV) + lang, Tab cycle, Enter apply→Refresh, Esc cancel. - **Error overlay**: anyhow chain 캡쳐 → popup (Clear + 빨간 border + 어떤 키든 dismiss). - **터미널 lifecycle**: `TuiTerminal::enter` 가 enable_raw_mode + AltScreen, Drop 이 종료 시 (panic 포함) restore. - **동기 facade**: `kebab-app::list_docs_with_config` 를 main thread 에서 호출, brief hang 수용. 비동기는 v1 scope 밖. - **Korean/wide-char 너비**: `unicode-width` 로 truncation. ## 키 매핑 (Library pane) `j/k/Down/Up` 이동 · `gg` 맨 위 · `G` 맨 아래 · `f` 필터 · `/` Search · `?` Ask · `Enter` Inspect (선택 doc) · `q`/`Esc` 종료. error overlay 활성 시 어떤 키든 dismiss. ## CLI `kebab tui` 서브커맨드 추가. `--config` 받아 `App::new(config)` + `run`. spec DoD 의 "kebab tui launches and shows Library on a real terminal (manual smoke)" 항목. ## 테스트 (10건, `tests/library.rs`) - 빈 library render (panic 0) - 3-doc render → 모든 title 표시 - q / Esc quit - / → Pane::Search 전환 - ? → Pane::Ask 전환 - Enter (빈 list) → Continue - Enter (3-doc) → Pane::Inspect - j 3 step → clamp at last - f 필터 overlay → 입력 → Enter → Refresh `TestBackend` 로 hermetic 렌더. test seam: `App::populate_library_for_testing` (#[doc(hidden)]). ## Spec deviation (HOTFIXES `2026-05-02 P9-1`) 1. `render_library` 의 `<B: Backend>` generic 제거 — ratatui 0.28 의 `Frame` 이 backend-agnostic. unused generic 은 clippy `-D warnings` 거부. 2. `populate_library_for_testing` 테스트 seam 추가 — `pub(crate) inner` 가 integration test 에서 안 보이므로. official UI API 아님 (`#[doc(hidden)]`). ## 검증 - `cargo test -p kebab-tui` 10 passed - `cargo clippy --workspace --all-targets -- -D warnings` clean - `cargo build --release -p kebab-cli` clean → `kebab tui` 가 `--help` 에 노출 - 실제 TUI launch 는 manual smoke (TTY 필요) ## 다음 task (out of scope) - P9-2 (search pane), P9-3 (ask pane), P9-4 (inspect pane) — 같은 App 의 sub-state slot 채움 - P9-5 (desktop tauri) — 별 분기 ## Test plan - [x] 단위 10건 통과 - [x] clippy `-D warnings` - [x] release binary 에 `tui` 서브커맨드 노출 - [x] HOTFIXES entry - [ ] 사용자 manual smoke: real terminal 에서 `kebab tui` 실행 + 키 동작 확인
altair823 added 1 commit 2026-05-02 13:28:03 +00:00
새 crate `kebab-tui` 가 §8 facade rule 따라 `kebab-app` 만 import.
Ratatui 0.28 + crossterm 0.28 기반 shell 이 다음을 제공:

- `App` 구조체: config + focus + library + 3 Option sub-state slot
  (search/ask/inspect — p9-2/3/4 가 자기 모듈에서 채우는 parallel-safety
  contract). p9-1 외에 App 정의 손대지 않음.
- `Pane` enum (Library/Search/Ask/Inspect/Jobs).
- `KeyOutcome` (Continue/Quit/SwitchPane/Refresh).
- `LibraryState` + 내부 inner: docs / list_state / filter / filter_edit /
  needs_refresh / loading / pending_g.
- `render_library` (Frame, area, &App) — heading/body, filter overlay
  toggleable, Korean/wide-char 너비는 unicode-width 로 계산.
- `handle_key_library`: j/k/Down/Up 이동, gg/G 끝, f 필터 overlay,
  /=>Search ?=>Ask Enter=>Inspect, q/Esc 종료. error overlay 가 켜
  있으면 어떤 키든 dismiss.
- 필터 overlay: tags_any (CSV) + lang 두 필드, Tab cycle, Enter
  apply→Refresh, Esc cancel.
- `ErrorOverlay`: anyhow chain 캡쳐 후 popup 렌더 (Clear + 빨간 border).
- 터미널 lifecycle: `TuiTerminal` 가 enter raw mode + alt screen,
  Drop 이 종료 시 (panic 포함) restore — 사용자 쉘 깨지지 않게.
- 비동기 없음: facade 호출은 main thread 동기. v1 의 brief hang 수용.

CLI: `kebab tui` 서브커맨드 추가, --config 받아 App::new + run.

테스트 10건 (`tests/library.rs`, TestBackend 사용):
- 빈 library / 3-doc render / q,Esc quit / / Search 전환 / ? Ask 전환
- Enter 빈 list 무동작 / Enter Inspect 전환 / j 이동 (3-step clamp) /
  f 필터 overlay → 입력 → Enter Refresh.

Test seam: `App::populate_library_for_testing` (#[doc(hidden)]) 가
`pub(crate)` inner 를 우회. spec parallel-safety contract 그대로 유지.

Spec deviation (HOTFIXES `2026-05-02 P9-1`):
- `render_library` 의 `<B: Backend>` generic 제거 — ratatui 0.28 의 Frame
  이 backend-agnostic.
- `populate_library_for_testing` 추가 (test seam, 공식 API 아님).

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

회차 1 — 가장 큰 actionable: p9-2/3/4 미머지 시점에 / ? Enter 키가 focus 만 바꾸고 본문은 Library 그대로 라 사용자에게 거짓말. (B) 옵션 추천 — focus 바꾸되 footer 에 'not yet implemented' 안내. 그 외 nit 1건 (named-arg width specifier 코멘트). 전체 디자인 견고 — slot 패턴, Drop 가드, error overlay anyhow chain 캡쳐, §8 facade 코멘트 모두 좋음.

회차 1 — 가장 큰 actionable: p9-2/3/4 미머지 시점에 / ? Enter 키가 focus 만 바꾸고 본문은 Library 그대로 라 사용자에게 거짓말. (B) 옵션 추천 — focus 바꾸되 footer 에 'not yet implemented' 안내. 그 외 nit 1건 (named-arg width specifier 코멘트). 전체 디자인 견고 — slot 패턴, Drop 가드, error overlay anyhow chain 캡쳐, §8 facade 코멘트 모두 좋음.
@@ -0,0 +11,4 @@
kebab-core = { path = "../kebab-core" }
kebab-config = { path = "../kebab-config" }
# UI facade rule (design §8): UI crates may only touch `kebab-app`. The
# search / store / embed / llm / rag layers stay invisible behind it.

(칭찬) Cargo.toml 의 §8 facade rule 코멘트 ("UI facade rule (design §8): UI crates may only touch kebab-app. The search / store / embed / llm / rag layers stay invisible behind it.") 가 미래에 누군가 kebab-search 또는 kebab-store-sqlite 를 직접 dep 추가하려고 할 때 정확한 거부 사유를 제공. spec 의 forbidden deps 절을 코드 dep 옆에 붙여둔 작은 자물쇠.

(칭찬) Cargo.toml 의 §8 facade rule 코멘트 ("UI facade rule (design §8): UI crates may only touch `kebab-app`. The search / store / embed / llm / rag layers stay invisible behind it.") 가 미래에 누군가 `kebab-search` 또는 `kebab-store-sqlite` 를 직접 dep 추가하려고 할 때 정확한 거부 사유를 제공. spec 의 forbidden deps 절을 코드 dep 옆에 붙여둔 작은 자물쇠.
@@ -0,0 +61,4 @@
pub struct AskState;
/// Forward-declared opaque sub-state. p9-4 fills the body.
pub struct InspectState;

(칭찬) AppOption<*State> slot 패턴이 spec 의 parallel-safety contract 를 정확히 표현. p9-2 가 SearchState body 채우면서 App 정의 손대지 않고 자기 모듈에서 populate_search 헬퍼 추가하기만 하면 됨. pub struct SearchState; opaque 가 P9-1 시점에 "이 자리는 이미 예약됨" 의 contract 를 박아둠 — p9-2/3/4 가 parallel 진행할 때 merge conflict 표면을 0 으로.

(칭찬) `App` 의 `Option<*State>` slot 패턴이 spec 의 parallel-safety contract 를 정확히 표현. p9-2 가 `SearchState` body 채우면서 `App` 정의 손대지 않고 자기 모듈에서 `populate_search` 헬퍼 추가하기만 하면 됨. `pub struct SearchState;` opaque 가 P9-1 시점에 "이 자리는 이미 예약됨" 의 contract 를 박아둠 — p9-2/3/4 가 parallel 진행할 때 merge conflict 표면을 0 으로.
@@ -0,0 +16,4 @@
pub struct ErrorOverlay {
pub title: String,
/// Each chain link as a separate line, root-cause last.
pub chain: Vec<String>,

(칭찬) ErrorOverlayanyhow::Error 를 직접 보유하지 않고 Vec<String> 으로 chain 캡쳐. "!Sync 한 toolchain 에서 lifetime gymnastics 강요" 의 함정을 노트에 적시한 게 좋음 — 미래에 누군가 Option<&anyhow::Error> 로 "최적화" 시도하면 즉시 코멘트가 답.

(칭찬) `ErrorOverlay` 가 `anyhow::Error` 를 직접 보유하지 않고 `Vec<String>` 으로 chain 캡쳐. "`!Sync` 한 toolchain 에서 lifetime gymnastics 강요" 의 함정을 노트에 적시한 게 좋음 — 미래에 누군가 `Option<&anyhow::Error>` 로 "최적화" 시도하면 즉시 코멘트가 답.
@@ -0,0 +195,4 @@
.unwrap_or_else(|_| "?".to_string());
let updated_short = updated.split('T').next().unwrap_or("?");
format!(
"{title:<title_w$} {tags:<12} {updated_short:<10} {chunk_count}",

(nit) format!("{title:<title_w$} {tags:<12} ...")title_w$ 는 Rust 의 named-arg width specifier 이지만 처음 보면 $ 가 무엇인지 헷갈립니다. 미래 reader 가 같은 패턴을 다른 곳에서 쓰려고 할 때 doc 링크 한 줄 (https://doc.rust-lang.org/std/fmt/#width$ 절) 이 코멘트로 박혀 있으면 친절합니다.

How to apply: 한 줄 코멘트만 추가 — // <width$> is the named-arg width form (std::fmt §Width).. 코드 동작은 정확합니다.

(nit) `format!("{title:<title_w$} {tags:<12} ...")` 의 `title_w$` 는 Rust 의 named-arg width specifier 이지만 처음 보면 `$` 가 무엇인지 헷갈립니다. 미래 reader 가 같은 패턴을 다른 곳에서 쓰려고 할 때 doc 링크 한 줄 (https://doc.rust-lang.org/std/fmt/#width 의 `$` 절) 이 코멘트로 박혀 있으면 친절합니다. How to apply: 한 줄 코멘트만 추가 — `// `<width$>` is the named-arg width form (std::fmt §Width).`. 코드 동작은 정확합니다.
@@ -0,0 +47,4 @@
Pane::Search | Pane::Ask | Pane::Inspect | Pane::Jobs => {
handle_key_library(app, key)
}
};

(suggestion / UX) / ? Enter 키가 KeyOutcome::SwitchPane(Search/Ask/Inspect) 를 emit 하면 run loop 가 app.focus = p 로 바꿉니다. 하지만 p9-2/3/4 는 아직 안 머지돼서 Search/Ask/Inspect/Jobs arm 이 모두 handle_key_library 로 위임 + render_library 로 placeholder 렌더. 즉 사용자가 / 누르면 헤더에 "Search" 가 나오는데 본문은 Library, 키 매핑은 Library — focus state 가 사실상 거짓말.

Why: P9-1 단독 머지 후 사용자가 kebab tui 실행하고 / 누르면 "왜 Library 그대로지?" 라는 의문 발생. p9-2 가 머지될 때까지 dead key 로 두는 게 더 정직.

How to apply (둘 중 택일):

  • (A, 추천) handle_key_library/ ? Enter arm 을 일시적으로 Continue 로 바꾸고 footer hint 에서도 그 키들 제거. p9-2/3/4 머지 시 다시 활성화 (revert 가 단순한 한 줄).
  • (B) / ? Enter 누르면 focus 만 바꾸고 footer 에 "<pane> not yet implemented — q to return" 안내 메시지 추가. 사용자에게 정직성 + 다음 task 신호.

(B) 가 spec 의 키 매핑 invariant 를 그대로 유지하면서 UX 도 honest 해 추천.

(suggestion / UX) `/` `?` Enter 키가 `KeyOutcome::SwitchPane(Search/Ask/Inspect)` 를 emit 하면 run loop 가 `app.focus = p` 로 바꿉니다. 하지만 p9-2/3/4 는 아직 안 머지돼서 `Search/Ask/Inspect/Jobs` arm 이 모두 `handle_key_library` 로 위임 + `render_library` 로 placeholder 렌더. 즉 사용자가 `/` 누르면 헤더에 "Search" 가 나오는데 본문은 Library, 키 매핑은 Library — focus state 가 사실상 거짓말. Why: P9-1 단독 머지 후 사용자가 `kebab tui` 실행하고 `/` 누르면 "왜 Library 그대로지?" 라는 의문 발생. p9-2 가 머지될 때까지 dead key 로 두는 게 더 정직. How to apply (둘 중 택일): - (A, 추천) `handle_key_library` 의 `/` `?` Enter arm 을 일시적으로 `Continue` 로 바꾸고 footer hint 에서도 그 키들 제거. p9-2/3/4 머지 시 다시 활성화 (revert 가 단순한 한 줄). - (B) `/` `?` Enter 누르면 focus 만 바꾸고 footer 에 `"<pane> not yet implemented — q to return"` 안내 메시지 추가. 사용자에게 정직성 + 다음 task 신호. (B) 가 spec 의 키 매핑 invariant 를 그대로 유지하면서 UX 도 honest 해 추천.
@@ -0,0 +32,4 @@
// propagated them; just log and let the OS recover any
// remaining noise.
let _ = disable_raw_mode();
let _ = execute!(stdout(), LeaveAlternateScreen);

(칭찬) TuiTerminal::Drop 이 raw_mode + AltScreen 모두 best-effort 복구. 특히 "Errors here would clobber a real panic if we propagated them" 코멘트가 "왜 ? 가 아니라 let _ = ?" 의 의도를 정확히 표현. terminal 을 corrupt 한 채로 user shell 에 빠지는 것보다 quiet recovery 가 정직 — color_eyre / catch_unwind 같은 추가 layer 없이도 안전하게 정리.

(칭찬) `TuiTerminal::Drop` 이 raw_mode + AltScreen 모두 best-effort 복구. 특히 "Errors here would clobber a real panic if we propagated them" 코멘트가 "왜 ? 가 아니라 let _ = ?" 의 의도를 정확히 표현. terminal 을 corrupt 한 채로 user shell 에 빠지는 것보다 quiet recovery 가 정직 — color_eyre / catch_unwind 같은 추가 layer 없이도 안전하게 정리.
@@ -16,1 +16,4 @@
## 2026-05-02 — P9-1 TUI Library: render_library generic + test seam
**Discovered**: P9-1 implementation start.

(칭찬) HOTFIXES entry 가 두 deviation (render_library generic 제거, test seam 추가) 을 "왜 spec literal 그대로 못 쓰는지" + "무엇으로 대체했는지" 두 축으로 정직하게 기록. 특히 <B: Backend> generic 이 ratatui 0.28 의 surface 와 어긋나 있다는 사실 ("unused generic 은 clippy -D warnings 거부") 을 명시한 부분이 미래에 누군가 spec literal 따라가려고 시도할 때 즉시 답을 줌.

(칭찬) HOTFIXES entry 가 두 deviation (`render_library` generic 제거, test seam 추가) 을 "왜 spec literal 그대로 못 쓰는지" + "무엇으로 대체했는지" 두 축으로 정직하게 기록. 특히 `<B: Backend>` generic 이 ratatui 0.28 의 surface 와 어긋나 있다는 사실 ("unused generic 은 clippy `-D warnings` 거부") 을 명시한 부분이 미래에 누군가 spec literal 따라가려고 시도할 때 즉시 답을 줌.
altair823 added 1 commit 2026-05-02 13:31:36 +00:00
- p9-2/3/4 미머지 시점에 / ? Enter 키로 focus 가 Search/Ask/Inspect 로
  옮겨가면 헤더만 바뀌고 본문은 Library 그대로 + 키 매핑도 Library 라
  사용자에게 거짓말. footer hint 가 \"Search pane not yet implemented
  (lands with p9-2) — q to return\" 로 전환된다. 새 stub 핸들러
  `handle_key_unimplemented_pane` 가 q / Esc 만 받아 Library 로 복귀,
  나머지 키는 no-op (이전 구현은 handle_key_library 로 위임해서 focus
  와 다른 pane state 가 mutate 되던 절뚝거림 차단).
- `format_doc_row` 의 `{title:<title_w$}` 가 std::fmt 의 named-arg
  width specifier — 미래 reader 가 같은 패턴 보고 헷갈리지 않도록
  doc 링크 한 줄 코멘트 추가.

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

회차 2 — 회차 1 지적 2건 (stub pane UX, named-arg width 코멘트) 모두 반영. 새 handle_key_unimplemented_pane 가 focus / 키 매핑 일관성 보장. 10 tests + clippy clean. 머지 가능. p9-2/3/4 가 자연스럽게 plug-in 가능.

회차 2 — 회차 1 지적 2건 (stub pane UX, named-arg width 코멘트) 모두 반영. 새 handle_key_unimplemented_pane 가 focus / 키 매핑 일관성 보장. 10 tests + clippy clean. 머지 가능. p9-2/3/4 가 자연스럽게 plug-in 가능.
@@ -0,0 +195,4 @@
.unwrap_or_else(|_| "?".to_string());
let updated_short = updated.split('T').next().unwrap_or("?");
// `<width$>` is std::fmt's named-arg width form (`title_w` is the
// named arg below; `$` says "use it as the padding width"). See

(칭찬) std::fmt named-arg width specifier 코멘트 + 공식 docs 링크 (#width §"Width via named parameters"). 한 줄짜리 "왜 $ 가 의미가 있는가" 가 미래 contributor 의 고민을 절약.

(칭찬) std::fmt named-arg width specifier 코멘트 + 공식 docs 링크 (`#width §"Width via named parameters"`). 한 줄짜리 "왜 `$` 가 의미가 있는가" 가 미래 contributor 의 고민을 절약.
@@ -0,0 +97,4 @@
Constraint::Length(1),
])
.split(f.area());
render_header(f, outer[0], app);

(칭찬) 새 handle_key_unimplemented_pane 가 stub focus 동안 오직 q/Esc 만 받고 나머지는 no-op. 이전 구현이 handle_key_library 에 위임해서 사용자가 Search 로 옮긴 후 j 누르면 Library selection 이 mutate 되던 절뚝거림 차단. 코멘트 "Does NOT delegate to handle_key_library because that would let j/k/f mutate Library state while focus says otherwise — confusing UX" 가 결정 근거를 코드 옆에 박아둠 — 미래 reviewer 가 "왜 두 핸들러를 분리?" 묻지 않게.

(칭찬) 새 `handle_key_unimplemented_pane` 가 stub focus 동안 *오직* q/Esc 만 받고 나머지는 no-op. 이전 구현이 `handle_key_library` 에 위임해서 사용자가 Search 로 옮긴 후 j 누르면 Library selection 이 mutate 되던 절뚝거림 차단. 코멘트 "Does NOT delegate to handle_key_library because that would let j/k/f mutate Library state while focus says otherwise — confusing UX" 가 결정 근거를 코드 옆에 박아둠 — 미래 reviewer 가 "왜 두 핸들러를 분리?" 묻지 않게.
@@ -0,0 +127,4 @@
Span::raw(" / "),
Span::raw(pane_label),
]);
f.render_widget(Paragraph::new(line), area);

(칭찬) footer hint 가 pane 별로 미구현 메시지 + "lands with p9-N" 명시 — 사용자에게 honest + 향후 task 신호. p9-2 머지 직후 한 줄 (Pane::Search => "...") 만 바꾸면 자연 활성화. revert surface 가 작아 follow-up task 의 friction 0.

(칭찬) footer hint 가 pane 별로 미구현 메시지 + "lands with p9-N" 명시 — 사용자에게 honest + 향후 task 신호. p9-2 머지 직후 한 줄 (`Pane::Search => "..."`) 만 바꾸면 자연 활성화. revert surface 가 작아 follow-up task 의 friction 0.
altair823 merged commit 5db401340e into main 2026-05-02 13:37:20 +00:00
altair823 deleted branch feat/p9-1-tui-library 2026-05-02 13:37:36 +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#42