Android CI build + delete-chat (self-only) + SFU copy refresh

- codemagic.yaml: add an Android workflow that builds an installable debug
  APK (Linux instance, no signing needed to test) — the Android equivalent of
  the iOS TestFlight loop. mobile/scripts/android-patch.js injects the
  camera/mic/notification permissions into the CI-generated manifest (android/
  is gitignored, same as ios/).
- Delete chat (self-only): new POST /api/messages/clear bulk-hides a whole DM
  (or group) for the requester via the existing per-user message_hidden
  mechanism — the other party keeps their copy entirely. "Delete chat" button
  added to the DM contact-info panel; chat-cleared synced to my other devices.
  Verified end-to-end on Postgres (A clears -> empty for A, B untouched).
- Meetings copy: SFU is live, so drop the "coming soon" / "small group (mesh)"
  wording on the welcome card and the Meetings header.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-08-22 17:12:42 +05:30
parent 948eae249f
commit 07c728bf6a
5 changed files with 177 additions and 3 deletions
+19
View File
@@ -1768,6 +1768,25 @@ route('POST', '/api/messages/hide', async (req, res) => {
try { CHAT.pushToUser(u.id, { type: 'chat-hidden', id, conversation_id: m.conversation_id || null }); } catch (_) {} // my other devices
json(res, 200, { ok: true });
});
// "Delete chat" — SELF-ONLY. Hides the whole conversation from MY view (synced to my other devices); the
// other party keeps their copy entirely. DM via {with:peerId}; group via {group:conversationId} (I stay a
// member — new messages will start a fresh thread). Reuses the per-user "deleted for me" hide mechanism.
route('POST', '/api/messages/clear', async (req, res) => {
const u = await currentUser(req);
if (!u) return json(res, 401, { error: 'unauthorized' });
const { with: withRaw, group } = await readBody(req);
if (group) {
if (!await R.conversations.isMember(group, u.id)) return json(res, 403, { error: 'not a member of this group' });
await R.messages.hideConversationForUser(group, u.id);
try { CHAT.pushToUser(u.id, { type: 'chat-cleared', kind: 'group', id: group }); } catch (_) {} // my other devices
return json(res, 200, { ok: true });
}
const peer = await R.users.resolve(withRaw);
if (!peer) return json(res, 400, { error: 'with or group required' });
await R.messages.hideThreadForUser(u.team_id, u.id, peer);
try { CHAT.pushToUser(u.id, { type: 'chat-cleared', kind: 'dm', id: peer }); } catch (_) {} // my other devices
json(res, 200, { ok: true });
});
// #13 Pin / unpin a message for the whole conversation (any participant may pin/unpin). Broadcast so every
// participant's pinned strip updates live.
route('POST', '/api/messages/pin', async (req, res) => {