Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { SymbolView } from "../../components/AppSymbol";
import { AppText as Text } from "../../components/AppText";
import { useAppearancePreferences } from "../settings/appearance/AppearancePreferencesProvider";
import type { FilePreviewItem } from "../../components/FilePreviewModal";
import { isBotBadgeImage } from "./botBadgeImages";
import { PullRequestMarkdown } from "./PullRequestMarkdown";
import { pullRequestDescriptionContent } from "./pullRequestDescriptionContent";

Expand All @@ -35,7 +36,11 @@ export function PullRequestDescription(props: {
parseMarkdownWithOptions(props.markdown, { gfm: true, html: true, math: false }),
(markdown) => parseMarkdownWithOptions(markdown, { gfm: true, html: false, math: false }),
);
return pullRequestDescriptionContent(document, splitPullRequestBody(props.markdown));
const content = pullRequestDescriptionContent(document, splitPullRequestBody(props.markdown));
return {
...content,
attachments: content.attachments.filter((attachment) => !isBotBadgeImage(attachment.uri)),
};
}, [props.markdown]);
const gallery = useMemo<FilePreviewItem[]>(
() =>
Expand Down
26 changes: 26 additions & 0 deletions apps/mobile/src/features/pull-requests/botBadgeImages.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { describe, expect, it } from "vite-plus/test";
import { isBotBadgeImage } from "./botBadgeImages";

describe("bot badge images", () => {
it("matches bot badges but not screenshots hosted by the same services", () => {
const badges = [
"https://static.devin.ai/assets/gh-devin-review-light.svg?v=4",
"https://cursor.com/assets/images/open-in-web-dark.png",
"https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg",
"https://img.shields.io/badge/P1-orange",
"https://greptile-static-assets.s3.amazonaws.com/badges/ViewArtifacts.svg?v=4",
"https://www.gstatic.com/codereviewagent/high-priority.svg",
"https://user-images.githubusercontent.com/23264/106598434-9e719e00-654f-11eb-9e59-6167043cfa01.png",
];
const screenshots = [
// Cursor cloud agents embed their screenshots and recordings as artifacts.
"https://cursor.com/artifacts/c/art-41567e7e-97f5-46ff-9e6e-df6a85723409",
"https://www.cursor.com/artifacts/c/art-98360ed1-44f8-4459-bea3-43bf93cc74fc",
"https://app.devin.ai/attachments/abc/ss_1.png",
"https://user-images.githubusercontent.com/23264/1-other-upload.png",
"https://storage.googleapis.com/some-bucket/before.png",
];
expect(badges.filter(isBotBadgeImage)).toEqual(badges);
expect(screenshots.filter(isBotBadgeImage)).toEqual([]);
});
});
53 changes: 53 additions & 0 deletions apps/mobile/src/features/pull-requests/botBadgeImages.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/**
* Logos, buttons, and status icons that review bots, agents, and CI stamp into pull requests.
* Each entry is a host plus a path that only serves those assets, so screenshots uploaded to
* the same service (such as cursor.com/artifacts or app.devin.ai/attachments) stay visible.
* Only the description's Attachments strip skips them; markdown still renders them inline.
*/
const BOT_BADGE_PREFIXES = [
// Coding agents and review bots
"static.devin.ai/assets/",
"cursor.com/assets/images/",
"storage.googleapis.com/coderabbit_public_assets/",
"greptile-static-assets.s3.amazonaws.com/badges/",
"static.graphite.dev/",
"qodo.ai/wp-content/uploads/",
"cubic.dev/buttons/",
"github.githubassets.com/static/images/icons/copilot-code-review/",
"gstatic.com/codereviewagent/",
"vercel.com/vercel-agent-view-suggestion-",
"agents-vade-review.vercel.sh/",
"new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/",
"pr-comments-assets.blacksmith.sh/",
"app.roocode.com/cloud-agents/github/",
"app.parameter.ai/_static/icons/",
// CI, deploy, and dependency bots
"img.shields.io/",
"raster.shields.io/",
"dependabot-badges.githubapp.com/",
"developer.mend.io/api/mc/badges/",
"api.securityscorecards.dev/projects/",
"github-app-statics.socket.dev/",
"static.deepsource.com/comment_artifacts/",
"sonarsource.github.io/sonarcloud-github-static-resources/",
"codescene.io/imgs/svg/",
"app.codecov.io/gh/",
"github.com/codecov/engineering-team/assets/",
"codspeed.io/pr-report/",
"static.trunk.io/assets/",
"res.cloudinary.com/snyk/",
"api.segment.io/v1/pixel/",
"cla-assistant.io/pull/badge/",
"vercel.com/static/status/",
"vercel.com/api/www/avatar",
"app.netlify.com/qr-code/",
"workers.cloudflare.com/logo.svg",
// Cloudflare Pages reuses one old GitHub upload as its logo.
"user-images.githubusercontent.com/23264/106598434-9e719e00-654f-11eb-9e59-6167043cfa01.png",
"qr.expo.dev/",
];

export function isBotBadgeImage(href: string) {
const path = href.replace(/^https?:\/\/(?:www\.)?/i, "").toLowerCase();
return BOT_BADGE_PREFIXES.some((prefix) => path.startsWith(prefix));
}
Loading