fix(telegram): preserve tables across legacy message delivery (#118257)

Co-authored-by: Peter Steinberger <steipete@macos.shared>
This commit is contained in:
Peter Steinberger
2026-08-02 15:32:46 -07:00
committed by GitHub
parent a1c2009b91
commit 474c4e671e
3 changed files with 46 additions and 19 deletions
@@ -323,6 +323,25 @@ describe("deliverReplies", () => {
expect(firstMockCallArg(sendMessage, 1)).toBe("hello");
});
it("keeps native-command tables visible in non-rich block-mode replies", async () => {
const { runtime, sendMessage, bot } = createSendMessageHarness();
const table = "| A | B |\n| --- | --- |\n| 1 | 2 |";
await deliverWith({
replies: [{ text: `Before\n\n${table}\n\nAfter` }],
runtime,
bot,
tableMode: "block",
});
expect(sendMessage).toHaveBeenCalledTimes(1);
const sent = firstSendText(sendMessage);
expect(sent).toContain("Before");
expect(sent).toContain(`<pre><code>${table}\n</code></pre>`);
expect(sent).toContain("After");
expectRecordFields(firstMockCallArg(sendMessage, 2), { parse_mode: "HTML" });
});
it("delivers prepared HTML without reparsing visible syntax as Markdown", async () => {
const runtime = createRuntime();
const sendMessage = vi.fn().mockResolvedValue({ message_id: 1, chat: { id: "123" } });
+16 -4
View File
@@ -139,12 +139,24 @@ describe("markdownToTelegramHtml", () => {
);
});
it("renders block-mode tables as code in legacy Telegram HTML", () => {
it.each([
{ name: "a table-only reply", before: "", after: "" },
{ name: "a table between surrounding prose", before: "Before\n\n", after: "\n\nAfter" },
])("keeps $name visible in one-shot and chunked legacy Telegram HTML", ({ before, after }) => {
const table = "| A | B |\n| --- | --- |\n| 1 | 2 |";
const markdown = `${before}${table}${after}`;
const html = markdownToTelegramHtml(markdown, { tableMode: "block" });
const chunks = markdownToTelegramChunks(markdown, 4096, { tableMode: "block" });
expect(markdownToTelegramHtml(table, { tableMode: "block" })).toBe(
"<pre><code>| A | B |\n| --- | --- |\n| 1 | 2 |\n</code></pre>",
);
expect(html).toContain(`<pre><code>${table}\n</code></pre>`);
expect(chunks.map((chunk) => chunk.html)).toEqual([html]);
expect(chunks[0]?.text).toContain("| 1 | 2 |");
if (before) {
expect(html).toContain("Before");
}
if (after) {
expect(html).toContain("After");
}
});
it("normalizes raw code language HTML without leaking tags", () => {
+11 -15
View File
@@ -147,19 +147,22 @@ function preserveTelegramListBoundarySpacing(markdown: string): string {
return out.join("\n");
}
export function markdownToTelegramHtml(
markdown: string,
options: { tableMode?: MarkdownTableMode; wrapFileRefs?: boolean } = {},
): string {
const tableMode = options.tableMode === "block" ? "code" : options.tableMode;
const ir = markdownToIR(preserveTelegramListBoundarySpacing(markdown ?? ""), {
function parseTelegramLegacyMarkdown(markdown: string, tableMode?: MarkdownTableMode): MarkdownIR {
return markdownToIR(preserveTelegramListBoundarySpacing(markdown ?? ""), {
assistantTranscriptRoleHeaders: true,
linkify: true,
enableSpoilers: true,
headingStyle: "none",
blockquotePrefix: "",
tableMode,
tableMode: tableMode === "block" ? "code" : tableMode,
});
}
export function markdownToTelegramHtml(
markdown: string,
options: { tableMode?: MarkdownTableMode; wrapFileRefs?: boolean } = {},
): string {
const ir = parseTelegramLegacyMarkdown(markdown, options.tableMode);
const html = renderTelegramHtml(ir);
const telegramHtml = renderSupportedTelegramHtml(html);
// Apply file reference wrapping if requested (for chunked rendering)
@@ -910,14 +913,7 @@ export function markdownToTelegramChunks(
limit: number,
options: { tableMode?: MarkdownTableMode } = {},
): TelegramFormattedChunk[] {
const ir = markdownToIR(preserveTelegramListBoundarySpacing(markdown ?? ""), {
assistantTranscriptRoleHeaders: true,
linkify: true,
enableSpoilers: true,
headingStyle: "none",
blockquotePrefix: "",
tableMode: options.tableMode,
});
const ir = parseTelegramLegacyMarkdown(markdown, options.tableMode);
return renderTelegramChunksWithinHtmlLimit(ir, limit);
}