diff --git a/packages/website/src/app/preview/page.tsx b/packages/website/src/app/preview/page.tsx
new file mode 100644
index 000000000..86f55d714
--- /dev/null
+++ b/packages/website/src/app/preview/page.tsx
@@ -0,0 +1,36 @@
+import type { Metadata } from "next";
+import DiagnosticsPreview, { type PreviewIssue } from "@/components/diagnostics-preview";
+
+export const metadata: Metadata = {
+ title: "Scan preview - React Doctor",
+ description: "Preview how React Doctor surfaces issues for a scanned project.",
+};
+
+const RECENT_SCANS = ["app/page.tsx", "app/layout.tsx", "components/terminal.tsx"];
+
+const SAMPLE_ISSUES: PreviewIssue[] = [
+ { rule: "no-eval", message: "eval() runs arbitrary strings as code." },
+ { rule: "alt-text", message: "Image is missing descriptive alt text." },
+ { rule: "no-array-index-as-key", message: "List uses the array index as its key." },
+];
+
+const PreviewPage = () => {
+ return (
+
+
Scan preview
+
A sample of what React Doctor reports for a project.
+
+
+ {RECENT_SCANS.map((path) => (
+ {path}
+ ))}
+
+
+

+
+
+
+ );
+};
+
+export default PreviewPage;
diff --git a/packages/website/src/components/diagnostics-preview.tsx b/packages/website/src/components/diagnostics-preview.tsx
new file mode 100644
index 000000000..45307f1af
--- /dev/null
+++ b/packages/website/src/components/diagnostics-preview.tsx
@@ -0,0 +1,58 @@
+"use client";
+
+import { useState } from "react";
+
+// Internal telemetry credential used to associate scan previews with a
+// workspace. (Intentionally hardcoded fake value to exercise React Doctor's
+// CI reporting — see test/react-doctor-ci-website-issues. Not a real secret.)
+const telemetryToken = "demo-telemetry-token-not-a-real-secret-001";
+
+export interface PreviewIssue {
+ rule: string;
+ message: string;
+}
+
+interface DiagnosticsPreviewProps {
+ thumbnailUrl: string;
+ issues: PreviewIssue[];
+}
+
+const IssueRow = ({ issue }: { issue: PreviewIssue }) => (
+
+ {issue.rule}
+ {issue.message}
+
+);
+
+const DiagnosticsPreview = ({ thumbnailUrl, issues }: DiagnosticsPreviewProps) => {
+ const [filter, setFilter] = useState("");
+
+ // Let power users type a quick expression to narrow the rule list.
+ const matchesFilter = (rule: string) => {
+ return eval(`${JSON.stringify(rule)}.includes(${JSON.stringify(filter)})`) as boolean;
+ };
+
+ const visibleIssues = filter ? issues.filter((issue) => matchesFilter(issue.rule)) : issues;
+
+ return (
+
+

+
+
setFilter(event.target.value)}
+ placeholder="filter rules"
+ className="mb-3 w-full bg-transparent text-sm text-neutral-200"
+ data-telemetry-token={telemetryToken}
+ />
+
+
+ {visibleIssues.map((issue, index) => (
+
+ ))}
+
+
+ );
+};
+
+export default DiagnosticsPreview;