feat(markdown): integrate Markdown component for enhanced text rendering

- Added a reusable Markdown component to render markdown content with styled typography for dark mode.
- Updated KanbanCard to utilize the new Markdown component for displaying feature summaries, improving readability and presentation.
- Included the `react-markdown` library as a dependency for markdown parsing.

Modified: package.json, package-lock.json, markdown.tsx, kanban-card.tsx
This commit is contained in:
Kacper
2025-12-09 23:49:19 +01:00
parent ef0519adf9
commit 6f9d90b199
5 changed files with 1228 additions and 23 deletions

1168
app/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -34,6 +34,7 @@
"next": "16.0.7",
"react": "19.2.0",
"react-dom": "19.2.0",
"react-markdown": "^10.1.0",
"sonner": "^2.0.7",
"tailwind-merge": "^3.4.0",
"zustand": "^5.0.9"

View File

@@ -0,0 +1,48 @@
"use client";
import ReactMarkdown from "react-markdown";
import { cn } from "@/lib/utils";
interface MarkdownProps {
children: string;
className?: string;
}
/**
* Reusable Markdown component for rendering markdown content
* Styled for dark mode with proper typography
*/
export function Markdown({ children, className }: MarkdownProps) {
return (
<div
className={cn(
"prose prose-sm prose-invert max-w-none",
// Headings
"[&_h1]:text-xl [&_h1]:text-zinc-200 [&_h1]:font-semibold [&_h1]:mt-4 [&_h1]:mb-2",
"[&_h2]:text-lg [&_h2]:text-zinc-200 [&_h2]:font-semibold [&_h2]:mt-4 [&_h2]:mb-2",
"[&_h3]:text-base [&_h3]:text-zinc-200 [&_h3]:font-semibold [&_h3]:mt-3 [&_h3]:mb-2",
"[&_h4]:text-sm [&_h4]:text-zinc-200 [&_h4]:font-semibold [&_h4]:mt-2 [&_h4]:mb-1",
// Paragraphs
"[&_p]:text-zinc-300 [&_p]:leading-relaxed [&_p]:my-2",
// Lists
"[&_ul]:my-2 [&_ul]:pl-4 [&_ol]:my-2 [&_ol]:pl-4",
"[&_li]:text-zinc-300 [&_li]:my-0.5",
// Code
"[&_code]:text-cyan-400 [&_code]:bg-zinc-800/50 [&_code]:px-1.5 [&_code]:py-0.5 [&_code]:rounded [&_code]:text-sm",
"[&_pre]:bg-zinc-900/80 [&_pre]:border [&_pre]:border-white/10 [&_pre]:rounded-lg [&_pre]:my-2 [&_pre]:p-3 [&_pre]:overflow-x-auto",
"[&_pre_code]:bg-transparent [&_pre_code]:p-0",
// Strong/Bold
"[&_strong]:text-zinc-200 [&_strong]:font-semibold",
// Links
"[&_a]:text-blue-400 [&_a]:no-underline hover:[&_a]:underline",
// Blockquotes
"[&_blockquote]:border-l-2 [&_blockquote]:border-zinc-600 [&_blockquote]:pl-4 [&_blockquote]:text-zinc-400 [&_blockquote]:italic [&_blockquote]:my-2",
// Horizontal rules
"[&_hr]:border-zinc-700 [&_hr]:my-4",
className
)}
>
<ReactMarkdown>{children}</ReactMarkdown>
</div>
);
}

View File

@@ -50,6 +50,7 @@ import {
formatModelName,
DEFAULT_MODEL,
} from "@/lib/agent-context-parser";
import { Markdown } from "@/components/ui/markdown";
interface KanbanCardProps {
feature: Feature;
@@ -674,7 +675,7 @@ export function KanbanCard({
{/* Summary Modal */}
<Dialog open={isSummaryDialogOpen} onOpenChange={setIsSummaryDialogOpen}>
<DialogContent
className="max-w-2xl max-h-[80vh] overflow-hidden flex flex-col"
className="max-w-4xl max-h-[80vh] overflow-hidden flex flex-col"
data-testid={`summary-dialog-${feature.id}`}
>
<DialogHeader>
@@ -682,16 +683,16 @@ export function KanbanCard({
<Sparkles className="w-5 h-5 text-green-400" />
Implementation Summary
</DialogTitle>
<DialogDescription className="text-sm">
{feature.description}
<DialogDescription className="text-sm" title={feature.description}>
{feature.description.length > 100
? `${feature.description.slice(0, 100)}...`
: feature.description}
</DialogDescription>
</DialogHeader>
<div className="flex-1 overflow-y-auto">
<div className="prose prose-sm prose-invert max-w-none">
<pre className="whitespace-pre-wrap text-sm text-zinc-300 bg-zinc-900/50 p-4 rounded-lg border border-white/10">
{feature.summary || summary || agentInfo?.summary || "No summary available"}
</pre>
</div>
<div className="flex-1 overflow-y-auto p-4 bg-zinc-900/50 rounded-lg border border-white/10">
<Markdown>
{feature.summary || summary || agentInfo?.summary || "No summary available"}
</Markdown>
</div>
<DialogFooter>
<Button

View File

@@ -132,20 +132,21 @@ function getCurrentPhase(content: string): "planning" | "action" | "verification
* Extracts a summary from completed feature context
*/
function extractSummary(content: string): string | undefined {
// Look for summary sections - capture everything including subsections (###)
// Stop at same-level ## sections (but not ###), or tool markers, or end
const summaryMatch = content.match(/## Summary[^\n]*\n([\s\S]*?)(?=\n## [^#]|\n🔧|$)/i);
if (summaryMatch) {
return summaryMatch[1].trim();
}
// Look for completion markers and extract surrounding text
const completionMatch = content.match(/✓ (?:Feature|Verification|Task) (?:successfully|completed|verified)[^\n]*(?:\n[^\n]{1,200})?/i);
if (completionMatch) {
return completionMatch[0].trim();
}
// Look for summary sections
const summaryMatch = content.match(/## Summary[^\n]*\n([\s\S]{1,500}?)(?=\n##|\n🔧|$)/i);
if (summaryMatch) {
return summaryMatch[1].trim();
}
// Look for "What was done" type sections
const whatWasDoneMatch = content.match(/(?:What was done|Changes made|Implemented)[^\n]*\n([\s\S]{1,500}?)(?=\n##|\n🔧|$)/i);
const whatWasDoneMatch = content.match(/(?:What was done|Changes made|Implemented)[^\n]*\n([\s\S]*?)(?=\n## [^#]|\n🔧|$)/i);
if (whatWasDoneMatch) {
return whatWasDoneMatch[1].trim();
}