From 25f7265e5576bddae484ba58fc5c48e064401b82 Mon Sep 17 00:00:00 2001 From: Lukas Remis Date: Wed, 30 Sep 2026 20:16:56 +0200 Subject: [PATCH] Mail list: rows glide up into a removed row's place; subjects stopped blinking The thread-labels query is keyed by the visible ids, so every row leaving the list dropped the label chips until they reloaded and the subjects jumped left and back. Keep the previous labels meanwhile. Rows now animate their position, so the ones below a removed row slide up over it (off under reduced motion). Co-Authored-By: Claude Opus 5.5 --- frontend/src/components/list/MessageList.tsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/frontend/src/components/list/MessageList.tsx b/frontend/src/components/list/MessageList.tsx index 2aeeb20..9e1606b 100644 --- a/frontend/src/components/list/MessageList.tsx +++ b/frontend/src/components/list/MessageList.tsx @@ -1,4 +1,4 @@ -import { useQuery } from "@tanstack/react-query"; +import { keepPreviousData, useQuery } from "@tanstack/react-query"; import { useVirtualizer } from "@tanstack/react-virtual"; import { useEffect, useRef } from "react"; @@ -132,6 +132,9 @@ export function MessageList({ queryKey: ["thread-labels", threadIds], queryFn: () => api.threadLabelsBatch(threadIds), enabled: threadIds.length > 0, + // The key changes whenever a row joins or leaves the list; keep the chips + // already known meanwhile, or every subject jumps left and back. + placeholderData: keepPreviousData, }); const labelMap = labelsQuery.data ?? {}; @@ -199,7 +202,8 @@ export function MessageList({ height: item.size, transform: `translateY(${item.start}px)`, }} - className={`group absolute inset-x-0 px-2 pt-[7px] ${ + // A row leaving the list lets the ones below glide up into its place. + className={`group absolute inset-x-0 px-2 pt-[7px] motion-safe:transition-transform motion-safe:duration-200 motion-safe:ease-out ${ dimmed ? "pointer-events-none opacity-40" : "cursor-pointer" }`} >