# Participant Colors Report

## 1. Color strategy

Ticket conversation participants receive a stable visual identity through a small, curated palette rather than per-render random colors. The requester always uses a fixed neutral tone; every other participant (assignee, collaborator, support agent) receives a deterministic color derived from their user id.

Color is applied sparingly as identity:

- Sender name text
- Avatar ring (when a photo is present) or avatar background (initials fallback)
- Role badge accent (background, border, text)
- Subtle inbound bubble tint for team messages (not requester, not outbound)

The conversation layout, bubble tails, timestamps, and outbound primary styling are unchanged.

## 2. Requester fixed color

The requester uses a dedicated `requester` token independent of user id hashing:

| Token | Light | Dark |
| --- | --- | --- |
| text | `#475569` | `#94A3B8` |
| background | `#F1F5F9` | `#1E293B` |
| border | `#CBD5E1` | `#334155` |
| avatarBackground | `#64748B` | `#475569` |
| avatarText | `#FFFFFF` | `#F1F5F9` |

Requester inbound bubbles keep the default surface styling (no team accent stripe).

## 3. Deterministic hash method

`hashStringToIndex(value, paletteLength)` applies a 32-bit rolling hash (similar to djb2) over the trimmed user id string and returns `Math.abs(hash) % paletteLength`.

Properties:

- Same user id → same palette index → same color on every render and refresh
- Different user ids distribute across the 12-tone palette
- Empty or whitespace ids fall back to palette index 0 with key `fallback`

`getParticipantColor(userId, role?, mode?)` returns `getRequesterColor()` when `role === 'requester'`, otherwise selects from the mode-specific palette.

`isRequesterParticipant(participant, ticket)` checks `participant.id` or `participant.user_id` against `ticket.requester.id`, with a `role === 'requester'` fallback when id is unavailable.

## 4. Files changed

| File | Change |
| --- | --- |
| `resources/js/src/features/tickets/utils/participantColors.ts` | New utility, palettes, hash, helpers |
| `resources/js/src/shared/components/ui/UserAvatar/UserAvatar.tsx` | Optional ring/background/text color props |
| `resources/js/src/features/tickets/components/RepliesThread.tsx` | Participant colors on names, avatars, badges, team bubbles |
| `resources/js/src/features/tickets/components/CollaboratorsSection.tsx` | Matching avatar/name colors in sidebar |
| `resources/js/src/features/tickets/components/NotesSection.tsx` | Author avatar/name colors for internal notes |
| `resources/js/src/__tests__/unit/features/participantColors.test.ts` | Unit tests for utility |
| `resources/js/src/__tests__/unit/features/RepliesThread.test.tsx` | Rendering tests for conversation colors |

## 5. Tests added

**Utility (`participantColors.test.ts`)**

- Same user id always returns the same color
- Different user ids distribute across the palette
- Requester role always returns fixed requester color
- Missing/empty user id uses safe fallback
- Repeated calls do not randomize colors
- `isRequesterParticipant` id/role detection

**Rendering (`RepliesThread.test.tsx`)**

- Requester name uses fixed requester text color
- Two agents receive distinct deterministic name colors
- Avatar backgrounds match participant tokens for initials

## 6. Dark mode notes

Light and dark each have a full 12-tone palette with adjusted text, background, border, and avatar tokens. Components read `theme.mode` from the styled-components theme object (`lightTheme.mode` / `darkTheme.mode`), so colors adapt automatically when the app theme toggles.

Dark palette uses lighter text on deep tinted backgrounds to preserve readability.

## 7. Accessibility notes

- Color supplements — never replaces — name text, role labels, and avatar initials
- Role badges remain text-based (`Requester`, `Agent`, etc.)
- Avatar initials use high-contrast `avatarText` on `avatarBackground`
- Inbound team bubble accent uses `border-inline-start` for RTL/LTR correctness
- Outbound (current user) messages keep existing primary contrast

## 8. Remaining gaps

- Avatar image URLs are not yet returned on reply/note user objects; ring colors apply when images are available via collaborator sidebar avatars
- Activity timeline and ticket hero requester/assignee avatars still use default gradient (outside conversation scope)
- Palette is duplicated in TS rather than theme tokens; could move to `theme.colors.participants` in a future refactor
- No automated contrast-ratio audit in CI (manual palette design targets WCAG-friendly pairs)
