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:
@@ -90,6 +90,7 @@ OWLEN uses a modal, vim-inspired interface. Press `F1` (available from any mode)
|
||||
- **Normal Mode**: Navigate with `h/j/k/l`, `w/b`, `gg/G`.
|
||||
- **Editing Mode**: Enter with `i` or `a`. Send messages with `Enter`.
|
||||
- **Command Mode**: Enter with `:`. Access commands like `:quit`, `:save`, `:theme`.
|
||||
- **Tutorial Command**: Type `:tutorial` any time for a quick summary of the most important keybindings.
|
||||
|
||||
## Documentation
|
||||
|
||||
|
||||
@@ -625,6 +625,8 @@ pub struct UiSettings {
|
||||
pub show_role_labels: bool,
|
||||
#[serde(default = "UiSettings::default_wrap_column")]
|
||||
pub wrap_column: u16,
|
||||
#[serde(default = "UiSettings::default_show_onboarding")]
|
||||
pub show_onboarding: bool,
|
||||
}
|
||||
|
||||
impl UiSettings {
|
||||
@@ -647,6 +649,10 @@ impl UiSettings {
|
||||
fn default_wrap_column() -> u16 {
|
||||
100
|
||||
}
|
||||
|
||||
const fn default_show_onboarding() -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for UiSettings {
|
||||
@@ -657,6 +663,7 @@ impl Default for UiSettings {
|
||||
max_history_lines: Self::default_max_history_lines(),
|
||||
show_role_labels: Self::default_show_role_labels(),
|
||||
wrap_column: Self::default_wrap_column(),
|
||||
show_onboarding: Self::default_show_onboarding(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
9
docs/migrations/README.md
Normal file
9
docs/migrations/README.md
Normal file
@@ -0,0 +1,9 @@
|
||||
## Migration Notes
|
||||
|
||||
Owlen is still in alpha, so configuration and storage formats may change between releases. This directory collects short guides that explain how to update a local environment when breaking changes land.
|
||||
|
||||
### Schema 1.1.0 (October 2025)
|
||||
|
||||
Owlen `config.toml` files now carry a `schema_version`. On startup the loader upgrades any existing file and warns when deprecated keys are present. No manual changes are required, but if you track the file in version control you may notice `schema_version = "1.1.0"` added near the top.
|
||||
|
||||
If you previously set `agent.max_tool_calls`, replace it with `agent.max_iterations`. The former is now ignored.
|
||||
Reference in New Issue
Block a user