diff --git a/apps/ui/package.json b/apps/ui/package.json index e66433fdc..5a5ac3490 100644 --- a/apps/ui/package.json +++ b/apps/ui/package.json @@ -102,6 +102,8 @@ "react-markdown": "10.1.0", "react-resizable-panels": "3.0.6", "rehype-raw": "7.0.0", + "rehype-sanitize": "^6.0.0", + "remark-gfm": "^4.0.1", "sonner": "2.0.7", "tailwind-merge": "3.4.0", "usehooks-ts": "3.1.1", diff --git a/apps/ui/src/components/ui/markdown.tsx b/apps/ui/src/components/ui/markdown.tsx index 1d4f8ef9d..ff7facbf1 100644 --- a/apps/ui/src/components/ui/markdown.tsx +++ b/apps/ui/src/components/ui/markdown.tsx @@ -1,13 +1,97 @@ -import ReactMarkdown from 'react-markdown'; +import ReactMarkdown, { Components } from 'react-markdown'; import rehypeRaw from 'rehype-raw'; import rehypeSanitize from 'rehype-sanitize'; +import remarkGfm from 'remark-gfm'; import { cn } from '@/lib/utils'; +import { Square, CheckSquare } from 'lucide-react'; interface MarkdownProps { children: string; className?: string; } +/** + * Renders a tasks code block as a proper task list with checkboxes + */ +function TasksBlock({ content }: { content: string }) { + const lines = content.split('\n'); + + return ( +
+ {lines.map((line, idx) => { + const trimmed = line.trim(); + + // Check for phase/section headers (## Phase 1: ...) + const headerMatch = trimmed.match(/^##\s+(.+)$/); + if (headerMatch) { + return ( +
+ {headerMatch[1]} +
+ ); + } + + // Check for task items (- [ ] or - [x]) + const taskMatch = trimmed.match(/^-\s*\[([ xX])\]\s*(.+)$/); + if (taskMatch) { + const isChecked = taskMatch[1].toLowerCase() === 'x'; + const taskText = taskMatch[2]; + + return ( +
+ {isChecked ? ( + + ) : ( + + )} + + {taskText} + +
+ ); + } + + // Empty lines + if (!trimmed) { + return
; + } + + // Other content (render as-is) + return ( +
+ {trimmed} +
+ ); + })} +
+ ); +} + +/** + * Custom components for ReactMarkdown + */ +const markdownComponents: Components = { + // Handle code blocks - special case for 'tasks' language + code({ className, children }) { + const match = /language-(\w+)/.exec(className || ''); + const language = match ? match[1] : ''; + const content = String(children).replace(/\n$/, ''); + + // Special handling for tasks code blocks + if (language === 'tasks') { + return ; + } + + // Regular code (inline or block) + return {children}; + }, +}; + /** * Reusable Markdown component for rendering markdown content * Theme-aware styling that adapts to all predefined themes @@ -42,10 +126,20 @@ export function Markdown({ children, className }: MarkdownProps) { '[&_hr]:border-border [&_hr]:my-4', // Images '[&_img]:max-w-full [&_img]:h-auto [&_img]:rounded-lg [&_img]:my-2 [&_img]:border [&_img]:border-border', + // Tables + '[&_table]:w-full [&_table]:border-collapse [&_table]:my-4', + '[&_th]:border [&_th]:border-border [&_th]:bg-muted [&_th]:px-3 [&_th]:py-2 [&_th]:text-left [&_th]:text-foreground [&_th]:font-semibold', + '[&_td]:border [&_td]:border-border [&_td]:px-3 [&_td]:py-2 [&_td]:text-foreground-secondary', className )} > - {children} + + {children} +
); } diff --git a/apps/ui/src/components/views/board-view/dialogs/plan-approval-dialog.tsx b/apps/ui/src/components/views/board-view/dialogs/plan-approval-dialog.tsx index d49d408e0..f0e64102d 100644 --- a/apps/ui/src/components/views/board-view/dialogs/plan-approval-dialog.tsx +++ b/apps/ui/src/components/views/board-view/dialogs/plan-approval-dialog.tsx @@ -11,7 +11,7 @@ import { } from '@/components/ui/dialog'; import { Button } from '@/components/ui/button'; import { Textarea } from '@/components/ui/textarea'; -import { Markdown } from '@/components/ui/markdown'; +import { PlanContentViewer } from './plan-content-viewer'; import { Label } from '@/components/ui/label'; import { Feature } from '@/store/app-store'; import { Check, RefreshCw, Edit2, Eye } from 'lucide-react'; @@ -42,6 +42,10 @@ export function PlanApprovalDialog({ const [editedPlan, setEditedPlan] = useState(planContent); const [showRejectFeedback, setShowRejectFeedback] = useState(false); const [rejectFeedback, setRejectFeedback] = useState(''); + const [showFullDescription, setShowFullDescription] = useState(false); + + const DESCRIPTION_LIMIT = 250; + const TITLE_LIMIT = 50; // Reset state when dialog opens or plan content changes useEffect(() => { @@ -50,6 +54,7 @@ export function PlanApprovalDialog({ setIsEditMode(false); setShowRejectFeedback(false); setRejectFeedback(''); + setShowFullDescription(false); } }, [open, planContent]); @@ -82,15 +87,31 @@ export function PlanApprovalDialog({ - {viewOnly ? 'View Plan' : 'Review Plan'} + + {viewOnly ? 'View Plan' : 'Review Plan'} + {feature?.title && feature.title.length <= TITLE_LIMIT && ( + - {feature.title} + )} + {viewOnly ? 'View the generated plan for this feature.' : 'Review the generated plan before implementation begins.'} {feature && ( - Feature: {feature.description.slice(0, 150)} - {feature.description.length > 150 ? '...' : ''} + Feature:{' '} + {showFullDescription || feature.description.length <= DESCRIPTION_LIMIT + ? feature.description + : `${feature.description.slice(0, DESCRIPTION_LIMIT)}...`} + {feature.description.length > DESCRIPTION_LIMIT && ( + + )} )} @@ -135,9 +156,7 @@ export function PlanApprovalDialog({ disabled={isLoading} /> ) : ( -
- {editedPlan || 'No plan content available.'} -
+ )} diff --git a/apps/ui/src/components/views/board-view/dialogs/plan-content-viewer.tsx b/apps/ui/src/components/views/board-view/dialogs/plan-content-viewer.tsx new file mode 100644 index 000000000..dd90b0f42 --- /dev/null +++ b/apps/ui/src/components/views/board-view/dialogs/plan-content-viewer.tsx @@ -0,0 +1,216 @@ +'use client'; + +import { useMemo, useState } from 'react'; +import { ChevronDown, ChevronRight, Wrench } from 'lucide-react'; +import { Markdown } from '@/components/ui/markdown'; +import { cn } from '@/lib/utils'; + +interface ToolCall { + tool: string; + input: string; +} + +interface ParsedPlanContent { + toolCalls: ToolCall[]; + planMarkdown: string; +} + +/** + * Parses plan content to separate tool calls from the actual plan/specification markdown. + * Tool calls appear at the beginning (exploration phase), followed by the plan markdown. + */ +function parsePlanContent(content: string): ParsedPlanContent { + const lines = content.split('\n'); + const toolCalls: ToolCall[] = []; + let planStartIndex = -1; + + let currentTool: string | null = null; + let currentInput: string[] = []; + let inJsonBlock = false; + let braceDepth = 0; + + for (let i = 0; i < lines.length; i++) { + const line = lines[i]; + const trimmed = line.trim(); + + // Check if this line starts the actual plan/spec (markdown heading) + // Plans typically start with # or ## headings + if ( + !inJsonBlock && + (trimmed.match(/^#{1,3}\s+\S/) || // Markdown headings (including emoji like ## ✅ Plan) + trimmed.startsWith('---') || // Horizontal rule often used as separator + trimmed.match(/^\*\*\S/)) // Bold text starting a section + ) { + // Flush any active tool call before starting the plan + if (currentTool && currentInput.length > 0) { + toolCalls.push({ + tool: currentTool, + input: currentInput.join('\n').trim(), + }); + currentTool = null; + currentInput = []; + } + planStartIndex = i; + break; + } + + // Detect tool call start (supports tool names with dots/hyphens like web.run, file-read) + const toolMatch = trimmed.match(/^(?:🔧\s*)?Tool:\s*([^\s]+)/i); + if (toolMatch && !inJsonBlock) { + // Save previous tool call if exists + if (currentTool && currentInput.length > 0) { + toolCalls.push({ + tool: currentTool, + input: currentInput.join('\n').trim(), + }); + } + currentTool = toolMatch[1]; + currentInput = []; + continue; + } + + // Detect Input: line + if (trimmed.startsWith('Input:') && currentTool) { + const inputContent = trimmed.replace(/^Input:\s*/, ''); + if (inputContent) { + currentInput.push(inputContent); + // Check if JSON starts + if (inputContent.includes('{')) { + braceDepth = + (inputContent.match(/\{/g) || []).length - (inputContent.match(/\}/g) || []).length; + inJsonBlock = braceDepth > 0; + } + } + continue; + } + + // If we're collecting input for a tool + if (currentTool) { + if (inJsonBlock) { + currentInput.push(line); + braceDepth += (trimmed.match(/\{/g) || []).length - (trimmed.match(/\}/g) || []).length; + if (braceDepth <= 0) { + inJsonBlock = false; + // Save tool call + toolCalls.push({ + tool: currentTool, + input: currentInput.join('\n').trim(), + }); + currentTool = null; + currentInput = []; + } + } else if (trimmed.startsWith('{')) { + // JSON block starting + currentInput.push(line); + braceDepth = (trimmed.match(/\{/g) || []).length - (trimmed.match(/\}/g) || []).length; + inJsonBlock = braceDepth > 0; + if (!inJsonBlock) { + // Single-line JSON + toolCalls.push({ + tool: currentTool, + input: currentInput.join('\n').trim(), + }); + currentTool = null; + currentInput = []; + } + } else if (trimmed === '') { + // Empty line might end the tool call section + if (currentInput.length > 0) { + toolCalls.push({ + tool: currentTool, + input: currentInput.join('\n').trim(), + }); + currentTool = null; + currentInput = []; + } + } + } + } + + // Save any remaining tool call + if (currentTool && currentInput.length > 0) { + toolCalls.push({ + tool: currentTool, + input: currentInput.join('\n').trim(), + }); + } + + // Extract plan markdown + let planMarkdown = ''; + if (planStartIndex >= 0) { + planMarkdown = lines.slice(planStartIndex).join('\n').trim(); + } else if (toolCalls.length === 0) { + // No tool calls found, treat entire content as markdown + planMarkdown = content.trim(); + } + + return { toolCalls, planMarkdown }; +} + +interface PlanContentViewerProps { + content: string; + className?: string; +} + +export function PlanContentViewer({ content, className }: PlanContentViewerProps) { + const [showToolCalls, setShowToolCalls] = useState(false); + + const { toolCalls, planMarkdown } = useMemo(() => parsePlanContent(content), [content]); + + if (!content || !content.trim()) { + return ( +
+ No plan content available. +
+ ); + } + + return ( +
+ {/* Tool Calls Section - Collapsed by default */} + {toolCalls.length > 0 && ( +
+ + + {showToolCalls && ( +
+ {toolCalls.map((tc, idx) => ( +
+
Tool: {tc.tool}
+
+                    {tc.input}
+                  
+
+ ))} +
+ )} +
+ )} + + {/* Plan/Specification Content - Main focus */} + {planMarkdown ? ( +
+ {planMarkdown} +
+ ) : toolCalls.length > 0 ? ( +
+

No specification content found.

+

The plan appears to only contain exploration tool calls.

+
+ ) : null} +
+ ); +} diff --git a/package-lock.json b/package-lock.json index c86ba4aa9..4726a7d51 100644 --- a/package-lock.json +++ b/package-lock.json @@ -150,6 +150,8 @@ "react-markdown": "10.1.0", "react-resizable-panels": "3.0.6", "rehype-raw": "7.0.0", + "rehype-sanitize": "^6.0.0", + "remark-gfm": "^4.0.1", "sonner": "2.0.7", "tailwind-merge": "3.4.0", "usehooks-ts": "3.1.1", @@ -12108,6 +12110,16 @@ "node": "^18.17.0 || >=20.5.0" } }, + "node_modules/markdown-table": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.4.tgz", + "integrity": "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/matcher": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/matcher/-/matcher-3.0.0.tgz", @@ -12131,6 +12143,34 @@ "node": ">= 0.4" } }, + "node_modules/mdast-util-find-and-replace": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.2.tgz", + "integrity": "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "escape-string-regexp": "^5.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-find-and-replace/node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/mdast-util-from-markdown": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.2.tgz", @@ -12155,6 +12195,107 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/mdast-util-gfm": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-3.1.0.tgz", + "integrity": "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==", + "license": "MIT", + "dependencies": { + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-gfm-autolink-literal": "^2.0.0", + "mdast-util-gfm-footnote": "^2.0.0", + "mdast-util-gfm-strikethrough": "^2.0.0", + "mdast-util-gfm-table": "^2.0.0", + "mdast-util-gfm-task-list-item": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-autolink-literal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.1.tgz", + "integrity": "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "ccount": "^2.0.0", + "devlop": "^1.0.0", + "mdast-util-find-and-replace": "^3.0.0", + "micromark-util-character": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-footnote": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-strikethrough": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-2.0.0.tgz", + "integrity": "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-table": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-2.0.0.tgz", + "integrity": "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "markdown-table": "^3.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-task-list-item": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-2.0.0.tgz", + "integrity": "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/mdast-util-mdx-expression": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz", @@ -12374,6 +12515,127 @@ "micromark-util-types": "^2.0.0" } }, + "node_modules/micromark-extension-gfm": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz", + "integrity": "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==", + "license": "MIT", + "dependencies": { + "micromark-extension-gfm-autolink-literal": "^2.0.0", + "micromark-extension-gfm-footnote": "^2.0.0", + "micromark-extension-gfm-strikethrough": "^2.0.0", + "micromark-extension-gfm-table": "^2.0.0", + "micromark-extension-gfm-tagfilter": "^2.0.0", + "micromark-extension-gfm-task-list-item": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-autolink-literal": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz", + "integrity": "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==", + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-footnote": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-strikethrough": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.1.0.tgz", + "integrity": "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-table": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.1.tgz", + "integrity": "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-tagfilter": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-2.0.0.tgz", + "integrity": "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==", + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-task-list-item": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.1.0.tgz", + "integrity": "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/micromark-factory-destination": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", @@ -14162,6 +14424,24 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/remark-gfm": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.1.tgz", + "integrity": "sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-gfm": "^3.0.0", + "micromark-extension-gfm": "^3.0.0", + "remark-parse": "^11.0.0", + "remark-stringify": "^11.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/remark-parse": { "version": "11.0.0", "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz", @@ -14195,6 +14475,21 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/remark-stringify": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-11.0.0.tgz", + "integrity": "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-to-markdown": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",