Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 24 additions & 9 deletions apps/desktop/src/chat/components/context-bar.test.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,10 @@
import { cleanup, fireEvent, render, screen } from "@testing-library/react";
import type { ReactNode } from "react";
import { beforeEach, describe, expect, it, vi } from "vitest";

const { openNewMock } = vi.hoisted(() => ({
openNewMock: vi.fn(),
}));

vi.mock("@hypr/ui/components/ui/tooltip", () => ({
Tooltip: ({ children }: { children: ReactNode }) => <div>{children}</div>,
TooltipContent: ({ children }: { children: ReactNode }) => (
<div>{children}</div>
),
TooltipTrigger: ({ children }: { children: ReactNode }) => <>{children}</>,
}));

vi.mock("~/store/zustand/tabs", () => ({
useTabs: <T,>(selector: (state: { openNew: typeof openNewMock }) => T) =>
selector({ openNew: openNewMock }),
Expand Down Expand Up @@ -54,6 +45,28 @@ describe("ContextBar", () => {
expect(screen.queryByText("Search sessions...")).toBeNull();
});

it("does not render chip tooltips", () => {
render(
<ContextBar
entities={[
{
kind: "human",
key: "human:manual:artem",
source: "manual",
humanId: "artem",
name: "Artem",
email: "artem@example.com",
organizationName: "Char",
pending: true,
},
]}
/>,
);

expect(screen.getByText("Artem")).toBeTruthy();
expect(screen.queryByText(/artem@example.com/)).toBeNull();
});

it("centers the squircle chip strip above the input surface", () => {
const { container } = render(
<ContextBar
Expand Down Expand Up @@ -287,10 +300,12 @@ describe("ContextBar", () => {
const removeButton = screen.getByRole("button", {
name: "Remove Current Note",
});
const removeIcon = removeButton.querySelector("svg");

expect(iconSlot?.className).toContain("size-4");
expect(iconSlot?.contains(removeButton)).toBe(true);
expect(contextIcon?.className.baseVal).toContain("group-hover:opacity-0");
expect(removeIcon?.className.baseVal).toContain("size-3.5");
expect(removeButton.className).toContain("group-hover:opacity-100");
expect(removeButton.className).toContain("group-hover:pointer-events-auto");

Expand Down
80 changes: 33 additions & 47 deletions apps/desktop/src/chat/components/context-bar.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
import { ChevronUpIcon, XIcon } from "lucide-react";
import { ChevronUpIcon, XCircleIcon } from "lucide-react";
import { useMemo, useState } from "react";

import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from "@hypr/ui/components/ui/tooltip";
import { cn } from "@hypr/utils";

import { type ContextChipProps, renderChip } from "~/chat/context/registry";
Expand Down Expand Up @@ -48,49 +43,40 @@ function ContextChip({
};

return (
<Tooltip>
<TooltipTrigger asChild>
<span
data-chat-context-chip
onClick={handleClick}
<span
data-chat-context-chip
onClick={handleClick}
className={cn([
"group border-border/60 inline-flex h-7 max-w-56 min-w-0 shrink-0 items-center gap-1.5 rounded-[10px] border px-2.5 text-xs leading-4 shadow-xs",
pending
? "bg-card/60 text-muted-foreground"
: "bg-card/90 text-muted-foreground",
isClickable ? "hover:bg-accent/20 cursor-pointer" : "cursor-default",
])}
>
<span className="relative flex size-4 shrink-0 items-center justify-center">
<Icon
className={cn([
"group border-border/60 inline-flex h-7 max-w-56 min-w-0 shrink-0 items-center gap-1.5 rounded-[10px] border px-2.5 text-xs leading-4 shadow-xs",
pending
? "bg-card/60 text-muted-foreground"
: "bg-card/90 text-muted-foreground",
isClickable
? "hover:bg-accent/20 cursor-pointer"
: "cursor-default",
"text-muted-foreground size-3.5 shrink-0 transition-opacity",
chip.removable && onRemove ? "group-hover:opacity-0" : "",
])}
>
<span className="relative flex size-4 shrink-0 items-center justify-center">
<Icon
className={cn([
"text-muted-foreground size-3.5 shrink-0 transition-opacity",
chip.removable && onRemove ? "group-hover:opacity-0" : "",
])}
/>
{chip.removable && onRemove && (
<button
type="button"
aria-label={`Remove ${chip.label}`}
onClick={(e) => {
e.stopPropagation();
onRemove(chip.key);
}}
className="hover:bg-accent/30 hover:text-foreground pointer-events-none absolute inset-0 flex items-center justify-center rounded-[5px] opacity-0 transition-opacity group-hover:pointer-events-auto group-hover:opacity-100 focus-visible:pointer-events-auto focus-visible:opacity-100"
>
<XIcon className="size-3" />
</button>
)}
</span>
<span className="truncate">{chip.label}</span>
</span>
</TooltipTrigger>
<TooltipContent side="top" className="z-110">
{chip.tooltip}
</TooltipContent>
</Tooltip>
/>
{chip.removable && onRemove && (
<button
type="button"
aria-label={`Remove ${chip.label}`}
onClick={(e) => {
e.stopPropagation();
onRemove(chip.key);
}}
className="text-muted-foreground hover:text-foreground pointer-events-none absolute inset-0 flex items-center justify-center opacity-0 transition-opacity group-hover:pointer-events-auto group-hover:opacity-100 focus-visible:pointer-events-auto focus-visible:opacity-100"
>
<XCircleIcon className="size-3.5" />
</button>
)}
</span>
<span className="truncate">{chip.label}</span>
</span>
);
}

Expand Down
9 changes: 0 additions & 9 deletions apps/desktop/src/chat/context/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ export type ContextChipProps = {
key: string;
icon: React.ComponentType<{ className?: string }>;
label: string;
tooltip: string;
removable?: boolean;
entityKind?: ContextEntityKind;
entityId?: string;
Expand Down Expand Up @@ -40,7 +39,6 @@ const renderers: RendererMap = {
key: entity.key,
icon: isFromTool ? SearchIcon : CalendarIcon,
label,
tooltip: entity.title || "Session",
removable: entity.removable,
entityKind: "session",
entityId: entity.sessionId,
Expand All @@ -51,14 +49,10 @@ const renderers: RendererMap = {
human: {
toChip: (entity) => {
const label = entity.name || entity.email || "Person";
const tooltip = [entity.name, entity.email, entity.organizationName]
.filter(Boolean)
.join(" • ");
return {
key: entity.key,
icon: UserIcon,
label,
tooltip: tooltip || label,
removable: entity.removable,
entityKind: "human",
entityId: entity.humanId,
Expand All @@ -73,7 +67,6 @@ const renderers: RendererMap = {
key: entity.key,
icon: Building2Icon,
label,
tooltip: label,
removable: entity.removable,
entityKind: "organization",
entityId: entity.organizationId,
Expand All @@ -88,7 +81,6 @@ const renderers: RendererMap = {
key: entity.key,
icon: UserIcon,
label: "Account",
tooltip: entity.email || "Account",
};
},
},
Expand All @@ -99,7 +91,6 @@ const renderers: RendererMap = {
key: entity.key,
icon: MonitorIcon,
label: "Device",
tooltip: "Device",
};
},
},
Expand Down
Loading