| author | |
| committer | |
| log | 81dbc870dad7b64d9920c7526862f52993266c93 |
| tree | de93c9d0cbf4c0878ba9f81b328a7571dc3a5db4 |
| parent | d07a07f33c0a1b317dea6234636976231c3688b5 |
| signature | Signed by PGP key B5690EEEBB952194 |
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>3 files changed, 85 insertions(+), 1 deletions(-)
apps/mobile/src/features/pull-requests/PullRequestDescription.tsx+6-1| ... | @@ -15,6 +15,7 @@ import { SymbolView } from "../../components/AppSymbol"; | ... | @@ -15,6 +15,7 @@ import { SymbolView } from "../../components/AppSymbol"; |
| 15 | import { AppText as Text } from "../../components/AppText"; | 15 | import { AppText as Text } from "../../components/AppText"; |
| 16 | import { useAppearancePreferences } from "../settings/appearance/AppearancePreferencesProvider"; | 16 | import { useAppearancePreferences } from "../settings/appearance/AppearancePreferencesProvider"; |
| 17 | import type { FilePreviewItem } from "../../components/FilePreviewModal"; | 17 | import type { FilePreviewItem } from "../../components/FilePreviewModal"; |
| 18 | import { isBotBadgeImage } from "./botBadgeImages"; | ||
| 18 | import { PullRequestMarkdown } from "./PullRequestMarkdown"; | 19 | import { PullRequestMarkdown } from "./PullRequestMarkdown"; |
| 19 | import { pullRequestDescriptionContent } from "./pullRequestDescriptionContent"; | 20 | import { pullRequestDescriptionContent } from "./pullRequestDescriptionContent"; |
| 20 | 21 | ||
| ... | @@ -35,7 +36,11 @@ export function PullRequestDescription(props: { | ... | @@ -35,7 +36,11 @@ export function PullRequestDescription(props: { |
| 35 | parseMarkdownWithOptions(props.markdown, { gfm: true, html: true, math: false }), | 36 | parseMarkdownWithOptions(props.markdown, { gfm: true, html: true, math: false }), |
| 36 | (markdown) => parseMarkdownWithOptions(markdown, { gfm: true, html: false, math: false }), | 37 | (markdown) => parseMarkdownWithOptions(markdown, { gfm: true, html: false, math: false }), |
| 37 | ); | 38 | ); |
| 38 | return pullRequestDescriptionContent(document, splitPullRequestBody(props.markdown)); | 39 | const content = pullRequestDescriptionContent(document, splitPullRequestBody(props.markdown)); |
| 40 | return { | ||
| 41 | ...content, | ||
| 42 | attachments: content.attachments.filter((attachment) => !isBotBadgeImage(attachment.uri)), | ||
| 43 | }; | ||
| 39 | }, [props.markdown]); | 44 | }, [props.markdown]); |
| 40 | const gallery = useMemo<FilePreviewItem[]>( | 45 | const gallery = useMemo<FilePreviewItem[]>( |
| 41 | () => | 46 | () => |
apps/mobile/src/features/pull-requests/botBadgeImages.test.ts created+26| ... | @@ -0,0 +1,26 @@ | ||
| 1 | import { describe, expect, it } from "vite-plus/test"; | ||
| 2 | import { isBotBadgeImage } from "./botBadgeImages"; | ||
| 3 | |||
| 4 | describe("bot badge images", () => { | ||
| 5 | it("matches bot badges but not screenshots hosted by the same services", () => { | ||
| 6 | const badges = [ | ||
| 7 | "https://static.devin.ai/assets/gh-devin-review-light.svg?v=4", | ||
| 8 | "https://cursor.com/assets/images/open-in-web-dark.png", | ||
| 9 | "https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg", | ||
| 10 | "https://img.shields.io/badge/P1-orange", | ||
| 11 | "https://greptile-static-assets.s3.amazonaws.com/badges/ViewArtifacts.svg?v=4", | ||
| 12 | "https://www.gstatic.com/codereviewagent/high-priority.svg", | ||
| 13 | "https://user-images.githubusercontent.com/23264/106598434-9e719e00-654f-11eb-9e59-6167043cfa01.png", | ||
| 14 | ]; | ||
| 15 | const screenshots = [ | ||
| 16 | // Cursor cloud agents embed their screenshots and recordings as artifacts. | ||
| 17 | "https://cursor.com/artifacts/c/art-41567e7e-97f5-46ff-9e6e-df6a85723409", | ||
| 18 | "https://www.cursor.com/artifacts/c/art-98360ed1-44f8-4459-bea3-43bf93cc74fc", | ||
| 19 | "https://app.devin.ai/attachments/abc/ss_1.png", | ||
| 20 | "https://user-images.githubusercontent.com/23264/1-other-upload.png", | ||
| 21 | "https://storage.googleapis.com/some-bucket/before.png", | ||
| 22 | ]; | ||
| 23 | expect(badges.filter(isBotBadgeImage)).toEqual(badges); | ||
| 24 | expect(screenshots.filter(isBotBadgeImage)).toEqual([]); | ||
| 25 | }); | ||
| 26 | }); | ||
apps/mobile/src/features/pull-requests/botBadgeImages.ts created+53| ... | @@ -0,0 +1,53 @@ | ||
| 1 | /** | ||
| 2 | * Logos, buttons, and status icons that review bots, agents, and CI stamp into pull requests. | ||
| 3 | * Each entry is a host plus a path that only serves those assets, so screenshots uploaded to | ||
| 4 | * the same service (such as cursor.com/artifacts or app.devin.ai/attachments) stay visible. | ||
| 5 | * Only the description's Attachments strip skips them; markdown still renders them inline. | ||
| 6 | */ | ||
| 7 | const BOT_BADGE_PREFIXES = [ | ||
| 8 | // Coding agents and review bots | ||
| 9 | "static.devin.ai/assets/", | ||
| 10 | "cursor.com/assets/images/", | ||
| 11 | "storage.googleapis.com/coderabbit_public_assets/", | ||
| 12 | "greptile-static-assets.s3.amazonaws.com/badges/", | ||
| 13 | "static.graphite.dev/", | ||
| 14 | "qodo.ai/wp-content/uploads/", | ||
| 15 | "cubic.dev/buttons/", | ||
| 16 | "github.githubassets.com/static/images/icons/copilot-code-review/", | ||
| 17 | "gstatic.com/codereviewagent/", | ||
| 18 | "vercel.com/vercel-agent-view-suggestion-", | ||
| 19 | "agents-vade-review.vercel.sh/", | ||
| 20 | "new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/", | ||
| 21 | "pr-comments-assets.blacksmith.sh/", | ||
| 22 | "app.roocode.com/cloud-agents/github/", | ||
| 23 | "app.parameter.ai/_static/icons/", | ||
| 24 | // CI, deploy, and dependency bots | ||
| 25 | "img.shields.io/", | ||
| 26 | "raster.shields.io/", | ||
| 27 | "dependabot-badges.githubapp.com/", | ||
| 28 | "developer.mend.io/api/mc/badges/", | ||
| 29 | "api.securityscorecards.dev/projects/", | ||
| 30 | "github-app-statics.socket.dev/", | ||
| 31 | "static.deepsource.com/comment_artifacts/", | ||
| 32 | "sonarsource.github.io/sonarcloud-github-static-resources/", | ||
| 33 | "codescene.io/imgs/svg/", | ||
| 34 | "app.codecov.io/gh/", | ||
| 35 | "github.com/codecov/engineering-team/assets/", | ||
| 36 | "codspeed.io/pr-report/", | ||
| 37 | "static.trunk.io/assets/", | ||
| 38 | "res.cloudinary.com/snyk/", | ||
| 39 | "api.segment.io/v1/pixel/", | ||
| 40 | "cla-assistant.io/pull/badge/", | ||
| 41 | "vercel.com/static/status/", | ||
| 42 | "vercel.com/api/www/avatar", | ||
| 43 | "app.netlify.com/qr-code/", | ||
| 44 | "workers.cloudflare.com/logo.svg", | ||
| 45 | // Cloudflare Pages reuses one old GitHub upload as its logo. | ||
| 46 | "user-images.githubusercontent.com/23264/106598434-9e719e00-654f-11eb-9e59-6167043cfa01.png", | ||
| 47 | "qr.expo.dev/", | ||
| 48 | ]; | ||
| 49 | |||
| 50 | export function isBotBadgeImage(href: string) { | ||
| 51 | const path = href.replace(/^https?:\/\/(?:www\.)?/i, "").toLowerCase(); | ||
| 52 | return BOT_BADGE_PREFIXES.some((prefix) => path.startsWith(prefix)); | ||
| 53 | } | ||