Skip to content

feat(web): replace placeholder avatars with minidenticon-based UserAvatar#1072

Merged
brendan-kellam merged 6 commits intomainfrom
brendan-kellam/minidenticons-avatar
Apr 2, 2026
Merged

feat(web): replace placeholder avatars with minidenticon-based UserAvatar#1072
brendan-kellam merged 6 commits intomainfrom
brendan-kellam/minidenticons-avatar

Conversation

@brendan-kellam
Copy link
Copy Markdown
Contributor

@brendan-kellam brendan-kellam commented Apr 1, 2026

Summary

  • Adds minidenticons library and a new UserAvatar component that generates deterministic avatar icons from email addresses
  • Replaces all placeholder avatar usage across chat sharing, settings members, redeem pages, opengraph image generation, and the me-control dropdown
  • UserAvatar uses forwardRef and spreads props so it works correctly as a Radix asChild target (e.g., DropdownMenuTrigger)
  • Adds a /api/minidenticon endpoint that generates minidenticon PNGs via sharp, used as fallback avatars in email templates where data URIs aren't supported
  • Updates inviteUserEmail and joinRequestSubmittedEmail to use the new endpoint instead of the generic placeholder avatar

Test plan

  • Verify avatars render correctly for users with and without profile images
  • Verify the me-control dropdown still opens on click (tests forwardRef + asChild integration)
  • Check opengraph image generation for chats still works
  • Verify invite/redeem pages show identicon avatars
  • Verify /api/minidenticon?email=test@example.com returns a valid PNG with muted background
  • Verify email templates render minidenticon fallback avatars when no profile image is set
image

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Avatars are now deterministic identicons generated from users' email addresses across the web app and outgoing invite/join emails.
    • Shared UI (members, invites, chat, settings, redeem flows) now uses a unified avatar component for consistent fallback behavior.
  • Chores

    • Added a dependency to support identicon generation and updated related dev tooling versions.

…atar component

Adds the minidenticons library and a new UserAvatar component that generates
deterministic avatar icons from email addresses. Replaces all placeholder avatar
usage across chat, settings, and redeem pages with this unified component.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 1, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds a client-side UserAvatar component that generates identicons from emails, replaces inline avatar/placeholder logic across multiple UI locations, adds a Next.js API route to render minidenticon PNGs, and updates email templates and invite rendering to use identicon-based fallbacks.

Changes

Cohort / File(s) Summary
Changelog & Dependencies
CHANGELOG.md, packages/web/package.json
Changelog entry for avatar change; add minidenticons (^4.2.1) and bump some dev deps.
New Avatar Component
packages/web/src/components/userAvatar.tsx
Add UserAvatar (forwardRef) accepting email and imageUrl, computes identicon data URI via minidenticons, and delegates rendering to existing Avatar primitives. Exports UserAvatar.
Client UI updates (multiple locations)
packages/web/src/app/[domain]/chat/components/shareChatPopover/ee/invitePanel.tsx, .../shareSettings.tsx, .../meControlDropdownMenu.tsx, .../settings/members/components/invitesList.tsx, .../membersList.tsx, .../requestsList.tsx, packages/web/src/app/redeem/components/acceptInviteCard.tsx, packages/web/src/app/redeem/components/inviteNotFoundCard.tsx, packages/web/src/features/chat/components/chatThread/messageAvatar.tsx
Replace inline Avatar + fallback/initials/placeholder logic with UserAvatar, passing email and/or imageUrl. Removed local initials helpers and adjusted placeholder imports where applicable.
OpenGraph & Data selection
packages/web/src/app/[domain]/chat/[id]/opengraph-image.tsx
Extend Prisma select to include creator email; prefer createdBy.image and fall back to identicon data URI generated from createdBy.email when image is absent.
Server API for identicons
packages/web/src/app/api/minidenticon/route.ts
Add GET route that accepts email query param, generates a 50x50 minidenticon SVG (via minidenticons), converts to PNG with sharp (flatten background, resize), and returns PNG with long cache headers; wrapped with apiHandler(..., { track: false }).
Emails & Invite flow
packages/web/src/emails/inviteUserEmail.tsx, packages/web/src/emails/joinRequestSubmittedEmail.tsx, packages/web/src/actions.ts
Invite/Join-request emails now accept/use baseUrl and build minidenticon URLs (${baseUrl}/api/minidenticon?email=...) as avatar fallbacks. createInvites now passes baseUrl: env.AUTH_URL into InviteUserEmail. Updated preview props and invite link examples.
Data shape tweak
packages/web/src/app/[domain]/settings/members/components/requestsList.tsx, packages/web/src/actions.ts
Request interface gains optional image?: string; getOrgAccountRequests mapping now includes image: request.requestedBy.image ?? undefined.
Misc
CHANGELOG.md
Add unreleased changelog entry noting identicon/avatar changes (PR #1072).

Sequence Diagram(s)

sequenceDiagram
  participant UI as Client UI
  participant UA as UserAvatar
  participant API as /api/minidenticon
  participant Gen as minidenticons+sharp
  UI->>UA: render(email?, imageUrl?)
  alt imageUrl provided
    UA->>UI: use imageUrl as src
  else no imageUrl but email present
    UA->>API: GET /api/minidenticon?email=<encoded>
    API->>Gen: generate SVG (minidenticons) -> convert to PNG (sharp)
    Gen-->>API: PNG bytes
    API-->>UA: image/png (Cache-Control: public, max-age=31536000, immutable)
    UA->>UI: use returned PNG as src
  else no email/image
    UA->>UI: render existing placeholder/fallback
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • msukkari
🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main change: replacing placeholder avatars with a new minidenticon-based UserAvatar component across the codebase.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch brendan-kellam/minidenticons-avatar

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions

This comment has been minimized.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 1, 2026

License Audit

Status: FAIL

Metric Count
Total packages 2118
Resolved (non-standard) 7
Unresolved 2
Strong copyleft 0
Weak copyleft 27

Fail Reasons

  • 2 package(s) have unresolvable licenses: @react-grab/cli, @react-grab/mcp

Unresolved Packages

Package Version License Reason
@react-grab/cli 0.1.23 UNKNOWN Package not found on npm registry; no repository or homepage available
@react-grab/mcp 0.1.23 UNKNOWN Package not found on npm registry; no repository or homepage available

Weak Copyleft Packages (informational)

Package Version License
@img/sharp-libvips-darwin-arm64 1.0.4 LGPL-3.0-or-later
@img/sharp-libvips-darwin-arm64 1.2.4 LGPL-3.0-or-later
@img/sharp-libvips-darwin-x64 1.0.4 LGPL-3.0-or-later
@img/sharp-libvips-darwin-x64 1.2.4 LGPL-3.0-or-later
@img/sharp-libvips-linux-arm 1.0.5 LGPL-3.0-or-later
@img/sharp-libvips-linux-arm 1.2.4 LGPL-3.0-or-later
@img/sharp-libvips-linux-arm64 1.0.4 LGPL-3.0-or-later
@img/sharp-libvips-linux-arm64 1.2.4 LGPL-3.0-or-later
@img/sharp-libvips-linux-ppc64 1.2.4 LGPL-3.0-or-later
@img/sharp-libvips-linux-riscv64 1.2.4 LGPL-3.0-or-later
@img/sharp-libvips-linux-s390x 1.0.4 LGPL-3.0-or-later
@img/sharp-libvips-linux-s390x 1.2.4 LGPL-3.0-or-later
@img/sharp-libvips-linux-x64 1.0.4 LGPL-3.0-or-later
@img/sharp-libvips-linux-x64 1.2.4 LGPL-3.0-or-later
@img/sharp-libvips-linuxmusl-arm64 1.0.4 LGPL-3.0-or-later
@img/sharp-libvips-linuxmusl-arm64 1.2.4 LGPL-3.0-or-later
@img/sharp-libvips-linuxmusl-x64 1.0.4 LGPL-3.0-or-later
@img/sharp-libvips-linuxmusl-x64 1.2.4 LGPL-3.0-or-later
@img/sharp-wasm32 0.33.5 Apache-2.0 AND LGPL-3.0-or-later AND MIT
@img/sharp-wasm32 0.34.5 Apache-2.0 AND LGPL-3.0-or-later AND MIT
@img/sharp-win32-arm64 0.34.5 Apache-2.0 AND LGPL-3.0-or-later
@img/sharp-win32-ia32 0.33.5 Apache-2.0 AND LGPL-3.0-or-later
@img/sharp-win32-ia32 0.34.5 Apache-2.0 AND LGPL-3.0-or-later
@img/sharp-win32-x64 0.33.5 Apache-2.0 AND LGPL-3.0-or-later
@img/sharp-win32-x64 0.34.5 Apache-2.0 AND LGPL-3.0-or-later
axe-core 4.10.3 MPL-2.0
dompurify 3.3.1 (MPL-2.0 OR Apache-2.0)
Resolved Packages (7)
Package Version Original Resolved Source
codemirror-lang-elixir 4.0.0 UNKNOWN Apache-2.0 GitHub repo (livebook-dev/codemirror-lang-elixir)
lezer-elixir 1.1.2 UNKNOWN Apache-2.0 GitHub repo (livebook-dev/lezer-elixir)
map-stream 0.1.0 UNKNOWN MIT npm page
memorystream 0.3.1 UNKNOWN MIT npm page / GitHub repo (JSBizon/node-memorystream)
pause-stream 0.0.11 ["MIT", "Apache2"] MIT OR Apache-2.0 npm page (dual-license list ["MIT","Apache2"])
posthog-js 1.345.5 SEE LICENSE IN LICENSE Apache-2.0 GitHub repo (PostHog/posthog-js)
valid-url 1.0.9 UNKNOWN MIT npm page / GitHub repo (ogt/valid-url)

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
packages/web/src/components/userAvatar.tsx (1)

22-30: Consider adding a generic fallback when no email is available.

When both imageUrl and email are null/undefined, the AvatarFallback renders null, leaving an empty circle with only the bg-muted background. Consider displaying a generic icon or initials placeholder for this edge case.

💡 Optional: Add a generic fallback
                 <AvatarFallback className="bg-muted">
                     {identiconUri ? (
                         <img src={identiconUri} alt={email ?? 'avatar'} className="h-full w-full" />
-                    ) : null}
+                    ) : (
+                        <span className="text-muted-foreground text-xs">?</span>
+                    )}
                 </AvatarFallback>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/web/src/components/userAvatar.tsx` around lines 22 - 30, The
AvatarFallback currently renders null when both imageUrl and identiconUri are
missing and email is falsy; update the fallback logic inside the Avatar
component (references: Avatar, AvatarImage, AvatarFallback, identiconUri,
imageUrl, email) to render a generic placeholder (e.g., a simple SVG/person icon
or a single-character placeholder like '?' or initials derived from a
displayName prop) whenever identiconUri is falsy and email is falsy, ensuring
you provide sensible alt text and keep existing classes (e.g., "bg-muted" and
"h-full w-full") so the visual size and styling remain consistent.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@packages/web/src/components/userAvatar.tsx`:
- Around line 22-30: The AvatarFallback currently renders null when both
imageUrl and identiconUri are missing and email is falsy; update the fallback
logic inside the Avatar component (references: Avatar, AvatarImage,
AvatarFallback, identiconUri, imageUrl, email) to render a generic placeholder
(e.g., a simple SVG/person icon or a single-character placeholder like '?' or
initials derived from a displayName prop) whenever identiconUri is falsy and
email is falsy, ensuring you provide sensible alt text and keep existing classes
(e.g., "bg-muted" and "h-full w-full") so the visual size and styling remain
consistent.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 2f19f897-b8c4-455b-b0ab-2f85afaec97c

📥 Commits

Reviewing files that changed from the base of the PR and between 331f025 and 46d97c1.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (13)
  • CHANGELOG.md
  • packages/web/package.json
  • packages/web/src/app/[domain]/chat/[id]/opengraph-image.tsx
  • packages/web/src/app/[domain]/chat/components/shareChatPopover/ee/invitePanel.tsx
  • packages/web/src/app/[domain]/chat/components/shareChatPopover/shareSettings.tsx
  • packages/web/src/app/[domain]/components/meControlDropdownMenu.tsx
  • packages/web/src/app/[domain]/settings/members/components/invitesList.tsx
  • packages/web/src/app/[domain]/settings/members/components/membersList.tsx
  • packages/web/src/app/[domain]/settings/members/components/requestsList.tsx
  • packages/web/src/app/redeem/components/acceptInviteCard.tsx
  • packages/web/src/app/redeem/components/inviteNotFoundCard.tsx
  • packages/web/src/components/userAvatar.tsx
  • packages/web/src/features/chat/components/chatThread/messageAvatar.tsx

brendan-kellam and others added 3 commits April 1, 2026 16:14
The org avatar should use the placeholder image, not a minidenticon
generated from the org name.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace placeholder avatars in email templates with dynamically generated
minidenticon PNGs. The new endpoint converts minidenticon SVGs to PNGs
via sharp, making them compatible with email clients that don't support
data URIs.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/web/src/app/api/minidenticon/route.ts`:
- Around line 11-17: The handler currently reads the raw email from
request.nextUrl.searchParams.get('email') and passes it to minidenticon, which
can leak PII; instead, derive a non-PII deterministic seed by normalizing and
hashing the email (e.g., normalizedEmail = email.trim().toLowerCase(); seed =
sha256(normalizedEmail).hex()) and pass that seed into minidenticon (replace the
use of the raw email variable). Also stop echoing or logging the plain email
anywhere in this route and consider accepting a query param named "seed" (or
continue accepting "email" but immediately replace it with the hashed seed) so
the produced SVG/PNG uses the hash-only seed.
- Around line 10-14: Replace the manual email check in the GET route with
Zod-based query validation: add a Zod schema for the query (e.g. const
minidenticonQuerySchema = z.object({ email: z.string().email() })) and import
queryParamsSchemaValidationError and serviceErrorResponse, then call
minidenticonQuerySchema.safeParse(Object.fromEntries(request.nextUrl.searchParams))
inside the apiHandler; if safeParse returns success, read email from
parsed.data, otherwise return
queryParamsSchemaValidationError(parseResult.error) (or wrap with
serviceErrorResponse if the project pattern requires it). Ensure imports and
references use GET, apiHandler, queryParamsSchemaValidationError, and
serviceErrorResponse so the route follows the established
listReposQueryParamsSchema pattern.

In `@packages/web/src/emails/inviteUserEmail.tsx`:
- Line 76: The image src currently uses the nullish coalescing operator with
host.avatarUrl which doesn’t guard against empty strings; update the expression
that builds the src in inviteUserEmail.tsx to use a truthy/trim check on
host.avatarUrl (e.g. check host.avatarUrl?.trim()) and fall back to the
minidenticon URL using baseUrl and encodeURIComponent(host.email) when the
avatar is empty or whitespace so broken empty URLs never render.

In `@packages/web/src/emails/joinRequestSubmittedEmail.tsx`:
- Line 72: The img src currently uses requestor.avatarUrl ?? fallback which
leaves an empty string as src; change the expression to treat empty strings as
absent (e.g. use requestor.avatarUrl && requestor.avatarUrl.trim() !== '' ?
requestor.avatarUrl :
`${baseUrl}/api/minidenticon?email=${encodeURIComponent(requestor.email)}` or
simply requestor.avatarUrl?.trim() ||
`${baseUrl}/api/minidenticon?email=${encodeURIComponent(requestor.email)}`) so
the minidenticon fallback is used when avatarUrl is empty.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6e2c42c5-1226-49dc-89e2-343b5ba4c49c

📥 Commits

Reviewing files that changed from the base of the PR and between b3df558 and 0d9aa36.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (6)
  • packages/web/package.json
  • packages/web/src/actions.ts
  • packages/web/src/app/api/minidenticon/route.ts
  • packages/web/src/components/userAvatar.tsx
  • packages/web/src/emails/inviteUserEmail.tsx
  • packages/web/src/emails/joinRequestSubmittedEmail.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/web/package.json
  • packages/web/src/components/userAvatar.tsx

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/web/src/actions.ts`:
- Line 726: InviteUserEmail is being given baseUrl: env.AUTH_URL, which points
at the auth-service and causes minidenticon image URLs to 404; change the
baseUrl passed to InviteUserEmail (in packages/web/src/actions.ts where
InviteUserEmail is called) to the public web app origin where
packages/web/src/app/api/minidenticon/route.ts is hosted (for example use the
public/web origin env var such as NEXT_PUBLIC_APP_URL or WEB_APP_ORIGIN instead
of AUTH_URL) so the generated `${baseUrl}/api/minidenticon` links resolve
correctly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: cf6fe3b8-dde4-4e3d-8ca4-237a067af85e

📥 Commits

Reviewing files that changed from the base of the PR and between 0d9aa36 and 12a0661.

📒 Files selected for processing (2)
  • packages/web/src/actions.ts
  • packages/web/src/app/[domain]/settings/members/components/requestsList.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/web/src/app/[domain]/settings/members/components/requestsList.tsx

@brendan-kellam brendan-kellam merged commit 85cf4be into main Apr 2, 2026
10 of 11 checks passed
@brendan-kellam brendan-kellam deleted the brendan-kellam/minidenticons-avatar branch April 2, 2026 00:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant