feat(web): add /api/avatar resolver#1159
Conversation
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>
|
Caution Review failedThe pull request is closed. ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
WalkthroughAdds a new Changes
Sequence DiagramsequenceDiagram
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
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Review rate limit: 5/8 reviews remaining, refill in 16 minutes and 38 seconds.Comment |
This comment has been minimized.
This comment has been minimized.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
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
📒 Files selected for processing (5)
CHANGELOG.mdpackages/web/src/app/(app)/browse/components/commitParts.tsxpackages/web/src/app/api/(server)/avatar/route.tspackages/web/src/components/userAvatar.tsxpackages/web/src/features/chat/components/chatThread/messageAvatar.tsx
💤 Files with no reviewable changes (1)
- packages/web/src/features/chat/components/chatThread/messageAvatar.tsx
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>
Summary
GET /api/avatar?email=<email>. Looks up the email againstUser(org-scoped). If found andimageis set, 302 to that URL withCache-Control: public, max-age=300. Otherwise returns an inline minidenticon SVG withCache-Control: public, max-age=31536000, immutable. Never 4xx — auth/lookup failures fall through to the identicon so<img>always resolves.UserAvatarto compute its src from this resolver instead of inlining aminidenticondata 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.<AvatarImage>for a raw<img>insideUserAvatar.AvatarImagedelays painting until its internalnew Image().onloadfires (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.titleattribute on displayed avatars inAuthorsAvatarGroupso hovering an avatar shows the email.MessageAvatarwrapper (it just forwarded toUserAvatar).Caching properties
immutable— they're a deterministic function of email, output cannot change.🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Changed
Removed
Documentation