mirror of
https://github.com/block/buzz.git
synced 2026-08-18 06:50:31 +02:00
Pass agent role into inbox profile hovers
This commit is contained in:
@@ -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}
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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,
|
||||
}) => {
|
||||
|
||||
Reference in New Issue
Block a user