diff --git a/apps/code/src/renderer/features/inbox/components/utils/SignalReportSummaryMarkdown.tsx b/apps/code/src/renderer/features/inbox/components/utils/SignalReportSummaryMarkdown.tsx index d2c3d81d3..864336da6 100644 --- a/apps/code/src/renderer/features/inbox/components/utils/SignalReportSummaryMarkdown.tsx +++ b/apps/code/src/renderer/features/inbox/components/utils/SignalReportSummaryMarkdown.tsx @@ -1,4 +1,5 @@ import { MarkdownRenderer } from "@features/editor/components/MarkdownRenderer"; +import { formatSignalReportSummaryMarkdown } from "@features/inbox/utils/formatSignalReportSummaryMarkdown"; import { Box } from "@radix-ui/themes"; interface SignalReportSummaryMarkdownProps { @@ -23,10 +24,11 @@ export function SignalReportSummaryMarkdown({ variant, pending, }: SignalReportSummaryMarkdownProps) { - const raw = content?.trim() ? content : fallback; + const rawContent = content?.trim() ? content : fallback; + const raw = formatSignalReportSummaryMarkdown(rawContent); /** List rows: only the first line (before first newline); CSS still caps visual lines. */ - const listMarkdown = raw.split(/\r?\n/)[0] ?? ""; + const listMarkdown = rawContent.split(/\r?\n/)[0] ?? ""; const italicStyle = pending ? { fontStyle: "italic" as const } : undefined; diff --git a/apps/code/src/renderer/features/inbox/utils/formatSignalReportSummaryMarkdown.test.ts b/apps/code/src/renderer/features/inbox/utils/formatSignalReportSummaryMarkdown.test.ts new file mode 100644 index 000000000..c65825d10 --- /dev/null +++ b/apps/code/src/renderer/features/inbox/utils/formatSignalReportSummaryMarkdown.test.ts @@ -0,0 +1,35 @@ +import { describe, expect, it } from "vitest"; +import { formatSignalReportSummaryMarkdown } from "./formatSignalReportSummaryMarkdown"; + +describe("formatSignalReportSummaryMarkdown", () => { + it.each([ + { + name: "puts section body text on a new line after the header", + input: + "**What's happening:** Error tracking issue keyed on `app:dashboard_query`.", + expected: + "**What's happening:**\n\nError tracking issue keyed on `app:dashboard_query`.", + }, + { + name: "separates consecutive section headers onto their own lines", + input: + "**What's happening:** Users hit rate limits. **Root cause:** All four rate limiters are contended. **How to resolve:** Reduce blocking.", + expected: + "**What's happening:**\n\nUsers hit rate limits.\n\n**Root cause:**\n\nAll four rate limiters are contended.\n\n**How to resolve:**\n\nReduce blocking.", + }, + { + name: "separates a section header from preceding intro text", + input: + "Users on busy orgs are hitting hard limits. **What's happening:** Error tracking issue.", + expected: + "Users on busy orgs are hitting hard limits.\n\n**What's happening:**\n\nError tracking issue.", + }, + { + name: "leaves content without section headers unchanged", + input: "Plain summary with no structured sections.", + expected: "Plain summary with no structured sections.", + }, + ])("$name", ({ input, expected }) => { + expect(formatSignalReportSummaryMarkdown(input)).toBe(expected); + }); +}); diff --git a/apps/code/src/renderer/features/inbox/utils/formatSignalReportSummaryMarkdown.ts b/apps/code/src/renderer/features/inbox/utils/formatSignalReportSummaryMarkdown.ts new file mode 100644 index 000000000..c83720795 --- /dev/null +++ b/apps/code/src/renderer/features/inbox/utils/formatSignalReportSummaryMarkdown.ts @@ -0,0 +1,35 @@ +const SIGNAL_SUMMARY_SECTION_HEADERS = [ + "What's happening", + "Root cause", + "How to resolve", +] as const; + +function escapeRegExp(value: string): string { + return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); +} + +/** + * Inserts line breaks around signal report summary section headers so each + * label and its body render on separate lines (matches agent output like + * `**What's happening:** text`). + */ +export function formatSignalReportSummaryMarkdown(content: string): string { + let result = content; + + for (const header of SIGNAL_SUMMARY_SECTION_HEADERS) { + const escaped = escapeRegExp(header); + const boldHeaderPattern = `\\*\\*${escaped}:\\*\\*`; + + result = result.replace( + new RegExp(`([^\\n])\\s*(${boldHeaderPattern})`, "gi"), + "$1\n\n$2", + ); + + result = result.replace( + new RegExp(`(${boldHeaderPattern})\\s+`, "gi"), + "$1\n\n", + ); + } + + return result; +}