Skip to content

feat: add markdown preview with toggle to view raw source#863

Open
Naresh-official wants to merge 5 commits intosourcebot-dev:mainfrom
Naresh-official:feature/markdown-preview-toggle
Open

feat: add markdown preview with toggle to view raw source#863
Naresh-official wants to merge 5 commits intosourcebot-dev:mainfrom
Naresh-official:feature/markdown-preview-toggle

Conversation

@Naresh-official
Copy link

@Naresh-official Naresh-official commented Feb 7, 2026

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

  • Introduces a UI toggle to enable or disable the Markdown preview
  • Preview component is only mounted when enabled

Results

Screenshot from 2026-02-07 16-28-39 Screenshot from 2026-02-07 16-29-11

Summary by CodeRabbit

Release Notes

  • New Features
    • Added markdown file preview with syntax highlighting, video embedding, and theme-aware rendering.
    • Added Preview/Code view toggle for markdown files to seamlessly switch between rendered and source content.
    • Improved file type detection and language identification for enhanced browsing experience.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 7, 2026

Walkthrough

Adds markdown preview functionality to the code browser with a toggle between preview and code viewing modes. Introduces UI components for toggling views, rendering markdown with syntax highlighting and theme support, and API endpoints for serving raw repository files with proper MIME type handling.

Changes

Cohort / File(s) Summary
Dependencies
packages/web/package.json
Updated @radix-ui/react-toggle dependency and added @radix-ui/react-toggle-group, github-markdown-css, and rehype-highlight packages for markdown rendering and toggle UI components.
Preview Panel Client-Side Logic
packages/web/src/app/[domain]/browse/.../components/codePreviewPanelClient.tsx
New client component that manages view mode state (preview/code) and conditionally renders either markdown or code previews based on file language and user selection.
Markdown Preview Rendering
packages/web/src/app/[domain]/browse/.../components/pureMarkDownPreviewPanel.tsx
New component that renders markdown with theme-aware GitHub styling, syntax highlighting, lazy-loaded images, video support, and custom URL resolution for internal repository links.
Toggle UI Components
packages/web/src/components/ui/toggle-group.tsx
New Radix UI-based toggle group and toggle item components with context-based variant and size styling propagation.
Component Refactoring
packages/web/src/app/[domain]/browse/.../components/sourcePreviewPanel.tsx, packages/web/src/app/[domain]/browse/.../page.tsx
Renamed CodePreviewPanel to SourcePreviewPanel and updated to use new SourcePreviewPanelClient component with consistent prop interfaces.
Raw File API Route
packages/web/src/app/api/[domain]/raw/[...path]/route.ts
New Next.js API endpoint for serving raw repository files with MIME type detection, path parameter parsing (supporting both new and legacy URL structures), and cache headers.
Git Raw File Fetching
packages/web/src/features/git/getRawFileSourceApi.ts
New API module that fetches binary files from repositories at specified Git references, with error handling and automatic fallback to repository default branch or HEAD.
Language Detection Enhancement
packages/web/src/lib/languageDetection.ts
Extended language detection with an override map for ambiguous file extensions to provide explicit language mappings before falling back to standard detection.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant SourcePreviewPanelClient
    participant MarkdownPreview as PureMarkDownPreviewPanel
    participant CodePreview as PureCodePreviewPanel
    participant API as Raw File API

    User->>SourcePreviewPanelClient: View markdown file
    SourcePreviewPanelClient->>SourcePreviewPanelClient: Detect language = "Markdown"
    SourcePreviewPanelClient->>User: Show Preview/Code toggle
    
    User->>SourcePreviewPanelClient: Click "Preview" mode
    SourcePreviewPanelClient->>MarkdownPreview: Render with markdown source
    MarkdownPreview->>MarkdownPreview: Parse markdown & detect URLs
    
    alt Internal Repository Link
        MarkdownPreview->>API: Fetch raw file from /api/{domain}/raw/{path}
        API->>API: Resolve Git reference
        API->>User: Return file with Content-Type
        MarkdownPreview->>MarkdownPreview: Render with resolved URL
    else External Link
        MarkdownPreview->>MarkdownPreview: Render with target="_blank"
    end
    
    MarkdownPreview->>User: Display styled markdown with syntax highlighting
    
    User->>SourcePreviewPanelClient: Click "Code" mode
    SourcePreviewPanelClient->>CodePreview: Render source code
    CodePreview->>User: Display syntax-highlighted code
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

sourcebot-team

Suggested reviewers

  • msukkari
  • brendan-kellam
🚥 Pre-merge checks | ✅ 2 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Merge Conflict Detection ⚠️ Warning ❌ Merge conflicts detected (55 files):

⚔️ .env.development (content)
⚔️ .github/workflows/_build.yml (content)
⚔️ .github/workflows/release-mcp.yml (content)
⚔️ CHANGELOG.md (content)
⚔️ CLAUDE.md (content)
⚔️ CONTRIBUTING.md (content)
⚔️ Dockerfile (content)
⚔️ LICENSE.md (content)
⚔️ docs/docs/features/mcp-server.mdx (content)
⚔️ ee/LICENSE (content)
⚔️ packages/backend/package.json (content)
⚔️ packages/backend/src/connectionManager.ts (content)
⚔️ packages/backend/src/posthog.ts (content)
⚔️ packages/backend/src/posthogEvents.ts (content)
⚔️ packages/backend/src/repoCompileUtils.ts (content)
⚔️ packages/backend/src/repoIndexManager.ts (content)
⚔️ packages/db/prisma/schema.prisma (content)
⚔️ packages/mcp/CHANGELOG.md (content)
⚔️ packages/mcp/README.md (content)
⚔️ packages/mcp/package.json (content)
⚔️ packages/mcp/src/client.ts (content)
⚔️ packages/mcp/src/index.ts (content)
⚔️ packages/mcp/src/schemas.ts (content)
⚔️ packages/mcp/src/types.ts (content)
⚔️ packages/mcp/src/utils.ts (content)
⚔️ packages/shared/src/constants.ts (content)
⚔️ packages/shared/src/entitlements.ts (content)
⚔️ packages/shared/src/env.client.ts (content)
⚔️ packages/shared/src/env.server.ts (content)
⚔️ packages/shared/src/version.ts (content)
⚔️ packages/web/package.json (content)
⚔️ packages/web/src/app/[domain]/askgh/[owner]/[repo]/components/landingPage.tsx (content)
⚔️ packages/web/src/app/[domain]/askgh/[owner]/[repo]/components/repoIndexedGuard.tsx (content)
⚔️ packages/web/src/app/[domain]/askgh/[owner]/[repo]/page.tsx (content)
⚔️ packages/web/src/app/[domain]/browse/layout.tsx (content)
⚔️ packages/web/src/app/[domain]/chat/[id]/page.tsx (content)
⚔️ packages/web/src/app/[domain]/components/navigationMenu/index.tsx (content)
⚔️ packages/web/src/app/[domain]/components/repositoryCarousel.tsx (content)
⚔️ packages/web/src/app/[domain]/components/topBar.tsx (content)
⚔️ packages/web/src/app/[domain]/components/whatsNewIndicator.tsx (content)
⚔️ packages/web/src/app/[domain]/layout.tsx (content)
⚔️ packages/web/src/app/[domain]/search/components/searchResultsPage.tsx (content)
⚔️ packages/web/src/app/[domain]/search/page.tsx (content)
⚔️ packages/web/src/app/[domain]/settings/layout.tsx (content)
⚔️ packages/web/src/app/[domain]/settings/license/page.tsx (content)
⚔️ packages/web/src/app/api/(server)/chat/blocking/route.ts (content)
⚔️ packages/web/src/app/api/(server)/chat/route.ts (content)
⚔️ packages/web/src/app/components/authSecurityNotice.tsx (content)
⚔️ packages/web/src/app/layout.tsx (content)
⚔️ packages/web/src/app/posthogProvider.tsx (content)
⚔️ packages/web/src/features/chat/agent.ts (content)
⚔️ packages/web/src/features/chat/components/chatThread/answerCard.tsx (content)
⚔️ packages/web/src/features/git/getTreeApi.ts (content)
⚔️ packages/web/src/lib/languageDetection.ts (content)
⚔️ yarn.lock (content)

These conflicts must be resolved before merging into main.
Resolve conflicts locally and push changes to this branch.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main feature: adding markdown preview functionality with a toggle to switch between preview and raw source views. This directly aligns with the primary changes in the PR.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
⚔️ Resolve merge conflicts (beta)
  • Auto-commit resolved conflicts to branch feature/markdown-preview-toggle
  • Post resolved changes as copyable diffs in a comment

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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: Hardcoded text-blue-600 doesn't respect the app's theme.

Use a semantic Tailwind color class (e.g., text-primary or text-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 the img handler (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: defaultValue is redundant in a controlled ToggleGroup.

Since you're passing value={viewMode}, the component is controlled and defaultValue is ignored by Radix. Remove it to avoid confusion.

Fix
                        <ToggleGroup
                            type="single"
-                            defaultValue="preview"
                            value={viewMode}
                            onValueChange={(value) => value && setViewMode(value)}
                        >

23-24: Improve isMarkdown detection readability and type safety.

  1. The "gcc machine description" check is a necessary workaround — linguist-languages sometimes misidentifies .md files with this language. Add a comment explaining this.
  2. The viewMode state should use a union type ("preview" | "code") instead of string for type safety.
  3. 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 .md files, not "md", so the language === "md" check may be redundant. Use "markdown" to match the library's actual output.

Comment on lines 25 to 26
<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">
Copy link
Contributor

@coderabbitai coderabbitai bot Feb 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Hardcoded bg-white and unconditional dark class cause theme issues.

  • bg-white doesn't respect the app's theme system. Use bg-background for both modes.
  • The dark class on the <article> is applied unconditionally, which will cause github-markdown-css to 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.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image +1

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@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)]`)

Comment on lines +27 to +29
<ReactMarkdown
remarkPlugins={[remarkGfm]}
rehypePlugins={[rehypeRaw, rehypeHighlight]}
Copy link
Contributor

@coderabbitai coderabbitai bot Feb 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

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.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@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.

Comment on lines 86 to 99
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>
);
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

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.

@Naresh-official Naresh-official marked this pull request as draft February 7, 2026 13:28
brendan-kellam and others added 4 commits February 7, 2026 16:33
- Implement dynamic theme switching for markdown rendering using next-themes
- Load GitHub markdown CSS and highlight.js themes based on dark/light mode
- Enhance image handling with theme-aware source resolution
- Add internal API to serve raw files directly from git repositories.
- Update markdown preview to resolve images and links using the new API.
- Remove dependency on external GitHub raw URLs.
- Fix local backend development script.
@Naresh-official
Copy link
Author

Thanks for the review and suggestions. I’ve now incorporated the changes you mentioned and updated the implementation accordingly.
Let me know if there’s anything else I should improve.

@Naresh-official Naresh-official marked this pull request as ready for review February 16, 2026 16:17
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
packages/web/src/app/[domain]/browse/[...path]/components/sourcePreviewPanel.tsx (1)

77-83: ⚠️ Potential issue | 🔴 Critical

Missing codeHostType, domain, and externalWebUrl props passed to SourcePreviewPanelClient.

As noted in the codePreviewPanelClient.tsx review, this call site is missing required props. codeHostType is available from repoInfoResponse.codeHostType (line 34) and externalWebUrl from repoInfoResponse.externalWebUrl. However, domain is not in scope here — it needs to be threaded from the page via SourcePreviewPanelProps.

Proposed fix (assuming `domain` is added to `SourcePreviewPanelProps`)
 interface SourcePreviewPanelProps {
     path: string;
     repoName: string;
     revisionName?: string;
+    domain: string;
 }

-export const SourcePreviewPanel = async ({ path, repoName, revisionName }: SourcePreviewPanelProps) => {
+export const SourcePreviewPanel = async ({ path, repoName, revisionName, domain }: SourcePreviewPanelProps) => {
 <SourcePreviewPanelClient
     source={fileSourceResponse.source}
     language={fileSourceResponse.language}
     repoName={repoName}
     path={path}
     revisionName={revisionName ?? 'HEAD'}
+    codeHostType={repoInfoResponse.codeHostType}
+    externalWebUrl={repoInfoResponse.externalWebUrl ?? undefined}
+    domain={domain}
 />
packages/web/src/app/[domain]/browse/[...path]/page.tsx (1)

92-97: ⚠️ Potential issue | 🔴 Critical

domain should be passed to SourcePreviewPanel to complete the prop chain.

The domain parameter is available from params (it's part of the [domain] route segment), but it's not destructured or forwarded. Once SourcePreviewPanelProps is updated to require domain, this call site needs to pass it.

Proposed fix
 const params = await props.params;

 const {
     path: _rawPath,
+    domain,
 } = params;
 <SourcePreviewPanel
     path={path}
     repoName={repoName}
     revisionName={revisionName}
+    domain={domain}
 />

Note: domain is missing from the BrowsePageProps type at line 69 — it should be added there as well (it's already part of the Next.js route [domain] segment type at line 46).

🤖 Fix all issues with AI agents
In
`@packages/web/src/app/`[domain]/browse/[...path]/components/codePreviewPanelClient.tsx:
- Around line 8-17: SourcePreviewPanelClient is missing required props
codeHostType and domain at its call site; thread these from page.tsx through
SourcePreviewPanel to SourcePreviewPanelClient instead of making them optional:
add domain and codeHostType to SourcePreviewPanelProps, accept them in
SourcePreviewPanel (pull codeHostType from repoInfoResponse there), pass both
props into the <SourcePreviewPanelClient .../> invocation, and update
SourcePreviewPanelClientProps to include and use them when rendering
PureMarkDownPreviewPanel; also update page.tsx to supply params.domain into the
top-level SourcePreviewPanel.

In
`@packages/web/src/app/`[domain]/browse/[...path]/components/pureMarkDownPreviewPanel.tsx:
- Around line 36-65: The component's useEffect currently injects external CDN
links for GitHub markdown and Highlight.js styles which breaks
offline/self-hosted deployments and uses a terse arrow callback in the .forEach
that triggers a Biome lint warning; replace the runtime CDN injection in the
useEffect of PureMarkDownPreviewPanel (and any helper that creates
markdownLink/highlightLink) by bundling or importing the two CSS files into the
app (e.g., add them to static assets or import them as modules) and point the
injected link.href to the local asset paths or simply import the styles and
conditionally apply a CSS class based on currentTheme, and update the
.forEach((el) => el.remove()) to use an explicit block .forEach((el) => {
el.remove(); }) to satisfy the linter.

In `@packages/web/src/app/api/`[domain]/raw/[...path]/route.ts:
- Around line 6-35: The getContentType function currently returns
"image/svg+xml" for .svg files which allows inline SVG execution; update the
route handling that serves files so that responses for SVG files do not render
in-page: detect SVG responses using getContentType(filename) or by checking the
file extension and add either a strict Content-Security-Policy: sandbox header
(e.g. "sandbox;") or set Content-Disposition: attachment to force download for
.svg, and ensure other content types are unchanged; adjust any response-building
logic that references getContentType to include this special-case header for
SVGs.

In `@packages/web/src/features/git/getRawFileSourceApi.ts`:
- Around line 40-48: Remove the exploratory/dev comments around binary handling
and use the proper simple-git v3 API call: replace the incorrect
git.binaryCatFile(["blob", sha]) invocation with git.binaryCatFile(["-p", sha])
to match git cat-file -p semantics and retrieve the blob content; keep the
single call site in getRawFileSourceApi.ts and ensure binaryCatFile is used
directly (no extra commentary about availability).
🧹 Nitpick comments (6)
packages/web/src/features/git/getRawFileSourceApi.ts (1)

50-68: Fragile error-message matching for git failures.

String matching on error messages (e.g., "exists on disk", "fatal: path") is brittle across git versions and locales. This is acceptable for an MVP but consider a more robust approach long-term (e.g., checking git exit codes if simple-git exposes them via the error object).

packages/web/src/app/api/[domain]/raw/[...path]/route.ts (2)

59-93: The "-" separator approach relies on no repo path segment being exactly "-".

This works because path.indexOf("-") matches only segments that are the literal string "-", not segments containing a hyphen (like "my-repo"). However, this means a repository named literally - (while unlikely) would break the parsing. Worth a brief comment in the code to document this convention.

Additionally, in the legacy (no-separator) branch, the path.length < 3 check at line 74 is a duplicate of line 45 — both branches reach the same guard. It's harmless but redundant.


108-114: immutable cache directive is semantically misleading with max-age=3600.

immutable tells the browser to never revalidate during the freshness window, but 1 hour is short for content addressed by git ref. If ref is a commit SHA, immutable with a longer max-age would be correct. If ref can be HEAD or a branch name (mutable), immutable is incorrect since the content can change. Consider varying the caching strategy based on whether ref is a mutable reference.

packages/web/src/app/[domain]/browse/[...path]/components/pureMarkDownPreviewPanel.tsx (1)

27-28: Unused props codeHostType and externalWebUrl.

These are destructured and immediately aliased to underscore-prefixed variables (_codeHostType, _externalWebUrl), indicating they're unused. Since the component now uses the internal API for URL resolution (as per the learnings), consider removing them from both the interface and destructuring to reduce API surface confusion. If they're being kept for future use, a brief comment would clarify intent.

packages/web/src/app/[domain]/browse/[...path]/components/codePreviewPanelClient.tsx (1)

29-29: Consider a narrower type for viewMode.

Using useState<string>("preview") is overly broad. A union type makes the intent clearer and prevents invalid states.

♻️ Proposed fix
-    const [viewMode, setViewMode] = useState<string>("preview");
+    const [viewMode, setViewMode] = useState<"preview" | "code">("preview");
packages/web/src/app/[domain]/browse/[...path]/components/sourcePreviewPanel.tsx (1)

6-6: File name codePreviewPanelClient is inconsistent with the exported component SourcePreviewPanelClient.

The import from "./codePreviewPanelClient" references a file that exports SourcePreviewPanelClient. The file should be renamed to sourcePreviewPanelClient.tsx to match the export and avoid confusion during maintenance.

Comment on lines +8 to +17
interface SourcePreviewPanelClientProps {
path: string;
repoName: string;
revisionName: string;
source: string;
language: string;
codeHostType: string;
externalWebUrl?: string;
domain: string;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify the TypeScript props issue

echo "=== SourcePreviewPanelClientProps interface ==="
rg -n "interface SourcePreviewPanelClientProps" -g '*.tsx' -g '*.ts' -A 15

echo ""
echo "=== Call sites of SourcePreviewPanelClient ==="
rg -n "<SourcePreviewPanelClient" -g '*.tsx' -g '*.ts' -B 2 -A 10

echo ""
echo "=== SourcePreviewPanelProps interface ==="
rg -n "interface SourcePreviewPanelProps" -g '*.tsx' -g '*.ts' -A 15

Repository: sourcebot-dev/sourcebot

Length of output: 5066


🏁 Script executed:

#!/bin/bash
# Check for type overrides around the call site and implementation

echo "=== Check for type overrides or 'as any' ==="
rg -n "SourcePreviewPanelClient" -g '*.tsx' -g '*.ts' -B 5 -A 12 | grep -E "(as any|as const|type |@ts-)"

echo ""
echo "=== SourcePreviewPanelClient implementation (full destructure) ==="
sed -n '19,35p' packages/web/src/app/[domain]/browse/[...path]/components/codePreviewPanelClient.tsx

echo ""
echo "=== Check if domain or codeHostType are used in the component ==="
rg -n "domain|codeHostType" packages/web/src/app/[domain]/browse/[...path]/components/codePreviewPanelClient.tsx

Repository: sourcebot-dev/sourcebot

Length of output: 810


🏁 Script executed:

#!/bin/bash
# Verify that domain and codeHostType are actually required by child components

echo "=== Lines around 66-68 where domain and codeHostType are passed ==="
sed -n '60,75p' packages/web/src/app/[domain]/browse/[...path]/components/codePreviewPanelClient.tsx

echo ""
echo "=== Check SourcePreviewPanel to see if it has access to domain ==="
head -50 packages/web/src/app/[domain]/browse/[...path]/components/sourceReviewPanel.tsx

echo ""
echo "=== Find where SourcePreviewPanel is called from ==="
rg -n "<SourcePreviewPanel" -g '*.tsx' -g '*.ts' -B 5 -A 5

Repository: sourcebot-dev/sourcebot

Length of output: 3430


🏁 Script executed:

#!/bin/bash
# Check the full SourcePreviewPanel component to see if it should receive domain/codeHostType

echo "=== Full SourcePreviewPanel component ==="
cat packages/web/src/app/[domain]/browse/[...path]/components/sourcePreviewPanel.tsx

echo ""
echo "=== page.tsx context - does it have domain/codeHostType? ==="
sed -n '1,100p' packages/web/src/app/[domain]/browse/[...path]/page.tsx | head -50

Repository: sourcebot-dev/sourcebot

Length of output: 5310


🏁 Script executed:

#!/bin/bash
# Confirm: Check if repoInfoResponse has the data needed

echo "=== Does SourcePreviewPanel have access to codeHostType/externalWebUrl? ==="
grep -n "repoInfoResponse\." packages/web/src/app/[domain]/browse/[...path]/components/sourcePreviewPanel.tsx

echo ""
echo "=== Check page.tsx params structure ==="
sed -n '40,100p' packages/web/src/app/[domain]/browse/[...path]/page.tsx

Repository: sourcebot-dev/sourcebot

Length of output: 2351


Missing required props codeHostType and domain at the call site will cause a TypeScript compilation error.

SourcePreviewPanelClientProps declares codeHostType: string and domain: string as required, but the parent SourcePreviewPanel (in sourcePreviewPanel.tsx, lines 77-82) does not pass them:

<SourcePreviewPanelClient
    source={fileSourceResponse.source}
    language={fileSourceResponse.language}
    repoName={repoName}
    path={path}
    revisionName={revisionName ?? 'HEAD'}
/>

SourcePreviewPanel has access to codeHostType from repoInfoResponse, but neither domain nor codeHostType are available in SourcePreviewPanelProps. domain needs to be threaded from page.tsx (which has it in params) through to SourcePreviewPanel, and both codeHostType and domain must be passed to SourcePreviewPanelClient, which uses them when rendering PureMarkDownPreviewPanel.

You need to either:

  1. Thread domain and codeHostType through the component hierarchy from page.tsxSourcePreviewPanelSourcePreviewPanelClient, or
  2. Make these props optional and handle their absence in PureMarkDownPreviewPanel.
🤖 Prompt for AI Agents
In
`@packages/web/src/app/`[domain]/browse/[...path]/components/codePreviewPanelClient.tsx
around lines 8 - 17, SourcePreviewPanelClient is missing required props
codeHostType and domain at its call site; thread these from page.tsx through
SourcePreviewPanel to SourcePreviewPanelClient instead of making them optional:
add domain and codeHostType to SourcePreviewPanelProps, accept them in
SourcePreviewPanel (pull codeHostType from repoInfoResponse there), pass both
props into the <SourcePreviewPanelClient .../> invocation, and update
SourcePreviewPanelClientProps to include and use them when rendering
PureMarkDownPreviewPanel; also update page.tsx to supply params.domain into the
top-level SourcePreviewPanel.

Comment on lines +36 to +65
useEffect(() => {
const isDark = currentTheme === "dark";

document
.querySelectorAll(
"link[data-markdown-theme], link[data-highlight-theme]",
)
.forEach((el) => el.remove());

const markdownLink = document.createElement("link");
markdownLink.rel = "stylesheet";
markdownLink.href = isDark
? "https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/5.5.1/github-markdown-dark.min.css"
: "https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/5.5.1/github-markdown-light.min.css";
markdownLink.setAttribute("data-markdown-theme", "true");
document.head.appendChild(markdownLink);

const highlightLink = document.createElement("link");
highlightLink.rel = "stylesheet";
highlightLink.href = isDark
? "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github-dark.min.css"
: "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github.min.css";
highlightLink.setAttribute("data-highlight-theme", "true");
document.head.appendChild(highlightLink);

return () => {
markdownLink.remove();
highlightLink.remove();
};
}, [currentTheme]);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

External CDN dependency for theme CSS breaks offline/air-gapped deployments.

The component dynamically loads CSS from cdnjs.cloudflare.com at runtime. For a self-hosted tool (as Sourcebot is), this means the markdown preview will be unstyled if the instance has no internet access. Consider bundling these CSS files or importing them statically.

Also, per the Biome hint: the arrow in .forEach((el) => el.remove()) at line 43 implicitly returns the result of el.remove(). Use braces to satisfy the linter:

Fix for Biome lint warning
-            .forEach((el) => el.remove());
+            .forEach((el) => { el.remove(); });
📝 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.

Suggested change
useEffect(() => {
const isDark = currentTheme === "dark";
document
.querySelectorAll(
"link[data-markdown-theme], link[data-highlight-theme]",
)
.forEach((el) => el.remove());
const markdownLink = document.createElement("link");
markdownLink.rel = "stylesheet";
markdownLink.href = isDark
? "https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/5.5.1/github-markdown-dark.min.css"
: "https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/5.5.1/github-markdown-light.min.css";
markdownLink.setAttribute("data-markdown-theme", "true");
document.head.appendChild(markdownLink);
const highlightLink = document.createElement("link");
highlightLink.rel = "stylesheet";
highlightLink.href = isDark
? "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github-dark.min.css"
: "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github.min.css";
highlightLink.setAttribute("data-highlight-theme", "true");
document.head.appendChild(highlightLink);
return () => {
markdownLink.remove();
highlightLink.remove();
};
}, [currentTheme]);
useEffect(() => {
const isDark = currentTheme === "dark";
document
.querySelectorAll(
"link[data-markdown-theme], link[data-highlight-theme]",
)
.forEach((el) => { el.remove(); });
const markdownLink = document.createElement("link");
markdownLink.rel = "stylesheet";
markdownLink.href = isDark
? "https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/5.5.1/github-markdown-dark.min.css"
: "https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/5.5.1/github-markdown-light.min.css";
markdownLink.setAttribute("data-markdown-theme", "true");
document.head.appendChild(markdownLink);
const highlightLink = document.createElement("link");
highlightLink.rel = "stylesheet";
highlightLink.href = isDark
? "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github-dark.min.css"
: "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github.min.css";
highlightLink.setAttribute("data-highlight-theme", "true");
document.head.appendChild(highlightLink);
return () => {
markdownLink.remove();
highlightLink.remove();
};
}, [currentTheme]);
🧰 Tools
🪛 Biome (2.3.14)

[error] 43-43: This callback passed to forEach() iterable method should not return a value.

Either remove this return or remove the returned value.

(lint/suspicious/useIterableCallbackReturn)

🤖 Prompt for AI Agents
In
`@packages/web/src/app/`[domain]/browse/[...path]/components/pureMarkDownPreviewPanel.tsx
around lines 36 - 65, The component's useEffect currently injects external CDN
links for GitHub markdown and Highlight.js styles which breaks
offline/self-hosted deployments and uses a terse arrow callback in the .forEach
that triggers a Biome lint warning; replace the runtime CDN injection in the
useEffect of PureMarkDownPreviewPanel (and any helper that creates
markdownLink/highlightLink) by bundling or importing the two CSS files into the
app (e.g., add them to static assets or import them as modules) and point the
injected link.href to the local asset paths or simply import the styles and
conditionally apply a CSS class based on currentTheme, and update the
.forEach((el) => el.remove()) to use an explicit block .forEach((el) => {
el.remove(); }) to satisfy the linter.

Comment on lines +6 to +35
const getContentType = (filename: string): string => {
const ext = filename.split(".").pop()?.toLowerCase();
switch (ext) {
case "png":
return "image/png";
case "jpg":
case "jpeg":
return "image/jpeg";
case "gif":
return "image/gif";
case "svg":
return "image/svg+xml";
case "webp":
return "image/webp";
case "ico":
return "image/x-icon";
case "bmp":
return "image/bmp";
case "tiff":
return "image/tiff";
case "pdf":
return "application/pdf";
case "mp4":
return "video/mp4";
case "webm":
return "video/webm";
default:
return "application/octet-stream";
}
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Serving image/svg+xml without sandboxing is an XSS vector.

SVGs can embed <script> tags and event handlers. If a user navigates directly to /api/{domain}/raw/{repo}/-/malicious.svg, the browser will execute embedded JavaScript in the origin's context. Since this endpoint serves repo content from potentially untrusted sources, this is exploitable.

Consider mitigating with a Content-Security-Policy: sandbox header or Content-Disposition: attachment for SVG responses:

🔒 Proposed mitigation
     // eslint-disable-next-line `@typescript-eslint/no-explicit-any`
     return new Response(new Blob([buffer as any]), {
         headers: {
             "Content-Type": contentType,
             "Cache-Control": "public, max-age=3600, immutable",
+            // Sandbox SVG and HTML to prevent script execution
+            ...(contentType.includes("svg") || contentType.includes("html")
+                ? { "Content-Security-Policy": "sandbox" }
+                : {}),
         },
     });
🤖 Prompt for AI Agents
In `@packages/web/src/app/api/`[domain]/raw/[...path]/route.ts around lines 6 -
35, The getContentType function currently returns "image/svg+xml" for .svg files
which allows inline SVG execution; update the route handling that serves files
so that responses for SVG files do not render in-page: detect SVG responses
using getContentType(filename) or by checking the file extension and add either
a strict Content-Security-Policy: sandbox header (e.g. "sandbox;") or set
Content-Disposition: attachment to force download for .svg, and ensure other
content types are unchanged; adjust any response-building logic that references
getContentType to include this special-case header for SVGs.

Comment on lines +40 to +48
// simple-git's binaryCatFile returns the raw buffer from git cat-file -p <sha>
// Note: binaryCatFile is not available in all simple-git versions, checking typing...
// If simple-git doesn't support binaryCatFile directly in types, we use cat-file with buffer encoding.
// Assuming simple-git v3+ supports this via custom options or command execution.

// Using 'buffer' encoding with show/cat-file is tricky with simple-git's high-level API which returns string by default.
// The most reliable way for binary data with simple-git is using `binaryCatFile` if available or `raw` with buffer handling.
// Let's use `git.binaryCatFile` which is designed for this.
const buffer = await git.binaryCatFile(["blob", sha]);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# Check simple-git version in package.json
cat packages/web/package.json | jq '.dependencies["simple-git"] // .devDependencies["simple-git"]'

Repository: sourcebot-dev/sourcebot

Length of output: 76


🏁 Script executed:

# Read the actual file to confirm code state
cat -n packages/web/src/features/git/getRawFileSourceApi.ts | head -70

Repository: sourcebot-dev/sourcebot

Length of output: 3632


🏁 Script executed:

# Check if simple-git is installed and search for binaryCatFile
rg "binaryCatFile" --type ts --type tsx -i

Repository: sourcebot-dev/sourcebot

Length of output: 94


🏁 Script executed:

# Search for binaryCatFile usage/definition in the codebase
rg "binaryCatFile" --type ts -i

Repository: sourcebot-dev/sourcebot

Length of output: 993


🏁 Script executed:

# Check simple-git type definitions for binaryCatFile
fd "index.d.ts" node_modules/simple-git --exec cat {} \; 2>/dev/null | head -300

Repository: sourcebot-dev/sourcebot

Length of output: 49


🏁 Script executed:

# Find simple-git type definition files
fd -t f "*.d.ts" node_modules/simple-git 2>/dev/null | head -10

Repository: sourcebot-dev/sourcebot

Length of output: 49


🏁 Script executed:

# Alternative: check simple-git package.json for main entry
cat node_modules/simple-git/package.json | jq '.types, .main, .exports'

Repository: sourcebot-dev/sourcebot

Length of output: 135


🏁 Script executed:

# Search for binaryCatFile in simple-git package
find node_modules/simple-git -type f \( -name "*.d.ts" -o -name "*.ts" \) -exec grep -l "binaryCatFile" {} \; 2>/dev/null

Repository: sourcebot-dev/sourcebot

Length of output: 49


🏁 Script executed:

# Since node_modules isn't available in sandbox, let's check the package-lock or yarn.lock
# for the exact version that will be installed
cat packages/web/package-lock.json | jq '.dependencies["simple-git"] // .packages["node_modules/simple-git"]' 2>/dev/null | head -20

Repository: sourcebot-dev/sourcebot

Length of output: 129


🌐 Web query:

simple-git v3.27.0 binaryCatFile API documentation

💡 Result:

binaryCatFile (simple-git v3.27.0) — API

What it does: Same as catFile, but returns the native Buffer from git cat-file’s stdout (ie, binary-safe output). [1]

Signature (TypeScript):

binaryCatFile(options: string[], callback?: SimpleGitTaskCallback<any>): Response<any>;

[1]

Parameters

  • options: string[] — arguments passed to git cat-file (for example ['-p', '<object>']). [1]

Return

  • Response<any> (promise-like / thenable returned by simple-git). The resolved value is the raw binary output (Buffer). [1]

Example

import simpleGit from 'simple-git';

const git = simpleGit();
const buf = await git.binaryCatFile(['-p', 'HEAD:path/to/image.png']);
// buf is a Buffer

[1]

Related

  • catFile(options) returns string output (non-binary-focused), whereas binaryCatFile is explicitly for Buffer output. [1]

Sources

[1] simple-git v3.27.0 type definitions (dist/typings/simple-git.d.ts) on UNPKG. (app.unpkg.com)

Citations:


Remove leftover development comments and fix incorrect binaryCatFile API usage.

Lines 40-47 contain exploratory comments expressing uncertainty about binaryCatFile availability. These should be removed—binaryCatFile is a standard API in simple-git v3.27.0 (your project's version).

However, there's a critical functional issue: Line 48 uses git.binaryCatFile(["blob", sha]), which translates to git cat-file blob <sha>. This is invalid git syntax. The correct usage is git.binaryCatFile(["-p", sha]) to print/retrieve the blob content, matching the git cat-file -p option for reading object contents.

Fix both the comments and the API arguments:

Suggested fix
-                // 2. Read the binary content using cat-file
-                // simple-git's binaryCatFile returns the raw buffer from git cat-file -p <sha>
-                // Note: binaryCatFile is not available in all simple-git versions, checking typing...
-                // If simple-git doesn't support binaryCatFile directly in types, we use cat-file with buffer encoding.
-                // Assuming simple-git v3+ supports this via custom options or command execution.
-
-                // Using 'buffer' encoding with show/cat-file is tricky with simple-git's high-level API which returns string by default.
-                // The most reliable way for binary data with simple-git is using `binaryCatFile` if available or `raw` with buffer handling.
-                // Let's use `git.binaryCatFile` which is designed for this.
-                const buffer = await git.binaryCatFile(["blob", sha]);
+                // Read the binary blob content
+                const buffer = await git.binaryCatFile(["-p", sha]);
🤖 Prompt for AI Agents
In `@packages/web/src/features/git/getRawFileSourceApi.ts` around lines 40 - 48,
Remove the exploratory/dev comments around binary handling and use the proper
simple-git v3 API call: replace the incorrect git.binaryCatFile(["blob", sha])
invocation with git.binaryCatFile(["-p", sha]) to match git cat-file -p
semantics and retrieve the blob content; keep the single call site in
getRawFileSourceApi.ts and ensure binaryCatFile is used directly (no extra
commentary about availability).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants