Fetches and manages the conversation timeline (Note engagements and attachments) for a single ticket, powering the drawer's timeline view with optional live-polling support. ## Key Components ### Interfaces - **`TicketEngagementFile`** — Attachment metadata: `id`, `name`, `url`, `mime`, `size` - **`TicketEngagement`** — A single timeline entry with `body`, `authorRole` (`'customer' | 'support'`), resolved `authorName`/`authorEmail`/`authorAvatarUrl`, `createdAt`, and `attachments` - **`UseTicketEngagementsReturn`** — Hook return shape: `engagements`, `isLoading`, `isFetching`, `error`, `refetch` ### Hook **`useTicketEngagements(externalTicketId, enabled?)`** - Skips fetching for optimistic (`temp-*`) ticket IDs and unauthenticated (`anon`) identities - Posts to `/api/chat/agent/list-engagements` via `embedAuthedFetch` - Disables all caching (`staleTime: 0`, `gcTime: 0`) to ensure HubSpot truth on every drawer open - No interval polling — live refresh is push-driven (`TicketLiveProvider` invalidates `['ticket-engagements', id]` on stream events); focus/mount refetch is the no-stream fallback - Fixes a skeleton double-flash by treating a fetchable ticket as "loading" from the very first render, even before `useChatIdentity` resolves ## Usage Example ```typescript // Fetch when the drawer opens; live updates arrive via TicketLiveProvider const { engagements, isLoading, error } = useTicketEngagements(ticket.external_id) ``` ## Notes - Server enforces ticket ownership via `ticketBelongsToCustomer` before returning notes — customers cannot enumerate another customer's timeline by guessing ticket IDs - `authorName` for `support` engagements is resolved by matching the HubSpot owner email against the `profiles` table; returns `null` for non-Flamingo employees - For `customer` messages, `authorName`/`authorAvatarUrl` are `null` on the wire; the drawer reads live identity values for the current user's own messages