From c1140a7ab596a162561c8828b9a7580800b886a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Flor=C3=AAncio?= Date: Sat, 28 Feb 2026 17:41:45 +0000 Subject: [PATCH] fix: consolidate markdown styles into shared .markdown-body class (#332) feat: improve markdown styles --- apps/web/app/globals.css | 289 +++++++++--------- .../editors/tiptap/tiptap-plan-editor.tsx | 2 +- apps/web/components/github/pr-shared.tsx | 2 +- .../release-notes/release-notes-dialog.tsx | 2 +- .../components/settings/changelog-list.tsx | 2 +- .../components/shared/markdown-components.tsx | 60 +--- .../task/chat/clarification-input-overlay.tsx | 2 +- .../task/chat/messages/chat-message.tsx | 4 +- .../clarification-request-message.tsx | 2 +- .../task/chat/messages/inline-code.tsx | 5 +- .../task/chat/messages/thinking-message.tsx | 2 +- 11 files changed, 166 insertions(+), 206 deletions(-) diff --git a/apps/web/app/globals.css b/apps/web/app/globals.css index a7cd44c6e8..7d386395b9 100644 --- a/apps/web/app/globals.css +++ b/apps/web/app/globals.css @@ -547,6 +547,151 @@ /* Legacy Milkdown styles removed - plan editor now uses TipTap */ +/* ── Shared markdown body styles ── + Single source of truth for rendered markdown across TipTap (plan editor) + and ReactMarkdown (chat, PR, changelog, etc.). */ +.markdown-body { + font-size: 13px; + line-height: 1.625; + color: var(--foreground); + font-family: + -apple-system, "system-ui", "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, + "Apple Color Emoji", "Segoe UI Emoji"; +} + +.markdown-body h1, +.markdown-body h2, +.markdown-body h3, +.markdown-body h4, +.markdown-body h5 { + font-weight: 700; + color: var(--foreground); + margin-top: 1.25rem; + margin-bottom: 0.375rem; + line-height: 1.3; +} + +.markdown-body :is(h1, h2, h3, h4, h5):first-child { + margin-top: 0; +} + +.markdown-body h1 { + font-size: 1.25rem; +} + +.markdown-body h2 { + font-size: 1.0625rem; +} + +.markdown-body h3 { + font-size: 0.9375rem; +} + +.markdown-body p { + line-height: 1.625; +} + +.markdown-body ul { + list-style-type: disc; + padding-left: 1.25rem; + margin-top: 1rem; + margin-bottom: 1rem; +} + +.markdown-body ol { + list-style-type: decimal; + padding-left: 1.25rem; + margin-top: 1rem; + margin-bottom: 1rem; +} + +.markdown-body li { + margin-top: 0.125rem; + margin-bottom: 0.125rem; +} + +.markdown-body li > ul, +.markdown-body li > ol { + margin-top: 0.25rem; + margin-bottom: 0.25rem; +} + +/* Inline code — accent (indigo) color */ +.markdown-body :not(pre) > code, +.markdown-body code:not(pre code) { + background: var(--muted); + color: var(--accent); + padding: 0.1rem 0.375rem; + border-radius: 0.25rem; + font-size: 0.90em; + font-family: var(--font-mono); +} + +.markdown-body a { + color: var(--primary); + text-decoration-line: underline; + text-underline-offset: 2px; +} + +.markdown-body a:hover { + text-decoration-line: underline; +} + +.markdown-body blockquote { + border-left: 3px solid var(--primary); + padding-left: 0.75rem; + margin-left: 0; + margin-top: 0.5rem; + margin-bottom: 0.5rem; + color: var(--muted-foreground); + font-style: italic; +} + +.markdown-body hr { + border: none; + border-top: 1px solid var(--border); + margin: 1.5rem 0; +} + +.markdown-body table { + border-collapse: collapse; + width: 100%; + margin: 0.75rem 0; +} + +.markdown-body th, +.markdown-body td { + border: 1px solid var(--border); + padding: 0.25rem 0.5rem; + font-size: 13px; +} + +.markdown-body th { + background: var(--muted); + font-weight: 600; +} + +.markdown-body thead { + background: color-mix(in oklch, var(--muted) 50%, transparent); +} + +.markdown-body tbody tr { + border-bottom: 1px solid var(--border); +} + +.markdown-body tbody tr:last-child { + border-bottom: 0; +} + +.markdown-body tbody tr:hover { + background: color-mix(in oklch, var(--muted) 50%, transparent); +} + +.markdown-body strong { + color: var(--foreground); + font-weight: 700; +} + /* TipTap editor styles */ .tiptap p.is-editor-empty:first-child::before { content: attr(data-placeholder); @@ -723,7 +868,9 @@ background-color: rgba(99, 102, 241, 0.12) !important; } -/* TipTap Plan Editor Styles */ +/* TipTap Plan Editor Styles + Element styles (headings, lists, code, etc.) come from .markdown-body above. + Only TipTap-specific layout, container, and editor behavior styles remain here. */ .tiptap-plan-wrapper { height: 100%; overflow: auto; @@ -745,80 +892,7 @@ outline: none; } -/* Base text styling */ -.tiptap-plan-wrapper .tiptap { - color: var(--foreground); - font-family: - -apple-system, "system-ui", "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, - "Apple Color Emoji", "Segoe UI Emoji"; -} - -.tiptap-plan-wrapper .tiptap, -.tiptap-plan-wrapper - .tiptap - *:not(.mermaid-container):not(.mermaid-container *):not(pre code):not(pre code *) { - font-size: 13px !important; - line-height: 1.625; -} - -/* Headings */ -.tiptap-plan-wrapper .tiptap h1, -.tiptap-plan-wrapper .tiptap h2, -.tiptap-plan-wrapper .tiptap h3, -.tiptap-plan-wrapper .tiptap h4 { - font-weight: 700 !important; - color: var(--foreground) !important; - margin-top: 1.25rem !important; - margin-bottom: 0.375rem !important; - line-height: 1.3 !important; -} - -.tiptap-plan-wrapper .tiptap :is(h1, h2, h3, h4):first-child { - margin-top: 0 !important; -} - -.tiptap-plan-wrapper .tiptap h1:not(.mermaid-container):not(.mermaid-container *) { - font-size: 1.25rem !important; -} -.tiptap-plan-wrapper .tiptap h2:not(.mermaid-container):not(.mermaid-container *) { - font-size: 1.0625rem !important; -} -.tiptap-plan-wrapper .tiptap h3:not(.mermaid-container):not(.mermaid-container *) { - font-size: 0.9375rem !important; -} - -/* Paragraphs */ -.tiptap-plan-wrapper .tiptap p { - line-height: 1.625 !important; - font-size: 13px !important; -} - -/* Lists */ -.tiptap-plan-wrapper .tiptap ul { - list-style-type: disc !important; - padding-left: 1.25rem !important; - margin-top: 1rem !important; - margin-bottom: 1rem !important; -} - -.tiptap-plan-wrapper .tiptap li > ul { - margin-top: 0.25rem !important; - margin-bottom: 0.25rem !important; -} - -.tiptap-plan-wrapper .tiptap ol { - list-style-type: decimal !important; - padding-left: 1.25rem !important; - margin-top: 1rem !important; - margin-bottom: 1rem !important; -} - -.tiptap-plan-wrapper .tiptap li { - margin-top: 0.125rem !important; - margin-bottom: 0.125rem !important; -} - -/* Task lists */ +/* Task lists (TipTap-specific data attribute) */ .tiptap-plan-wrapper .tiptap ul[data-type="taskList"] { list-style: none !important; padding-left: 0 !important; @@ -834,17 +908,7 @@ margin-top: 0.125rem; } -/* Inline code (not code blocks) */ -.tiptap-plan-wrapper .tiptap :not(pre) > code { - background: var(--muted) !important; - color: var(--foreground) !important; - padding: 0.125rem 0.375rem !important; - border-radius: 0.25rem !important; - font-size: 0.85em !important; - font-family: var(--font-mono) !important; -} - -/* Code blocks */ +/* Code blocks (TipTap-specific: lowlight/highlight.js syntax highlighting) */ .tiptap-plan-wrapper .tiptap pre { background: var(--background) !important; padding: 0.5rem 0.75rem !important; @@ -870,50 +934,6 @@ color: #1e1e1e; } -/* Blockquote */ -.tiptap-plan-wrapper .tiptap blockquote { - border-left: 3px solid var(--primary) !important; - padding-left: 0.75rem !important; - margin-left: 0 !important; - margin-top: 0.5rem !important; - margin-bottom: 0.5rem !important; - color: var(--muted-foreground) !important; - font-style: italic !important; -} - -/* Links */ -.tiptap-plan-wrapper .tiptap a { - color: var(--primary) !important; - text-decoration: underline !important; - text-underline-offset: 2px !important; -} - -/* Horizontal rule */ -.tiptap-plan-wrapper .tiptap hr { - border: none !important; - border-top: 1px solid var(--border) !important; - margin: 1.5rem 0 !important; -} - -/* Tables */ -.tiptap-plan-wrapper .tiptap table { - border-collapse: collapse; - width: 100%; - margin: 1.5rem 0; -} - -.tiptap-plan-wrapper .tiptap th, -.tiptap-plan-wrapper .tiptap td { - border: 1px solid var(--border); - padding: 0.25rem 0.5rem; - font-size: 13px; -} - -.tiptap-plan-wrapper .tiptap th { - background: var(--muted); - font-weight: 600; -} - /* Highlight marks */ .tiptap-plan-wrapper mark { background-color: rgba(255, 212, 0, 0.3); @@ -1268,15 +1288,6 @@ "Apple Color Emoji", "Segoe UI Emoji"; } -.chat-message-list .prose { - font-size: inherit; - font-family: inherit; -} - -.chat-message-list .prose :where(p, li, ul, ol, blockquote, h1, h2, h3, h4, h5, h6, td, th) { - font-family: inherit; -} - /* Shiki code block styling */ .shiki-code-block pre.shiki { margin: 0; diff --git a/apps/web/components/editors/tiptap/tiptap-plan-editor.tsx b/apps/web/components/editors/tiptap/tiptap-plan-editor.tsx index 8a969b3a01..297253c433 100644 --- a/apps/web/components/editors/tiptap/tiptap-plan-editor.tsx +++ b/apps/web/components/editors/tiptap/tiptap-plan-editor.tsx @@ -383,7 +383,7 @@ export function TipTapPlanEditor(props: TipTapPlanEditorProps) { return (
{editor && isReady && ( diff --git a/apps/web/components/github/pr-shared.tsx b/apps/web/components/github/pr-shared.tsx index bb45a82699..757db8eb8d 100644 --- a/apps/web/components/github/pr-shared.tsx +++ b/apps/web/components/github/pr-shared.tsx @@ -155,7 +155,7 @@ export function AddToContextButton({ export function PRMarkdownBody({ body }: { body: string }) { return ( -
+
{body} diff --git a/apps/web/components/release-notes/release-notes-dialog.tsx b/apps/web/components/release-notes/release-notes-dialog.tsx index 27b14af8f6..82b61580a1 100644 --- a/apps/web/components/release-notes/release-notes-dialog.tsx +++ b/apps/web/components/release-notes/release-notes-dialog.tsx @@ -60,7 +60,7 @@ export function ReleaseNotesDialog({ )}
)} -
+
{entry.notes} diff --git a/apps/web/components/settings/changelog-list.tsx b/apps/web/components/settings/changelog-list.tsx index 5077c5eef7..f3440f5a65 100644 --- a/apps/web/components/settings/changelog-list.tsx +++ b/apps/web/components/settings/changelog-list.tsx @@ -45,7 +45,7 @@ function ChangelogEntryCard({ entry }: { entry: ChangelogEntry }) {
-
+
{entry.notes} diff --git a/apps/web/components/shared/markdown-components.tsx b/apps/web/components/shared/markdown-components.tsx index f1ddb078d9..e6dd392511 100644 --- a/apps/web/components/shared/markdown-components.tsx +++ b/apps/web/components/shared/markdown-components.tsx @@ -41,7 +41,9 @@ export function getTextContent(children: ReactNode): string { /** * Shared markdown component overrides for ReactMarkdown. - * Used by both chat messages and PR comment/review rendering. + * Element styles (headings, lists, inline code, etc.) are handled by + * the `.markdown-body` CSS class in globals.css. Only behavioral overrides + * (code routing, link target, table overflow wrapper) remain here. */ export const markdownComponents = { code: ({ className, children }: { className?: string; children?: ReactNode }) => { @@ -57,64 +59,14 @@ export const markdownComponents = { } return {content}; }, - ol: ({ children }: { children?: ReactNode }) => ( -
    {children}
- ), - ul: ({ children }: { children?: ReactNode }) => ( -
    {children}
- ), - li: ({ children }: { children?: ReactNode }) =>
  • {children}
  • , a: ({ href, children }: { href?: string; children?: ReactNode }) => ( - + {children} ), - p: ({ children }: { children?: ReactNode }) =>

    {children}

    , - h1: ({ children }: { children?: ReactNode }) => ( -

    {children}

    - ), - h2: ({ children }: { children?: ReactNode }) => ( -

    {children}

    - ), - h3: ({ children }: { children?: ReactNode }) => ( -

    {children}

    - ), - h4: ({ children }: { children?: ReactNode }) => ( -

    {children}

    - ), - h5: ({ children }: { children?: ReactNode }) => ( -
    {children}
    - ), - hr: ({ children }: { children?: ReactNode }) =>
    {children}, table: ({ children }: { children?: ReactNode }) => ( -
    - - {children} -
    +
    + {children}
    ), - thead: ({ children }: { children?: ReactNode }) => ( - {children} - ), - tbody: ({ children }: { children?: ReactNode }) => ( - {children} - ), - tr: ({ children }: { children?: ReactNode }) => ( - {children} - ), - th: ({ children }: { children?: ReactNode }) => ( - - {children} - - ), - td: ({ children }: { children?: ReactNode }) => ( - - {children} - - ), }; diff --git a/apps/web/components/task/chat/clarification-input-overlay.tsx b/apps/web/components/task/chat/clarification-input-overlay.tsx index 739f16e5c1..e8df0d3386 100644 --- a/apps/web/components/task/chat/clarification-input-overlay.tsx +++ b/apps/web/components/task/chat/clarification-input-overlay.tsx @@ -229,7 +229,7 @@ export function ClarificationInputOverlay({ message, onResolved }: Clarification
    -
    +
    {question.prompt} diff --git a/apps/web/components/task/chat/messages/chat-message.tsx b/apps/web/components/task/chat/messages/chat-message.tsx index dfdb4e4f4f..2173ae179e 100644 --- a/apps/web/components/task/chat/messages/chat-message.tsx +++ b/apps/web/components/task/chat/messages/chat-message.tsx @@ -44,7 +44,7 @@ function renderContentWithFileRefs(content: string): React.ReactNode[] { parts.push( @{filePath} , @@ -258,7 +258,7 @@ function AgentMessageContent({ comment, showRaw, onToggleRaw, showRichBlocks }: {comment.content || "(empty)"} ) : ( -
    +
    {comment.content || "(empty)"} diff --git a/apps/web/components/task/chat/messages/clarification-request-message.tsx b/apps/web/components/task/chat/messages/clarification-request-message.tsx index a53de8fefe..c799f8545a 100644 --- a/apps/web/components/task/chat/messages/clarification-request-message.tsx +++ b/apps/web/components/task/chat/messages/clarification-request-message.tsx @@ -75,7 +75,7 @@ export function ClarificationRequestMessage({ comment }: ClarificationRequestMes {/* Content */}
    {/* Question */} -
    +
    {question.prompt} diff --git a/apps/web/components/task/chat/messages/inline-code.tsx b/apps/web/components/task/chat/messages/inline-code.tsx index 7f0335a467..b5fc317de6 100644 --- a/apps/web/components/task/chat/messages/inline-code.tsx +++ b/apps/web/components/task/chat/messages/inline-code.tsx @@ -29,10 +29,7 @@ export function InlineCode({ children }: InlineCodeProps) { {children} diff --git a/apps/web/components/task/chat/messages/thinking-message.tsx b/apps/web/components/task/chat/messages/thinking-message.tsx index 483fef790d..4cc3a067da 100644 --- a/apps/web/components/task/chat/messages/thinking-message.tsx +++ b/apps/web/components/task/chat/messages/thinking-message.tsx @@ -57,7 +57,7 @@ export const ThinkingMessage = memo(function ThinkingMessage({ comment }: { comm > {!isShort && (
    -
    +
    {text}