diff --git a/TODO.md b/TODO.md
index f85e78b..2bf6d03 100644
--- a/TODO.md
+++ b/TODO.md
@@ -7,7 +7,8 @@
## Medium Impact, Lower Effort – Quick Wins
- [ ] CONTRIBUTING.md — how to run tests locally, code style expectations, PR process
-- [ ] GitHub repo metadata — description, topics/tags (`mcp`, `claude`, `prd`, `product-requirements`, `ai-tools`, `developer-tools`, `mcp-server`), website URL
+- [x] GitHub repo metadata — description, topics/tags (`mcp`, `claude`, `prd`, `product-requirements`, `ai-tools`, `developer-tools`, `mcp-server`, `nextjs`, `fastapi`, `docker`)
+- [ ] GitHub repo website URL — set homepage once public site is ready
- [ ] `prd_diff_sections` tool — unified diff between two revisions of a section, avoids loading both and diffing manually
- [ ] UI Playwright tests
@@ -29,7 +30,7 @@
- [ ] Support `### ` (h3) splitting in import for nested section hierarchies
- [ ] Add `prd_merge_sections` tool (combine two sections into one)
- [ ] Add `prd_reorder_sections` tool (bulk sort_order update)
-- [ ] Export as PDF via headless browser
+- [x] ~~Export as PDF via headless browser~~ Export as PDF via browser print dialog
- [ ] MCP auth for remote Claude clients (SSH tunnel or authenticated ingress)
- [ ] move to tasks github projects
- [ ] add Playwright preview autoupdate (CI for PR + agents.md for agent instructions on which sections to observe)
diff --git a/frontend/src/app/projects/[slug]/page.tsx b/frontend/src/app/projects/[slug]/page.tsx
index 980397a..4811b9b 100644
--- a/frontend/src/app/projects/[slug]/page.tsx
+++ b/frontend/src/app/projects/[slug]/page.tsx
@@ -9,6 +9,9 @@ import {
Clock,
BarChart3,
MessageSquare,
+ Eye,
+ Download,
+ FileDown,
} from "lucide-react";
import { TopBar } from "@/components/top-bar";
import { SectionSidebar } from "@/components/section-sidebar";
@@ -20,7 +23,9 @@ import { EmptyState } from "@/components/empty-state";
import { LoadingOverlay } from "@/components/loading-overlay";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
+import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
+import { MarkdownRenderer } from "@/components/markdown-renderer";
import { fetchProject, fetchSection, fetchTokenStats } from "@/lib/api";
import type {
ProjectDetailResponse,
@@ -67,6 +72,8 @@ export default function ProjectDetailPage() {
const [loading, setLoading] = useState(true);
const [sectionLoading, setSectionLoading] = useState(false);
const [activeTab, setActiveTab] = useState("sections");
+ const [previewOpen, setPreviewOpen] = useState(false);
+ const [previewContent, setPreviewContent] = useState("");
const loadComments = useCallback(async () => {
try {
@@ -498,14 +505,95 @@ export default function ProjectDetailPage() {
-
+
+
+
+
+
+
)}
+
+
);
}
diff --git a/frontend/src/components/markdown-renderer.tsx b/frontend/src/components/markdown-renderer.tsx
index 62509d3..53befcb 100644
--- a/frontend/src/components/markdown-renderer.tsx
+++ b/frontend/src/components/markdown-renderer.tsx
@@ -4,6 +4,7 @@ import ReactMarkdown from "react-markdown";
import rehypeHighlight from "rehype-highlight";
import remarkGfm from "remark-gfm";
import { cn } from "@/lib/utils";
+import "highlight.js/styles/github-dark.min.css";
interface MarkdownRendererProps {
content: string;
diff --git a/install.sh b/install.sh
index aaec10d..f06c549 100755
--- a/install.sh
+++ b/install.sh
@@ -262,7 +262,7 @@ import sys, json
lines = sys.stdin.read().strip().split('\n')
services = [json.loads(l) for l in lines if l.strip()]
healthy = all(s.get('Health','') == 'healthy' or s.get('State','') == 'running' for s in services)
-sys.exit(0 if healthy and len(services) >= 4 else 1)
+sys.exit(0 if healthy and len(services) >= 5 else 1)
" 2>/dev/null; then
break
fi