diff --git a/nobilis b/nobilis index 70911bb..b5cc7fd 160000 --- a/nobilis +++ b/nobilis @@ -1 +1 @@ -Subproject commit 70911bbd03a7e509233dcbb0a23fd3b0a3874221 +Subproject commit b5cc7fd50afa789603bb0ebe174235b84eb151e3 diff --git a/src/renderer/src/components/ConversationTools.tsx b/src/renderer/src/components/ConversationTools.tsx index 307c31c..b82b48a 100644 --- a/src/renderer/src/components/ConversationTools.tsx +++ b/src/renderer/src/components/ConversationTools.tsx @@ -306,8 +306,16 @@ function PinnedMessages({ buffer }: { buffer: BufferEntry }): JSX.Element | null type="button" className="history-row" onClick={() => { - store.setJumpTarget(message.id) setOpen(false) + // Through the same path a search result takes, rather than + // straight to the jump: a pin is usually the oldest thing in + // the conversation, so it is the least likely message to + // already be on screen - and it used to be the one click that + // did nothing at all. + void store.jumpToMessage(buffer.id, message.id).then((there) => { + if (there) store.setJumpTarget(message.id) + else store.toast('info', 'That message could not be reached') + }) }} > diff --git a/src/renderer/src/state/store.ts b/src/renderer/src/state/store.ts index cc85bfa..997e61b 100644 --- a/src/renderer/src/state/store.ts +++ b/src/renderer/src/state/store.ts @@ -2324,12 +2324,40 @@ export class ChatStore { const before = (this.state.messagesByBuffer[bufferId] || []).length await this.loadMoreHistory(bufferId) if (loaded()) return true - // Nothing came back, so there is nothing older to find it in. - if ((this.state.messagesByBuffer[bufferId] || []).length === before) return false + // Nothing came back: either the store has been read to its beginning, or + // the message is far enough back that paging to it is not a wait but a + // refusal. Both are the moment to ask the service for that one part of + // the conversation instead - a pin is routinely years older than + // anything kept here, and it was still worth listing. + if ((this.state.messagesByBuffer[bufferId] || []).length === before) { + return await this.fetchAround(bufferId, messageId) + } } return loaded() } + /** + * Asks the service for the part of the conversation one message is in. + * + * Then pages once more rather than rendering the fetched window directly: + * the daemon has put those messages in the same store everything else is + * read from, so the ordinary load-more path picks them up - and because + * nothing exists between them and what is already on screen, one page + * reaches them however old they are. + */ + private async fetchAround(bufferId: string, messageId: string): Promise { + try { + await window.moho.rpc<{ ts: number }>('loadMessageContext', { bufferId, messageId }) + } catch (e) { + // Said rather than swallowed: "nothing happened when I clicked it" is + // the complaint this whole path exists to answer. + this.toast('error', `Couldn't reach that message: ${(e as Error).message}`) + return false + } + await this.loadMoreHistory(bufferId) + return (this.state.messagesByBuffer[bufferId] || []).some((m) => m.id === messageId) + } + async loadMoreHistory(bufferId: string): Promise { if (this.state.loadingMore[bufferId]) return const list = this.state.messagesByBuffer[bufferId] || []