diff --git a/src/ai/insight_chat.rs b/src/ai/insight_chat.rs index c51befd..1c20b45 100644 --- a/src/ai/insight_chat.rs +++ b/src/ai/insight_chat.rs @@ -1071,8 +1071,13 @@ fn is_rendered(m: &ChatMessage) -> bool { /// Given a rendered index to start discarding from, find the raw index at /// which to truncate. The cut position is the raw length after all prior /// rendered messages — which also strips any tool-call scaffolding that -/// immediately precedes the discarded rendered message. Returns `None` if -/// `discard_from_rendered_index` is past the end of the rendered view. +/// immediately precedes the discarded rendered message. +/// +/// Discarding *at* the end (`discard == rendered_count`) is a no-op success: +/// returns `Some(messages.len())`. The mobile client hits this when +/// regenerating after a failed turn — its optimistic user bubble lives at +/// the index just past the server's persisted history. Strictly past the end +/// (`discard > rendered_count`) returns `None`. pub(crate) fn find_raw_cut( messages: &[ChatMessage], discard_from_rendered_index: usize, @@ -1089,10 +1094,8 @@ pub(crate) fn find_raw_cut( rendered_count += 1; last_kept_raw_end = i + 1; } - if rendered_count == discard_from_rendered_index { - // Discarding past the last rendered message is a no-op, but we - // surface it as "nothing to cut" rather than silent success. - return None; + if discard_from_rendered_index == rendered_count { + return Some(messages.len()); } None } @@ -1361,4 +1364,18 @@ mod tests { let msgs = vec![ChatMessage::user("q1"), assistant_text("a1")]; assert!(find_raw_cut(&msgs, 5).is_none()); } + + #[test] + fn rewind_at_end_is_noop_success() { + // Mobile client retries after a failed turn that never persisted — + // its optimistic user bubble's index equals the server's rendered + // count. Should resolve to "no cut" rather than an out-of-range error. + let msgs = vec![ + ChatMessage::system("s"), + ChatMessage::user("q1"), + assistant_text("a1"), + ]; + let cut = find_raw_cut(&msgs, 2).expect("boundary cut should succeed"); + assert_eq!(cut, msgs.len()); + } }