diff --git a/apps/mobile/src/app/inbox/[...id].tsx b/apps/mobile/src/app/inbox/[...id].tsx index 031443224..4b9937f6e 100644 --- a/apps/mobile/src/app/inbox/[...id].tsx +++ b/apps/mobile/src/app/inbox/[...id].tsx @@ -51,7 +51,10 @@ import type { SignalReportStatus, SuggestedReviewersArtefact, } from "@/features/inbox/types"; -import { inboxStatusLabel } from "@/features/inbox/utils"; +import { + formatSignalReportSummaryMarkdown, + inboxStatusLabel, +} from "@/features/inbox/utils"; import { computeReportAgeHours, type InboxReportActionType, @@ -492,7 +495,9 @@ export default function ReportDetailScreen() { {/* Summary */} {report.summary && ( - + )} diff --git a/apps/mobile/src/features/inbox/components/TinderView.tsx b/apps/mobile/src/features/inbox/components/TinderView.tsx index 5f8928555..caeae12e8 100644 --- a/apps/mobile/src/features/inbox/components/TinderView.tsx +++ b/apps/mobile/src/features/inbox/components/TinderView.tsx @@ -37,7 +37,7 @@ import type { SignalReportPriority, SignalReportStatus, } from "../types"; -import { inboxStatusLabel } from "../utils"; +import { formatSignalReportSummaryMarkdown, inboxStatusLabel } from "../utils"; import { SwipeableReportCard } from "./SwipeableReportCard"; const log = logger.scope("tinder-view"); @@ -423,7 +423,11 @@ export function TinderView({ {/* Summary */} {expandedReport.summary && ( - + )} {/* Signal count + time */} diff --git a/apps/mobile/src/features/inbox/utils.test.ts b/apps/mobile/src/features/inbox/utils.test.ts index 9663998ec..ddb7204b8 100644 --- a/apps/mobile/src/features/inbox/utils.test.ts +++ b/apps/mobile/src/features/inbox/utils.test.ts @@ -8,6 +8,7 @@ import type { import { buildInboxViewedProperties, buildReviewerOptions, + formatSignalReportSummaryMarkdown, reviewerMatchesAvailable, toSuggestedReviewerWriteContent, } from "./utils"; @@ -61,6 +62,44 @@ function makeReport( }; } +describe("formatSignalReportSummaryMarkdown", () => { + it.each([ + { + name: "puts section body 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: "splits consecutive headers packed on one line", + input: + "**What's happening:** Users hit rate limits. **Root cause:** Limiters are contended. **How to resolve:** Reduce blocking.", + expected: + "**What's happening:**\n\nUsers hit rate limits.\n\n**Root cause:**\n\nLimiters are contended.\n\n**How to resolve:**\n\nReduce blocking.", + }, + { + name: "leaves already-separated headers sane", + input: + "**What's happening:**\n\nUsers hit rate limits.\n\n**Root cause:**\n\nLimiters are contended.", + expected: + "**What's happening:**\n\nUsers hit rate limits.\n\n**Root cause:**\n\nLimiters are contended.", + }, + { + name: "leaves content without headers unchanged", + input: "Plain summary with no structured sections.", + expected: "Plain summary with no structured sections.", + }, + { + name: "matches headers case-insensitively", + input: "**what's happening:** lowercase header body.", + expected: "**what's happening:**\n\nlowercase header body.", + }, + ])("$name", ({ input, expected }) => { + expect(formatSignalReportSummaryMarkdown(input)).toBe(expected); + }); +}); + describe("buildInboxViewedProperties", () => { it("emits zero counts for an empty list", () => { const props = buildInboxViewedProperties([], 0, { diff --git a/apps/mobile/src/features/inbox/utils.ts b/apps/mobile/src/features/inbox/utils.ts index cfcd8aa37..e55b6f3bb 100644 --- a/apps/mobile/src/features/inbox/utils.ts +++ b/apps/mobile/src/features/inbox/utils.ts @@ -8,6 +8,32 @@ import type { SuggestedReviewerWriteEntry, } from "./types"; +const SIGNAL_SUMMARY_SECTION_HEADERS = [ + "What's happening", + "Root cause", + "How to resolve", +] as const; + +/** + * Inserts blank lines around signal report summary section headers so each + * label and its body render on their own line (agent output often packs them + * together, e.g. `**What's happening:** text **Root cause:** ...`). + */ +export function formatSignalReportSummaryMarkdown(content: string): string { + let result = content; + + for (const header of SIGNAL_SUMMARY_SECTION_HEADERS) { + const boldHeader = `\\*\\*${header}:\\*\\*`; + result = result.replace( + new RegExp(`([^\\n])\\s*(${boldHeader})`, "gi"), + "$1\n\n$2", + ); + result = result.replace(new RegExp(`(${boldHeader})\\s+`, "gi"), "$1\n\n"); + } + + return result; +} + export function inboxStatusLabel(status: SignalReportStatus): string { switch (status) { case "ready":