feat(messaging-ui): @mentions — autocomplete, resolve to ids, highlight
- contract: SendOpts.mentions (opaque userId notify-list) + optional listMembers(threadId) for autocomplete/highlight (capability-degrading) - mock: listMembers; KernelClientAdapter.send forwards mentions to the socket (which already carries them → inbox MENTION items) - composer: type @ → member/@channel/@here autocomplete; Enter picks the first; on send, mentions resolve to ids; message bodies highlight @mentions - useMembers hook; mentions.tsx helpers (trailingMentionQuery/insertMention/ resolveMentions/highlightMentions) - 4 mention tests (helpers + render autocomplete→send carries id); 56 total green Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -133,11 +133,11 @@ export class KernelClientAdapter implements MessagingAdapter {
|
||||
// Attachments are intentionally not forwarded here: kernel-client exposes no media/presign yet,
|
||||
// and the SDK Attachment carries a display `url`, not a storage `contentRef`. Media is a follow-up
|
||||
// (kernel-client media methods + a contentRef on Attachment). Text + reply threading work today.
|
||||
const m = await this.socket.sendMessage(
|
||||
threadId,
|
||||
content,
|
||||
opts?.parentInteractionId ? { parentInteractionId: opts.parentInteractionId } : undefined,
|
||||
);
|
||||
const sendOpts = {
|
||||
...(opts?.parentInteractionId ? { parentInteractionId: opts.parentInteractionId } : {}),
|
||||
...(opts?.mentions && opts.mentions.length ? { mentions: opts.mentions } : {}),
|
||||
};
|
||||
const m = await this.socket.sendMessage(threadId, content, Object.keys(sendOpts).length ? sendOpts : undefined);
|
||||
return this.toMessage(m);
|
||||
}
|
||||
|
||||
|
||||
@@ -150,6 +150,15 @@ export class MockAdapter implements MessagingAdapter {
|
||||
if (t) t.participants = t.participants.filter((p) => p !== ME);
|
||||
}
|
||||
|
||||
async listMembers(threadId: string): Promise<Person[]> {
|
||||
const t = this.threads.get(threadId);
|
||||
if (!t) return [];
|
||||
return t.participants.map((id) => {
|
||||
if (id === ME) return { id: ME, name: 'You', kind: 'staff' };
|
||||
return MOCK_PEOPLE.find((p) => p.id === id) ?? { id, name: id, kind: 'staff' };
|
||||
});
|
||||
}
|
||||
|
||||
async openThread(p: { participantIds: string[]; membership?: Membership; subject?: string }): Promise<{ threadId: string }> {
|
||||
const membership = p.membership ?? (p.participantIds.length === 1 ? 'dm' : 'group');
|
||||
const threadId = `th_mock_${this.seq++}`;
|
||||
|
||||
Reference in New Issue
Block a user