From 2a834f1b14d4278fb679fb5227938b6834ed0b94980f2ba4c54a0f332465281f Mon Sep 17 00:00:00 2001 From: AidarKC Date: Sun, 21 Jun 2026 12:27:41 +0400 Subject: [PATCH] fix ui dm chatid lowercase normalization --- ...2026-06-21_1226_fix_dm_chatid_lowercase.md | 19 ++++++++++ VERSION.properties | 4 +-- shine-UI/js/app.js | 3 +- shine-UI/js/pages/chat-view.js | 10 +++--- shine-UI/js/pages/messages-list.js | 8 +++-- shine-UI/js/state.js | 35 ++++++++++++------- 6 files changed, 56 insertions(+), 23 deletions(-) create mode 100644 Dev_Docs/Pending_Features/2026-06-21_1226_fix_dm_chatid_lowercase.md diff --git a/Dev_Docs/Pending_Features/2026-06-21_1226_fix_dm_chatid_lowercase.md b/Dev_Docs/Pending_Features/2026-06-21_1226_fix_dm_chatid_lowercase.md new file mode 100644 index 0000000..ce8b296 --- /dev/null +++ b/Dev_Docs/Pending_Features/2026-06-21_1226_fix_dm_chatid_lowercase.md @@ -0,0 +1,19 @@ +# Исправление chatId личных сообщений через lowercase + +- краткое описание фичи: + - В клиентском UI SHiNE для личных сообщений технический `chatId` теперь канонизируется через `trim().toLowerCase()` при приёме DM, открытии чата и восстановлении сообщений из IndexedDB. + - Цель: исключить рассинхрон, когда unread-индикатор есть, а входящие сообщения конкретного собеседника не видны из-за разного регистра логина. + +- что именно проверять: + - Отправить личные сообщения между двумя пользователями, у одного из которых логин отображается с заглавными буквами. + - Убедиться, что входящие сообщения показываются внутри открытого чата, а не только в общем unread-индикаторе. + - Перезагрузить страницу и проверить, что история чата после гидрации из IndexedDB остаётся в одном диалоге. + - Проверить, что переход в чат из списка диалогов и из графа связей открывает тот же диалог без дублирования. + +- ожидаемый результат: + - Все сообщения одного собеседника попадают в один и тот же DM-чат независимо от регистра логина. + - Общий unread, список диалогов и содержимое открытого чата совпадают между собой. + - После перезагрузки UI не появляется отдельный дубль диалога с тем же логином в другом регистре. + +- статус: + - pending diff --git a/VERSION.properties b/VERSION.properties index dda8933..3ad2a6e 100644 --- a/VERSION.properties +++ b/VERSION.properties @@ -1,2 +1,2 @@ -client.version=1.2.228 -server.version=1.2.214 +client.version=1.2.230 +server.version=1.2.216 diff --git a/shine-UI/js/app.js b/shine-UI/js/app.js index ed368f8..22b61b1 100644 --- a/shine-UI/js/app.js +++ b/shine-UI/js/app.js @@ -29,6 +29,7 @@ import { addSignedMessageToChat, markIncomingReadByBaseKey, markOutgoingReadByBaseKey, + normalizeDmChatId, setContacts, } from './state.js'; @@ -912,7 +913,7 @@ async function init() { const fromLogin = parsed.fromLogin || ''; const toLogin = parsed.toLogin || ''; const messageType = Number(parsed.messageType || 0); - const chatId = messageType === 2 ? toLogin : fromLogin; + const chatId = normalizeDmChatId(messageType === 2 ? toLogin : fromLogin); const text = (messageType === 1 || messageType === 2) ? String(parsed.text || '') : ''; diff --git a/shine-UI/js/pages/chat-view.js b/shine-UI/js/pages/chat-view.js index 6f7a3cb..f9e9af7 100644 --- a/shine-UI/js/pages/chat-view.js +++ b/shine-UI/js/pages/chat-view.js @@ -10,6 +10,7 @@ import { markChatRead, markOutgoingSent, markReadReceiptSentByBaseKey, + normalizeDmChatId, authService, setContacts, state, @@ -334,11 +335,12 @@ function renderLog(list, chatId, { onOpenActions } = {}) { } export function render({ navigate, route }) { - const chatId = route.params.chatId || 'u1'; - const contact = directMessages.find((d) => d.id === chatId) || { + const routeChatId = route.params.chatId || 'u1'; + const chatId = normalizeDmChatId(routeChatId) || 'u1'; + const contact = directMessages.find((d) => normalizeDmChatId(d.id) === chatId) || { id: chatId, - name: chatId, - initials: (chatId[0] || '?').toUpperCase(), + name: String(routeChatId || chatId), + initials: (String(routeChatId || chatId)[0] || '?').toUpperCase(), }; const screen = document.createElement('section'); diff --git a/shine-UI/js/pages/messages-list.js b/shine-UI/js/pages/messages-list.js index cb94bda..d29bf9f 100644 --- a/shine-UI/js/pages/messages-list.js +++ b/shine-UI/js/pages/messages-list.js @@ -2,6 +2,7 @@ import { directMessages } from '../mock-data.js'; import { getChatMessages, isSessionInvalidError, + normalizeDmChatId, setContacts, state, terminateCurrentSession, @@ -126,7 +127,7 @@ export function render({ navigate }) { `; row.prepend(avatarWrap); - row.addEventListener('click', () => navigate(`chat-view/${encodeURIComponent(item.id)}`)); + row.addEventListener('click', () => navigate(`chat-view/${encodeURIComponent(normalizeDmChatId(item.id))}`)); return row; } @@ -139,12 +140,13 @@ export function render({ navigate }) { const contactRows = contacts.map((login) => { const preview = directMessages.find((item) => item.id.toLowerCase() === login.toLowerCase()); - const chat = getChatMessages(login); + const canonicalLogin = normalizeDmChatId(login); + const chat = getChatMessages(canonicalLogin); const lastChat = chat[chat.length - 1]; const unread = chat.filter((m) => m?.from === 'in' && m?.unread).length; const lastTimeMs = Number(lastChat?.createdAtMs || 0); return { - id: login, + id: canonicalLogin, name: preview?.name || login, lastMessage: lastChat?.text || preview?.lastMessage || 'Диалог пока пуст.', time: formatChatRowTime(lastTimeMs), diff --git a/shine-UI/js/state.js b/shine-UI/js/state.js index d6be9a2..6ec8784 100644 --- a/shine-UI/js/state.js +++ b/shine-UI/js/state.js @@ -92,6 +92,10 @@ const DEFAULT_ARWEAVE_SERVER = 'https://arweave.net'; const DEFAULT_CALL_PREFLIGHT_TIMEOUT_MS = 6000; const DEFAULT_OPENAI_BASE_URL = 'https://api.openai.com/v1'; +export function normalizeDmChatId(value) { + return String(value || '').trim().toLowerCase(); +} + function normalizeToolsSettings(rawTools) { const source = rawTools && typeof rawTools === 'object' ? rawTools : {}; const stt = source.speechToText && typeof source.speechToText === 'object' ? source.speechToText : {}; @@ -376,11 +380,12 @@ function sortChatMessagesInPlace(chatId) { } function persistMessageRecord(chatId, row) { - if (!chatId || !row?.messageKey) return; + const normalizedChatId = normalizeDmChatId(chatId); + if (!normalizedChatId || !row?.messageKey) return; const resolvedTs = resolveChatMessageTimeMs(row); void putStoredMessage({ messageKey: row.messageKey, - chatId, + chatId: normalizedChatId, from: row.from || 'in', text: String(row.text || ''), baseKey: String(row.baseKey || ''), @@ -408,7 +413,7 @@ export async function hydrateMessagesFromStore() { rows .sort((a, b) => Number(a?.ts || 0) - Number(b?.ts || 0)) .forEach((row) => { - const chatId = String(row?.chatId || '').trim(); + const chatId = normalizeDmChatId(row?.chatId); const messageKey = String(row?.messageKey || '').trim(); if (!chatId || !messageKey) return; if (state.knownMessageKeys[messageKey]) return; @@ -437,10 +442,12 @@ export async function hydrateMessagesFromStore() { } export function getChatMessages(chatId) { - if (!state.chats[chatId]) { - state.chats[chatId] = []; + const normalizedChatId = normalizeDmChatId(chatId); + if (!normalizedChatId) return []; + if (!state.chats[normalizedChatId]) { + state.chats[normalizedChatId] = []; } - return state.chats[chatId]; + return state.chats[normalizedChatId]; } export function addChatMessage(chatId, text) { @@ -583,9 +590,10 @@ export function addSignedMessageToChat({ revisionTimeMs = 0, deleted = false, } = {}) { + const normalizedChatId = normalizeDmChatId(chatId); const id = String(messageKey || '').trim(); - if (!chatId || !id) return false; - const list = getChatMessages(chatId); + if (!normalizedChatId || !id) return false; + const list = getChatMessages(normalizedChatId); const existingIndex = list.findIndex((row) => String(row?.messageKey || '').trim() === id); const existing = existingIndex >= 0 ? list[existingIndex] : null; const nextRevision = Number(revisionTimeMs || 0); @@ -599,7 +607,7 @@ export function addSignedMessageToChat({ if (existingIndex >= 0) { list.splice(existingIndex, 1); removeStoredMessageRecord(id); - sortChatMessagesInPlace(chatId); + sortChatMessagesInPlace(normalizedChatId); return true; } return false; @@ -623,17 +631,18 @@ export function addSignedMessageToChat({ if (existingIndex < 0) { list.push(row); } - sortChatMessagesInPlace(chatId); - persistMessageRecord(chatId, row); + sortChatMessagesInPlace(normalizedChatId); + persistMessageRecord(normalizedChatId, row); return true; } export function markChatRead(chatId) { - const list = getChatMessages(chatId); + const normalizedChatId = normalizeDmChatId(chatId); + const list = getChatMessages(normalizedChatId); list.forEach((row) => { if (row?.from === 'in') { row.unread = false; - persistMessageRecord(chatId, row); + persistMessageRecord(normalizedChatId, row); } }); }