diff --git a/app/repository.py b/app/repository.py index efa5620da..fcfe5049c 100644 --- a/app/repository.py +++ b/app/repository.py @@ -183,6 +183,11 @@ class ContactRepository: await db.conn.commit() return cursor.rowcount > 0 + @staticmethod + async def mark_all_read(timestamp: int) -> None: + """Mark all contacts as read at the given timestamp.""" + await db.conn.execute("UPDATE contacts SET last_read_at = ?", (timestamp,)) + @staticmethod async def get_by_pubkey_first_byte(hex_byte: str) -> list[Contact]: """Get contacts whose public key starts with the given hex byte (2 chars).""" @@ -269,6 +274,11 @@ class ChannelRepository: await db.conn.commit() return cursor.rowcount > 0 + @staticmethod + async def mark_all_read(timestamp: int) -> None: + """Mark all channels as read at the given timestamp.""" + await db.conn.execute("UPDATE channels SET last_read_at = ?", (timestamp,)) + class MessageRepository: @staticmethod diff --git a/app/routers/read_state.py b/app/routers/read_state.py index 4c66ed5e0..5135d19ec 100644 --- a/app/routers/read_state.py +++ b/app/routers/read_state.py @@ -7,7 +7,7 @@ from fastapi import APIRouter, Query from app.database import db from app.models import UnreadCounts -from app.repository import MessageRepository +from app.repository import ChannelRepository, ContactRepository, MessageRepository logger = logging.getLogger(__name__) router = APIRouter(prefix="/read-state", tags=["read-state"]) @@ -35,9 +35,8 @@ async def mark_all_read() -> dict: """ now = int(time.time()) - # Update all contacts and channels in one transaction - await db.conn.execute("UPDATE contacts SET last_read_at = ?", (now,)) - await db.conn.execute("UPDATE channels SET last_read_at = ?", (now,)) + await ContactRepository.mark_all_read(now) + await ChannelRepository.mark_all_read(now) await db.conn.commit() logger.info("Marked all contacts and channels as read at %d", now) diff --git a/frontend/src/components/MessageList.tsx b/frontend/src/components/MessageList.tsx index 5a9e35b21..d5640a239 100644 --- a/frontend/src/components/MessageList.tsx +++ b/frontend/src/components/MessageList.tsx @@ -1,4 +1,12 @@ -import { useEffect, useLayoutEffect, useRef, useCallback, useState, type ReactNode } from 'react'; +import { + useEffect, + useLayoutEffect, + useRef, + useCallback, + useMemo, + useState, + type ReactNode, +} from 'react'; import type { Contact, Message, MessagePath, RadioConfig } from '../types'; import { CONTACT_TYPE_REPEATER } from '../types'; import { formatTime, parseSenderFromText } from '../utils/messageParser'; @@ -296,7 +304,10 @@ export function MessageList({ // Sort messages by received_at ascending (oldest first) // Note: Deduplication is handled by useConversationMessages.addMessageIfNew() // and the database UNIQUE constraint on (type, conversation_key, text, sender_timestamp) - const sortedMessages = [...messages].sort((a, b) => a.received_at - b.received_at); + const sortedMessages = useMemo( + () => [...messages].sort((a, b) => a.received_at - b.received_at), + [messages] + ); // Helper to get a unique sender key for grouping messages const getSenderKey = (msg: Message, sender: string | null): string => { diff --git a/frontend/src/components/SettingsModal.tsx b/frontend/src/components/SettingsModal.tsx index 370dd61d3..acb212150 100644 --- a/frontend/src/components/SettingsModal.tsx +++ b/frontend/src/components/SettingsModal.tsx @@ -485,7 +485,10 @@ export function SettingsModal({ ) : ( setActiveTab(v as SettingsTab)} + onValueChange={(v) => { + setActiveTab(v as SettingsTab); + setError(''); + }} className="w-full" >