Files
Kigi-CLI/crates/codegen/kigi-pager-minimal/src/welcome.rs
T
ZacharyZhang-NY 6f31415ed6 §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.
2026-07-18 02:48:46 -04:00

143 lines
5.5 KiB
Rust

//! Minimal-mode welcome card.
//!
//! Minimal skips the full-screen welcome view entirely, so the start of a
//! session is otherwise invisible — you land straight at the prompt. To make a
//! fresh session obvious (and on `/new` / `Ctrl+N`), this commits a compact,
//! rounded card once into native scrollback: the braille logo, the version, the
//! cwd, the model, and a one-line hint. It mirrors the full-TUI hero box's style
//! (rounded dim border + logo) without its menu/onboarding.
//!
//! It is printed via [`kigi_ratatui_inline::Terminal::insert_before`] — the same
//! one-shot mechanism the commit pipeline uses — gated on an `AppView` flag set
//! at session creation, so it prints exactly once per session and re-prints when
//! a new session starts.
use ratatui::style::{Modifier, Style};
use ratatui::text::{Line, Span};
use ratatui::widgets::{Block, BorderType, Borders, Widget};
use kigi_tui::app::PagerTerminal;
use kigi_tui::app::app_view::{ActiveView, AppView};
use kigi_tui::minimal_api;
use kigi_tui::theme::Theme;
/// Commit the welcome card when one is pending (set at session start / `/new`).
///
/// Called at the top of the minimal draw, before `commit_active`, so the card
/// lands above the first conversation block in native scrollback.
pub fn maybe_commit_welcome(app: &mut AppView, terminal: &mut PagerTerminal) {
if !minimal_api::minimal_welcome_pending(app) {
return;
}
let width = terminal.viewport_area().width;
// Too narrow to draw a bordered card — leave the flag set and retry next
// frame (e.g. during an initial 0-width probe).
if width < 8 {
return;
}
// NB: the pending flag is cleared only after the `insert_before` at the
// bottom SUCCEEDS — clearing it up front meant a failed insert silently
// dropped the card forever (bugbot). A failed frame retries next draw.
// Reset the live viewport to the TOP of the screen and clear what's visible,
// so the welcome card commits at row 0 and the app "owns" the window. The
// viewport is not bottom-pinned, so subsequent commits flow downward from
// here. Pre-existing native scrollback is untouched — scrolling up still
// shows whatever was there before.
let live_h = terminal.viewport_area().height;
terminal.set_viewport_area(ratatui::layout::Rect {
x: 0,
y: 0,
width,
height: live_h,
});
let _ = terminal.clear();
let theme = Theme::current();
let version = kigi_version::VERSION;
let (cwd, model) = match &app.active_view {
ActiveView::Agent(id) => {
let agent = app.agents.get(id);
(
agent
.map(|a| a.session.cwd.display().to_string())
.unwrap_or_default(),
agent.and_then(|a| a.session.models.current_model_name()),
)
}
_ => (app.cwd.display().to_string(), None),
};
// Info lines below the logo: title + version, cwd, optional model, hint.
let mut info: Vec<Line<'static>> = Vec::new();
info.push(Line::from(vec![
Span::styled(
"Kigi",
Style::default()
.fg(theme.accent_user)
.add_modifier(Modifier::BOLD),
),
Span::styled(format!(" v{version}"), theme.muted()),
]));
if !cwd.is_empty() {
info.push(Line::from(Span::styled(cwd, theme.muted())));
}
if let Some(model) = model {
info.push(Line::from(Span::styled(
format!("Model · {model}"),
theme.muted(),
)));
}
info.push(Line::from(Span::styled("/help for commands", theme.dim())));
let logo_lines = minimal_api::compact_logo_line_count();
// logo (+ a blank separator row) when present, then the info lines, wrapped
// in a border with one row of vertical padding top and bottom.
let logo_block = if logo_lines > 0 { logo_lines + 1 } else { 0 };
let height = 2 + 1 + logo_block + info.len() as u16 + 1;
// RGB themes: blend a soft border. Terminal-native (both Reset): fall
// through to Reset so the terminal default fg draws the chrome.
let border_color = kigi_tui::render::color::blend_color(theme.bg_base, theme.gray_dim, 0.45)
.unwrap_or(theme.gray_dim);
let inserted = terminal.insert_before(height, move |buf| {
let area = buf.area;
Block::new()
.borders(Borders::ALL)
.border_type(BorderType::Rounded)
.border_style(Style::default().fg(border_color))
.render(area, buf);
let inner_x = area.x + 2;
let inner_w = area.width.saturating_sub(4);
// Top border + one row of vertical padding.
let mut y = area.y + 2;
if logo_lines > 0 {
let logo_area = ratatui::layout::Rect {
x: area.x + 1,
y,
width: area.width.saturating_sub(2),
height: logo_lines,
};
minimal_api::render_compact_logo(logo_area, buf, &Theme::current());
y += logo_lines + 1;
}
for line in &info {
buf.set_line(inner_x, y, line, inner_w);
y += 1;
}
});
if inserted.is_err() {
// Terminal write failed — keep the flag pending so the card retries on
// the next frame instead of being dropped forever.
return;
}
minimal_api::set_minimal_welcome_pending(app, false);
// Trailing gap, matching every committed block, so the first conversation
// block is separated from the card.
super::commit::insert_gap(terminal);
}