# Expo Chat: Message Avatars Design

**Date:** 2026-04-19
**Scope:** Render participant avatars in the Expo chat screen for both incoming and outgoing message groups by using existing member profile data.

## Context

The Expo chat screen at `xtablo-expo/app/(app)/channel/[cid].tsx` already:
- groups messages by sender and time
- resolves sender names from `useTabloMembers(channelId)`
- renders a circular initial badge for other participants
- does not render remote avatars
- does not show an avatar on outgoing message groups

Channel membership data already includes `avatar_url`, so the screen has the information needed to attempt avatar loading without changing the chat transport.

## Goal

Show a profile avatar for each message group in the channel timeline:
- for other participants
- for the current user
- with an initial-based fallback when no avatar is available or loading fails

The change should stay within the Expo app and reuse existing data sources.

## Non-Goals

- changing the chat REST or websocket payloads
- adding avatar upload or editing flows
- changing message grouping rules
- redesigning the bubble layout beyond what is required to display avatars

## Architecture

The existing `members` query remains the primary source of sender display information.

### Sender Display Resolution

The screen should resolve sender display metadata from:
1. `userMap[userId]` from `useTabloMembers(channelId)`
2. the authenticated Expo user as a fallback for the current user
3. hard fallbacks for missing name/avatar values

Suggested resolved display shape:

```ts
type MessageAuthorDisplay = {
  name: string;
  avatarUrl: string | null;
  initial: string;
};
```

This resolution logic should live in a small pure helper so it can be tested independently of the React Native screen.

### Avatar Fallback Rule

Avatar rendering should follow this order:
1. render remote image when `avatarUrl` is present and has not failed for that user
2. fall back to the existing initial badge when the URL is missing
3. fall back to the existing initial badge after `Image` load failure for that user

The failure state should be tracked locally in the screen so repeated groups from the same sender do not keep retrying a known-bad URL during the same render session.

## Components

### New helper

Add a pure helper in `xtablo-expo/features/chat/` that:
- resolves sender name
- resolves sender avatar URL
- derives the fallback initial

### Channel screen changes

Update `xtablo-expo/app/(app)/channel/[cid].tsx` to:
- render an avatar for both incoming and outgoing message groups
- use `Image` for remote avatars
- fall back to the existing colored initial badge
- track image failures per user id

The rest of the screen remains unchanged:
- message fetching
- message grouping
- pagination
- typing indicator
- input behavior

## Data Flow

The screen should process author display data in this order:

1. read `members` from `useTabloMembers(channelId)`
2. build `userMap` keyed by member id
3. resolve each message group's author display data from the helper
4. render the avatar image when possible
5. fall back to initials when the avatar is unavailable or failed

## Visual Treatment

The avatar should reuse the current compact chat scale:
- circular
- 24x24
- aligned with the sender row

For outgoing groups, the sender row should remain right-aligned while still showing the avatar.

## Error Handling And Verification

Verification should focus on avatar-source correctness and renderer stability:

- helper tests cover member lookup, current-user fallback, and failure fallback
- `npx tsc --noEmit` in `xtablo-expo` still passes
- Expo lint does not report new errors from the chat screen change
- manual UI check confirms:
  - avatars appear for both sides when URLs exist
  - initials appear when URLs are missing
  - initials replace avatars after image load failure
  - message alignment and input behavior remain unchanged

## Success Criteria

The feature is complete when:
- both incoming and outgoing message groups attempt to render avatars
- avatar URLs are sourced from existing member/profile data
- broken or missing avatar URLs fall back cleanly to initials
- no backend or transport changes are required
