feat(ui): mesh chat polish — transport pills in image modal, hop-route modal, reaction dropdown, real read-tracking
Demo images / Build & push demo images (push) Failing after 3m29s
Demo images / Build & push demo images (push) Failing after 3m29s
- Image quality modal: 'Send via' pills (LoRa / FIPS / Tor) when the peer is federation-reachable — mesh.transport-advice now returns has_fips + last_transport alongside has_tor. Picking FIPS/Tor routes the image over the content-ref path instead of the radio. - Attachment modals (transport chooser, image quality, new hop modal) Teleport to body so the backdrop dims the FULL viewport — rendered in-place they sat inside a transformed glass panel that trapped position:fixed to the right chat panel. - Click a message's transport pill → route modal: radio hops + live SNR/RSSI quality for LoRa transports, overlay/circuit shape for FIPS/Tor, delivery + E2E state. - Reactions move behind a compact 'React ▾' dropdown with a larger 12-emoji palette. - Unread badges now clear like a normal chat app: opening a contact clears ALL twins of the merged conversation (badge sums every contact_id — clearing just the clicked one left it stuck), and only once the chat has scrolled to the latest messages; scrolled up into history, new arrivals accumulate until you scroll back down. - Refresh button shows only the spinner while refreshing (text+spinner overflowed the fixed button width). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
e62f911810
commit
0aa3941c40
@@ -264,8 +264,14 @@ export const useMeshStore = defineStore('mesh', () => {
|
||||
|
||||
// Track unread message counts per peer (contact_id -> count)
|
||||
const unreadCounts = ref<Record<number, number>>({})
|
||||
// Currently viewing chat for this contact_id (clears unread)
|
||||
const viewingChatId = ref<number | null>(null)
|
||||
// Contact ids of the chat currently on screen — ALL twins of the merged
|
||||
// conversation, not just the clicked row's id, since the unread badge sums
|
||||
// across every underlying contact_id (a message can land on any twin).
|
||||
const viewingChatIds = ref<number[]>([])
|
||||
// Whether the open chat is scrolled to (near) the bottom. New messages only
|
||||
// auto-clear as read while the latest messages are actually in view —
|
||||
// standard chat-app semantics; scrolled-up-in-history still counts unread.
|
||||
const viewingAtBottom = ref(true)
|
||||
// Total unread count for nav badge
|
||||
const totalUnread = computed(() =>
|
||||
Object.values(unreadCounts.value).reduce((a, b) => a + b, 0)
|
||||
@@ -401,8 +407,11 @@ export const useMeshStore = defineStore('mesh', () => {
|
||||
m => m.direction === 'received' && !messages.value.some(existing => existing.id === m.id)
|
||||
)
|
||||
for (const msg of newMsgs) {
|
||||
// Don't count as unread if we're currently viewing that chat
|
||||
if (msg.peer_contact_id !== viewingChatId.value) {
|
||||
// Don't count as unread if we're currently viewing that chat AND the
|
||||
// bottom (latest messages) is in view — i.e. the user actually sees it.
|
||||
const seenLive =
|
||||
viewingChatIds.value.includes(msg.peer_contact_id) && viewingAtBottom.value
|
||||
if (!seenLive) {
|
||||
unreadCounts.value[msg.peer_contact_id] = (unreadCounts.value[msg.peer_contact_id] || 0) + 1
|
||||
}
|
||||
}
|
||||
@@ -530,13 +539,15 @@ export const useMeshStore = defineStore('mesh', () => {
|
||||
federatedPositions.value = next
|
||||
}
|
||||
|
||||
function markChatRead(contactId: number) {
|
||||
viewingChatId.value = contactId
|
||||
delete unreadCounts.value[contactId]
|
||||
function markChatRead(contactId: number | number[]) {
|
||||
const ids = Array.isArray(contactId) ? contactId : [contactId]
|
||||
viewingChatIds.value = ids
|
||||
for (const id of ids) delete unreadCounts.value[id]
|
||||
}
|
||||
|
||||
function clearViewingChat() {
|
||||
viewingChatId.value = null
|
||||
viewingChatIds.value = []
|
||||
viewingAtBottom.value = true
|
||||
}
|
||||
|
||||
async function sendMessage(contactId: number, message: string) {
|
||||
@@ -681,6 +692,8 @@ export const useMeshStore = defineStore('mesh', () => {
|
||||
tier: 'auto-mesh' | 'choose' | 'resource-mesh' | 'tor-only' | 'impossible'
|
||||
est_seconds: number
|
||||
has_tor: boolean
|
||||
has_fips: boolean
|
||||
last_transport: string | null
|
||||
reason: string
|
||||
size: number
|
||||
mesh_auto_max: number
|
||||
@@ -999,6 +1012,7 @@ export const useMeshStore = defineStore('mesh', () => {
|
||||
error,
|
||||
sending,
|
||||
unreadCounts,
|
||||
viewingAtBottom,
|
||||
totalUnread,
|
||||
nodePositions,
|
||||
federatedPositions,
|
||||
|
||||
Reference in New Issue
Block a user