From 7361b98f76a47dcaab2158aa4eb8953542653afa Mon Sep 17 00:00:00 2001 From: Mitch Capper Date: Thu, 2 Apr 2026 13:12:02 -0700 Subject: [PATCH] Filter control for what models on main tab --- src/App.tsx | 2 + src/components/app/app-content.tsx | 3 + src/components/provider-card.tsx | 7 +- src/hooks/app/use-app-plugin-views.ts | 4 +- src/hooks/app/use-settings-plugin-actions.ts | 24 +++++++ src/hooks/app/use-settings-plugin-list.ts | 6 +- src/lib/plugin-types.ts | 1 + src/lib/settings.ts | 27 +++++++- src/pages/overview.tsx | 1 + src/pages/settings.tsx | 73 +++++++++++++------- 10 files changed, 117 insertions(+), 31 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index d5edc031..8d834415 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -158,6 +158,7 @@ function App() { const { handleReorder, handleToggle, + handleToggleOverviewLabel, } = useSettingsPluginActions({ pluginSettings, setPluginSettings, @@ -241,6 +242,7 @@ function App() { onRetryPlugin: handleRetryPlugin, onReorder: handleReorder, onToggle: handleToggle, + onToggleOverviewLabel: handleToggleOverviewLabel, onAutoUpdateIntervalChange: handleAutoUpdateIntervalChange, onThemeModeChange: handleThemeModeChange, onDisplayModeChange: handleDisplayModeChange, diff --git a/src/components/app/app-content.tsx b/src/components/app/app-content.tsx index e362fa76..944aff85 100644 --- a/src/components/app/app-content.tsx +++ b/src/components/app/app-content.tsx @@ -26,6 +26,7 @@ export type AppContentActionProps = { onRetryPlugin: (id: string) => void onReorder: (orderedIds: string[]) => void onToggle: (id: string) => void + onToggleOverviewLabel: (pluginId: string, label: string) => void onAutoUpdateIntervalChange: (value: AutoUpdateIntervalMinutes) => void onThemeModeChange: (mode: ThemeMode) => void onDisplayModeChange: (mode: DisplayMode) => void @@ -46,6 +47,7 @@ export function AppContent({ onRetryPlugin, onReorder, onToggle, + onToggleOverviewLabel, onAutoUpdateIntervalChange, onThemeModeChange, onDisplayModeChange, @@ -100,6 +102,7 @@ export function AppContent({ plugins={settingsPlugins} onReorder={onReorder} onToggle={onToggle} + onToggleOverviewLabel={onToggleOverviewLabel} autoUpdateInterval={autoUpdateInterval} onAutoUpdateIntervalChange={onAutoUpdateIntervalChange} themeMode={themeMode} diff --git a/src/components/provider-card.tsx b/src/components/provider-card.tsx index 2b02722c..5703f08b 100644 --- a/src/components/provider-card.tsx +++ b/src/components/provider-card.tsx @@ -30,6 +30,7 @@ interface ProviderCardProps { onRetry?: () => void scopeFilter?: "overview" | "all" displayMode: DisplayMode + disabledOverviewLabels?: string[] resetTimerDisplayMode?: ResetTimerDisplayMode onResetTimerDisplayModeToggle?: () => void } @@ -94,6 +95,7 @@ export function ProviderCard({ displayMode, resetTimerDisplayMode = "relative", onResetTimerDisplayModeToggle, + disabledOverviewLabels = [], }: ProviderCardProps) { const cooldownRemainingMs = useMemo(() => { if (!lastManualRefreshAt) return 0 @@ -102,14 +104,15 @@ export function ProviderCard({ }, [lastManualRefreshAt]) // Filter lines based on scope - match by label since runtime lines can differ from manifest + const disabledSet = new Set(disabledOverviewLabels) const overviewLabels = new Set( skeletonLines - .filter(line => line.scope === "overview") + .filter(line => line.scope === "overview" && !disabledSet.has(line.label)) .map(line => line.label) ) const filteredSkeletonLines = scopeFilter === "all" ? skeletonLines - : skeletonLines.filter(line => line.scope === "overview") + : skeletonLines.filter(line => line.scope === "overview" && !disabledSet.has(line.label)) const filteredLines = scopeFilter === "all" ? lines : lines.filter(line => overviewLabels.has(line.label)) diff --git a/src/hooks/app/use-app-plugin-views.ts b/src/hooks/app/use-app-plugin-views.ts index 3f3c59ea..ff6bada6 100644 --- a/src/hooks/app/use-app-plugin-views.ts +++ b/src/hooks/app/use-app-plugin-views.ts @@ -4,7 +4,7 @@ import type { PluginMeta } from "@/lib/plugin-types" import type { PluginSettings } from "@/lib/settings" import type { PluginState } from "@/hooks/app/types" -export type DisplayPluginState = { meta: PluginMeta } & PluginState +export type DisplayPluginState = { meta: PluginMeta; disabledOverviewLabels: string[] } & PluginState type UseAppPluginViewsArgs = { activeView: ActiveView @@ -33,7 +33,7 @@ export function useAppPluginViews({ if (!meta) return null const state = pluginStates[id] ?? { data: null, loading: false, error: null, lastManualRefreshAt: null } - return { meta, ...state } + return { meta, ...state, disabledOverviewLabels: pluginSettings.disabledOverviewLabels?.[id] || [] } }) .filter((plugin): plugin is DisplayPluginState => Boolean(plugin)) }, [pluginSettings, pluginStates, pluginsMeta]) diff --git a/src/hooks/app/use-settings-plugin-actions.ts b/src/hooks/app/use-settings-plugin-actions.ts index e8ed93dc..572b1283 100644 --- a/src/hooks/app/use-settings-plugin-actions.ts +++ b/src/hooks/app/use-settings-plugin-actions.ts @@ -91,8 +91,32 @@ export function useSettingsPluginActions({ startBatch, ]) + const handleToggleOverviewLabel = useCallback((pluginId: string, label: string) => { + if (!pluginSettings) return + const currentDisabled = pluginSettings.disabledOverviewLabels?.[pluginId] || [] + const isCurrentlyDisabled = currentDisabled.includes(label) + + const nextDisabled = isCurrentlyDisabled + ? currentDisabled.filter((l) => l !== label) + : [...currentDisabled, label] + + const nextSettings: PluginSettings = { + ...pluginSettings, + disabledOverviewLabels: { + ...pluginSettings.disabledOverviewLabels, + [pluginId]: nextDisabled, + }, + } + + setPluginSettings(nextSettings) + scheduleTrayIconUpdate("settings", TRAY_SETTINGS_DEBOUNCE_MS) + void savePluginSettings(nextSettings).catch((error) => { + console.error("Failed to save plugin overview label toggle:", error) + }) + }, [pluginSettings, scheduleTrayIconUpdate, setPluginSettings]) return { handleReorder, handleToggle, + handleToggleOverviewLabel, } } diff --git a/src/hooks/app/use-settings-plugin-list.ts b/src/hooks/app/use-settings-plugin-list.ts index 618c72a0..e2248ccd 100644 --- a/src/hooks/app/use-settings-plugin-list.ts +++ b/src/hooks/app/use-settings-plugin-list.ts @@ -1,11 +1,13 @@ import { useMemo } from "react" -import type { PluginMeta } from "@/lib/plugin-types" +import type { ManifestLine, PluginMeta } from "@/lib/plugin-types" import type { PluginSettings } from "@/lib/settings" export type SettingsPluginState = { id: string name: string enabled: boolean + lines: ManifestLine[] + disabledOverviewLabels: string[] } type UseSettingsPluginListArgs = { @@ -26,6 +28,8 @@ export function useSettingsPluginList({ pluginSettings, pluginsMeta }: UseSettin id, name: meta.name, enabled: !pluginSettings.disabled.includes(id), + lines: meta.lines, + disabledOverviewLabels: pluginSettings.disabledOverviewLabels?.[id] || [], } }) .filter((plugin): plugin is SettingsPluginState => Boolean(plugin)) diff --git a/src/lib/plugin-types.ts b/src/lib/plugin-types.ts index 5a0c031d..a804ae86 100644 --- a/src/lib/plugin-types.ts +++ b/src/lib/plugin-types.ts @@ -53,4 +53,5 @@ export type PluginDisplayState = { loading: boolean error: string | null lastManualRefreshAt: number | null + disabledOverviewLabels?: string[] } diff --git a/src/lib/settings.ts b/src/lib/settings.ts index a94d0a7c..8105c5ce 100644 --- a/src/lib/settings.ts +++ b/src/lib/settings.ts @@ -8,6 +8,7 @@ export const REFRESH_COOLDOWN_MS = 300_000; export type PluginSettings = { order: string[]; disabled: string[]; + disabledOverviewLabels?: Record; }; export type AutoUpdateIntervalMinutes = 5 | 15 | 30 | 60; @@ -83,6 +84,7 @@ const DEFAULT_ENABLED_PLUGINS = new Set(["claude", "codex", "cursor"]); export const DEFAULT_PLUGIN_SETTINGS: PluginSettings = { order: [], disabled: [], + disabledOverviewLabels: {}, }; export async function loadPluginSettings(): Promise { @@ -91,6 +93,7 @@ export async function loadPluginSettings(): Promise { return { order: Array.isArray(stored.order) ? stored.order : [], disabled: Array.isArray(stored.disabled) ? stored.disabled : [], + disabledOverviewLabels: stored.disabledOverviewLabels || {}, }; } @@ -148,7 +151,16 @@ export function normalizePluginSettings( disabled.push(id); } } - return { order, disabled }; + const disabledOverviewLabels: Record = {}; + if (settings.disabledOverviewLabels) { + for (const [id, labels] of Object.entries(settings.disabledOverviewLabels)) { + if (knownSet.has(id) && Array.isArray(labels)) { + disabledOverviewLabels[id] = labels; + } + } + } + + return { order, disabled, disabledOverviewLabels }; } export function arePluginSettingsEqual( @@ -163,6 +175,19 @@ export function arePluginSettingsEqual( for (let i = 0; i < a.disabled.length; i += 1) { if (a.disabled[i] !== b.disabled[i]) return false; } + const aLabels = a.disabledOverviewLabels || {}; + const bLabels = b.disabledOverviewLabels || {}; + const aKeys = Object.keys(aLabels); + const bKeys = Object.keys(bLabels); + if (aKeys.length !== bKeys.length) return false; + for (const key of aKeys) { + const aList = aLabels[key]; + const bList = bLabels[key]; + if (!bList || aList.length !== bList.length) return false; + for (let i = 0; i < aList.length; i += 1) { + if (aList[i] !== bList[i]) return false; + } + } return true; } diff --git a/src/pages/overview.tsx b/src/pages/overview.tsx index 1787f808..0b302014 100644 --- a/src/pages/overview.tsx +++ b/src/pages/overview.tsx @@ -43,6 +43,7 @@ export function OverviewPage({ displayMode={displayMode} resetTimerDisplayMode={resetTimerDisplayMode} onResetTimerDisplayModeToggle={onResetTimerDisplayModeToggle} + disabledOverviewLabels={plugin.disabledOverviewLabels} /> ))} diff --git a/src/pages/settings.tsx b/src/pages/settings.tsx index 1f7abada..febf1fae 100644 --- a/src/pages/settings.tsx +++ b/src/pages/settings.tsx @@ -40,6 +40,8 @@ interface PluginConfig { id: string; name: string; enabled: boolean; + lines: import("@/lib/plugin-types").ManifestLine[]; + disabledOverviewLabels: string[]; } const TRAY_PREVIEW_SIZE_PX = getTrayIconSizePx(1); @@ -196,9 +198,11 @@ function MenubarIconStylePreview({ function SortablePluginItem({ plugin, onToggle, + onToggleOverviewLabel, }: { plugin: PluginConfig; onToggle: (id: string) => void; + onToggleOverviewLabel: (pluginId: string, label: string) => void; }) { const { attributes, @@ -208,45 +212,61 @@ function SortablePluginItem({ transition, isDragging, } = useSortable({ id: plugin.id }); - const style = { transform: CSS.Transform.toString(transform), transition, }; + const overviewLines = plugin.lines ? plugin.lines.filter((l) => l.scope === "overview") : []; return (
- - - - {plugin.name} - - - onToggle(plugin.id)} - /> + + {plugin.name} + + onToggle(plugin.id)} + /> +
+ {plugin.enabled && overviewLines.length > 0 && ( +
+ {overviewLines.map((line) => { + const isLineEnabled = !plugin.disabledOverviewLabels.includes(line.label); + return ( + + ); + })} +
+ )} ); } @@ -255,6 +275,7 @@ interface SettingsPageProps { plugins: PluginConfig[]; onReorder: (orderedIds: string[]) => void; onToggle: (id: string) => void; + onToggleOverviewLabel: (pluginId: string, label: string) => void; autoUpdateInterval: AutoUpdateIntervalMinutes; onAutoUpdateIntervalChange: (value: AutoUpdateIntervalMinutes) => void; themeMode: ThemeMode; @@ -276,6 +297,7 @@ export function SettingsPage({ plugins, onReorder, onToggle, + onToggleOverviewLabel, autoUpdateInterval, onAutoUpdateIntervalChange, themeMode, @@ -504,6 +526,7 @@ export function SettingsPage({ key={plugin.id} plugin={plugin} onToggle={onToggle} + onToggleOverviewLabel={onToggleOverviewLabel} /> ))}