mirror of
https://github.com/block/buzz.git
synced 2026-11-02 07:22:55 +00:00
fix(desktop): never show a trigger title as the prompt author
The conversation variant's author row resolved its name through
`resolveUserLabel({ fallbackName: item.title })`, whose last fallback is
the prompt item's title. That title describes the *trigger* that started
the turn — "@Mention", "Prompt", "Buzz event", or any title-cased Buzz
event kind — and is never an identity. On `default`/`compactPreview` it
only seeds avatar initials, so it was harmless; the conversation author
row promotes it to displayed text, so an unresolved sender was rendered
as if "@Mention" were their name.
Identity resolution for the displayed author now stops at the profile:
display name, then NIP-05 handle, then the truncated pubkey. A truncated
pubkey is terse but true, and it keeps the utterance attributed in a
full-cover view where an unlabelled bubble would read as orphaned. The
title stays available as trigger chrome in the footer.
The avatar seed keeps the old chain verbatim, so `default` and
`compactPreview` markup is untouched — the byte-for-byte fixture still
passes unchanged.
The existing author-row test was asserting the bug: its expectation came
from `item.title`, with no profile in the lookup. It now supplies a
profile and asserts the resolved display name, and the fixture's prompt
title is the realistic "@Mention" rather than a person's name so no test
can silently depend on a title reading as an identity again. Two new
tests cover the unresolved sender (truncated pubkey, and an explicit
assertion that no trigger word appears) and NIP-05-only resolution.
Non-vacuous: reverting the fix fails the unresolved-sender test — and
only that test — with `actual: '@Mention'`.
Verified at this tree: full desktop suite 5381 passing / 0 failing,
typecheck clean, `pnpm check` findings identical to main's baseline,
file-size ratchet clean. Transcript file also green under UTC/de-DE and
Asia/Tokyo/ja-JP.
Signed-off-by: ss-dev-01 <11939edb7df583f855dbef923f2358f1184538f88ca452e19e7e35e42ad6d796@buzz.block.builderlab.xyz>
Co-authored-by: Bradley Axen <baxen@squareup.com>
Signed-off-by: Bradley Axen <baxen@squareup.com>
This commit is contained in:
@@ -139,6 +139,23 @@ const AGENT = {
|
|||||||
agentPubkey: "f".repeat(64),
|
agentPubkey: "f".repeat(64),
|
||||||
};
|
};
|
||||||
const AUTHOR = "a".repeat(64);
|
const AUTHOR = "a".repeat(64);
|
||||||
|
const AUTHOR_TRUNCATED = `${AUTHOR.slice(0, 8)}…${AUTHOR.slice(-4)}`;
|
||||||
|
/**
|
||||||
|
* What the transcript builder actually puts in a prompt item's `title`: a
|
||||||
|
* description of the trigger that started the turn, not an identity. Real values
|
||||||
|
* are "Prompt", "Buzz event", and title-cased event kinds like "@Mention"
|
||||||
|
* (`agentSessionTranscriptHelpers.ts` `parsePromptText`). The author row must
|
||||||
|
* never display this as a name.
|
||||||
|
*/
|
||||||
|
const TRIGGER_TITLE = "@Mention";
|
||||||
|
const AUTHOR_PROFILES = {
|
||||||
|
[AUTHOR]: {
|
||||||
|
displayName: "Ada Lovelace",
|
||||||
|
avatarUrl: null,
|
||||||
|
nip05Handle: null,
|
||||||
|
ownerPubkey: null,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
function items() {
|
function items() {
|
||||||
const shared = { channelId: "chan-1", sessionId: "sess-1", turnId: "turn-1" };
|
const shared = { channelId: "chan-1", sessionId: "sess-1", turnId: "turn-1" };
|
||||||
@@ -149,7 +166,7 @@ function items() {
|
|||||||
type: "message",
|
type: "message",
|
||||||
renderClass: "message",
|
renderClass: "message",
|
||||||
role: "user",
|
role: "user",
|
||||||
title: "Ada",
|
title: TRIGGER_TITLE,
|
||||||
text: "please summarize the plan",
|
text: "please summarize the plan",
|
||||||
timestamp: "2026-06-14T19:00:00.000Z",
|
timestamp: "2026-06-14T19:00:00.000Z",
|
||||||
messageId: "event-1",
|
messageId: "event-1",
|
||||||
@@ -426,12 +443,14 @@ test("conversation marks the transcript container and centers a reading column",
|
|||||||
});
|
});
|
||||||
|
|
||||||
test("conversation renders the prompt as a filled right-aligned bubble with an author label", async () => {
|
test("conversation renders the prompt as a filled right-aligned bubble with an author label", async () => {
|
||||||
const { container } = await renderTranscript("conversation");
|
const { container } = await renderTranscript("conversation", {
|
||||||
|
profiles: AUTHOR_PROFILES,
|
||||||
|
});
|
||||||
const author = container.querySelector(
|
const author = container.querySelector(
|
||||||
'[data-testid="transcript-user-message-author"]',
|
'[data-testid="transcript-user-message-author"]',
|
||||||
);
|
);
|
||||||
assert.ok(author, "conversation should label the prompt author");
|
assert.ok(author, "conversation should label the prompt author");
|
||||||
assert.equal(author.textContent, "Ada");
|
assert.equal(author.textContent, "Ada Lovelace");
|
||||||
|
|
||||||
const row = container.querySelector(
|
const row = container.querySelector(
|
||||||
'[data-testid="transcript-user-message"]',
|
'[data-testid="transcript-user-message"]',
|
||||||
@@ -443,6 +462,48 @@ test("conversation renders the prompt as a filled right-aligned bubble with an a
|
|||||||
assert.doesNotMatch(bubble.className, /max-h-36/);
|
assert.doesNotMatch(bubble.className, /max-h-36/);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("conversation never shows the trigger title as the prompt author when the sender is unresolved", async () => {
|
||||||
|
// Regression guard. The label chain's last fallback used to be the prompt
|
||||||
|
// item's `title`, which is a description of the trigger ("@Mention",
|
||||||
|
// "Prompt", "Buzz event") rather than an identity. On the other variants that
|
||||||
|
// string only seeds avatar initials, so it was harmless; the conversation
|
||||||
|
// author row displays it, so an unresolved sender was rendered as "@Mention"
|
||||||
|
// — a trigger presented as a person's name. With no profile for the author,
|
||||||
|
// the row must fall back to the truncated pubkey instead.
|
||||||
|
const { container } = await renderTranscript("conversation");
|
||||||
|
const author = container.querySelector(
|
||||||
|
'[data-testid="transcript-user-message-author"]',
|
||||||
|
);
|
||||||
|
assert.ok(author, "the author row should still render without a profile");
|
||||||
|
assert.equal(author.textContent, AUTHOR_TRUNCATED);
|
||||||
|
assert.doesNotMatch(
|
||||||
|
author.textContent,
|
||||||
|
/@Mention|Prompt|Buzz event/,
|
||||||
|
"the trigger title must never be displayed as an author name",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("conversation prefers the resolved profile over both the trigger title and the pubkey", async () => {
|
||||||
|
// The truncated-pubkey fallback must not shadow a real identity: with a
|
||||||
|
// profile present the row shows the display name, and with only a NIP-05
|
||||||
|
// handle it shows that.
|
||||||
|
const { container } = await renderTranscript("conversation", {
|
||||||
|
profiles: {
|
||||||
|
[AUTHOR]: {
|
||||||
|
displayName: null,
|
||||||
|
avatarUrl: null,
|
||||||
|
nip05Handle: "ada@example.com",
|
||||||
|
ownerPubkey: null,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
assert.equal(
|
||||||
|
container.querySelector('[data-testid="transcript-user-message-author"]')
|
||||||
|
.textContent,
|
||||||
|
"ada@example.com",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
test("conversation renders agent messages as unboxed prose at full fidelity", async () => {
|
test("conversation renders agent messages as unboxed prose at full fidelity", async () => {
|
||||||
const { container } = await renderTranscript("conversation");
|
const { container } = await renderTranscript("conversation");
|
||||||
const message = container.querySelector(
|
const message = container.querySelector(
|
||||||
|
|||||||
@@ -50,13 +50,31 @@ export function UserMessageBubble({
|
|||||||
const authorProfile = item.authorPubkey
|
const authorProfile = item.authorPubkey
|
||||||
? profiles?.[item.authorPubkey.toLowerCase()]
|
? profiles?.[item.authorPubkey.toLowerCase()]
|
||||||
: null;
|
: null;
|
||||||
const authorLabel = item.authorPubkey
|
// The other variants seed the avatar from `resolveUserLabel(…, fallbackName:
|
||||||
|
// item.title)`, whose last resort is the prompt item's title. That title
|
||||||
|
// describes the *trigger* that started the turn ("@Mention", "Prompt", "Buzz
|
||||||
|
// event"), never a person — harmless when it only picks avatar initials, so
|
||||||
|
// this chain is left exactly as it was for `default`/`compactPreview`.
|
||||||
|
const triggerSeededLabel = item.authorPubkey
|
||||||
? resolveUserLabel({
|
? resolveUserLabel({
|
||||||
pubkey: item.authorPubkey,
|
pubkey: item.authorPubkey,
|
||||||
fallbackName: item.title,
|
fallbackName: item.title,
|
||||||
profiles,
|
profiles,
|
||||||
})
|
})
|
||||||
: item.title || "User";
|
: item.title || "User";
|
||||||
|
// Focus mode promotes the author to displayed text, where a trigger title
|
||||||
|
// would read as a false identity — an unresolved sender showing up as
|
||||||
|
// "@Mention". Identity resolution here therefore stops at the profile
|
||||||
|
// (display name, then NIP-05 handle, then the truncated pubkey): a truncated
|
||||||
|
// pubkey is a real, if terse, identity, and it keeps the utterance attributed
|
||||||
|
// in a full-cover view. `item.title` stays available as trigger chrome in the
|
||||||
|
// footer; it is never a name. Only when the item carries no author at all does
|
||||||
|
// the row fall back to a generic placeholder.
|
||||||
|
const authorLabel = isConversation
|
||||||
|
? item.authorPubkey
|
||||||
|
? resolveUserLabel({ pubkey: item.authorPubkey, profiles })
|
||||||
|
: "User"
|
||||||
|
: triggerSeededLabel;
|
||||||
const handleBubbleClick = React.useCallback(
|
const handleBubbleClick = React.useCallback(
|
||||||
(event: React.MouseEvent<HTMLDivElement>) => {
|
(event: React.MouseEvent<HTMLDivElement>) => {
|
||||||
if (!messageLink || isNestedInteractiveTarget(event)) return;
|
if (!messageLink || isNestedInteractiveTarget(event)) return;
|
||||||
|
|||||||
Reference in New Issue
Block a user