From 7aab0dd047d3a3effe7dcb7c30cd891c966fbaca Mon Sep 17 00:00:00 2001 From: npeter83 Date: Fri, 19 Jun 2026 03:55:55 +0200 Subject: [PATCH] fix(channels): drop a stale column filter when sent to "without full history" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A focus-channel deep-link sets (and persists) the channel-name column filter via the DataTable's externalFilter. That persisted value then survived into unrelated visits: clicking the header's "N without full history" link landed on the right tab with the right status chip, but a leftover name filter (e.g. a channel focused in a past session) hid every row — "No channels". Add a resetFiltersToken to DataTable that clears its column filters when it advances, and bump it from the header intent. The token's ref starts at 0 so the clear fires even when the table remounts on navigation, while a plain reload (token still 0) keeps the user's persisted filters. --- frontend/src/App.tsx | 5 +++++ frontend/src/components/Channels.tsx | 4 ++++ frontend/src/components/DataTable.tsx | 17 +++++++++++++++++ 3 files changed, 26 insertions(+) diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index ea37c48..179cc92 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -148,6 +148,9 @@ export default function App() { setChannelsViewState(v); localStorage.setItem(CHANNELS_VIEW_KEY, v); }; + // Bumped to tell the channel manager to drop a stale column filter when we send the user + // there to see a specific set (the header's "without full history" link). + const [channelsFilterReset, setChannelsFilterReset] = useState(0); // "Focus this channel in the manager": jump to the Channels page and seed its name filter // so the channel is isolated. Cleared when leaving the page so it doesn't re-apply later. const [focusChannelName, setFocusChannelName] = useState(null); @@ -423,6 +426,7 @@ export default function App() { onGoToFullHistory={() => { setChannelFilter("needs_full"); setChannelsView("subscribed"); // the status filter applies to the subscriptions tab + setChannelsFilterReset((n) => n + 1); // drop any stale column filter hiding the rows setPage("channels"); }} /> @@ -449,6 +453,7 @@ export default function App() { setStatusFilter={setChannelFilter} view={channelsView} setView={setChannelsView} + filtersResetToken={channelsFilterReset} onOpenWizard={() => setWizardOpen(true)} onViewChannel={(id, name) => { setFilters({ ...filters, channelId: id, channelName: name, show: "all" }); diff --git a/frontend/src/components/Channels.tsx b/frontend/src/components/Channels.tsx index daf1caa..76447a7 100644 --- a/frontend/src/components/Channels.tsx +++ b/frontend/src/components/Channels.tsx @@ -52,6 +52,7 @@ export default function Channels({ setStatusFilter, view, setView, + filtersResetToken, onOpenWizard, }: { canWrite: boolean; @@ -66,6 +67,8 @@ export default function Channels({ // (the header's "without full history" link, focus-channel) can force it back here. view: ChannelsView; setView: (v: ChannelsView) => void; + // Bumped by the header's "without full history" intent to clear a stale column filter. + filtersResetToken: number; onOpenWizard: () => void; }) { const { t } = useTranslation(); @@ -510,6 +513,7 @@ export default function Channels({ controlsPosition="top" controlsLeading={statusChips} externalFilter={focusChannelName ? { key: "channel", value: focusChannelName } : null} + resetFiltersToken={filtersResetToken} rowClassName={(c) => (c.hidden ? "opacity-60" : "")} emptyText={t("channels.empty")} /> diff --git a/frontend/src/components/DataTable.tsx b/frontend/src/components/DataTable.tsx index f2e729d..cad982d 100644 --- a/frontend/src/components/DataTable.tsx +++ b/frontend/src/components/DataTable.tsx @@ -67,6 +67,7 @@ export default function DataTable({ controlsPosition = "bottom", controlsLeading, externalFilter, + resetFiltersToken, }: { rows: T[]; columns: Column[]; @@ -81,6 +82,10 @@ export default function DataTable({ // Set a column's filter from outside (e.g. "focus this channel" deep-links here). Applied // whenever its value changes; the user can still clear it from the header afterwards. externalFilter?: { key: string; value: string } | null; + // Bump this to clear all column filters — for navigation intents that mean "show me this + // set" (e.g. the header's "without full history" link), so a stale persisted filter can't + // hide the rows the user was just sent to see. + resetFiltersToken?: number; }) { const { t } = useTranslation(); const initial = loadPersist(persistKey); @@ -104,6 +109,18 @@ export default function DataTable({ // eslint-disable-next-line react-hooks/exhaustive-deps }, [externalFilter?.key, externalFilter?.value]); + // Clear column filters when the reset token advances. The ref starts at 0 (not at the prop) + // so a navigation that bumped the token still clears even when it remounts this table fresh + // (e.g. arriving from another page); a plain reload keeps the token at 0 and the effect is a + // no-op, so the user's persisted filters survive. + const resetRef = useRef(0); + useEffect(() => { + if (resetFiltersToken === undefined || resetFiltersToken === resetRef.current) return; + resetRef.current = resetFiltersToken; + setFilters({}); + setPage(0); + }, [resetFiltersToken]); + useEffect(() => { if (!openFilter) return; function onDown(e: MouseEvent) {