feat: render agent avatars as squircles (#7106)

## Summary

- render every agent/AI identity as a 30% squircle across desktop and
mobile while keeping human avatars circular
- propagate agent identity through message, thread, profile, reaction,
member, DM, search, workflow, project, huddle, forum, pulse, and
agent-management surfaces
- preserve squircle geometry for fallbacks, focus/status treatments,
add-agent controls, and overlapping avatar outlines (`calc(30% + 2px)`
for the outer background)

### Related issue

None found. This change was requested and visually reviewed in the
originating Buzz thread.

### Testing

- `just desktop-test` — 5,799 passed
- `just mobile-test` — 2,008 passed
- pre-push gates passed at `0d59d77b120dcb90aac2f918e422c11c9fa5353b`:
desktop check, TypeScript typecheck, desktop full test suite, mobile
format/analyze and full test suite, Rust tests, Tauri checks, and
differential file-size gate
- deterministic desktop visual sweep covered channel messages/thread
summaries; thread, subthread, and sub-subthread depths; reactions and
reactor popovers; hover/full profiles; added-to-channel activity;
channel members/settings; agent library/team overlaps; agent creation;
mention autocomplete; and DM header/sidebar/settings

### UI evidence

The complete labeled visual matrix is available in the originating Buzz
review thread. GitHub-hosted copies will be added in a follow-up PR
comment using the repository screenshot script.

---------

Signed-off-by: Fizz <dae5f6af70b8695a8b83c8deae555f63be41630ec2b8cd493e41a439c9527dd8@buzz.block.builderlab.xyz>
Co-authored-by: Fizz <dae5f6af70b8695a8b83c8deae555f63be41630ec2b8cd493e41a439c9527dd8@buzz.block.builderlab.xyz>
This commit is contained in:
Arjun Mahanti
2026-08-31 13:31:33 -04:00
committed by GitHub
parent 17ecf0b8c9
commit bc006f6708
109 changed files with 873 additions and 170 deletions

View File

@@ -37,6 +37,7 @@ type UserAvatarProps = {
displayName: string;
size?: UserAvatarSize;
accent?: boolean;
shape?: "circle" | "squircle";
className?: string;
fallbackDelayMs?: number;
testId?: string;
@@ -47,6 +48,7 @@ export function UserAvatar({
displayName,
size = "md",
accent = false,
shape,
className,
fallbackDelayMs = 200,
testId,
@@ -61,12 +63,20 @@ export function UserAvatar({
: avatarUrl
? rewriteRelayUrl(avatarUrl)
: null;
const resolvedShape = shape ?? "circle";
const radiusClass =
resolvedShape === "squircle" ? "rounded-[30%]" : "rounded-full";
return (
<Avatar
// Animated avatars carry their own backdrop disc and transparent
// surroundings — any container fill would flatten the pop-out.
className={cn(sizeClasses[size], !animated && "shadow-xs", className)}
className={cn(
sizeClasses[size],
radiusClass,
!animated && "shadow-xs",
className,
)}
data-testid={testId}
onMouseEnter={animated ? () => setIsHovered(true) : undefined}
onMouseLeave={animated ? () => setIsHovered(false) : undefined}

View File

@@ -43,7 +43,6 @@ import {
saveReviewPlaybackPosition,
setVideoReviewOpen,
} from "./videoPlayerState";
type VideoReviewReaction = {
emoji: string;
emojiUrl?: string;
@@ -55,11 +54,11 @@ type VideoReviewReaction = {
avatarUrl: string | null;
}>;
};
export type VideoReviewComment = {
id: string;
author: string;
avatarUrl?: string | null;
isAgent?: boolean;
body: string;
createdAt: number;
time: string;
@@ -67,7 +66,6 @@ export type VideoReviewComment = {
parentId?: string | null;
reactions?: VideoReviewReaction[];
};
export type VideoReviewContext = {
channelId?: string | null;
channelName?: string;
@@ -91,7 +89,6 @@ export type VideoReviewContext = {
rootEventId?: string;
title?: string;
};
type VideoPlayerProps = {
src: string;
poster?: string;
@@ -108,14 +105,12 @@ type VideoPlayerProps = {
/** imeta `filename`, used as the save-dialog name. */
filename?: string;
};
type TimecodedComment = {
comment: VideoReviewComment;
seconds: number | null;
timecode: string | null;
text: string;
};
const QUICK_REACTIONS = ["😂", "😍", "😮", "🙌", "👍", "👎"];
const DEFAULT_PLAYBACK_SPEED = 1;
const INLINE_SPEED_CONTROL_MIN_WIDTH = 220;
@@ -1813,6 +1808,9 @@ function VideoReviewDialog({
avatarUrl={item.comment.avatarUrl ?? null}
className="h-4 w-4 shadow-none"
displayName={item.comment.author}
shape={
item.comment.isAgent ? "squircle" : "circle"
}
size="xs"
/>
</button>
@@ -2143,6 +2141,7 @@ function VideoReviewCommentBody({
avatarUrl={item.comment.avatarUrl ?? null}
className="h-6 w-6 shadow-none"
displayName={item.comment.author}
shape={item.comment.isAgent ? "squircle" : "circle"}
size="xs"
/>
<p className="truncate text-sm font-semibold text-foreground">