mirror of
https://github.com/multica-ai/multica.git
synced 2026-08-03 03:03:56 +02:00
refactor(views): give a project mention the component an issue mention has
`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) <noreply@anthropic.com>
This commit is contained in:
@@ -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"
|
||||
>
|
||||
<IssueChip
|
||||
issueId={issueId}
|
||||
|
||||
@@ -2,4 +2,5 @@ export { ProjectsPage } from "./projects-page";
|
||||
export { ProjectDetail } from "./project-detail";
|
||||
export { ProjectPicker } from "./project-picker";
|
||||
export { ProjectChip } from "./project-chip";
|
||||
export { ProjectMentionCard } from "./project-mention-card";
|
||||
export { LocalDirectoryHint } from "./local-directory-hint";
|
||||
|
||||
52
packages/views/projects/components/project-mention-card.tsx
Normal file
52
packages/views/projects/components/project-mention-card.tsx
Normal file
@@ -0,0 +1,52 @@
|
||||
"use client";
|
||||
|
||||
import { AppLink } from "../../navigation";
|
||||
import { useWorkspacePaths } from "@multica/core/paths";
|
||||
import { ProjectChip } from "./project-chip";
|
||||
|
||||
interface ProjectMentionCardProps {
|
||||
projectId: string;
|
||||
/** Fallback text when the project is not resolvable (e.g. its title). */
|
||||
fallbackLabel?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigable chip — wraps ProjectChip in an AppLink pointing at the project's
|
||||
* detail page. Counterpart of IssueMentionCard, and deliberately shaped like
|
||||
* it: `ProjectChip` is presentational, so whatever wraps it decides that a
|
||||
* mention is a link, and that decision belongs in one place.
|
||||
*
|
||||
* It was in none until now — the wrapper lived inline in the readonly
|
||||
* renderer — and the cost showed up as gaps that landed on project mentions
|
||||
* alone: `.project-mention` never got the CSS rule cancelling generic link
|
||||
* chrome, and the link hover card never learned to skip it. A named component
|
||||
* is where the next such rule has somewhere obvious to go.
|
||||
*
|
||||
* Rendered as a real anchor: a mention must be reachable by Tab, activatable by
|
||||
* Enter, and expose a URL to copy or open in a new tab. A `<span onClick>`
|
||||
* 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 (
|
||||
<AppLink
|
||||
href={p.projectDetail(projectId)}
|
||||
newTabTitle={fallbackLabel}
|
||||
className="project-mention inline-flex"
|
||||
>
|
||||
<ProjectChip
|
||||
projectId={projectId}
|
||||
fallbackLabel={fallbackLabel}
|
||||
className="cursor-pointer hover:bg-accent transition-colors"
|
||||
/>
|
||||
</AppLink>
|
||||
);
|
||||
}
|
||||
@@ -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 `<span onClick>` 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 (
|
||||
<span className="inline align-middle" onClick={(e) => e.stopPropagation()}>
|
||||
<AppLink
|
||||
href={p.projectDetail(projectId)}
|
||||
newTabTitle={label}
|
||||
className="project-mention not-prose inline-flex"
|
||||
>
|
||||
<ProjectChip
|
||||
projectId={projectId}
|
||||
fallbackLabel={label}
|
||||
className="cursor-pointer hover:bg-accent transition-colors"
|
||||
/>
|
||||
</AppLink>
|
||||
<ProjectMentionCard projectId={projectId} fallbackLabel={label} />
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user