diff --git a/src/mindroot/coreplugins/chat/static/js/chat.js b/src/mindroot/coreplugins/chat/static/js/chat.js
index 0f2ce57..53e28b9 100644
--- a/src/mindroot/coreplugins/chat/static/js/chat.js
+++ b/src/mindroot/coreplugins/chat/static/js/chat.js
@@ -34,7 +34,8 @@ function debounce(func, delay) {
   };
 }
 
-// Create a throttled parser that limits parsing frequency but doesn't skip updates
+// Single place to perform a safe markdown parse and update the message
+function updateParsedMessage(markdown, messageIndex, chatInstance) {
 const throttledParse = throttle((markdown, messageIndex, chatInstance) => {
   try {
     const result = markdownRenderer.parse(markdown);
@@ -46,32 +47,44 @@ const throttledParse = throttle((markdown, messageIndex, chatInstance) => {
     }
   } catch (e) {
     console.error("Could not parse markdown:", e);
+    console.error(\"Could not parse markdown:\", e);
     if (chatInstance.messages[messageIndex]) {
       chatInstance.messages[messageIndex].content = `<pre><code>${markdown}</code></pre>`;
       chatInstance.requestUpdate();
     }
   }
-}, 100); // Throttle to max 10 updates per second
-
-function tryParse(markdown, useThrottle = false, messageIndex = null, chatInstance = null) {
-  if (!useThrottle || !chatInstance || messageIndex === null) {
-    // Immediate parsing for cases where we don't need throttling
-    try {
-      return markdownRenderer.parse(markdown);
-    } catch (e) {
-      console.error("Could not parse markdown:", e);
-      return `<pre><code>${markdown}</code></pre>`;
+}, 350); // Throttle to max 10 updates per second
+
+// Debounced parser to ensure we always flush the last update after a quiet period
+const debouncedParse = debounce((markdown, messageIndex, chatInstance) => {
+  try {
+    const result = markdownRenderer.parse(markdown);
+    if (chatInstance.messages[messageIndex]) {
+      chatInstance.messages[messageIndex].content = result;
+      chatInstance.requestUpdate();
+      chatInstance._scrollToBottom();
+      if (typeof window.initializeCodeCopyButtons === 'function') {
+        try { window.initializeCodeCopyButtons(); } catch (_) {}
+      }
     }
-  } else {
-    // Throttled parsing - will update the message in place
-    throttledParse(markdown, messageIndex, chatInstance);
-    // Return current content immediately (might be partially parsed)
-    try {
-      return markdownRenderer.parse(markdown);
-    } catch (e) {
-      return `<pre><code>${markdown}</code></pre>`;
+  } catch (e) {
+    console.error("Could not parse markdown:", e);
+    if (chatInstance.messages[messageIndex]) {
+      chatInstance.messages[messageIndex].content = `<pre><code>${markdown}</code></pre>`;
+      chatInstance.requestUpdate();
     }
   }
+}, 150);
+}
+
+function tryParse(markdown) {
+  // Immediate parse for non-streaming contexts
+  try {
+    return markdownRenderer.parse(markdown);
+  } catch (e) {
+    console.error("Could not parse markdown:", e);
+    return `<pre><code>${markdown}</code></pre>`;
+  }
 }
 
 class Chat extends BaseEl {
@@ -103,6 +116,12 @@ class Chat extends BaseEl {
     console.log('Chat component created');
     this.history = new ChatHistory(this);
     console.log(this);
+
+    // Per-message streaming schedulers (index -> state)
+    this._streamSchedulers = new Map();
+    this._lastCopyInit = 0;
+    // Throttle scroll to reduce layout thrash during streaming
+    this._scrollToBottomThrottled = throttle(() => this._scrollToBottom(), 150);
   }
   
   exposeSubcomponents() {
@@ -178,6 +197,82 @@ class Chat extends BaseEl {
     }, 100);
   }
 
+  // Hybrid scheduler: leading throttle + trailing debounce + guaranteed final flush
+  _ensureScheduler(messageIndex) {
+    if (!this._streamSchedulers.has(messageIndex)) {
+      this._streamSchedulers.set(messageIndex, {
+        lastText: '',
+        lastRun: 0,
+        leadingTimer: null,
+        trailingTimer: null,
+        running: false
+      });
+    }
+    return this._streamSchedulers.get(messageIndex);
+  }
+
+  _scheduleMarkdownUpdate(messageIndex, text, { final = false } = {}) {
+    const MIN_INTERVAL = 150;   // ms between heavy parses while streaming
+    const QUIET_DEBOUNCE = 120; // flush after quiet period
+    const state = this._ensureScheduler(messageIndex);
+    state.lastText = text ?? '';
+
+    const parseNow = (isFinal) => {
+      if (!this.messages[messageIndex]) return;
+      // Avoid overlapping parses
+      if (state.running) return;
+      state.running = true;
+      try {
+        const result = markdownRenderer.parse(state.lastText);
+        this.messages[messageIndex].content = result;
+      } catch (e) {
+        console.error('Could not parse markdown:', e);
+        this.messages[messageIndex].content = `<pre><code>${state.lastText}</code></pre>`;
+      } finally {
+        state.lastRun = Date.now();
+        state.running = false;
+      }
+      // Only initialize copy buttons occasionally or on final
+      const now = Date.now();
+      if (isFinal || now - this._lastCopyInit > 1000) {
+        if (typeof window.initializeCodeCopyButtons === 'function') {
+          try { window.initializeCodeCopyButtons(); } catch (_) {}
+        }
+        this._lastCopyInit = now;
+      }
+      this.requestUpdate();
+      this._scrollToBottomThrottled();
+    };
+
+    // Final flush: cancel timers and render immediately
+    if (final) {
+      if (state.leadingTimer) clearTimeout(state.leadingTimer);
+      if (state.trailingTimer) clearTimeout(state.trailingTimer);
+      state.leadingTimer = null;
+      state.trailingTimer = null;
+      parseNow(true);
+      return;
+    }
+
+    const now = Date.now();
+    const elapsed = now - state.lastRun;
+
+    // Leading throttle: if enough time has passed, schedule a near-immediate parse
+    if (elapsed >= MIN_INTERVAL && !state.leadingTimer) {
+      state.leadingTimer = setTimeout(() => {
+        state.leadingTimer = null;
+        parseNow(false);
+      }, 0);
+    }
+
+    // Trailing debounce: always ensure a quiet-period flush happens
+    if (state.trailingTimer) clearTimeout(state.trailingTimer);
+    state.trailingTimer = setTimeout(() => {
+      state.trailingTimer = null;
+      parseNow(false);
+    }, QUIET_DEBOUNCE);
+  }
+
   _showError(event) {
     console.log("NOTIFICATION", event)
     const data = JSON.parse(event.data);
@@ -276,18 +371,11 @@ class Chat extends BaseEl {
         this.msgSoFar = data.params
       }
 
-      if (content) {
-        this.messages[this.messages.length - 1].content = content
-        this.requestUpdate();
-        this._scrollToBottom()
       } else if (this.msgSoFar) {
-        // Use throttled parsing for frequent updates - this ensures all updates are shown
+      } else if (this.msgSoFar) {
+        // Hybrid scheduled parsing for frequent partial updates when no handler content
         const messageIndex = this.messages.length - 1;
-        const parsedContent = tryParse(this.msgSoFar, true, messageIndex, this);
-        this.messages[messageIndex].content = parsedContent;
-        this.requestUpdate();
-        this._scrollToBottom();
-        window.initializeCodeCopyButtons();
+        this._scheduleMarkdownUpdate(messageIndex, this.msgSoFar, { final: false });
       }
     } else {
       console.log('partial. data.params', data.params)
@@ -320,8 +408,7 @@ class Chat extends BaseEl {
           </action-component>`;
       }
       this.requestUpdate();
-      this._scrollToBottom()
-      window.initializeCodeCopyButtons();
+      this._scrollToBottomThrottled();
     }
   }
 
@@ -374,7 +461,14 @@ class Chat extends BaseEl {
       msg.spinning = 'no'
       console.log('Spinner set to false:', msg);
     }
-    window.initializeCodeCopyButtons();
+    // Ensure final flush of any in-flight partial markdown for the last AI message
+    const lastIndex = this.messages.length - 1;
+    const sched = this._streamSchedulers.get(lastIndex);
+    if (sched && typeof sched.lastText === 'string' && sched.lastText.length > 0) {
+      this._scheduleMarkdownUpdate(lastIndex, sched.lastText, { final: true });
+    } else if (typeof window.initializeCodeCopyButtons === 'function') {
+      try { window.initializeCodeCopyButtons(); } catch (_) {}
+    }
     this.requestUpdate();
   }
 
