Three root causes fixed:
1. API GET /api/sync returned raw entities but mobile client expected
{changes: [...], sync_token} format — pullChanges() iterated
data.changes which was undefined, silently skipping all server data.
Now transforms entities into the SyncPullChange format.
2. Mobile outbox writes used snake_case keys (due_date, list_id, etc.)
but server processOperation spreads data directly into Drizzle which
expects camelCase (dueDate, listId). Fixed all outbox writes to use
camelCase. Also fixed task_tag → taskTag entity type.
3. Missing completedAt in task outbox payloads — completion state was
lost during sync. Added completedAt to both create and update outbox
entries, and added Date conversion in server update handler.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>