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
+18
View File
@@ -213,6 +213,24 @@ const messages = {
markDeleted: (id) => db.prepare("UPDATE messages SET deleted=1, body='', attachment_id=NULL, poll_id=NULL WHERE id=?").run(id),
// #18 Delete-for-me: hide a message from ONE user's view (the row + everyone else are untouched).
hideForUser: (messageId, userId) => db.prepare('INSERT INTO message_hidden (message_id,user_id,hidden_at) VALUES (?,?,?) ON CONFLICT(message_id,user_id) DO NOTHING').run(messageId, userId, now()),
// "Delete chat" (self-only): hide the ENTIRE DM thread with one peer from a single user's view. Bulk-inserts
// message_hidden rows for every message of the pair, reusing the exact filters the thread + conversation list
// already apply (they skip message_hidden), so the chat disappears for this user while the peer keeps their
// copy. New messages afterwards start a fresh thread (they aren't hidden) — WhatsApp-style.
hideThreadForUser: (teamId, userId, peerId) => db.prepare(
`INSERT INTO message_hidden (message_id,user_id,hidden_at)
SELECT id, ?, ? FROM messages
WHERE team_id=? AND conversation_id IS NULL
AND ((sender_id=? AND recipient_id=?) OR (sender_id=? AND recipient_id=?))
ON CONFLICT (message_id,user_id) DO NOTHING`
).run(userId, now(), teamId, userId, peerId, peerId, userId),
// "Delete chat" (self-only) for a GROUP conversation: hide every message from one member's view (they stay a
// member; new messages arrive as a fresh thread). The peer/other members are untouched.
hideConversationForUser: (conversationId, userId) => db.prepare(
`INSERT INTO message_hidden (message_id,user_id,hidden_at)
SELECT id, ?, ? FROM messages WHERE conversation_id=?
ON CONFLICT (message_id,user_id) DO NOTHING`
).run(userId, now(), conversationId),
hiddenForUser: async (userId) => (await db.prepare('SELECT message_id FROM message_hidden WHERE user_id=?').all(userId)).map((r) => r.message_id),
// Last message in a group that THIS user hasn't hidden (so a "delete for me" on the last message
// rolls the sidebar preview back to the previous one, instead of showing what they just removed).