Load messages

RG client
  → TCP: {"action":"get_messages","chat_guid":"...","limit":50,"describe_images":true}
  → Pi4 front-door relay, then Mac fallback relay if needed
  → GET /api/v1/chat/{guid}/message (BlueBubbles)
  → Returns text messages plus attachment pseudo-messages
  → Client renders text, generic attachment, and image-description bubbles

Image description flow

BlueBubbles message attachments
  → relay detects image/* attachments
  → relay returns "[Describing image...]" pseudo-message immediately
  → relay background worker retrieves and normalizes image bytes
  → mmx vision describe runs on the Mac relay host
  → description is cached at ~/.cache/aquaping/image_descriptions.json
  → next client poll receives same pseudo-message GUID with updated text

Send message

RG client
  → TCP: {"action":"send","chat_guid":"...","text":"hello"}
  → Pi4 front-door relay, then Mac fallback relay if needed
  → POST /api/v1/message/text (BlueBubbles)
  → Client waits for response, then re-fetches messages