fix: QoL change unread / read behaviour

This commit is contained in:
2026-07-30 14:08:08 +02:00
parent 033b0bb5be
commit 0d2af19c6d
4 changed files with 95 additions and 38 deletions

82
tui.go
View File

@@ -415,7 +415,7 @@ func (m *App) startReply() {
m.writeMode, m.writeThreadID, m.replyDraft, m.err = writeReply, thread.ID, "", nil
m.restoreReplyDraft(thread.ID)
m.folded[thread.ID] = false
m.focus = threadDetailPane
m.focusThreadDetail()
m.scroll = m.detailMaxScroll()
}
@@ -960,6 +960,9 @@ func (m App) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.folded[thread.ID] = thread.IsResolved && m.foldResolved
break
}
if msg.thread.IsResolved {
m.markThreadRead(msg.threadID)
}
sortReviewThreads(m.details.Threads, m.threadStatusOrder, m.threadWithinStatus)
m.threadIndex = indexThread(m.details.Threads, selected)
m.writeThreadID = ""
@@ -1247,7 +1250,7 @@ func (m App) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
if m.screen == threadScreen {
m.listHidden = !m.listHidden
if m.listHidden {
m.focus = threadDetailPane
m.focusThreadDetail()
} else {
m.focus = threadListPane
}
@@ -1290,7 +1293,7 @@ func (m App) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
return m, nil
}
if m.screen == threadScreen {
m.focus = threadDetailPane
m.focusThreadDetail()
}
case "h":
if m.screen == threadScreen {
@@ -1480,29 +1483,53 @@ func (m *App) trackThreadUpdates(details PRDetails) {
}
func (m *App) markCurrentThreadRead() {
if m.threadIndex >= 0 && m.threadIndex < len(m.details.Threads) {
thread := m.details.Threads[m.threadIndex]
delete(m.unreadThreads, thread.ID)
delete(m.newThreads, thread.ID)
prID := m.currentPRKey()
state := m.readState.Data[prID]
if state.Threads == nil {
state.Threads = make(map[string]bool)
}
if state.Comments == nil {
state.Comments = make(map[string]bool)
}
state.Initialized = true
state.Threads[thread.ID] = true
for _, comment := range thread.Comments {
state.Comments[comment.ID] = true
delete(m.unreadComments, comment.ID)
}
m.readState.Data[prID] = state
if err := m.readState.save(); err != nil {
m.recordHealth("read state", healthWarning, err.Error())
if m.threadIndex < 0 || m.threadIndex >= len(m.details.Threads) {
return
}
m.markThreadRead(m.details.Threads[m.threadIndex].ID)
}
func (m *App) markThreadRead(threadID string) {
thread := m.threadByID(threadID)
if thread == nil {
return
}
prID := m.currentPRKey()
state := m.readState.Data[prID]
changed := m.unreadThreads[thread.ID] || m.newThreads[thread.ID] ||
!state.Initialized || !state.Threads[thread.ID]
for _, comment := range thread.Comments {
if m.unreadComments[comment.ID] || !state.Comments[comment.ID] {
changed = true
break
}
}
if !changed {
return
}
delete(m.unreadThreads, thread.ID)
delete(m.newThreads, thread.ID)
if state.Threads == nil {
state.Threads = make(map[string]bool)
}
if state.Comments == nil {
state.Comments = make(map[string]bool)
}
state.Initialized = true
state.Threads[thread.ID] = true
for _, comment := range thread.Comments {
state.Comments[comment.ID] = true
delete(m.unreadComments, comment.ID)
}
m.readState.Data[prID] = state
if err := m.readState.save(); err != nil {
m.recordHealth("read state", healthWarning, err.Error())
}
}
func (m *App) focusThreadDetail() {
m.focus = threadDetailPane
m.markCurrentThreadRead()
}
func (m *App) markCommentRead(commentID string) {
@@ -1545,10 +1572,9 @@ func (m *App) scrollToFirstUnread() {
return
}
width, _ := m.detailPaneSize()
dividerAnchor := "unread:" + firstUnread
commentAnchor := "comment:" + firstUnread + ":header"
for index, line := range m.renderedDetailLines(width) {
if line.anchor == dividerAnchor || line.anchor == commentAnchor {
if line.anchor == commentAnchor {
m.scroll = min(index, m.detailMaxScroll())
return
}
@@ -1595,8 +1621,10 @@ func (m *App) moveToUnread(direction int) {
}
if m.unreadThreads[m.details.Threads[index].ID] {
m.threadIndex, m.scroll = index, 0
m.focus = threadDetailPane
m.scrollToFirstUnread()
anchor := m.detailScrollAnchor()
m.focusThreadDetail()
m.restoreDetailAnchor(anchor)
return
}
}