2026-05-31 — PRD-17 Live Message Updates

Implemented client-side live message updates without changing the relay protocol. The PyGame loop now keeps relay calls off the render/input thread by using background worker threads and a main-thread queue. Active chats poll for recent messages, merge by BlueBubbles GUID, auto-scroll only when already bottom-anchored, and preserve scroll position when the user is reading history.

Key UX decisions: - If the user is at rest at the bottom, including while OSK typing, new incoming messages take the bottom position automatically. - If the user is scrolled up, the selected message remains anchored and a small new-message pill appears; Select jumps to the newest message. - Sends are optimistic: outgoing bubbles appear immediately, then become confirmed or failed after the relay responds. - Poll failures stay silent and only surface as a subtle offline dot after repeated failures. - Non-active chat updates persist unread metadata in config.json.

Added focused fake-PyGame unittest coverage for duplicate polls, appends, middle inserts, bottom auto-scroll, OSK-open bottom auto-scroll, scrolled-up anchor preservation, optimistic send confirmation/failure, cache truncation, and unread metadata.

Deployment record: - Implementation commit pushed to Forgejo as f6daed1. - Pi4 workspace clone created/updated at /mnt/kitchen/from-house/workspace/aquaping. - RG40XXV runtime updated at /userdata/roms/ports/AQUAPING/client.py. - Previous RG runtime backed up at /userdata/roms/ports/AQUAPING/client.py.bak.20260531-prd17. - Deployed RG client SHA-256: 5a5080a35626b690117f6ae5c9faba27c9ddde48e09389a7102d38e5eca0d4c4.