fix(feed): a filter on a deleted tag matches nothing and its chip says so

A saved view or the stored filter outlives its tag. feedquery dropped the unknown id, so the
view showed the whole feed under its old name. The id now lands in a MISSING_TAG_CATEGORY that
filters to nothing in the SQL and in feed_summary's mirror alike; the feed chip reads "Deleted
tag" (a placeholder while the tag list loads, the id if it failed). Channels' persisted tag
filter gets the same chip, first in the row so the +N overflow cannot fold it away.
This commit is contained in:
2026-10-03 21:53:05 +02:00
parent 52f12e5331
commit 8f1208fdfe
11 changed files with 190 additions and 26 deletions
+13 -3
View File
@@ -1,5 +1,6 @@
import type { TFunction } from "i18next";
import { useTranslation } from "react-i18next";
import { DEFAULT_TAG_MODES, type FeedFilters } from "./api";
import { DEFAULT_TAG_MODES, type FeedFilters, type Tag } from "./api";
import { useChannelNames } from "./useChannelNames";
import { parseSort } from "./feedSort";
import { useTags } from "./useTags";
@@ -35,6 +36,15 @@ export function clearedFilters(current: FeedFilters): FeedFilters {
return { ...DEFAULT_SIDEBAR_FILTERS, q: current.q, scope: "my" };
}
// A tag chip's name. A saved view or the stored filter can outlive its tag: the feed then matches
// nothing (E32 S2), and the chip says why instead of reading as a bare id — but only once the tag
// list has loaded; while it loads (a placeholder) or if it failed, the id, never a guessed "deleted".
function tagLabel(id: number, tags: { data?: Tag[]; isSuccess: boolean }, t: TFunction): string {
const tag = tags.data?.find((x) => x.id === id);
if (tag) return tag.name;
return tags.isSuccess ? t("feed.chips.deletedTag") : String(id);
}
/**
* The single source of truth for "what is currently narrowing the feed" — it backs BOTH the side
* panel's active-count badge and the removable chip row above the feed, so the two can never
@@ -56,10 +66,10 @@ export function useActiveFilters(
// Tags: one chip each — removing one shouldn't drop the rest.
for (const id of filters.tags) {
const tag = (tagsQuery.data ?? []).find((x) => x.id === id);
out.push({
key: `tag:${id}`,
label: tag?.name ?? String(id),
label: tagLabel(id, tagsQuery, t),
pending: tagsQuery.isPending,
remove: () => setFilters({ ...filters, tags: filters.tags.filter((x) => x !== id) }),
});
}