// Pure filter/sort logic for the Threads list panel. Operates on plain snapshots // so it's unit-testable without real matrix-js-sdk Thread objects. export type ThreadFilter = 'all' | 'unread' | 'participating'; export type ThreadSort = 'recent' | 'oldest'; export type ThreadSnapshot = { id: string; latestTs: number; unread: number; participated: boolean; }; const THREAD_FILTERS: readonly ThreadFilter[] = ['all', 'unread', 'participating']; const THREAD_SORTS: readonly ThreadSort[] = ['recent', 'oldest']; /** Type guard for persisted/untrusted filter values (localStorage can hold junk). */ export function isThreadFilter(value: unknown): value is ThreadFilter { return typeof value === 'string' && (THREAD_FILTERS as readonly string[]).includes(value); } /** Type guard for persisted/untrusted sort values. */ export function isThreadSort(value: unknown): value is ThreadSort { return typeof value === 'string' && (THREAD_SORTS as readonly string[]).includes(value); } /** Keep only the threads matching the filter. Pure — returns a new array. */ export function filterThreads(threads: T[], filter: ThreadFilter): T[] { if (filter === 'unread') return threads.filter((t) => t.unread > 0); if (filter === 'participating') return threads.filter((t) => t.participated); return [...threads]; } /** * Order threads by last activity. `recent` = newest first, `oldest` = oldest first. * Ties broken by id for stable, deterministic output. Pure — returns a new array. */ export function sortThreads(threads: T[], sort: ThreadSort): T[] { const idCmp = (a: T, b: T): number => (a.id < b.id ? -1 : a.id > b.id ? 1 : 0); return [...threads].sort((a, b) => { const diff = sort === 'oldest' ? a.latestTs - b.latestTs : b.latestTs - a.latestTs; return diff !== 0 ? diff : idCmp(a, b); }); }