Files
openclaw/ui
Peter Steinberger f8e35afecc feat: avatar upload UI, self-service profiles, server-side avatar route (#111421)
* feat: avatar upload UI, self-service profiles, server-side avatar proxy

* refactor(ui): make resolveAvatar synchronous so avatar updates render immediately

resolveAvatar no longer does async work (gravatar moved server-side), so
the until()-deferred render left the identity chip img stale after
updateSelfUser. Rendering synchronously reuses the img element and
reflects avatarUrl changes on the next render.

* fix(gateway): bound avatar Gravatar lookup to one timeout budget

Linked-email Gravatar lookups ran sequentially, each with its own 5s
timeout, so an upstream outage stalled the held connection for
5s x linked-email-count. Resolve them concurrently and pick the first
hit in email order, keeping primary-email precedence while capping the
request to a single timeout budget.

* fix(gateway): cap per-profile Gravatar fan-out to bound sockets and memory

A profile with many linked emails would fan out one concurrent fetch per
email with no bound, each able to buffer up to MAX_GRAVATAR_BYTES before
the cache budget applies. Cap the lookups to the first 8 primary-ordered
emails so a single avatar request holds a bounded number of sockets and
transient bytes.

* test: reconcile avatar tests with server-side gravatar model after merge

main landed a client-side Gravatar path (browser computes the gravatar.com
URL); this branch resolves avatars through the same-origin gateway route,
which is the only approach that works under the Control UI CSP (img-src
'self'). Update the identity-section, app-sidebar footer chip, and
profile-page e2e expectations to assert the canonical /api/users/<id>/avatar
route (gateway serves the Gravatar fallback behind it) instead of a
CSP-blocked direct gravatar.com image.

* test(gateway): fix fetchImpl mock param type for check-test-types

The Gravatar mock typed its param as (url: string), narrower than the
fetchImpl signature (URL | RequestInfo); function-param contravariance
made it unassignable under tsgo:test. Widen to the fetch input type and
extract the URL via a small helper that avoids no-base-to-string on a
Request.

* fix(gateway): short-circuit Gravatar lookups and version presence avatar URL

Two autoreview findings on the avatar path:

- Privacy: the concurrent Promise.all fan-out queried every linked email's
  hash against Gravatar even when the primary already had one, exposing
  secondary work/personal addresses. Resolve sequentially with short-circuit
  so a later hash is disclosed only after the earlier email is a definite
  miss, under one shared request deadline that still bounds total latency.

- Staleness: presence published an unversioned /api/users/<id>/avatar, so a
  reconnecting viewer's <img> kept the stale cached image after an upload.
  Carry the profile revision as ?v=<updatedAt> so a changed avatar refetches.
2026-07-19 08:52:01 -07:00
..