feat(desktop): add smart chips for GitHub issue and commit URLs

Extend smart link rendering to convert GitHub issue URLs (CircleDot icon)
and commit URLs (GitCommitHorizontal icon, 7-char short SHA) into inline
chips, matching the existing PR chip pattern.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Taylor Ho
2026-04-08 15:18:27 -10:00
co-authored by Claude Opus 4.6
parent a3784fc00a
commit 8d624903e9
2 changed files with 94 additions and 19 deletions
+50 -19
View File
@@ -1,4 +1,4 @@
import { GitPullRequest } from "lucide-react";
import { CircleDot, GitCommitHorizontal, GitPullRequest } from "lucide-react";
import * as React from "react";
import ReactMarkdown, { type Components } from "react-markdown";
import remarkBreaks from "remark-breaks";
@@ -15,6 +15,12 @@ import remarkMentions from "@/shared/lib/remarkMentions";
const GITHUB_PR_RE =
/^https?:\/\/github\.com\/([^/]+)\/([^/]+)\/pull\/(\d+)\/?$/;
const GITHUB_ISSUE_RE =
/^https?:\/\/github\.com\/([^/]+)\/([^/]+)\/issues\/(\d+)\/?$/;
const GITHUB_COMMIT_RE =
/^https?:\/\/github\.com\/([^/]+)\/([^/]+)\/commit\/([0-9a-f]{7,40})\/?$/;
type MarkdownProps = {
channelNames?: string[];
className?: string;
@@ -46,25 +52,50 @@ function createMarkdownComponents(
return {
a: ({ children, href, ...props }) => {
const prMatch = href ? GITHUB_PR_RE.exec(href) : null;
if (prMatch) {
const [, owner, repo, number] = prMatch;
return (
<a
{...props}
className="relative inline-flex items-center gap-1 rounded-md bg-primary/10 px-1.5 py-0.5 text-sm font-medium text-primary no-underline transition-colors hover:bg-primary/20"
href={href}
rel="noreferrer"
target="_blank"
>
<span className="pointer-events-none select-none inline-flex items-center gap-1" aria-hidden="true">
<GitPullRequest className="size-3.5" />
{owner}/{repo}#{number}
</span>
<span className="absolute w-0 overflow-hidden whitespace-nowrap">{href}</span>
</a>
);
if (href) {
let Icon: React.ComponentType<{ className?: string }> | null = null;
let label: string | null = null;
const prMatch = GITHUB_PR_RE.exec(href);
if (prMatch) {
const [, owner, repo, number] = prMatch;
Icon = GitPullRequest;
label = `${owner}/${repo}#${number}`;
}
const issueMatch = !Icon ? GITHUB_ISSUE_RE.exec(href) : null;
if (issueMatch) {
const [, owner, repo, number] = issueMatch;
Icon = CircleDot;
label = `${owner}/${repo}#${number}`;
}
const commitMatch = !Icon ? GITHUB_COMMIT_RE.exec(href) : null;
if (commitMatch) {
const [, owner, repo, sha] = commitMatch;
Icon = GitCommitHorizontal;
label = `${owner}/${repo}@${sha.slice(0, 7)}`;
}
if (Icon && label) {
return (
<a
{...props}
className="relative inline-flex items-center gap-1 rounded-md bg-primary/10 px-1.5 py-0.5 text-sm font-medium text-primary no-underline transition-colors hover:bg-primary/20"
href={href}
rel="noreferrer"
target="_blank"
>
<span className="pointer-events-none select-none inline-flex items-center gap-1" aria-hidden="true">
<Icon className="size-3.5" />
{label}
</span>
<span className="absolute w-0 overflow-hidden whitespace-nowrap">{href}</span>
</a>
);
}
}
return (
<a
{...props}
+44
View File
@@ -72,6 +72,50 @@ test("selecting a PR chip copies the full URL, not the chip label", async ({
await expect(visibleLabel).toBeVisible();
});
test("GitHub issue URL renders as an inline smart chip", async ({ page }) => {
const issueUrl = "https://github.com/block/sprout/issues/99";
const message = `See ${issueUrl} for context`;
await page.goto("/");
await page.getByTestId("channel-general").click();
await expect(page.getByTestId("chat-title")).toHaveText("general");
const input = page.getByTestId("message-input");
await input.fill(message);
await page.getByTestId("send-message").click();
const lastRow = page.getByTestId("message-row").last();
const issueChip = lastRow.locator("a", { hasText: "block/sprout#99" });
await expect(issueChip).toBeVisible();
await expect(issueChip).toHaveAttribute("href", issueUrl);
await expect(issueChip.locator("svg")).toBeVisible();
});
test("GitHub commit URL renders as an inline smart chip", async ({ page }) => {
const commitUrl =
"https://github.com/block/sprout/commit/abc1234def5678901234567890abcdef12345678";
const message = `Reverted in ${commitUrl}`;
await page.goto("/");
await page.getByTestId("channel-general").click();
await expect(page.getByTestId("chat-title")).toHaveText("general");
const input = page.getByTestId("message-input");
await input.fill(message);
await page.getByTestId("send-message").click();
const lastRow = page.getByTestId("message-row").last();
// Should show short SHA
const commitChip = lastRow.locator("a", {
hasText: "block/sprout@abc1234",
});
await expect(commitChip).toBeVisible();
await expect(commitChip).toHaveAttribute("href", commitUrl);
await expect(commitChip.locator("svg")).toBeVisible();
});
test("non-PR GitHub links render as regular links", async ({ page }) => {
const repoUrl = "https://github.com/block/sprout";
const message = `Check out ${repoUrl}`;