feat(mesh-ui): reply banner + inline reaction chips (Phase 2a)
Tap a bubble to open an action menu with Reply + 6 quick reactions. Reply stashes the target MessageKey and flips the Send button to "Reply" mode, routing through mesh.send-reply. Reactions call mesh.send-reaction immediately and render as chips under the target bubble, collapsed per emoji with a count and self-highlight. Reaction messages are filtered out of the main chat stream so they don't create standalone bubbles. Reply bubbles show a "↳ quoted snippet" header when the target is still in the local window. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -48,6 +48,8 @@ export type MeshMessageTypeLabel =
|
||||
| 'lightning_relay'
|
||||
| 'lightning_relay_response'
|
||||
| 'content_ref'
|
||||
| 'reply'
|
||||
| 'reaction'
|
||||
|
||||
export interface MeshMessage {
|
||||
id: number
|
||||
@@ -61,6 +63,10 @@ export interface MeshMessage {
|
||||
message_type?: MeshMessageTypeLabel
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
typed_payload?: Record<string, any> | null
|
||||
/// Cross-transport identity for this message — (sender_pubkey, sender_seq)
|
||||
/// forms a stable MessageKey used by replies/reactions.
|
||||
sender_pubkey?: string | null
|
||||
sender_seq?: number | null
|
||||
}
|
||||
|
||||
export interface InvoiceData {
|
||||
@@ -394,6 +400,34 @@ export const useMeshStore = defineStore('mesh', () => {
|
||||
}
|
||||
}
|
||||
|
||||
async function sendReply(contactId: number, targetPubkey: string, targetSeq: number, text: string) {
|
||||
sending.value = true
|
||||
try {
|
||||
const res = await rpcClient.call<{ sent: boolean; message_id: number; sender_seq: number }>({
|
||||
method: 'mesh.send-reply',
|
||||
params: { contact_id: contactId, target_pubkey: targetPubkey, target_seq: targetSeq, text },
|
||||
})
|
||||
if (res.sent) await fetchMessages()
|
||||
return res
|
||||
} finally {
|
||||
sending.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function sendReaction(contactId: number, targetPubkey: string, targetSeq: number, emoji: string) {
|
||||
try {
|
||||
const res = await rpcClient.call<{ sent: boolean; message_id: number; sender_seq: number }>({
|
||||
method: 'mesh.send-reaction',
|
||||
params: { contact_id: contactId, target_pubkey: targetPubkey, target_seq: targetSeq, emoji },
|
||||
})
|
||||
if (res.sent) await fetchMessages()
|
||||
return res
|
||||
} catch (err: unknown) {
|
||||
error.value = err instanceof Error ? err.message : 'Failed to send reaction'
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchContent(params: {
|
||||
cid: string
|
||||
sender_onion: string
|
||||
@@ -530,6 +564,8 @@ export const useMeshStore = defineStore('mesh', () => {
|
||||
sendAlert,
|
||||
sendContent,
|
||||
fetchContent,
|
||||
sendReply,
|
||||
sendReaction,
|
||||
getSessionStatus,
|
||||
rotatePrekeys,
|
||||
getNodePositions,
|
||||
|
||||
Reference in New Issue
Block a user