The TipTap/ProseMirror editor stores agent messages with trailing paragraph nodes that produce trailing newlines (e.g. \`\n\n\n\`) in the \`content\` field. While Chatwoot's native channel delivery already handles this, webhook payloads and API responses were returning raw content with trailing whitespace — causing visible blank space below messages in every external integration that consumes Chatwoot webhooks (WhatsApp via Evolution API, Telegram bots, custom webhook consumers). Closes #13459 ## Root cause \`Messages::WebhookContentNormalizer\` already strips CommonMark hard line breaks (\`\\\` + newline) for webhook consumers, but it did not strip trailing whitespace. All webhook and API responses flow through this normaliser, so it is the single correct place to apply the fix without touching stored data. ## What changed Added \`.rstrip\` to \`Messages::WebhookContentNormalizer.normalize\`: \`\`\`ruby # before text.gsub(/\\\r?\n/, "\n") # after text.gsub(/\\\r?\n/, "\n").rstrip \`\`\` ## Trade-offs considered | Option | Decision | |---|---| | \`before_save\` on \`Message\` model | Would clean stored data but is a broader change affecting all message creation paths and would require a data migration for existing records. Out of scope for this bug. | | Trim in each channel's send path | DRY violation — many channels, each would need the same patch. | | Fix at normaliser level (chosen) | Single location, only affects webhook/API output, zero risk to stored data or native channel delivery. | **Known limitation:** existing messages in the database still have trailing newlines in storage. They will be delivered correctly through webhooks after this fix, but a follow-up migration could clean stored content if needed. ## How to reproduce 1. Send an agent reply from the Chatwoot UI 2. Inspect the \`content\` field of the outgoing \`message_created\` webhook payload 3. Observe trailing \`\n\n\n\` after the message text After this fix, the \`content\` field is trimmed before delivery. --------- Co-authored-by: Ramalau Debeila <rdebeila@datacentrix.co.za> Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
42 lines
1.4 KiB
Ruby
42 lines
1.4 KiB
Ruby
require 'rails_helper'
|
|
|
|
RSpec.describe Messages::WebhookContentNormalizer do
|
|
describe '.normalize' do
|
|
it 'returns nil unchanged' do
|
|
expect(described_class.normalize(nil)).to be_nil
|
|
end
|
|
|
|
it 'returns blank string unchanged' do
|
|
expect(described_class.normalize('')).to eq('')
|
|
end
|
|
|
|
it 'strips trailing newlines added by TipTap/ProseMirror' do
|
|
expect(described_class.normalize("hello\n\n\n")).to eq('hello')
|
|
end
|
|
|
|
it 'preserves intentional trailing spaces' do
|
|
expect(described_class.normalize("hello \n\n")).to eq('hello ')
|
|
end
|
|
|
|
it 'replaces CommonMark hard line breaks (backslash-newline) with plain newlines' do
|
|
expect(described_class.normalize("hello\\\nworld")).to eq("hello\nworld")
|
|
end
|
|
|
|
it 'replaces CommonMark hard line breaks with CRLF with plain newlines' do
|
|
expect(described_class.normalize("hello\\\r\nworld")).to eq("hello\nworld")
|
|
end
|
|
|
|
it 'preserves intentional internal newlines' do
|
|
expect(described_class.normalize("line one\nline two")).to eq("line one\nline two")
|
|
end
|
|
|
|
it 'strips trailing CRLF newlines without leaving dangling carriage returns' do
|
|
expect(described_class.normalize("hello\r\n\r\n")).to eq('hello')
|
|
end
|
|
|
|
it 'handles both hard line breaks and trailing newlines together' do
|
|
expect(described_class.normalize("hello\\\nworld\n\n\n")).to eq("hello\nworld")
|
|
end
|
|
end
|
|
end
|