From 065a80580bf882385d6d69617aef95fe97e2a949 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=A8=8B=E5=BA=8F=E5=91=98=E9=98=BF=E6=B1=9F=28Relakkes?= =?UTF-8?q?=29?= Date: Sat, 9 May 2026 22:42:48 +0800 Subject: [PATCH] Render scheduled task summaries as markdown Scheduled task results can contain assistant-authored markdown, but the runs panel previously displayed that content as plain pre-wrapped text. Reusing the desktop markdown renderer keeps task summaries aligned with chat and settings surfaces while adding a compact prose variant for the narrow log panel. Constraint: Desktop task logs need dense formatting without introducing a new markdown dependency Rejected: Keep whitespace-pre-wrap summary text | markdown syntax remains visible in completed task runs Confidence: high Scope-risk: narrow Directive: Keep scheduled run summary rendering on the shared MarkdownRenderer path so links, lists, inline code, and future markdown fixes stay consistent Tested: cd desktop && bun run test src/components/markdown/MarkdownRenderer.test.tsx src/components/tasks/TaskRunsPanel.test.tsx Tested: cd desktop && bun run lint Tested: cd desktop && bun run build Tested: bun run check:desktop Tested: agent-browser E2E against isolated CLAUDE_CONFIG_DIR with real backend and Vite UI; verified strong/li/code/link DOM output and no raw markdown bold markers Not-tested: Tauri native packaged app shell --- .../markdown/MarkdownRenderer.test.tsx | 16 +++++++ .../components/markdown/MarkdownRenderer.tsx | 22 +++++++-- .../components/tasks/TaskRunsPanel.test.tsx | 47 +++++++++++++++++++ .../src/components/tasks/TaskRunsPanel.tsx | 10 ++-- 4 files changed, 89 insertions(+), 6 deletions(-) create mode 100644 desktop/src/components/tasks/TaskRunsPanel.test.tsx diff --git a/desktop/src/components/markdown/MarkdownRenderer.test.tsx b/desktop/src/components/markdown/MarkdownRenderer.test.tsx index d794ca10..b47bd5b8 100644 --- a/desktop/src/components/markdown/MarkdownRenderer.test.tsx +++ b/desktop/src/components/markdown/MarkdownRenderer.test.tsx @@ -51,6 +51,22 @@ describe('MarkdownRenderer', () => { expect(screen.getByText('Body copy.')).toBeInTheDocument() }) + it('applies compact prose classes for dense surfaces', () => { + const { container } = render( + , + ) + + const root = container.firstChild as HTMLDivElement + expect(root).toBeInTheDocument() + expect(root.className).toContain('prose-p:text-xs') + expect(root.className).toContain('prose-li:text-xs') + expect(screen.getByText('Done')).toBeInTheDocument() + expect(screen.getByText('One')).toBeInTheDocument() + }) + it('uses semantic code colors for inline code so both themes stay readable', () => { const { container } = render( , diff --git a/desktop/src/components/markdown/MarkdownRenderer.tsx b/desktop/src/components/markdown/MarkdownRenderer.tsx index 62b4ccc4..d9d0f236 100644 --- a/desktop/src/components/markdown/MarkdownRenderer.tsx +++ b/desktop/src/components/markdown/MarkdownRenderer.tsx @@ -6,7 +6,7 @@ import { MermaidRenderer } from '../chat/MermaidRenderer' type Props = { content: string - variant?: 'default' | 'document' + variant?: 'default' | 'document' | 'compact' className?: string } @@ -136,8 +136,24 @@ const DOCUMENT_PROSE_CLASSES = ` prose-li:my-1.5 prose-table:my-0` -function getProseClasses(variant: 'default' | 'document', className?: string) { - return [BASE_PROSE_CLASSES, variant === 'document' ? DOCUMENT_PROSE_CLASSES : '', className ?? ''] +const COMPACT_PROSE_CLASSES = ` + prose-p:my-1 prose-p:text-xs prose-p:leading-5 prose-p:text-[var(--color-text-secondary)] + prose-headings:mt-2 prose-headings:mb-1 prose-headings:leading-snug + prose-h1:text-base prose-h2:text-sm prose-h3:text-xs prose-h4:text-xs + prose-blockquote:my-2 prose-blockquote:border-l-2 prose-blockquote:border-[var(--color-outline-variant)] prose-blockquote:pl-3 prose-blockquote:text-[var(--color-text-secondary)] + prose-code:text-[12px] + prose-ul:my-1 prose-ol:my-1 prose-ul:pl-4 prose-ol:pl-4 + prose-li:my-0.5 prose-li:text-xs prose-li:leading-5 prose-li:text-[var(--color-text-secondary)] + prose-table:text-xs + [&_.md-table-wrap]:my-2` + +function getProseClasses(variant: 'default' | 'document' | 'compact', className?: string) { + return [ + BASE_PROSE_CLASSES, + variant === 'document' ? DOCUMENT_PROSE_CLASSES : '', + variant === 'compact' ? COMPACT_PROSE_CLASSES : '', + className ?? '', + ] .filter(Boolean) .join(' ') } diff --git a/desktop/src/components/tasks/TaskRunsPanel.test.tsx b/desktop/src/components/tasks/TaskRunsPanel.test.tsx new file mode 100644 index 00000000..f0fd2d8c --- /dev/null +++ b/desktop/src/components/tasks/TaskRunsPanel.test.tsx @@ -0,0 +1,47 @@ +import { afterEach, describe, expect, it, vi } from 'vitest' +import { cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react' +import '@testing-library/jest-dom' + +import { TaskRunsPanel } from './TaskRunsPanel' +import { useSettingsStore } from '../../stores/settingsStore' +import { useTaskStore } from '../../stores/taskStore' +import type { TaskRun } from '../../types/task' + +afterEach(() => { + cleanup() + useSettingsStore.setState(useSettingsStore.getInitialState(), true) + useTaskStore.setState(useTaskStore.getInitialState(), true) +}) + +describe('TaskRunsPanel', () => { + it('renders scheduled task summaries as markdown', async () => { + const run: TaskRun = { + id: 'run-1', + taskId: 'task-1', + taskName: 'Daily summary', + startedAt: '2026-05-08T12:05:37.000Z', + status: 'completed', + prompt: 'Summarize recent commits', + output: '最近7天有3个commit,主要改动:\n\n**1. 2865d50 - UI无障碍改进**\n- 添加 theme-color meta 标签\n- 修复 select 标签问题', + durationMs: 12000, + sessionId: 'session-1', + } + useSettingsStore.setState({ locale: 'en' }) + useTaskStore.setState({ + fetchTaskRuns: vi.fn(async () => [run]), + } as Partial>) + + const { container } = render( + , + ) + + await waitFor(() => expect(screen.getByRole('button', { name: 'Summary' })).toBeInTheDocument()) + fireEvent.click(screen.getByRole('button', { name: 'Summary' })) + + expect(screen.getByText('1. 2865d50 - UI无障碍改进')).toBeInTheDocument() + expect(container.querySelector('strong')).toHaveTextContent('1. 2865d50 - UI无障碍改进') + expect(screen.getByText('添加 theme-color meta 标签')).toBeInTheDocument() + expect(container.querySelector('li')).toHaveTextContent('添加 theme-color meta 标签') + expect(container.textContent).not.toContain('**1. 2865d50') + }) +}) diff --git a/desktop/src/components/tasks/TaskRunsPanel.tsx b/desktop/src/components/tasks/TaskRunsPanel.tsx index 6f203087..172b0bbb 100644 --- a/desktop/src/components/tasks/TaskRunsPanel.tsx +++ b/desktop/src/components/tasks/TaskRunsPanel.tsx @@ -5,6 +5,7 @@ import { useTabStore } from '../../stores/tabStore' import { useTranslation } from '../../i18n' import { parseRunOutput } from '../../lib/parseRunOutput' import type { TaskRun } from '../../types/task' +import { MarkdownRenderer } from '../markdown/MarkdownRenderer' function RunOutput({ run }: { run: TaskRun }) { const t = useTranslation() @@ -28,10 +29,13 @@ function RunOutput({ run }: { run: TaskRun }) { ) } - // Render AI text response with proper formatting (not monospace
)
   return (
-    
- {text} +
+
) }