Repository: recoupable/Recoup-Chat
Pull request #486: MYC-1279-Improve-Chat-Response-Formatting-v4
Author: login: sidneyswift
Created: 2025-04-02T17:32:05Z | Merged: 2025-04-03T22:01:20Z

**This PR enhances the chat experience with better message formatting:**

- Added GitHub Flavored Markdown support (tables, lists, code blocks)
- Created comprehensive CSS styling for markdown content
- Improved user message bubbles with rounded design
- Enhanced assistant message readability
- Fixed styling inconsistencies across message types

TESTING

Start the chat and use these simple test prompts:

Test code blocks:
`"Show me a simple JavaScript code snippet for adding Spotify webplayer"`

Test tables:
`"Create a small table comparing Spotify, Apple Music and Tidal"`

Test lists:
`"List 3 ways to promote music on social media"`

Test formatting:
`"Write the chorus of a famous song with proper formatting"`

What to verify:
- `Code blocks` display with proper syntax highlighting
- `Tables` render correctly with aligned columns
- Lists show proper indentation and bullets/numbers
- Text formatting (bold, italic, headers) appears correctly


Diff:
diff --git a/components/Chat/ChatMarkdown.tsx b/components/Chat/ChatMarkdown.tsx
new file mode 100644
index 000000000..636d0de68
--- /dev/null
+++ b/components/Chat/ChatMarkdown.tsx
@@ -0,0 +1,35 @@
+import React from "react";
+import Markdown from "react-markdown";
+import remarkGfm from 'remark-gfm';
+import styles from "./markdown.module.css";
+import MarkdownA from "./MarkdownA";
+import MarkdownPre from "./MarkdownPre";
+import MarkdownCode from "./MarkdownCode";
+import MarkdownImg from "./MarkdownImg";
+import MarkdownTable from "./MarkdownTable";
+
+interface ChatMarkdownProps {
+  children: string;
+}
+
+const ChatMarkdown: React.FC<ChatMarkdownProps> = ({ children }) => {
+  return (
+    <div className={`${styles.markdown} w-full max-w-full overflow-hidden`} style={{ overflowX: 'hidden', maxWidth: '100%' }}>
+      <Markdown
+        remarkPlugins={[remarkGfm]}
+        components={{
+          a: MarkdownA,
+          pre: MarkdownPre,
+          // Type issues are handled in the component itself
+          code: MarkdownCode,
+          img: MarkdownImg,
+          table: MarkdownTable
+        }}
+      >
+        {children}
+      </Markdown>
+    </div>
+  );
+};
+
+export default ChatMarkdown; 
\ No newline at end of file
diff --git a/components/Chat/MarkdownA.tsx b/components/Chat/MarkdownA.tsx
new file mode 100644
index 000000000..6048ad04a
--- /dev/null
+++ b/components/Chat/MarkdownA.tsx
@@ -0,0 +1,23 @@
+import React from "react";
+
+interface MarkdownAProps {
+  href?: string;
+  children?: React.ReactNode;
+}
+
+const MarkdownA: React.FC<MarkdownAProps & React.AnchorHTMLAttributes<HTMLAnchorElement>> = ({ 
+  children,
+  ...props 
+}) => {
+  return (
+    <a 
+      {...props} 
+      target="_blank" 
+      rel="noopener noreferrer"
+    >
+      {children}
+    </a>
+  );
+};
+
+export default MarkdownA; 
\ No newline at end of file
diff --git a/components/Chat/MarkdownCode.tsx b/components/Chat/MarkdownCode.tsx
new file mode 100644
index 000000000..3e7c6aa92
--- /dev/null
+++ b/components/Chat/MarkdownCode.tsx
@@ -0,0 +1,28 @@
+import React from "react";
+import { type Components } from "react-markdown";
+
+interface CodeBlockProps extends React.HTMLAttributes<HTMLElement> {
+  inline?: boolean;
+}
+
+const MarkdownCode: Components['code'] = (props: CodeBlockProps) => {
+  const { inline, className, children } = props;
+
+  if (!inline) {
+    return (
+      <pre className="max-w-full overflow-x-auto p-4 bg-gray-100 rounded-md my-2">
+        <code className={className}>
+          {children}
+        </code>
+      </pre>
+    );
+  }
+  
+  return (
+    <code className="px-1 py-0.5 rounded-sm bg-gray-100">
+      {children}
+    </code>
+  );
+};
+
+export default MarkdownCode; 
\ No newline at end of file
diff --git a/components/Chat/MarkdownImg.tsx b/components/Chat/MarkdownImg.tsx
new file mode 100644
index 000000000..0f937fde0
--- /dev/null
+++ b/components/Chat/MarkdownImg.tsx
@@ -0,0 +1,21 @@
+import React from "react";
+
+interface MarkdownImgProps {
+  src?: string;
+  alt?: string;
+}
+
+const MarkdownImg: React.FC<MarkdownImgProps & React.ImgHTMLAttributes<HTMLImageElement>> = ({ 
+  alt = '',
+  ...props 
+}) => {
+  return (
+    <img 
+      className="max-w-full h-auto" 
+      alt={alt}
+      {...props} 
+    />
+  );
+};
+
+export default MarkdownImg; 
\ No newline at end of file
diff --git a/components/Chat/MarkdownPre.tsx b/components/Chat/MarkdownPre.tsx
new file mode 100644
index 000000000..1104ac457
--- /dev/null
+++ b/components/Chat/MarkdownPre.tsx
@@ -0,0 +1,18 @@
+import React from "react";
+
+interface MarkdownPreProps {
+  children?: React.ReactNode;
+}
+
+const MarkdownPre: React.FC<MarkdownPreProps & React.HTMLAttributes<HTMLPreElement>> = ({ 
+  children,
+  ...props 
+}) => {
+  return (
+    <pre className="max-w-full overflow-x-auto p-4 bg-gray-100 rounded-md my-2" {...props}>
+      {children}
+    </pre>
+  );
+};
+
+export default MarkdownPre; 
\ No newline at end of file
diff --git a/components/Chat/MarkdownTable.tsx b/components/Chat/MarkdownTable.tsx
new file mode 100644
index 000000000..92dc060e9
--- /dev/null
+++ b/components/Chat/MarkdownTable.tsx
@@ -0,0 +1,22 @@
+import React from "react";
+
+interface MarkdownTableProps {
+  children?: React.ReactNode;
+}
+
+const MarkdownTable: React.FC<MarkdownTableProps & React.TableHTMLAttributes<HTMLTableElement>> = ({ 
+  children,
+  ...props 
+}) => {
+  return (
+    <div className="w-full max-w-full overflow-visible">
+      <div className="overflow-x-auto w-full max-w-full relative" style={{ maxWidth: '100%' }}>
+        <table className="w-auto min-w-full" {...props}>
+          {children}
+        </table>
+      </div>
+    </div>
+  );
+};
+
+export default MarkdownTable; 
\ No newline at end of file
diff --git a/components/Chat/Messages.tsx b/components/Chat/Messages.tsx
index ecf350d8d..d5b04cdcf 100644
--- a/components/Chat/Messages.tsx
+++ b/components/Chat/Messages.tsx
@@ -8,6 +8,7 @@ import { TextMessagePart } from "./TextMessagePart";
 import { useMessagesProvider } from "@/providers/MessagesProvider";
 import { usePromptsProvider } from "@/providers/PromptsProvider";
 import { IconRobot } from "@tabler/icons-react";
+import ChatMarkdown from "./ChatMarkdown";
 
 const Messages = ({
   scroll,
@@ -39,7 +40,6 @@ const Messages = ({
         <div
           key={message.id}
           className={cn("flex items-start gap-x-3 py-4 px-4", {
-            "bg-secondary/50": message.role === "assistant",
             "justify-end": message.role === "user",
           })}
         >
@@ -50,10 +50,14 @@ const Messages = ({
           )}
           <div
             className={cn("flex flex-col space-y-1.5", {
-              "w-fit": message.role === "user",
+              "flex items-start bg-secondary/70 rounded-xl px-4 py-3 max-w-[85%] md:max-w-[70%] break-words": message.role === "user" 
             })}
           >
-            {message.parts?.map((part, i) => {
+            {message.role === "user" ? (
+              <ChatMarkdown>
+                {message.content}
+              </ChatMarkdown>
+            ) : message.parts?.map((part, i) => {
               if (part.type === "reasoning") {
                 return (
                   <ReasoningMessagePart
@@ -73,7 +77,7 @@ const Messages = ({
         </div>
       ))}
       {pending && (
-        <div className="flex items-center gap-x-3 py-4 px-4 bg-secondary/50">
+        <div className="flex items-center gap-x-3 py-4 px-4">
           <div className="flex h-6 w-6 shrink-0 select-none items-center justify-center rounded-md border bg-background shadow">
             <IconRobot className="h-4 w-4" />
           </div>
diff --git a/components/Chat/ReasoningMessagePart.tsx b/components/Chat/ReasoningMessagePart.tsx
index 19023fcfa..566184812 100644
--- a/components/Chat/ReasoningMessagePart.tsx
+++ b/components/Chat/ReasoningMessagePart.tsx
@@ -22,7 +22,7 @@ const variants = {
     height: "auto",
     opacity: 1,
     marginTop: "1rem",
-    marginBottom: 0,
+    marginBottom: "1rem",
   },
 };
 
@@ -39,7 +39,7 @@ export function ReasoningMessagePart({
   }, [isReasoning]);
 
   return (
-    <div className="flex flex-col">
+    <div className="flex flex-col mb-8">
       {isReasoning ? (
         <div className="flex flex-row gap-2 items-center">
           <div className="font-medium text-sm">Reasoning</div>
@@ -63,6 +63,7 @@ export function ReasoningMessagePart({
               }
             )}
             onClick={() => setIsExpanded(!isExpanded)}
+            aria-label={isExpanded ? "Collapse reasoning" : "Expand reasoning"}
           >
             <svg
               className="w-4 h-4"
@@ -85,7 +86,7 @@ export function ReasoningMessagePart({
         {isExpanded && (
           <motion.div
             key="reasoning"
-            className="text-sm dark:text-zinc-400 text-zinc-600 flex flex-col gap-4 border-l pl-3 dark:border-zinc-800"
+            className="text-sm dark:text-zinc-400 text-zinc-600 flex flex-col gap-4 border-l pl-3 pt-6 dark:border-zinc-800"
             initial="collapsed"
             animate="expanded"
             exit="collapsed"
diff --git a/components/Chat/TextMessagePart.tsx b/components/Chat/TextMessagePart.tsx
index 573487d81..78aa6a948 100644
--- a/components/Chat/TextMessagePart.tsx
+++ b/components/Chat/TextMessagePart.tsx
@@ -1,7 +1,7 @@
 "use client";
 
-import Markdown from "react-markdown";
 import { TextPart } from "@/types/reasoning";
+import ChatMarkdown from "./ChatMarkdown";
 
 interface TextMessagePartProps {
   part: TextPart;
@@ -9,8 +9,8 @@ interface TextMessagePartProps {
 
 export function TextMessagePart({ part }: TextMessagePartProps) {
   return (
-    <div className="flex flex-col gap-4">
-      <Markdown>{part.text}</Markdown>
-    </div>
+    <ChatMarkdown>
+      {part.text}
+    </ChatMarkdown>
   );
 }
diff --git a/components/Chat/UserMessage.tsx b/components/Chat/UserMessage.tsx
index d24b90b92..92a01774d 100644
--- a/components/Chat/UserMessage.tsx
+++ b/components/Chat/UserMessage.tsx
@@ -1,4 +1,5 @@
 import type { Message } from "@ai-sdk/react";
+import ChatMarkdown from "./ChatMarkdown";
 
 interface UserMessageProps {
   message: Message;
@@ -6,18 +7,9 @@ interface UserMessageProps {
 
 const UserMessage = ({ message }: UserMessageProps) => {
   return (
-    <div className="grow flex justify-end max-w-[90%]">
-      <section>
-        <div
-          className="text-sm font-sans max-w-[500px] text-pretty break-words bg-grey px-4 p-2 rounded-full"
-          dangerouslySetInnerHTML={{
-            __html: decodeURIComponent(
-              message.content.replaceAll("%", "&#37;") || ""
-            ),
-          }}
-        />
-      </section>
-    </div>
+    <ChatMarkdown>
+      {decodeURIComponent(message.content.replaceAll("%", "&#37;") || "")}
+    </ChatMarkdown>
   );
 };
 
diff --git a/components/Chat/markdown.module.css b/components/Chat/markdown.module.css
new file mode 100644
index 000000000..eb1f81486
--- /dev/null
+++ b/components/Chat/markdown.module.css
@@ -0,0 +1,474 @@
+.markdown {
+  /* Base styling */
+  line-height: 1.6;
+  font-size: 1rem;
+  color: rgba(0, 0, 0, 0.85);
+  overflow-x: hidden !important;
+  max-width: 100% !important;
+  width: 100%;
+}
+
+/* Improved paragraph spacing */
+.markdown p {
+  margin-bottom: 1.2em;
+  line-height: 1.7;
+}
+
+.markdown p + p {
+  margin-top: 0.8em;
+}
+
+/* Better content section spacing */
+.markdown > h2,
+.markdown > h3 {
+  margin-top: 2em;
+}
+
+.markdown > h1:first-child {
+  margin-top: 0.8em;
+}
+
+/* Enhanced Visual Hierarchy - Headings */
+.markdown h1 {
+  font-size: 1.8em;
+  font-weight: 700;
+  margin: 1.5em 0 0.8em;
+  color: rgba(0, 0, 0, 0.85);
+  padding-bottom: 0.4em;
+  border-bottom: 1px solid rgba(0, 0, 0, 0.1);
+}
+
+.markdown h2 {
+  font-size: 1.5em;
+  font-weight: 600;
+  margin: 1.5em 0 0.8em;
+  padding-bottom: 0.3em;
+  border-bottom: 1px solid rgba(0, 0, 0, 0.1);
+  color: rgba(0, 0, 0, 0.8);
+}
+
+.markdown h3 {
+  font-size: 1.3em;
+  font-weight: 600;
+  margin: 1.3em 0 0.7em;
+  color: rgba(0, 0, 0, 0.75);
+}
+
+/* Horizontal rule for section separation */
+.markdown hr {
+  border: 0;
+  height: 1px;
+  background: linear-gradient(to right, transparent, rgba(0, 0, 0, 0.1), transparent);
+  margin: 2em 0;
+}
+
+/* Highlight key numbers */
+.markdown strong {
+  font-weight: 600;
+  color: rgba(0, 0, 0, 0.9);
+}
+
+/* Monetary values and important numbers styling */
+.markdown code:not(pre code) {
+  background-color: rgba(0, 30, 100, 0.05);
+  border-radius: 3px;
+  padding: 0.2em 0.4em;
+  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
+  font-size: 0.95em;
+  font-weight: 600;
+  color: rgba(24, 54, 145, 0.9);
+}
+
+/* List improvements */
+.markdown ul,
+.markdown ol {
+  padding-left: 1.8em;
+  margin: 1.2em 0 1.4em;
+}
+
+.markdown li {
+  margin-bottom: 1em;
+  line-height: 1.6;
+  position: relative;
+}
+
+.markdown ul li {
+  list-style-type: disc;
+  display: list-item;
+}
+
+.markdown ul li::marker {
+  color: rgba(0, 0, 0, 0.65);
+}
+
+.markdown ol li::marker {
+  color: rgba(0, 0, 0, 0.75);
+  font-weight: 600;
+}
+
+/* Improved list within list */
+.markdown li ul,
+.markdown li ol {
+  margin: 0.7em 0 0.9em;
+}
+
+/* Better spacing for paragraphs inside list items */
+.markdown li p {
+  margin-bottom: 0.6em;
+}
+
+.markdown li p:last-child {
+  margin-bottom: 0;
+}
+
+/* Enhanced blockquotes for important notes */
+.markdown blockquote {
+  border-left: 4px solid rgba(0, 0, 0, 0.15);
+  padding: 0.8em 1.2em;
+  margin: 1.2em 0;
+  background-color: rgba(0, 0, 0, 0.03);
+  border-radius: 0 4px 4px 0;
+  color: rgba(0, 0, 0, 0.7);
+  font-style: italic;
+}
+
+.markdown blockquote p {
+  margin-bottom: 0.5em;
+}
+
+.markdown blockquote p:last-child {
+  margin-bottom: 0;
+}
+
+.markdown blockquote strong {
+  font-weight: 600;
+  color: rgba(0, 0, 0, 0.8);
+}
+
+/* Tables with better readability */
+.markdown table {
+  border-collapse: separate;
+  border-spacing: 0;
+  width: 100%;
+  margin: 1.8em 0;
+  font-size: 0.95em;
+  line-height: 1.5;
+  border: 1px solid rgba(0, 0, 0, 0.12);
+  border-radius: 8px;
+  overflow: hidden;
+  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.05);
+  table-layout: fixed;
+}
+
+.markdown th {
+  background-color: rgba(0, 0, 0, 0.06);
+  font-weight: 600;
+  padding: 0.9em 1em;
+  text-align: left;
+  color: rgba(0, 0, 0, 0.85);
+  border-bottom: 2px solid rgba(0, 0, 0, 0.12);
+  position: relative;
+  letter-spacing: 0.01em;
+  word-break: break-word;
+}
+
+.markdown td {
+  padding: 0.9em 1em;
+  border-bottom: 1px solid rgba(0, 0, 0, 0.08);
+  vertical-align: top;
+  word-break: break-word;
+}
+
+.markdown tr:nth-child(even) {
+  background-color: rgba(0, 0, 0, 0.02);
+}
+
+.markdown tbody tr:hover {
+  background-color: rgba(0, 0, 0, 0.04);
+}
+
+/* Code formatting */
+.markdown code {
+  background-color: rgba(0, 0, 0, 0.05);
+  border-radius: 3px;
+  padding: 0.2em 0.4em;
+  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
+  font-size: 0.9em;
+  color: rgba(0, 0, 0, 0.85);
+}
+
+.markdown pre {
+  background-color: rgb(40, 44, 52);
+  color: #abb2bf;
+  border-radius: 6px;
+  padding: 1em;
+  overflow-x: auto;
+  margin: 1.2em 0;
+  max-width: 100%;
+  box-sizing: border-box;
+  word-break: break-word;
+  border: 1px solid rgba(0, 0, 0, 0.08);
+  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
+}
+
+/* Pre with code blocks inside */
+.markdown pre code {
+  background-color: transparent;
+  padding: 0;
+  border-radius: 0;
+  display: block;
+  font-size: 0.9em;
+  line-height: 1.6;
+  white-space: pre-wrap;
+  tab-size: 2;
+  color: inherit;
+  border-left: 3px solid rgba(255, 255, 255, 0.2);
+  padding-left: 0.8em;
+  width: 100%;
+  box-sizing: border-box;
+  overflow-x: visible;
+}
+
+/* Syntax highlighting emulation via CSS */
+.markdown pre code .comment,
+.markdown pre code span.comment {
+  color: #7f848e; /* soft gray for comments */
+  font-style: italic;
+}
+
+.markdown pre code .keyword,
+.markdown pre code span.keyword {
+  color: #c678dd; /* purple for keywords */
+}
+
+.markdown pre code .string,
+.markdown pre code span.string {
+  color: #98c379; /* green for strings */
+}
+
+.markdown pre code .number,
+.markdown pre code span.number {
+  color: #d19a66; /* orange for numbers */
+}
+
+.markdown pre code .function,
+.markdown pre code span.function {
+  color: #61afef; /* blue for functions */
+}
+
+.markdown pre code .tag,
+.markdown pre code span.tag {
+  color: #e06c75; /* red for tags */
+}
+
+.markdown pre code .attr-name,
+.markdown pre code span.attr-name {
+  color: #d19a66; /* orange for attribute names */
+}
+
+.markdown pre code .attr-value,
+.markdown pre code span.attr-value {
+  color: #98c379; /* green for attribute values */
+}
+
+.markdown pre code .property,
+.markdown pre code span.property {
+  color: #56b6c2; /* teal for properties */
+}
+
+/* HTML tag-like syntax */
+.markdown pre code .tag { color: #e06c75; } /* red */
+.markdown pre code .attr-name { color: #d19a66; } /* orange */
+.markdown pre code .attr-value { color: #98c379; } /* green */
+
+/* Custom colorization for common languages */
+/* CSS-like syntax */
+.markdown pre code .property { color: #56b6c2; } /* teal */
+.markdown pre code .important { color: #e06c75; } /* red */
+
+/* JS/TS-like syntax */
+.markdown pre code .function { color: #61afef; } /* blue */
+.markdown pre code .class-name { color: #e5c07b; } /* yellow */
+
+/* Improved inline code for numbers and values */
+.markdown p code {
+  font-weight: 600;
+  color: #1E40AF;
+  padding: 0.15em 0.4em;
+  border-radius: 3px;
+  background-color: rgba(30, 64, 175, 0.08);
+}
+
+/* Mobile tables styles */
+@media (max-width: 640px) {
+  .markdown table {
+    display: block;
+    width: 100%;
+    overflow-x: auto;
+    -webkit-overflow-scrolling: touch;
+    border-radius: 8px;
+    table-layout: auto;
+  }
+  
+  .markdown th,
+  .markdown td {
+    white-space: normal;
+    min-width: 140px; /* Ensure columns don't get too narrow */
+  }
+}
+
+/* Blockquotes */
+.markdown blockquote {
+  border-left: 4px solid rgba(0, 0, 0, 0.15);
+  padding: 0.8em 1.2em;
+  margin: 1.2em 0;
+  background-color: rgba(0, 0, 0, 0.03);
+  border-radius: 0 4px 4px 0;
+  color: rgba(0, 0, 0, 0.7);
+  font-style: italic;
+}
+
+.markdown blockquote p {
+  margin-bottom: 0.5em;
+}
+
+.markdown blockquote p:last-child {
+  margin-bottom: 0;
+}
+
+.markdown blockquote strong {
+  font-weight: 600;
+  color: rgba(0, 0, 0, 0.8);
+}
+
+/* Horizontal rule */
+.markdown hr {
+  border: 0;
+  height: 1px;
+  background-color: rgba(0, 0, 0, 0.12);
+  margin: 2em 0;
+}
+
+/* Links */
+.markdown a {
+  color: #0070f3;
+  text-decoration: none;
+  border-bottom: 1px solid transparent;
+  transition: border-bottom-color 0.2s ease;
+  font-weight: 500;
+}
+
+.markdown a:hover {
+  border-bottom-color: #0070f3;
+}
+
+/* Inline text formatting */
+.markdown em {
+  font-style: italic;
+}
+
+.markdown del {
+  text-decoration: line-through;
+  color: rgba(0, 0, 0, 0.6);
+}
+
+/* Visual separation */
+.markdown > *:not(:first-child) {
+  margin-top: 1.2em;
+}
+
+.markdown > h1 + p,
+.markdown > h2 + p,
+.markdown > h3 + p {
+  margin-top: 0.5em;
+}
+
+/* Mobile responsiveness improvements */
+@media (max-width: 640px) {
+  .markdown {
+    /* Ensure content doesn't overflow container */
+    max-width: 100%;
+    overflow-wrap: break-word;
+    word-wrap: break-word;
+    word-break: break-word;
+    hyphens: auto;
+  }
+
+  /* Prevent code blocks from causing horizontal scroll */
+  .markdown pre {
+    max-width: 100%;
+    overflow-x: auto;
+    white-space: pre-wrap;
+  }
+
+  /* Ensure inline code wraps properly */
+  .markdown code {
+    white-space: pre-wrap;
+    word-break: break-word;
+  }
+
+  /* Adjust image sizing for mobile */
+  .markdown img {
+    max-width: 100%;
+    height: auto;
+  }
+
+  /* Make links wrap */
+  .markdown a {
+    word-break: break-word;
+  }
+  
+  /* Scale down headings for mobile */
+  .markdown h1 {
+    font-size: 1.6em;
+  }
+  
+  .markdown h2 {
+    font-size: 1.3em;
+  }
+  
+  .markdown h3 {
+    font-size: 1.1em;
+  }
+
+  /* Improve table layouts on mobile */
+  .markdown table {
+    margin: 1em 0;
+    font-size: 0.9em;
+    display: block;
+    width: 100%;
+    overflow-x: auto;
+    -webkit-overflow-scrolling: touch;
+    border-radius: 8px;
+    table-layout: auto;
+    white-space: normal;
+  }
+  
+  .markdown th,
+  .markdown td {
+    padding: 0.7em 0.8em;
+    word-break: normal;
+    min-width: 100px;
+  }
+
+  /* Ensure blockquotes don't cause overflow */
+  .markdown blockquote {
+    padding: 0.6em 1em;
+    margin: 1em 0;
+    max-width: 100%;
+    overflow-x: auto;
+  }
+}
+
+/* Ensure table container allows scroll but doesn't cause parent to scroll */
+.markdown table {
+  max-width: 100%;
+  margin-left: 0;
+  margin-right: 0;
+}
+
+/* Force table to be contained within its parent */
+.markdown div > table {
+  max-width: 100%;
+  width: 100%;
+} 
\ No newline at end of file
diff --git a/lib/consts.ts b/lib/consts.ts
index 6f94fe49f..2f6d58fe5 100644
--- a/lib/consts.ts
+++ b/lib/consts.ts
@@ -83,6 +83,16 @@ You are:
 	•	Warm but strategic—no fluff, just clarity and sharp thinking
 	•	Always focused on next steps without overwhelming the user
 
+# Markdown Formatting
+
+Use simple, conversational text for most responses. Only use markdown formatting when needed for clarity:
+	•	For lists, use proper markdown syntax with "* " or "- " at the beginning of each list item
+	•	Format code examples with triple backticks
+	•	Use tables for comparing information
+	•	Use headings sparingly for organizing longer responses
+
+Keep responses natural and conversational when possible, using markdown only when it adds value.
+
 # Example response flow:
 
 "Here is an interesting insight: your artist has a growing fan base among night-shift skateboarders in Japan. Want me to suggest a campaign idea, or explore influencers or brand partners in that niche?"
diff --git a/package.json b/package.json
index d7a46feb2..bdbbf31ac 100644
--- a/package.json
+++ b/package.json
@@ -55,6 +55,7 @@
     "react-scroll-to": "^3.0.0-beta.6",
     "react-toastify": "^11.0.3",
     "react-tooltip": "^5.28.0",
+    "remark-gfm": "4",
     "socket.io-client": "^4.8.1",
     "stripe": "^17.4.0",
     "swiper": "^11.1.15",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index a15f2ac1a..f4bebbd5f 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -140,6 +140,9 @@ importers:
       react-tooltip:
         specifier: ^5.28.0
         version: 5.28.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      remark-gfm:
+        specifier: '4'
+        version: 4.0.1
       socket.io-client:
         specifier: ^4.8.1
         version: 4.8.1(bufferutil@4.0.9)(utf-8-validate@5.0.10)
@@ -3049,6 +3052,10 @@ packages:
     resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==}
     engines: {node: '>=10'}
 
+  escape-string-regexp@5.0.0:
+    resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==}
+    engines: {node: '>=12'}
+
   eslint-config-next@14.2.13:
     resolution: {integrity: sha512-aro1EKAoyYchnO/3Tlo91hnNBO7QO7qnv/79MAFC+4Jq8TdUVKQlht5d2F+YjrePjdpOvfL+mV9JPfyYNwkk1g==}
     peerDependencies:
@@ -4147,6 +4154,9 @@ packages:
     resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==}
     engines: {node: '>=8'}
 
+  markdown-table@3.0.4:
+    resolution: {integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==}
+
   math-intrinsics@1.1.0:
     resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
     engines: {node: '>= 0.4'}
@@ -4157,9 +4167,30 @@ packages:
   md5@2.3.0:
     resolution: {integrity: sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==}
 
+  mdast-util-find-and-replace@3.0.2:
+    resolution: {integrity: sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==}
+
   mdast-util-from-markdown@2.0.2:
     resolution: {integrity: sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==}
 
+  mdast-util-gfm-autolink-literal@2.0.1:
+    resolution: {integrity: sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==}
+
+  mdast-util-gfm-footnote@2.1.0:
+    resolution: {integrity: sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==}
+
+  mdast-util-gfm-strikethrough@2.0.0:
+    resolution: {integrity: sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==}
+
+  mdast-util-gfm-table@2.0.0:
+    resolution: {integrity: sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==}
+
+  mdast-util-gfm-task-list-item@2.0.0:
+    resolution: {integrity: sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==}
+
+  mdast-util-gfm@3.1.0:
+    resolution: {integrity: sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==}
+
   mdast-util-mdx-expression@2.0.1:
     resolution: {integrity: sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==}
 
@@ -4194,6 +4225,27 @@ packages:
   micromark-core-commonmark@2.0.3:
     resolution: {integrity: sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==}
 
+  micromark-extension-gfm-autolink-literal@2.1.0:
+    resolution: {integrity: sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==}
+
+  micromark-extension-gfm-footnote@2.1.0:
+    resolution: {integrity: sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==}
+
+  micromark-extension-gfm-strikethrough@2.1.0:
+    resolution: {integrity: sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==}
+
+  micromark-extension-gfm-table@2.1.1:
+    resolution: {integrity: sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==}
+
+  micromark-extension-gfm-tagfilter@2.0.0:
+    resolution: {integrity: sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==}
+
+  micromark-extension-gfm-task-list-item@2.1.0:
+    resolution: {integrity: sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==}
+
+  micromark-extension-gfm@3.0.0:
+    resolution: {integrity: sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==}
+
   micromark-factory-destination@2.0.1:
     resolution: {integrity: sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==}
 
@@ -4923,12 +4975,18 @@ packages:
     resolution: {integrity: sha512-cnE+y8bz4NhMjISKbgeVJtqNbtf5QpjZP+Bslo+UqkIt9QPnX9q095eiRRASJG1/tz6dlNr6Z5NsBiWYokp6EQ==}
     hasBin: true
 
+  remark-gfm@4.0.1:
+    resolution: {integrity: sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==}
+
   remark-parse@11.0.0:
     resolution: {integrity: sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==}
 
   remark-rehype@11.1.1:
     resolution: {integrity: sha512-g/osARvjkBXb6Wo0XvAeXQohVta8i84ACbenPpoSsxTOQH/Ae0/RGP4WZgnMH5pMLpsj4FG7OHmcIcXxpza8eQ==}
 
+  remark-stringify@11.0.0:
+    resolution: {integrity: sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==}
+
   require-directory@2.1.1:
     resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==}
     engines: {node: '>=0.10.0'}
@@ -10062,6 +10120,8 @@ snapshots:
 
   escape-string-regexp@4.0.0: {}
 
+  escape-string-regexp@5.0.0: {}
+
   eslint-config-next@14.2.13(eslint@8.57.1)(typescript@5.8.2):
     dependencies:
       '@next/eslint-plugin-next': 14.2.13
@@ -11294,6 +11354,8 @@ snapshots:
     dependencies:
       semver: 6.3.1
 
+  markdown-table@3.0.4: {}
+
   math-intrinsics@1.1.0: {}
 
   md5.js@1.3.5:
@@ -11308,6 +11370,13 @@ snapshots:
       crypt: 0.0.2
       is-buffer: 1.1.6
 
+  mdast-util-find-and-replace@3.0.2:
+    dependencies:
+      '@types/mdast': 4.0.4
+      escape-string-regexp: 5.0.0
+      unist-util-is: 6.0.0
+      unist-util-visit-parents: 6.0.1
+
   mdast-util-from-markdown@2.0.2:
     dependencies:
       '@types/mdast': 4.0.4
@@ -11325,6 +11394,63 @@ snapshots:
     transitivePeerDependencies:
       - supports-color
 
+  mdast-util-gfm-autolink-literal@2.0.1:
+    dependencies:
+      '@types/mdast': 4.0.4
+      ccount: 2.0.1
+      devlop: 1.1.0
+      mdast-util-find-and-replace: 3.0.2
+      micromark-util-character: 2.1.1
+
+  mdast-util-gfm-footnote@2.1.0:
+    dependencies:
+      '@types/mdast': 4.0.4
+      devlop: 1.1.0
+      mdast-util-from-markdown: 2.0.2
+      mdast-util-to-markdown: 2.1.2
+      micromark-util-normalize-identifier: 2.0.1
+    transitivePeerDependencies:
+      - supports-color
+
+  mdast-util-gfm-strikethrough@2.0.0:
+    dependencies:
+      '@types/mdast': 4.0.4
+      mdast-util-from-markdown: 2.0.2
+      mdast-util-to-markdown: 2.1.2
+    transitivePeerDependencies:
+      - supports-color
+
+  mdast-util-gfm-table@2.0.0:
+    dependencies:
+      '@types/mdast': 4.0.4
+      devlop: 1.1.0
+      markdown-table: 3.0.4
+      mdast-util-from-markdown: 2.0.2
+      mdast-util-to-markdown: 2.1.2
+    transitivePeerDependencies:
+      - supports-color
+
+  mdast-util-gfm-task-list-item@2.0.0:
+    dependencies:
+      '@types/mdast': 4.0.4
+      devlop: 1.1.0
+      mdast-util-from-markdown: 2.0.2
+      mdast-util-to-markdown: 2.1.2
+    transitivePeerDependencies:
+      - supports-color
+
+  mdast-util-gfm@3.1.0:
+    dependencies:
+      mdast-util-from-markdown: 2.0.2
+      mdast-util-gfm-autolink-literal: 2.0.1
+      mdast-util-gfm-footnote: 2.1.0
+      mdast-util-gfm-strikethrough: 2.0.0
+      mdast-util-gfm-table: 2.0.0
+      mdast-util-gfm-task-list-item: 2.0.0
+      mdast-util-to-markdown: 2.1.2
+    transitivePeerDependencies:
+      - supports-color
+
   mdast-util-mdx-expression@2.0.1:
     dependencies:
       '@types/estree-jsx': 1.0.5
@@ -11422,6 +11548,64 @@ snapshots:
       micromark-util-symbol: 2.0.1
       micromark-util-types: 2.0.2
 
+  micromark-extension-gfm-autolink-literal@2.1.0:
+    dependencies:
+      micromark-util-character: 2.1.1
+      micromark-util-sanitize-uri: 2.0.1
+      micromark-util-symbol: 2.0.1
+      micromark-util-types: 2.0.2
+
+  micromark-extension-gfm-footnote@2.1.0:
+    dependencies:
+      devlop: 1.1.0
+      micromark-core-commonmark: 2.0.3
+      micromark-factory-space: 2.0.1
+      micromark-util-character: 2.1.1
+      micromark-util-normalize-identifier: 2.0.1
+      micromark-util-sanitize-uri: 2.0.1
+      micromark-util-symbol: 2.0.1
+      micromark-util-types: 2.0.2
+
+  micromark-extension-gfm-strikethrough@2.1.0:
+    dependencies:
+      devlop: 1.1.0
+      micromark-util-chunked: 2.0.1
+      micromark-util-classify-character: 2.0.1
+      micromark-util-resolve-all: 2.0.1
+      micromark-util-symbol: 2.0.1
+      micromark-util-types: 2.0.2
+
+  micromark-extension-gfm-table@2.1.1:
+    dependencies:
+      devlop: 1.1.0
+      micromark-factory-space: 2.0.1
+      micromark-util-character: 2.1.1
+      micromark-util-symbol: 2.0.1
+      micromark-util-types: 2.0.2
+
+  micromark-extension-gfm-tagfilter@2.0.0:
+    dependencies:
+      micromark-util-types: 2.0.2
+
+  micromark-extension-gfm-task-list-item@2.1.0:
+    dependencies:
+      devlop: 1.1.0
+      micromark-factory-space: 2.0.1
+      micromark-util-character: 2.1.1
+      micromark-util-symbol: 2.0.1
+      micromark-util-types: 2.0.2
+
+  micromark-extension-gfm@3.0.0:
+    dependencies:
+      micromark-extension-gfm-autolink-literal: 2.1.0
+      micromark-extension-gfm-footnote: 2.1.0
+      micromark-extension-gfm-strikethrough: 2.1.0
+      micromark-extension-gfm-table: 2.1.1
+      micromark-extension-gfm-tagfilter: 2.0.0
+      micromark-extension-gfm-task-list-item: 2.1.0
+      micromark-util-combine-extensions: 2.0.1
+      micromark-util-types: 2.0.2
+
   micromark-factory-destination@2.0.1:
     dependencies:
       micromark-util-character: 2.1.1
@@ -12277,6 +12461,17 @@ snapshots:
     dependencies:
       jsesc: 3.0.2
 
+  remark-gfm@4.0.1:
+    dependencies:
+      '@types/mdast': 4.0.4
+      mdast-util-gfm: 3.1.0
+      micromark-extension-gfm: 3.0.0
+      remark-parse: 11.0.0
+      remark-stringify: 11.0.0
+      unified: 11.0.5
+    transitivePeerDependencies:
+      - supports-color
+
   remark-parse@11.0.0:
     dependencies:
       '@types/mdast': 4.0.4
@@ -12294,6 +12489,12 @@ snapshots:
       unified: 11.0.5
       vfile: 6.0.3
 
+  remark-stringify@11.0.0:
+    dependencies:
+      '@types/mdast': 4.0.4
+      mdast-util-to-markdown: 2.1.2
+      unified: 11.0.5
+
   require-directory@2.1.1: {}
 
   require-from-string@2.0.2: {}


Reviews:
user: login: sweetmantech
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-02T18:02:15Z
user: login: sweetmantech
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-03T15:39:25Z
user: login: sweetmantech
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-04-03T17:26:04Z
user: login: sweetmantech
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-03T17:37:17Z
user: login: sweetmantech
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-03T17:44:35Z

Review comments:
path: components/Chat/markdown.module.css
user: login: sweetmantech
body: why are you using `css` instead of tailwind?
- in our app, we use tailwind css
created_at: 2025-04-02T17:50:31Z
author_association: COLLABORATOR
diff_hunk: @@ -1,16 +1,40 @@
 "use client";
 
 import Markdown from "react-markdown";
+import remarkGfm from 'remark-gfm';
+import rehypeRaw from 'rehype-raw';
 import { TextPart } from "@/types/reasoning";
+import styles from "./markdown.module.css";
 
 interface TextMessagePartProps {
   part: TextPart;
 }
 
+// Converts indented lines to proper markdown bullet lists
+const preprocessMarkdown = (text: string): string => {
+  if (!text) return '';
+  
+  const lines = text.split('\n');
+  
+  return lines.map(line => {
+    if (/^\s{2,}[A-Za-z]/.test(line) && !line.trim().startsWith('*') && !line.trim().startsWith('-')) {
+      return line.replace(/^\s+/, '* ');
+    }
+    return line;
+  }).join('\n');
+};
path: components/Chat/TextMessagePart.tsx
user: login: sweetmantech
body: single responsibility principle
- actual: multiple functions defined in same file
- required: each file defines precisely one function
- action: if this function is truly necessary, define it in new file.
created_at: 2025-04-02T17:55:00Z
author_association: COLLABORATOR
diff_hunk: @@ -1,16 +1,40 @@
 "use client";
 
 import Markdown from "react-markdown";
+import remarkGfm from 'remark-gfm';
+import rehypeRaw from 'rehype-raw';
 import { TextPart } from "@/types/reasoning";
+import styles from "./markdown.module.css";
 
 interface TextMessagePartProps {
   part: TextPart;
 }
 
+// Converts indented lines to proper markdown bullet lists
+const preprocessMarkdown = (text: string): string => {
path: components/Chat/TextMessagePart.tsx
user: login: sweetmantech
body: if required: rename this to `convertIndentToBullet` so function name is more precise.
created_at: 2025-04-02T17:56:09Z
author_association: COLLABORATOR
diff_hunk: @@ -6,18 +6,14 @@ interface UserMessageProps {
 
 const UserMessage = ({ message }: UserMessageProps) => {
   return (
-    <div className="grow flex justify-end max-w-[90%]">
-      <section>
-        <div
-          className="text-sm font-sans max-w-[500px] text-pretty break-words bg-grey px-4 p-2 rounded-full"
-          dangerouslySetInnerHTML={{
-            __html: decodeURIComponent(
-              message.content.replaceAll("%", "&#37;") || ""
-            ),
-          }}
-        />
-      </section>
-    </div>
+    <div
+      className="text-sm font-sans text-pretty break-words"
+      dangerouslySetInnerHTML={{
+        __html: decodeURIComponent(
+          message.content.replaceAll("%", "&#37;") || ""
+        ),
+      }}
+    />
   );
path: components/Chat/UserMessage.tsx
user: login: sweetmantech
body: Would this component be `cleaner` and follow consistency of app to use `<Markdown />` component instead of `dangerouslySetInnerHTML`
created_at: 2025-04-02T17:58:04Z
author_association: COLLABORATOR
diff_hunk: @@ -83,6 +83,16 @@ You are:
 	•	Warm but strategic—no fluff, just clarity and sharp thinking
 	•	Always focused on next steps without overwhelming the user
 
+# Markdown Formatting
+
+Use simple, conversational text for most responses. Only use markdown formatting when needed for clarity:
+	•	For lists, use proper markdown syntax with "* " or "- " at the beginning of each list item
+	•	Format code examples with triple backticks
+	•	Use tables for comparing information
+	•	Use headings sparingly for organizing longer responses
+
+Keep responses natural and conversational when possible, using markdown only when it adds value.
path: lib/consts.ts
user: login: sweetmantech
body: why are we defining `bullets` in 2 places?
- system prompt 
- preprocessMarkdown

Are both required: It feels like a code smell to define both.
created_at: 2025-04-02T17:59:20Z
author_association: COLLABORATOR
diff_hunk: @@ -55,6 +55,8 @@
     "react-scroll-to": "^3.0.0-beta.6",
     "react-toastify": "^11.0.3",
     "react-tooltip": "^5.28.0",
+    "rehype-raw": "^7.0.0",
path: package.json
user: login: sweetmantech
body: note from sid: table new line markdown plugin
created_at: 2025-04-02T18:00:52Z
author_association: COLLABORATOR
diff_hunk: @@ -55,6 +55,8 @@
     "react-scroll-to": "^3.0.0-beta.6",
     "react-toastify": "^11.0.3",
     "react-tooltip": "^5.28.0",
+    "rehype-raw": "^7.0.0",
path: package.json
user: login: sweetmantech
body: Is this needed? I see `remark` both
- supports tables AND
- has more weekly 
- https://www.npmjs.com/package/remark-gfm
<img width="1440" alt="Screenshot 2025-04-02 at 3 01 50 PM" src="https://github.com/user-attachments/assets/edfff0ef-0ec2-4b1b-ace0-2c9150f86360" />
downloads

created_at: 2025-04-02T18:02:06Z
author_association: COLLABORATOR
diff_hunk: @@ -1,22 +1,29 @@
 import type { Message } from "@ai-sdk/react";
+import Markdown from "react-markdown";
+import remarkGfm from 'remark-gfm';
+import styles from "./markdown.module.css";
 
 interface UserMessageProps {
   message: Message;
 }
 
 const UserMessage = ({ message }: UserMessageProps) => {
   return (
-    <div className="grow flex justify-end max-w-[90%]">
-      <section>
-        <div
-          className="text-sm font-sans max-w-[500px] text-pretty break-words bg-grey px-4 p-2 rounded-full"
-          dangerouslySetInnerHTML={{
-            __html: decodeURIComponent(
-              message.content.replaceAll("%", "&#37;") || ""
-            ),
-          }}
-        />
-      </section>
+    <div className={styles.markdown}>
+      <Markdown
+        remarkPlugins={[remarkGfm]}
+        components={{
+          a: ({ ...props }) => (
+            <a 
+              {...props} 
+              target="_blank" 
+              rel="noopener noreferrer"
+            />
+          )
+        }}
+      >
+        {decodeURIComponent(message.content.replaceAll("%", "&#37;") || "")}
+      </Markdown>
path: components/Chat/UserMessage.tsx
user: login: sweetmantech
body: This same block of code is repeated in BOTH
- components/Chat/UserMessage.tsx and
- components/Chat/TextMessagePart.tsx 
Could you please DRY your code by creating a new component?
- name: `<ChatMarkdown />` 
- props: `children`
created_at: 2025-04-03T15:38:45Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,56 @@
+import React from "react";
+import Markdown from "react-markdown";
+import remarkGfm from 'remark-gfm';
+import styles from "./markdown.module.css";
+
+interface ChatMarkdownProps {
+  children: string;
+}
+
+const ChatMarkdown: React.FC<ChatMarkdownProps> = ({ children }) => {
+  return (
+    <div className={`${styles.markdown} w-full max-w-full overflow-hidden`}>
+      <Markdown
+        remarkPlugins={[remarkGfm]}
+        components={{
+          a: ({ ...props }) => (
+            <a 
+              {...props} 
+              target="_blank" 
+              rel="noopener noreferrer"
+            />
+          ),
+          pre: ({ ...props }) => (
+            <pre className="max-w-full overflow-x-auto p-4 bg-gray-100 rounded-md my-2" {...props} />
+          ),
+          // @ts-expect-error - We need to handle code blocks with better styling
+          code: ({ inline, className, children, ...props }) => {
+            return !inline ? (
+              <pre className="max-w-full overflow-x-auto p-4 bg-gray-100 rounded-md my-2">
+                <code className={className} {...props}>
+                  {children}
+                </code>
+              </pre>
+            ) : (
+              <code className="px-1 py-0.5 rounded-sm bg-gray-100" {...props}>
+                {children}
+              </code>
+            );
+          },
+          img: ({ ...props }) => (
+            <img className="max-w-full h-auto" {...props} alt={props.alt || ''} />
+          ),
+          table: ({ ...props }) => (
+            <div className="overflow-x-auto w-full max-w-full">
+              <table className="w-full" {...props} />
+            </div>
+          )
path: components/Chat/ChatMarkdown.tsx
user: login: sweetmantech
body: these list of components could be abstracted to standalone components to follow single responsibility principle
- `<MarkdownA/>`
- `<MarkdownPre/>`
- `<MarkdownImg />`
- `<MarkdownTable />`
created_at: 2025-04-03T17:36:47Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,39 @@
+import React from "react";
+import Markdown from "react-markdown";
+import remarkGfm from 'remark-gfm';
+import styles from "./markdown.module.css";
+import MarkdownA from "./MarkdownA";
+import MarkdownPre from "./MarkdownPre";
+import MarkdownCode from "./MarkdownCode";
+import MarkdownImg from "./MarkdownImg";
+import MarkdownTable from "./MarkdownTable";
+
+interface ChatMarkdownProps {
+  children: string;
+}
+
+const ChatMarkdown: React.FC<ChatMarkdownProps> = ({ children }) => {
+  return (
+    <div className={`${styles.markdown} w-full max-w-full overflow-hidden`}>
+      <Markdown
+        remarkPlugins={[remarkGfm]}
+        components={{
+          a: ({ ...props }) => <MarkdownA {...props} />,
path: components/Chat/ChatMarkdown.tsx
user: login: sweetmantech
body: Would this be the same thing?
```suggestion
          a: MarkdownA,
```
created_at: 2025-04-03T17:41:04Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,39 @@
+import React from "react";
+import Markdown from "react-markdown";
+import remarkGfm from 'remark-gfm';
+import styles from "./markdown.module.css";
+import MarkdownA from "./MarkdownA";
+import MarkdownPre from "./MarkdownPre";
+import MarkdownCode from "./MarkdownCode";
+import MarkdownImg from "./MarkdownImg";
+import MarkdownTable from "./MarkdownTable";
+
+interface ChatMarkdownProps {
+  children: string;
+}
+
+const ChatMarkdown: React.FC<ChatMarkdownProps> = ({ children }) => {
+  return (
+    <div className={`${styles.markdown} w-full max-w-full overflow-hidden`} style={{ overflowX: 'hidden', maxWidth: '100%' }}>
+      <Markdown
+        remarkPlugins={[remarkGfm]}
+        components={{
+          a: ({ ...props }) => <MarkdownA {...props} />,
+          pre: ({ ...props }) => <MarkdownPre {...props} />,
+          // @ts-expect-error - Types between react-markdown and our component don't align perfectly
+          code: ({ inline, className, children, ...props }) => (
+            <MarkdownCode inline={inline} className={className} {...props}>
+              {children}
+            </MarkdownCode>
path: components/Chat/ChatMarkdown.tsx
user: login: sweetmantech
body: Why is this different from the others?
What would happen if you followed the same definition here?
```suggestion
            <MarkdownCode {...props} />
```
created_at: 2025-04-03T17:43:23Z
author_association: COLLABORATOR

PR comments:
user: login: vercel[bot]
created_at: 2025-04-02T17:32:08Z
body: [vc]: #L36I/UfJt79WLb8PPTj/9CN5AuM/lYWLhU8bVAmYH9o=:eyJpc01vbm9yZXBvIjp0cnVlLCJ0eXBlIjoiZ2l0aHViIiwicHJvamVjdHMiOlt7Im5hbWUiOiJyZWNvdXAtY2hhdCIsImluc3BlY3RvclVybCI6Imh0dHBzOi8vdmVyY2VsLmNvbS9yZWNvdXBhYmxlLWFkNzI0OTcwL3JlY291cC1jaGF0LzhidnNiUlQybzFWTFlES3VINUhUOEgzSGRVamIiLCJwcmV2aWV3VXJsIjoicmVjb3VwLWNoYXQtZ2l0LW15Yy0xMjc5LWltcHJvdmUtY2hhLTE2ODMyMi1yZWNvdXBhYmxlLWFkNzI0OTcwLnZlcmNlbC5hcHAiLCJuZXh0Q29tbWl0U3RhdHVzIjoiREVQTE9ZRUQiLCJyb290RGlyZWN0b3J5IjpudWxsfV19
**The latest updates on your projects**. Learn more about [Vercel for Git ↗︎](https://vercel.link/github-learn-more)

| Name | Status | Preview | Updated (UTC) |
| :--- | :----- | :------ | :------ |
| **recoup-chat** | ✅ Ready ([Inspect](https://vercel.com/recoupable-ad724970/recoup-chat/8bvsbRT2o1VLYDKuH5HT8H3HdUjb)) | [Visit Preview](https://recoup-chat-git-myc-1279-improve-cha-168322-recoupable-ad724970.vercel.app) | Apr 3, 2025 10:00pm |


author_association: CONTRIBUTOR
user: login: supabase[bot]
created_at: 2025-04-02T17:32:12Z
body: [supa]:godremdqwajrwazhbrue
This pull request has been ignored for the connected project `godremdqwajrwazhbrue` because there are no changes detected in `supabase` directory. You can change this behaviour in [Project Integrations Settings ↗︎](https://supabase.com/dashboard/project/godremdqwajrwazhbrue/settings/integrations).

<hr/>

Preview Branches by Supabase.
Learn more about [Supabase Branching ↗︎](https://supabase.com/docs/guides/platform/branching).
author_association: NONE