diff --git a/CHANGELOG.md b/CHANGELOG.md index dd2fda4ad..81f267003 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -71,6 +71,7 @@ - Security: bump `tar` to 7.5.3. - Models: align ZAI thinking toggles. - iMessage/Signal: include sender metadata for non-queued group messages. (#1059) +- Discord: preserve whitespace when chunking long lines so message splits keep spacing intact. ## 2026.1.15 diff --git a/src/discord/chunk.test.ts b/src/discord/chunk.test.ts index e21a89d3e..069865273 100644 --- a/src/discord/chunk.test.ts +++ b/src/discord/chunk.test.ts @@ -63,6 +63,27 @@ describe("chunkDiscordText", () => { } }); + it("preserves whitespace when splitting long lines", () => { + const text = Array.from({ length: 40 }, () => "word").join(" "); + const chunks = chunkDiscordText(text, { maxChars: 20, maxLines: 50 }); + expect(chunks.length).toBeGreaterThan(1); + expect(chunks.join("")).toBe(text); + }); + + it("preserves mixed whitespace across chunk boundaries", () => { + const text = "alpha beta\tgamma delta epsilon zeta"; + const chunks = chunkDiscordText(text, { maxChars: 12, maxLines: 50 }); + expect(chunks.length).toBeGreaterThan(1); + expect(chunks.join("")).toBe(text); + }); + + it("keeps leading whitespace when splitting long lines", () => { + const text = " indented line with words that force splits"; + const chunks = chunkDiscordText(text, { maxChars: 14, maxLines: 50 }); + expect(chunks.length).toBeGreaterThan(1); + expect(chunks.join("")).toBe(text); + }); + it("keeps reasoning italics balanced across chunks", () => { const body = Array.from({ length: 25 }, (_, i) => `${i + 1}. line`).join("\n"); const text = `Reasoning:\n_${body}_`; diff --git a/src/discord/chunk.ts b/src/discord/chunk.ts index 3c0b13159..c9bbb1f62 100644 --- a/src/discord/chunk.ts +++ b/src/discord/chunk.ts @@ -76,8 +76,8 @@ function splitLongLine( } if (breakIdx <= 0) breakIdx = limit; out.push(remaining.slice(0, breakIdx)); - const brokeOnSeparator = breakIdx < remaining.length && /\s/.test(remaining[breakIdx]); - remaining = remaining.slice(breakIdx + (brokeOnSeparator ? 1 : 0)); + // Keep the separator for the next segment so words don't get glued together. + remaining = remaining.slice(breakIdx); } if (remaining.length) out.push(remaining); return out;