aboutsummaryrefslogtreecommitdiffstats
path: root/renderer/src/components/DocumentPane.tsx
diff options
context:
space:
mode:
authorClawd <ai@clawd.bot>2026-02-28 07:30:40 -0800
committerClawd <ai@clawd.bot>2026-02-28 07:30:40 -0800
commit6d70c5f8a3ed90564b08616a3fb041409916059c (patch)
tree4c6480c5a606ab0a178dca72efa93c60ebe70c34 /renderer/src/components/DocumentPane.tsx
parent66f66d1c17213f55aa56d69c0cccc309b16f3362 (diff)
Phase 4: React UI
- Add renderer/src/types.ts with Project, Session, Message, Phase types - Add renderer/src/styles/globals.css with full styling - Dark theme with accent colors - Header, document pane, chat pane, action bar layouts - Phase indicator, token usage bar, buttons - Add renderer/src/components/Header.tsx - Project/session dropdowns with create buttons - Phase indicator showing current workflow state - Add renderer/src/components/DocumentPane.tsx - Markdown viewer/editor with toggle - Syntax highlighting for review comments - Task checkbox rendering - Add renderer/src/components/ChatPane.tsx - Message list with auto-scroll - Input field with Enter to send - Loading state indicator - Add renderer/src/components/ActionBar.tsx - Token usage bar with color coding - Review/Submit buttons for workflow - Permission mode toggle for implement phase - Add renderer/src/App.tsx - Full state management for projects, sessions, messages - Claude message subscription - Workflow handlers (review, submit, phase advance) - Update renderer/src/main.tsx to render App
Diffstat (limited to 'renderer/src/components/DocumentPane.tsx')
-rw-r--r--renderer/src/components/DocumentPane.tsx104
1 files changed, 104 insertions, 0 deletions
diff --git a/renderer/src/components/DocumentPane.tsx b/renderer/src/components/DocumentPane.tsx
new file mode 100644
index 0000000..e44b0fb
--- /dev/null
+++ b/renderer/src/components/DocumentPane.tsx
@@ -0,0 +1,104 @@
1import React, { useState, useMemo } from "react";
2import type { Phase } from "../types";
3
4interface DocumentPaneProps {
5 content: string;
6 onChange: (content: string) => void;
7 phase: Phase;
8 disabled: boolean;
9}
10
11function renderMarkdown(md: string): string {
12 return (
13 md
14 // Headers
15 .replace(/^### (.*$)/gm, "<h3>$1</h3>")
16 .replace(/^## (.*$)/gm, "<h2>$1</h2>")
17 .replace(/^# (.*$)/gm, "<h1>$1</h1>")
18 // Bold/italic
19 .replace(/\*\*([^*]+)\*\*/g, "<strong>$1</strong>")
20 .replace(/\*([^*]+)\*/g, "<em>$1</em>")
21 // Code blocks
22 .replace(
23 /```(\w*)\n([\s\S]*?)```/g,
24 '<pre><code class="language-$1">$2</code></pre>'
25 )
26 .replace(/`([^`]+)`/g, "<code>$1</code>")
27 // Lists
28 .replace(/^- \[x\] (.*$)/gm, '<li class="task done">☑ $1</li>')
29 .replace(/^- \[ \] (.*$)/gm, '<li class="task">☐ $1</li>')
30 .replace(/^- (.*$)/gm, "<li>$1</li>")
31 // Review comments (highlight them)
32 .replace(/(\/\/ REVIEW:.*$)/gm, '<mark class="review">$1</mark>')
33 .replace(/(\/\/ NOTE:.*$)/gm, '<mark class="note">$1</mark>')
34 // Paragraphs
35 .replace(/\n\n/g, "</p><p>")
36 .replace(/^(.+)$/gm, "<p>$1</p>")
37 // Clean up
38 .replace(/<p><\/p>/g, "")
39 .replace(/<p>(<h[1-3]>)/g, "$1")
40 .replace(/(<\/h[1-3]>)<\/p>/g, "$1")
41 .replace(/<p>(<pre>)/g, "$1")
42 .replace(/(<\/pre>)<\/p>/g, "$1")
43 .replace(/<p>(<li)/g, "$1")
44 .replace(/(<\/li>)<\/p>/g, "$1")
45 );
46}
47
48export function DocumentPane({
49 content,
50 onChange,
51 phase,
52 disabled,
53}: DocumentPaneProps) {
54 const [isEditing, setIsEditing] = useState(false);
55 const renderedHtml = useMemo(() => renderMarkdown(content), [content]);
56
57 if (phase === "implement") {
58 return (
59 <div className="document-pane">
60 <div className="document-header">
61 <span>plan.md</span>
62 <span className="badge">Implementing...</span>
63 </div>
64 <div
65 className="document-content rendered"
66 dangerouslySetInnerHTML={{ __html: renderedHtml }}
67 />
68 </div>
69 );
70 }
71
72 const filename = phase === "research" ? "research.md" : "plan.md";
73
74 return (
75 <div className="document-pane">
76 <div className="document-header">
77 <span>{filename}</span>
78 <button onClick={() => setIsEditing(!isEditing)}>
79 {isEditing ? "Preview" : "Edit"}
80 </button>
81 </div>
82
83 {isEditing ? (
84 <textarea
85 className="document-content editing"
86 value={content}
87 onChange={(e) => onChange(e.target.value)}
88 disabled={disabled}
89 placeholder={`${filename} will appear here...`}
90 />
91 ) : (
92 <div
93 className="document-content rendered"
94 dangerouslySetInnerHTML={{
95 __html:
96 renderedHtml ||
97 '<p class="empty">Document will appear here after Claude generates it...</p>',
98 }}
99 onClick={() => !disabled && setIsEditing(true)}
100 />
101 )}
102 </div>
103 );
104}