fix(ux): cap the in-app toast stack; stable "Unread First" room sort
- Toast queue: a burst of notifications appended unboundedly and could cover the viewport. Cap at 5 in the atom writer, dropping the OLDEST non-sticky toast (sticky = action toasts requiring a click, never dropped). The drop scan excludes the just-appended newest (`length - 1` bound) so a fresh toast is never the one eaten when the cap is full of stickies — it stretches instead. Container gains a maxHeight + overflowY safety net and scrolls the newest (bottom) toast into view if the stack ever overflows. +4 unit tests incl. the cap-full-of-stickies boundary. - "Unread First" room sort left the entire read tail (all counts tie at 0) in arbitrary Map order. factoryRoomIdByUnread now breaks ties by recent activity. Relocated from Home.tsx (module-private) to utils/sort.ts (exported, pure) and unit-tested (equal-count and read-tail cases fall back to activity). Bug-hunt findings from LOTUS_TODO. Three review passes: the second caught that the cap could silently drop the newest notification when full of stickies (real bug, untested boundary) — fixed and covered; a third traced the corrected loop. Gate-green (tsc, eslint, prettier, 920 tests, build). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -27,8 +27,11 @@ import { useVirtualizer } from '@tanstack/react-virtual';
|
||||
import { useAtom, useAtomValue } from 'jotai';
|
||||
import { selectAtom } from 'jotai/utils';
|
||||
import FocusTrap from 'focus-trap-react';
|
||||
import { Unread } from '../../../../types/matrix/room';
|
||||
import { factoryRoomIdByActivity, factoryRoomIdByAtoZ } from '../../../utils/sort';
|
||||
import {
|
||||
factoryRoomIdByActivity,
|
||||
factoryRoomIdByAtoZ,
|
||||
factoryRoomIdByUnread,
|
||||
} from '../../../utils/sort';
|
||||
import {
|
||||
NavButton,
|
||||
NavCategory,
|
||||
@@ -210,17 +213,6 @@ function HomeEmpty() {
|
||||
);
|
||||
}
|
||||
|
||||
const factoryRoomIdByUnread =
|
||||
(roomToUnread: Map<string, Unread>) =>
|
||||
(aId: string, bId: string): number => {
|
||||
const aUnread = roomToUnread.get(aId);
|
||||
const bUnread = roomToUnread.get(bId);
|
||||
const aHas = (aUnread?.total ?? 0) > 0;
|
||||
const bHas = (bUnread?.total ?? 0) > 0;
|
||||
if (aHas !== bHas) return aHas ? -1 : 1;
|
||||
return (bUnread?.total ?? 0) - (aUnread?.total ?? 0);
|
||||
};
|
||||
|
||||
const DEFAULT_CATEGORY_ID = makeNavCategoryId('home', 'room');
|
||||
const FAVORITES_CATEGORY_ID = makeNavCategoryId('home', 'favorite');
|
||||
const LOW_PRIORITY_CATEGORY_ID = makeNavCategoryId('home', 'lowpriority');
|
||||
@@ -331,7 +323,7 @@ export function Home() {
|
||||
} else if (homeRoomSort === 'alpha') {
|
||||
comparator = factoryRoomIdByAtoZ(mx);
|
||||
} else if (homeRoomSort === 'unread') {
|
||||
comparator = factoryRoomIdByUnread(roomToUnread);
|
||||
comparator = factoryRoomIdByUnread(roomToUnread, mx);
|
||||
} else {
|
||||
comparator = factoryRoomIdByActivity(mx);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user