From 735d08c91db46810b931fb495da0657038496dc9 Mon Sep 17 00:00:00 2001 From: npeter83 Date: Fri, 14 Aug 2026 03:50:43 +0200 Subject: [PATCH] refactor(channels): adopt shared Tabs + usePersistedTab for the manager tab row (R13 S2b) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Retire the hand-rolled channel-manager view pills for the shared component (given an optional className so it embeds in the justify-between header without the default mb-4), and back view + layout on usePersistedTab instead of manual getAccountRaw/setAccountRaw useState. That subsumes the account-pin re-hydrate (usePersistedTab now re-reads on the pin), so ChannelsProvider's bespoke markManagerDirty/useAccountRehydrate is gone; setView keeps the history-tab logic (_chTab pushState + popstate) layered on the stable raw setter, and useAccountPersistedState's setter is now memoized so that stable action holds. Per the user's call, unify every channel-manager pill on the rounded-lg idiom: the view tabs via , the sync chips and tag chips + the "manage tags" affordance from rounded-full to rounded-lg — no more odd-one-out rounded-full. gate-green: tsc + vitest 120 + eslint-0 + knip + prettier. Self-E2E'd: tab switch, browser Back across tabs (Blocked->Hidden->Subscriptions with localStorage in sync), and a fresh tab restoring view=hidden on the FIRST load; zero console errors. --- frontend/src/components/Channels.tsx | 28 ++++++------ frontend/src/components/ChannelsProvider.tsx | 47 ++++++-------------- frontend/src/components/Tabs.tsx | 6 ++- frontend/src/lib/storage.ts | 15 ++++--- 4 files changed, 42 insertions(+), 54 deletions(-) diff --git a/frontend/src/components/Channels.tsx b/frontend/src/components/Channels.tsx index 0f1f7fe9..53523402 100644 --- a/frontend/src/components/Channels.tsx +++ b/frontend/src/components/Channels.tsx @@ -12,6 +12,7 @@ import { buildChannelColumns } from "./channelColumns"; import { notify } from "../lib/notifications"; import Tooltip from "./Tooltip"; import DataTable from "./DataTable"; +import Tabs, { type TabDef } from "./Tabs"; import ChannelLayoutGrid from "./ChannelLayoutGrid"; import { LoadingState, StateMessage } from "./QueryState"; import { useChannelTagActions } from "../lib/useChannelTagActions"; @@ -299,7 +300,7 @@ export default function Channels() { key={c.value || "all"} onClick={() => setFilters({ ...filters, sync: c.value })} aria-pressed={syncFilter === c.value} - className="pill text-xs px-2.5 py-1 rounded-full border" + className="pill text-xs px-2.5 py-1 rounded-lg border" > {t(c.labelKey)} @@ -431,20 +432,17 @@ export default function Channels() { }, [filters, search, sort, resetPage]); const cardPager = ; + const viewTabs: TabDef[] = ( + ["subscribed", "discovery", "hidden", "blocked"] as ChannelsView[] + ).map((v) => ({ id: v, label: t(`channels.tabs.${v}`) })); const tabs = (
-
- {(["subscribed", "discovery", "hidden", "blocked"] as ChannelsView[]).map((v) => ( - - ))} -
+ setView(id as ChannelsView)} + className="flex flex-wrap items-center gap-1" + />
{view === "subscribed" && statsBlock} {view === "subscribed" && layout === "cards" && sortControl} @@ -476,7 +474,7 @@ export default function Channels() { key={tg.id} onClick={() => toggleTagFilter(tg.id)} aria-pressed={tagFilter.includes(String(tg.id))} - className="pill text-xs px-2 py-1 rounded-full border" + className="pill text-xs px-2 py-1 rounded-lg border" > {tg.name} @@ -491,7 +489,7 @@ export default function Channels() { )}