mirror of
https://github.com/duckietm/Nitro-V3.git
synced 2026-06-19 15:06:20 +00:00
docs(messenger): design spec for groups, offline messages, read receipts, typing
Brainstormed design for extending the existing React friends/messenger: - full custom friend groups (CRUD + assign) with Online/Offline primary view + group chip filter - offline messages stored in messenger_offline, replayed on login (no new packets) - 2-state read receipts (sent/read) via per-conversation last-read timestamp - typing indicator (ephemeral packets) Cross-component: Nitro-V3 client + Nitro_Render_V3 renderer + Arcturus emulator. Reuse official Habbo header IDs for category packets; custom IDs for receipts/typing.
This commit is contained in:
@@ -0,0 +1,127 @@
|
||||
# Messenger upgrade — Friend groups, offline messages, read receipts, typing
|
||||
|
||||
**Date:** 2026-06-02
|
||||
**Status:** Approved design (brainstorming) — pending implementation plan
|
||||
**Scope:** Cross-component (Nitro-V3 client + Nitro_Render_V3 renderer + Arcturus emulator + DB). CMS untouched.
|
||||
|
||||
## Goal
|
||||
|
||||
Extend the existing (already-React) friends list & instant messenger with four features:
|
||||
|
||||
1. **Friend groups** — full custom: create / rename / delete named groups and assign friends to them.
|
||||
2. **Offline messages** — messages to an offline friend are stored and delivered on their next login, tagged "sent while offline".
|
||||
3. **Read receipts** — 2-state, WhatsApp-style: `✓` sent, `✓✓` read.
|
||||
4. **Typing indicator** — "X is typing…" inside a conversation thread.
|
||||
|
||||
**No rewrite.** We build on the existing React components, hooks, emulator messenger classes, and renderer protocol. We reuse existing structures wherever they exist and add packets only where unavoidable.
|
||||
|
||||
## Non-goals (explicitly out of scope this round)
|
||||
|
||||
- Read-receipt privacy toggle (receipts always on).
|
||||
- "Last seen / online status" text and in-conversation message search.
|
||||
- 3-state receipts (no separate "delivered" step).
|
||||
- Per-message IDs (we use a last-read-timestamp model instead — see §Read receipts).
|
||||
- Any CMS / Prisma change.
|
||||
- Group chats, bots, and StaffChat are excluded from receipts and typing.
|
||||
|
||||
## Current state (verified)
|
||||
|
||||
- **Client (Nitro-V3):** friends + messenger are already React/TSX under `src/components/friends/**`, driven by `useFriends` / `useFriendsState` / `useFriendsActions` / `useMessenger`. `MessengerFriend.categoryId` and `MessengerSettings.categories` exist in the data model but there is **no group UI**. No receipts, no typing, no offline UI.
|
||||
- **Renderer (Nitro_Render_V3):** `MessengerInitParser` exposes `categories: FriendCategoryData[]`; `FriendParser` carries `categoryId`. `NewConsoleMessageParser` exposes `senderId, messageText, secondsSinceSent, extraData`. **No** category-management composers, **no** receipt/typing/messageId for the messenger. Typing exists only for *room* chat.
|
||||
- **Emulator (Arcturus):** `Messenger`, `MessengerBuddy`, `Message`, `MessengerCategory` exist. `MessengerInitComposer` sends categories; `FriendsComposer` serializes `categoryId`. **No** category create/rename/delete/assign handlers and **no DB setter** for category. Instant messages are fire-and-forget (delivered only if recipient online, else dropped). `messenger_offline` table exists but is **never read/written**. No receipts, no messenger typing.
|
||||
- **Build integration:** `Nitro-V3/vite.config.mjs` aliases `@nitrots/nitro-renderer` directly to local `../Nitro_Render_V3/index.ts` **source**. New renderer code is picked up live by the client dev server — no separate renderer build/publish step required.
|
||||
|
||||
## Protocol strategy
|
||||
|
||||
- **Friend-category packets:** reuse the **official Habbo header IDs** for the revision the client connects with, where the official client shipped that op. If an op never existed officially, use a free custom ID. *(Planning task: confirm the connecting revision and pull the official IDs; fall back to custom per-op.)*
|
||||
- **Read receipts & typing:** never existed in the official messenger → **custom** header IDs.
|
||||
- **Offline messages:** **no new packets** — replayed through the existing `FriendChatMessageComposer`.
|
||||
- Header IDs are a contract: every new packet needs a constant in Arcturus `Incoming.java`/`Outgoing.java` **and** an identical-ID parser/event or composer in the renderer. The spec's §"Packet table" is the single source of truth; keep both sides in lockstep.
|
||||
|
||||
## Data model (owned by Arcturus; Prisma/CMS untouched)
|
||||
|
||||
| Table | State | Change |
|
||||
|---|---|---|
|
||||
| `messenger_categories(id, user_id, name)` | exists, unwritten | Add create/rename/delete persistence. Cap **20 groups/user**, name ≤ 25 chars (column limit). |
|
||||
| `messenger_friendships.category` | exists, no setter | Add setter + `UPDATE` to assign a friend to a group. Deleting a group resets members to `0`. |
|
||||
| `messenger_offline(id, user_id, user_from_id, message, sended_on)` | exists, unused | `INSERT` on send-to-offline; `SELECT`+`DELETE` on recipient login. Cap per-user inbox (default **200**, configurable). |
|
||||
| `messenger_read_state(reader_id, peer_id, last_read)` PK(reader_id, peer_id) | **new** | Drives read receipts via last-read timestamp per conversation. |
|
||||
|
||||
## Feature designs
|
||||
|
||||
### 1. Friend groups (CRUD + assign)
|
||||
|
||||
**Server (Arcturus):**
|
||||
- New incoming handlers in `messages/incoming/friends/`, registered in `PacketManager.registerFriends()`:
|
||||
`AddFriendCategoryEvent(name)`, `RenameFriendCategoryEvent(id, name)`, `RemoveFriendCategoryEvent(id)`, `MoveFriendToCategoryEvent(friendId, categoryId)`.
|
||||
- Persistence added to `Messenger` / `MessengerCategory`; add `MessengerBuddy.setCategoryId()` + DB `UPDATE`.
|
||||
- Responses reuse existing composers: `MessengerInitComposer` (refreshed categories list) and `UpdateFriendComposer` (moved friend's new `categoryId`).
|
||||
- Limits enforced server-side (≤20 groups, name length, dedupe). Delete → members → category `0`.
|
||||
|
||||
**Renderer (Nitro_Render_V3):** new outgoing composers `AddFriendCategoryComposer`, `RenameFriendCategoryComposer`, `RemoveFriendCategoryComposer`, `MoveFriendToCategoryComposer` with the official/fallback header IDs. (Categories arrive via existing `MessengerInitParser`; add a small `FriendCategoriesEvent` only if a standalone refresh is needed.)
|
||||
|
||||
**Client (Nitro-V3):**
|
||||
- `useFriendsState` exposes `categories`; `useFriendsActions` adds `addCategory / renameCategory / removeCategory / moveFriendToCategory` wired to the composers.
|
||||
- **Layout decision:** Online/Offline remains the **primary** view. A **chip-filter row** at the top of `FriendsListView` (one chip per group, like the navigator filter chips) filters the list to a single group. Groups *filter*, they do not restructure the Online/Offline sections.
|
||||
- **Group management UI:** an "manage groups" affordance in `FriendsListView` (add / rename / delete) and a per-friend assignment control (dropdown / context action) in `FriendsListGroupItemView`.
|
||||
|
||||
### 2. Offline messages
|
||||
|
||||
**Server:** in `FriendPrivateMessageEvent`, if the recipient is offline → `INSERT` into `messenger_offline` (respect inbox cap; drop oldest when full). On recipient login, after the friend list is sent (`RequestInitFriendsEvent`), replay each stored row as `FriendChatMessageComposer(fromId, message, secondsSinceSent = now - sended_on, extraData = "offline")`, then `DELETE` the delivered rows.
|
||||
|
||||
**Renderer:** no change — `NewConsoleMessageParser` already exposes `extraData`.
|
||||
|
||||
**Client:** when `extraData === "offline"`, tag the message in the thread with a subtle "📨 inviato mentre eri offline" marker (`MessengerThreadChat.offlineDelivered = true`). Sender side: the message already shows `✓` (it left the client and was stored); it flips to `✓✓` when the recipient reads it after login (via the read-receipt catch-up batch, §3).
|
||||
|
||||
### 3. Read receipts (2-state ✓ / ✓✓)
|
||||
|
||||
**Model:** per-conversation **last-read timestamp** (no per-message IDs). `✓✓` applies to every own message in the thread with `date ≤ T`.
|
||||
|
||||
**Packets (custom):**
|
||||
- Incoming `MarkConsoleRead(peerId)` — "I've read everything from `peerId` up to now."
|
||||
- Outgoing `ConsoleReadReceipt(readerId, timestamp)` — "`readerId` has read up to `timestamp`."
|
||||
|
||||
**Server:** on `MarkConsoleRead` → upsert `read_state(me, peer, now)`; if peer online, send them `ConsoleReadReceipt(myId, now)`. On login, send a batch of `ConsoleReadReceipt` (one per conversation with a stored read_state) so an offline-sender catches up. 1:1 only.
|
||||
|
||||
**Client:**
|
||||
- `MessengerThreadChat` gains `status: 'SENT' | 'READ'`.
|
||||
- On send → `SENT` (`✓`). On thread focus/open → send `MarkConsoleRead(peerId)`.
|
||||
- On `ConsoleReadReceipt(readerId, T)` → mark all own messages to `readerId` with `date ≤ T` as `READ` (`✓✓`).
|
||||
- Render `✓` / `✓✓` on own messages in `FriendsMessengerThreadGroup`.
|
||||
|
||||
### 4. Typing indicator
|
||||
|
||||
**Packets (custom, ephemeral, never stored):**
|
||||
- Incoming `ConsoleTyping(peerId, isTyping)`.
|
||||
- Outgoing `FriendTyping(senderId, isTyping)`.
|
||||
|
||||
**Server:** relay to peer if online; light cooldown to prevent flooding. 1:1 only.
|
||||
|
||||
**Client:** debounce the message input → `ConsoleTyping(start)` while typing, `ConsoleTyping(stop)` on idle/blur/send. On `FriendTyping(isTyping)` show a "X sta scrivendo…" row in the thread with an auto-timeout fallback.
|
||||
|
||||
## Edge cases
|
||||
|
||||
- Group deleted → members fall back to uncategorized (`0`).
|
||||
- Offline inbox full → drop oldest (configurable; alternative reject documented).
|
||||
- Typing & receipts: 1:1 only — never StaffChat, group chat, or bots.
|
||||
- Receipts always on (no privacy toggle this round).
|
||||
- Renderer and emulator header IDs must stay in lockstep (this spec is the source of truth).
|
||||
- Self-messages / messages to non-friends rejected as today.
|
||||
|
||||
## Testing
|
||||
|
||||
- **Renderer (Vitest, currently 138):** one test per new parser/composer — header init + field read/write order.
|
||||
- **Client (Vitest, currently 214):** `useMessenger` status transitions (SENT→READ, offline tag), category actions/reducer, rendering of `✓✓` / typing row / offline marker.
|
||||
- **Emulator:** manual two-session integration — offline send→login replay, receipt round-trip (online + offline catch-up), typing relay, group CRUD + assignment + delete-fallback. (Arcturus has limited unit-test infra.)
|
||||
|
||||
## Build sequencing (one spec, phased plan — each phase independently shippable)
|
||||
|
||||
1. **Friend groups** — most self-contained (CRUD + chip-filter UI + assignment).
|
||||
2. **Offline messages** — server + DB, no new packets, small client marker.
|
||||
3. **Read receipts** — packets across all three components + new table.
|
||||
4. **Typing indicator** — packets, smallest.
|
||||
|
||||
## Open items for planning
|
||||
|
||||
- Confirm the client's connecting revision and source the official friend-category header IDs (custom fallback per-op).
|
||||
- Decide the feature branch base in each repo (current branches are mid-`mentions-system` work — do **not** build on top of those).
|
||||
Reference in New Issue
Block a user