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.
This commit is contained in:
ayn2op 2026-09-27 19:19:07 -04:00
parent 25bc35212e
commit f45dfd58ad
2 changed files with 8 additions and 9 deletions

View file

@ -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}
}
}

View file

@ -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")
}