From e90603d4db4fc40ef3350412e998b7f861ffa79c Mon Sep 17 00:00:00 2001 From: npeter83 Date: Sat, 22 Aug 2026 06:33:48 +0200 Subject: [PATCH] fix(feed): deep-review remediations for the filter-bar overhaul MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Correctness / behaviour: - Move the Personal Views modal from inside the collapsible filter rail to the feed page (moduleRegistry FeedPage), so opening it from the always-visible toolbar view chip works while the rail is unpinned (its default) instead of no-op'ing and latching modalOpen into a later stale pop-open. - Migrate a persisted legacy global `tagMode` into per-category `tagModes` on the localStorage seed paths too (loadAccountFilters), matching the saved-view and URL paths — an upgrading user's topic any/all no longer silently reverts to 'or'. - DateFilterSelect: reset the custom reveal on an external clear while the popover is closed (guard on !open, not maxAgeDays), so the trigger reverts to "Any time" instead of lingering on "Custom range" with empty fields. - PersonalViewsModal delete: only advance the detail selection when the delete actually happened (remove() now resolves a boolean) — cancelling the confirm no longer switches the selected view. - SavedViewsProvider: prefer the explicitly-applied (sticky) view among several that capture identical filters, so apply(B) isn't re-attributed to an earlier duplicate A (wrong "modified" chip name). A11y: - Give the clickable panel title a descriptive accessible name (titleActionLabel, wired to "Personal views") instead of announcing only "Filters". - Give the "+N" overflow pill an aria-label ("N more filters"). - Use t("views.reorder") for the saved-view drag handle instead of a hardcoded English "reorder" literal. Also extract the chip-fit decision into a pure, testable lib/chipOverflow.ts and measure the inter-chip gap live (rem-safe) instead of a frozen 6px constant. --- frontend/src/components/ActiveFilterChips.tsx | 62 +++++++++---------- frontend/src/components/DateFilterSelect.tsx | 11 ++-- .../src/components/FeedFiltersProvider.tsx | 21 ++++++- frontend/src/components/FilterRail.tsx | 3 + .../src/components/PersonalViewsModal.tsx | 10 +-- .../src/components/SavedViewsProvider.tsx | 17 ++--- frontend/src/components/SidePanel.tsx | 5 ++ frontend/src/components/Sidebar.tsx | 5 +- frontend/src/components/moduleRegistry.tsx | 31 ++++++---- frontend/src/i18n/locales/en/feed.json | 2 + frontend/src/i18n/locales/hu/feed.json | 2 + frontend/src/lib/chipOverflow.ts | 53 ++++++++++++++++ 12 files changed, 156 insertions(+), 66 deletions(-) create mode 100644 frontend/src/lib/chipOverflow.ts diff --git a/frontend/src/components/ActiveFilterChips.tsx b/frontend/src/components/ActiveFilterChips.tsx index a90446ad..cfc909c7 100644 --- a/frontend/src/components/ActiveFilterChips.tsx +++ b/frontend/src/components/ActiveFilterChips.tsx @@ -2,6 +2,7 @@ import { useLayoutEffect, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; import { Filter, X } from "lucide-react"; import type { ActiveFilter } from "../lib/useActiveFilters"; +import { decideChipLayout } from "../lib/chipOverflow"; import { PopoverItem, PopoverSurface, useAnchoredPopover } from "./AnchoredPopover"; // The applied-filter row above the feed: every narrowing currently in force, each removable on its @@ -18,7 +19,6 @@ import { PopoverItem, PopoverSurface, useAnchoredPopover } from "./AnchoredPopov // Below this lane width an overflow collapses all the way to a single count pill (mobile), instead of // the "fit some + N more" treatment used on wider lanes. const NARROW_PX = 420; -const GAP_PX = 6; // matches gap-1.5 (px) const chipCls = "chip group inline-flex items-center gap-1 pl-2.5 pr-1.5 py-1 text-sm whitespace-nowrap hover:border-accent hover:bg-accent/20 transition"; @@ -43,11 +43,15 @@ function ChipButton({ f, testId }: { f: ActiveFilter; testId?: string }) { // The "+N" / "N filters" pill and its popover listing the (overflowed / all) chips, each removable. function OverflowPopover({ label, + ariaLabel, chips, onClearAll, icon, }: { label: string; + /** Accessible name for the trigger + popover — describes what's inside (e.g. "3 more filters"), + * since the visible label can be a bare "+3". Defaults to `label`. */ + ariaLabel?: string; chips: ActiveFilter[]; onClearAll: () => void; icon?: boolean; @@ -60,12 +64,14 @@ function OverflowPopover({ mode: "menu", placement: "bottom-end", }); + const name = ariaLabel ?? label; return ( <>