mirror of
https://github.com/block/buzz.git
synced 2026-08-18 06:50:31 +02:00
feat(catalog): resolve publisher display name in catalog detail pane (#3640)
The catalog detail pane hardcoded "Community member" for every non-own catalog entry. The publisher pubkey (`catalogSource.ownerPubkey`) was already on every entry — it just was not being resolved to a name. ## What changed **`desktop/src/features/agents/ui/PersonaCatalogDialog.tsx`** `PersonaCatalogDetail` now calls `useUsersBatchQuery([ownerPubkey])` when the selected entry is a community (non-own) catalog agent. The label derivation is extracted into the exported pure function `resolveCatalogOwnerLabel` and uses truthy fallbacks to handle empty or whitespace-only kind:0 fields: - Own entry → `"You"` (unchanged) - `displayName` present and non-blank → the display name - `displayName` absent/blank but `name` present and non-blank → the name - Loading, unresolvable, or both candidates blank → `"Community member"` (fallback preserved) The batch query is disabled (`enabled: false`) when the entry is not a community entry, so there is no extra network call for own entries or built-in agents. **`desktop/src/features/agents/ui/personaCatalogOwnerLabel.test.mjs`** Unit tests for `resolveCatalogOwnerLabel` covering: populated `displayName` wins; whitespace-only `displayName` falls through to `name`; both candidates empty/whitespace/null/undefined all fall through to `"Community member"`. **`desktop/tests/e2e/agents.spec.ts`** - Updated the existing assertion — it previously checked for the hardcoded fallback; now asserts the resolved mock display name `"alice"`. - Added "catalog detail shows Community member when the publisher profile cannot be resolved" — installs a catalog event from an unknown pubkey and asserts the fallback still renders. --------- Signed-off-by: Will Pfleger <pfleger.will@gmail.com> Co-authored-by: npub1g8493u0xfsjrvflg4n08ezd7vec99mnwzlv0qgwpr9d7gvjwhuzqx59rhw <41ea58f1e64c243627e8acde7c89be667052ee6e17d8f021c1195be4324ebf04@buzz.block.builderlab.xyz>
This commit is contained in:
co-authored by
npub1g8493u0xfsjrvflg4n08ezd7vec99mnwzlv0qgwpr9d7gvjwhuzqx59rhw
parent
4933672eb4
commit
02be413b82
@@ -2,6 +2,7 @@ import * as React from "react";
|
||||
|
||||
import { isCatalogPersonaSelected } from "@/features/agents/lib/catalog";
|
||||
import { isCatalogPersona } from "@/features/agents/lib/personaCatalogRelay";
|
||||
import { useUsersBatchQuery } from "@/features/profile/hooks";
|
||||
import { ProfileAvatar } from "@/features/profile/ui/ProfileAvatar";
|
||||
import type { AgentPersona } from "@/shared/api/types";
|
||||
import { useFeedbackToasts } from "@/shared/hooks/useToastEffect";
|
||||
@@ -276,7 +277,42 @@ function PersonaCatalogChooser({
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Derives the "Added by" label for a catalog entry from a resolved profile
|
||||
* summary. Prefers `displayName`, falls back to `name`, then to the default
|
||||
* "Community member" string when both are absent, null, or whitespace-only.
|
||||
*/
|
||||
export function resolveCatalogOwnerLabel(
|
||||
summary:
|
||||
| { displayName?: string | null; name?: string | null }
|
||||
| null
|
||||
| undefined,
|
||||
): string {
|
||||
return (
|
||||
summary?.displayName?.trim() || summary?.name?.trim() || "Community member"
|
||||
);
|
||||
}
|
||||
|
||||
function PersonaCatalogDetail({ persona }: { persona: AgentPersona }) {
|
||||
const isCommunityEntry =
|
||||
isCatalogPersona(persona) && !persona.catalogSource.isOwn;
|
||||
const ownerPubkey = isCommunityEntry
|
||||
? persona.catalogSource.ownerPubkey
|
||||
: undefined;
|
||||
const ownerBatchQuery = useUsersBatchQuery(ownerPubkey ? [ownerPubkey] : [], {
|
||||
enabled: !!ownerPubkey,
|
||||
});
|
||||
|
||||
let addedByLabel: string;
|
||||
if (!isCommunityEntry) {
|
||||
addedByLabel = "You";
|
||||
} else {
|
||||
const summary = ownerPubkey
|
||||
? ownerBatchQuery.data?.profiles[ownerPubkey.toLowerCase()]
|
||||
: undefined;
|
||||
addedByLabel = resolveCatalogOwnerLabel(summary);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="w-full min-w-0 max-w-full space-y-6 overflow-x-hidden">
|
||||
<div className="flex items-center gap-3">
|
||||
@@ -290,14 +326,7 @@ function PersonaCatalogDetail({ persona }: { persona: AgentPersona }) {
|
||||
{persona.displayName}
|
||||
</h3>
|
||||
{persona.isBuiltIn ? null : (
|
||||
<PersonaAddedBy
|
||||
className="mt-0.5"
|
||||
label={
|
||||
isCatalogPersona(persona) && !persona.catalogSource.isOwn
|
||||
? "Community member"
|
||||
: "You"
|
||||
}
|
||||
/>
|
||||
<PersonaAddedBy className="mt-0.5" label={addedByLabel} />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
|
||||
import { resolveCatalogOwnerLabel } from "./PersonaCatalogDialog.tsx";
|
||||
|
||||
// ── null / undefined summary ──────────────────────────────────────────────────
|
||||
|
||||
test("test_null_summary_returns_community_member", () => {
|
||||
assert.equal(resolveCatalogOwnerLabel(null), "Community member");
|
||||
});
|
||||
|
||||
test("test_undefined_summary_returns_community_member", () => {
|
||||
assert.equal(resolveCatalogOwnerLabel(undefined), "Community member");
|
||||
});
|
||||
|
||||
// ── populated displayName ─────────────────────────────────────────────────────
|
||||
|
||||
test("test_display_name_present_returns_display_name", () => {
|
||||
assert.equal(
|
||||
resolveCatalogOwnerLabel({ displayName: "Alice", name: "alice" }),
|
||||
"Alice",
|
||||
);
|
||||
});
|
||||
|
||||
test("test_display_name_present_without_name_returns_display_name", () => {
|
||||
assert.equal(resolveCatalogOwnerLabel({ displayName: "Alice" }), "Alice");
|
||||
});
|
||||
|
||||
// ── empty / whitespace displayName with valid name ────────────────────────────
|
||||
|
||||
test("test_empty_display_name_falls_through_to_name", () => {
|
||||
assert.equal(
|
||||
resolveCatalogOwnerLabel({ displayName: "", name: "alice" }),
|
||||
"alice",
|
||||
);
|
||||
});
|
||||
|
||||
test("test_whitespace_only_display_name_falls_through_to_name", () => {
|
||||
assert.equal(
|
||||
resolveCatalogOwnerLabel({ displayName: " ", name: "alice" }),
|
||||
"alice",
|
||||
);
|
||||
});
|
||||
|
||||
// ── both candidates absent / empty ────────────────────────────────────────────
|
||||
|
||||
test("test_both_null_returns_community_member", () => {
|
||||
assert.equal(
|
||||
resolveCatalogOwnerLabel({ displayName: null, name: null }),
|
||||
"Community member",
|
||||
);
|
||||
});
|
||||
|
||||
test("test_both_empty_returns_community_member", () => {
|
||||
assert.equal(
|
||||
resolveCatalogOwnerLabel({ displayName: "", name: "" }),
|
||||
"Community member",
|
||||
);
|
||||
});
|
||||
|
||||
test("test_both_whitespace_returns_community_member", () => {
|
||||
assert.equal(
|
||||
resolveCatalogOwnerLabel({ displayName: " ", name: "\t" }),
|
||||
"Community member",
|
||||
);
|
||||
});
|
||||
|
||||
test("test_display_name_absent_name_present_returns_name", () => {
|
||||
assert.equal(resolveCatalogOwnerLabel({ name: "alice" }), "alice");
|
||||
});
|
||||
|
||||
test("test_display_name_null_name_present_returns_name", () => {
|
||||
assert.equal(
|
||||
resolveCatalogOwnerLabel({ displayName: null, name: "alice" }),
|
||||
"alice",
|
||||
);
|
||||
});
|
||||
@@ -1736,8 +1736,10 @@ test("a community member can discover and add another member's catalog agent", a
|
||||
);
|
||||
await expect(remoteEntry).toContainText("Alice’s Reviewer");
|
||||
await remoteEntry.click();
|
||||
// The detail pane resolves the publisher's display name; 'Community member'
|
||||
// is only the fallback for an unresolvable pubkey.
|
||||
await expect(page.getByTestId("persona-catalog-detail-pane")).toContainText(
|
||||
"Added by Community member",
|
||||
"Added by alice",
|
||||
);
|
||||
|
||||
await page
|
||||
@@ -1790,6 +1792,38 @@ test("a community member can discover and add another member's catalog agent", a
|
||||
expect(await countCommandInvocations(page, "create_persona")).toBe(1);
|
||||
});
|
||||
|
||||
test("catalog detail shows Community member when the publisher profile cannot be resolved", async ({
|
||||
page,
|
||||
}) => {
|
||||
// A pubkey that is not in the mock profile registry — profile resolution
|
||||
// will fail and the detail pane must fall back gracefully.
|
||||
const unknownPubkey =
|
||||
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
|
||||
const personaId = "unresolvable-reviewer";
|
||||
await installMockBridge(page, {
|
||||
personaCatalogEvents: [
|
||||
createCatalogEvent({
|
||||
ownerPubkey: unknownPubkey,
|
||||
sourcePersonaId: personaId,
|
||||
displayName: "Mystery Agent",
|
||||
systemPrompt: "Published by someone whose profile cannot be fetched.",
|
||||
}),
|
||||
],
|
||||
});
|
||||
await gotoApp(page);
|
||||
await page.getByTestId("open-agents-view").click();
|
||||
await openPersonaCatalog(page);
|
||||
|
||||
await page
|
||||
.getByTestId(
|
||||
`persona-catalog-list-item-catalog:${unknownPubkey}:${personaId}`,
|
||||
)
|
||||
.click();
|
||||
await expect(page.getByTestId("persona-catalog-detail-pane")).toContainText(
|
||||
"Added by Community member",
|
||||
);
|
||||
});
|
||||
|
||||
test("one share level selector drives both the link and send paths", async ({
|
||||
page,
|
||||
}) => {
|
||||
|
||||
Reference in New Issue
Block a user