/** * ReadmeView — on-demand README generator, rendered as a full-height view * alongside Chat and Diagram in the main content area. * * Design principles: * - Matches the DiagramView pattern exactly: fills the main pane, no modal * - Scrollable markdown panel with a sticky action bar at the top * - Progress bar during generation, same visual language as tour/diagram loading * - Copy + Regenerate in the action bar — accessible, not tucked in a corner */ import { useState, useEffect, useRef, useCallback } from "react"; import ReactMarkdown from "react-markdown"; import remarkGfm from "remark-gfm"; import { streamReadme } from "../api"; /** * TerminalBlock — wraps every fenced code block in a macOS-style terminal window. * ReactMarkdown passes the element as children of
; we intercept
 * the 
 render and fish the language out of code's className prop.
 */
function TerminalBlock({ children }) {
  const codeChild = Array.isArray(children) ? children[0] : children;
  const lang = codeChild?.props?.className?.replace("language-", "") ?? null;
  return (
    
{lang && {lang}}
{children}
); } const MD_COMPONENTS = { pre: TerminalBlock }; const STAGE_LABELS = { loading: "Analysing repository…", generating: "Generating README…", }; export default function ReadmeView({ repo, contextualAt, onClose }) { const [status, setStatus] = useState("idle"); const [progress, setProgress] = useState(0); const [message, setMessage] = useState(""); const [content, setContent] = useState(null); const [fromCache, setFromCache] = useState(false); const [error, setError] = useState(null); const [copied, setCopied] = useState(false); const [rawMode, setRawMode] = useState(false); // preview vs markdown source const cancelRef = useRef(null); const generate = useCallback((force = false) => { cancelRef.current?.(); setStatus("loading"); setProgress(0); setContent(null); setError(null); cancelRef.current = streamReadme(repo, { force, onProgress: ({ stage, progress: p, message: m }) => { setStatus(stage || "loading"); setProgress(p ?? 0); setMessage(m || STAGE_LABELS[stage] || "Working…"); }, onDone: ({ content: md, from_cache }) => { setContent(md); setFromCache(from_cache); setStatus("done"); setProgress(1); }, onError: (msg) => { setError(msg); setStatus("error"); }, }); }, [repo]); useEffect(() => { generate(false); return () => cancelRef.current?.(); }, [generate]); function handleCopy() { if (!content) return; navigator.clipboard.writeText(content).then(() => { setCopied(true); setTimeout(() => setCopied(false), 1800); }); } const isLoading = status === "loading" || status === "generating"; return (
{/* Sticky action bar */}
{onClose && ( )} README.md {fromCache && status === "done" && ( cached )} {contextualAt && status === "done" && ( )} {isLoading && ( {message || STAGE_LABELS[status]} )}
{status === "done" && ( <> {/* Preview / Markdown source toggle — reuses the app-wide view-toggle system */}
)}
{/* Thin progress bar */} {isLoading && (
)} {/* Quality tip — only shown when contextual retrieval hasn't been applied yet */} {status === "done" && !contextualAt && (
Quality improves when the repo is indexed with contextual retrieval enabled (CONTEXTUAL_TOP_N>0). Re-index to get richer README output.
)} {/* Content area */}
{isLoading && (

{message || STAGE_LABELS[status]}

)} {status === "error" && (

{error}

)} {content && ( rawMode ? ( /* Raw markdown source — monospace, line-numbered feel */
{content}
) : (
{content}
) )}
); }