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} +
+
) }