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

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

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

Conversation

@brendan-kellam

@brendan-kellambrendan-kellam commented Apr 1, 2026

Copy link
Copy Markdown
Contributor

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

coderabbitaiBot commented Apr 1, 2026

Copy link
Copy Markdown
Contributor

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 nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring 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 nameStatusExplanation
Title check✅ PassedThe title clearly and concisely summarizes the main change: replacing placeholder avatars with a new minidenticon-based UserAvatar component across the codebase.
Description Check✅ PassedCheck 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

github-actionsBot commented Apr 1, 2026

Copy link
Copy Markdown
Contributor

License Audit

Status: FAIL

MetricCount
Total packages2118
Resolved (non-standard)7
Unresolved2
Strong copyleft0
Weak copyleft27

Fail Reasons

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

Unresolved Packages

PackageVersionLicenseReason
@react-grab/cli0.1.23UNKNOWNPackage not found on npm registry; no repository or homepage available
@react-grab/mcp0.1.23UNKNOWNPackage not found on npm registry; no repository or homepage available

Weak Copyleft Packages (informational)

PackageVersionLicense
@img/sharp-libvips-darwin-arm641.0.4LGPL-3.0-or-later
@img/sharp-libvips-darwin-arm641.2.4LGPL-3.0-or-later
@img/sharp-libvips-darwin-x641.0.4LGPL-3.0-or-later
@img/sharp-libvips-darwin-x641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-arm1.0.5LGPL-3.0-or-later
@img/sharp-libvips-linux-arm1.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-arm641.0.4LGPL-3.0-or-later
@img/sharp-libvips-linux-arm641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-ppc641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-riscv641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-s390x1.0.4LGPL-3.0-or-later
@img/sharp-libvips-linux-s390x1.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-x641.0.4LGPL-3.0-or-later
@img/sharp-libvips-linux-x641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linuxmusl-arm641.0.4LGPL-3.0-or-later
@img/sharp-libvips-linuxmusl-arm641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linuxmusl-x641.0.4LGPL-3.0-or-later
@img/sharp-libvips-linuxmusl-x641.2.4LGPL-3.0-or-later
@img/sharp-wasm320.33.5Apache-2.0 AND LGPL-3.0-or-later AND MIT
@img/sharp-wasm320.34.5Apache-2.0 AND LGPL-3.0-or-later AND MIT
@img/sharp-win32-arm640.34.5Apache-2.0 AND LGPL-3.0-or-later
@img/sharp-win32-ia320.33.5Apache-2.0 AND LGPL-3.0-or-later
@img/sharp-win32-ia320.34.5Apache-2.0 AND LGPL-3.0-or-later
@img/sharp-win32-x640.33.5Apache-2.0 AND LGPL-3.0-or-later
@img/sharp-win32-x640.34.5Apache-2.0 AND LGPL-3.0-or-later
axe-core4.10.3MPL-2.0
dompurify3.3.1(MPL-2.0 OR Apache-2.0)
Resolved Packages (7)
PackageVersionOriginalResolvedSource
codemirror-lang-elixir4.0.0UNKNOWNApache-2.0GitHub repo (livebook-dev/codemirror-lang-elixir)
lezer-elixir1.1.2UNKNOWNApache-2.0GitHub repo (livebook-dev/lezer-elixir)
map-stream0.1.0UNKNOWNMITnpm page
memorystream0.3.1UNKNOWNMITnpm page / GitHub repo (JSBizon/node-memorystream)
pause-stream0.0.11["MIT", "Apache2"]MIT OR Apache-2.0npm page (dual-license list ["MIT","Apache2"])
posthog-js1.345.5SEE LICENSE IN LICENSEApache-2.0GitHub repo (PostHog/posthog-js)
valid-url1.0.9UNKNOWNMITnpm page / GitHub repo (ogt/valid-url)

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

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-kellamand 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>

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

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

Comment threadpackages/web/src/app/api/minidenticon/route.ts
Comment threadpackages/web/src/app/api/minidenticon/route.ts
Comment threadpackages/web/src/emails/inviteUserEmail.tsx
Comment threadpackages/web/src/emails/joinRequestSubmittedEmail.tsx

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

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

Comment threadpackages/web/src/actions.ts
@brendan-kellam
brendan-kellam merged commit 85cf4be into mainApr 2, 2026
10 of 11 checks passed
@brendan-kellam
brendan-kellam deleted the brendan-kellam/minidenticons-avatar branch April 2, 2026 00:49
@github-actionsgithub-actionsBot mentioned this pull request Apr 2, 2026
Sign up for freeto 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

@brendan-kellam
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

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

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

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

Conversation

@brendan-kellam

@brendan-kellambrendan-kellam commented Apr 1, 2026

Copy link
Copy Markdown
Contributor

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

coderabbitaiBot commented Apr 1, 2026

Copy link
Copy Markdown
Contributor

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 nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring 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 nameStatusExplanation
Title check✅ PassedThe title clearly and concisely summarizes the main change: replacing placeholder avatars with a new minidenticon-based UserAvatar component across the codebase.
Description Check✅ PassedCheck 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

github-actionsBot commented Apr 1, 2026

Copy link
Copy Markdown
Contributor

License Audit

Status: FAIL

MetricCount
Total packages2118
Resolved (non-standard)7
Unresolved2
Strong copyleft0
Weak copyleft27

Fail Reasons

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

Unresolved Packages

PackageVersionLicenseReason
@react-grab/cli0.1.23UNKNOWNPackage not found on npm registry; no repository or homepage available
@react-grab/mcp0.1.23UNKNOWNPackage not found on npm registry; no repository or homepage available

Weak Copyleft Packages (informational)

PackageVersionLicense
@img/sharp-libvips-darwin-arm641.0.4LGPL-3.0-or-later
@img/sharp-libvips-darwin-arm641.2.4LGPL-3.0-or-later
@img/sharp-libvips-darwin-x641.0.4LGPL-3.0-or-later
@img/sharp-libvips-darwin-x641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-arm1.0.5LGPL-3.0-or-later
@img/sharp-libvips-linux-arm1.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-arm641.0.4LGPL-3.0-or-later
@img/sharp-libvips-linux-arm641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-ppc641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-riscv641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-s390x1.0.4LGPL-3.0-or-later
@img/sharp-libvips-linux-s390x1.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-x641.0.4LGPL-3.0-or-later
@img/sharp-libvips-linux-x641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linuxmusl-arm641.0.4LGPL-3.0-or-later
@img/sharp-libvips-linuxmusl-arm641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linuxmusl-x641.0.4LGPL-3.0-or-later
@img/sharp-libvips-linuxmusl-x641.2.4LGPL-3.0-or-later
@img/sharp-wasm320.33.5Apache-2.0 AND LGPL-3.0-or-later AND MIT
@img/sharp-wasm320.34.5Apache-2.0 AND LGPL-3.0-or-later AND MIT
@img/sharp-win32-arm640.34.5Apache-2.0 AND LGPL-3.0-or-later
@img/sharp-win32-ia320.33.5Apache-2.0 AND LGPL-3.0-or-later
@img/sharp-win32-ia320.34.5Apache-2.0 AND LGPL-3.0-or-later
@img/sharp-win32-x640.33.5Apache-2.0 AND LGPL-3.0-or-later
@img/sharp-win32-x640.34.5Apache-2.0 AND LGPL-3.0-or-later
axe-core4.10.3MPL-2.0
dompurify3.3.1(MPL-2.0 OR Apache-2.0)
Resolved Packages (7)
PackageVersionOriginalResolvedSource
codemirror-lang-elixir4.0.0UNKNOWNApache-2.0GitHub repo (livebook-dev/codemirror-lang-elixir)
lezer-elixir1.1.2UNKNOWNApache-2.0GitHub repo (livebook-dev/lezer-elixir)
map-stream0.1.0UNKNOWNMITnpm page
memorystream0.3.1UNKNOWNMITnpm page / GitHub repo (JSBizon/node-memorystream)
pause-stream0.0.11["MIT", "Apache2"]MIT OR Apache-2.0npm page (dual-license list ["MIT","Apache2"])
posthog-js1.345.5SEE LICENSE IN LICENSEApache-2.0GitHub repo (PostHog/posthog-js)
valid-url1.0.9UNKNOWNMITnpm page / GitHub repo (ogt/valid-url)

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

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-kellamand 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>

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

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

Comment threadpackages/web/src/app/api/minidenticon/route.ts
Comment threadpackages/web/src/app/api/minidenticon/route.ts
Comment threadpackages/web/src/emails/inviteUserEmail.tsx
Comment threadpackages/web/src/emails/joinRequestSubmittedEmail.tsx

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

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

Comment threadpackages/web/src/actions.ts
@brendan-kellam
brendan-kellam merged commit 85cf4be into mainApr 2, 2026
10 of 11 checks passed
@brendan-kellam
brendan-kellam deleted the brendan-kellam/minidenticons-avatar branch April 2, 2026 00:49
@github-actionsgithub-actionsBot mentioned this pull request Apr 2, 2026
Sign up for freeto 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

@brendan-kellam
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

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

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

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

Conversation

@brendan-kellam

@brendan-kellambrendan-kellam commented Apr 1, 2026

Copy link
Copy Markdown
Contributor

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

coderabbitaiBot commented Apr 1, 2026

Copy link
Copy Markdown
Contributor

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 nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring 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 nameStatusExplanation
Title check✅ PassedThe title clearly and concisely summarizes the main change: replacing placeholder avatars with a new minidenticon-based UserAvatar component across the codebase.
Description Check✅ PassedCheck 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

github-actionsBot commented Apr 1, 2026

Copy link
Copy Markdown
Contributor

License Audit

Status: FAIL

MetricCount
Total packages2118
Resolved (non-standard)7
Unresolved2
Strong copyleft0
Weak copyleft27

Fail Reasons

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

Unresolved Packages

PackageVersionLicenseReason
@react-grab/cli0.1.23UNKNOWNPackage not found on npm registry; no repository or homepage available
@react-grab/mcp0.1.23UNKNOWNPackage not found on npm registry; no repository or homepage available

Weak Copyleft Packages (informational)

PackageVersionLicense
@img/sharp-libvips-darwin-arm641.0.4LGPL-3.0-or-later
@img/sharp-libvips-darwin-arm641.2.4LGPL-3.0-or-later
@img/sharp-libvips-darwin-x641.0.4LGPL-3.0-or-later
@img/sharp-libvips-darwin-x641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-arm1.0.5LGPL-3.0-or-later
@img/sharp-libvips-linux-arm1.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-arm641.0.4LGPL-3.0-or-later
@img/sharp-libvips-linux-arm641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-ppc641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-riscv641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-s390x1.0.4LGPL-3.0-or-later
@img/sharp-libvips-linux-s390x1.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-x641.0.4LGPL-3.0-or-later
@img/sharp-libvips-linux-x641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linuxmusl-arm641.0.4LGPL-3.0-or-later
@img/sharp-libvips-linuxmusl-arm641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linuxmusl-x641.0.4LGPL-3.0-or-later
@img/sharp-libvips-linuxmusl-x641.2.4LGPL-3.0-or-later
@img/sharp-wasm320.33.5Apache-2.0 AND LGPL-3.0-or-later AND MIT
@img/sharp-wasm320.34.5Apache-2.0 AND LGPL-3.0-or-later AND MIT
@img/sharp-win32-arm640.34.5Apache-2.0 AND LGPL-3.0-or-later
@img/sharp-win32-ia320.33.5Apache-2.0 AND LGPL-3.0-or-later
@img/sharp-win32-ia320.34.5Apache-2.0 AND LGPL-3.0-or-later
@img/sharp-win32-x640.33.5Apache-2.0 AND LGPL-3.0-or-later
@img/sharp-win32-x640.34.5Apache-2.0 AND LGPL-3.0-or-later
axe-core4.10.3MPL-2.0
dompurify3.3.1(MPL-2.0 OR Apache-2.0)
Resolved Packages (7)
PackageVersionOriginalResolvedSource
codemirror-lang-elixir4.0.0UNKNOWNApache-2.0GitHub repo (livebook-dev/codemirror-lang-elixir)
lezer-elixir1.1.2UNKNOWNApache-2.0GitHub repo (livebook-dev/lezer-elixir)
map-stream0.1.0UNKNOWNMITnpm page
memorystream0.3.1UNKNOWNMITnpm page / GitHub repo (JSBizon/node-memorystream)
pause-stream0.0.11["MIT", "Apache2"]MIT OR Apache-2.0npm page (dual-license list ["MIT","Apache2"])
posthog-js1.345.5SEE LICENSE IN LICENSEApache-2.0GitHub repo (PostHog/posthog-js)
valid-url1.0.9UNKNOWNMITnpm page / GitHub repo (ogt/valid-url)

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

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-kellamand 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>

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

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

Comment threadpackages/web/src/app/api/minidenticon/route.ts
Comment threadpackages/web/src/app/api/minidenticon/route.ts
Comment threadpackages/web/src/emails/inviteUserEmail.tsx
Comment threadpackages/web/src/emails/joinRequestSubmittedEmail.tsx

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

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

Comment threadpackages/web/src/actions.ts
@brendan-kellam
brendan-kellam merged commit 85cf4be into mainApr 2, 2026
10 of 11 checks passed
@brendan-kellam
brendan-kellam deleted the brendan-kellam/minidenticons-avatar branch April 2, 2026 00:49
@github-actionsgithub-actionsBot mentioned this pull request Apr 2, 2026
Sign up for freeto 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

@brendan-kellam
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

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

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

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

Conversation

@brendan-kellam

@brendan-kellambrendan-kellam commented Apr 1, 2026

Copy link
Copy Markdown
Contributor

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

coderabbitaiBot commented Apr 1, 2026

Copy link
Copy Markdown
Contributor

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 nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring 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 nameStatusExplanation
Title check✅ PassedThe title clearly and concisely summarizes the main change: replacing placeholder avatars with a new minidenticon-based UserAvatar component across the codebase.
Description Check✅ PassedCheck 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

github-actionsBot commented Apr 1, 2026

Copy link
Copy Markdown
Contributor

License Audit

Status: FAIL

MetricCount
Total packages2118
Resolved (non-standard)7
Unresolved2
Strong copyleft0
Weak copyleft27

Fail Reasons

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

Unresolved Packages

PackageVersionLicenseReason
@react-grab/cli0.1.23UNKNOWNPackage not found on npm registry; no repository or homepage available
@react-grab/mcp0.1.23UNKNOWNPackage not found on npm registry; no repository or homepage available

Weak Copyleft Packages (informational)

PackageVersionLicense
@img/sharp-libvips-darwin-arm641.0.4LGPL-3.0-or-later
@img/sharp-libvips-darwin-arm641.2.4LGPL-3.0-or-later
@img/sharp-libvips-darwin-x641.0.4LGPL-3.0-or-later
@img/sharp-libvips-darwin-x641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-arm1.0.5LGPL-3.0-or-later
@img/sharp-libvips-linux-arm1.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-arm641.0.4LGPL-3.0-or-later
@img/sharp-libvips-linux-arm641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-ppc641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-riscv641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-s390x1.0.4LGPL-3.0-or-later
@img/sharp-libvips-linux-s390x1.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-x641.0.4LGPL-3.0-or-later
@img/sharp-libvips-linux-x641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linuxmusl-arm641.0.4LGPL-3.0-or-later
@img/sharp-libvips-linuxmusl-arm641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linuxmusl-x641.0.4LGPL-3.0-or-later
@img/sharp-libvips-linuxmusl-x641.2.4LGPL-3.0-or-later
@img/sharp-wasm320.33.5Apache-2.0 AND LGPL-3.0-or-later AND MIT
@img/sharp-wasm320.34.5Apache-2.0 AND LGPL-3.0-or-later AND MIT
@img/sharp-win32-arm640.34.5Apache-2.0 AND LGPL-3.0-or-later
@img/sharp-win32-ia320.33.5Apache-2.0 AND LGPL-3.0-or-later
@img/sharp-win32-ia320.34.5Apache-2.0 AND LGPL-3.0-or-later
@img/sharp-win32-x640.33.5Apache-2.0 AND LGPL-3.0-or-later
@img/sharp-win32-x640.34.5Apache-2.0 AND LGPL-3.0-or-later
axe-core4.10.3MPL-2.0
dompurify3.3.1(MPL-2.0 OR Apache-2.0)
Resolved Packages (7)
PackageVersionOriginalResolvedSource
codemirror-lang-elixir4.0.0UNKNOWNApache-2.0GitHub repo (livebook-dev/codemirror-lang-elixir)
lezer-elixir1.1.2UNKNOWNApache-2.0GitHub repo (livebook-dev/lezer-elixir)
map-stream0.1.0UNKNOWNMITnpm page
memorystream0.3.1UNKNOWNMITnpm page / GitHub repo (JSBizon/node-memorystream)
pause-stream0.0.11["MIT", "Apache2"]MIT OR Apache-2.0npm page (dual-license list ["MIT","Apache2"])
posthog-js1.345.5SEE LICENSE IN LICENSEApache-2.0GitHub repo (PostHog/posthog-js)
valid-url1.0.9UNKNOWNMITnpm page / GitHub repo (ogt/valid-url)

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

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-kellamand 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>

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

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

Comment threadpackages/web/src/app/api/minidenticon/route.ts
Comment threadpackages/web/src/app/api/minidenticon/route.ts
Comment threadpackages/web/src/emails/inviteUserEmail.tsx
Comment threadpackages/web/src/emails/joinRequestSubmittedEmail.tsx

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

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

Comment threadpackages/web/src/actions.ts
@brendan-kellam
brendan-kellam merged commit 85cf4be into mainApr 2, 2026
10 of 11 checks passed
@brendan-kellam
brendan-kellam deleted the brendan-kellam/minidenticons-avatar branch April 2, 2026 00:49
@github-actionsgithub-actionsBot mentioned this pull request Apr 2, 2026
Sign up for freeto 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

@brendan-kellam
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

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

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

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

Conversation

@brendan-kellam

@brendan-kellambrendan-kellam commented Apr 1, 2026

Copy link
Copy Markdown
Contributor

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

coderabbitaiBot commented Apr 1, 2026

Copy link
Copy Markdown
Contributor

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 nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring 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 nameStatusExplanation
Title check✅ PassedThe title clearly and concisely summarizes the main change: replacing placeholder avatars with a new minidenticon-based UserAvatar component across the codebase.
Description Check✅ PassedCheck 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

github-actionsBot commented Apr 1, 2026

Copy link
Copy Markdown
Contributor

License Audit

Status: FAIL

MetricCount
Total packages2118
Resolved (non-standard)7
Unresolved2
Strong copyleft0
Weak copyleft27

Fail Reasons

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

Unresolved Packages

PackageVersionLicenseReason
@react-grab/cli0.1.23UNKNOWNPackage not found on npm registry; no repository or homepage available
@react-grab/mcp0.1.23UNKNOWNPackage not found on npm registry; no repository or homepage available

Weak Copyleft Packages (informational)

PackageVersionLicense
@img/sharp-libvips-darwin-arm641.0.4LGPL-3.0-or-later
@img/sharp-libvips-darwin-arm641.2.4LGPL-3.0-or-later
@img/sharp-libvips-darwin-x641.0.4LGPL-3.0-or-later
@img/sharp-libvips-darwin-x641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-arm1.0.5LGPL-3.0-or-later
@img/sharp-libvips-linux-arm1.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-arm641.0.4LGPL-3.0-or-later
@img/sharp-libvips-linux-arm641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-ppc641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-riscv641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-s390x1.0.4LGPL-3.0-or-later
@img/sharp-libvips-linux-s390x1.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-x641.0.4LGPL-3.0-or-later
@img/sharp-libvips-linux-x641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linuxmusl-arm641.0.4LGPL-3.0-or-later
@img/sharp-libvips-linuxmusl-arm641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linuxmusl-x641.0.4LGPL-3.0-or-later
@img/sharp-libvips-linuxmusl-x641.2.4LGPL-3.0-or-later
@img/sharp-wasm320.33.5Apache-2.0 AND LGPL-3.0-or-later AND MIT
@img/sharp-wasm320.34.5Apache-2.0 AND LGPL-3.0-or-later AND MIT
@img/sharp-win32-arm640.34.5Apache-2.0 AND LGPL-3.0-or-later
@img/sharp-win32-ia320.33.5Apache-2.0 AND LGPL-3.0-or-later
@img/sharp-win32-ia320.34.5Apache-2.0 AND LGPL-3.0-or-later
@img/sharp-win32-x640.33.5Apache-2.0 AND LGPL-3.0-or-later
@img/sharp-win32-x640.34.5Apache-2.0 AND LGPL-3.0-or-later
axe-core4.10.3MPL-2.0
dompurify3.3.1(MPL-2.0 OR Apache-2.0)
Resolved Packages (7)
PackageVersionOriginalResolvedSource
codemirror-lang-elixir4.0.0UNKNOWNApache-2.0GitHub repo (livebook-dev/codemirror-lang-elixir)
lezer-elixir1.1.2UNKNOWNApache-2.0GitHub repo (livebook-dev/lezer-elixir)
map-stream0.1.0UNKNOWNMITnpm page
memorystream0.3.1UNKNOWNMITnpm page / GitHub repo (JSBizon/node-memorystream)
pause-stream0.0.11["MIT", "Apache2"]MIT OR Apache-2.0npm page (dual-license list ["MIT","Apache2"])
posthog-js1.345.5SEE LICENSE IN LICENSEApache-2.0GitHub repo (PostHog/posthog-js)
valid-url1.0.9UNKNOWNMITnpm page / GitHub repo (ogt/valid-url)

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

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-kellamand 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>

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

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

Comment threadpackages/web/src/app/api/minidenticon/route.ts
Comment threadpackages/web/src/app/api/minidenticon/route.ts
Comment threadpackages/web/src/emails/inviteUserEmail.tsx
Comment threadpackages/web/src/emails/joinRequestSubmittedEmail.tsx

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

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

Comment threadpackages/web/src/actions.ts
@brendan-kellam
brendan-kellam merged commit 85cf4be into mainApr 2, 2026
10 of 11 checks passed
@brendan-kellam
brendan-kellam deleted the brendan-kellam/minidenticons-avatar branch April 2, 2026 00:49
@github-actionsgithub-actionsBot mentioned this pull request Apr 2, 2026
Sign up for freeto 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

@brendan-kellam
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

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

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

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

Conversation

@brendan-kellam

@brendan-kellambrendan-kellam commented Apr 1, 2026

Copy link
Copy Markdown
Contributor

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

coderabbitaiBot commented Apr 1, 2026

Copy link
Copy Markdown
Contributor

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 nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring 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 nameStatusExplanation
Title check✅ PassedThe title clearly and concisely summarizes the main change: replacing placeholder avatars with a new minidenticon-based UserAvatar component across the codebase.
Description Check✅ PassedCheck 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

github-actionsBot commented Apr 1, 2026

Copy link
Copy Markdown
Contributor

License Audit

Status: FAIL

MetricCount
Total packages2118
Resolved (non-standard)7
Unresolved2
Strong copyleft0
Weak copyleft27

Fail Reasons

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

Unresolved Packages

PackageVersionLicenseReason
@react-grab/cli0.1.23UNKNOWNPackage not found on npm registry; no repository or homepage available
@react-grab/mcp0.1.23UNKNOWNPackage not found on npm registry; no repository or homepage available

Weak Copyleft Packages (informational)

PackageVersionLicense
@img/sharp-libvips-darwin-arm641.0.4LGPL-3.0-or-later
@img/sharp-libvips-darwin-arm641.2.4LGPL-3.0-or-later
@img/sharp-libvips-darwin-x641.0.4LGPL-3.0-or-later
@img/sharp-libvips-darwin-x641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-arm1.0.5LGPL-3.0-or-later
@img/sharp-libvips-linux-arm1.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-arm641.0.4LGPL-3.0-or-later
@img/sharp-libvips-linux-arm641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-ppc641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-riscv641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-s390x1.0.4LGPL-3.0-or-later
@img/sharp-libvips-linux-s390x1.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-x641.0.4LGPL-3.0-or-later
@img/sharp-libvips-linux-x641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linuxmusl-arm641.0.4LGPL-3.0-or-later
@img/sharp-libvips-linuxmusl-arm641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linuxmusl-x641.0.4LGPL-3.0-or-later
@img/sharp-libvips-linuxmusl-x641.2.4LGPL-3.0-or-later
@img/sharp-wasm320.33.5Apache-2.0 AND LGPL-3.0-or-later AND MIT
@img/sharp-wasm320.34.5Apache-2.0 AND LGPL-3.0-or-later AND MIT
@img/sharp-win32-arm640.34.5Apache-2.0 AND LGPL-3.0-or-later
@img/sharp-win32-ia320.33.5Apache-2.0 AND LGPL-3.0-or-later
@img/sharp-win32-ia320.34.5Apache-2.0 AND LGPL-3.0-or-later
@img/sharp-win32-x640.33.5Apache-2.0 AND LGPL-3.0-or-later
@img/sharp-win32-x640.34.5Apache-2.0 AND LGPL-3.0-or-later
axe-core4.10.3MPL-2.0
dompurify3.3.1(MPL-2.0 OR Apache-2.0)
Resolved Packages (7)
PackageVersionOriginalResolvedSource
codemirror-lang-elixir4.0.0UNKNOWNApache-2.0GitHub repo (livebook-dev/codemirror-lang-elixir)
lezer-elixir1.1.2UNKNOWNApache-2.0GitHub repo (livebook-dev/lezer-elixir)
map-stream0.1.0UNKNOWNMITnpm page
memorystream0.3.1UNKNOWNMITnpm page / GitHub repo (JSBizon/node-memorystream)
pause-stream0.0.11["MIT", "Apache2"]MIT OR Apache-2.0npm page (dual-license list ["MIT","Apache2"])
posthog-js1.345.5SEE LICENSE IN LICENSEApache-2.0GitHub repo (PostHog/posthog-js)
valid-url1.0.9UNKNOWNMITnpm page / GitHub repo (ogt/valid-url)

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

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-kellamand 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>

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

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

Comment threadpackages/web/src/app/api/minidenticon/route.ts
Comment threadpackages/web/src/app/api/minidenticon/route.ts
Comment threadpackages/web/src/emails/inviteUserEmail.tsx
Comment threadpackages/web/src/emails/joinRequestSubmittedEmail.tsx

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

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

Comment threadpackages/web/src/actions.ts
@brendan-kellam
brendan-kellam merged commit 85cf4be into mainApr 2, 2026
10 of 11 checks passed
@brendan-kellam
brendan-kellam deleted the brendan-kellam/minidenticons-avatar branch April 2, 2026 00:49
@github-actionsgithub-actionsBot mentioned this pull request Apr 2, 2026
Sign up for freeto 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

@brendan-kellam
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

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

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

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

Conversation

@brendan-kellam

@brendan-kellambrendan-kellam commented Apr 1, 2026

Copy link
Copy Markdown
Contributor

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

coderabbitaiBot commented Apr 1, 2026

Copy link
Copy Markdown
Contributor

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 nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring 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 nameStatusExplanation
Title check✅ PassedThe title clearly and concisely summarizes the main change: replacing placeholder avatars with a new minidenticon-based UserAvatar component across the codebase.
Description Check✅ PassedCheck 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

github-actionsBot commented Apr 1, 2026

Copy link
Copy Markdown
Contributor

License Audit

Status: FAIL

MetricCount
Total packages2118
Resolved (non-standard)7
Unresolved2
Strong copyleft0
Weak copyleft27

Fail Reasons

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

Unresolved Packages

PackageVersionLicenseReason
@react-grab/cli0.1.23UNKNOWNPackage not found on npm registry; no repository or homepage available
@react-grab/mcp0.1.23UNKNOWNPackage not found on npm registry; no repository or homepage available

Weak Copyleft Packages (informational)

PackageVersionLicense
@img/sharp-libvips-darwin-arm641.0.4LGPL-3.0-or-later
@img/sharp-libvips-darwin-arm641.2.4LGPL-3.0-or-later
@img/sharp-libvips-darwin-x641.0.4LGPL-3.0-or-later
@img/sharp-libvips-darwin-x641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-arm1.0.5LGPL-3.0-or-later
@img/sharp-libvips-linux-arm1.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-arm641.0.4LGPL-3.0-or-later
@img/sharp-libvips-linux-arm641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-ppc641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-riscv641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-s390x1.0.4LGPL-3.0-or-later
@img/sharp-libvips-linux-s390x1.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-x641.0.4LGPL-3.0-or-later
@img/sharp-libvips-linux-x641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linuxmusl-arm641.0.4LGPL-3.0-or-later
@img/sharp-libvips-linuxmusl-arm641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linuxmusl-x641.0.4LGPL-3.0-or-later
@img/sharp-libvips-linuxmusl-x641.2.4LGPL-3.0-or-later
@img/sharp-wasm320.33.5Apache-2.0 AND LGPL-3.0-or-later AND MIT
@img/sharp-wasm320.34.5Apache-2.0 AND LGPL-3.0-or-later AND MIT
@img/sharp-win32-arm640.34.5Apache-2.0 AND LGPL-3.0-or-later
@img/sharp-win32-ia320.33.5Apache-2.0 AND LGPL-3.0-or-later
@img/sharp-win32-ia320.34.5Apache-2.0 AND LGPL-3.0-or-later
@img/sharp-win32-x640.33.5Apache-2.0 AND LGPL-3.0-or-later
@img/sharp-win32-x640.34.5Apache-2.0 AND LGPL-3.0-or-later
axe-core4.10.3MPL-2.0
dompurify3.3.1(MPL-2.0 OR Apache-2.0)
Resolved Packages (7)
PackageVersionOriginalResolvedSource
codemirror-lang-elixir4.0.0UNKNOWNApache-2.0GitHub repo (livebook-dev/codemirror-lang-elixir)
lezer-elixir1.1.2UNKNOWNApache-2.0GitHub repo (livebook-dev/lezer-elixir)
map-stream0.1.0UNKNOWNMITnpm page
memorystream0.3.1UNKNOWNMITnpm page / GitHub repo (JSBizon/node-memorystream)
pause-stream0.0.11["MIT", "Apache2"]MIT OR Apache-2.0npm page (dual-license list ["MIT","Apache2"])
posthog-js1.345.5SEE LICENSE IN LICENSEApache-2.0GitHub repo (PostHog/posthog-js)
valid-url1.0.9UNKNOWNMITnpm page / GitHub repo (ogt/valid-url)

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

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-kellamand 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>

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

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

Comment threadpackages/web/src/app/api/minidenticon/route.ts
Comment threadpackages/web/src/app/api/minidenticon/route.ts
Comment threadpackages/web/src/emails/inviteUserEmail.tsx
Comment threadpackages/web/src/emails/joinRequestSubmittedEmail.tsx

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

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

Comment threadpackages/web/src/actions.ts
@brendan-kellam
brendan-kellam merged commit 85cf4be into mainApr 2, 2026
10 of 11 checks passed
@brendan-kellam
brendan-kellam deleted the brendan-kellam/minidenticons-avatar branch April 2, 2026 00:49
@github-actionsgithub-actionsBot mentioned this pull request Apr 2, 2026
Sign up for freeto 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

@brendan-kellam
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

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

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

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

Conversation

@brendan-kellam

@brendan-kellambrendan-kellam commented Apr 1, 2026

Copy link
Copy Markdown
Contributor

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

coderabbitaiBot commented Apr 1, 2026

Copy link
Copy Markdown
Contributor

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 nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring 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 nameStatusExplanation
Title check✅ PassedThe title clearly and concisely summarizes the main change: replacing placeholder avatars with a new minidenticon-based UserAvatar component across the codebase.
Description Check✅ PassedCheck 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

github-actionsBot commented Apr 1, 2026

Copy link
Copy Markdown
Contributor

License Audit

Status: FAIL

MetricCount
Total packages2118
Resolved (non-standard)7
Unresolved2
Strong copyleft0
Weak copyleft27

Fail Reasons

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

Unresolved Packages

PackageVersionLicenseReason
@react-grab/cli0.1.23UNKNOWNPackage not found on npm registry; no repository or homepage available
@react-grab/mcp0.1.23UNKNOWNPackage not found on npm registry; no repository or homepage available

Weak Copyleft Packages (informational)

PackageVersionLicense
@img/sharp-libvips-darwin-arm641.0.4LGPL-3.0-or-later
@img/sharp-libvips-darwin-arm641.2.4LGPL-3.0-or-later
@img/sharp-libvips-darwin-x641.0.4LGPL-3.0-or-later
@img/sharp-libvips-darwin-x641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-arm1.0.5LGPL-3.0-or-later
@img/sharp-libvips-linux-arm1.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-arm641.0.4LGPL-3.0-or-later
@img/sharp-libvips-linux-arm641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-ppc641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-riscv641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-s390x1.0.4LGPL-3.0-or-later
@img/sharp-libvips-linux-s390x1.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-x641.0.4LGPL-3.0-or-later
@img/sharp-libvips-linux-x641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linuxmusl-arm641.0.4LGPL-3.0-or-later
@img/sharp-libvips-linuxmusl-arm641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linuxmusl-x641.0.4LGPL-3.0-or-later
@img/sharp-libvips-linuxmusl-x641.2.4LGPL-3.0-or-later
@img/sharp-wasm320.33.5Apache-2.0 AND LGPL-3.0-or-later AND MIT
@img/sharp-wasm320.34.5Apache-2.0 AND LGPL-3.0-or-later AND MIT
@img/sharp-win32-arm640.34.5Apache-2.0 AND LGPL-3.0-or-later
@img/sharp-win32-ia320.33.5Apache-2.0 AND LGPL-3.0-or-later
@img/sharp-win32-ia320.34.5Apache-2.0 AND LGPL-3.0-or-later
@img/sharp-win32-x640.33.5Apache-2.0 AND LGPL-3.0-or-later
@img/sharp-win32-x640.34.5Apache-2.0 AND LGPL-3.0-or-later
axe-core4.10.3MPL-2.0
dompurify3.3.1(MPL-2.0 OR Apache-2.0)
Resolved Packages (7)
PackageVersionOriginalResolvedSource
codemirror-lang-elixir4.0.0UNKNOWNApache-2.0GitHub repo (livebook-dev/codemirror-lang-elixir)
lezer-elixir1.1.2UNKNOWNApache-2.0GitHub repo (livebook-dev/lezer-elixir)
map-stream0.1.0UNKNOWNMITnpm page
memorystream0.3.1UNKNOWNMITnpm page / GitHub repo (JSBizon/node-memorystream)
pause-stream0.0.11["MIT", "Apache2"]MIT OR Apache-2.0npm page (dual-license list ["MIT","Apache2"])
posthog-js1.345.5SEE LICENSE IN LICENSEApache-2.0GitHub repo (PostHog/posthog-js)
valid-url1.0.9UNKNOWNMITnpm page / GitHub repo (ogt/valid-url)

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

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-kellamand 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>

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

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

Comment threadpackages/web/src/app/api/minidenticon/route.ts
Comment threadpackages/web/src/app/api/minidenticon/route.ts
Comment threadpackages/web/src/emails/inviteUserEmail.tsx
Comment threadpackages/web/src/emails/joinRequestSubmittedEmail.tsx

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

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

Comment threadpackages/web/src/actions.ts
@brendan-kellam
brendan-kellam merged commit 85cf4be into mainApr 2, 2026
10 of 11 checks passed
@brendan-kellam
brendan-kellam deleted the brendan-kellam/minidenticons-avatar branch April 2, 2026 00:49
@github-actionsgithub-actionsBot mentioned this pull request Apr 2, 2026
Sign up for freeto 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

@brendan-kellam