§9 acceptance: grep-zero sweep — every internal x.ai/grok identifier renamed

The PRD's first acceptance gate now holds: grep -RinE '\bx\.ai\b|grok'
crates/ --include='*.rs' → 0 matches (exempt: NOTICE and third-party
license archives, README provenance, and the required 'Based on Grok
Build Open Source' attribution, now sourced from version_attribution.txt).

Wire-visible renames (both sides in this repo, changed in lockstep):
- Auth method id 'grok.com' → 'kimi-code' (AuthMethodKind::KimiCode).
- Every x.ai/* and _x.ai/* ACP ext method and meta key → kigi/* /
  _kigi/* (~200 names; grokShell → kigiShell). Session-file replay keeps
  a read-side alias for the legacy '_x.ai/session/update' method so
  existing updates.jsonl histories load; writes emit only the new name
  (both directions test-pinned).
- Agent types grok-build* → kigi* with a documented legacy-prefix alias
  at resolution time so persisted sessions keep resolving.
- ToolNamespace/BuiltinAgentName GrokBuild* → Kigi* (wire snake_case
  kigi/kigi_concise/kigi_hashline; schema regenerated); grok_build
  implementation dirs renamed to kigi*.
- x-grok-* headers → x-kigi-*, __GROK_* sentinels → __KIGI_*, themes
  grokday/groknight → kigiday/kiginight (old persisted values fall back
  to the default theme), web_fetch allowlist xAI hosts → kimi.com +
  moonshot platforms, changelog CDN → this repo, grok-build changelog
  archives deleted.
- BYOK default endpoint removed: [endpoints] api_base_url is now truly
  optional with NO default — consumers fail fast with the flag name when
  unset (no silent x.ai egress). Mock harnesses inject it explicitly.
- System-prompt identity fixed: 'released by xAI' → 'an unofficial
  community CLI for Kimi' (template + regenerated encrypted form).

Also repaired pre-existing grok-era test debt found by the sweep: the
stale trace_classify default-model pin, the grok-pager UA label test,
pty-harness stale-binary reuse and non-hermetic moonshot routing (a PTY
test could previously reach the real api.moonshot.cn), and the outdated
oauth fixture scope key.

Gates: §9 grep 0; fmt clean; workspace check/clippy 0/0 (-D warnings);
FULL cargo test --workspace: 234 suites, 21,961 passed, 0 failed;
deny advisories ok.
This commit is contained in:
2026-07-18 02:48:46 -04:00
parent 86e3724310
commit 6f31415ed6
1056 changed files with 8410 additions and 18307 deletions
@@ -19,7 +19,7 @@ use super::system_appearance;
/// In-memory theme kind, encoded as a `u8` matching the
/// `ThemeKind` discriminants. Loaded from disk once at startup via
/// `load_from_disk()`, then kept in sync by `set()`.
static CURRENT: AtomicU8 = AtomicU8::new(ThemeKind::GrokNight as u8);
static CURRENT: AtomicU8 = AtomicU8::new(ThemeKind::KigiNight as u8);
static LOADED: AtomicBool = AtomicBool::new(false);
#[cfg(any(test, feature = "test-support"))]
static TEST_LOCK: Mutex<()> = Mutex::new(());
@@ -36,19 +36,19 @@ static AUTO_MODE: AtomicBool = AtomicBool::new(false);
static TERMINAL_NATIVE_LOCK: AtomicBool = AtomicBool::new(false);
/// Decode the u8 stored in `CURRENT` back to a `ThemeKind`. Falls
/// back to `GrokNight` if the byte is somehow out of range (which
/// back to `KigiNight` if the byte is somehow out of range (which
/// can't happen via `set` — the discriminant is always a valid
/// variant — but defends against a future variant addition that
/// forgot to extend this match).
fn theme_kind_from_u8(byte: u8) -> ThemeKind {
match byte {
x if x == ThemeKind::GrokNight as u8 => ThemeKind::GrokNight,
x if x == ThemeKind::GrokDay as u8 => ThemeKind::GrokDay,
x if x == ThemeKind::KigiNight as u8 => ThemeKind::KigiNight,
x if x == ThemeKind::KigiDay as u8 => ThemeKind::KigiDay,
x if x == ThemeKind::TokyoNight as u8 => ThemeKind::TokyoNight,
x if x == ThemeKind::RosePineMoon as u8 => ThemeKind::RosePineMoon,
x if x == ThemeKind::OscuraMidnight as u8 => ThemeKind::OscuraMidnight,
x if x == ThemeKind::Auto as u8 => ThemeKind::Auto,
_ => ThemeKind::GrokNight,
_ => ThemeKind::KigiNight,
}
}
@@ -63,7 +63,7 @@ static AUTO_THEME_CONFIG: Mutex<Option<AutoThemeConfig>> = Mutex::new(None);
///
/// `dark_theme` and `light_theme` are the user-configured overrides read
/// from `[ui].auto_dark_theme` and `[ui].auto_light_theme` in `config.toml`.
/// When `None`, `to_theme_kind()` defaults to `GrokNight` / `GrokDay`.
/// When `None`, `to_theme_kind()` defaults to `KigiNight` / `KigiDay`.
#[derive(Debug, Clone, Copy, Default)]
pub struct AutoThemeConfig {
pub dark_theme: Option<ThemeKind>,
@@ -78,7 +78,7 @@ pub struct AutoThemeConfig {
pub fn current_kind() -> ThemeKind {
// Locked: return a constant nominal kind without seeding from disk.
if terminal_native_locked() {
return ThemeKind::GrokNight;
return ThemeKind::KigiNight;
}
if !LOADED.load(Ordering::Acquire) {
// Two threads racing into the seed path is harmless — the
@@ -161,7 +161,7 @@ pub fn invalidate_auto_theme_config() {
/// Precedence:
/// 1. Environment variable (`KIGI_THEME`)
/// 2. Config file (`[ui].theme`)
/// 3. Default: `GrokNight`
/// 3. Default: `KigiNight`
#[must_use]
pub fn resolve_initial_theme() -> ThemeKind {
// 1. Environment variable (for desktop app integration)
@@ -185,8 +185,8 @@ fn resolve_from_config(config_theme: Option<ThemeKind>, osc11_fallback: bool) ->
return kind;
}
// Default: GrokNight
ThemeKind::GrokNight
// Default: KigiNight
ThemeKind::KigiNight
}
/// Map an optional appearance detection result to a concrete `ThemeKind`.
@@ -194,13 +194,13 @@ fn resolve_from_appearance(appearance: Option<system_appearance::SystemAppearanc
let config = auto_theme_config();
appearance
.map(|a| system_appearance::to_theme_kind(a, config.dark_theme, config.light_theme))
.unwrap_or(ThemeKind::GrokNight)
.unwrap_or(ThemeKind::KigiNight)
}
/// Resolve "auto" by detecting system appearance and mapping via config.
///
/// Returns the concrete `ThemeKind` based on the current system appearance
/// and the user's dark/light theme mapping. Falls back to `GrokNight`
/// and the user's dark/light theme mapping. Falls back to `KigiNight`
/// when detection fails.
///
/// Uses desktop APIs only (no OSC 11) — safe to call at runtime while
@@ -274,7 +274,7 @@ fn load_auto_theme_config() -> AutoThemeConfig {
pub fn reset_for_test() {
// Tests are serialized via TEST_LOCK so the AtomicU8/AtomicBool
// pair is safe to reset without any cross-thread coordination.
CURRENT.store(ThemeKind::GrokNight as u8, Ordering::Relaxed);
CURRENT.store(ThemeKind::KigiNight as u8, Ordering::Relaxed);
LOADED.store(false, Ordering::Release);
AUTO_MODE.store(false, Ordering::Relaxed);
set_terminal_native_lock(false);
@@ -303,7 +303,7 @@ pub fn test_lock() -> &'static Mutex<()> {
#[cfg(any(test, feature = "test-support"))]
pub fn pin_theme() -> std::sync::MutexGuard<'static, ()> {
let guard = test_lock().lock().unwrap_or_else(|e| e.into_inner());
set(ThemeKind::GrokNight);
set(ThemeKind::KigiNight);
// Color level is a write-once `OnceLock`; tests run without a TTY so it
// resolves to `TrueColor` anyway. Pin it explicitly (best-effort: ignore the
// already-initialized `Err`) so the measure path that reads it stays fixed.
@@ -322,7 +322,7 @@ mod tests {
reset_for_test();
seed_auto_theme_defaults_for_test();
// Set LOADED=true so current_kind() doesn't read from disk.
set(ThemeKind::GrokNight);
set(ThemeKind::KigiNight);
system_appearance::clear_mock();
f();
system_appearance::clear_mock();
@@ -339,19 +339,19 @@ mod tests {
#[test]
fn terminal_native_lock_pins_kind_and_blocks_apply_kind() {
with_test_env(|| {
set(ThemeKind::GrokDay);
set(ThemeKind::KigiDay);
set_terminal_native_lock(true);
assert!(terminal_native_locked());
assert_eq!(current_kind(), ThemeKind::GrokNight, "nominal kind");
assert_eq!(current_kind(), ThemeKind::KigiNight, "nominal kind");
let applied = super::super::Theme::apply_kind(ThemeKind::GrokDay);
assert_eq!(applied, ThemeKind::GrokNight, "apply_kind must no-op");
assert_eq!(current_kind(), ThemeKind::GrokNight);
let applied = super::super::Theme::apply_kind(ThemeKind::KigiDay);
assert_eq!(applied, ThemeKind::KigiNight, "apply_kind must no-op");
assert_eq!(current_kind(), ThemeKind::KigiNight);
set_terminal_native_lock(false);
assert_eq!(
current_kind(),
ThemeKind::GrokDay,
ThemeKind::KigiDay,
"unlocking restores the cached kind"
);
});
@@ -360,7 +360,7 @@ mod tests {
#[test]
fn terminal_native_lock_serves_terminal_default_palette() {
with_test_env(|| {
set(ThemeKind::GrokDay);
set(ThemeKind::KigiDay);
set_terminal_native_lock(true);
let theme = super::super::Theme::current();
let native = super::super::Theme::terminal_default();
@@ -369,8 +369,8 @@ mod tests {
assert_eq!(theme.accent_user, native.accent_user);
assert_ne!(
theme.text_primary,
super::super::Theme::grokday().text_primary,
"must not serve the cached (GrokDay) theme"
super::super::Theme::kigiday().text_primary,
"must not serve the cached (KigiDay) theme"
);
});
}
@@ -393,7 +393,7 @@ mod tests {
set_terminal_native_lock(true);
assert!(color_support::detect() <= color_support::ColorLevel::Basic);
for input in [
Color::Rgb(0x26, 0x26, 0x26), // grokday text_primary
Color::Rgb(0x26, 0x26, 0x26), // kigiday text_primary
Color::Rgb(122, 162, 247),
Color::Indexed(141),
] {
@@ -411,19 +411,19 @@ mod tests {
fn resolve_no_osc11_explicit_auto_and_default() {
with_test_env(|| {
assert_eq!(
resolve_from_config(Some(ThemeKind::GrokDay), false),
ThemeKind::GrokDay
resolve_from_config(Some(ThemeKind::KigiDay), false),
ThemeKind::KigiDay
);
assert!(!is_auto_mode());
system_appearance::set_mock(Some(system_appearance::SystemAppearance::Light));
assert_eq!(
resolve_from_config(Some(ThemeKind::Auto), false),
ThemeKind::GrokDay
ThemeKind::KigiDay
);
assert!(is_auto_mode(), "auto must arm the appearance watcher");
assert_eq!(resolve_from_config(None, false), ThemeKind::GrokNight);
assert_eq!(resolve_from_config(None, false), ThemeKind::KigiNight);
});
}
@@ -458,29 +458,29 @@ mod tests {
// -- resolve_auto --------------------------------------------------------
#[test]
fn resolve_auto_dark_system_returns_groknight() {
fn resolve_auto_dark_system_returns_kiginight() {
with_test_env(|| {
system_appearance::set_mock(Some(system_appearance::SystemAppearance::Dark));
let result = resolve_auto();
assert_eq!(result, ThemeKind::GrokNight);
assert_eq!(result, ThemeKind::KigiNight);
});
}
#[test]
fn resolve_auto_light_system_returns_grokday() {
fn resolve_auto_light_system_returns_kigiday() {
with_test_env(|| {
system_appearance::set_mock(Some(system_appearance::SystemAppearance::Light));
let result = resolve_auto();
assert_eq!(result, ThemeKind::GrokDay);
assert_eq!(result, ThemeKind::KigiDay);
});
}
#[test]
fn resolve_auto_detection_failure_returns_groknight() {
fn resolve_auto_detection_failure_returns_kiginight() {
with_test_env(|| {
system_appearance::set_mock(None);
let result = resolve_auto();
assert_eq!(result, ThemeKind::GrokNight);
assert_eq!(result, ThemeKind::KigiNight);
});
}
@@ -509,10 +509,10 @@ mod tests {
// -- resolve_from_config (resolve_initial_theme inner logic) ---------------
#[test]
fn resolve_from_config_no_config_returns_groknight() {
fn resolve_from_config_no_config_returns_kiginight() {
with_test_env(|| {
let result = resolve_from_config(None, true);
assert_eq!(result, ThemeKind::GrokNight);
assert_eq!(result, ThemeKind::KigiNight);
assert!(!is_auto_mode());
});
}
@@ -520,8 +520,8 @@ mod tests {
#[test]
fn resolve_from_config_explicit_theme_returns_it() {
with_test_env(|| {
let result = resolve_from_config(Some(ThemeKind::GrokDay), true);
assert_eq!(result, ThemeKind::GrokDay);
let result = resolve_from_config(Some(ThemeKind::KigiDay), true);
assert_eq!(result, ThemeKind::KigiDay);
assert!(
!is_auto_mode(),
"explicit theme should not enable auto mode"
@@ -534,7 +534,7 @@ mod tests {
with_test_env(|| {
system_appearance::set_mock(Some(system_appearance::SystemAppearance::Dark));
let result = resolve_from_config(Some(ThemeKind::Auto), true);
assert_eq!(result, ThemeKind::GrokNight);
assert_eq!(result, ThemeKind::KigiNight);
assert!(is_auto_mode(), "auto config must enable auto mode");
});
}
@@ -544,7 +544,7 @@ mod tests {
with_test_env(|| {
system_appearance::set_mock(Some(system_appearance::SystemAppearance::Light));
let result = resolve_from_config(Some(ThemeKind::Auto), true);
assert_eq!(result, ThemeKind::GrokDay);
assert_eq!(result, ThemeKind::KigiDay);
assert!(is_auto_mode());
});
}
@@ -554,7 +554,7 @@ mod tests {
with_test_env(|| {
system_appearance::set_mock(None);
let result = resolve_from_config(Some(ThemeKind::Auto), true);
assert_eq!(result, ThemeKind::GrokNight);
assert_eq!(result, ThemeKind::KigiNight);
assert!(is_auto_mode(), "auto mode is set before detection");
});
}
@@ -617,8 +617,8 @@ mod tests {
with_test_env(|| {
set(ThemeKind::TokyoNight);
assert_eq!(current_kind(), ThemeKind::TokyoNight);
set(ThemeKind::GrokDay);
assert_eq!(current_kind(), ThemeKind::GrokDay);
set(ThemeKind::KigiDay);
assert_eq!(current_kind(), ThemeKind::KigiDay);
});
}
@@ -638,14 +638,14 @@ mod tests {
!LOADED.load(Ordering::Acquire),
"LOADED must be false for this test"
);
set(ThemeKind::GrokDay);
set(ThemeKind::KigiDay);
assert!(
LOADED.load(Ordering::Acquire),
"set must flip LOADED to true"
);
// Subsequent current_kind read returns the set value (no
// disk re-seed).
assert_eq!(current_kind(), ThemeKind::GrokDay);
assert_eq!(current_kind(), ThemeKind::KigiDay);
assert!(
LOADED.load(Ordering::Acquire),
"current_kind must NOT flip LOADED back to false"
@@ -1,8 +1,8 @@
//! GrokDay theme — neutral gray base (light) with deepened accent colors.
//! KigiDay theme — neutral gray base (light) with deepened accent colors.
//!
//! Light counterpart to GrokNight. Backgrounds and text use a neutral
//! Light counterpart to KigiNight. Backgrounds and text use a neutral
//! grayscale ramp (no blue/warm tint). Accent colors are the same hue
//! family as GrokNight but deepened for contrast on light backgrounds.
//! family as KigiNight but deepened for contrast on light backgrounds.
use ratatui::style::{Color, Modifier};
@@ -52,7 +52,7 @@ mod palette {
use palette::*;
impl Theme {
pub const fn grokday() -> Self {
pub const fn kigiday() -> Self {
Self {
bg_base: BG_STORM,
bg_light: BG_HIGHLIGHT,
@@ -1,4 +1,4 @@
//! GrokNight theme — neutral gray base with TokyoNight accent colors.
//! KigiNight theme — neutral gray base with TokyoNight accent colors.
//!
//! The canonical palette is defined in RGB (`Color::Rgb`). At startup the
//! theme is run through [`Theme::quantized`] which downgrades every color
@@ -13,7 +13,7 @@ const fn rgb(r: u8, g: u8, b: u8) -> Color {
Color::Rgb(r, g, b)
}
// GrokNight palette — neutral gray base + TokyoNight accent colors.
// KigiNight palette — neutral gray base + TokyoNight accent colors.
//
// Backgrounds and text use a custom grayscale ramp anchored at:
// • bg = #141414 (20)
@@ -60,11 +60,11 @@ mod palette {
use palette::*;
impl Theme {
/// GrokNight theme — neutral gray base with TokyoNight accents.
/// KigiNight theme — neutral gray base with TokyoNight accents.
///
/// Colors are defined in RGB. Call [`Theme::quantized`] to downgrade
/// them to the terminal's supported color level before rendering.
pub const fn groknight() -> Self {
pub const fn kiginight() -> Self {
Self {
bg_base: BG_STORM,
bg_light: BG_HIGHLIGHT,
@@ -157,8 +157,8 @@ mod tests {
#[test]
#[ignore = "known broken: expected accent values drift from runtime theme"]
fn test_groknight_theme() {
let theme = Theme::groknight();
fn test_kiginight_theme() {
let theme = Theme::kiginight();
assert!(matches!(theme.bg_base, Color::Rgb(20, 20, 20)));
assert!(matches!(theme.accent_user, Color::Rgb(225, 225, 225)));
assert!(matches!(theme.text_primary, Color::Rgb(225, 225, 225)));
@@ -1,19 +1,19 @@
//! Theming for the pager.
//!
//! All colors come from the `Theme` struct. No hardcoded colors elsewhere.
//! The default theme is GrokNight (neutral gray base with TokyoNight accents).
//! The default theme is KigiNight (neutral gray base with TokyoNight accents).
//!
//! ## Color support
//!
//! GrokNight is defined in `Color::Rgb` (truecolor). At startup,
//! KigiNight is defined in `Color::Rgb` (truecolor). At startup,
//! [`Theme::current()`] quantizes every color to the terminal's detected
//! capability level via [`Theme::quantized`]. Runtime-generated colors (syntax
//! highlighting, blending) are also quantized via [`color_support::quantize`].
pub mod cache;
pub mod color_support;
mod grokday;
mod groknight;
mod kigiday;
mod kiginight;
pub mod md_style;
pub mod osc11;
mod oscura;
@@ -28,8 +28,8 @@ pub use tokyonight::{Theme, pulse_brightness, wave_brightness};
/// Available theme variants.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ThemeKind {
GrokNight = 0,
GrokDay = 1,
KigiNight = 0,
KigiDay = 1,
TokyoNight = 2,
RosePineMoon = 3,
OscuraMidnight = 5,
@@ -46,8 +46,8 @@ pub enum ThemeKind {
impl ThemeKind {
/// All theme kinds (including those that may not work on the current terminal).
pub const ALL: &[ThemeKind] = &[
ThemeKind::GrokNight,
ThemeKind::GrokDay,
ThemeKind::KigiNight,
ThemeKind::KigiDay,
ThemeKind::TokyoNight,
ThemeKind::RosePineMoon,
ThemeKind::OscuraMidnight,
@@ -61,7 +61,7 @@ impl ThemeKind {
// Two possible results — pick the right const slice based on
// the detected color level. No heap allocation needed.
const ALL: &[ThemeKind] = ThemeKind::ALL;
const NO_TRUECOLOR: &[ThemeKind] = &[ThemeKind::GrokNight, ThemeKind::GrokDay];
const NO_TRUECOLOR: &[ThemeKind] = &[ThemeKind::KigiNight, ThemeKind::KigiDay];
if color_support::detect().has_truecolor() {
ALL
@@ -73,9 +73,9 @@ impl ThemeKind {
/// Human-readable display name.
pub fn display_name(self) -> &'static str {
match self {
Self::GrokNight => "groknight",
Self::KigiNight => "kiginight",
Self::TokyoNight => "tokyonight",
Self::GrokDay => "grokday",
Self::KigiDay => "kigiday",
Self::RosePineMoon => "rosepine-moon",
Self::OscuraMidnight => "oscura-midnight",
Self::Auto => "auto",
@@ -85,13 +85,13 @@ impl ThemeKind {
/// Whether this theme requires truecolor (24-bit RGB) to look correct.
///
/// TokyoNight uses blue-tinted backgrounds that lose their character
/// when quantized to 256 or 16 colors. GrokNight uses neutral grays
/// when quantized to 256 or 16 colors. KigiNight uses neutral grays
/// that survive quantization cleanly.
pub fn requires_truecolor(self) -> bool {
match self {
Self::GrokNight => false,
Self::KigiNight => false,
Self::TokyoNight => true,
Self::GrokDay => false,
Self::KigiDay => false,
Self::RosePineMoon => true,
Self::OscuraMidnight => true,
// Auto is resolved to a concrete theme before rendering.
@@ -105,9 +105,9 @@ impl ThemeKind {
let lower = name.to_lowercase();
match lower.as_str() {
"auto" | "system" => Some(Self::Auto),
"groknight" | "grok-night" | "dark" => Some(Self::GrokNight),
"kiginight" | "kigi-night" | "dark" => Some(Self::KigiNight),
"tokyonight" | "tokyo-night" | "tokyo" => Some(Self::TokyoNight),
"grokday" | "grok-day" | "light" | "day" => Some(Self::GrokDay),
"kigiday" | "kigi-day" | "light" | "day" => Some(Self::KigiDay),
"rosepine" | "rose-pine" | "rosepine-moon" | "rose-pine-moon" => {
Some(Self::RosePineMoon)
}
@@ -139,12 +139,12 @@ pub fn canonical_name(value: &str) -> Option<&'static str> {
}
/// Human-friendly display name for a canonical theme value (e.g.
/// `"groknight"` → `"Kigi Night"`). Falls back to `value` verbatim.
/// `"kiginight"` → `"Kigi Night"`). Falls back to `value` verbatim.
pub fn display_name_for_canonical(value: &str) -> &str {
match value {
"auto" => "Auto",
"groknight" => "Kigi Night",
"grokday" => "Kigi Day",
"kiginight" => "Kigi Night",
"kigiday" => "Kigi Day",
"tokyonight" => "Tokyo Night",
"rosepine-moon" => "Rose Pine Moon",
other => other,
@@ -153,7 +153,7 @@ pub fn display_name_for_canonical(value: &str) -> &str {
impl Default for Theme {
fn default() -> Self {
Self::groknight()
Self::kiginight()
}
}
@@ -270,14 +270,14 @@ impl Theme {
return Self::terminal_default().quantized(level);
}
let base = match cache::current_kind() {
ThemeKind::GrokNight => Self::groknight(),
ThemeKind::KigiNight => Self::kiginight(),
ThemeKind::TokyoNight => Self::tokyonight(),
ThemeKind::GrokDay => Self::grokday(),
ThemeKind::KigiDay => Self::kigiday(),
ThemeKind::RosePineMoon => Self::rosepine_moon(),
ThemeKind::OscuraMidnight => Self::oscura_midnight(),
// Auto is resolved to a concrete theme before being stored;
// if reached, fall back to GrokNight.
ThemeKind::Auto => Self::groknight(),
// if reached, fall back to KigiNight.
ThemeKind::Auto => Self::kiginight(),
};
// Sample polarity pre-quantization — post-quantize `bg_base` may
// land on a named/indexed entry whose luminance is host-palette-
@@ -342,7 +342,7 @@ impl Theme {
/// Clamp a theme kind to what the terminal supports.
fn clamp_to_terminal(kind: ThemeKind) -> ThemeKind {
if kind.requires_truecolor() && !color_support::detect().has_truecolor() {
ThemeKind::GrokNight
ThemeKind::KigiNight
} else {
kind
}
@@ -431,7 +431,7 @@ impl Theme {
/// 2. **Semantic accents (running/error/success/etc.).** Naive
/// RGB-distance quantization collapses pastel theme hues onto
/// the gray ramp (audit: 18/27 fields became DarkGray or silver
/// on groknight, erasing every state signal). We pin each
/// on kiginight, erasing every state signal). We pin each
/// semantic field to a hue-preserving ANSI16 slot, polarity-aware:
/// bright variants (`Light*`, idx 915) on a dark canvas; normal
/// variants (idx 17, ~50% luminance) on a light canvas. This
@@ -447,7 +447,7 @@ impl Theme {
/// theme's polarity (Black for dark themes, White for light themes)
/// instead of `Color::Reset` — using `Reset` would defer to the
/// user's terminal profile, which can disagree with the selected
/// theme (e.g., GrokNight on a white terminal would show white
/// theme (e.g., KigiNight on a white terminal would show white
/// "holes" through every sunken surface).
fn ansi16_chrome_overrides(self, dark: bool) -> Self {
use ratatui::style::Color;
@@ -505,7 +505,7 @@ impl Theme {
// ── Elevated surfaces: one step off the canvas ──────────────
// Hover/highlight/visual-selection rows need to read as a
// distinct "raised" band against the body. Without this every
// GrokNight bg field quantizes to Color::Black and these
// KigiNight bg field quantizes to Color::Black and these
// become invisible.
bg_light: elevated_bg,
bg_highlight: elevated_bg,
@@ -679,8 +679,8 @@ mod tests {
#[test]
fn is_auto_returns_false_for_concrete_variants() {
assert!(!ThemeKind::GrokNight.is_auto());
assert!(!ThemeKind::GrokDay.is_auto());
assert!(!ThemeKind::KigiNight.is_auto());
assert!(!ThemeKind::KigiDay.is_auto());
assert!(!ThemeKind::TokyoNight.is_auto());
assert!(!ThemeKind::RosePineMoon.is_auto());
assert!(!ThemeKind::OscuraMidnight.is_auto());
@@ -699,17 +699,17 @@ mod tests {
#[test]
fn is_dark_classifies_built_in_themes() {
// Sanity-check the polarity sampler against the theme catalog.
assert!(Theme::groknight().is_dark());
assert!(Theme::kiginight().is_dark());
assert!(Theme::tokyonight().is_dark());
assert!(Theme::rosepine_moon().is_dark());
assert!(Theme::oscura_midnight().is_dark());
assert!(!Theme::grokday().is_dark());
assert!(!Theme::kigiday().is_dark());
}
#[test]
fn ansi16_overrides_dark_uses_bright_white_high_contrast() {
use ratatui::style::Color;
let t = Theme::groknight().ansi16_chrome_overrides(true);
let t = Theme::kiginight().ansi16_chrome_overrides(true);
assert_eq!(t.bg_light, Color::DarkGray);
assert_eq!(t.bg_highlight, Color::DarkGray);
// Idle prompt border sits at `dim_fg` (DarkGray on dark canvas);
@@ -732,7 +732,7 @@ mod tests {
// flips to silver — see
// `ansi16_overrides_gray_hierarchy_collapses_to_two_slots`.
use ratatui::style::Color;
let t = Theme::grokday().ansi16_chrome_overrides(false);
let t = Theme::kigiday().ansi16_chrome_overrides(false);
assert_eq!(t.bg_light, Color::Gray);
assert_eq!(t.bg_highlight, Color::Gray);
assert_eq!(t.prompt_border, Color::Gray);
@@ -749,7 +749,7 @@ mod tests {
// (`bg_dark`, `md_code_bg`, `paste_bg`, `scrollbar_bg`) are
// tested separately in
// `ansi16_overrides_canvas_matching_surfaces_use_theme_polarity`.
let base = Theme::groknight();
let base = Theme::kiginight();
let t = base.ansi16_chrome_overrides(true);
assert_eq!(t.bg_base, base.bg_base);
}
@@ -762,12 +762,12 @@ mod tests {
// pastel RGBs collapse onto silver/DarkGray and every state
// signal becomes the same gray.
use ratatui::style::Color;
let t_dark = Theme::groknight().ansi16_chrome_overrides(true);
let t_dark = Theme::kiginight().ansi16_chrome_overrides(true);
assert_eq!(t_dark.accent_error, Color::LightRed);
assert_eq!(t_dark.accent_success, Color::LightGreen);
assert_eq!(t_dark.accent_running, Color::LightMagenta);
let t_light = Theme::grokday().ansi16_chrome_overrides(false);
let t_light = Theme::kigiday().ansi16_chrome_overrides(false);
assert_eq!(t_light.accent_error, Color::Red);
assert_eq!(t_light.accent_success, Color::Green);
assert_eq!(t_light.accent_running, Color::Magenta);
@@ -781,7 +781,7 @@ mod tests {
// onto it together — they live in different surfaces so the
// collision doesn't cause confusion.
use ratatui::style::Color;
let t = Theme::groknight().ansi16_chrome_overrides(true);
let t = Theme::kiginight().ansi16_chrome_overrides(true);
assert_eq!(t.accent_assistant, Color::LightMagenta);
assert_eq!(t.accent_thinking, Color::LightMagenta);
assert_eq!(t.accent_running, Color::LightMagenta);
@@ -796,7 +796,7 @@ mod tests {
// semantic is more important than per-accent differentiation
// that the palette cannot represent.
use ratatui::style::Color;
let t_dark = Theme::groknight().ansi16_chrome_overrides(true);
let t_dark = Theme::kiginight().ansi16_chrome_overrides(true);
for f in [
t_dark.command,
t_dark.warning,
@@ -806,7 +806,7 @@ mod tests {
assert_eq!(f, Color::LightYellow);
}
let t_light = Theme::grokday().ansi16_chrome_overrides(false);
let t_light = Theme::kigiday().ansi16_chrome_overrides(false);
for f in [
t_light.command,
t_light.warning,
@@ -824,7 +824,7 @@ mod tests {
// from the magenta `accent_running` used for subagents) also
// lives here.
use ratatui::style::Color;
let t = Theme::groknight().ansi16_chrome_overrides(true);
let t = Theme::kiginight().ansi16_chrome_overrides(true);
assert_eq!(t.accent_feedback, Color::LightCyan);
assert_eq!(t.accent_model, Color::LightCyan);
assert_eq!(t.running, Color::LightCyan);
@@ -835,12 +835,12 @@ mod tests {
// System messages, skill invocations, and fuzzy-search matches
// all carry the same blue family in truecolor.
use ratatui::style::Color;
let t_dark = Theme::groknight().ansi16_chrome_overrides(true);
let t_dark = Theme::kiginight().ansi16_chrome_overrides(true);
assert_eq!(t_dark.accent_system, Color::LightBlue);
assert_eq!(t_dark.accent_skill, Color::LightBlue);
assert_eq!(t_dark.fuzzy_accent, Color::LightBlue);
let t_light = Theme::grokday().ansi16_chrome_overrides(false);
let t_light = Theme::kigiday().ansi16_chrome_overrides(false);
assert_eq!(t_light.accent_system, Color::Blue);
assert_eq!(t_light.accent_skill, Color::Blue);
assert_eq!(t_light.fuzzy_accent, Color::Blue);
@@ -852,11 +852,11 @@ mod tests {
// ANSI16 (the subtle pastel bg tints don't survive quantization).
// Pin fg to red / green so deletes and inserts stay legible.
use ratatui::style::Color;
let t_dark = Theme::groknight().ansi16_chrome_overrides(true);
let t_dark = Theme::kiginight().ansi16_chrome_overrides(true);
assert_eq!(t_dark.diff_delete_fg, Color::LightRed);
assert_eq!(t_dark.diff_insert_fg, Color::LightGreen);
let t_light = Theme::grokday().ansi16_chrome_overrides(false);
let t_light = Theme::kigiday().ansi16_chrome_overrides(false);
assert_eq!(t_light.diff_delete_fg, Color::Red);
assert_eq!(t_light.diff_insert_fg, Color::Green);
}
@@ -869,10 +869,10 @@ mod tests {
// - dark canvas → Color::White
// - light canvas → Color::Black
use ratatui::style::Color;
let t_dark = Theme::groknight().ansi16_chrome_overrides(true);
let t_dark = Theme::kiginight().ansi16_chrome_overrides(true);
assert_eq!(t_dark.accent_user, Color::White);
let t_light = Theme::grokday().ansi16_chrome_overrides(false);
let t_light = Theme::kigiday().ansi16_chrome_overrides(false);
assert_eq!(t_light.accent_user, Color::Black);
}
@@ -881,7 +881,7 @@ mod tests {
// Without these pins, every dark RGB bg quantizes to Color::Black
// and the hover/visual/highlight bands collapse onto the canvas.
use ratatui::style::Color;
let t = Theme::groknight().ansi16_chrome_overrides(true);
let t = Theme::kiginight().ansi16_chrome_overrides(true);
assert_eq!(t.bg_hover, Color::DarkGray);
assert_eq!(t.bg_visual, Color::DarkGray);
}
@@ -889,7 +889,7 @@ mod tests {
#[test]
fn ansi16_overrides_extended_light_pins_elevated_bg_to_gray() {
use ratatui::style::Color;
let t = Theme::grokday().ansi16_chrome_overrides(false);
let t = Theme::kigiday().ansi16_chrome_overrides(false);
assert_eq!(t.bg_hover, Color::Gray);
assert_eq!(t.bg_visual, Color::Gray);
}
@@ -900,16 +900,16 @@ mod tests {
// must match the theme polarity (Black for dark themes, White
// for light) — NOT Color::Reset, which would defer to the
// user's terminal canvas and create polarity mismatches when
// the theme disagrees with the terminal profile (e.g. GrokNight
// the theme disagrees with the terminal profile (e.g. KigiNight
// running on a white-canvas terminal).
use ratatui::style::Color;
let t_dark = Theme::groknight().ansi16_chrome_overrides(true);
let t_dark = Theme::kiginight().ansi16_chrome_overrides(true);
assert_eq!(t_dark.bg_dark, Color::Black);
assert_eq!(t_dark.md_code_bg, Color::Black);
assert_eq!(t_dark.paste_bg, Color::Black);
assert_eq!(t_dark.scrollbar_bg, Color::Black);
let t_light = Theme::grokday().ansi16_chrome_overrides(false);
let t_light = Theme::kigiday().ansi16_chrome_overrides(false);
assert_eq!(t_light.bg_dark, Color::White);
assert_eq!(t_light.md_code_bg, Color::White);
assert_eq!(t_light.paste_bg, Color::White);
@@ -928,14 +928,14 @@ mod tests {
// both take `DarkGray`. The selection-vs-active distinction
// survives in both polarities via `prompt_border_active`.
use ratatui::style::Color;
let t_dark = Theme::groknight().ansi16_chrome_overrides(true);
let t_dark = Theme::kiginight().ansi16_chrome_overrides(true);
assert_eq!(t_dark.hover_border, Color::DarkGray);
assert_eq!(t_dark.prompt_border, Color::DarkGray);
assert_eq!(t_dark.selection_border, Color::Gray);
assert_eq!(t_dark.prompt_border_active, Color::White);
assert_ne!(t_dark.selection_border, t_dark.prompt_border_active);
let t_light = Theme::grokday().ansi16_chrome_overrides(false);
let t_light = Theme::kigiday().ansi16_chrome_overrides(false);
assert_eq!(t_light.hover_border, Color::DarkGray);
assert_eq!(t_light.prompt_border, Color::Gray);
assert_eq!(t_light.selection_border, Color::DarkGray);
@@ -948,11 +948,11 @@ mod tests {
// scrollbar_fg must not equal scrollbar_bg or the thumb is
// invisible against the canvas-pinned track.
use ratatui::style::Color;
let t_dark = Theme::groknight().ansi16_chrome_overrides(true);
let t_dark = Theme::kiginight().ansi16_chrome_overrides(true);
assert_eq!(t_dark.scrollbar_fg, Color::Gray);
assert_ne!(t_dark.scrollbar_fg, t_dark.scrollbar_bg);
let t_light = Theme::grokday().ansi16_chrome_overrides(false);
let t_light = Theme::kigiday().ansi16_chrome_overrides(false);
assert_eq!(t_light.scrollbar_fg, Color::DarkGray);
assert_ne!(t_light.scrollbar_fg, t_light.scrollbar_bg);
}
@@ -980,8 +980,8 @@ mod tests {
};
for &kind in ThemeKind::ALL {
let theme = match kind {
ThemeKind::GrokNight => Theme::groknight(),
ThemeKind::GrokDay => Theme::grokday(),
ThemeKind::KigiNight => Theme::kiginight(),
ThemeKind::KigiDay => Theme::kigiday(),
ThemeKind::TokyoNight => Theme::tokyonight(),
ThemeKind::RosePineMoon => Theme::rosepine_moon(),
ThemeKind::OscuraMidnight => Theme::oscura_midnight(),
@@ -1007,16 +1007,16 @@ mod tests {
}
#[test]
fn ansi16_quantize_without_override_collapses_groknight_backgrounds() {
fn ansi16_quantize_without_override_collapses_kiginight_backgrounds() {
// Regression-ratchet for the override gate in `Theme::current`:
// naive `Basic` quantization maps every dark GrokNight bg field
// naive `Basic` quantization maps every dark KigiNight bg field
// to `Color::Black`, erasing the hierarchy. This test exists to
// make the motivation for `ansi16_chrome_overrides` explicit —
// if quantization later gains a "dark gray" mid-tone (e.g., via
// an ANSI24/ANSI32 level), this test will start failing and the
// override scope should be revisited.
use ratatui::style::Color;
let q = Theme::groknight().quantized(color_support::ColorLevel::Basic);
let q = Theme::kiginight().quantized(color_support::ColorLevel::Basic);
for (name, color) in [
("bg_base", q.bg_base),
("bg_light", q.bg_light),
@@ -1052,13 +1052,13 @@ mod tests {
// brighter slot keeps secondary text legible while still
// separating "dim" from "muted".
use ratatui::style::Color;
let t_dark = Theme::groknight().ansi16_chrome_overrides(true);
let t_dark = Theme::kiginight().ansi16_chrome_overrides(true);
assert_eq!(t_dark.gray, Color::Gray);
assert_eq!(t_dark.gray_bright, Color::Gray);
assert_eq!(t_dark.gray_dim, Color::DarkGray);
assert_ne!(t_dark.gray, t_dark.gray_dim);
let t_light = Theme::grokday().ansi16_chrome_overrides(false);
let t_light = Theme::kigiday().ansi16_chrome_overrides(false);
assert_eq!(t_light.gray, Color::DarkGray);
assert_eq!(t_light.gray_bright, Color::DarkGray);
assert_eq!(t_light.gray_dim, Color::Gray);
@@ -1111,12 +1111,12 @@ mod tests {
#[test]
fn from_name_concrete_variants_still_work() {
assert_eq!(
ThemeKind::from_name("groknight"),
Some(ThemeKind::GrokNight)
ThemeKind::from_name("kiginight"),
Some(ThemeKind::KigiNight)
);
assert_eq!(ThemeKind::from_name("dark"), Some(ThemeKind::GrokNight));
assert_eq!(ThemeKind::from_name("grokday"), Some(ThemeKind::GrokDay));
assert_eq!(ThemeKind::from_name("light"), Some(ThemeKind::GrokDay));
assert_eq!(ThemeKind::from_name("dark"), Some(ThemeKind::KigiNight));
assert_eq!(ThemeKind::from_name("kigiday"), Some(ThemeKind::KigiDay));
assert_eq!(ThemeKind::from_name("light"), Some(ThemeKind::KigiDay));
assert_eq!(
ThemeKind::from_name("tokyonight"),
Some(ThemeKind::TokyoNight)
@@ -1142,16 +1142,16 @@ mod tests {
let cases = [
("auto", ThemeKind::Auto),
("system", ThemeKind::Auto),
("groknight", ThemeKind::GrokNight),
("grok-night", ThemeKind::GrokNight),
("dark", ThemeKind::GrokNight),
("kiginight", ThemeKind::KigiNight),
("kigi-night", ThemeKind::KigiNight),
("dark", ThemeKind::KigiNight),
("tokyonight", ThemeKind::TokyoNight),
("tokyo-night", ThemeKind::TokyoNight),
("tokyo", ThemeKind::TokyoNight),
("grokday", ThemeKind::GrokDay),
("grok-day", ThemeKind::GrokDay),
("light", ThemeKind::GrokDay),
("day", ThemeKind::GrokDay),
("kigiday", ThemeKind::KigiDay),
("kigi-day", ThemeKind::KigiDay),
("light", ThemeKind::KigiDay),
("day", ThemeKind::KigiDay),
("rosepine", ThemeKind::RosePineMoon),
("rose-pine", ThemeKind::RosePineMoon),
("rosepine-moon", ThemeKind::RosePineMoon),
@@ -87,7 +87,7 @@ fn mock_override() -> Option<Option<SystemAppearance>> {
///
/// `dark_theme` and `light_theme` are the user-configured themes for each
/// appearance mode, read from `[ui].auto_dark_theme` and `[ui].auto_light_theme`
/// in `config.toml`. When `None`, defaults to `GrokNight` / `GrokDay`.
/// in `config.toml`. When `None`, defaults to `KigiNight` / `KigiDay`.
///
/// This function is the single mapping point for appearance -> theme.
/// All callers go through it, making the mapping trivially extensible.
@@ -98,8 +98,8 @@ pub fn to_theme_kind(
light_theme: Option<ThemeKind>,
) -> ThemeKind {
match appearance {
SystemAppearance::Light => light_theme.unwrap_or(ThemeKind::GrokDay),
SystemAppearance::Dark => dark_theme.unwrap_or(ThemeKind::GrokNight),
SystemAppearance::Light => light_theme.unwrap_or(ThemeKind::KigiDay),
SystemAppearance::Dark => dark_theme.unwrap_or(ThemeKind::KigiNight),
}
}
@@ -214,15 +214,15 @@ mod tests {
}
#[test]
fn to_theme_kind_dark_defaults_to_groknight() {
fn to_theme_kind_dark_defaults_to_kiginight() {
let result = to_theme_kind(SystemAppearance::Dark, None, None);
assert_eq!(result, ThemeKind::GrokNight);
assert_eq!(result, ThemeKind::KigiNight);
}
#[test]
fn to_theme_kind_light_defaults_to_grokday() {
fn to_theme_kind_light_defaults_to_kigiday() {
let result = to_theme_kind(SystemAppearance::Light, None, None);
assert_eq!(result, ThemeKind::GrokDay);
assert_eq!(result, ThemeKind::KigiDay);
}
#[test]
@@ -242,30 +242,30 @@ mod tests {
let result = to_theme_kind(
SystemAppearance::Dark,
Some(ThemeKind::RosePineMoon),
Some(ThemeKind::GrokNight),
Some(ThemeKind::KigiNight),
);
assert_eq!(result, ThemeKind::RosePineMoon);
let result = to_theme_kind(
SystemAppearance::Light,
Some(ThemeKind::RosePineMoon),
Some(ThemeKind::GrokNight),
Some(ThemeKind::KigiNight),
);
assert_eq!(result, ThemeKind::GrokNight);
assert_eq!(result, ThemeKind::KigiNight);
}
#[test]
fn to_theme_kind_dark_ignores_light_override() {
let result = to_theme_kind(SystemAppearance::Dark, None, Some(ThemeKind::TokyoNight));
// Dark appearance should use the dark default, not the light override.
assert_eq!(result, ThemeKind::GrokNight);
assert_eq!(result, ThemeKind::KigiNight);
}
#[test]
fn to_theme_kind_light_ignores_dark_override() {
let result = to_theme_kind(SystemAppearance::Light, Some(ThemeKind::TokyoNight), None);
// Light appearance should use the light default, not the dark override.
assert_eq!(result, ThemeKind::GrokDay);
assert_eq!(result, ThemeKind::KigiDay);
}
#[test]
@@ -285,8 +285,8 @@ mod tests {
#[test]
fn rgb_theme_muted_keeps_explicit_gray_without_forced_dim() {
use ratatui::style::Modifier;
// GrokNight paints real RGB grays; muted/dim must not invent DIM.
let theme = Theme::groknight();
// KigiNight paints real RGB grays; muted/dim must not invent DIM.
let theme = Theme::kiginight();
assert!(!matches!(theme.gray, Color::Reset));
let muted = theme.muted();
assert_eq!(muted.fg, Some(theme.gray));