From 21267745ee64a1a4d100b95abd351151d03fd83d Mon Sep 17 00:00:00 2001 From: Naiyuan Qing <145280634+NevilleQingNY@users.noreply.github.com> Date: Fri, 31 Jul 2026 16:05:46 +0800 Subject: [PATCH] refactor(views): give a project mention the component an issue mention has MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `IssueMentionCard` owns "chip inside a link" for issues; the project equivalent lived inline in the readonly renderer, so nothing named the pairing and nothing held the rules that come with being a link. That cost was not hypothetical. Both gaps fixed a commit ago landed on project mentions alone: `.project-mention` never got the CSS rule cancelling generic link chrome, and the hover card never learned to skip it. Each was written for `.issue-mention` at the component that owns it, and project had no such place for the second half to be written. `ProjectMentionCard` is that place. No behaviour change: same anchor, same href, same hover affordance, same accessibility contract that project-mention-a11y.test.tsx pins. The "open in new tab" preference stays out — it is scoped to issue links, and inheriting it by symmetry would be inventing product. Also drops `not-prose` from both cards. It has no definition anywhere in the repo — Tailwind's typography plugin is not installed, and the class does not appear in built CSS — so it read as protection that was not there. The editor's `MentionView` keeps its hand-rolled anchors: it needs a modifier-click intent hook `AppLink` does not expose, and it does the same for issues, so the two stay symmetric there too. Co-Authored-By: Claude Opus 5 (1M context) --- .../issues/components/issue-mention-card.tsx | 2 +- packages/views/projects/components/index.ts | 1 + .../components/project-mention-card.tsx | 52 +++++++++++++++++++ packages/views/rich-content/rich-content.tsx | 35 +++---------- 4 files changed, 62 insertions(+), 28 deletions(-) create mode 100644 packages/views/projects/components/project-mention-card.tsx diff --git a/packages/views/issues/components/issue-mention-card.tsx b/packages/views/issues/components/issue-mention-card.tsx index ff26729b54..3fd88804c7 100644 --- a/packages/views/issues/components/issue-mention-card.tsx +++ b/packages/views/issues/components/issue-mention-card.tsx @@ -27,7 +27,7 @@ export function IssueMentionCard({ issueId, fallbackLabel }: IssueMentionCardPro href={p.issueDetail(issueId)} target={openInNewTab ? "_blank" : undefined} newTabTitle={fallbackLabel} - className="issue-mention not-prose align-middle" + className="issue-mention align-middle" > ` + * looks identical and works for a mouse, which is exactly why that regression + * is easy to miss (see project-mention-a11y.test.tsx). + * + * AppLink owns plain-click, modifier-click and the desktop new-tab adapter, so + * none of that is reimplemented here. Unlike an issue there is no "open in new + * tab" preference to honour — that setting is scoped to issue links. + */ +export function ProjectMentionCard({ + projectId, + fallbackLabel, +}: ProjectMentionCardProps) { + const p = useWorkspacePaths(); + return ( + + + + ); +} diff --git a/packages/views/rich-content/rich-content.tsx b/packages/views/rich-content/rich-content.tsx index c4efe3950f..24441ea523 100644 --- a/packages/views/rich-content/rich-content.tsx +++ b/packages/views/rich-content/rich-content.tsx @@ -39,7 +39,7 @@ import remarkMath from "remark-math"; import rehypeRaw from "rehype-raw"; import rehypeSanitize from "rehype-sanitize"; import { cn } from "@multica/ui/lib/utils"; -import { useWorkspacePaths, useWorkspaceSlug } from "@multica/core/paths"; +import { useWorkspaceSlug } from "@multica/core/paths"; import { useConfigStore } from "@multica/core/config"; import type { Attachment } from "@multica/core/types"; import { @@ -48,10 +48,10 @@ import { markdownSanitizeSchema, markdownUrlTransform, } from "@multica/ui/markdown"; -import { AppLink, useAppOrigin } from "../navigation"; +import { useAppOrigin } from "../navigation"; import { IssueMentionCard } from "../issues/components/issue-mention-card"; import { useResolveIssueIdentifier } from "../issues/hooks"; -import { ProjectChip } from "../projects/components/project-chip"; +import { ProjectMentionCard } from "../projects/components/project-mention-card"; import { useLinkHover, LinkHoverCard } from "../editor/link-hover-card"; import { openLink, @@ -144,34 +144,15 @@ function IdentifierIssueMentionLink({ } /** - * Project mention chip. - * - * Rendered as a real anchor via AppLink, matching IssueMentionCard: a mention is - * a link, so it must be reachable by Tab and activatable by Enter, and expose a - * URL to copy / open in a new tab. A `` looks identical and works - * for a mouse, which is exactly why the regression is easy to miss — the readonly - * renderer used one, and unifying the surfaces would have propagated it to Chat, - * which previously had the accessible version. - * - * AppLink also owns plain-click, modifier-click and the desktop new-tab adapter, - * so none of that is reimplemented here. The wrapper only shields surrounding - * click handlers (e.g. collapsed-comment expanders) from mention clicks. + * Project mention chip. Navigation and accessibility are owned by the AppLink + * inside ProjectMentionCard; the wrapper only shields surrounding click + * handlers (e.g. collapsed-comment expanders) from mention clicks — the same + * shape as IssueMentionLink above. */ function ProjectMentionLink({ projectId, label }: { projectId: string; label?: string }) { - const p = useWorkspacePaths(); return ( e.stopPropagation()}> - - - + ); }