diff --git a/crates/codegen/kigi-tui/src/app/actions.rs b/crates/codegen/kigi-tui/src/app/actions.rs index 63a8aaf..ddea2e8 100644 --- a/crates/codegen/kigi-tui/src/app/actions.rs +++ b/crates/codegen/kigi-tui/src/app/actions.rs @@ -623,10 +623,6 @@ pub enum Action { /// to config.toml). `/plan ` uses `EnterPlanMode` instead /// because it also starts a turn. SetPlanMode(PlanModeKind), - /// Enter feedback mode (visual prompt change, not a send). - EnterFeedbackMode, - /// Send feedback text collected in feedback mode. - SendFeedback(String), /// Enter remember mode (visual prompt change, not a send). EnterRememberMode, /// Send a remember note from # mode. Routes through LLM rewrite when a @@ -1717,12 +1713,6 @@ pub enum Effect { FetchBundleStatus, /// Fetch a bundled entry's raw content via `kigi/bundle/entry/get`. FetchCatalogEntry { kind: String, name: String }, - /// Send feedback about the current session (fire-and-forget POST). - SendFeedback { - agent_id: AgentId, - session_id: acp::SessionId, - feedback_text: String, - }, /// Save a remember note to global MEMORY.md (async file write). SaveMemoryNote { agent_id: AgentId, @@ -2267,15 +2257,6 @@ pub enum TaskResult { agent_id: AgentId, error: String, }, - /// Feedback submitted successfully (fire-and-forget). - FeedbackComplete { - agent_id: AgentId, - }, - /// Feedback submission failed. - FeedbackFailed { - agent_id: AgentId, - error: String, - }, /// Memory note saved to global MEMORY.md. MemoryNoteSaved { agent_id: AgentId, diff --git a/crates/codegen/kigi-tui/src/app/agent_view/mod.rs b/crates/codegen/kigi-tui/src/app/agent_view/mod.rs index 6cd08de..ec35b62 100644 --- a/crates/codegen/kigi-tui/src/app/agent_view/mod.rs +++ b/crates/codegen/kigi-tui/src/app/agent_view/mod.rs @@ -287,8 +287,6 @@ pub enum PromptInputMode { Normal, /// Bash mode (`!` prefix): Enter sends `Action::SendBashCommand`. Bash, - /// Feedback mode (`~` prefix, teal accent): Enter sends `Action::SendFeedback`. - Feedback, /// Remember mode (`#` prefix, green accent): Enter sends `Action::SendRememberNote`. Remember, } @@ -297,7 +295,6 @@ impl PromptInputMode { match self { PromptInputMode::Normal => None, PromptInputMode::Bash => Some(theme.command), - PromptInputMode::Feedback => Some(theme.accent_feedback), PromptInputMode::Remember => Some(theme.accent_remember), } } @@ -305,14 +302,12 @@ impl PromptInputMode { match self { PromptInputMode::Normal => None, PromptInputMode::Bash => Some(("! ", theme.command)), - PromptInputMode::Feedback => Some(("~ ", theme.accent_feedback)), PromptInputMode::Remember => Some(("# ", theme.accent_remember)), } } pub fn placeholder_override(self, multiline: bool) -> Option<&'static str> { match self { PromptInputMode::Normal | PromptInputMode::Bash => None, - PromptInputMode::Feedback => Some("Type your feedback..."), PromptInputMode::Remember => { if multiline { Some("Save a memory note... (Enter for newline, Shift+Enter to save)") @@ -326,7 +321,6 @@ impl PromptInputMode { match self { PromptInputMode::Normal => None, PromptInputMode::Bash => Some("Run shell command"), - PromptInputMode::Feedback => Some("Send feedback"), PromptInputMode::Remember => Some("Save memory note"), } } @@ -334,7 +328,6 @@ impl PromptInputMode { match self { PromptInputMode::Normal => Action::SendPrompt(text), PromptInputMode::Bash => Action::SendBashCommand(text), - PromptInputMode::Feedback => Action::SendFeedback(text), PromptInputMode::Remember => Action::SendRememberNote(text), } } @@ -351,7 +344,6 @@ impl PromptInputMode { || ctrl_u || ctrl_c } - PromptInputMode::Feedback => key.code == KeyCode::Backspace || key.code == KeyCode::Esc, } } } @@ -3078,10 +3070,6 @@ mod prompt_input_mode_tests { PromptInputMode::Bash.accent_color(&theme), Some(theme.command) ); - assert_eq!( - PromptInputMode::Feedback.accent_color(&theme), - Some(theme.accent_feedback) - ); assert_eq!( PromptInputMode::Remember.accent_color(&theme), Some(theme.accent_remember) @@ -3095,10 +3083,6 @@ mod prompt_input_mode_tests { PromptInputMode::Bash.prefix_override(&theme), Some(("! ", theme.command)) ); - assert_eq!( - PromptInputMode::Feedback.prefix_override(&theme), - Some(("~ ", theme.accent_feedback)) - ); assert_eq!( PromptInputMode::Remember.prefix_override(&theme), Some(("# ", theme.accent_remember)) @@ -3110,14 +3094,6 @@ mod prompt_input_mode_tests { assert_eq!(PromptInputMode::Normal.placeholder_override(true), None); assert_eq!(PromptInputMode::Bash.placeholder_override(false), None); assert_eq!(PromptInputMode::Bash.placeholder_override(true), None); - assert_eq!( - PromptInputMode::Feedback.placeholder_override(false), - Some("Type your feedback...") - ); - assert_eq!( - PromptInputMode::Feedback.placeholder_override(true), - Some("Type your feedback...") - ); assert_eq!( PromptInputMode::Remember.placeholder_override(false), Some("Save a memory note... (Shift+Enter for multiline)") @@ -3134,10 +3110,6 @@ mod prompt_input_mode_tests { PromptInputMode::Bash.prompt_info_override(), Some("Run shell command") ); - assert_eq!( - PromptInputMode::Feedback.prompt_info_override(), - Some("Send feedback") - ); assert_eq!( PromptInputMode::Remember.prompt_info_override(), Some("Save memory note") @@ -3151,9 +3123,6 @@ mod prompt_input_mode_tests { let t2 = "ls -l".to_string(); assert!(matches!(PromptInputMode::Bash.send_action(t2.clone()), Action::SendBashCommand(t) if t == t2)); - let t3 = "this is feedback".to_string(); - assert!(matches!(PromptInputMode::Feedback.send_action(t3.clone()), - Action::SendFeedback(t) if t == t3)); let t4 = "remember this".to_string(); assert!(matches!(PromptInputMode::Remember.send_action(t4.clone()), Action::SendRememberNote(t) if t == t4)); @@ -3182,15 +3151,4 @@ mod prompt_input_mode_tests { assert!(!mode.is_exit_key(&KeyEvent::new(KeyCode::Char('a'), KeyModifiers::NONE))); } } - #[test] - fn is_exit_key_feedback_uses_stricter_set() { - let mode = PromptInputMode::Feedback; - assert!(mode.is_exit_key(&KeyEvent::new(KeyCode::Backspace, KeyModifiers::NONE))); - assert!(mode.is_exit_key(&KeyEvent::new(KeyCode::Esc, KeyModifiers::NONE))); - assert!(!mode.is_exit_key(&KeyEvent::new(KeyCode::Char('w'), KeyModifiers::CONTROL))); - assert!(!mode.is_exit_key(&KeyEvent::new(KeyCode::Char('u'), KeyModifiers::CONTROL))); - assert!(!mode.is_exit_key(&KeyEvent::new(KeyCode::Char('c'), KeyModifiers::CONTROL))); - assert!(!mode.is_exit_key(&KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE))); - assert!(!mode.is_exit_key(&KeyEvent::new(KeyCode::Char('?'), KeyModifiers::NONE))); - } } diff --git a/crates/codegen/kigi-tui/src/app/agent_view/prompt.rs b/crates/codegen/kigi-tui/src/app/agent_view/prompt.rs index 51c94b3..77cfa02 100644 --- a/crates/codegen/kigi-tui/src/app/agent_view/prompt.rs +++ b/crates/codegen/kigi-tui/src/app/agent_view/prompt.rs @@ -868,10 +868,9 @@ impl AgentView { { self.prompt.history_search.deactivate(); // Detect `! ` prefix to restore bash mode. Refined: only reset to Normal - // if currently in Bash (preserve Feedback/Remember if active). The ! prefix - // restore only applies when not in Feedback/Remember. - if self.prompt_input_mode != PromptInputMode::Feedback - && self.prompt_input_mode != PromptInputMode::Remember + // if currently in Bash (preserve Remember if active). The ! prefix + // restore only applies when not in Remember. + if self.prompt_input_mode != PromptInputMode::Remember && let Some(cmd) = text.strip_prefix("! ") { self.prompt_input_mode = PromptInputMode::Bash; diff --git a/crates/codegen/kigi-tui/src/app/dispatch/notes.rs b/crates/codegen/kigi-tui/src/app/dispatch/notes.rs index 33678b3..b6c3e22 100644 --- a/crates/codegen/kigi-tui/src/app/dispatch/notes.rs +++ b/crates/codegen/kigi-tui/src/app/dispatch/notes.rs @@ -1,4 +1,4 @@ -//! Feedback, remember-note, btw, and recap dispatchers. +//! Remember-note, btw, and recap dispatchers. use super::ctx::with_active_agent; use crate::app::actions::Effect; @@ -18,16 +18,6 @@ fn next_rewrite_nonce() -> u64 { REWRITE_NONCE.fetch_add(1, Ordering::Relaxed) } -/// Enter feedback mode: visual change to prompt bar (teal accent, pencil prefix). -/// No side effects — the user types feedback text and presses Enter to send. -pub(super) fn dispatch_enter_feedback_mode(app: &mut AppView) -> Vec { - with_active_agent(app, |agent| { - agent.prompt_input_mode = PromptInputMode::Feedback; - agent.prompt.set_text(""); - }); - vec![] -} - /// Enter remember mode: visual change to prompt bar (remember accent, `#` prefix). /// No side effects — the user types a memory note and presses Enter to send. pub(super) fn dispatch_enter_remember_mode(app: &mut AppView) -> Vec { @@ -38,47 +28,6 @@ pub(super) fn dispatch_enter_remember_mode(app: &mut AppView) -> Vec { vec![] } -/// Send feedback text to the server. Shows a thank-you message immediately -/// and fires the HTTP POST as a background effect. -pub(super) fn dispatch_send_feedback(app: &mut AppView, text: String) -> Vec { - let ActiveView::Agent(id) = app.active_view else { - return vec![]; - }; - let Some(agent) = app.agents.get_mut(&id) else { - return vec![]; - }; - - agent.prompt_input_mode = PromptInputMode::Normal; - agent.prompt.set_text(""); - // Submitting feedback retires any edit-contextual ephemeral tip. - agent.ephemeral_tip.clear_on_submit(); - - let trimmed = text.trim().to_string(); - if trimmed.is_empty() { - agent.scrollback.push_block(RenderBlock::system( - "Please provide feedback text.".to_string(), - )); - return vec![]; - } - - let Some(session_id) = agent.session.session_id.clone() else { - agent - .scrollback - .push_block(RenderBlock::system("No active session.".to_string())); - return vec![]; - }; - - agent.scrollback.push_block(RenderBlock::system( - "Thanks for the feedback! The Kigi team is on it.".to_string(), - )); - - vec![Effect::SendFeedback { - agent_id: id, - session_id, - feedback_text: trimmed, - }] -} - /// Send a raw remember note for LLM-powered rewriting via `kigi/memory/rewrite`. /// Clears remember mode and prompts the LLM to reformat the note with session /// context. Falls back to direct `SaveMemoryNote` when no session is available. diff --git a/crates/codegen/kigi-tui/src/app/dispatch/router.rs b/crates/codegen/kigi-tui/src/app/dispatch/router.rs index 2f50c90..405f318 100644 --- a/crates/codegen/kigi-tui/src/app/dispatch/router.rs +++ b/crates/codegen/kigi-tui/src/app/dispatch/router.rs @@ -33,8 +33,7 @@ use super::modes::{ set_permission_mode, set_plan_mode, set_yolo_mode, }; use super::notes::{ - dispatch_enter_feedback_mode, dispatch_enter_remember_mode, - dispatch_save_remember_note_from_modal, dispatch_send_btw, dispatch_send_feedback, + dispatch_enter_remember_mode, dispatch_save_remember_note_from_modal, dispatch_send_btw, dispatch_send_recap, dispatch_send_remember_note, }; use super::permissions::{ @@ -784,8 +783,6 @@ pub(crate) fn dispatch(action: Action, app: &mut AppView) -> Vec { Action::ShowPlan => dispatch_show_plan(app), Action::EnterPlanMode { description } => dispatch_enter_plan_mode(app, description), Action::SetPlanMode(kind) => set_plan_mode(app, kind), - Action::EnterFeedbackMode => dispatch_enter_feedback_mode(app), - Action::SendFeedback(text) => dispatch_send_feedback(app, text), Action::EnterRememberMode => dispatch_enter_remember_mode(app), Action::SendRememberNote(text) => dispatch_send_remember_note(app, text), Action::SaveRememberNoteFromModal => dispatch_save_remember_note_from_modal(app), diff --git a/crates/codegen/kigi-tui/src/app/dispatch/task_result.rs b/crates/codegen/kigi-tui/src/app/dispatch/task_result.rs index 0a6cd05..aac40d0 100644 --- a/crates/codegen/kigi-tui/src/app/dispatch/task_result.rs +++ b/crates/codegen/kigi-tui/src/app/dispatch/task_result.rs @@ -663,17 +663,6 @@ pub(super) fn dispatch_task_result(result: TaskResult, app: &mut AppView) -> Vec } vec![] } - TaskResult::FeedbackComplete { .. } => vec![], - TaskResult::FeedbackFailed { agent_id, error } => { - if let Some(agent) = app.agents.get_mut(&agent_id) { - agent - .scrollback - .push_block(crate::scrollback::block::RenderBlock::system(format!( - "Couldn't send feedback: {error}" - ))); - } - vec![] - } TaskResult::MemoryNoteSaved { agent_id, result } => { handle_memory_note_saved(app, agent_id, result) } diff --git a/crates/codegen/kigi-tui/src/app/dispatch/tests/router.rs b/crates/codegen/kigi-tui/src/app/dispatch/tests/router.rs index d8df697..919644e 100644 --- a/crates/codegen/kigi-tui/src/app/dispatch/tests/router.rs +++ b/crates/codegen/kigi-tui/src/app/dispatch/tests/router.rs @@ -32,23 +32,6 @@ fn seed_foreign_resume_hint( }), ); } -/// Sending feedback is a submit: it retires the active ephemeral tip. -#[test] -fn send_feedback_clears_active_ephemeral_tip() { - let mut app = test_app_with_agent(); - let id = AgentId(0); - let agent = app.agents.get_mut(&id).unwrap(); - let _ = agent.ephemeral_tip.show( - crate::tips::EphemeralTip::new("t", ratatui::text::Line::from("hint")), - &mut std::collections::HashMap::new(), - ); - assert!(agent.ephemeral_tip.is_active()); - let _ = dispatch(Action::SendFeedback("it broke".into()), &mut app); - assert!( - !app.agents.get(&id).unwrap().ephemeral_tip.is_active(), - "feedback submit must clear the tip" - ); -} /// Sending a remember note is a submit: it retires the active ephemeral tip. #[test] fn send_remember_note_clears_active_ephemeral_tip() { diff --git a/crates/codegen/kigi-tui/src/app/effects/mod.rs b/crates/codegen/kigi-tui/src/app/effects/mod.rs index a8b3c34..e496553 100644 --- a/crates/codegen/kigi-tui/src/app/effects/mod.rs +++ b/crates/codegen/kigi-tui/src/app/effects/mod.rs @@ -2599,61 +2599,6 @@ pub(crate) fn execute( } }); } - Effect::SendFeedback { agent_id, session_id, feedback_text } => { - use kigi_shell::session::ClientType; - use kigi_shell::session::acp_types::ClientFeedbackInput; - let terminal_info = Some( - crate::terminal::terminal_context().feedback_info(), - ); - let tx = acp_tx.clone(); - tasks - .spawn(async move { - let input = ClientFeedbackInput { - session_id: session_id.0.to_string(), - client_type: ClientType::Tui, - rating_type: None, - rating_value: None, - feedback_text: Some(feedback_text), - feedback_categories: vec![], - context_type: None, - turn_number: None, - request_id: None, - client_version: Some(kigi_version::VERSION.to_string()), - metadata: None, - terminal_info, - }; - let raw_params = match serde_json::value::to_raw_value(&input) { - Ok(v) => v, - Err(e) => { - return TaskResult::FeedbackFailed { - agent_id, - error: sanitize_user_error( - &format!("couldn't serialize feedback: {e}"), - ), - }; - } - }; - let request = acp::ExtRequest::new( - "kigi/feedback", - raw_params.into(), - ); - match acp_send(request, &tx).await { - Ok(_) => { - TaskResult::FeedbackComplete { - agent_id, - } - } - Err(e) => { - TaskResult::FeedbackFailed { - agent_id, - error: sanitize_user_error( - &format!("couldn't send feedback: {e}"), - ), - } - } - } - }); - } Effect::RewriteMemoryNote { agent_id, session_id, diff --git a/crates/codegen/kigi-tui/src/app/mouse.rs b/crates/codegen/kigi-tui/src/app/mouse.rs index dcaa095..864d253 100644 --- a/crates/codegen/kigi-tui/src/app/mouse.rs +++ b/crates/codegen/kigi-tui/src/app/mouse.rs @@ -169,8 +169,7 @@ impl AgentView { .map(str::to_owned) { self.prompt.history_search.deactivate(); - if self.prompt_input_mode != PromptInputMode::Feedback - && self.prompt_input_mode != PromptInputMode::Remember + if self.prompt_input_mode != PromptInputMode::Remember && let Some(cmd) = text.strip_prefix("! ") { self.prompt_input_mode = PromptInputMode::Bash; diff --git a/crates/codegen/kigi-tui/src/slash/commands/feedback.rs b/crates/codegen/kigi-tui/src/slash/commands/feedback.rs index eca6bcc..939bd0d 100644 --- a/crates/codegen/kigi-tui/src/slash/commands/feedback.rs +++ b/crates/codegen/kigi-tui/src/slash/commands/feedback.rs @@ -1,9 +1,14 @@ -//! `/feedback` -- send session feedback. +//! `/feedback` -- open the Kigi GitHub issues page. use crate::app::actions::Action; use crate::slash::command::{CommandExecCtx, CommandResult, SlashCommand}; -/// Send session feedback inline or enter feedback mode. +/// Where feedback goes: the project's own issue tracker. Kigi is a community +/// build, so its feedback belongs on its GitHub repo — mirroring the official +/// kimi-cli, whose `/feedback` opens its repo's issues page. +pub const FEEDBACK_ISSUES_URL: &str = "https://github.com/ZacharyZhang-NY/Kigi-CLI/issues"; + +/// Open the Kigi issue tracker in the browser. pub struct FeedbackCommand; impl SlashCommand for FeedbackCommand { @@ -12,27 +17,75 @@ impl SlashCommand for FeedbackCommand { } fn description(&self) -> &str { - "Send feedback about the current session" + "Report feedback on the Kigi GitHub issues page" } fn usage(&self) -> &str { - "/feedback [text]" + "/feedback" } - fn takes_args(&self) -> bool { - true + fn run(&self, _ctx: &mut CommandExecCtx, _args: &str) -> CommandResult { + CommandResult::Action(Action::OpenUrl(FEEDBACK_ISSUES_URL.into())) } +} - fn arg_placeholder(&self) -> Option<&str> { - Some("[feedback text]") +#[cfg(test)] +mod tests { + use super::*; + use crate::acp::model_state::ModelState; + + static DEFAULT_BUNDLE_STATE: crate::app::bundle::BundleState = + crate::app::bundle::BundleState { + has_cache: false, + version: String::new(), + personas: Vec::new(), + roles: Vec::new(), + agents: Vec::new(), + skills: Vec::new(), + persona_details: Vec::new(), + role_details: Vec::new(), + }; + + fn make_ctx<'a>(models: &'a ModelState) -> CommandExecCtx<'a> { + CommandExecCtx { + models, + session_id: None, + bundle_state: &DEFAULT_BUNDLE_STATE, + screen_mode: crate::app::ScreenMode::Inline, + pager_state: crate::settings::PagerLocalSnapshot { + multiline_mode: false, + yolo_mode: false, + ..crate::settings::PagerLocalSnapshot::default() + }, + } } - fn run(&self, _ctx: &mut CommandExecCtx, args: &str) -> CommandResult { - let trimmed = args.trim(); - if trimmed.is_empty() { - CommandResult::Action(Action::EnterFeedbackMode) - } else { - CommandResult::Action(Action::SendFeedback(trimmed.to_string())) + #[test] + fn feedback_opens_github_issues() { + let models = ModelState::default(); + let mut ctx = make_ctx(&models); + match FeedbackCommand.run(&mut ctx, "") { + CommandResult::Action(Action::OpenUrl(url)) => { + assert_eq!(url, FEEDBACK_ISSUES_URL); + } + other => panic!("expected OpenUrl, got {other:?}"), } + } + + #[test] + fn feedback_ignores_stray_args() { + let models = ModelState::default(); + let mut ctx = make_ctx(&models); + assert!(matches!( + FeedbackCommand.run(&mut ctx, "some typed text"), + CommandResult::Action(Action::OpenUrl(_)) + )); + } + + #[test] + fn feedback_metadata() { + let cmd = FeedbackCommand; + assert_eq!(cmd.name(), "feedback"); + assert!(!cmd.takes_args()); } } diff --git a/crates/codegen/kigi-tui/src/views/prompt_widget/mod.rs b/crates/codegen/kigi-tui/src/views/prompt_widget/mod.rs index cee63e3..dae8dd0 100644 --- a/crates/codegen/kigi-tui/src/views/prompt_widget/mod.rs +++ b/crates/codegen/kigi-tui/src/views/prompt_widget/mod.rs @@ -174,7 +174,7 @@ pub struct PromptStyle { pub prefix_override: Option<(&'static str, ratatui::style::Color)>, /// Override the placeholder text shown when the textarea is empty. /// When `Some(text)`, uses this instead of the default `"Build anything"`. - /// Used for feedback mode (`"Type your feedback..."`). + /// Used for remember mode (`"Save a memory note..."`). pub placeholder_override: Option<&'static str>, /// Compact mode (currently unused for info_block sizing). pub compact: bool,