Skip to content

feat(web): add /api/avatar resolver#1159

Merged
brendan-kellam merged 4 commits intomainfrom
brendan/avatar-resolver
Apr 29, 2026
Merged

feat(web): add /api/avatar resolver#1159
brendan-kellam merged 4 commits intomainfrom
brendan/avatar-resolver

Conversation

@brendan-kellam
Copy link
Copy Markdown
Contributor

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

Summary

  • Adds GET /api/avatar?email=<email>. Looks up the email against User (org-scoped). If found and image is set, 302 to that URL with Cache-Control: public, max-age=300. Otherwise returns an inline minidenticon SVG with Cache-Control: public, max-age=31536000, immutable. Never 4xx — auth/lookup failures fall through to the identicon so <img> always resolves.
  • Updates UserAvatar to compute its src from this resolver instead of inlining a minidenticon data URI on the client. Every existing call site automatically picks up real profile pictures where the email matches a Sourcebot user — no consumer changes needed.
  • Swaps Radix's <AvatarImage> for a raw <img> inside UserAvatar. AvatarImage delays painting until its internal new Image().onload fires (async even when the URL is in HTTP cache), which manifests as a flicker every time the avatar mounts under aggressive churn (the use case that surfaced this: a CodeMirror gutter that mounts/unmounts cells per visible line during scroll). Raw <img> paints cached responses synchronously.
  • Adds a native title attribute on displayed avatars in AuthorsAvatarGroup so hovering an avatar shows the email.
  • Removes the unused MessageAvatar wrapper (it just forwarded to UserAvatar).

Caching properties

  • User images: 5 min cache, so profile-picture updates propagate within minutes.
  • Identicons: 1 year immutable — they're a deterministic function of email, output cannot change.
  • Browser handles caching natively from response headers; no client-side fetching, no React state, no provider.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Server-resolved profile pictures with deterministic identicon fallback for missing images.
  • Changed

    • Avatar visuals now load via a centralized avatar endpoint and show email tooltips on hover.
    • Image rendering logic updated for more consistent display across the app.
  • Removed

    • In-app message avatar component removed.
  • Documentation

    • Changelog updated to document the avatar API change.

Adds a new `/api/avatar?email=<email>` endpoint that resolves an email
to either the matching Sourcebot user's profile image (302 redirect with
short cache) or a deterministic minidenticon SVG (long-lived immutable
cache). Falls back to the identicon on auth or lookup failure so avatars
never break for anonymous viewers.

Updates `UserAvatar` to compute its src from this resolver instead of
generating an inline minidenticon data URI client-side. Every existing
call site automatically picks up real profile pictures where the email
matches a Sourcebot user — no consumer changes needed.

Also swaps Radix's `<AvatarImage>` for a raw `<img>`. AvatarImage delays
painting until its internal `new Image().onload` fires (async even from
HTTP cache), which manifests as a flicker every time the avatar mounts
under aggressive churn (e.g., in a CodeMirror gutter). The browser
paints cached `<img>` synchronously.

Adds a native `title` tooltip to the displayed avatars in
`AuthorsAvatarGroup` and removes the unused `MessageAvatar` wrapper.

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

coderabbitai Bot commented Apr 29, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: bfe4541b-4917-418f-89ec-67a6df0f5877

📥 Commits

Reviewing files that changed from the base of the PR and between 9cd3080 and 4c2e9ed.

📒 Files selected for processing (1)
  • packages/web/src/app/api/(server)/avatar/route.ts

Walkthrough

Adds a new /api/avatar GET endpoint that resolves avatars by email (redirects to stored image or returns a deterministic identicon), updates UserAvatar to use that endpoint and adds author email tooltips, and removes the MessageAvatar component.

Changes

Cohort / File(s) Summary
Avatar API
packages/web/src/app/api/(server)/avatar/route.ts
New GET route validating email via zod; optional-auth org-scoped user lookup; 302 redirect to user image when present; otherwise returns deterministic minidenticon SVG; responses set Cache-Control: public, max-age=300; 400 on email validation failure.
User Avatar UI
packages/web/src/components/userAvatar.tsx, packages/web/src/app/(app)/browse/components/commitParts.tsx
UserAvatar now uses /api/avatar?email=... as image src and renders a plain <img> when src is truthy (no inline SVG identicon); AuthorsAvatarGroup passes author email as title for tooltip.
Removed Chat Avatar
packages/web/src/features/chat/components/chatThread/messageAvatar.tsx
Deleted MessageAvatar component and its props/type; chat UI no longer exports this component.
Changelog
CHANGELOG.md
Adds Unreleased ### Changed entry documenting the /api/avatar profile picture resolution API (refs PR #1159).

Sequence Diagram

sequenceDiagram
    participant UA as UserAvatar Component
    participant Browser as Browser
    participant API as /api/avatar
    participant Auth as Auth Middleware
    participant DB as User DB
    participant Identicon as Minidenticons

    UA->>Browser: render <img src="/api/avatar?email=...">
    Browser->>API: GET /api/avatar?email=...
    API->>Auth: optional auth (org scope)
    Auth-->>API: auth context
    API->>DB: lookup user by email within org
    alt user has image URL
        DB-->>API: image URL
        API->>Browser: 302 Redirect to image URL (Cache-Control: public, max-age=300)
        Browser->>Browser: fetch image URL
    else no image / not found / error
        API->>Identicon: generate deterministic SVG from email
        Identicon-->>API: SVG
        API->>Browser: 200 SVG (Content-Type: image/svg+xml, Cache-Control: public, max-age=300)
    end
    Browser-->>UA: display avatar
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'feat(web): add /api/avatar resolver' directly and clearly summarizes the main change—introducing a new GET /api/avatar endpoint. It is specific, concise, and highlights the primary feature addition.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ 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/avatar-resolver

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
Review rate limit: 5/8 reviews remaining, refill in 16 minutes and 38 seconds.

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.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

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

Inline comments:
In `@CHANGELOG.md`:
- Line 19: Change the two-sentence CHANGELOG entry into a single sentence under
[Unreleased] by merging the clauses about the new /api/avatar endpoint and the
minidenticon fallback into one sentence while preserving the PR link (e.g.,
"UserAvatar now resolves profile pictures via a new /api/avatar endpoint and
falls back to a minidenticon when no Sourcebot user match is found.
[`#1159`](https://github.com/sourcebot-dev/sourcebot/pull/1159)"); update the
existing line in CHANGELOG.md accordingly.

In `@packages/web/src/app/api/`(server)/avatar/route.ts:
- Around line 31-49: Handle ServiceError results from the auth lookup before
returning the identicon: after calling lookup check isServiceError(lookup) and
immediately return serviceErrorResponse(lookup) for transient/server errors; if
you intentionally want to fall back on auth failures only, branch on the
specific auth error case and ensure that path uses a short Cache-Control (no
long-lived immutable caching) before calling minidenticon(email, 50, 50). Update
the code around the isServiceError(lookup) / lookup handling (the lookup
variable, isServiceError, serviceErrorResponse, and minidenticon usage) so
ServiceErrors are not silently swallowed and long-lived caching is only applied
to true fallbacks.
- Around line 16-19: Replace the manual query extraction and early return with
Zod safeParse validation: define or reuse the query schema (e.g.,
queryParamsSchema with email string), call
queryParamsSchema.safeParse(Object.fromEntries(request.nextUrl.searchParams)),
and if parse fails return queryParamsSchemaValidationError(parse.error);
otherwise extract the validated email from parse.data and continue; update the
conditional that referenced the raw email variable (currently around
request.nextUrl.searchParams.get('email')) to use the validated value instead.

In `@packages/web/src/components/userAvatar.tsx`:
- Around line 21-33: The src assignment uses the nullish coalescing operator
(imageUrl ?? resolverUri) which treats an empty string as a valid value and
prevents falling back to resolverUri; change the expression to use logical OR
(imageUrl || resolverUri) so that empty string imageUrl also falls back to
resolverUri (update the src variable near the top of the userAvatar component
where imageUrl and resolverUri are referenced, leaving the rest of the
Avatar/render logic unchanged).
🪄 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: 8175af3e-d501-45ef-a613-0cb638940acf

📥 Commits

Reviewing files that changed from the base of the PR and between cbf50e7 and 614800a.

📒 Files selected for processing (5)
  • CHANGELOG.md
  • packages/web/src/app/(app)/browse/components/commitParts.tsx
  • packages/web/src/app/api/(server)/avatar/route.ts
  • packages/web/src/components/userAvatar.tsx
  • packages/web/src/features/chat/components/chatThread/messageAvatar.tsx
💤 Files with no reviewable changes (1)
  • packages/web/src/features/chat/components/chatThread/messageAvatar.tsx

Comment thread CHANGELOG.md Outdated
Comment thread packages/web/src/app/api/(server)/avatar/route.ts Outdated
Comment thread packages/web/src/app/api/(server)/avatar/route.ts
Comment thread packages/web/src/components/userAvatar.tsx
brendan-kellam and others added 2 commits April 29, 2026 15:45
Replaces the manual `searchParams.get('email')` + plain-text 400 response
with the Zod safeParse + queryParamsSchemaValidationError pattern used
elsewhere in the API. Errors now return structured JSON consistent with
the rest of the public API surface.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@brendan-kellam brendan-kellam merged commit 6faeb6d into main Apr 29, 2026
6 of 7 checks passed
@brendan-kellam brendan-kellam deleted the brendan/avatar-resolver branch April 29, 2026 22:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant