feat(kebab-tui): p9-fb-14 color theme module — role-based palette #70

Merged
altair823 merged 2 commits from feat/p9-fb-14-theme into main 2026-05-03 03:13:00 +00:00
Owner

요약

도그푸딩 item 12 ("빈약한 색감") 해소. inline Style::default().fg(Color::*) 호출을 single source theme 모듈로 격리하고 dark / light 두 팔레트 제공.

변경

  • kebab-tui::theme::{Theme, Role, Palette} (new, 132 lines) — 16 Role enum, dark + light 팔레트가 exhaustive match 로 매핑.
  • Theme::from_name(s) — 알 수 없는 값 → dark fallback (config typo 가 TUI 죽이지 않음).
  • App.theme: ThemeApp::newconfig.ui.theme 에서 resolve. 모든 pane 이 app.theme.style(Role::X) 사용.
  • Config.ui.theme: String[ui] theme = \"dark\"|\"light\" (default "dark"), #[serde(default)] 로 기존 config 호환.
  • Pane sweep: search.rs / ask.rs / library.rs / inspect.rs / run.rs / error_popup.rs 의 모든 inline Style 제거. 일부 helper 가 theme: &Theme 파라미터 추가.

Out of scope

  • T 키 runtime toggle — mode machine (p9-fb-12) 미진행이라 skip
  • 사용자 정의 [theme.custom] 절 — P+
  • truecolor fallback — terminal 가정

테스트

  • 신규 4 unit (theme.rs): role exhaustive 통과, from_name fallback, default 핀, primary roles decoration 검증
  • 기존 75 TUI 테스트 + cargo test --workspace -j 1 exit 0
  • cargo clippy -p kebab-tui -p kebab-config --all-targets -- -D warnings clean

문서

  • README Configuration: [ui] theme = \"dark\"|\"light\" 안내
  • docs/SMOKE.md: config 예시에 [ui] 절 추가
  • HANDOFF: 2026-05-03 entry
  • spec status planned → in_progress

p9-fb-11 (ask markdown render) 의 Theme 의존성 unblock.

## 요약 도그푸딩 item 12 (\"빈약한 색감\") 해소. inline `Style::default().fg(Color::*)` 호출을 single source `theme` 모듈로 격리하고 dark / light 두 팔레트 제공. ## 변경 - **`kebab-tui::theme::{Theme, Role, Palette}`** (new, 132 lines) — 16 Role enum, dark + light 팔레트가 exhaustive match 로 매핑. - **`Theme::from_name(s)`** — 알 수 없는 값 → dark fallback (config typo 가 TUI 죽이지 않음). - **`App.theme: Theme`** — `App::new` 가 `config.ui.theme` 에서 resolve. 모든 pane 이 `app.theme.style(Role::X)` 사용. - **`Config.ui.theme: String`** — `[ui] theme = \"dark\"|\"light\"` (default \"dark\"), `#[serde(default)]` 로 기존 config 호환. - **Pane sweep**: search.rs / ask.rs / library.rs / inspect.rs / run.rs / error_popup.rs 의 모든 inline Style 제거. 일부 helper 가 `theme: &Theme` 파라미터 추가. ## Out of scope - `T` 키 runtime toggle — mode machine (p9-fb-12) 미진행이라 skip - 사용자 정의 `[theme.custom]` 절 — P+ - truecolor fallback — terminal 가정 ## 테스트 - 신규 4 unit (theme.rs): role exhaustive 통과, from_name fallback, default 핀, primary roles decoration 검증 - 기존 75 TUI 테스트 + `cargo test --workspace -j 1` exit 0 - `cargo clippy -p kebab-tui -p kebab-config --all-targets -- -D warnings` clean ## 문서 - README Configuration: `[ui] theme = \"dark\"|\"light\"` 안내 - docs/SMOKE.md: config 예시에 `[ui]` 절 추가 - HANDOFF: 2026-05-03 entry - spec status planned → in_progress p9-fb-11 (ask markdown render) 의 Theme 의존성 unblock.
altair823 added 1 commit 2026-05-03 03:10:17 +00:00
도그푸딩 item 12 — TUI 가 모든 정보 종류에 같은 회색 / 시안 만 쓰던
\"빈약한 색감\" 해소. inline `Style::default().fg(Color::*)` 호출을
single source `theme` 모듈로 격리 + dark / light 두 팔레트 제공.

## 핵심 변경

- **`kebab-tui::theme::{Theme, Role, Palette}`** 신규 (132 라인). 16
  개 Role enum (BorderActive/BorderInactive/Title/Path/ModeLexical/
  ModeVector/ModeHybrid/Selected/Hint/Heading/Warning/Error/Success/
  CitationMarker/Bullet/Body) 을 dark + light 두 팔레트가 exhaustive
  match 로 매핑. 새 Role 추가 시 두 팔레트 모두 갱신해야 컴파일됨.

- **`Theme::from_name(s)`** — 알 수 없는 값 (e.g. \"solarized\") →
  dark fallback. config typo 가 TUI 를 죽이지 않음 (spec 명시).

- **`App.theme: Theme`** 신규 — `App::new` 가 `config.ui.theme` 에서
  resolve. 모든 pane (library/search/ask/inspect/run/error_popup) 이
  `app.theme.style(Role::X)` 로 style 가져옴.

- **`Config.ui.theme: String`** 신규 — `[ui] theme = \"dark\" | \"light\"`
  (default `\"dark\"`). `#[serde(default)]` 로 기존 config 파일 호환.

- **Pane sweep**: search.rs / ask.rs / library.rs / inspect.rs /
  run.rs / error_popup.rs 의 모든 inline `Style::default().fg(Color::*)`
  / `add_modifier(Modifier::DIM/REVERSED)` 호출 제거. 일부 helper
  (`render_filter_overlay`, `header_kv`, `kv`, `push_section_header`,
  `build_doc_lines`, `build_chunk_lines`, `render_input/answer/bottom/
  status/citations`, `render_error_overlay`) 가 `theme: &Theme` 파라
  미터 추가.

## Out of scope

- `T` 키 runtime toggle — mode machine (p9-fb-12) 미진행이라 NORMAL
  모드 정의 불가, config 만으로 결정. 추후 p9-fb-12 후속에서 추가.
- 사용자 정의 `[theme.custom]` 절 — P+ task.
- truecolor → 256-color fallback — terminal 가정.

## 테스트

- 신규 4 개 (theme.rs):
  - `every_role_resolves_in_dark_and_light` — 16 Role 전부 panic 없이
    Style 반환 (exhaustive match runtime 검증)
  - `from_name_recognizes_dark_light_and_falls_back` — 입력 정규화 +
    fallback 정책
  - `default_palette_is_dark` — 기본값 pin
  - `primary_roles_carry_decoration_in_dark` — Title/Selected/Heading/
    Error/Warning/Success 가 bare default 로 회귀 안 함
- 기존 75 개 TUI 테스트 (14 lib + 18 ask + 12 inspect + 10 library +
  17 search + 4 theme) 모두 통과
- `cargo test --workspace --no-fail-fast -j 1` exit 0
- `cargo clippy -p kebab-tui -p kebab-config --all-targets -- -D warnings`
  clean

## 문서

- README Configuration 절: `[ui]` 섹션 + `theme = \"dark\"|\"light\"`
  안내
- docs/SMOKE.md: config 예시에 `[ui] theme = \"dark\"` 라인 추가
- HANDOFF: 2026-05-03 머지 후 발견 entry
- spec status: planned → in_progress

p9-fb-11 (ask markdown render) 의 `Theme` 의존성 unblock.

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

회차 1 — 큰 그림은 깔끔: 16 Role 이 dark/light exhaustive 하게 매핑되고, pane sweep 가 모든 inline Style 을 Role 호출로 교체. config/serde 의 #[serde(default)] 처리, App.theme 의 from_name fallback 정책, helper 들 (header_kv/kv/push_section_header/render_filter_overlay 등) 의 theme: &Theme 파라미터 추가도 합리적입니다.

actionable nit 3 건 inline. 모두 cosmetic / 의도 명확화.

회차 1 — 큰 그림은 깔끔: 16 Role 이 dark/light exhaustive 하게 매핑되고, pane sweep 가 모든 inline Style 을 Role 호출로 교체. config/serde 의 #[serde(default)] 처리, App.theme 의 from_name fallback 정책, helper 들 (header_kv/kv/push_section_header/render_filter_overlay 등) 의 theme: &Theme 파라미터 추가도 합리적입니다. actionable nit 3 건 inline. 모두 cosmetic / 의도 명확화.
@@ -165,3 +173,3 @@
Style::default()
theme.style(crate::theme::Role::Body)
};
out.push(Line::from(vec![

Role::Heading 의 dark/light 양쪽 팔레트 모두 이미 Modifier::BOLD 를 포함합니다 (theme.rs 의 dark_style/light_style 의 Heading arm). 여기서 .add_modifier(Modifier::BOLD) 는 중복이고, 다른 위치에서 같은 Role 을 쓸 때 이 패턴을 따라 하면 "Heading 은 BOLD 없이 와서 직접 추가해야 한다" 는 잘못된 규약이 퍼집니다.

제안: theme.style(crate::theme::Role::Heading) 만. 같은 정리가 ask.rs Q label 위치에 두 번 있고, 한 군데는 A label (Role::Success + BOLD — 이건 Success 가 색만 있어 BOLD 추가가 의도) 과 모양이 비슷해서 더 헷갈립니다.

`Role::Heading` 의 dark/light 양쪽 팔레트 모두 이미 `Modifier::BOLD` 를 포함합니다 (theme.rs 의 dark_style/light_style 의 `Heading` arm). 여기서 `.add_modifier(Modifier::BOLD)` 는 중복이고, 다른 위치에서 같은 Role 을 쓸 때 이 패턴을 따라 하면 "Heading 은 BOLD 없이 와서 직접 추가해야 한다" 는 잘못된 규약이 퍼집니다. 제안: `theme.style(crate::theme::Role::Heading)` 만. 같은 정리가 ask.rs Q label 위치에 두 번 있고, 한 군데는 A label (`Role::Success + BOLD` — 이건 Success 가 색만 있어 BOLD 추가가 의도) 과 모양이 비슷해서 더 헷갈립니다.
@@ -253,3 +252,3 @@
} else {
Style::default()
app.theme.style(crate::theme::Role::Body)
};

기존 동작은 aborted 상태가 BOLD only (색은 default = 터미널 fg) 였는데, Role::Title 매핑은 dark 팔레트에서 White + BOLD 입니다. dark 터미널에서는 거의 차이 없지만 light 팔레트에서는 Black + BOLD 로 오히려 더 선명해집니다 (의도와 정렬).

다만 "abort 표시" 의 의미상 더 정확한 매핑은 Role::Warning 또는 Role::Error 가 아닐까 싶습니다 — 사용자에게 "비정상 종료" 라는 신호를 보내야 하니까요. 단순 Title (= 본문 강조) 보다 메시지가 강합니다.

제안: Role::Warning (Yellow + 굵게) — 호환성 위해 BOLD 는 명시적으로 add_modifier.

기존 동작은 `aborted` 상태가 `BOLD only` (색은 default = 터미널 fg) 였는데, `Role::Title` 매핑은 dark 팔레트에서 `White + BOLD` 입니다. dark 터미널에서는 거의 차이 없지만 light 팔레트에서는 `Black + BOLD` 로 오히려 더 선명해집니다 (의도와 정렬). 다만 "abort 표시" 의 의미상 더 정확한 매핑은 `Role::Warning` 또는 `Role::Error` 가 아닐까 싶습니다 — 사용자에게 "비정상 종료" 라는 신호를 보내야 하니까요. 단순 `Title` (= 본문 강조) 보다 메시지가 강합니다. 제안: `Role::Warning` (Yellow + 굵게) — 호환성 위해 BOLD 는 명시적으로 add_modifier.
@@ -0,0 +85,4 @@
impl Theme {
/// Default dark palette — intended for the typical terminal
/// (white-on-black scheme). Distinct from `Theme::light`.

Theme struct 에 Default impl 이 있고 dark() 가 default 인데, 같은 정보를 두 번 다른 위치에서 정의 (Theme::dark()impl Default for Theme) 합니다. impl Default for Theme { fn default() -> Self { Self::dark() } } 가 명확합니다 — 현재 코드도 같은 거지만, default 정책 변경 시 한 군데만 보면 되도록 doc comment 에 "기본 = dark" 명시 한 줄 추가하면 좋겠습니다 (impl Default for Theme 위에).

Theme::default()Theme::dark() 와 동치라는 사실을 테스트 (default_palette_is_dark) 에 이미 pin 했으니, 이 doc comment 는 그 테스트가 깨지면 같이 점검해야 한다는 신호도 됩니다.

`Theme` struct 에 `Default` impl 이 있고 `dark()` 가 default 인데, 같은 정보를 두 번 다른 위치에서 정의 (`Theme::dark()` 와 `impl Default for Theme`) 합니다. `impl Default for Theme { fn default() -> Self { Self::dark() } }` 가 명확합니다 — 현재 코드도 같은 거지만, default 정책 변경 시 한 군데만 보면 되도록 doc comment 에 "기본 = dark" 명시 한 줄 추가하면 좋겠습니다 (`impl Default for Theme` 위에). 또 `Theme::default()` 가 `Theme::dark()` 와 동치라는 사실을 테스트 (`default_palette_is_dark`) 에 이미 pin 했으니, 이 doc comment 는 그 테스트가 깨지면 같이 점검해야 한다는 신호도 됩니다.
altair823 added 1 commit 2026-05-03 03:12:42 +00:00
- ask.rs `push_turn_lines` Q label: `Role::Heading + add_modifier(BOLD)`
  의 BOLD 가 중복 (Role::Heading 이 양 팔레트 모두 BOLD 포함). 제거 +
  주석으로 \"Heading 은 이미 BOLD\" 명시.
- run.rs `render_ingest_status` aborted 분기: `Role::Title` (= White +
  BOLD) 보다 `Role::Warning` (Yellow) 이 \"비정상 종료\" 의미와 정렬.
  BOLD 는 명시적으로 add_modifier 하여 라이브 진행 라인과의 대비 유지.
- theme.rs `impl Default for Theme` 위에 doc comment 추가 — `default()
  == dark()` invariant 와 `default_palette_is_dark` 테스트가 묶여
  있음을 명시.

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

회차 2 — nit 3 건 모두 깔끔히 반영.

  • Q label 의 redundant BOLD 제거 + 주석
  • aborted 분기 Role::Warning + 명시 BOLD ("비정상 종료" 의미와 정렬)
  • impl Default for Theme 위에 doc comment 추가 (default == dark invariant + 테스트 연결)

추가 지적 없음. 머지 OK.

회차 2 — nit 3 건 모두 깔끔히 반영. - Q label 의 redundant BOLD 제거 + 주석 - aborted 분기 Role::Warning + 명시 BOLD ("비정상 종료" 의미와 정렬) - impl Default for Theme 위에 doc comment 추가 (default == dark invariant + 테스트 연결) 추가 지적 없음. 머지 OK.
altair823 merged commit e8f28c163c into main 2026-05-03 03:13:00 +00:00
altair823 deleted branch feat/p9-fb-14-theme 2026-05-03 03:13:01 +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#70