From edc6cf5936db1285966c2fcc07c88bb7e5310174 Mon Sep 17 00:00:00 2001 From: Marco Beretta <81851188+berry-13@users.noreply.github.com> Date: Sun, 16 Aug 2026 06:02:44 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=A9=B9=20fix:=20Stop=20Archived=20and=20S?= =?UTF-8?q?hared=20Chats=20Dialogs=20Crashing=20on=20Open=20(#14886)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: stop the virtualized data table looping on render Opening Archived chats or Shared chats with 50 or more rows threw "Too many re-renders". DataTable passed an inline getItemKey to useVirtualizer, and virtual-core lists that option among the deps of its getMeasurementOptions memo, whose onChange notifies. getVirtualItems() is read during render, so every render built a new closure, notified, and dispatched a render-phase update on the component that was still rendering, until React gave up at 25 passes. It only fired past the 50-row virtualization threshold, which is why both dialogs looked fine while empty. Memoize getItemKey and estimateSize so their identity tracks their inputs. DataTable.spec had mocked @tanstack/react-virtual away, attributing the same error to jsdom, which hid this from CI. Keep that mock, since its row assertions need every row rendered, and add a spec that drives the real virtualizer and fails without the fix. Also restyle both dialogs, which is what made them look unfinished: - add the 19 keys these components pull from @librechat/client but the app locale never defined, so the empty state rendered com_ui_no_data verbatim - rename Shared links to Shared chats, matching the sibling Archived chats - transparent table with a rounded hover highlight painted on the cells, since border-radius does not apply to a table row, which needs separated borders - row height 56 to 40, dividers dropped, skeletons follow the same height - row hover uses surface-secondary-alt: plain surface-secondary is 247 against a 255 dialog in light mode and reads as nothing - row action buttons use surface-hover-alt, because surface-hover is also 227 in light and would vanish against the row highlight - drop the focus ring from the dialog containers and stop Shared chats seating focus in its search field, so neither flashes an outline on open - narrow both dialogs and let the table height follow its content * Fix compact row actions and selection count * fix: update selected count translation test to match interpolated output --- bun.lock | 2 +- .../Nav/SettingsTabs/Data/SharedLinks.tsx | 35 ++++-- .../General/ArchivedChatsModal.tsx | 7 +- .../General/ArchivedChatsTable.tsx | 101 +++++++++--------- client/src/locales/en/translation.json | 23 +++- package-lock.json | 2 +- packages/client/package.json | 2 +- .../client/src/components/Button.spec.tsx | 15 +++ packages/client/src/components/Button.tsx | 5 +- .../components/DataTable/DataTable.spec.tsx | 11 +- .../src/components/DataTable/DataTable.tsx | 95 ++++++++++------ .../DataTable.virtualization.spec.tsx | 83 ++++++++++++++ .../DataTable/DataTableComponents.tsx | 20 +++- .../DataTable/DataTableSearch.spec.tsx | 8 +- .../components/DataTable/DataTableSearch.tsx | 10 +- .../client/src/locales/Translation.spec.ts | 5 +- .../client/src/locales/en/translation.json | 2 +- 17 files changed, 312 insertions(+), 114 deletions(-) create mode 100644 packages/client/src/components/DataTable/DataTable.virtualization.spec.tsx diff --git a/bun.lock b/bun.lock index c1057dc4ec..8d153fe368 100644 --- a/bun.lock +++ b/bun.lock @@ -408,7 +408,7 @@ }, "packages/client": { "name": "@librechat/client", - "version": "0.4.75", + "version": "0.4.76", "devDependencies": { "@babel/core": "^7.28.5", "@babel/preset-env": "^7.29.5", diff --git a/client/src/components/Nav/SettingsTabs/Data/SharedLinks.tsx b/client/src/components/Nav/SettingsTabs/Data/SharedLinks.tsx index 5e66812729..91ddf85467 100644 --- a/client/src/components/Nav/SettingsTabs/Data/SharedLinks.tsx +++ b/client/src/components/Nav/SettingsTabs/Data/SharedLinks.tsx @@ -1,4 +1,4 @@ -import { useCallback, useState, useMemo } from 'react'; +import { useCallback, useState, useMemo, useRef } from 'react'; import { Trans } from 'react-i18next'; import { useRecoilValue } from 'recoil'; import { Link } from 'react-router-dom'; @@ -42,6 +42,7 @@ export default function SharedLinks() { const localize = useLocalize(); const { showToast } = useToastContext(); const [isOpen, setIsOpen] = useState(false); + const contentRef = useRef(null); const searchStore = useRecoilValue(store.search); const [isDeleteOpen, setIsDeleteOpen] = useState(false); const isSmallScreen = useMediaQuery('(max-width: 768px)'); @@ -64,6 +65,17 @@ export default function SharedLinks() { })); }, []); + const getRowId = useCallback((row: SharedLinkRow) => row.shareId, []); + + /** Radix would otherwise seat focus on the search field, flashing its ring every + * time the dialog opens. Anchor focus to the content instead: it is a landing + * spot rather than a tab stop, so it shows no ring and the first Tab reaches a + * real control that does. */ + const handleOpenAutoFocus = useCallback((event: Event) => { + event.preventDefault(); + contentRef.current?.focus(); + }, []); + const allLinks = useMemo(() => { if (!data?.pages) { return []; @@ -184,11 +196,11 @@ export default function SharedLinks() { to={`/share/${shareId}`} target="_blank" rel="noopener noreferrer" - className="group flex items-center gap-1 truncate rounded-sm text-link underline decoration-1 underline-offset-2 hover:decoration-2 focus:outline-none focus:ring-2 focus:ring-text-primary" + className="group flex items-center gap-1.5 truncate rounded-sm font-medium text-text-primary underline-offset-4 hover:underline focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-text-primary" > {title}