fix: skip placeholder for DraftSender channels to prevent duplicate chat bubbles

When a channel supports DraftSender (e.g. Telegram private chats), both
a placeholder message ("Thinking...") and a streaming draft bubble were
created simultaneously. On final response, preSend edited the placeholder
but never called sendMessage, so the draft bubble lingered — causing the
user to see two near-identical messages until the draft auto-expired.

By skipping SendPlaceholder for DraftSender channels, the streaming draft
serves as the sole visual indicator. The final sendMessage then replaces
the draft with the permanent response in a single atomic transition.

https://claude.ai/code/session_01RPaJHXgB9DhsS9JZxWbTno
This commit is contained in:
Claude 2026-03-03 09:37:37 +00:00
parent 94c4aedf87
commit fcd6efe40b

View file

@ -284,10 +284,15 @@ func (c *BaseChannel) HandleMessage(
c.placeholderRecorder.RecordReactionUndo(c.name, chatID, undo) c.placeholderRecorder.RecordReactionUndo(c.name, chatID, undo)
} }
} }
// Placeholder — independent pipeline // Placeholder — independent pipeline.
if pc, ok := c.owner.(PlaceholderCapable); ok { // Skip for DraftSender channels: the streaming draft bubble serves
if phID, err := pc.SendPlaceholder(ctx, chatID); err == nil && phID != "" { // as the placeholder, and sendMessage on final response replaces it.
c.placeholderRecorder.RecordPlaceholder(c.name, chatID, phID) // Sending both a placeholder AND drafts causes duplicate chat bubbles.
if _, isDrafter := c.owner.(DraftSender); !isDrafter {
if pc, ok := c.owner.(PlaceholderCapable); ok {
if phID, err := pc.SendPlaceholder(ctx, chatID); err == nil && phID != "" {
c.placeholderRecorder.RecordPlaceholder(c.name, chatID, phID)
}
} }
} }
} }