Compare commits

...

1 Commits

Author SHA1 Message Date
Lambda
3e0610f277 feat(issues): open agent activity chip on hover
The header 'agent is working' chip previously required a click to reveal the
activity card. Open it on hover instead so the live signal reads as a
glanceable status surface. The hover config lives on Base UI's Popover.Trigger
(openOnHover + delay/closeDelay), and the trigger stays a real button so
click/keyboard access is retained for touch and a11y.

Add a regression test asserting openOnHover is wired on the trigger so a
click-only implementation can no longer pass.

MUL-3507

Co-authored-by: multica-agent <github@multica.ai>
2026-06-22 16:56:11 +08:00
2 changed files with 38 additions and 4 deletions

View File

@@ -8,6 +8,9 @@ import { renderWithI18n } from "../../test/i18n";
const mockState = vi.hoisted(() => ({
snapshot: [] as unknown[],
taskMessagesOptions: vi.fn(),
// Captures the props the chip passes to PopoverTrigger so a test can assert
// the card is wired to open on hover, not only on click.
triggerProps: undefined as Record<string, unknown> | undefined,
}));
vi.mock("@multica/core/hooks", () => ({
@@ -43,10 +46,14 @@ vi.mock("@multica/ui/components/ui/popover", async () => {
PopoverTrigger: ({
render,
children,
...props
}: {
render: React.ReactElement;
children: React.ReactNode;
}) => React.cloneElement(render, undefined, children),
} & Record<string, unknown>) => {
mockState.triggerProps = props;
return React.cloneElement(render, undefined, children);
},
PopoverContent: ({ children }: { children: React.ReactNode }) => (
<div data-testid="agent-popover-content">{children}</div>
),
@@ -100,6 +107,7 @@ beforeEach(() => {
cleanup();
vi.clearAllMocks();
mockState.snapshot = [];
mockState.triggerProps = undefined;
});
describe("IssueAgentHeaderChip", () => {
@@ -129,6 +137,20 @@ describe("IssueAgentHeaderChip", () => {
expect(mockState.taskMessagesOptions).not.toHaveBeenCalled();
});
it("opens the activity card on hover, not only on click", () => {
mockState.snapshot = [makeTask({})];
renderWithI18n(<IssueAgentHeaderChip issueId="issue-1" />);
// Base UI gates hover-to-open on `openOnHover` on the trigger. Without it
// the chip would be click-only, which is the behavior MUL-3507 replaces.
// The trigger stays a real <button>, so click/keyboard access is retained.
expect(mockState.triggerProps?.openOnHover).toBe(true);
expect(
screen.getByRole("button", { name: "Walt is working" }),
).toBeInTheDocument();
});
it("uses the concise multi-agent working label", () => {
mockState.snapshot = [
makeTask({ id: "task-1", agent_id: "agent-1" }),

View File

@@ -32,9 +32,10 @@ import { useT } from "../../i18n";
// - queued only → "{name} is queued" / "N agents queued",
// half-opacity avatars / muted text (no beam)
//
// Click opens a compact Popover card with the same active rows as the right
// panel. Those rows show necessary status/time and task entry actions, but do
// not render event counts or prefetch task messages for a count.
// Hovering the chip opens a compact Popover card with the same active rows as
// the right panel (click / keyboard still toggle it for touch and a11y). Those
// rows show necessary status/time and task entry actions, but do not render
// event counts or prefetch task messages for a count.
interface IssueAgentHeaderChipProps {
issueId: string;
@@ -106,7 +107,18 @@ function ActiveChip({ issueId, running, queued }: ActiveChipProps) {
return (
<div className="flex items-center gap-1">
<Popover>
{/* Hover opens the card so the live activity reads as a glanceable
status surface, not a click target. In Base UI the hover config
lives on the Trigger (a popover can have multiple triggers), not
the Root. The trigger stays a real button, so click and keyboard
(Enter/Space) still toggle it for touch and a11y. A short open
delay avoids flicker when the pointer merely passes over the chip;
the close delay keeps it open while the pointer travels across the
hover bridge into the interactive rows. */}
<PopoverTrigger
openOnHover
delay={150}
closeDelay={200}
render={
<button
type="button"