mirror of
https://github.com/block/buzz.git
synced 2026-10-27 04:30:52 +00:00
**Category:** improvement **User Impact:** Activity feeds now clearly identify the agent and keep update recency visible even when channel names are long. **Problem:** The activity header led with a generic label, making it hard to tell which agent was in view, while channel scope and recency competed for limited horizontal space. Long channel names could hide the update timestamp entirely. **Solution:** Lead with the resolved agent avatar and name, then place mode and scope in a truncating metadata region with recency pinned at the right edge. This preserves the compact two-line header while keeping the most important identity and freshness signals legible. <details> <summary>File changes</summary> **desktop/src/features/channels/ui/AgentSessionThreadPanel.tsx** Reorganizes the activity header around the agent identity, reuses the existing resolved profile avatar and label helpers, and separates scope truncation from the always-visible recency label. **desktop/tests/e2e/activity-scope-label-screenshots.spec.ts** Expands activity-header coverage across channel-scoped, all-channel, raw, long-name, and narrow layouts, including measured truncation and recency visibility. </details> ## Reproduction steps 1. Open an agent's activity feed from a channel. 2. Confirm the agent avatar and name lead the header. 3. Open a feed scoped to a channel with a long name and resize the panel narrowly. 4. Confirm the mode and channel scope truncate while the recency label remains visible at the right edge. 5. Toggle Raw mode and open an all-channel feed to confirm the same hierarchy and truncation behavior. ## Screenshots | Long channel | Narrow layout | |---|---| | <img width="380" height="671" alt="image" src="https://github.com/user-attachments/assets/19682aac-9938-41ed-8c27-fe59bf8b7535" /> | <img width="371" height="771" alt="image" src="https://github.com/user-attachments/assets/92a6fc05-c9ba-4c11-a18c-22b5225d8b9a" /> | | Raw mode | All channels | |---|---| | <img width="380" height="671" alt="image" src="https://github.com/user-attachments/assets/c8135600-e3c9-4644-8350-fa5f6b2d3aaa" /> | <img width="380" height="671" alt="image" src="https://github.com/user-attachments/assets/bac73a6a-4ed5-42d6-98cc-039a75c48ef3" /> | --------- Signed-off-by: Taylor Ho <taylorkmho@gmail.com> Co-authored-by: npub1223z34hd7vtwc6qj4s7flsxkj644nlre2nthu7lrrmkumhu3xddsrx9r6w <52a228d6edf316ec6812ac3c9fc0d696ab59fc7954d77e7be31eedcddf91335b@buzz.block.builderlab.xyz>
164 lines
6.0 KiB
TypeScript
164 lines
6.0 KiB
TypeScript
import { expect, test } from "@playwright/test";
|
|
|
|
import { waitForAnimations } from "../helpers/animations";
|
|
import { installMockBridge, TEST_IDENTITIES } from "../helpers/bridge";
|
|
|
|
const SHOTS = "test-results/activity-scope-label";
|
|
|
|
const AGENT_PUBKEY = TEST_IDENTITIES.tyler.pubkey;
|
|
const LONG_AGENT_NAME =
|
|
"Observer Agent With An Exceptionally Long Display Name";
|
|
const AGENTS_CHANNEL_ID = "94a444a4-c0a3-5966-ab05-530c6ddc2301"; // #agents
|
|
|
|
// Open the activity pane via profile → "View activity" (same ingress the
|
|
// observer-feed screenshot spec uses).
|
|
async function openActivityFromChannel(
|
|
page: import("@playwright/test").Page,
|
|
channelTestId: string,
|
|
channelTitle: string,
|
|
) {
|
|
await page.goto("/", { waitUntil: "domcontentloaded" });
|
|
await page.getByTestId(channelTestId).click();
|
|
await expect(page.getByTestId("chat-title")).toHaveText(channelTitle);
|
|
|
|
const messageRow = page
|
|
.getByTestId("message-row")
|
|
.filter({ has: page.getByText("Observer Agent", { exact: false }) });
|
|
await expect(messageRow.first()).toBeVisible({ timeout: 8_000 });
|
|
await messageRow.first().getByRole("button").first().click();
|
|
|
|
const profilePanel = page.getByTestId("user-profile-panel");
|
|
await expect(profilePanel).toBeVisible({ timeout: 10_000 });
|
|
|
|
const activityBtn = page.getByTestId(
|
|
`user-profile-view-activity-${AGENT_PUBKEY}`,
|
|
);
|
|
await expect(activityBtn).toBeVisible({ timeout: 5_000 });
|
|
await activityBtn.click();
|
|
|
|
const panel = page.getByTestId("agent-session-thread-panel");
|
|
await expect(panel).toBeVisible({ timeout: 10_000 });
|
|
return panel;
|
|
}
|
|
|
|
test.describe("activity panel scope label", () => {
|
|
test("channel-targeted pane shows the channel name", async ({ page }) => {
|
|
await installMockBridge(page, {
|
|
managedAgents: [
|
|
{
|
|
pubkey: AGENT_PUBKEY,
|
|
name: "Observer Agent",
|
|
status: "running" as const,
|
|
channelNames: ["agents"],
|
|
},
|
|
],
|
|
});
|
|
|
|
const panel = await openActivityFromChannel(
|
|
page,
|
|
"channel-agents",
|
|
"agents",
|
|
);
|
|
await expect(page.getByTestId("agent-session-agent-name")).toHaveText(
|
|
"Observer Agent",
|
|
);
|
|
await expect(page.getByTestId("agent-session-agent-avatar")).toBeVisible();
|
|
const scope = page.getByTestId("agent-session-scope-label");
|
|
await expect(scope).toHaveText("Activity · #agents");
|
|
const recency = page.getByTestId("agent-session-recency-label");
|
|
await expect(recency).toHaveText("No updates yet");
|
|
await expect(recency).toBeVisible();
|
|
|
|
// Simulate long-scope pressure deterministically: scope owns truncation
|
|
// while the independently shrinking recency stays visible.
|
|
await scope.evaluate((element) => {
|
|
element.style.width = "5rem";
|
|
element.style.flex = "none";
|
|
});
|
|
expect(
|
|
await scope.evaluate((element) => element.scrollWidth),
|
|
).toBeGreaterThan(await scope.evaluate((element) => element.clientWidth));
|
|
await expect(recency).toBeVisible();
|
|
|
|
await page.getByTestId("agent-session-settings-menu-trigger").click();
|
|
await page.getByTestId("agent-session-toggle-raw-feed").click();
|
|
await expect(scope).toHaveText("Raw ACP activity · #agents");
|
|
expect(
|
|
await scope.evaluate((element) => element.scrollWidth),
|
|
).toBeGreaterThan(await scope.evaluate((element) => element.clientWidth));
|
|
await expect(recency).toHaveText("No updates yet");
|
|
await expect(recency).toBeVisible();
|
|
await page.keyboard.press("Escape");
|
|
|
|
await waitForAnimations(page);
|
|
await panel.screenshot({ path: `${SHOTS}/01-channel-scoped.png` });
|
|
});
|
|
|
|
test("unscoped pane shows All channels", async ({ page }) => {
|
|
// The agent lives in #random only. Restoring an agentSession URL on
|
|
// #agents (where the agent is not in the activity list) puts the pane in
|
|
// all-channels scope — the state that looked silently broken before the
|
|
// scope label existed. The app uses a hash router, so the deep link goes
|
|
// in the hash.
|
|
await installMockBridge(page, {
|
|
managedAgents: [
|
|
{
|
|
pubkey: AGENT_PUBKEY,
|
|
name: LONG_AGENT_NAME,
|
|
status: "running" as const,
|
|
channelNames: ["random"],
|
|
},
|
|
],
|
|
searchProfiles: [
|
|
{
|
|
pubkey: AGENT_PUBKEY,
|
|
displayName: LONG_AGENT_NAME,
|
|
isAgent: true,
|
|
},
|
|
],
|
|
});
|
|
|
|
await page.goto(
|
|
`/#/channels/${AGENTS_CHANNEL_ID}?agentSession=${AGENT_PUBKEY}`,
|
|
{ waitUntil: "domcontentloaded" },
|
|
);
|
|
await expect(page.getByTestId("chat-title")).toHaveText("agents");
|
|
|
|
const panel = page.getByTestId("agent-session-thread-panel");
|
|
await expect(panel).toBeVisible({ timeout: 10_000 });
|
|
await expect(page.getByTestId("agent-session-agent-name")).toHaveText(
|
|
LONG_AGENT_NAME,
|
|
);
|
|
const agentName = page.getByTestId("agent-session-agent-name");
|
|
expect(
|
|
await agentName.evaluate((element) => element.scrollWidth),
|
|
).toBeGreaterThan(
|
|
await agentName.evaluate((element) => element.clientWidth),
|
|
);
|
|
const scope = page.getByTestId("agent-session-scope-label");
|
|
await expect(scope).toHaveText("Activity · All channels");
|
|
const recency = page.getByTestId("agent-session-recency-label");
|
|
await expect(recency).toHaveText("No updates yet");
|
|
await expect(recency).toBeVisible();
|
|
|
|
await page.setViewportSize({ width: 720, height: 700 });
|
|
await expect(scope).toBeVisible();
|
|
await expect(recency).toBeVisible();
|
|
const recencyWidth = await recency.evaluate(
|
|
(element) => element.clientWidth,
|
|
);
|
|
|
|
await page.getByTestId("agent-session-settings-menu-trigger").click();
|
|
await page.getByTestId("agent-session-toggle-raw-feed").click();
|
|
await expect(scope).toHaveText("Raw ACP activity · All channels");
|
|
await expect(recency).toBeVisible();
|
|
expect(await recency.evaluate((element) => element.clientWidth)).toBe(
|
|
recencyWidth,
|
|
);
|
|
await page.keyboard.press("Escape");
|
|
|
|
await waitForAnimations(page);
|
|
await panel.screenshot({ path: `${SHOTS}/02-all-channels.png` });
|
|
});
|
|
});
|