Skip to content

feat: add facehash avatars to desktop app#3835

Open
devin-ai-integration[bot] wants to merge 4 commits intomainfrom
devin/1770730413-add-facehash-avatars
Open

feat: add facehash avatars to desktop app#3835
devin-ai-integration[bot] wants to merge 4 commits intomainfrom
devin/1770730413-add-facehash-avatars

Conversation

@devin-ai-integration
Copy link
Contributor

@devin-ai-integration devin-ai-integration bot commented Feb 10, 2026

feat: add facehash avatars to replace initials in desktop app

Summary

Replaces the plain gray circles with text initials throughout the desktop app with facehash — a deterministic avatar generator that creates unique faces from any string. Same input always produces the same face.

What changed:

  • Contacts people list (PersonItem) — 32px avatars
  • Contact detail header — 48px avatar
  • Duplicate contact entries — 32px avatars
  • Organization member grid — 48px avatars
  • Human search results — 32px avatars
  • Profile button sidebar — Facehash as fallback when Gravatar is unavailable

Gravatar behavior: The user's own profile avatar still uses Gravatar (SHA-256 hash of email → gravatar.com/avatar/{hash}?d=404). The ?d=404 parameter makes Gravatar return a 404 for emails without an account, which triggers the onError handler on the <img> tag to show Facehash instead of Gravatar's default mystery person. When no email is available, getAvatarUrl() returns null and Facehash renders immediately.

Note: During the profile query's loading state, showFacehash will be true, so users with a valid Gravatar may see a brief flash of the Facehash face before their Gravatar image loads.

Review & Testing Checklist for Human

  • Visual check in running app (not tested by Devin): Facehash was never tested in the actual desktop app — tauri dev was not successfully run. Verify faces render correctly at 32px and 48px, clip properly in rounded containers, and don't cause layout shifts compared to the old w-8 h-8 / w-12 h-12 divs.
  • Profile button Gravatar → Facehash fallback: Test with an email that has no Gravatar account. Verify the ?d=404onError path works in Tauri's webview and Facehash actually renders (not a broken image icon).
  • Profile button styling regression: The gradient background (bg-linear-to-br from-indigo-400 to-purple-500) and border (border border-t border-neutral-400) were removed from the profile avatar container. Verify this still looks acceptable, especially when Facehash is showing.
  • Package maturity: facehash is at v0.0.7 with 0 dependents on npm. Evaluate whether this is acceptable for production use.

Recommended test plan:

  1. Run the desktop app locally (ONBOARDING=0 pnpm -F desktop tauri dev)
  2. Navigate to Contacts → verify people list shows faces instead of initials
  3. Click a contact → verify detail view shows larger face avatar
  4. Check organization details → verify member grid shows faces
  5. Use search → verify human results show face avatars
  6. Check sidebar profile button:
    • With a Gravatar-linked email → should show Gravatar image
    • With a non-Gravatar email → should show Facehash face (not mystery person or broken image)

Notes

  • interactive={false} and showInitial={false} props disable hover animations and the letter overlay, keeping avatars clean in list contexts
  • getInitials helper is still exported from shared.tsx for any remaining usages
  • profile.data! non-null assertion on the <img> src is guarded by the showFacehash check above it

Link to Devin run: https://app.devin.ai/sessions/f69fed2f923742c1a720419dffad67f9
Requested by: @ComputelessComputer

Co-Authored-By: john@hyprnote.com <john@hyprnote.com>
@devin-ai-integration
Copy link
Contributor Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR that start with 'DevinAI' or '@devin'.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment and CI monitoring

@netlify
Copy link

netlify bot commented Feb 10, 2026

Deploy Preview for hyprnote-storybook canceled.

Name Link
🔨 Latest commit 9b0ec47
🔍 Latest deploy log https://app.netlify.com/projects/hyprnote-storybook/deploys/698d6c7dd2dbac00089d4ac0

@netlify
Copy link

netlify bot commented Feb 10, 2026

Deploy Preview for hyprnote canceled.

Name Link
🔨 Latest commit 9b0ec47
🔍 Latest deploy log https://app.netlify.com/projects/hyprnote/deploys/698d6c7ddc33190008e74440

Copy link
Contributor Author

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

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

Devin Review found 1 potential issue.

View 4 additional findings in Devin Review.

Open in Devin Review

devin-ai-integration bot and others added 3 commits February 12, 2026 05:55
- getAvatarUrl returns null when no email (instead of SVG data URI)
- Gravatar URL uses ?d=404 so it 404s for unknown emails
- img onError handler switches to Facehash when Gravatar fails

Co-Authored-By: john@hyprnote.com <john@hyprnote.com>
Co-Authored-By: john@hyprnote.com <john@hyprnote.com>
Co-Authored-By: john@hyprnote.com <john@hyprnote.com>
Copy link
Contributor Author

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

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

Devin Review found 1 new potential issue.

View 3 additional findings in Devin Review.

Open in Devin Review

Comment on lines 299 to +307
},
});

const facehashName = useMemo(
() => auth?.session?.user.email || name || "user",
[auth?.session?.user.email, name],
);

const showFacehash = !profile.data || imgError;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

🟡 imgError state is never reset, permanently hiding Gravatar after any load failure

Once the Gravatar image fails to load (e.g., transient network error, or 404 from ?d=404), setImgError(true) is called at line 341, and imgError is never reset back to false. The showFacehash flag at line 307 (!profile.data || imgError) will then always be true, meaning the <img> element is never rendered again — so even if React Query refetches the profile URL (e.g., on window refocus), the Gravatar can never recover.

Root Cause and Impact

The imgError state at apps/desktop/src/components/main/sidebar/profile/index.tsx:292 is set to true on image load failure but has no mechanism to reset. Since showFacehash at line 307 short-circuits to true when imgError is set, the <img> tag is conditionally excluded from the DOM, preventing any future load attempt.

Scenario: A user has a valid Gravatar, but the image fails to load due to a transient network issue. After the network recovers, the Facehash avatar is permanently shown instead of the Gravatar for the lifetime of the component.

Fix: Reset imgError to false when profile.data changes, e.g., with a useEffect that watches profile.data.

(Refers to lines 292-307)

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

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