From c386a1acfab0db99af57a9a18a49c72b89184f15 Mon Sep 17 00:00:00 2001 From: Clawd Date: Sat, 28 Feb 2026 13:48:34 -0800 Subject: Replace custom markdown renderer with react-markdown - Add react-markdown and remark-gfm dependencies - Remove hacky regex-based renderMarkdown function - Full GFM support: tables, task lists, strikethrough, autolinks - Update CSS for react-markdown output (task lists, blockquotes, hr) - Cleaner, more maintainable code --- renderer/src/components/DocumentPane.tsx | 183 ++++++++++--------------------- renderer/src/styles/globals.css | 50 ++++++--- 2 files changed, 93 insertions(+), 140 deletions(-) (limited to 'renderer/src') diff --git a/renderer/src/components/DocumentPane.tsx b/renderer/src/components/DocumentPane.tsx index c2e1b2c..95d7d03 100644 --- a/renderer/src/components/DocumentPane.tsx +++ b/renderer/src/components/DocumentPane.tsx @@ -1,4 +1,6 @@ -import React, { useState, useMemo } from "react"; +import React, { useState } from "react"; +import ReactMarkdown from "react-markdown"; +import remarkGfm from "remark-gfm"; import type { Phase } from "../types"; interface DocumentPaneProps { @@ -9,108 +11,6 @@ interface DocumentPaneProps { showOnboarding?: boolean; } -function renderTable(tableLines: string[]): string { - if (tableLines.length < 2) return tableLines.join("\n"); - - const parseRow = (line: string): string[] => { - return line - .split("|") - .slice(1, -1) // Remove empty first/last from |col|col| - .map((cell) => cell.trim()); - }; - - const headerCells = parseRow(tableLines[0]); - // Skip separator row (index 1) - const bodyRows = tableLines.slice(2); - - let html = ''; - headerCells.forEach((cell) => { - html += ``; - }); - html += ''; - - bodyRows.forEach((row) => { - if (row.trim()) { - html += ''; - parseRow(row).forEach((cell) => { - html += ``; - }); - html += ''; - } - }); - - html += '
${cell}
${cell}
'; - return html; -} - -function renderMarkdown(md: string): string { - // First, handle tables (before other transformations) - const lines = md.split("\n"); - const processedLines: string[] = []; - let tableBuffer: string[] = []; - let inTable = false; - - for (const line of lines) { - const isTableLine = /^\|.*\|$/.test(line.trim()); - - if (isTableLine) { - inTable = true; - tableBuffer.push(line); - } else { - if (inTable && tableBuffer.length > 0) { - processedLines.push(renderTable(tableBuffer)); - tableBuffer = []; - inTable = false; - } - processedLines.push(line); - } - } - - // Handle table at end of content - if (tableBuffer.length > 0) { - processedLines.push(renderTable(tableBuffer)); - } - - let result = processedLines.join("\n"); - - return ( - result - // Headers - .replace(/^### (.*$)/gm, "

$1

") - .replace(/^## (.*$)/gm, "

$1

") - .replace(/^# (.*$)/gm, "

$1

") - // Bold/italic - .replace(/\*\*([^*]+)\*\*/g, "$1") - .replace(/\*([^*]+)\*/g, "$1") - // Code blocks - .replace( - /```(\w*)\n([\s\S]*?)```/g, - '
$2
' - ) - .replace(/`([^`]+)`/g, "$1") - // Lists - .replace(/^- \[x\] (.*$)/gm, '
  • ☑ $1
  • ') - .replace(/^- \[ \] (.*$)/gm, '
  • ☐ $1
  • ') - .replace(/^- (.*$)/gm, "
  • $1
  • ") - // Review comments (highlight them) - .replace(/(\/\/ REVIEW:.*$)/gm, '$1') - .replace(/(\/\/ NOTE:.*$)/gm, '$1') - // Paragraphs - .replace(/\n\n/g, "

    ") - .replace(/^(.+)$/gm, "

    $1

    ") - // Clean up - .replace(/

    <\/p>/g, "") - .replace(/

    ()/g, "$1") - .replace(/(<\/h[1-3]>)<\/p>/g, "$1") - .replace(/

    (

    )/g, "$1")
    -      .replace(/(<\/pre>)<\/p>/g, "$1")
    -      .replace(/

    ()<\/p>/g, "$1") - .replace(/

    ()/g, "$1") - .replace(/(<\/table>)<\/p>/g, "$1") - ); -} - export function DocumentPane({ content, onChange, @@ -119,7 +19,6 @@ export function DocumentPane({ showOnboarding, }: DocumentPaneProps) { const [isEditing, setIsEditing] = useState(false); - const renderedHtml = useMemo(() => renderMarkdown(content), [content]); if (showOnboarding) { return ( @@ -130,27 +29,61 @@ export function DocumentPane({

    Claude Flow

    - A structured workflow for AI-assisted coding: Research → Plan → Implement. + A structured workflow for AI-assisted coding:{" "} + Research → Plan → Implement.

    Setup

    Export your Anthropic API key:

    -
    export ANTHROPIC_API_KEY=your-key-here
    -

    Get one at platform.claude.com

    +
    +            export ANTHROPIC_API_KEY=your-key-here
    +          
    +

    + Get one at{" "} + + platform.claude.com + +

    Getting Started

      -
    1. Add a project — Select a codebase folder
    2. -
    3. Create a session — Start a new task
    4. -
    5. Describe your work — Tell Claude what you want to build
    6. +
    7. + Add a project — Select a codebase folder +
    8. +
    9. + Create a session — Start a new task +
    10. +
    11. + Describe your work — Tell Claude what you want to + build +

    Workflow

    -

    Research: Claude analyzes your codebase and writes findings to research.md. Add notes like // REVIEW: check this — click Review when done.

    -

    Plan: Claude drafts an implementation plan in plan.md with code snippets and a TODO list. Iterate the same way.

    -

    Implement: Claude executes the plan, marking tasks complete as it goes.

    +

    + Research: Claude analyzes your codebase and writes + findings to research.md. Add notes like{" "} + // REVIEW: check this — click Review{" "} + when done. +

    +

    + Plan: Claude drafts an implementation plan in{" "} + plan.md with code snippets and a TODO list. Iterate the + same way. +

    +

    + Implement: Claude executes the plan, marking tasks + complete as it goes. +

    -

    Iterate on research and plan docs as long as you want. Click Submit when happy to move to the next phase.

    +

    + Iterate on research and plan docs as long as you want. Click{" "} + Submit when happy to move to the next phase. +

    ); @@ -163,10 +96,9 @@ export function DocumentPane({ plan.mdImplementing... -
    +
    + {content} +
    ); } @@ -193,13 +125,16 @@ export function DocumentPane({ ) : (
    Document will appear here after Claude generates it...

    ', - }} onClick={() => !disabled && setIsEditing(true)} - /> + > + {content ? ( + {content} + ) : ( +

    + Document will appear here after Claude generates it... +

    + )} +
    )} ); diff --git a/renderer/src/styles/globals.css b/renderer/src/styles/globals.css index 380a744..384ebc5 100644 --- a/renderer/src/styles/globals.css +++ b/renderer/src/styles/globals.css @@ -171,12 +171,14 @@ body { } .document-content.rendered p { margin: 8px 0; + line-height: 1.6; } .document-content.rendered code { background: var(--bg-tertiary); padding: 2px 6px; border-radius: 4px; font-size: 13px; + font-family: "SF Mono", Monaco, "Cascadia Code", monospace; } .document-content.rendered pre { background: var(--bg-tertiary); @@ -189,16 +191,26 @@ body { background: none; padding: 0; } +.document-content.rendered ul, +.document-content.rendered ol { + margin: 12px 0; + padding-left: 24px; +} .document-content.rendered li { - margin-left: 24px; - margin-bottom: 4px; + margin-bottom: 6px; + line-height: 1.5; } -.document-content.rendered li.task { +.document-content.rendered ul.contains-task-list { list-style: none; - margin-left: 0; + padding-left: 0; } -.document-content.rendered li.task.done { - color: var(--success); +.document-content.rendered li.task-list-item { + display: flex; + align-items: flex-start; + gap: 8px; +} +.document-content.rendered li.task-list-item input[type="checkbox"] { + margin-top: 4px; } .document-content.rendered table { width: 100%; @@ -219,17 +231,23 @@ body { .document-content.rendered tr:nth-child(even) td { background: var(--bg-secondary); } -.document-content.rendered mark.review { - background: var(--warning); - color: black; - padding: 2px 4px; - border-radius: 2px; +.document-content.rendered blockquote { + border-left: 3px solid var(--accent); + margin: 16px 0; + padding-left: 16px; + color: var(--text-secondary); } -.document-content.rendered mark.note { - background: var(--accent); - color: white; - padding: 2px 4px; - border-radius: 2px; +.document-content.rendered hr { + border: none; + border-top: 1px solid var(--border); + margin: 24px 0; +} +.document-content.rendered a { + color: var(--accent); + text-decoration: none; +} +.document-content.rendered a:hover { + text-decoration: underline; } .document-content.rendered .empty { color: var(--text-secondary); -- cgit v1.2.3