webui: Add Mermaid diagram support to markdown

* Installed mermaid library
* Extended markdown renderer to support mermaid code blocks
* Added CSS styling for mermaid diagrams
* Added a demo page for testing mermaid diagrams

Co-Authored-By: sketch <hello@sketch.dev>
diff --git a/webui/src/web-components/demo/mermaid-test/index.html b/webui/src/web-components/demo/mermaid-test/index.html
new file mode 100644
index 0000000..e2fd202
--- /dev/null
+++ b/webui/src/web-components/demo/mermaid-test/index.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="UTF-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1.0">
+  <title>Mermaid Diagram Test</title>
+  <script type="module" src="./mermaid-test.ts"></script>
+  <style>
+    body {
+      font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
+      padding: 20px;
+      max-width: 1000px;
+      margin: 0 auto;
+    }
+    h1 {
+      color: #333;
+    }
+    .container {
+      border: 1px solid #ddd;
+      border-radius: 4px;
+      padding: 20px;
+      margin: 20px 0;
+    }
+  </style>
+</head>
+<body>
+  <h1>Mermaid Diagram Testing</h1>
+  <p>This page tests the integration of Mermaid diagrams in the markdown renderer.</p>
+  
+  <div class="container">
+    <mermaid-test-component></mermaid-test-component>
+  </div>
+</body>
+</html>
\ No newline at end of file
diff --git a/webui/src/web-components/demo/mermaid-test/mermaid-test.ts b/webui/src/web-components/demo/mermaid-test/mermaid-test.ts
new file mode 100644
index 0000000..7d8e04f
--- /dev/null
+++ b/webui/src/web-components/demo/mermaid-test/mermaid-test.ts
@@ -0,0 +1,136 @@
+import { html, css, LitElement } from 'lit';
+import { customElement } from 'lit/decorators.js';
+import '../../sketch-timeline-message.ts';
+// Using simple objects matching the AgentMessage interface
+
+@customElement('mermaid-test-component')
+export class MermaidTestComponent extends LitElement {
+  static styles = css`
+    :host {
+      display: block;
+    }
+    .test-section {
+      margin-bottom: 30px;
+    }
+    h2 {
+      margin-top: 0;
+      color: #444;
+    }
+  `;
+
+  render() {
+    // Create a sample message with Mermaid diagrams
+    const flowchartMessage = {
+      id: 'test-1',
+      type: 'agent',
+      content: `# Mermaid Flowchart Example
+
+This is a test of a flowchart diagram in Mermaid syntax:
+
+\`\`\`mermaid
+graph TD
+    A[Start] --> B{Is it working?}
+    B -->|Yes| C[Great!]
+    B -->|No| D[Try again]
+    C --> E[Continue]
+    D --> B
+\`\`\`
+
+The above should render as a Mermaid diagram.`,
+      timestamp: new Date().toISOString(),
+    };
+
+    const sequenceDiagramMessage = {
+      id: 'test-2',
+      type: 'agent',
+      content: `# Mermaid Sequence Diagram Example
+
+Here's a sequence diagram showing a typical HTTP request:
+
+\`\`\`mermaid
+sequenceDiagram
+    participant Browser
+    participant Server
+    Browser->>Server: GET /index.html
+    Server-->>Browser: HTML Response
+    Browser->>Server: GET /style.css
+    Server-->>Browser: CSS Response
+    Browser->>Server: GET /script.js
+    Server-->>Browser: JS Response
+\`\`\`
+
+Complex diagrams should render properly.`,
+      timestamp: new Date().toISOString(),
+    };
+
+    const classDiagramMessage = {
+      id: 'test-3',
+      type: 'agent',
+      content: `# Mermaid Class Diagram Example
+
+A simple class diagram in Mermaid:
+
+\`\`\`mermaid
+classDiagram
+    class Animal {
+        +string name
+        +makeSound() void
+    }
+    class Dog {
+        +bark() void
+    }
+    class Cat {
+        +meow() void
+    }
+    Animal <|-- Dog
+    Animal <|-- Cat
+\`\`\`
+
+This represents a basic inheritance diagram.`,
+      timestamp: new Date().toISOString(),
+    };
+
+    const normalMarkdownMessage = {
+      id: 'test-4',
+      type: 'agent',
+      content: `# Regular Markdown
+
+This is regular markdown with:
+
+- A bullet list
+- **Bold text**
+- *Italic text*
+
+\`\`\`javascript
+// Regular code block
+const x = 10;
+console.log('This is not Mermaid');
+\`\`\`
+
+Regular markdown should continue to work properly.`,
+      timestamp: new Date().toISOString(),
+    };
+
+    return html`
+      <div class="test-section">
+        <h2>Flowchart Diagram Test</h2>
+        <sketch-timeline-message .message=${flowchartMessage}></sketch-timeline-message>
+      </div>
+      
+      <div class="test-section">
+        <h2>Sequence Diagram Test</h2>
+        <sketch-timeline-message .message=${sequenceDiagramMessage}></sketch-timeline-message>
+      </div>
+
+      <div class="test-section">
+        <h2>Class Diagram Test</h2>
+        <sketch-timeline-message .message=${classDiagramMessage}></sketch-timeline-message>
+      </div>
+
+      <div class="test-section">
+        <h2>Normal Markdown Test</h2>
+        <sketch-timeline-message .message=${normalMarkdownMessage}></sketch-timeline-message>
+      </div>
+    `;
+  }
+}
diff --git a/webui/src/web-components/demo/sketch-timeline-message.demo.html b/webui/src/web-components/demo/sketch-timeline-message.demo.html
index 3c5d77e..add83a1 100644
--- a/webui/src/web-components/demo/sketch-timeline-message.demo.html
+++ b/webui/src/web-components/demo/sketch-timeline-message.demo.html
@@ -19,6 +19,21 @@
           content: "a tool use message",
         },
         {
+          type:"agent",
+          content: `Mermaid Sequence Diagram Example
+\`\`\`mermaid
+sequenceDiagram
+    participant Browser
+    participant Server
+    Browser->>Server: GET /index.html
+    Server-->>Browser: HTML Response
+    Browser->>Server: GET /style.css
+    Server-->>Browser: CSS Response
+    Browser->>Server: GET /script.js
+    Server-->>Browser: JS Response
+\`\`\``,
+        },
+        {
           type: "commit",
           end_of_turn: false,
           content: "",
diff --git a/webui/src/web-components/sketch-timeline-message.ts b/webui/src/web-components/sketch-timeline-message.ts
index 0e16c3f..b9c1455 100644
--- a/webui/src/web-components/sketch-timeline-message.ts
+++ b/webui/src/web-components/sketch-timeline-message.ts
@@ -2,7 +2,8 @@
 import { unsafeHTML } from "lit/directives/unsafe-html.js";
 import { customElement, property } from "lit/decorators.js";
 import { AgentMessage } from "../types";
-import { marked, MarkedOptions } from "marked";
+import { marked, MarkedOptions, Renderer, Tokens } from "marked";
+import mermaid from "mermaid";
 import "./sketch-tool-calls";
 @customElement("sketch-timeline-message")
 export class SketchTimelineMessage extends LitElement {
@@ -417,16 +418,82 @@
       margin-block-start: 0.5em;
       margin-block-end: 0.5em;
     }
+    
+    /* Mermaid diagram styling */
+    .mermaid-container {
+      margin: 1em 0;
+      padding: 0.5em;
+      background-color: #f8f8f8;
+      border-radius: 4px;
+      overflow-x: auto;
+    }
+    
+    .mermaid {
+      text-align: center;
+    }
   `;
 
+  // Track mermaid diagrams that need rendering
+  private mermaidDiagrams = new Map();
+
   constructor() {
     super();
+    // Initialize mermaid with specific config
+    mermaid.initialize({
+      startOnLoad: false,
+      theme: 'default',
+      securityLevel: 'loose', // Allows more flexibility but be careful with user-generated content
+      fontFamily: 'monospace'
+    });
   }
 
   // See https://lit.dev/docs/components/lifecycle/
   connectedCallback() {
     super.connectedCallback();
   }
+  
+  // After the component is updated and rendered, render any mermaid diagrams
+  updated(changedProperties: Map<string, unknown>) {
+    super.updated(changedProperties);
+    this.renderMermaidDiagrams();
+  }
+  
+  // Render mermaid diagrams after the component is updated
+  renderMermaidDiagrams() {
+    // Add a small delay to ensure the DOM is fully rendered
+    setTimeout(() => {
+      // Find all mermaid containers in our shadow root
+      const containers = this.shadowRoot?.querySelectorAll('.mermaid');
+      if (!containers || containers.length === 0) return;
+      
+      // Process each mermaid diagram
+      containers.forEach(container => {
+        const id = container.id;
+        const code = container.textContent || '';
+        if (!code || !id) return; // Use return for forEach instead of continue
+        
+        try {
+          // Clear any previous content
+          container.innerHTML = code;
+          
+          // Render the mermaid diagram using promise
+          mermaid.render(`${id}-svg`, code)
+            .then(({ svg }) => {
+              container.innerHTML = svg;
+            })
+            .catch(err => {
+              console.error('Error rendering mermaid diagram:', err);
+              // Show the original code as fallback
+              container.innerHTML = `<pre>${code}</pre>`;
+            });
+        } catch (err) {
+          console.error('Error processing mermaid diagram:', err);
+          // Show the original code as fallback
+          container.innerHTML = `<pre>${code}</pre>`;
+        }
+      });
+    }, 100); // Small delay to ensure DOM is ready
+  }
 
   // See https://lit.dev/docs/components/lifecycle/
   disconnectedCallback() {
@@ -435,11 +502,31 @@
 
   renderMarkdown(markdownContent: string): string {
     try {
+      // Create a custom renderer
+      const renderer = new Renderer();
+      const originalCodeRenderer = renderer.code.bind(renderer);
+      
+      // Override the code renderer to handle mermaid diagrams
+      renderer.code = function({ text, lang, escaped }: Tokens.Code): string {
+        if (lang === 'mermaid') {
+          // Generate a unique ID for this diagram
+          const id = `mermaid-diagram-${Math.random().toString(36).substring(2, 10)}`;
+          
+          // Just create the container and mermaid div - we'll render it in the updated() lifecycle method
+          return `<div class="mermaid-container">
+                   <div class="mermaid" id="${id}">${text}</div>
+                 </div>`;
+        }
+        // Default rendering for other code blocks
+        return originalCodeRenderer({ text, lang, escaped });
+      };
+      
       // Set markdown options for proper code block highlighting and safety
       const markedOptions: MarkedOptions = {
         gfm: true, // GitHub Flavored Markdown
         breaks: true, // Convert newlines to <br>
         async: false,
+        renderer: renderer
         // DOMPurify is recommended for production, but not included in this implementation
       };
       return marked.parse(markdownContent, markedOptions) as string;