aboutsummaryrefslogtreecommitdiffstats
path: root/renderer/src/components
diff options
context:
space:
mode:
Diffstat (limited to 'renderer/src/components')
-rw-r--r--renderer/src/components/DocumentPane.tsx183
1 files changed, 59 insertions, 124 deletions
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 @@
1import React, { useState, useMemo } from "react"; 1import React, { useState } from "react";
2import ReactMarkdown from "react-markdown";
3import remarkGfm from "remark-gfm";
2import type { Phase } from "../types"; 4import type { Phase } from "../types";
3 5
4interface DocumentPaneProps { 6interface DocumentPaneProps {
@@ -9,108 +11,6 @@ interface DocumentPaneProps {
9 showOnboarding?: boolean; 11 showOnboarding?: boolean;
10} 12}
11 13
12function renderTable(tableLines: string[]): string {
13 if (tableLines.length < 2) return tableLines.join("\n");
14
15 const parseRow = (line: string): string[] => {
16 return line
17 .split("|")
18 .slice(1, -1) // Remove empty first/last from |col|col|
19 .map((cell) => cell.trim());
20 };
21
22 const headerCells = parseRow(tableLines[0]);
23 // Skip separator row (index 1)
24 const bodyRows = tableLines.slice(2);
25
26 let html = '<table><thead><tr>';
27 headerCells.forEach((cell) => {
28 html += `<th>${cell}</th>`;
29 });
30 html += '</tr></thead><tbody>';
31
32 bodyRows.forEach((row) => {
33 if (row.trim()) {
34 html += '<tr>';
35 parseRow(row).forEach((cell) => {
36 html += `<td>${cell}</td>`;
37 });
38 html += '</tr>';
39 }
40 });
41
42 html += '</tbody></table>';
43 return html;
44}
45
46function renderMarkdown(md: string): string {
47 // First, handle tables (before other transformations)
48 const lines = md.split("\n");
49 const processedLines: string[] = [];
50 let tableBuffer: string[] = [];
51 let inTable = false;
52
53 for (const line of lines) {
54 const isTableLine = /^\|.*\|$/.test(line.trim());
55
56 if (isTableLine) {
57 inTable = true;
58 tableBuffer.push(line);
59 } else {
60 if (inTable && tableBuffer.length > 0) {
61 processedLines.push(renderTable(tableBuffer));
62 tableBuffer = [];
63 inTable = false;
64 }
65 processedLines.push(line);
66 }
67 }
68
69 // Handle table at end of content
70 if (tableBuffer.length > 0) {
71 processedLines.push(renderTable(tableBuffer));
72 }
73
74 let result = processedLines.join("\n");
75
76 return (
77 result
78 // Headers
79 .replace(/^### (.*$)/gm, "<h3>$1</h3>")
80 .replace(/^## (.*$)/gm, "<h2>$1</h2>")
81 .replace(/^# (.*$)/gm, "<h1>$1</h1>")
82 // Bold/italic
83 .replace(/\*\*([^*]+)\*\*/g, "<strong>$1</strong>")
84 .replace(/\*([^*]+)\*/g, "<em>$1</em>")
85 // Code blocks
86 .replace(
87 /```(\w*)\n([\s\S]*?)```/g,
88 '<pre><code class="language-$1">$2</code></pre>'
89 )
90 .replace(/`([^`]+)`/g, "<code>$1</code>")
91 // Lists
92 .replace(/^- \[x\] (.*$)/gm, '<li class="task done">☑ $1</li>')
93 .replace(/^- \[ \] (.*$)/gm, '<li class="task">☐ $1</li>')
94 .replace(/^- (.*$)/gm, "<li>$1</li>")
95 // Review comments (highlight them)
96 .replace(/(\/\/ REVIEW:.*$)/gm, '<mark class="review">$1</mark>')
97 .replace(/(\/\/ NOTE:.*$)/gm, '<mark class="note">$1</mark>')
98 // Paragraphs
99 .replace(/\n\n/g, "</p><p>")
100 .replace(/^(.+)$/gm, "<p>$1</p>")
101 // Clean up
102 .replace(/<p><\/p>/g, "")
103 .replace(/<p>(<h[1-3]>)/g, "$1")
104 .replace(/(<\/h[1-3]>)<\/p>/g, "$1")
105 .replace(/<p>(<pre>)/g, "$1")
106 .replace(/(<\/pre>)<\/p>/g, "$1")
107 .replace(/<p>(<li)/g, "$1")
108 .replace(/(<\/li>)<\/p>/g, "$1")
109 .replace(/<p>(<table>)/g, "$1")
110 .replace(/(<\/table>)<\/p>/g, "$1")
111 );
112}
113
114export function DocumentPane({ 14export function DocumentPane({
115 content, 15 content,
116 onChange, 16 onChange,
@@ -119,7 +19,6 @@ export function DocumentPane({
119 showOnboarding, 19 showOnboarding,
120}: DocumentPaneProps) { 20}: DocumentPaneProps) {
121 const [isEditing, setIsEditing] = useState(false); 21 const [isEditing, setIsEditing] = useState(false);
122 const renderedHtml = useMemo(() => renderMarkdown(content), [content]);
123 22
124 if (showOnboarding) { 23 if (showOnboarding) {
125 return ( 24 return (
@@ -130,27 +29,61 @@ export function DocumentPane({
130 <div className="document-content rendered onboarding"> 29 <div className="document-content rendered onboarding">
131 <h1>Claude Flow</h1> 30 <h1>Claude Flow</h1>
132 <p> 31 <p>
133 A structured workflow for AI-assisted coding: <strong>Research → Plan → Implement</strong>. 32 A structured workflow for AI-assisted coding:{" "}
33 <strong>Research → Plan → Implement</strong>.
134 </p> 34 </p>
135 35
136 <h2>Setup</h2> 36 <h2>Setup</h2>
137 <p>Export your Anthropic API key:</p> 37 <p>Export your Anthropic API key:</p>
138 <pre><code>export ANTHROPIC_API_KEY=your-key-here</code></pre> 38 <pre>
139 <p>Get one at <a href="https://platform.claude.com" target="_blank" rel="noopener">platform.claude.com</a></p> 39 <code>export ANTHROPIC_API_KEY=your-key-here</code>
40 </pre>
41 <p>
42 Get one at{" "}
43 <a
44 href="https://platform.claude.com"
45 target="_blank"
46 rel="noopener"
47 >
48 platform.claude.com
49 </a>
50 </p>
140 51
141 <h2>Getting Started</h2> 52 <h2>Getting Started</h2>
142 <ol> 53 <ol>
143 <li><strong>Add a project</strong> — Select a codebase folder</li> 54 <li>
144 <li><strong>Create a session</strong> — Start a new task</li> 55 <strong>Add a project</strong> — Select a codebase folder
145 <li><strong>Describe your work</strong> — Tell Claude what you want to build</li> 56 </li>
57 <li>
58 <strong>Create a session</strong> — Start a new task
59 </li>
60 <li>
61 <strong>Describe your work</strong> — Tell Claude what you want to
62 build
63 </li>
146 </ol> 64 </ol>
147 65
148 <h2>Workflow</h2> 66 <h2>Workflow</h2>
149 <p><strong>Research:</strong> Claude analyzes your codebase and writes findings to <code>research.md</code>. Add notes like <code>// REVIEW: check this</code> — click <strong>Review</strong> when done.</p> 67 <p>
150 <p><strong>Plan:</strong> Claude drafts an implementation plan in <code>plan.md</code> with code snippets and a TODO list. Iterate the same way.</p> 68 <strong>Research:</strong> Claude analyzes your codebase and writes
151 <p><strong>Implement:</strong> Claude executes the plan, marking tasks complete as it goes.</p> 69 findings to <code>research.md</code>. Add notes like{" "}
70 <code>// REVIEW: check this</code> — click <strong>Review</strong>{" "}
71 when done.
72 </p>
73 <p>
74 <strong>Plan:</strong> Claude drafts an implementation plan in{" "}
75 <code>plan.md</code> with code snippets and a TODO list. Iterate the
76 same way.
77 </p>
78 <p>
79 <strong>Implement:</strong> Claude executes the plan, marking tasks
80 complete as it goes.
81 </p>
152 82
153 <p className="onboarding-tip">Iterate on research and plan docs as long as you want. Click <strong>Submit</strong> when happy to move to the next phase.</p> 83 <p className="onboarding-tip">
84 Iterate on research and plan docs as long as you want. Click{" "}
85 <strong>Submit</strong> when happy to move to the next phase.
86 </p>
154 </div> 87 </div>
155 </div> 88 </div>
156 ); 89 );
@@ -163,10 +96,9 @@ export function DocumentPane({
163 <span>plan.md</span> 96 <span>plan.md</span>
164 <span className="badge">Implementing...</span> 97 <span className="badge">Implementing...</span>
165 </div> 98 </div>
166 <div 99 <div className="document-content rendered">
167 className="document-content rendered" 100 <ReactMarkdown remarkPlugins={[remarkGfm]}>{content}</ReactMarkdown>
168 dangerouslySetInnerHTML={{ __html: renderedHtml }} 101 </div>
169 />
170 </div> 102 </div>
171 ); 103 );
172 } 104 }
@@ -193,13 +125,16 @@ export function DocumentPane({
193 ) : ( 125 ) : (
194 <div 126 <div
195 className="document-content rendered" 127 className="document-content rendered"
196 dangerouslySetInnerHTML={{
197 __html:
198 renderedHtml ||
199 '<p class="empty">Document will appear here after Claude generates it...</p>',
200 }}
201 onClick={() => !disabled && setIsEditing(true)} 128 onClick={() => !disabled && setIsEditing(true)}
202 /> 129 >
130 {content ? (
131 <ReactMarkdown remarkPlugins={[remarkGfm]}>{content}</ReactMarkdown>
132 ) : (
133 <p className="empty">
134 Document will appear here after Claude generates it...
135 </p>
136 )}
137 </div>
203 )} 138 )}
204 </div> 139 </div>
205 ); 140 );