perf(mobile): paginate threads to 40 (was 500) — kills the heavy-DOM jank at the source

User's insight: the jank/jumps come from rendering ALL ~500 messages on open. Load only the latest 40
(server + client PAGE), and the existing loadOlder() pages in older history on scroll-up with a scroll
anchor (no jump). Whole-thread SEARCH is a separate endpoint, unaffected. Also #1: keyboard-show only
pins to newest when already near the bottom, so replying to an OLD message no longer yanks to latest.
Build marker -> 2026-07-18-batch120.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-18 23:31:05 +05:30
parent 2bd8b3c8c1
commit f3c3e08be4
2 changed files with 5 additions and 5 deletions
+2 -2
View File
@@ -818,7 +818,7 @@ route('GET', '/api/messages/thread', async (req, res) => {
const group = q.get('group');
if (group) {
if (!R.conversations.isMember(group, u.id)) return json(res, 403, { error: 'not a member of this group' });
const rows = R.messages.threadByConversation(group, 500, before);
const rows = R.messages.threadByConversation(group, 40, before); // page size (latest 40 / older via ?before) — matches client PAGE for smooth open + lazy load
if (!peek && !before) {
R.conversations.markRead(group, u.id);
const evt = { type: 'group-read', group, by: u.id, byName: names[u.id] || u.email, at: now() };
@@ -837,7 +837,7 @@ route('GET', '/api/messages/thread', async (req, res) => {
const other = R.users.resolve(q.get('with')); // follow a merge redirect so a stale peer id still loads the thread
if (!other) return json(res, 400, { error: 'with or group required' });
if (!R.users.inTenant(other, u.team_id)) return json(res, 404, { error: 'no such contact' });
const rows = R.messages.thread(u.team_id, u.id, other, 500, before);
const rows = R.messages.thread(u.team_id, u.id, other, 40, before); // page size (latest 40 / older via ?before) — matches client PAGE for smooth open + lazy load
if (!peek && !before) { R.messages.markRead(u.team_id, u.id, other); try { CHAT.pushToUser(other, { type: 'chat-read', by: u.id }); } catch (_) {} try { CHAT.pushToUser(u.id, { type: 'notif-clear', kind: 'dm', id: other }); } catch (_) {} } // #13
const rxBy = groupReactions(R.reactions.forPair(u.team_id, u.id, other), u.id, names);
return json(res, 200, rows.map((m) => { const d = buildMsgDTO(m, names, u.id); d.reactions = dtoReactions(rxBy, m.id); return d; }));