Skip to content

Add support for embedded Avatars with a Avatar spec#34

Merged
brionmario merged 1 commit into
thunder-id:mainfrom
brionmario:avatars
Jul 21, 2026
Merged

Add support for embedded Avatars with a Avatar spec#34
brionmario merged 1 commit into
thunder-id:mainfrom
brionmario:avatars

Conversation

@brionmario

@brionmario brionmario commented Jul 20, 2026

Copy link
Copy Markdown
Member

Purpose

Adds support for generating and rendering embedded avatars (initials, blank, and curated anonymous animal/entity icons) as a first-class avatar: logo spec, alongside the existing emoji: scheme, and wires it through React's Logo/Avatar/BaseUserAvatar and Vue's Logo components so both platforms render every logo spec type consistently.

Approach

  • Introduced an avatar: URI scheme in @thunderid/browser (isAvatarUri, extractAvatarParamsFromUri, generateAvatarDataUri) parsed as comma-separated key=value pairs: shape, variant, content, colors, bg.
  • Supports five variants: one_letter / two_letter / blank (gradient or flat-color swatches) and anonymous_animal / anonymous_entity (curated SVG icon sets, generated at build time from source SVGs into anonymousAnimalIcons.generated.ts / anonymousEntityIcons.generated.ts via scripts/generate-anonymous-*-icons.mjs).
  • Added pickAnonymousAvatarName / pickAnonymousEntityName, which deterministically hash a seed (e.g. a session ID) to the same curated icon every time, so anonymous users/sessions keep a stable avatar across renders.
  • Added resolveLogoUri as the single entry point that tries emoji:avatar: → falls back to treating the spec as a plain URL, so callers render "whatever a logo spec is" without special-casing each scheme.
  • Wired resolveLogoUri into React's Logo, Avatar, and the embedded-flow ImageComponent adapter, and Vue's Logo, replacing the previous emoji-only special-casing. BaseUserAvatar now shows a deterministic anonymous avatar (via pickAnonymousAvatarName) instead of a static default when user is null.
  • Fixed SPACryptoUtils.generateRandomBytes to use the native Web Crypto crypto.getRandomValues instead of the randombytes npm package, dropping an extra dependency and its non-CSPRNG fallback behavior in unsupported environments.

Related Issues

Related PRs

  • N/A

Checklist

  • Followed the contribution guidelines.
  • Manual test round performed and verified.
  • Documentation provided. (Avatar Spec)
  • Tests provided.
    • Unit Tests
    • Integration Tests
  • Breaking changes. (Fill if applicable)
    • Breaking changes section filled.
    • breaking change label added.

Security checks

  • Followed secure coding standards in WSO2 Secure Coding Guidelines
  • Confirmed that this PR doesn't commit any keys, passwords, tokens, usernames, or other secrets.

@coderabbitai

coderabbitai Bot commented Jul 20, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@brionmario, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 15 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 7dc8a6b2-529b-4399-8620-6043dc64f93b

📥 Commits

Reviewing files that changed from the base of the PR and between 890810a and a132f93.

⛔ Files ignored due to path filters (60)
  • packages/browser/src/utils/anonymousAnimalIcons.generated.ts is excluded by !**/*.generated.*
  • packages/browser/src/utils/anonymousEntityIcons.generated.ts is excluded by !**/*.generated.*
  • packages/browser/static/images/avatars/anonymous_animals/capybara.svg is excluded by !**/*.svg
  • packages/browser/static/images/avatars/anonymous_animals/chameleon.svg is excluded by !**/*.svg
  • packages/browser/static/images/avatars/anonymous_animals/chinchilla.svg is excluded by !**/*.svg
  • packages/browser/static/images/avatars/anonymous_animals/chipmunk.svg is excluded by !**/*.svg
  • packages/browser/static/images/avatars/anonymous_animals/chupacabra.svg is excluded by !**/*.svg
  • packages/browser/static/images/avatars/anonymous_animals/dingo.svg is excluded by !**/*.svg
  • packages/browser/static/images/avatars/anonymous_animals/dinosaur.svg is excluded by !**/*.svg
  • packages/browser/static/images/avatars/anonymous_animals/frog.svg is excluded by !**/*.svg
  • packages/browser/static/images/avatars/anonymous_animals/giraffe.svg is excluded by !**/*.svg
  • packages/browser/static/images/avatars/anonymous_animals/hedgehog.svg is excluded by !**/*.svg
  • packages/browser/static/images/avatars/anonymous_animals/hippo.svg is excluded by !**/*.svg
  • packages/browser/static/images/avatars/anonymous_animals/jackal.svg is excluded by !**/*.svg
  • packages/browser/static/images/avatars/anonymous_animals/jackalope.svg is excluded by !**/*.svg
  • packages/browser/static/images/avatars/anonymous_animals/mink.svg is excluded by !**/*.svg
  • packages/browser/static/images/avatars/anonymous_animals/otter.svg is excluded by !**/*.svg
  • packages/browser/static/images/avatars/anonymous_animals/platypus.svg is excluded by !**/*.svg
  • packages/browser/static/images/avatars/anonymous_animals/quagga.svg is excluded by !**/*.svg
  • packages/browser/static/images/avatars/anonymous_animals/raccoon.svg is excluded by !**/*.svg
  • packages/browser/static/images/avatars/anonymous_animals/skunk.svg is excluded by !**/*.svg
  • packages/browser/static/images/avatars/anonymous_entity/anchor.svg is excluded by !**/*.svg
  • packages/browser/static/images/avatars/anonymous_entity/antenna.svg is excluded by !**/*.svg
  • packages/browser/static/images/avatars/anonymous_entity/anvil.svg is excluded by !**/*.svg
  • packages/browser/static/images/avatars/anonymous_entity/arch.svg is excluded by !**/*.svg
  • packages/browser/static/images/avatars/anonymous_entity/bridge.svg is excluded by !**/*.svg
  • packages/browser/static/images/avatars/anonymous_entity/chevron.svg is excluded by !**/*.svg
  • packages/browser/static/images/avatars/anonymous_entity/circuit_node.svg is excluded by !**/*.svg
  • packages/browser/static/images/avatars/anonymous_entity/compass.svg is excluded by !**/*.svg
  • packages/browser/static/images/avatars/anonymous_entity/cube.svg is excluded by !**/*.svg
  • packages/browser/static/images/avatars/anonymous_entity/diamond.svg is excluded by !**/*.svg
  • packages/browser/static/images/avatars/anonymous_entity/dome.svg is excluded by !**/*.svg
  • packages/browser/static/images/avatars/anonymous_entity/gate.svg is excluded by !**/*.svg
  • packages/browser/static/images/avatars/anonymous_entity/hexagon.svg is excluded by !**/*.svg
  • packages/browser/static/images/avatars/anonymous_entity/key.svg is excluded by !**/*.svg
  • packages/browser/static/images/avatars/anonymous_entity/lighthouse.svg is excluded by !**/*.svg
  • packages/browser/static/images/avatars/anonymous_entity/lock.svg is excluded by !**/*.svg
  • packages/browser/static/images/avatars/anonymous_entity/obelisk.svg is excluded by !**/*.svg
  • packages/browser/static/images/avatars/anonymous_entity/octagon.svg is excluded by !**/*.svg
  • packages/browser/static/images/avatars/anonymous_entity/orbit_ring.svg is excluded by !**/*.svg
  • packages/browser/static/images/avatars/anonymous_entity/parallelogram.svg is excluded by !**/*.svg
  • packages/browser/static/images/avatars/anonymous_entity/pavilion.svg is excluded by !**/*.svg
  • packages/browser/static/images/avatars/anonymous_entity/pentagon.svg is excluded by !**/*.svg
  • packages/browser/static/images/avatars/anonymous_entity/plus_facet.svg is excluded by !**/*.svg
  • packages/browser/static/images/avatars/anonymous_entity/silo.svg is excluded by !**/*.svg
  • packages/browser/static/images/avatars/anonymous_entity/spiral.svg is excluded by !**/*.svg
  • packages/browser/static/images/avatars/anonymous_entity/spire.svg is excluded by !**/*.svg
  • packages/browser/static/images/avatars/anonymous_entity/star.svg is excluded by !**/*.svg
  • packages/browser/static/images/avatars/anonymous_entity/tower.svg is excluded by !**/*.svg
  • packages/browser/static/images/avatars/anonymous_entity/townhouse.svg is excluded by !**/*.svg
  • packages/browser/static/images/avatars/anonymous_entity/triangle_stack.svg is excluded by !**/*.svg
  • packages/browser/static/images/avatars/anonymous_entity/turbine.svg is excluded by !**/*.svg
  • packages/browser/static/images/avatars/anonymous_entity/valve.svg is excluded by !**/*.svg
  • packages/browser/static/images/avatars/anonymous_entity/windmill.svg is excluded by !**/*.svg
  • packages/react/.vitest-attachments/27677941df85b03d5d2cfa78a06ab03580dd9ac6.png is excluded by !**/*.png
  • packages/react/.vitest-attachments/3dab8c5a94eed1b7587e7dbb5fed1db2c644801d.png is excluded by !**/*.png
  • packages/react/.vitest-attachments/5a7d509e64e503baebcb9f280cb5bd323b6d722e.png is excluded by !**/*.png
  • packages/react/src/components/adapters/__tests__/__screenshots__/ImageComponent.test.tsx/ImageComponent-renders-an-emoji--spec-as-its-glyph--not-an-image-1.png is excluded by !**/*.png
  • packages/react/src/components/auth/Callback/__tests__/__screenshots__/TokenCallback.test.tsx/TokenCallback-verifies-the-token-as-a-registration-flow-when-type-REGISTRATION-is-present-1.png is excluded by !**/*.png
  • packages/react/src/components/auth/Callback/__tests__/__screenshots__/TokenCallback.test.tsx/TokenCallback-verifies-the-token-as-an-authentication-flow-when-type-AUTHENTICATION-is-present-1.png is excluded by !**/*.png
📒 Files selected for processing (27)
  • packages/browser/scripts/generate-anonymous-animal-icons.mjs
  • packages/browser/scripts/generate-anonymous-entity-icons.mjs
  • packages/browser/src/index.ts
  • packages/browser/src/utils/SPACryptoUtils.ts
  • packages/browser/src/utils/__tests__/extractAvatarParamsFromUri.test.ts
  • packages/browser/src/utils/__tests__/generateAvatarDataUri.test.ts
  • packages/browser/src/utils/__tests__/logoUriSchemes.test.ts
  • packages/browser/src/utils/__tests__/pickAnonymousAvatarName.test.ts
  • packages/browser/src/utils/__tests__/pickAnonymousEntityName.test.ts
  • packages/browser/src/utils/__tests__/resolveLogoUri.test.ts
  • packages/browser/src/utils/extractAvatarParamsFromUri.ts
  • packages/browser/src/utils/generateAvatarDataUri.ts
  • packages/browser/src/utils/hashStr.ts
  • packages/browser/src/utils/isAvatarUri.ts
  • packages/browser/src/utils/pickAnonymousAvatarName.ts
  • packages/browser/src/utils/pickAnonymousEntityName.ts
  • packages/browser/src/utils/resolveLogoUri.ts
  • packages/react/src/components/adapters/ImageComponent.tsx
  • packages/react/src/components/adapters/__tests__/ImageComponent.test.tsx
  • packages/react/src/components/presentation/UserAvatar/BaseUserAvatar.tsx
  • packages/react/src/components/primitives/Avatar/Avatar.tsx
  • packages/react/src/components/primitives/Avatar/__tests__/Avatar.test.tsx
  • packages/react/src/components/primitives/Logo/Logo.styles.ts
  • packages/react/src/components/primitives/Logo/Logo.tsx
  • packages/react/src/index.ts
  • packages/vue/src/components/primitives/Logo/Logo.css.ts
  • packages/vue/src/components/primitives/Logo/Logo.ts
📝 Walkthrough

Walkthrough

Adds avatar URI parsing and SVG generation for gradient, anonymous animal, and anonymous entity avatars. Adds logo resolution across emoji, avatar, and URL schemes, integrates it into React and Vue components, exposes browser APIs, and replaces browser random byte generation with Web Crypto.

Changes

Avatar and logo resolution

Layer / File(s) Summary
Anonymous icon asset generation
packages/browser/scripts/*
Generators convert anonymous animal and entity SVGs into typed recolorable icon maps.
Avatar parsing and generation
packages/browser/src/utils/{isAvatarUri,extractAvatarParamsFromUri,generateAvatarDataUri,pickAnonymous*}.ts, packages/browser/src/index.ts, packages/browser/src/utils/__tests__/*
Avatar specifications are parsed, rendered as gradients or anonymous icons, deterministically mapped from seeds, exported, and tested.
Logo URI resolution
packages/browser/src/utils/resolveLogoUri.ts, packages/browser/src/utils/__tests__/{logoUriSchemes,resolveLogoUri}.test.ts
Emoji, avatar, and URL specifications resolve into typed renderable results.
React avatar and logo integration
packages/react/src/components/{adapters,presentation,primitives}/*, packages/react/src/index.ts, packages/react/src/**/__tests__/*
React components render resolved emoji or image content and support deterministic anonymous user avatars.
Vue logo integration
packages/vue/src/components/primitives/Logo/*
Vue logos render resolved emoji or image content with dedicated emoji styling.

Browser randomness update

Layer / File(s) Summary
Web Crypto random bytes
packages/browser/src/utils/SPACryptoUtils.ts
Random byte generation now uses crypto.getRandomValues and Buffer.from.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Consumer
  participant resolveLogoUri
  participant AvatarParser
  participant AvatarGenerator
  Consumer->>resolveLogoUri: resolve logo specification
  resolveLogoUri->>AvatarParser: parse avatar URI
  AvatarParser-->>resolveLogoUri: return avatar parameters
  resolveLogoUri->>AvatarGenerator: generate avatar data URI
  AvatarGenerator-->>resolveLogoUri: return image source
  resolveLogoUri-->>Consumer: return resolved logo
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
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.
Title check ✅ Passed The title clearly summarizes the main change: adding avatar spec support.
Description check ✅ Passed The description covers Purpose, Approach, issues, PRs, checklist, and security checks, so it mostly matches the template.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🧹 Nitpick comments (1)
packages/vue/src/components/primitives/Logo/Logo.ts (1)

45-55: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Remove invalid HTML attributes from the emoji span.

The height and width attributes are not valid on a <span> element. They will be passed to the DOM but will not natively affect the size of the emoji, as emoji dimensions are controlled by CSS font-size. Consider removing them to keep the DOM output valid and prevent misleading behavior.

🧹 Proposed cleanup
           ? h(
               'span',
               {
                 'aria-label': props.alt,
                 class: withVendorCSSClassPrefix('logo__emoji'),
-                height: props.height,
                 role: 'img',
-                width: props.width,
               },
               [resolvedLogo.glyph],
             )
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/vue/src/components/primitives/Logo/Logo.ts` around lines 45 - 55,
Remove the height and width attributes from the emoji span created in the Logo
component’s resolvedLogo glyph branch. Keep the aria-label, class, role, and
glyph content unchanged; emoji sizing should remain controlled by the existing
CSS.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/browser/src/utils/generateAvatarDataUri.ts`:
- Around line 47-53: Extract the duplicated hashStr implementation into one
shared utility and import it wherever needed. Update
packages/browser/src/utils/generateAvatarDataUri.ts lines 47-53 to use the
shared export, remove the duplicate from
packages/browser/src/utils/pickAnonymousAvatarName.ts lines 23-29 and import the
utility, and do the same in
packages/browser/src/utils/pickAnonymousEntityName.ts lines 23-29; preserve the
existing hashStr behavior and ensure the shared module re-exports it as needed.
- Around line 137-138: Replace the direct truthiness lookup in
generateAnonymousAnimalAvatar with
Object.prototype.hasOwnProperty.call(ANONYMOUS_ANIMAL_ICONS, content) before
selecting the key. Apply the same membership check in
generateAnonymousEntityAvatar for ANONYMOUS_ENTITY_ICONS, covering both
referenced sites in packages/browser/src/utils/generateAvatarDataUri.ts;
preserve the existing fallback behavior for unknown keys.

In `@packages/browser/src/utils/SPACryptoUtils.ts`:
- Around line 66-68: Update generateRandomBytes() to fill the requested buffer
through multiple crypto.getRandomValues() calls, limiting each temporary
Uint8Array request to the API’s 65,536-byte maximum. Preserve the existing
Buffer return type and support requests larger than that limit without throwing.

In `@packages/react/src/components/primitives/Avatar/Avatar.tsx`:
- Around line 141-144: Update the emoji branch in the Avatar rendering logic
around resolvedIcon to return a span with role="img", preserving the emoji glyph
as its content and applying the avatar’s alt text for accessibility instead of
returning a bare string.

In `@packages/vue/src/components/primitives/Logo/Logo.css.ts`:
- Around line 50-57: Replace the hardcoded `.thunderid-logo__emoji` selector
with a prefix-independent or dynamically vendor-prefixed selector that matches
the class generated by `withVendorCSSClassPrefix('logo__emoji')`; use the
existing vendor configuration or `getVendorPrefix()` utility if runtime scoping
is required.

---

Nitpick comments:
In `@packages/vue/src/components/primitives/Logo/Logo.ts`:
- Around line 45-55: Remove the height and width attributes from the emoji span
created in the Logo component’s resolvedLogo glyph branch. Keep the aria-label,
class, role, and glyph content unchanged; emoji sizing should remain controlled
by the existing CSS.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: c4e3e1b3-2729-44e6-bc8d-2c9373a2a07c

📥 Commits

Reviewing files that changed from the base of the PR and between a02382f and 890810a.

⛔ Files ignored due to path filters (60)
  • packages/browser/src/utils/anonymousAnimalIcons.generated.ts is excluded by !**/*.generated.*
  • packages/browser/src/utils/anonymousEntityIcons.generated.ts is excluded by !**/*.generated.*
  • packages/browser/static/images/avatars/anonymous_animals/capybara.svg is excluded by !**/*.svg
  • packages/browser/static/images/avatars/anonymous_animals/chameleon.svg is excluded by !**/*.svg
  • packages/browser/static/images/avatars/anonymous_animals/chinchilla.svg is excluded by !**/*.svg
  • packages/browser/static/images/avatars/anonymous_animals/chipmunk.svg is excluded by !**/*.svg
  • packages/browser/static/images/avatars/anonymous_animals/chupacabra.svg is excluded by !**/*.svg
  • packages/browser/static/images/avatars/anonymous_animals/dingo.svg is excluded by !**/*.svg
  • packages/browser/static/images/avatars/anonymous_animals/dinosaur.svg is excluded by !**/*.svg
  • packages/browser/static/images/avatars/anonymous_animals/frog.svg is excluded by !**/*.svg
  • packages/browser/static/images/avatars/anonymous_animals/giraffe.svg is excluded by !**/*.svg
  • packages/browser/static/images/avatars/anonymous_animals/hedgehog.svg is excluded by !**/*.svg
  • packages/browser/static/images/avatars/anonymous_animals/hippo.svg is excluded by !**/*.svg
  • packages/browser/static/images/avatars/anonymous_animals/jackal.svg is excluded by !**/*.svg
  • packages/browser/static/images/avatars/anonymous_animals/jackalope.svg is excluded by !**/*.svg
  • packages/browser/static/images/avatars/anonymous_animals/mink.svg is excluded by !**/*.svg
  • packages/browser/static/images/avatars/anonymous_animals/otter.svg is excluded by !**/*.svg
  • packages/browser/static/images/avatars/anonymous_animals/platypus.svg is excluded by !**/*.svg
  • packages/browser/static/images/avatars/anonymous_animals/quagga.svg is excluded by !**/*.svg
  • packages/browser/static/images/avatars/anonymous_animals/raccoon.svg is excluded by !**/*.svg
  • packages/browser/static/images/avatars/anonymous_animals/skunk.svg is excluded by !**/*.svg
  • packages/browser/static/images/avatars/anonymous_entity/anchor.svg is excluded by !**/*.svg
  • packages/browser/static/images/avatars/anonymous_entity/antenna.svg is excluded by !**/*.svg
  • packages/browser/static/images/avatars/anonymous_entity/anvil.svg is excluded by !**/*.svg
  • packages/browser/static/images/avatars/anonymous_entity/arch.svg is excluded by !**/*.svg
  • packages/browser/static/images/avatars/anonymous_entity/bridge.svg is excluded by !**/*.svg
  • packages/browser/static/images/avatars/anonymous_entity/chevron.svg is excluded by !**/*.svg
  • packages/browser/static/images/avatars/anonymous_entity/circuit_node.svg is excluded by !**/*.svg
  • packages/browser/static/images/avatars/anonymous_entity/compass.svg is excluded by !**/*.svg
  • packages/browser/static/images/avatars/anonymous_entity/cube.svg is excluded by !**/*.svg
  • packages/browser/static/images/avatars/anonymous_entity/diamond.svg is excluded by !**/*.svg
  • packages/browser/static/images/avatars/anonymous_entity/dome.svg is excluded by !**/*.svg
  • packages/browser/static/images/avatars/anonymous_entity/gate.svg is excluded by !**/*.svg
  • packages/browser/static/images/avatars/anonymous_entity/hexagon.svg is excluded by !**/*.svg
  • packages/browser/static/images/avatars/anonymous_entity/key.svg is excluded by !**/*.svg
  • packages/browser/static/images/avatars/anonymous_entity/lighthouse.svg is excluded by !**/*.svg
  • packages/browser/static/images/avatars/anonymous_entity/lock.svg is excluded by !**/*.svg
  • packages/browser/static/images/avatars/anonymous_entity/obelisk.svg is excluded by !**/*.svg
  • packages/browser/static/images/avatars/anonymous_entity/octagon.svg is excluded by !**/*.svg
  • packages/browser/static/images/avatars/anonymous_entity/orbit_ring.svg is excluded by !**/*.svg
  • packages/browser/static/images/avatars/anonymous_entity/parallelogram.svg is excluded by !**/*.svg
  • packages/browser/static/images/avatars/anonymous_entity/pavilion.svg is excluded by !**/*.svg
  • packages/browser/static/images/avatars/anonymous_entity/pentagon.svg is excluded by !**/*.svg
  • packages/browser/static/images/avatars/anonymous_entity/plus_facet.svg is excluded by !**/*.svg
  • packages/browser/static/images/avatars/anonymous_entity/silo.svg is excluded by !**/*.svg
  • packages/browser/static/images/avatars/anonymous_entity/spiral.svg is excluded by !**/*.svg
  • packages/browser/static/images/avatars/anonymous_entity/spire.svg is excluded by !**/*.svg
  • packages/browser/static/images/avatars/anonymous_entity/star.svg is excluded by !**/*.svg
  • packages/browser/static/images/avatars/anonymous_entity/tower.svg is excluded by !**/*.svg
  • packages/browser/static/images/avatars/anonymous_entity/townhouse.svg is excluded by !**/*.svg
  • packages/browser/static/images/avatars/anonymous_entity/triangle_stack.svg is excluded by !**/*.svg
  • packages/browser/static/images/avatars/anonymous_entity/turbine.svg is excluded by !**/*.svg
  • packages/browser/static/images/avatars/anonymous_entity/valve.svg is excluded by !**/*.svg
  • packages/browser/static/images/avatars/anonymous_entity/windmill.svg is excluded by !**/*.svg
  • packages/react/.vitest-attachments/27677941df85b03d5d2cfa78a06ab03580dd9ac6.png is excluded by !**/*.png
  • packages/react/.vitest-attachments/3dab8c5a94eed1b7587e7dbb5fed1db2c644801d.png is excluded by !**/*.png
  • packages/react/.vitest-attachments/5a7d509e64e503baebcb9f280cb5bd323b6d722e.png is excluded by !**/*.png
  • packages/react/src/components/adapters/__tests__/__screenshots__/ImageComponent.test.tsx/ImageComponent-renders-an-emoji--spec-as-its-glyph--not-an-image-1.png is excluded by !**/*.png
  • packages/react/src/components/auth/Callback/__tests__/__screenshots__/TokenCallback.test.tsx/TokenCallback-verifies-the-token-as-a-registration-flow-when-type-REGISTRATION-is-present-1.png is excluded by !**/*.png
  • packages/react/src/components/auth/Callback/__tests__/__screenshots__/TokenCallback.test.tsx/TokenCallback-verifies-the-token-as-an-authentication-flow-when-type-AUTHENTICATION-is-present-1.png is excluded by !**/*.png
📒 Files selected for processing (26)
  • packages/browser/scripts/generate-anonymous-animal-icons.mjs
  • packages/browser/scripts/generate-anonymous-entity-icons.mjs
  • packages/browser/src/index.ts
  • packages/browser/src/utils/SPACryptoUtils.ts
  • packages/browser/src/utils/__tests__/extractAvatarParamsFromUri.test.ts
  • packages/browser/src/utils/__tests__/generateAvatarDataUri.test.ts
  • packages/browser/src/utils/__tests__/logoUriSchemes.test.ts
  • packages/browser/src/utils/__tests__/pickAnonymousAvatarName.test.ts
  • packages/browser/src/utils/__tests__/pickAnonymousEntityName.test.ts
  • packages/browser/src/utils/__tests__/resolveLogoUri.test.ts
  • packages/browser/src/utils/extractAvatarParamsFromUri.ts
  • packages/browser/src/utils/generateAvatarDataUri.ts
  • packages/browser/src/utils/isAvatarUri.ts
  • packages/browser/src/utils/pickAnonymousAvatarName.ts
  • packages/browser/src/utils/pickAnonymousEntityName.ts
  • packages/browser/src/utils/resolveLogoUri.ts
  • packages/react/src/components/adapters/ImageComponent.tsx
  • packages/react/src/components/adapters/__tests__/ImageComponent.test.tsx
  • packages/react/src/components/presentation/UserAvatar/BaseUserAvatar.tsx
  • packages/react/src/components/primitives/Avatar/Avatar.tsx
  • packages/react/src/components/primitives/Avatar/__tests__/Avatar.test.tsx
  • packages/react/src/components/primitives/Logo/Logo.styles.ts
  • packages/react/src/components/primitives/Logo/Logo.tsx
  • packages/react/src/index.ts
  • packages/vue/src/components/primitives/Logo/Logo.css.ts
  • packages/vue/src/components/primitives/Logo/Logo.ts

Comment thread packages/browser/src/utils/generateAvatarDataUri.ts Outdated
Comment thread packages/browser/src/utils/generateAvatarDataUri.ts Outdated
Comment thread packages/browser/src/utils/SPACryptoUtils.ts Outdated
Comment thread packages/react/src/components/primitives/Avatar/Avatar.tsx
Comment on lines +50 to +57

.thunderid-logo__emoji {
display: flex;
align-items: center;
justify-content: center;
line-height: 1;
font-size: 1.75em;
}

@coderabbitai coderabbitai Bot Jul 20, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Avoid hardcoded vendor prefixes in CSS class names.

This CSS block hardcodes the .thunderid- prefix (.thunderid-logo__emoji). Because the Logo component dynamically computes the class name via withVendorCSSClassPrefix('logo__emoji'), this static style rule will fail to match if a different vendor prefix is configured at runtime.

As per path instructions, ask: "Should this read from the vendor config / getVendorPrefix() util instead of hardcoding the vendor name?" Consider avoiding the vendor prefix entirely if it isn't load-bearing, or find an alternative way to ensure the styles are scoped dynamically (e.g., dynamically interpolating getVendorPrefix(vendor) into the string).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/vue/src/components/primitives/Logo/Logo.css.ts` around lines 50 -
57, Replace the hardcoded `.thunderid-logo__emoji` selector with a
prefix-independent or dynamically vendor-prefixed selector that matches the
class generated by `withVendorCSSClassPrefix('logo__emoji')`; use the existing
vendor configuration or `getVendorPrefix()` utility if runtime scoping is
required.

Source: Path instructions

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Need to fix this separately. @coderabbitai Create an issue to track

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

repo="thunder-id/javascript-sdks"
title="Fix vendor-prefixed emoji selector in Vue Logo styles"
body=$(cat <<'EOF'
## Summary
The Vue Logo emoji style selector is statically scoped as `.thunderid-logo__emoji`, while the Logo component generates the corresponding class through `withVendorCSSClassPrefix('logo__emoji')`.

## Required changes
Update the emoji styling approach so it matches the runtime-configured vendor CSS class prefix. Avoid a hardcoded vendor prefix when it is not load-bearing; otherwise derive the prefix through the existing vendor configuration/utilities.

## Rationale
Consumers using a vendor prefix other than `thunderid` will render the emoji element with a class that does not match the static CSS selector, so its layout and typography styles will not apply.

## Affected area
- `packages/vue/src/components/primitives/Logo/Logo.css.ts`
- Vue Logo class generation in `packages/vue/src/components/primitives/Logo/Logo.ts`

## Acceptance criteria
- Emoji logo styles apply when a custom vendor prefix is configured.
- The implementation follows the repository vendor-prefix guidance and does not hardcode `thunderid` for a consumer-configurable runtime class name.
- Existing default-vendor Logo styling remains intact.

## Backlinks
- Pull request: https://github.com/thunder-id/javascript-sdks/pull/34
- Review comment: https://github.com/thunder-id/javascript-sdks/pull/34#discussion_r3617377888

Requested by: `@brionmario`
EOF
)

gh issue create \
  --repo "$repo" \
  --title "$title" \
  --body "$body" \
  --assignee "brionmario"

Length of output: 1966


@brionmario, I couldn’t create the follow-up: GitHub Issues are disabled for thunder-id/javascript-sdks.

Please enable Issues for the repository or point me to the preferred external tracker, and I can create it there.

@brionmario brionmario changed the title Add support for embedded Avatars Add support for embedded Avatars with a Avatar spec Jul 20, 2026
@brionmario

Copy link
Copy Markdown
Member Author

Addressed CodeRabbit's actionable comments in dc4394b:

  • Extracted duplicated hashStr into packages/browser/src/utils/hashStr.ts, now imported by generateAvatarDataUri.ts, pickAnonymousAvatarName.ts, and pickAnonymousEntityName.ts instead of each redefining it.
  • Fixed the prototype-pollution risk in generateAvatarDataUri.ts's curated-icon lookups (ANONYMOUS_ANIMAL_ICONS[content] / ANONYMOUS_ENTITY_ICONS[content]) by checking membership with Object.prototype.hasOwnProperty.call(...) instead of a truthy index access.
  • Chunked SPACryptoUtils.generateRandomBytes so it fills the buffer in ≤65,536-byte slices, since crypto.getRandomValues() throws past that limit.
  • Wrapped the emoji glyph in Avatar.tsx in <span role="img" aria-label={alt}> so screen readers announce it correctly instead of discarding alt. Updated Avatar.test.tsx's assertion to check for the absence of an <img> element specifically, since role="img" is now intentionally present.

Skipped: the hardcoded .thunderid- prefix in packages/vue/src/components/primitives/Logo/Logo.css.ts's new .thunderid-logo__emoji rule. Every other primitive's CSS file in this package (Button.css.ts, Card.css.ts, etc.) hardcodes the same .thunderid- prefix while the component classes are generated dynamically via withVendorCSSClassPrefix() — this new rule just follows that existing, pre-existing pattern. Fixing it correctly would mean reworking the Vue package's static-CSS-string injection architecture across every primitive, which is out of scope for this PR.

All affected unit tests pass and tsc/eslint show no new issues introduced by these changes (verified against the pre-fix commit).

@brionmario
brionmario merged commit cb25b67 into thunder-id:main Jul 21, 2026
2 of 3 checks passed
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.

2 participants