Fix duplicated posts in Bluesky threads and bogus list position in DMs

Thread buffers rebuilt themselves from scratch on every refresh with no
deduplication and no guard against overlapping runs, and the list control was
mutated from the update timer's background thread. Threads opened from the same
account also shared a single session.db entry, because the buffer name was
derived from the author instead of the post.

- Name conversation buffers after the post URI, and navigate to an already open
  thread instead of opening a second buffer for it.
- Deduplicate thread items by URI, and refuse a refresh while one is in flight.
- Move the clear-and-fill step onto the GUI thread via wx.CallAfter.
- Stop start_buffers() from starting a buffer that create_buffer() already
  started, since needs_init was never cleared.

The direct messages list never focused an item after being rebuilt, so its
position was announced as "0 of 1", which cannot exist. The conversation list
now focuses an item whenever it cannot restore the previous selection, and the
buffer navigation announcements no longer report a position while nothing is
focused.

Verified against the live Bluesky API that app.bsky.feed.getPostThread itself
returns no repeated posts, so the duplication was entirely on our side.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Jesús Pavón Abián
2026-09-22 16:29:04 +02:00
co-authored by Claude Opus 5
parent 77291d7c3d
commit 0e5a75048a
4 changed files with 116 additions and 29 deletions
+15 -3
View File
@@ -587,16 +587,28 @@ class Handler:
display_name = g(author, "displayName") or g(author, "display_name")
label = handle or display_name or _("Unknown")
title = _("Conversation with {0}").format(label)
# The buffer name identifies the buffer and keys its entry in session.db, so
# it has to be derived from the post instead of the author. Two threads from
# the same account would otherwise share a single database entry.
buffer_name = "%s-conversation" % (uri,)
account_name = buffer.session.get_name() if hasattr(buffer.session, "get_name") else None
# If this thread is already open, go to it rather than opening it twice.
if account_name != None and controller.search_buffer(buffer_name, account_name):
index = controller.view.search(buffer_name, account_name)
if index is not None:
controller.view.change_buffer(index)
return
from pubsub import pub
pub.sendMessage(
"createBuffer",
buffer_type="conversation",
session_type="blueski",
buffer_title=title,
parent_tab=controller.view.search(buffer.session.get_name(), buffer.session.get_name()) if hasattr(buffer.session, "get_name") else None,
parent_tab=controller.view.search(account_name, account_name) if account_name != None else None,
start=True,
kwargs=dict(parent=controller.view.nb, name=title, session=buffer.session, uri=uri, sound="search_updated.ogg")
kwargs=dict(parent=controller.view.nb, name=buffer_name, session=buffer.session, uri=uri, sound="search_updated.ogg")
)
def open_timeline(self, controller, buffer, default="posts"):