From f45dfd58adc8f7ebe8d159b1cdee3570f5374d97 Mon Sep 17 00:00:00 2001 From: ayn2op Date: Sun, 27 Sep 2026 19:19:07 -0400 Subject: [PATCH] fix(chat): expire typing indicators by deadline instead of timestamp equality On Windows the coarse clock can return the same time for two addTyper calls, so a stale expiry matched the extended indicator and removed it early. Compare the stored deadline against the current time instead. --- internal/ui/chat/model.go | 10 ++++------ internal/ui/chat/model_test.go | 7 ++++--- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/internal/ui/chat/model.go b/internal/ui/chat/model.go index 0137142..9674947 100644 --- a/internal/ui/chat/model.go +++ b/internal/ui/chat/model.go @@ -257,7 +257,7 @@ func (m *Model) Update(msg tview.Msg) tview.Cmd { case messageslist.Msg: return m.messagesList.Update(msg) case typingExpiredMsg: - if m.typers[msg.userID] == msg.until { + if until, ok := m.typers[msg.userID]; ok && !time.Now().Before(until) { m.removeTyper(msg.userID) } return nil @@ -421,10 +421,9 @@ func (m *Model) View() tview.Element { ) } -// typingExpiredMsg ends a user's typing indicator unless they typed again after it was set to expire at until. +// typingExpiredMsg ends a user's typing indicator unless they typed again and extended it. type typingExpiredMsg struct { userID discord.UserID - until time.Time } func (m *Model) clearTypers() { @@ -434,12 +433,11 @@ func (m *Model) clearTypers() { // addTyper shows userID as typing and returns a command that ends it after the typing duration. func (m *Model) addTyper(userID discord.UserID) tview.Cmd { - until := time.Now().Add(composer.TypingDuration) - m.typers[userID] = until + m.typers[userID] = time.Now().Add(composer.TypingDuration) m.updateFooter() return func() tview.Msg { time.Sleep(composer.TypingDuration) - return typingExpiredMsg{userID, until} + return typingExpiredMsg{userID} } } diff --git a/internal/ui/chat/model_test.go b/internal/ui/chat/model_test.go index 659e458..c7e3b26 100644 --- a/internal/ui/chat/model_test.go +++ b/internal/ui/chat/model_test.go @@ -3,6 +3,7 @@ package chat import ( "path/filepath" "testing" + "time" "github.com/ayn2op/arikawa/v3/discord" "github.com/ayn2op/arikawa/v3/gateway" @@ -41,13 +42,13 @@ func TestModelUpdate(t *testing.T) { t.Run("typing expires unless extended", func(t *testing.T) { m := newTestModel(t) m.addTyper(1) - first := m.typers[1] m.addTyper(1) - m.Update(typingExpiredMsg{userID: 1, until: first}) + m.Update(typingExpiredMsg{userID: 1}) if _, ok := m.typers[1]; !ok { t.Fatal("an extended typing indicator expired early") } - m.Update(typingExpiredMsg{userID: 1, until: m.typers[1]}) + m.typers[1] = time.Now().Add(-time.Second) + m.Update(typingExpiredMsg{userID: 1}) if _, ok := m.typers[1]; ok { t.Fatal("the typing indicator did not expire") }