feat(messaging-ui): message timestamps + group/channel settings panel (0.1.5)

- Every message now shows a Slack-style time (clock today, then 'Yesterday',
  weekday, else a date; full timestamp on hover).
- New ConversationSettings panel for groups AND channels (public + private):
  rename, member list, add people (from the directory), remove, and leave.
  Opened from a new thread header gear; DMs show no gear. Adapter gains
  addMember/removeMember/renameConversation (optional, OPA still gates writes).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-23 12:54:47 +05:30
parent aa73dee05d
commit 416cf59dc2
9 changed files with 350 additions and 3 deletions
@@ -163,6 +163,21 @@ export class MockAdapter implements MessagingAdapter {
return MOCK_PEOPLE.map((p) => ({ ...p }));
}
async addMember(threadId: string, userId: string): Promise<void> {
const t = this.threads.get(threadId);
if (t && !t.participants.includes(userId)) t.participants = [...t.participants, userId];
}
async removeMember(threadId: string, userId: string): Promise<void> {
const t = this.threads.get(threadId);
if (t) t.participants = t.participants.filter((p) => p !== userId);
}
async renameConversation(threadId: string, subject: string): Promise<void> {
const t = this.threads.get(threadId);
if (t) t.subject = subject;
}
async openThread(p: { participantIds: string[]; membership?: Membership; subject?: string }): Promise<{ threadId: string }> {
// A DM to someone you already have reuses the existing 1:1 thread (dedupe, like the live door).
if ((p.membership ?? (p.participantIds.length === 1 ? 'dm' : 'group')) === 'dm' && p.participantIds.length === 1) {