Strip blue link styling, status badge, project chip from search rows (PAP-3468)

Issue rows on /search now drop the right-side StatusBadge and project chip —
status is already conveyed by the StatusIcon on the left, and the project
hexagon ("Board UI") was visual noise. The Link wrapper picks up the default
browser <a> styling (blue underline through children); explicitly setting
no-underline + text-inherit removes the stray blue line. Wrapped the row in
React.memo and dropped the now-unused projects fetch from Search.tsx.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
DottaandClaude Opus 4.7 committed 2026-05-05 10:52:40 -05:00
1 parent 1b1c5f0349
commit 5d92571313
3 files changed
+13 -56

No files matched your search

+12 -34
View File
@@ -1,10 +1,9 @@
import type { ComponentType, SVGProps } from "react";
import { memo, type ComponentType, type SVGProps } from "react";
import { Bot, FileText, Hexagon, MessageSquare, Quote } from "lucide-react";
import type { Agent, CompanySearchResult, Project } from "@paperclipai/shared";
import type { Agent, CompanySearchResult } from "@paperclipai/shared";
import { Link } from "@/lib/router";
import { cn } from "@/lib/utils";
import { StatusIcon } from "../StatusIcon";
import { StatusBadge } from "../StatusBadge";
import { Identity } from "../Identity";
import { HighlightedText, type HighlightedTextProps } from "./HighlightedText";
@@ -47,15 +46,16 @@ function formatRelativeTime(input: string | null): string {
export interface SearchResultRowProps {
result: CompanySearchResult;
agentsById?: ReadonlyMap<string, Pick<Agent, "id" | "name">>;
projectsById?: ReadonlyMap<string, Pick<Project, "id" | "name">>;
isActive?: boolean;
className?: string;
}
export function SearchResultRow({
const ROW_BASE =
"group flex items-start gap-3 px-4 transition-colors no-underline text-inherit hover:bg-muted/40";
function SearchResultRowImpl({
result,
agentsById,
projectsById,
isActive,
className,
}: SearchResultRowProps) {
@@ -63,11 +63,7 @@ export function SearchResultRow({
return (
<Link
to={result.href}
className={cn(
"group flex items-start gap-3 px-4 py-2.5 transition-colors hover:bg-muted/40",
isActive && "bg-muted/40",
className,
)}
className={cn(ROW_BASE, "py-2.5", isActive && "bg-muted/40", className)}
data-result-type="agent"
>
<span className="mt-0.5 flex h-5 w-5 shrink-0 items-center justify-center rounded-full bg-muted text-muted-foreground">
@@ -94,11 +90,7 @@ export function SearchResultRow({
return (
<Link
to={result.href}
className={cn(
"group flex items-start gap-3 px-4 py-2.5 transition-colors hover:bg-muted/40",
isActive && "bg-muted/40",
className,
)}
className={cn(ROW_BASE, "py-2.5", isActive && "bg-muted/40", className)}
data-result-type="project"
>
<Hexagon className="mt-0.5 h-4 w-4 shrink-0 text-muted-foreground" />
@@ -122,7 +114,6 @@ export function SearchResultRow({
const assigneeName = issue.assigneeAgentId
? agentsById?.get(issue.assigneeAgentId)?.name ?? null
: null;
const projectName = issue.projectId ? projectsById?.get(issue.projectId)?.name ?? null : null;
const updated = formatRelativeTime(result.updatedAt ?? issue.updatedAt);
const titleHighlights = result.snippets.find((snippet) => snippet.field === "title")?.highlights;
const bodySnippets = result.snippets.filter((snippet) => snippet.field !== "title").slice(0, 2);
@@ -131,11 +122,7 @@ export function SearchResultRow({
<Link
to={result.href}
disableIssueQuicklook
className={cn(
"group flex items-start gap-3 px-4 py-3 transition-colors hover:bg-muted/40",
isActive && "bg-muted/40",
className,
)}
className={cn(ROW_BASE, "py-3", isActive && "bg-muted/40", className)}
data-result-type="issue"
>
<div className="mt-1 shrink-0">
@@ -154,17 +141,8 @@ export function SearchResultRow({
className="line-clamp-2 min-w-0 flex-1 text-sm font-medium leading-snug text-foreground"
/>
<div className="ml-2 hidden shrink-0 items-center gap-2.5 text-xs text-muted-foreground sm:flex">
<StatusBadge status={issue.status} />
{assigneeName ? <Identity name={assigneeName} size="sm" /> : null}
{projectName ? (
<span className="inline-flex items-center gap-1">
<Hexagon className="h-3 w-3" />
<span className="max-w-[10ch] truncate">{projectName}</span>
</span>
) : null}
{updated ? (
<span className="tabular-nums">{updated}</span>
) : null}
{updated ? <span className="tabular-nums">{updated}</span> : null}
</div>
</div>
{bodySnippets.map((snippet, index) => (
@@ -178,9 +156,7 @@ export function SearchResultRow({
/>
))}
<div className="mt-1.5 flex items-center gap-2 text-xs text-muted-foreground sm:hidden">
<StatusBadge status={issue.status} />
{assigneeName ? <span className="truncate">{assigneeName}</span> : null}
{projectName ? <span className="truncate">· {projectName}</span> : null}
{updated ? <span className="ml-auto tabular-nums">{updated}</span> : null}
</div>
</div>
@@ -188,6 +164,8 @@ export function SearchResultRow({
);
}
export const SearchResultRow = memo(SearchResultRowImpl);
interface SnippetLineProps {
text: string;
highlights?: HighlightedTextProps["highlights"];
+1 -19
View File
@@ -20,13 +20,12 @@ import { useBreadcrumbs } from "../context/BreadcrumbContext";
import { useDialogActions } from "../context/DialogContext";
import { searchApi } from "../api/search";
import { agentsApi } from "../api/agents";
import { projectsApi } from "../api/projects";
import { queryKeys } from "../lib/queryKeys";
import { loadRecentSearches, pushRecentSearch } from "../lib/recent-searches";
import { PageTabBar, type PageTabItem } from "../components/PageTabBar";
import { IssueGroupHeader } from "../components/IssueGroupHeader";
import { SearchResultRow } from "../components/search/SearchResultRow";
import type { Agent, Project } from "@paperclipai/shared";
import type { Agent } from "@paperclipai/shared";
const SEARCH_DEBOUNCE_MS = 250;
const IDENTIFIER_PATTERN = /^[A-Z]+-\d+$/;
@@ -202,24 +201,12 @@ export function Search() {
enabled: !!selectedCompanyId,
});
const { data: projects } = useQuery({
queryKey: queryKeys.projects.list(selectedCompanyId!),
queryFn: () => projectsApi.list(selectedCompanyId!),
enabled: !!selectedCompanyId,
});
const agentsById = useMemo<ReadonlyMap<string, Pick<Agent, "id" | "name">>>(() => {
const map = new Map<string, Pick<Agent, "id" | "name">>();
for (const agent of agents ?? []) map.set(agent.id, agent);
return map;
}, [agents]);
const projectsById = useMemo<ReadonlyMap<string, Pick<Project, "id" | "name">>>(() => {
const map = new Map<string, Pick<Project, "id" | "name">>();
for (const project of projects ?? []) map.set(project.id, project);
return map;
}, [projects]);
// Persist recent searches once we have a successful response with a non-empty query.
useEffect(() => {
if (!selectedCompanyId) return;
@@ -409,7 +396,6 @@ export function Search() {
totalResults={totalResults}
isFetching={isFetching && !!data}
agentsById={agentsById}
projectsById={projectsById}
/>
) : null}
</TabsContent>
@@ -438,7 +424,6 @@ interface SearchTabContentProps {
totalResults: number;
isFetching: boolean;
agentsById: ReadonlyMap<string, Pick<Agent, "id" | "name">>;
projectsById: ReadonlyMap<string, Pick<Project, "id" | "name">>;
}
function SearchTabContent({
@@ -460,7 +445,6 @@ function SearchTabContent({
totalResults,
isFetching,
agentsById,
projectsById,
}: SearchTabContentProps) {
if (showInitialState) {
return (
@@ -623,7 +607,6 @@ function SearchTabContent({
key={`${result.type}:${result.id}:${result.href}`}
result={result}
agentsById={agentsById}
projectsById={projectsById}
/>
))}
</div>
@@ -638,7 +621,6 @@ function SearchTabContent({
key={`${result.type}:${result.id}:${result.href}`}
result={result}
agentsById={agentsById}
projectsById={projectsById}
/>
))}
</div>
-3
View File
@@ -33,7 +33,6 @@ import { StatusBadge } from "@/components/StatusBadge";
import { storybookAgents, storybookProjects, storybookIssues } from "../fixtures/paperclipData";
const agentsById = new Map(storybookAgents.map((agent) => [agent.id, agent]));
const projectsById = new Map(storybookProjects.map((project) => [project.id, project]));
type IssueResultOverrides = Omit<Partial<CompanySearchResult>, "issue"> & {
issue?: Partial<NonNullable<CompanySearchResult["issue"]>>;
@@ -282,7 +281,6 @@ function SearchPagePreview({
key={result.id}
result={result}
agentsById={agentsById}
projectsById={projectsById}
/>
))}
</div>
@@ -580,7 +578,6 @@ function SearchStories() {
key={result.id}
result={result}
agentsById={agentsById}
projectsById={projectsById}
/>
))}
{fixtureAgents.map((result) => (