From 89465e8d8c6b9f98ccd6684c653704e898758705 Mon Sep 17 00:00:00 2001 From: Sawyer Date: Fri, 18 Sep 2026 10:10:34 -0700 Subject: [PATCH 1/2] test(web): cover avatar name resolution for a person's own turn (CL-8508) --- apps/web/src/chat/threads-api.test.ts | 33 ++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/apps/web/src/chat/threads-api.test.ts b/apps/web/src/chat/threads-api.test.ts index 4ace15ac0..da57207ab 100644 --- a/apps/web/src/chat/threads-api.test.ts +++ b/apps/web/src/chat/threads-api.test.ts @@ -2,7 +2,7 @@ import { describe, expect, test } from "bun:test"; import { agentDeploySourceAssetName } from "../agent-deploy"; import { MYRA_SOURCE_CONFIG } from "../myra-source"; -import { displayAgentName } from "./threads-api"; +import { displayAgentName, resolveAvatarName, type RoomParticipant } from "./threads-api"; describe("displayAgentName", () => { test("renders Myra's fixed display name for her asset", () => { @@ -18,3 +18,34 @@ describe("displayAgentName", () => { expect(displayAgentName("some-other-asset")).toBe("some-other-asset"); }); }); + +describe("resolveAvatarName", () => { + const participants: readonly RoomParticipant[] = [ + { id: "p1", kind: "person", name: "alice", address: "alice@example.com" }, + { id: "a1", kind: "agent", name: "Myra", address: "myra@example.com" }, + ]; + + test("uses the person's own real name for their own turn, never 'You'", () => { + const name = resolveAvatarName( + { author: "me", authorName: "You", address: "alice@example.com" }, + participants, + ); + expect(name).toBe("alice"); + }); + + test("uses the matching participant's name for another author", () => { + const name = resolveAvatarName( + { author: "other", authorName: "myra", address: "myra@example.com" }, + participants, + ); + expect(name).toBe("Myra"); + }); + + test("falls back to authorName when no participant matches", () => { + const name = resolveAvatarName( + { author: "other", authorName: "someone", address: "unknown@example.com" }, + participants, + ); + expect(name).toBe("someone"); + }); +}); From d218c10220a1f37c4c633696830695f09656dfac Mon Sep 17 00:00:00 2001 From: Sawyer Date: Fri, 18 Sep 2026 10:10:34 -0700 Subject: [PATCH 2/2] fix(web): person turns show the signed-in name on the avatar (CL-8508) --- apps/web/src/chat/threads-api.ts | 12 ++++++++++++ apps/web/src/pages/workbench-room-page.tsx | 10 ++++++---- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/apps/web/src/chat/threads-api.ts b/apps/web/src/chat/threads-api.ts index f18240dff..206a9574f 100644 --- a/apps/web/src/chat/threads-api.ts +++ b/apps/web/src/chat/threads-api.ts @@ -687,6 +687,18 @@ export function resolveParticipantName( ); } +/** A turn's avatar name: the matching participant's real name, including + * the person's own — never the "You" transcript label, which stays for the + * row's own text elsewhere. Falls back to `resolveParticipantName` when no + * participant matches the turn's address. */ +export function resolveAvatarName( + message: Pick, + participants: readonly RoomParticipant[], +): string { + const matched = participants.find((participant) => participant.address === message.address); + return matched?.name ?? resolveParticipantName(message, participants); +} + type RoomTurn = { readonly id: string; readonly messageId: string; diff --git a/apps/web/src/pages/workbench-room-page.tsx b/apps/web/src/pages/workbench-room-page.tsx index 6a4648da9..abbd8839a 100644 --- a/apps/web/src/pages/workbench-room-page.tsx +++ b/apps/web/src/pages/workbench-room-page.tsx @@ -29,6 +29,7 @@ import { ancestorChain, listRoomParticipants, readRoom, + resolveAvatarName, resolveParticipantName, sendToRoom, subscribeToInbox, @@ -60,9 +61,10 @@ function RoomMessageRow({ /** Undefined in the sub-thread panel, where a row is read-only context. */ readonly onReply?: (message: RoomMessage) => void; }) { - // Avatars read off the participant's display name (Myra → "M"), never the - // run address local part a mail turn otherwise carries. - const displayName = resolveParticipantName(message, participants); + // Avatars read off the participant's real name — the person's own + // included, never the "You" transcript label — falling back to the + // address local part a mail turn otherwise carries. + const avatarName = resolveAvatarName(message, participants); const matched = participants.find((participant) => participant.address === message.address); const kind = message.author !== "me" && matched?.kind === "agent" ? "agent" : "person"; // The person's own send carries a trailing roster block so agents in the @@ -75,7 +77,7 @@ function RoomMessageRow({