Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 27 additions & 6 deletions web/app/dashboard/reports/[id]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,7 @@ export default function ReviewReportPage() {
const [report, setReport] = useState<ReviewReportDetail | null>(null);
const [isLoading, setIsLoading] = useState(true);
const [isRetrying, setIsRetrying] = useState(false);
const [showReportMarkdown, setShowReportMarkdown] = useState(false);

const loadReport = useCallback(async () => {
if (!reportId) {
Expand All @@ -299,6 +300,10 @@ export default function ReviewReportPage() {
void loadReport();
}, [loadReport]);

useEffect(() => {
setShowReportMarkdown(false);
}, [reportId]);

const overallFindings = useMemo(
() => report?.findings.filter((finding) => finding.filePath === 'PR_OVERALL') || [],
[report?.findings]
Expand All @@ -321,6 +326,8 @@ export default function ReviewReportPage() {
const traceEntries = report?.trace?.entries || [];
const skippedFiles = report?.coverage?.skippedFiles || [];
const suppressedCount = report?.suppressedFindings?.length || 0;
const hasStructuredInsights = overallFindings.length > 0 || (report?.fileContexts.length || 0) > 0 || (report?.findings.length || 0) > 0 || traceEntries.length > 0;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Base markdown collapse on rendered context availability

hasStructuredInsights treats any report.fileContexts entry as structured content, but this page only renders highlightedFileContexts where totalFindings > 0. In reports that carry file context metadata but no actionable findings, the raw markdown is now collapsed by default even though the structured sections show only empty placeholders, so users must manually expand the only detailed report content. Derive this condition from the same rendered predicate (e.g., highlightedFileContexts.length) to avoid hiding content unexpectedly.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LOW · hasStructuredInsights 阈值可能过于宽松

当前条件 (report?.findings.length || 0) > 0 意味着只要存在任意一个 finding(即使严重级别为 low),就会默认折叠 Markdown。这意味着绝大多数报告都会默认隐藏原始 Markdown 内容,用户需要手动展开才能看到。如果这是预期行为则无问题,但可能需要与产品确认 UX 策略。

建议:如果希望更细粒度控制,可以考虑将阈值改为基于严重性(如只当存在 critical/high findings 时折叠),或增加可配置性。

const shouldRenderMarkdown = Boolean(report?.reportMarkdown) && (!hasStructuredInsights || showReportMarkdown);

const handleRetry = useCallback(async () => {
if (!reportId) {
Expand Down Expand Up @@ -448,21 +455,35 @@ export default function ReviewReportPage() {
<div className="border-b border-slate-200/80 px-6 py-5 dark:border-slate-800/80">
<div className="flex flex-wrap items-center justify-between gap-3">
<div>
<p className="text-xs font-semibold uppercase tracking-[0.18em] text-slate-500 dark:text-slate-400">Rendered Markdown</p>
<h2 className="mt-2 text-xl font-semibold text-slate-950 dark:text-slate-100">PR 审查报告</h2>
<p className="text-xs font-semibold uppercase tracking-[0.18em] text-slate-500 dark:text-slate-400">Raw Report</p>
<h2 className="mt-2 text-xl font-semibold text-slate-950 dark:text-slate-100">原始 Markdown 报告</h2>
</div>
<span className="rounded-full border border-slate-200/80 bg-slate-50 px-3 py-1 text-xs font-medium text-slate-600 dark:border-slate-700 dark:bg-slate-900 dark:text-slate-300">
GitHub-style prose
</span>
{report.reportMarkdown ? (
<button
type="button"
onClick={() => setShowReportMarkdown((current) => !current)}
className="inline-flex items-center justify-center rounded-full border border-slate-200/80 bg-slate-50 px-3 py-1 text-xs font-medium text-slate-600 transition hover:bg-slate-100 dark:border-slate-700 dark:bg-slate-900 dark:text-slate-300 dark:hover:bg-slate-800"
>
{shouldRenderMarkdown ? '收起原始报告' : '查看原始报告'}
</button>
) : (
<span className="rounded-full border border-slate-200/80 bg-slate-50 px-3 py-1 text-xs font-medium text-slate-600 dark:border-slate-700 dark:bg-slate-900 dark:text-slate-300">
暂无 Markdown
</span>
)}
</div>
</div>
<div className="px-6 py-6">
{report.reportMarkdown ? (
{shouldRenderMarkdown ? (
<div className="[&_a]:text-teal-700 [&_a]:no-underline hover:[&_a]:underline [&_blockquote]:rounded-r-2xl [&_blockquote]:border-l-4 [&_blockquote]:border-teal-400 [&_blockquote]:bg-teal-50/70 [&_blockquote]:px-5 [&_blockquote]:py-3 [&_blockquote]:text-slate-700 dark:[&_blockquote]:bg-teal-950/25 dark:[&_blockquote]:text-slate-200 [&_code]:rounded-md [&_code]:bg-slate-100 [&_code]:px-1.5 [&_code]:py-0.5 [&_code]:text-[0.92em] [&_code]:font-medium [&_code]:text-slate-700 dark:[&_code]:bg-slate-800 dark:[&_code]:text-slate-100 [&_h1]:mb-4 [&_h1]:text-3xl [&_h1]:font-semibold [&_h2]:mt-10 [&_h2]:border-b [&_h2]:border-slate-200 [&_h2]:pb-3 [&_h2]:text-xl [&_h2]:font-semibold dark:[&_h2]:border-slate-800 [&_h3]:mt-7 [&_h3]:text-lg [&_h3]:font-semibold [&_li]:my-1.5 [&_ol]:pl-5 [&_p]:my-4 [&_p]:leading-7 [&_pre]:overflow-x-auto [&_pre]:rounded-2xl [&_pre]:border [&_pre]:border-slate-800 [&_pre]:bg-slate-950 [&_pre]:px-4 [&_pre]:py-4 [&_pre]:text-[13px] [&_pre]:leading-6 [&_pre]:text-slate-100 [&_pre_code]:bg-transparent [&_pre_code]:p-0 [&_table]:block [&_table]:overflow-x-auto [&_table]:rounded-2xl [&_table]:border [&_table]:border-slate-200 dark:[&_table]:border-slate-800 [&_tbody_tr:nth-child(odd)]:bg-slate-50/80 dark:[&_tbody_tr:nth-child(odd)]:bg-slate-900/50 [&_td]:border-t [&_td]:border-slate-200 [&_td]:px-3 [&_td]:py-2 dark:[&_td]:border-slate-800 [&_th]:bg-slate-100 [&_th]:px-3 [&_th]:py-2 [&_th]:text-left dark:[&_th]:bg-slate-900">
<ReactMarkdown remarkPlugins={[remarkGfm]}>
{report.reportMarkdown}
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

HIGH · 用户生成内容直接渲染存在潜在 XSS 风险

report.reportMarkdown 来自后端 API,是用户生成内容。虽然 ReactMarkdown 默认不渲染原始 HTML(需要 rehype 插件),但如果后端或传输过程中被注入恶意内容,渲染行为可能不符合预期。需要确认 ReactMarkdown 的安全配置以及后端是否对 Markdown 内容进行了校验或 sanitization。

建议:确保 ReactMarkdown 未配置 rehype-raw 等允许原始 HTML 的插件;后端 API 应在入库前对 Markdown 内容进行 XSS 过滤(如 DOMPurify sanitize 或等效处理)。

</ReactMarkdown>
</div>
) : report.reportMarkdown ? (
<div className="rounded-2xl border border-dashed border-slate-300 bg-slate-50/60 px-4 py-10 text-center text-sm leading-6 text-slate-500 dark:border-slate-700 dark:bg-slate-900/40 dark:text-slate-400">
结构化报告已经展示在当前页面,原始 Markdown 已折叠。需要时可展开查看完整生成文本。
</div>
) : (
<div className="rounded-2xl border border-dashed border-slate-300 px-4 py-10 text-center text-sm text-slate-500 dark:border-slate-700 dark:text-slate-400">
当前报告没有 Markdown 内容。
Expand Down