feat: enhance user display name resolution and update related components

This commit is contained in:
Abhimanyu Saharan
2026-02-13 22:37:08 +05:30
parent c42e8484f8
commit 7b16b49218
4 changed files with 143 additions and 19 deletions

View File

@@ -35,6 +35,11 @@ import { SignedOutPanel } from "@/components/auth/SignedOutPanel";
import { DashboardSidebar } from "@/components/organisms/DashboardSidebar";
import { DashboardShell } from "@/components/templates/DashboardShell";
import { createExponentialBackoff } from "@/lib/backoff";
import {
DEFAULT_HUMAN_LABEL,
resolveHumanActorName,
resolveMemberDisplayName,
} from "@/lib/display-name";
import { apiDatetimeToMs, parseApiDatetime } from "@/lib/datetime";
import { cn } from "@/lib/utils";
import { usePageActive } from "@/hooks/usePageActive";
@@ -315,6 +320,11 @@ export default function ActivityPage() {
membershipQuery.data?.status === 200 ? membershipQuery.data.data : null;
return member ? ["owner", "admin"].includes(member.role) : false;
}, [membershipQuery.data]);
const currentUserDisplayName = useMemo(() => {
const member =
membershipQuery.data?.status === 200 ? membershipQuery.data.data : null;
return resolveMemberDisplayName(member, DEFAULT_HUMAN_LABEL);
}, [membershipQuery.data]);
const [isFeedLoading, setIsFeedLoading] = useState(false);
const [feedError, setFeedError] = useState<string | null>(null);
@@ -344,7 +354,10 @@ export default function ActivityPage() {
}, []);
const resolveAuthor = useCallback(
(agentId: string | null | undefined, fallbackName = "Admin") => {
(
agentId: string | null | undefined,
fallbackName: string = currentUserDisplayName,
) => {
if (agentId) {
const agent = agentsByIdRef.current.get(agentId);
if (agent) {
@@ -361,7 +374,7 @@ export default function ActivityPage() {
role: null,
};
},
[],
[currentUserDisplayName],
);
const boardNameForId = useCallback((boardId: string | null | undefined) => {
@@ -390,7 +403,7 @@ export default function ActivityPage() {
? taskMetaByIdRef.current.get(event.task_id)
: null;
const boardId = meta?.boardId ?? null;
const author = resolveAuthor(event.agent_id, "Admin");
const author = resolveAuthor(event.agent_id, currentUserDisplayName);
return {
id: `activity:${event.id}`,
created_at: event.created_at,
@@ -407,7 +420,7 @@ export default function ActivityPage() {
meta?.title ?? (event.task_id ? "Unknown task" : "Task activity"),
};
},
[boardNameForId, resolveAuthor],
[boardNameForId, currentUserDisplayName, resolveAuthor],
);
const mapTaskComment = useCallback(
@@ -416,7 +429,7 @@ export default function ActivityPage() {
? taskMetaByIdRef.current.get(comment.task_id)
: null;
const boardId = meta?.boardId ?? fallbackBoardId;
const author = resolveAuthor(comment.agent_id, "Admin");
const author = resolveAuthor(comment.agent_id, currentUserDisplayName);
return {
id: `comment:${comment.id}`,
created_at: comment.created_at,
@@ -433,7 +446,7 @@ export default function ActivityPage() {
meta?.title ?? (comment.task_id ? "Unknown task" : "Task activity"),
};
},
[boardNameForId, resolveAuthor],
[boardNameForId, currentUserDisplayName, resolveAuthor],
);
const mapApprovalEvent = useCallback(
@@ -464,7 +477,7 @@ export default function ActivityPage() {
? approval.created_at
: (approval.resolved_at ?? approval.created_at);
const action = humanizeApprovalAction(approval.action_type);
const author = resolveAuthor(approval.agent_id, "Admin");
const author = resolveAuthor(approval.agent_id, currentUserDisplayName);
const statusText =
nextStatus === "approved"
? "approved"
@@ -499,13 +512,16 @@ export default function ActivityPage() {
title: `Approval · ${action}`,
};
},
[boardNameForId, resolveAuthor],
[boardNameForId, currentUserDisplayName, resolveAuthor],
);
const mapBoardChat = useCallback(
(memory: BoardMemoryRead, boardId: string): FeedItem => {
const content = (memory.content ?? "").trim();
const actorName = (memory.source ?? "User").trim() || "User";
const actorName = resolveHumanActorName(
memory.source,
currentUserDisplayName,
);
const command = content.startsWith("/");
return {
id: `chat:${memory.id}`,
@@ -522,7 +538,7 @@ export default function ActivityPage() {
title: command ? "Board command" : "Board chat",
};
},
[boardNameForId],
[boardNameForId, currentUserDisplayName],
);
const mapAgentEvent = useCallback(

View File

@@ -114,6 +114,11 @@ import {
parseApiDatetime,
toLocalDateInput,
} from "@/lib/datetime";
import {
DEFAULT_HUMAN_LABEL,
resolveHumanActorName,
resolveMemberDisplayName,
} from "@/lib/display-name";
import { cn } from "@/lib/utils";
import { usePageActive } from "@/hooks/usePageActive";
import {
@@ -240,7 +245,7 @@ const toLiveFeedFromComment = (comment: TaskCommentRead): LiveFeedItem => ({
const toLiveFeedFromBoardChat = (memory: BoardChatMessage): LiveFeedItem => {
const content = (memory.content ?? "").trim();
const actorName = (memory.source ?? "User").trim() || "User";
const actorName = resolveHumanActorName(memory.source, DEFAULT_HUMAN_LABEL);
const isCommand = content.startsWith("/");
return {
id: `chat:${memory.id}`,
@@ -591,15 +596,16 @@ TaskCommentCard.displayName = "TaskCommentCard";
const ChatMessageCard = memo(function ChatMessageCard({
message,
fallbackSource,
}: {
message: BoardChatMessage;
fallbackSource: string;
}) {
const sourceLabel = resolveHumanActorName(message.source, fallbackSource);
return (
<div className="rounded-2xl border border-slate-200 bg-slate-50/60 p-4">
<div className="flex flex-wrap items-center justify-between gap-2">
<p className="text-sm font-semibold text-slate-900">
{message.source ?? "User"}
</p>
<p className="text-sm font-semibold text-slate-900">{sourceLabel}</p>
<span className="text-xs text-slate-400">
{formatShortTimestamp(message.created_at)}
</span>
@@ -776,6 +782,11 @@ export default function BoardDetailPage() {
membershipQuery.data?.status === 200 ? membershipQuery.data.data : null;
return member ? ["owner", "admin"].includes(member.role) : false;
}, [membershipQuery.data]);
const currentUserDisplayName = useMemo(() => {
const member =
membershipQuery.data?.status === 200 ? membershipQuery.data.data : null;
return resolveMemberDisplayName(member, DEFAULT_HUMAN_LABEL);
}, [membershipQuery.data]);
const canWrite = boardAccess.canWrite;
const [board, setBoard] = useState<Board | null>(null);
@@ -2002,6 +2013,7 @@ export default function BoardDetailPage() {
{
content: trimmed,
tags: ["chat"],
source: currentUserDisplayName,
},
);
if (result.status !== 200) {
@@ -2028,7 +2040,7 @@ export default function BoardDetailPage() {
return { ok: false, error: message };
}
},
[boardId, isSignedIn, pushLiveFeed],
[boardId, currentUserDisplayName, isSignedIn, pushLiveFeed],
);
const handleSendChat = useCallback(
@@ -3869,7 +3881,7 @@ export default function BoardDetailPage() {
authorLabel={
comment.agent_id
? (assigneeById.get(comment.agent_id) ?? "Agent")
: "Admin"
: currentUserDisplayName
}
/>
))}
@@ -3918,7 +3930,11 @@ export default function BoardDetailPage() {
</p>
) : (
chatMessages.map((message) => (
<ChatMessageCard key={message.id} message={message} />
<ChatMessageCard
key={message.id}
message={message}
fallbackSource={currentUserDisplayName}
/>
))
)}
<div ref={chatEndRef} />
@@ -3983,8 +3999,11 @@ export default function BoardDetailPage() {
null)
: null;
const authorName =
item.actor_name?.trim() ||
(authorAgent ? authorAgent.name : "Admin");
authorAgent?.name ??
resolveHumanActorName(
item.actor_name,
currentUserDisplayName,
);
const authorRole = authorAgent
? agentRoleLabel(authorAgent)
: null;

View File

@@ -0,0 +1,56 @@
import { describe, expect, it } from "vitest";
import type { OrganizationMemberRead } from "@/api/generated/model";
import {
DEFAULT_HUMAN_LABEL,
normalizeDisplayName,
resolveHumanActorName,
resolveMemberDisplayName,
} from "./display-name";
const memberWithUser = (user: {
preferred_name?: string | null;
name?: string | null;
}): OrganizationMemberRead =>
({
user,
}) as OrganizationMemberRead;
describe("display-name", () => {
it("normalizes empty strings to null", () => {
expect(normalizeDisplayName(" ")).toBeNull();
expect(normalizeDisplayName(" Abhimanyu ")).toBe("Abhimanyu");
});
it("resolves generic labels to fallback names", () => {
expect(resolveHumanActorName("Admin", "Abhimanyu")).toBe("Abhimanyu");
expect(resolveHumanActorName(" user ", "Abhimanyu")).toBe("Abhimanyu");
});
it("keeps explicit non-generic actor labels", () => {
expect(resolveHumanActorName("Abhimanyu", "User")).toBe("Abhimanyu");
});
it("prefers membership preferred_name over name", () => {
const member = memberWithUser({
preferred_name: "Abhimanyu",
name: "Admin",
});
expect(resolveMemberDisplayName(member)).toBe("Abhimanyu");
});
it("falls back to membership name when preferred_name missing", () => {
const member = memberWithUser({
preferred_name: null,
name: "Abhimanyu",
});
expect(resolveMemberDisplayName(member)).toBe("Abhimanyu");
});
it("returns default user label when member names are unavailable", () => {
expect(resolveMemberDisplayName(null)).toBe(DEFAULT_HUMAN_LABEL);
expect(resolveMemberDisplayName(memberWithUser({ name: "Admin" }))).toBe(
DEFAULT_HUMAN_LABEL,
);
});
});

View File

@@ -0,0 +1,33 @@
import type { OrganizationMemberRead } from "@/api/generated/model";
export const DEFAULT_HUMAN_LABEL = "User";
export const normalizeDisplayName = (
value: string | null | undefined,
): string | null => {
const trimmed = (value ?? "").trim();
return trimmed.length > 0 ? trimmed : null;
};
export const resolveHumanActorName = (
value: string | null | undefined,
fallbackName: string = DEFAULT_HUMAN_LABEL,
): string => {
const normalized = normalizeDisplayName(value);
if (!normalized) return fallbackName;
const lowered = normalized.toLowerCase();
if (lowered === "admin" || lowered === "user") {
return fallbackName;
}
return normalized;
};
export const resolveMemberDisplayName = (
member: OrganizationMemberRead | null | undefined,
fallbackName: string = DEFAULT_HUMAN_LABEL,
): string =>
resolveHumanActorName(
normalizeDisplayName(member?.user?.preferred_name) ??
normalizeDisplayName(member?.user?.name),
fallbackName,
);