From 3069151e7f63ad0ba2bcb1bb47edf451e5ccd39d Mon Sep 17 00:00:00 2001 From: DanS Date: Sat, 10 Oct 2026 09:50:03 -0500 Subject: [PATCH] fix(chat): bound the selection copy range to the message body The copy button of a text selection in a chat bubble copied body.substr(start, len) with byte offsets kept from earlier frames. Those offsets can outlive a thread change (an older message inserted above shifts the message indices, so the selection can land on a shorter body), and a start past the end made substr throw std::out_of_range out of the chat render; the main loop catches it, but the rest of that frame's UI is dropped. The copy now runs only when the start is not past the end of the body. substr already clamps the length, so the copied text is the same in every case that did not throw before. Co-Authored-By: Claude Opus 5.5 --- src/ui/windows/chat_tab.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/ui/windows/chat_tab.cpp b/src/ui/windows/chat_tab.cpp index b04ae8b..7e26819 100644 --- a/src/ui/windows/chat_tab.cpp +++ b/src/ui/windows/chat_tab.cpp @@ -1653,7 +1653,10 @@ void RenderChatTab(App* app) if (cbhov) { ImGui::SetMouseCursor(ImGuiMouseCursor_Hand); if (ImGui::IsMouseClicked(ImGuiMouseButton_Left)) { - ImGui::SetClipboardText(m.body.substr(a, b2 - a).c_str()); + // Offsets can outlive a thread change (an older message inserted above shifts + // indices): never let substr throw std::out_of_range from the render loop. + if (a >= 0 && static_cast(a) <= m.body.size()) + ImGui::SetClipboardText(m.body.substr(a, b2 - a).c_str()); anyMsgClicked = true; } }