Pass agent role into inbox profile hovers

This commit is contained in:
klopez4212
2026-06-29 12:54:05 +01:00
parent d1f6b3601e
commit f69ecb71e9
5 changed files with 107 additions and 1 deletions
+18
View File
@@ -49,6 +49,7 @@ import type { HomeFeedResponse } from "@/shared/api/types";
import { KIND_REACTION } from "@/shared/constants/kinds";
import { topChromeInset } from "@/shared/layout/chromeLayout";
import { cn } from "@/shared/lib/cn";
import { normalizePubkey } from "@/shared/lib/pubkey";
import { resolveMentionNames } from "@/shared/lib/resolveMentionNames";
import { useElementWidth } from "@/shared/hooks/use-mobile";
import {
@@ -212,6 +213,21 @@ export function HomeView({
enabled: feedProfilePubkeys.length > 0,
});
const feedProfiles = feedProfilesQuery.data?.profiles;
const inboxAgentPubkeys = React.useMemo(() => {
const pubkeys = new Set<string>();
for (const item of feed?.feed.agentActivity ?? []) {
pubkeys.add(normalizePubkey(item.pubkey));
}
for (const [pubkey, profile] of Object.entries(feedProfiles ?? {})) {
if (profile.isAgent) {
pubkeys.add(normalizePubkey(pubkey));
}
}
return pubkeys;
}, [feed?.feed.agentActivity, feedProfiles]);
const inboxItems = React.useMemo(
() =>
buildInboxItems({
@@ -460,6 +476,7 @@ export function HomeView({
{showListPane ? (
<InboxListPane
activeReminderEventIds={activeReminderEventIds}
agentPubkeys={inboxAgentPubkeys}
doneSet={effectiveDoneSet}
dueReminderCount={dueReminderCount}
filter={filter}
@@ -527,6 +544,7 @@ export function HomeView({
{showDetailPane ? (
<InboxDetailPane
agentPubkeys={inboxAgentPubkeys}
canDelete={canDelete}
canOpenChannel={Boolean(
selectedItem?.item.channelId &&
@@ -38,6 +38,7 @@ const MembersSidebar = React.lazy(async () => {
});
type InboxDetailPaneProps = {
agentPubkeys?: ReadonlySet<string>;
canDelete: boolean;
canOpenChannel: boolean;
canReply: boolean;
@@ -69,6 +70,7 @@ type InboxDetailPaneProps = {
};
export function InboxDetailPane({
agentPubkeys,
canDelete,
canOpenChannel,
canReply,
@@ -324,6 +326,7 @@ export function InboxDetailPane({
<div className="mx-6 my-3 border-t border-border/60" />
) : null}
<InboxMessageRow
agentPubkeys={agentPubkeys}
canReply={canReply}
channelId={item.item.channelId}
isFocusHighlightVisible={isFocusHighlightVisible}
+14 -1
View File
@@ -17,6 +17,7 @@ import { UserProfilePopover } from "@/features/profile/ui/UserProfilePopover";
import { RemindersPanel } from "@/features/reminders/ui/RemindersPanel";
import { TopChromeInsetHeader } from "@/shared/layout/TopChromeInsetHeader";
import { cn } from "@/shared/lib/cn";
import { normalizePubkey } from "@/shared/lib/pubkey";
import {
ContextMenu,
ContextMenuContent,
@@ -97,6 +98,7 @@ function ActivityLabel({
type InboxListPaneProps = {
activeReminderEventIds?: ReadonlySet<string>;
agentPubkeys?: ReadonlySet<string>;
doneSet: ReadonlySet<string>;
filter: InboxFilter;
items: InboxItem[];
@@ -116,6 +118,7 @@ type InboxListPaneProps = {
export function InboxListPane({
activeReminderEventIds,
agentPubkeys,
doneSet,
filter,
items,
@@ -154,6 +157,9 @@ export function InboxListPane({
const hasActiveReminder = activeReminderEventIds?.has(item.id) ?? false;
const hasChannelTarget = Boolean(item.item.channelId);
const typeLabel = getInboxTypeLabel(item);
const isSenderAgent =
agentPubkeys?.has(normalizePubkey(item.item.pubkey)) === true;
const profileRole = isSenderAgent ? "bot" : undefined;
const rowHighlightColor = isSelected
? "color-mix(in srgb, hsl(var(--background)) 70%, hsl(var(--muted)) 30%)"
: "color-mix(in srgb, hsl(var(--background)) 75%, hsl(var(--muted)) 25%)";
@@ -188,11 +194,16 @@ export function InboxListPane({
<div className="relative flex min-w-0 items-start gap-2.5">
<div className="relative shrink-0">
<UserProfilePopover
botIdenticonValue={item.senderLabel}
enableProfilePanel={false}
pubkey={item.item.pubkey}
role={profileRole}
triggerElement="span"
>
<span className="inline-flex shrink-0 rounded-full focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring">
<span
className="inline-flex shrink-0 rounded-full focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring"
data-testid={`home-inbox-item-avatar-${item.id}`}
>
<UserAvatar
avatarUrl={item.avatarUrl}
className="h-9 w-9"
@@ -207,8 +218,10 @@ export function InboxListPane({
<div className="flex min-w-0 items-start gap-2">
<span className="min-w-0 flex-1">
<UserProfilePopover
botIdenticonValue={item.senderLabel}
enableProfilePanel={false}
pubkey={item.item.pubkey}
role={profileRole}
triggerElement="span"
>
<span className="block max-w-full truncate rounded text-sm font-semibold leading-4 text-foreground focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring">
@@ -8,6 +8,7 @@ import { useReactionHandler } from "@/features/messages/ui/useReactionHandler";
import { useMessageEmoji } from "@/features/messages/lib/useMessageEmoji";
import { UserProfilePopover } from "@/features/profile/ui/UserProfilePopover";
import { cn } from "@/shared/lib/cn";
import { normalizePubkey } from "@/shared/lib/pubkey";
import { Markdown } from "@/shared/ui/markdown";
import { UserAvatar } from "@/shared/ui/UserAvatar";
@@ -31,6 +32,7 @@ function toTimelineMessage(message: InboxDisplayMessage): TimelineMessage {
}
type InboxMessageRowProps = {
agentPubkeys?: ReadonlySet<string>;
canReply: boolean;
/** Channel UUID for "Copy link" — passed straight through to MessageActionBar. */
channelId?: string | null;
@@ -45,6 +47,7 @@ type InboxMessageRowProps = {
};
export function InboxMessageRow({
agentPubkeys,
canReply,
channelId = null,
isFocusHighlightVisible,
@@ -70,6 +73,9 @@ export function InboxMessageRow({
errorMessage: reactionErrorMessage,
select: handleReactionSelect,
} = useReactionHandler(timelineMessage, onToggleReaction);
const isAuthorAgent =
agentPubkeys?.has(normalizePubkey(message.authorPubkey)) === true;
const profileRole = isAuthorAgent ? "bot" : undefined;
return (
<div className="relative px-5 py-2">
@@ -117,7 +123,9 @@ export function InboxMessageRow({
<div className="relative shrink-0">
<UserProfilePopover
botIdenticonValue={message.authorLabel}
pubkey={message.authorPubkey}
role={profileRole}
triggerElement="span"
>
<span className="inline-flex shrink-0 rounded-full focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring">
@@ -134,7 +142,9 @@ export function InboxMessageRow({
<div className="min-w-0 flex-1">
<div className="flex min-w-0 flex-wrap items-center gap-x-2 gap-y-0">
<UserProfilePopover
botIdenticonValue={message.authorLabel}
pubkey={message.authorPubkey}
role={profileRole}
triggerElement="span"
>
<span className="block max-w-full truncate rounded text-sm font-semibold text-foreground focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring">
+62
View File
@@ -2,6 +2,9 @@ import { expect, test } from "@playwright/test";
import { installMockBridge } from "../helpers/bridge";
const DEFAULT_AGENT_ACTIVITY_PUBKEY =
"db0b028cd36f4d3e36c8300cce87252c1f7fc9495ffecc53f393fcac341ffd36";
async function getTimelineMetrics(page: import("@playwright/test").Page) {
return page.getByTestId("message-timeline").evaluate((element) => {
const timeline = element as HTMLDivElement;
@@ -80,6 +83,42 @@ async function selectHomeInboxFilter(
await page.getByRole("menuitemradio", { name: label }).click();
}
async function readCommandPayloadLog(page: import("@playwright/test").Page) {
return page.evaluate(() => {
return (
(
window as Window & {
__BUZZ_E2E_COMMAND_LOG__?: Array<{
command: string;
payload: unknown;
}>;
}
).__BUZZ_E2E_COMMAND_LOG__ ?? []
);
});
}
async function readStartHuddleMemberPubkeys(
page: import("@playwright/test").Page,
) {
const commandLog = await readCommandPayloadLog(page);
return commandLog.flatMap((entry) => {
if (entry.command !== "start_huddle") {
return [];
}
const payload =
entry.payload && typeof entry.payload === "object"
? (entry.payload as {
memberPubkeys?: unknown;
member_pubkeys?: unknown;
})
: null;
const memberPubkeys = payload?.memberPubkeys ?? payload?.member_pubkeys;
return Array.isArray(memberPubkeys) ? memberPubkeys.map(String) : [];
});
}
test.beforeEach(async ({ page }) => {
await installMockBridge(page);
});
@@ -191,6 +230,29 @@ test("inbox feed shows channel and agent activity sections", async ({
);
});
test("inbox agent hover huddle passes the agent pubkey", async ({ page }) => {
await page.goto("/");
await selectHomeInboxFilter(page, "Agents");
const agentRow = page.getByTestId("home-inbox-item-mock-feed-agent");
await expect(agentRow).toContainText(
"Agent progress: channel index complete.",
);
await agentRow.getByTestId("home-inbox-item-avatar-mock-feed-agent").hover();
const profilePopover = page.locator(
'[data-testid="user-profile-popover"][data-state="open"]',
);
await expect(profilePopover).toBeVisible();
await profilePopover
.getByTestId(`user-profile-popover-huddle-${DEFAULT_AGENT_ACTIVITY_PUBKEY}`)
.click();
await expect
.poll(() => readStartHuddleMemberPubkeys(page))
.toEqual(expect.arrayContaining([DEFAULT_AGENT_ACTIVITY_PUBKEY]));
});
test("opens a mocked forum activity item from the inbox feed", async ({
page,
}) => {