refactor(channels): adopt shared Tabs + usePersistedTab for the manager tab row (R13 S2b)

Retire the hand-rolled channel-manager view pills for the shared <Tabs> 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 <Tabs>, 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.
This commit is contained in:
2026-08-14 03:50:43 +02:00
parent e6688edbcc
commit 735d08c91d
4 changed files with 42 additions and 54 deletions
+5 -1
View File
@@ -18,13 +18,17 @@ export default function Tabs({
tabs,
active,
onChange,
className = "flex flex-wrap gap-1 mb-4",
}: {
tabs: TabDef[];
active: string;
onChange: (id: string) => void;
// Override the row wrapper — e.g. the channel manager embeds the rail in a justify-between header
// and drops the default bottom margin. Omit for the standalone-row default.
className?: string;
}) {
return (
<div className="flex flex-wrap gap-1 mb-4">
<div className={className}>
{tabs.map((tb) => {
const on = tb.id === active;
return (