From 62b79c7e7de66e11e6cca49e86f46ad468157506 Mon Sep 17 00:00:00 2001 From: riyadhafraa <49757212-riyadhafraa@users.noreply.replit.com> Date: Tue, 21 Apr 2026 10:20:22 +0000 Subject: [PATCH] Task #42: Tell members when someone leaves a group on their own Original task: Post a bilingual system message into the group chat when a member leaves via POST /api/conversations/:id/leave, with real-time delivery via Socket.IO and rendering in the chat thread. Findings: - The leave handler in artifacts/api-server/src/routes/conversations.ts already inserts a `member_left` system message (and `admin_promoted` when a successor is auto-promoted) and emits via Socket.IO using insertAndEmitSystemMessage. Direct conversations and last-leaver cleanup paths are correctly skipped. - artifacts/teaboy-os/src/pages/chat.tsx renderSystemMessage already handles `member_left`, `admin_promoted`, and `adminPromotedAfterLeave`. - Bilingual locale keys exist in src/locales/en.json and ar.json under chat.system.memberLeft / adminPromoted / adminPromotedAfterLeave. Change made: - Extended the MessageWithSender.kind enum in lib/api-spec/openapi.yaml to include `member_left` and `admin_promoted` (previously only user/group_renamed/members_added/member_removed were declared). - Re-ran `pnpm --filter @workspace/api-spec run codegen` to regenerate api-zod and api-client-react schemas so the spec, runtime validation, and client types stay in sync. - Verified `pnpm -w run typecheck` passes across all packages. No behavioural code changes were necessary; the feature was already implemented end-to-end and only the API spec enum needed to catch up. Replit-Task-Id: 40fccb3a-4fc0-4ad7-a6e9-d1ac67ef7769 --- .../src/generated/api.schemas.ts | 2 ++ lib/api-spec/openapi.yaml | 2 +- lib/api-zod/src/generated/api.ts | 21 ++++++++++++++++++- 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/lib/api-client-react/src/generated/api.schemas.ts b/lib/api-client-react/src/generated/api.schemas.ts index a1e887fa..0f4a6589 100644 --- a/lib/api-client-react/src/generated/api.schemas.ts +++ b/lib/api-client-react/src/generated/api.schemas.ts @@ -269,6 +269,8 @@ export const MessageWithSenderKind = { group_renamed: "group_renamed", members_added: "members_added", member_removed: "member_removed", + member_left: "member_left", + admin_promoted: "admin_promoted", } as const; export type MessageWithSenderMeta = { [key: string]: unknown } | null; diff --git a/lib/api-spec/openapi.yaml b/lib/api-spec/openapi.yaml index b130292b..a7925fe3 100644 --- a/lib/api-spec/openapi.yaml +++ b/lib/api-spec/openapi.yaml @@ -1708,7 +1708,7 @@ components: type: string kind: type: string - enum: [user, group_renamed, members_added, member_removed] + enum: [user, group_renamed, members_added, member_removed, member_left, admin_promoted] meta: type: object nullable: true diff --git a/lib/api-zod/src/generated/api.ts b/lib/api-zod/src/generated/api.ts index 9da58854..9dd5c1a3 100644 --- a/lib/api-zod/src/generated/api.ts +++ b/lib/api-zod/src/generated/api.ts @@ -596,6 +596,8 @@ export const ListConversationsResponseItem = zod.object({ "group_renamed", "members_added", "member_removed", + "member_left", + "admin_promoted", ]), meta: zod.record(zod.string(), zod.unknown()).nullish(), sender: zod.object({ @@ -666,6 +668,8 @@ export const GetConversationResponse = zod.object({ "group_renamed", "members_added", "member_removed", + "member_left", + "admin_promoted", ]), meta: zod.record(zod.string(), zod.unknown()).nullish(), sender: zod.object({ @@ -728,6 +732,8 @@ export const UpdateConversationResponse = zod.object({ "group_renamed", "members_added", "member_removed", + "member_left", + "admin_promoted", ]), meta: zod.record(zod.string(), zod.unknown()).nullish(), sender: zod.object({ @@ -788,6 +794,8 @@ export const AddConversationParticipantsResponse = zod.object({ "group_renamed", "members_added", "member_removed", + "member_left", + "admin_promoted", ]), meta: zod.record(zod.string(), zod.unknown()).nullish(), sender: zod.object({ @@ -845,6 +853,8 @@ export const RemoveConversationParticipantResponse = zod.object({ "group_renamed", "members_added", "member_removed", + "member_left", + "admin_promoted", ]), meta: zod.record(zod.string(), zod.unknown()).nullish(), sender: zod.object({ @@ -906,6 +916,8 @@ export const UpdateConversationStateResponse = zod.object({ "group_renamed", "members_added", "member_removed", + "member_left", + "admin_promoted", ]), meta: zod.record(zod.string(), zod.unknown()).nullish(), sender: zod.object({ @@ -950,7 +962,14 @@ export const ListMessagesResponseItem = zod.object({ conversationId: zod.number(), senderId: zod.number(), content: zod.string(), - kind: zod.enum(["user", "group_renamed", "members_added", "member_removed"]), + kind: zod.enum([ + "user", + "group_renamed", + "members_added", + "member_removed", + "member_left", + "admin_promoted", + ]), meta: zod.record(zod.string(), zod.unknown()).nullish(), sender: zod.object({ id: zod.number(),