feat(feed): saved views as a header popover + Personal Views modal + a view chip

Retire the standalone 'Saved views' sidebar panel. The saved-views feature now
lives in three lighter surfaces, coordinated by a new SavedViewsProvider (query +
CRUD + the active/sticky view + modal state):

- the filter panel's header ICON opens a quick-pick popover (apply a view, active
  one checked, plus 'Manage views…');
- the header TITLE ('Filters') opens a new Personal Views modal — a master/detail
  manager: left = the reorderable list + a save-current-filters input, right = the
  selected view's name, its captured filters (shown as the same chips the toolbar
  row uses), default toggle, apply, and delete;
- a SelectedViewChip leads the applied-filter row, naming the active view with a
  warn-coloured 'modified' dot once the live filters diverge from it; clicking it
  reopens the picker.

SidePanel/FilterRail gain optional iconExpanded + onTitleClick slots (feed-only,
hidden for the demo account); the collapsed tab keeps its plain pin behaviour.
SavedViewsWidget is deleted and dropped from the panel layout.

Verified live: icon popover lists HH + Manage; applying HH surfaces the chip and
switches scope; removing a filter lights the dirty dot; the title opens the
master/detail modal with the captured-filters summary. 374 vitest, tsc, eslint
(0 err).
This commit is contained in:
2026-08-22 04:03:58 +02:00
parent 829cadf5fb
commit 7ebbf45921
13 changed files with 598 additions and 380 deletions
+20 -4
View File
@@ -20,6 +20,8 @@ export default function SidePanel({
onReset,
editHint,
actions,
iconExpanded,
onTitleClick,
children,
}: {
title: string;
@@ -34,6 +36,11 @@ export default function SidePanel({
editHint?: string;
// Extra header controls shown when NOT editing (e.g. the feed's Clear all + Share view).
actions?: ReactNode;
// Optional override for the icon in the EXPANDED header only (the collapsed tab keeps the plain
// `icon`) — the feed passes its saved-views popover trigger here so the header icon opens the picker.
iconExpanded?: ReactNode;
// When set, the expanded-header title becomes a button (the feed opens its Personal Views modal).
onTitleClick?: () => void;
children: ReactNode;
}) {
const { t } = useTranslation();
@@ -99,10 +106,19 @@ export default function SidePanel({
>
{pinned ? <PinOff className="w-4 h-4" /> : <Pin className="w-4 h-4" />}
</button>
<span className="shrink-0 flex text-accent">{icon}</span>
<span className="flex-1 min-w-0 truncate text-sm font-bold uppercase tracking-[0.12em]">
{title}
</span>
{iconExpanded ?? <span className="shrink-0 flex text-accent">{icon}</span>}
{onTitleClick ? (
<button
onClick={onTitleClick}
className="flex-1 min-w-0 truncate text-left text-sm font-bold uppercase tracking-[0.12em] transition hover:text-accent"
>
{title}
</button>
) : (
<span className="flex-1 min-w-0 truncate text-sm font-bold uppercase tracking-[0.12em]">
{title}
</span>
)}
{count > 0 && (
<span
title={t("sidebar.activeCount", { count })}