feat(tui): add onboarding tutorial with :tutorial command and first‑run UI

- Introduce `show_onboarding` UI setting (default true) and persist its state after first launch.
- Show onboarding status line and system status on initial run; fallback to normal status thereafter.
- Implement `show_tutorial` method displaying keybinding tips and system status.
- Register `:tutorial` command in command palette.
- Add migration documentation explaining `schema_version` update and deprecation of `agent.max_tool_calls`.
- Update README with description of the new tutorial command.
This commit is contained in:
2025-10-12 02:32:35 +02:00
parent d0d3079df5
commit 38aba1a6bb
4 changed files with 67 additions and 2 deletions

View File

@@ -20,6 +20,13 @@ use crate::events::Event;
use std::collections::{BTreeSet, HashSet};
use std::sync::Arc;
const ONBOARDING_STATUS_LINE: &str =
"Welcome to Owlen! Press F1 for help or type :tutorial for keybinding tips.";
const ONBOARDING_SYSTEM_STATUS: &str = "Normal ▸ h/j/k/l • Insert ▸ i,a • Visual ▸ v • Command ▸ :";
const TUTORIAL_STATUS: &str = "Tutorial loaded. Review quick tips in the footer.";
const TUTORIAL_SYSTEM_STATUS: &str =
"Normal ▸ h/j/k/l • Insert ▸ i,a • Visual ▸ v • Command ▸ : • Send ▸ Enter";
#[derive(Clone, Debug)]
pub(crate) struct ModelSelectorItem {
kind: ModelSelectorItemKind,
@@ -202,6 +209,7 @@ impl ChatApp {
let config_guard = controller.config_async().await;
let theme_name = config_guard.ui.theme.clone();
let current_provider = config_guard.general.default_provider.clone();
let show_onboarding = config_guard.ui.show_onboarding;
drop(config_guard);
let theme = owlen_core::theme::get_theme(&theme_name).unwrap_or_else(|| {
eprintln!("Warning: Theme '{}' not found, using default", theme_name);
@@ -211,7 +219,11 @@ impl ChatApp {
let app = Self {
controller,
mode: InputMode::Normal,
status: "Normal mode • Press F1 for help".to_string(),
status: if show_onboarding {
ONBOARDING_STATUS_LINE.to_string()
} else {
"Normal mode • Press F1 for help".to_string()
},
error: None,
models: Vec::new(),
available_providers: Vec::new(),
@@ -252,13 +264,27 @@ impl ChatApp {
available_themes: Vec::new(),
selected_theme_index: 0,
pending_consent: None,
system_status: String::new(),
system_status: if show_onboarding {
ONBOARDING_SYSTEM_STATUS.to_string()
} else {
String::new()
},
_execution_budget: 50,
agent_mode: false,
agent_running: false,
operating_mode: owlen_core::mode::Mode::default(),
};
if show_onboarding {
let mut cfg = app.controller.config_mut();
if cfg.ui.show_onboarding {
cfg.ui.show_onboarding = false;
if let Err(err) = config::save_config(&cfg) {
eprintln!("Warning: Failed to persist onboarding preference: {err}");
}
}
}
Ok((app, session_rx))
}
@@ -402,6 +428,24 @@ impl ChatApp {
self.system_status.clear();
}
pub fn show_tutorial(&mut self) {
self.error = None;
self.status = TUTORIAL_STATUS.to_string();
self.system_status = TUTORIAL_SYSTEM_STATUS.to_string();
let tutorial_body = concat!(
"Keybindings overview:\n",
" • Movement: h/j/k/l, gg/G, w/b\n",
" • Insert text: i or a (Esc to exit)\n",
" • Visual select: v (Esc to exit)\n",
" • Command mode: : (press Enter to run, Esc to cancel)\n",
" • Send message: Enter in Insert mode\n",
" • Help overlay: F1 or ?\n"
);
self.controller
.conversation_mut()
.push_system_message(tutorial_body.to_string());
}
pub fn command_buffer(&self) -> &str {
&self.command_buffer
}
@@ -439,6 +483,7 @@ impl ChatApp {
("n", "Alias for new"),
("theme", "Switch theme"),
("themes", "List available themes"),
("tutorial", "Show keybinding tutorial"),
("reload", "Reload configuration and themes"),
("e", "Edit a file"),
("edit", "Alias for edit"),
@@ -1688,6 +1733,9 @@ impl ChatApp {
}
}
}
"tutorial" => {
self.show_tutorial();
}
"themes" => {
// Load all themes and enter browser mode
let themes = owlen_core::theme::load_all_themes();