Files
kebab/docs/superpowers/specs/2026-05-04-p9-fb-24-tui-affordances-design.md
altair823 0c76677131 spec(p9-fb-24): TUI status/key bar + Library header + page scroll
도그푸딩 피드백 3 건 (Library 컬럼 헤더 부재, PgUp/PgDn 페이지 스크롤,
모든 모드에서 항상 떠 있는 상태바 + 키 안내바 + 버전 정보) 을 단일
spec 으로 묶음.

설계 핵심:

- bottom 영역을 2 row 로 분할: 윗줄 = 상태바 (`kebab v0.1.0 │ pane │
  doc_count │ 동적 상태`), 아랫줄 = 기존 footer_hints 그대로 이전.
- ingest progress 의 dedicated row 를 status bar 의 동적 영역으로 흡수
  (시각적 source 단일화).
- Library `List` 위에 `format_doc_header` 헤더 row 추가 (TITLE / TAGS
  / UPDATED / CHUNKS, display-width 정렬, Role::Heading).
- Ask + Inspect 양쪽에 PgUp/PgDn (fixed step 10). Ask 는 j/k 와 동일
  하게 follow_tail = false 로 freeze.

p9-fb-13 (footer 단행 row) + p9-fb-03 (ingest dedicated row) frozen
spec 들과 layout 충돌. frozen 텍스트는 그대로 두고 본 spec + 머지 후
HOTFIXES `2026-05-04 — p9-fb-24` 항목이 live source of truth.

Spec status `planned`. 다음 단계: writing-plans skill 로 implementation
plan 작성.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 16:11:17 +00:00

8.6 KiB
Raw Blame History

p9-fb-24 — TUI status/key bar + Library header + page scroll

Date: 2026-05-04 Status: planned Audience: kebab-tui implementer / reviewer. Source feedback: 사용자 도그푸딩 2026-05-04 — (1) Library 컬럼이 무엇을 뜻하는지 헤더 부재, (2) Ask transcript / Inspect 둘 다 페이지 단위 스크롤 키 필요, (3) 모든 모드에서 항상 떠 있는 상태바 + 키 안내바 (버전 정보 포함) 가 있으면 좋겠다.

Goal

  • bottom 영역을 2 row 로 분할: 윗줄 = 항상 떠 있는 상태바 (kebab v0.1.0 │ pane │ docs 수 │ 동적 상태), 아랫줄 = 기존 mode-aware 키 안내 (현 footer_hints 그대로 이전).
  • ingest progress 의 dedicated row 를 새 status bar 의 동적 상태 영역으로 흡수 — 시각적 source 단일화.
  • Library 의 List 위에 컬럼 헤더 row 추가 (TITLE / TAGS / UPDATED / CHUNKS, display-width 정렬, Role::Heading 색).
  • Ask + Inspect 양쪽에 PgUp / PgDn 페이지 스크롤 (fixed step 10). Ask 의 PgDn / PgUp 은 j / k 와 동일하게 follow_tail = false 로 freeze.

Non-goals

  • viewport-aware 페이지 스텝 (fixed step 10 으로 시작, 후속 task 에서 viewport-relative 업그레이드 가능).
  • Library ListTable 위젯 마이그레이션 (별 header row + 기존 List 유지 — sort/정렬 인디케이터 필요할 때 후속).
  • 키 안내바 콘텐츠 확장 — 현 footer_hints 출력 그대로 이전, 키 추가/제거 없음.
  • conversation id 풀 표시 — Ask 진입 시 8 자 prefix 만.

Allowed dependencies

  • kebab-tui 자체 (ratatui 0.28, crossterm 0.28). 신규 crate 없음.
  • env!("CARGO_PKG_VERSION") (compile-time, std).

Public surface

  • kebab_tui::run::render_status_bar(f, area, app) 신규 (pub(crate)).
  • kebab_tui::run::render_key_hints(f, area, app) — 기존 render_footer rename. 동작 동일.
  • kebab_tui::library::format_doc_header(area_width: u16) -> ratatui::text::Line<'static> 신규.
  • 기존 IngestState 의 dedicated render path 제거 — status bar 가 흡수.

Behavior contract

상태바 (line 1)

좌→우 fragment, separator (단순 ASCII pipe + 양쪽 공백):

kebab v0.1.0  │  <pane>  │  <doc_count> docs  │  [conv_<id_8>…  │  ]<dynamic_status>
  • 버전: env!("CARGO_PKG_VERSION") — workspace pinned 단일 값.
  • pane 라벨 (영문): Library / Search / Ask / Inspect / Jobs.
  • doc_count: app.library.inner.docs.len() 직접 읽음. Library 의 needs_refresh 사이클이 이미 갱신 보장.
  • conversation_id (Ask 전용): current_question.is_some() || !turns.is_empty() 일 때만. 표시 form: conv_<8 hex chars>… (전체 32 hex 의 head 8 자 + ellipsis).
  • dynamic_status: 우선순위 cascade — 한 번에 하나만:
    1. streaming…app.ask.as_ref().map(|s| s.streaming).unwrap_or(false)
    2. searching…app.search.as_ref().map(|s| s.is_searching()).unwrap_or(false)
    3. indexing N/M (P%)app.ingest_state.is_some() && !ingest_state.is_terminal(). terminal (Completed/Aborted) 후 final 메시지 (indexed N+M (T) / aborted at N/M) 3 초 hold 후 idle.
    4. idle — fallback.

스타일: 전체 Role::Hint, dynamic_status 만 우선순위별 색 (streaming/searching = Role::Heading, indexing = Role::Warning, idle = Role::Hint).

키 안내바 (line 2)

기존 footer_hints(focus, mode, filter_open) 출력 그대로 single-line Paragraph. Role::Hint. wrap 시 자연스럽게 다음 줄 (단, 권장 환경 80+ 컬럼에서 wrap 거의 발생 안 함).

레이아웃

render_root Constraint 변경:

이전: [Length(3) header, Min(0) main, Length(1) ingest_status_optional, Length(1) footer]
이후: [Length(3) header, Min(0) main, Length(1) status_bar, Length(1) key_hints]
  • ingest_status_optional 제거. status bar 가 흡수.
  • error overlay 는 modal layer (Layout 영향 없음) — 그대로.

콘텐츠 영역 손실: 0 ~ 1 row (이전엔 ingest 진행 시만 1 row 차지, 평소엔 0 — 평균 +0.x row 손실).

Library 헤더

┌Library — 42 docs──────────────────────────────────────┐
│TITLE                              TAGS         UPDATED  CHUNKS│
│친애하는 미스터 최                  rust,prog    2025-04  12   │
│architecture-spec                   docs         2025-05  47   │
│...                                                    │
└──────────────────────────────────────────────────────┘
  • Block inner 안 vertical Layout 두 단계: Length(1) 헤더 paragraph + Min(0) List.
  • format_doc_header(area_width)format_doc_row 와 동일 컬럼 폭 계산식 사용 (display-width 정렬, TAGS_COL_W=12, UPDATED 10, CHUNKS unpadded).
  • 헤더 라벨: TITLE / TAGS / UPDATED / CHUNKS (영문 cap).
  • 색: theme.style(Role::Heading) (Bold cyan/팔레트별).
  • docs.is_empty() 상태에서도 헤더는 표시. List 영역에 "(no docs)" hint.

PgUp / PgDn

const PAGE_STEP: u16 = 10; 모듈 상수 (kebab-tui::input 또는 별 pager.rs).

Ask (crates/kebab-tui/src/ask.rs::handle_key_ask):

  • KeyCode::PageDown: s.scroll = s.scroll.saturating_add(PAGE_STEP); s.follow_tail = false;
  • KeyCode::PageUp: s.scroll = s.scroll.saturating_sub(PAGE_STEP); s.follow_tail = false;
  • mode 무관 (Insert / Normal 양쪽). 기존 j/k 와 동일 의미 (자동 tail freeze).

Inspect (crates/kebab-tui/src/inspect.rs):

  • 기존 +/-10 hardcode 를 PAGE_STEP 상수 참조로 교체. 동작 동일 (10 → 10).

cheatsheet popup Ask section 에 PgUp / PgDn row 추가, Inspect 는 기존 row 유지 (이미 명시).

Tests

신규 단위 / 통합

  • render_status_bar snapshot — 5 pane × 4 dynamic state (idle / streaming / searching / indexing) ≈ 8~10 case. 각 case 에서 version + pane + doc_count + dynamic 텍스트 visible.
  • render_status_bar Ask conv_id case — current_question.is_some()conv_<8hex>… 형태 visible.
  • render_status_bar ingest absorb — IngestState::Indexing { current, total } 일 때 indexing 12/40 (30%) 정확.
  • format_doc_header 단위 — 라벨 + display-width 정렬이 format_doc_row 와 boundary 일치.
  • library integration — TestBackend, docs 3 fixture, header row + data row 모두 visible. Hangul 제목 정렬 회귀 확인.
  • Ask PageDown / PageUp 신규 통합 — fixed step 10, follow_tail false 변경.
  • Inspect PageDown / PageUp 회귀 — PAGE_STEP 상수 path.

기존 영향

  • footer_hints 8 단위 테스트 — rename 외 무수정 통과.
  • 기존 ingest progress render 테스트 — status bar 통합 후 텍스트 visible 검증으로 재작성 (위치만 이동, 콘텐츠 동일).
  • p9-fb-22 Ask follow-tail 통합 테스트 — j/k / Shift-G / Ctrl-L / submission 시 follow_tail 동작 그대로 통과 (PgUp/PgDn 만 추가).

Spec contract impact

  • p9-fb-13 follow-up (footer 단행 row) frozen 텍스트와 충돌. frozen 그대로 두고 본 spec + HOTFIXES 2026-05-04 — p9-fb-24 항목이 live source of truth.
  • p9-fb-03 (TUI background ingest) 의 dedicated status row 가 status bar 의 동적 영역으로 흡수 — 시각적 위치 변경, 콘텐츠 동등. HOTFIXES 항목 cross-link.
  • p9-fb-22 (cursor + follow-tail) Ask 키 매핑 보존 + PgUp/PgDn 추가 (충돌 없음).
  • p9-fb-21 (cheatsheet) popup 의 Ask section 에 PgUp / PgDn row 추가.

Risks / notes

  • 80 컬럼 wrap: kebab v0.1.0 │ Library │ 42 docs │ idle ≈ 50 자, Ask conv_id 추가 시 ≈ 60 자. 80 컬럼 안전. 60 컬럼 미만 환경은 status bar wrap → 임시 한 줄 추가 차지. kebab TUI 권장 환경 80+ 가정.
  • 콘텐츠 영역 1 row 손실: 24 row 작은 터미널에서 transcript 영역 1 row 짧아짐. 실사용 무시 수준.
  • dynamic status priority cascade: 동시 active 상태 (streaming + indexing 등) 시 streaming 우선 표기. 사용자 인지 우선순위와 일치 (포커스 = Ask 면 streaming, ingest 는 background).
  • PAGE_STEP = 10 magic: viewport 와 무관 fixed. 24 row 작은 터미널에서 한 페이지 = 10 row 가 viewport 보다 큼 (overflow 무해). 80 row 큰 터미널에서는 한 페이지가 viewport 보다 작음 (느린 페이징). 후속 task 가 viewport-aware 로 업그레이드 시 본 spec 의 동작은 frozen.

Live deviations

추후 발견되는 deviation 은 tasks/HOTFIXES.md2026-05-04 — p9-fb-24 항목에 dated 로그로 추가. spec 자체는 frozen.