From 8f11966e21563dc539f307016c17fd6eb141bc1a Mon Sep 17 00:00:00 2001 From: Matt Pua Date: Fri, 22 May 2026 17:37:27 -0400 Subject: [PATCH 1/2] fix(inbox): break signal summary sections onto separate lines Insert line breaks around What's happening, Root cause, and How to resolve headers so report summaries are easier to scan. Co-authored-by: Cursor --- .../utils/SignalReportSummaryMarkdown.tsx | 5 ++- .../formatSignalReportSummaryMarkdown.test.ts | 33 +++++++++++++++++ .../formatSignalReportSummaryMarkdown.ts | 35 +++++++++++++++++++ 3 files changed, 72 insertions(+), 1 deletion(-) create mode 100644 apps/code/src/renderer/features/inbox/utils/formatSignalReportSummaryMarkdown.test.ts create mode 100644 apps/code/src/renderer/features/inbox/utils/formatSignalReportSummaryMarkdown.ts 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..a3b49473e 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,7 +24,9 @@ export function SignalReportSummaryMarkdown({ variant, pending, }: SignalReportSummaryMarkdownProps) { - const raw = content?.trim() ? content : fallback; + const raw = formatSignalReportSummaryMarkdown( + content?.trim() ? content : fallback, + ); /** List rows: only the first line (before first newline); CSS still caps visual lines. */ const listMarkdown = raw.split(/\r?\n/)[0] ?? ""; 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..da7b097c8 --- /dev/null +++ b/apps/code/src/renderer/features/inbox/utils/formatSignalReportSummaryMarkdown.test.ts @@ -0,0 +1,33 @@ +import { describe, expect, it } from "vitest"; +import { formatSignalReportSummaryMarkdown } from "./formatSignalReportSummaryMarkdown"; + +describe("formatSignalReportSummaryMarkdown", () => { + it("puts section body text on a new line after the header", () => { + const input = + "**What's happening:** Error tracking issue keyed on `app:dashboard_query`."; + expect(formatSignalReportSummaryMarkdown(input)).toBe( + "**What's happening:**\n\nError tracking issue keyed on `app:dashboard_query`.", + ); + }); + + it("separates consecutive section headers onto their own lines", () => { + const input = + "**What's happening:** Users hit rate limits. **Root cause:** All four rate limiters are contended. **How to resolve:** Reduce blocking."; + expect(formatSignalReportSummaryMarkdown(input)).toBe( + "**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.", + ); + }); + + it("separates a section header from preceding intro text", () => { + const input = + "Users on busy orgs are hitting hard limits. **What's happening:** Error tracking issue."; + expect(formatSignalReportSummaryMarkdown(input)).toBe( + "Users on busy orgs are hitting hard limits.\n\n**What's happening:**\n\nError tracking issue.", + ); + }); + + it("leaves content without section headers unchanged", () => { + const input = "Plain summary with no structured sections."; + expect(formatSignalReportSummaryMarkdown(input)).toBe(input); + }); +}); 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; +} From a914629e86a8d02d7ad8913b92a0b1946eb958d8 Mon Sep 17 00:00:00 2001 From: Matt Pua Date: Mon, 25 May 2026 09:56:27 -0400 Subject: [PATCH 2/2] fix(inbox): keep list preview text when formatting summary sections Use unformatted content for inbox list rows so line breaks added for detail view do not truncate previews to section headers only. Co-authored-by: Cursor --- .../utils/SignalReportSummaryMarkdown.tsx | 7 +-- .../formatSignalReportSummaryMarkdown.test.ts | 56 ++++++++++--------- 2 files changed, 32 insertions(+), 31 deletions(-) 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 a3b49473e..864336da6 100644 --- a/apps/code/src/renderer/features/inbox/components/utils/SignalReportSummaryMarkdown.tsx +++ b/apps/code/src/renderer/features/inbox/components/utils/SignalReportSummaryMarkdown.tsx @@ -24,12 +24,11 @@ export function SignalReportSummaryMarkdown({ variant, pending, }: SignalReportSummaryMarkdownProps) { - const raw = formatSignalReportSummaryMarkdown( - 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 index da7b097c8..c65825d10 100644 --- a/apps/code/src/renderer/features/inbox/utils/formatSignalReportSummaryMarkdown.test.ts +++ b/apps/code/src/renderer/features/inbox/utils/formatSignalReportSummaryMarkdown.test.ts @@ -2,32 +2,34 @@ import { describe, expect, it } from "vitest"; import { formatSignalReportSummaryMarkdown } from "./formatSignalReportSummaryMarkdown"; describe("formatSignalReportSummaryMarkdown", () => { - it("puts section body text on a new line after the header", () => { - const input = - "**What's happening:** Error tracking issue keyed on `app:dashboard_query`."; - expect(formatSignalReportSummaryMarkdown(input)).toBe( - "**What's happening:**\n\nError tracking issue keyed on `app:dashboard_query`.", - ); - }); - - it("separates consecutive section headers onto their own lines", () => { - const input = - "**What's happening:** Users hit rate limits. **Root cause:** All four rate limiters are contended. **How to resolve:** Reduce blocking."; - expect(formatSignalReportSummaryMarkdown(input)).toBe( - "**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.", - ); - }); - - it("separates a section header from preceding intro text", () => { - const input = - "Users on busy orgs are hitting hard limits. **What's happening:** Error tracking issue."; - expect(formatSignalReportSummaryMarkdown(input)).toBe( - "Users on busy orgs are hitting hard limits.\n\n**What's happening:**\n\nError tracking issue.", - ); - }); - - it("leaves content without section headers unchanged", () => { - const input = "Plain summary with no structured sections."; - expect(formatSignalReportSummaryMarkdown(input)).toBe(input); + 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); }); });