feat: add markdown preview with toggle to view raw source#863
feat: add markdown preview with toggle to view raw source#863Naresh-official wants to merge 3 commits intosourcebot-dev:mainfrom
Conversation
|
Important Review skippedDraft detected. Please check the settings in the CodeRabbit UI or the You can disable this status message by setting the
WalkthroughThis PR adds markdown preview functionality to the code preview panel, introduces new toggle-group UI components from Radix UI, and updates dependencies. A new client-side component enables users to toggle between code and markdown views, with dedicated markdown rendering utilizing ReactMarkdown and syntax highlighting. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant CodePreviewPanelClient
participant ToggleGroup
participant ViewState as View Mode State
participant MarkdownPanel as PureMarkDownPreviewPanel
participant CodePanel as PureCodePreviewPanel
User->>CodePreviewPanelClient: Open file (markdown detected)
CodePreviewPanelClient->>ViewState: Initialize viewMode = "preview"
CodePreviewPanelClient->>ToggleGroup: Render toggle buttons (Preview/Code)
CodePreviewPanelClient->>MarkdownPanel: Render markdown preview
User->>ToggleGroup: Click "Code" button
ToggleGroup->>ViewState: Update viewMode = "code"
CodePreviewPanelClient->>CodePanel: Render code view
CodePanel-->>User: Display syntax highlighted code
User->>ToggleGroup: Click "Preview" button
ToggleGroup->>ViewState: Update viewMode = "preview"
CodePreviewPanelClient->>MarkdownPanel: Render markdown preview
MarkdownPanel->>MarkdownPanel: Process markdown with custom renderers
MarkdownPanel-->>User: Display formatted markdown
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~30 minutes Suggested reviewers
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 5
🤖 Fix all issues with AI agents
In
`@packages/web/src/app/`[domain]/browse/[...path]/components/codePreviewPanelClient.tsx:
- Line 26: Remove the debugging console.log in the CodePreviewPanelClient
component: delete the line that calls console.log({language, path, repoName,
revisionName}) (or replace it with a dev-only guard using
NODE_ENV==='development' if you want to keep local debugging). Locate the
console.log call inside the CodePreviewPanelClient component in
codePreviewPanelClient.tsx and remove or guard it so it does not run in
production.
In
`@packages/web/src/app/`[domain]/browse/[...path]/components/pureMarkDownPreviewPanel.tsx:
- Around line 86-99: The video renderer in pureMarkDownPreviewPanel.tsx (the
video: ({ src = "", ...props }) => { ... } handler) currently passes src through
unchanged, so relative video paths break; update this handler to rewrite
relative src the same way the img and source handlers do (use the same
IMAGE_BASE_URL or the existing resolve logic used for img/source) before
rendering the <video>, ensuring the src is resolved for both absolute and
relative URLs while preserving {...props} and controls.
- Line 22: IMAGE_BASE_URL is currently hardcoded to GitHub's raw domain which
breaks image resolution for non-GitHub repos; update
pureMarkDownPreviewPanel.tsx to build the image base URL from the repository's
code host instead of assuming raw.githubusercontent.com: either accept a
precomputed imageBaseUrl prop from the server component (which knows the code
host) or add code-host-aware logic that inspects repoName/revisionName and
composes the correct raw file URL for GitHub, GitLab, Bitbucket, and ADO; change
the constant IMAGE_BASE_URL to use the new prop or host-aware builder and adjust
any references in the component to use that value.
- Around line 25-26: In pureMarkDownPreviewPanel.tsx replace the hardcoded
bg-white on the wrapper with bg-background and remove the unconditional "dark"
class on the <article>; instead wire the component to the app theme (e.g., use
next-themes or the existing theme context) and conditionally add the "dark"
class to the article only when the current theme is dark so github-markdown-css
toggles correctly, and ensure you use Tailwind tokens like
bg-background/border-border/text-foreground rather than CSS variable syntax;
update the JSX in the PureMarkDownPreviewPanel component to read the theme and
apply classes accordingly.
- Around line 27-29: The ReactMarkdown rendering currently uses rehypeRaw
without sanitization (see ReactMarkdown component and the rehypePlugins array
containing rehypeRaw and rehypeHighlight); import and add rehypeSanitize to the
rehypePlugins list immediately after rehypeRaw so raw HTML is sanitized before
rendering, and if your UI requires additional tags/attributes (e.g., video,
source, iframe attrs), extend rehypeSanitize's schema (starting from
defaultSchema) to allowlist those elements/attributes before passing it to
rehypeSanitize.
🧹 Nitpick comments (4)
packages/web/src/app/[domain]/browse/[...path]/components/pureMarkDownPreviewPanel.tsx (2)
150-160: Hardcodedtext-blue-600doesn't respect the app's theme.Use a semantic Tailwind color class (e.g.,
text-primaryortext-foreground) instead.As per coding guidelines, "Use Tailwind color classes directly (e.g.,
border-border,bg-card,text-foreground) instead of CSS variable syntax (e.g.,border-[var(--border)])".
42-49: The.github/prefix check is redundant.
srcSet.startsWith(".github/")is already covered by!srcSet.startsWith("http"). The same redundancy exists in theimghandler (Line 65). Consider simplifying:Simplified condition
- if ( - srcSet.startsWith(".github/") || - !srcSet.startsWith("http") - ) { + if (!srcSet.startsWith("http")) {packages/web/src/app/[domain]/browse/[...path]/components/codePreviewPanelClient.tsx (2)
32-35:defaultValueis redundant in a controlledToggleGroup.Since you're passing
value={viewMode}, the component is controlled anddefaultValueis ignored by Radix. Remove it to avoid confusion.Fix
<ToggleGroup type="single" - defaultValue="preview" value={viewMode} onValueChange={(value) => value && setViewMode(value)} >
23-24: ImproveisMarkdowndetection readability and type safety.
- The
"gcc machine description"check is a necessary workaround — linguist-languages sometimes misidentifies.mdfiles with this language. Add a comment explaining this.- The
viewModestate should use a union type ("preview" | "code") instead ofstringfor type safety.- The long boolean expression is hard to scan and should be refactored for clarity.
Suggested refactor
- const [viewMode, setViewMode] = useState<string>("preview"); - const isMarkdown = language.toLowerCase() === "gcc machine description" || language.toLowerCase() === "md" || path.toLocaleLowerCase().endsWith(".md") || path.toLocaleLowerCase().endsWith(".markdown"); + const [viewMode, setViewMode] = useState<"preview" | "code">("preview"); + const lang = language.toLowerCase(); + const lowerPath = path.toLowerCase(); + // "gcc machine description" is included because some language classifiers + // misidentify .md files as GCC Machine Description files. + const isMarkdown = + lang === "markdown" || + lang === "gcc machine description" || + lowerPath.endsWith(".md") || + lowerPath.endsWith(".markdown");Note: linguist-languages returns
"Markdown"as the canonical language name for.mdfiles, not"md", so thelanguage === "md"check may be redundant. Use"markdown"to match the library's actual output.
| const [viewMode, setViewMode] = useState<string>("preview"); | ||
| const isMarkdown = language.toLowerCase() === "gcc machine description" || language.toLowerCase() === "md" || path.toLocaleLowerCase().endsWith(".md") || path.toLocaleLowerCase().endsWith(".markdown"); | ||
|
|
||
| console.log({language,path,repoName,revisionName}); |
There was a problem hiding this comment.
Remove console.log before merging.
This is a debugging artifact that will log to the browser console on every render in production.
Fix
- console.log({language,path,repoName,revisionName});📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| console.log({language,path,repoName,revisionName}); |
🤖 Prompt for AI Agents
In
`@packages/web/src/app/`[domain]/browse/[...path]/components/codePreviewPanelClient.tsx
at line 26, Remove the debugging console.log in the CodePreviewPanelClient
component: delete the line that calls console.log({language, path, repoName,
revisionName}) (or replace it with a dev-only guard using
NODE_ENV==='development' if you want to keep local debugging). Locate the
console.log call inside the CodePreviewPanelClient component in
codePreviewPanelClient.tsx and remove or guard it so it does not run in
production.
| repoName, | ||
| revisionName, | ||
| }: PureMarkDownPreviewPanelProps) => { | ||
| const IMAGE_BASE_URL = "https://raw.githubusercontent.com/"+repoName.split("/").slice(1).join("/")+"/"+revisionName+"/"; |
There was a problem hiding this comment.
IMAGE_BASE_URL is hardcoded to GitHub's raw.githubusercontent.com.
This component is used in a codebase that supports multiple code hosts (GitHub, GitLab, Bitbucket, Azure DevOps — as evidenced by ADO handling in codePreviewPanel.tsx Line 42). Hardcoding the URL to GitHub means image resolution will break for all non-GitHub repositories.
Consider either:
- Passing an image base URL from the server component (which has access to code host info).
- Making the URL construction code-host-aware.
🤖 Prompt for AI Agents
In
`@packages/web/src/app/`[domain]/browse/[...path]/components/pureMarkDownPreviewPanel.tsx
at line 22, IMAGE_BASE_URL is currently hardcoded to GitHub's raw domain which
breaks image resolution for non-GitHub repos; update
pureMarkDownPreviewPanel.tsx to build the image base URL from the repository's
code host instead of assuming raw.githubusercontent.com: either accept a
precomputed imageBaseUrl prop from the server component (which knows the code
host) or add code-host-aware logic that inspects repoName/revisionName and
composes the correct raw file URL for GitHub, GitLab, Bitbucket, and ADO; change
the constant IMAGE_BASE_URL to use the new prop or host-aware builder and adjust
any references in the component to use that value.
There was a problem hiding this comment.
I think a more complete solution may be to serve up assets from our own apis instead of having to hit the code host. That is a larger change however, so I'm not opposed with having something like what you have now as a MVP solution.
There was a problem hiding this comment.
@brendan-kellam Thanks for the context! That makes sense—serving assets through your own APIs would indeed be a cleaner and more maintainable solution that works uniformly across all code hosts.
I understand you're okay with the current GitHub-hardcoded approach as an MVP to get the feature out. Would you like me to create a follow-up issue to track the future work of serving markdown assets (images, videos, etc.) through Sourcebot's own APIs instead of direct code-host URLs? That way the technical debt is documented and can be prioritized later.
✏️ Learnings added
Learnt from: brendan-kellam
Repo: sourcebot-dev/sourcebot PR: 863
File: packages/web/src/app/[domain]/browse/[...path]/components/pureMarkDownPreviewPanel.tsx:22-22
Timestamp: 2026-02-08T00:36:03.683Z
Learning: In the Sourcebot codebase, the preferred architecture for serving markdown assets (images, videos, etc.) in preview panels is to serve them through Sourcebot's own APIs rather than constructing direct URLs to code hosts like raw.githubusercontent.com. This provides a uniform solution across all supported code hosts (GitHub, GitLab, Bitbucket, Azure DevOps).
Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.
| <div className="w-full flex justify-center bg-white dark:bg-background"> | ||
| <article className="markdown-body dark dark:bg-background w-full max-w-4xl px-6 py-10"> |
There was a problem hiding this comment.
Hardcoded bg-white and unconditional dark class cause theme issues.
bg-whitedoesn't respect the app's theme system. Usebg-backgroundfor both modes.- The
darkclass on the<article>is applied unconditionally, which will causegithub-markdown-cssto always render dark-mode styles, even when the app is in light mode.
Proposed fix
- <div className="w-full flex justify-center bg-white dark:bg-background">
- <article className="markdown-body dark dark:bg-background w-full max-w-4xl px-6 py-10">
+ <div className="w-full flex justify-center bg-background">
+ <article className="markdown-body w-full max-w-4xl px-6 py-10">You'll need to integrate with the app's theme (e.g., via next-themes) to conditionally apply github-markdown-css's dark/light mode class.
As per coding guidelines, "Use Tailwind color classes directly (e.g., border-border, bg-card, text-foreground) instead of CSS variable syntax (e.g., border-[var(--border)])".
🤖 Prompt for AI Agents
In
`@packages/web/src/app/`[domain]/browse/[...path]/components/pureMarkDownPreviewPanel.tsx
around lines 25 - 26, In pureMarkDownPreviewPanel.tsx replace the hardcoded
bg-white on the wrapper with bg-background and remove the unconditional "dark"
class on the <article>; instead wire the component to the app theme (e.g., use
next-themes or the existing theme context) and conditionally add the "dark"
class to the article only when the current theme is dark so github-markdown-css
toggles correctly, and ensure you use Tailwind tokens like
bg-background/border-border/text-foreground rather than CSS variable syntax;
update the JSX in the PureMarkDownPreviewPanel component to read the theme and
apply classes accordingly.
There was a problem hiding this comment.
@brendan-kellam The Comment image shows the CHANGELOG.md file rendered in preview mode with proper markdown formatting—version sections, issue links, and structured headings are all displaying correctly in dark theme. The PR description images demonstrate the README preview working beautifully with the Sourcebot branding, badges, and features section.
Thanks for the screenshot and the +1! The markdown preview feature is working well. Just to clarify—are you planning to address the theme-switching concern I flagged (the hardcoded bg-white and unconditional dark class), or would you like me to help with that implementation? Happy to provide guidance on wiring this up to next-themes if needed.
🧠 Learnings used
Learnt from: CR
Repo: sourcebot-dev/sourcebot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-02-05T22:59:12.488Z
Learning: Applies to **/*.{jsx,tsx} : Use Tailwind color classes directly (e.g., `border-border`, `bg-card`, `text-foreground`) instead of CSS variable syntax (e.g., `border-[var(--border)]`)
| <ReactMarkdown | ||
| remarkPlugins={[remarkGfm]} | ||
| rehypePlugins={[rehypeRaw, rehypeHighlight]} |
There was a problem hiding this comment.
Security: rehype-raw without rehype-sanitize enables XSS.
rehypeRaw passes raw HTML through to the rendered output. Without rehypeSanitize (already a project dependency), any markdown source containing <script>, <iframe>, onerror handlers, etc., will execute in the user's browser. This is especially dangerous since the markdown content comes from external repositories.
🔒 Proposed fix: add rehype-sanitize
import rehypeHighlight from "rehype-highlight";
import "github-markdown-css/github-markdown.css";
import "highlight.js/styles/github-dark.css";
import rehypeRaw from "rehype-raw";
+import rehypeSanitize, { defaultSchema } from "rehype-sanitize";Then update the plugins array — rehypeSanitize must come after rehypeRaw:
- rehypePlugins={[rehypeRaw, rehypeHighlight]}
+ rehypePlugins={[rehypeRaw, rehypeSanitize, rehypeHighlight]}You may need to extend defaultSchema to allowlist elements like <video>, <source>, etc., that your custom components rely on.
🤖 Prompt for AI Agents
In
`@packages/web/src/app/`[domain]/browse/[...path]/components/pureMarkDownPreviewPanel.tsx
around lines 27 - 29, The ReactMarkdown rendering currently uses rehypeRaw
without sanitization (see ReactMarkdown component and the rehypePlugins array
containing rehypeRaw and rehypeHighlight); import and add rehypeSanitize to the
rehypePlugins list immediately after rehypeRaw so raw HTML is sanitized before
rendering, and if your UI requires additional tags/attributes (e.g., video,
source, iframe attrs), extend rehypeSanitize's schema (starting from
defaultSchema) to allowlist those elements/attributes before passing it to
rehypeSanitize.
There was a problem hiding this comment.
@brendan-kellam Thanks for acknowledging this! Given the critical nature of the XSS vulnerability, I'd recommend addressing this before merging the PR. The fix is straightforward—just adding rehype-sanitize to the pipeline—but it's essential for safely rendering markdown from external repositories.
| video: ({ src = "", ...props }) => { | ||
| return ( | ||
| <video | ||
| src={src} | ||
| controls | ||
| preload="metadata" | ||
| className="max-w-full h-auto my-4" | ||
| {...props} | ||
| > | ||
| Your browser does not support the video | ||
| tag. | ||
| </video> | ||
| ); | ||
| }, |
There was a problem hiding this comment.
Video src URLs are not resolved for relative paths.
The img and source handlers rewrite relative URLs using IMAGE_BASE_URL, but the video handler passes src through as-is. Relative video paths in markdown will be broken.
🤖 Prompt for AI Agents
In
`@packages/web/src/app/`[domain]/browse/[...path]/components/pureMarkDownPreviewPanel.tsx
around lines 86 - 99, The video renderer in pureMarkDownPreviewPanel.tsx (the
video: ({ src = "", ...props }) => { ... } handler) currently passes src through
unchanged, so relative video paths break; update this handler to rewrite
relative src the same way the img and source handlers do (use the same
IMAGE_BASE_URL or the existing resolve logic used for img/source) before
rendering the <video>, ensuring the src is resolved for both absolute and
relative URLs while preserving {...props} and controls.

Fix: Add toggle for Markdown preview #794
Summary
This PR adds a toggle to control the visibility of the Markdown preview panel. The preview is now rendered conditionally based on user intent.
Details
Results
Summary by CodeRabbit