From 40a7f7903cd79d21f70ec58f7a635991fb7c779a Mon Sep 17 00:00:00 2001 From: unRekable Date: Sat, 17 Oct 2026 20:03:46 +0200 Subject: [PATCH 1/2] Land a search hit as a jump: the hit flashes and the pill points back to present. --- crates/tinyclient/src/gui/conversation.rs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/crates/tinyclient/src/gui/conversation.rs b/crates/tinyclient/src/gui/conversation.rs index fecf7e2..a1f1070 100644 --- a/crates/tinyclient/src/gui/conversation.rs +++ b/crates/tinyclient/src/gui/conversation.rs @@ -395,7 +395,8 @@ impl Pane { root: Option<(Rc, u32)>, cx: &mut Context, ) { - let around = self.landing.take().or(self.around.take()); + let landing = self.landing.take(); + let around = self.around.take(); // Where the one being left was, for coming back to it. if let Some((left, place)) = self.key().cloned().zip(self.place()) { self.places.put(left, place); @@ -450,13 +451,17 @@ impl Pane { self.pending_at.clear(); self.relay(true, ListOffset::default()); self.ask_reactions(cx); - // M2: a conversation opens at its newest message rather than the first unread. + // M2: a conversation opens at its newest message rather than the first unread. A search + // hit's landing is the user's own ask: it lands as a jump, flashed, with the pill. let around = around.filter(|_| !super::app::prefs::choices().newest); match back { Some(place) => self.load(Page::Around { id: place.id.clone() }, Purpose::Restore(place.id, place.offset), cx), - None => match around { - Some(id) => self.load(Page::Around { id }, Purpose::Open, cx), - None => self.load(Page::Newest, Purpose::Open, cx), + None => match landing { + Some(id) => self.load(Page::Around { id: id.clone() }, Purpose::Jump(id), cx), + None => match around { + Some(id) => self.load(Page::Around { id }, Purpose::Open, cx), + None => self.load(Page::Newest, Purpose::Open, cx), + }, }, } cx.notify(); From f74c0d6d65df3fc06e430640d2055976f17cd0ce Mon Sep 17 00:00:00 2001 From: unRekable Date: Sat, 17 Oct 2026 20:28:08 +0200 Subject: [PATCH 2/2] Let a search hit's landing win over a saved place, and fall back to newest when its page fails. --- crates/tinyclient/src/gui/conversation.rs | 19 +++++++++++++------ .../tinyclient/src/gui/conversation/side.rs | 11 +++++------ 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/crates/tinyclient/src/gui/conversation.rs b/crates/tinyclient/src/gui/conversation.rs index a1f1070..44b1696 100644 --- a/crates/tinyclient/src/gui/conversation.rs +++ b/crates/tinyclient/src/gui/conversation.rs @@ -451,13 +451,14 @@ impl Pane { self.pending_at.clear(); self.relay(true, ListOffset::default()); self.ask_reactions(cx); - // M2: a conversation opens at its newest message rather than the first unread. A search - // hit's landing is the user's own ask: it lands as a jump, flashed, with the pill. + // M2: a conversation opens at its newest message rather than the first unread. The + // search hit is the user's own ask: it wins over where this conversation was left, and + // lands as a jump, flashed, with the pill. let around = around.filter(|_| !super::app::prefs::choices().newest); - match back { - Some(place) => self.load(Page::Around { id: place.id.clone() }, Purpose::Restore(place.id, place.offset), cx), - None => match landing { - Some(id) => self.load(Page::Around { id: id.clone() }, Purpose::Jump(id), cx), + match landing { + Some(id) => self.load(Page::Around { id: id.clone() }, Purpose::Jump(id), cx), + None => match back { + Some(place) => self.load(Page::Around { id: place.id.clone() }, Purpose::Restore(place.id, place.offset), cx), None => match around { Some(id) => self.load(Page::Around { id }, Purpose::Open, cx), None => self.load(Page::Newest, Purpose::Open, cx), @@ -545,6 +546,12 @@ impl Pane { self.load(Page::Newest, Purpose::Open, cx); return; } + if matches!(purpose, Purpose::Jump(_)) && self.window.messages.is_empty() { + // The message a search hit asked for is gone from the history, and nothing + // is held beside it: open as if for the first time. + self.load(Page::Newest, Purpose::Open, cx); + return; + } if matches!(purpose, Purpose::Older | Purpose::Open) { self.older_failed = Some(match other { Ok(Response::Error { detail, .. }) => detail, diff --git a/crates/tinyclient/src/gui/conversation/side.rs b/crates/tinyclient/src/gui/conversation/side.rs index 1cfed63..d0af73c 100644 --- a/crates/tinyclient/src/gui/conversation/side.rs +++ b/crates/tinyclient/src/gui/conversation/side.rs @@ -384,15 +384,14 @@ impl Shell { /// jumps to it. pub(in crate::gui) fn open_search_hit(&mut self, hit: &MessageHit, cx: &mut Context) { let key = hit.conversation.clone(); + // The pane may never have been built: nothing may have opened yet. It is made here, so + // the landing reaches it before the conversation is told to open. + let pane = self.pane(cx); if self.open.as_ref() == Some(&key) { - if let Some(pane) = self.conversation_state.pane.get() { - pane.update(cx, |pane, cx| pane.jump_to(&hit.message_id, cx)); - } + pane.update(cx, |pane, cx| pane.jump_to(&hit.message_id, cx)); return; } - if let Some(pane) = self.conversation_state.pane.get() { - pane.update(cx, |pane, _| pane.land_next(hit.message_id.clone())); - } + pane.update(cx, |pane, _| pane.land_next(hit.message_id.clone())); let title = if hit.title.is_empty() { hit.conversation.id.clone() } else { hit.title.clone() }; self.select(key, &title, cx); }