From f5f4281d06a25163cb7fb451fad266765497843c Mon Sep 17 00:00:00 2001 From: Jason Date: Thu, 30 Jul 2026 08:52:48 +0800 Subject: [PATCH] refactor(ui): always show icon-only app switcher, drop auto-compact The toolbar app switcher used a ResizeObserver-based overflow detection (useAutoCompact) to collapse app labels when space ran out. With the number of managed apps growing, the labels are collapsed in practice anyway, so remove the mechanism and render icons only. Buttons now carry title/aria-label so app names remain discoverable via tooltip and accessible to screen readers. --- src/App.tsx | 10 +------ src/components/AppSwitcher.tsx | 14 ++------- src/hooks/useAutoCompact.ts | 52 ---------------------------------- 3 files changed, 3 insertions(+), 73 deletions(-) delete mode 100644 src/hooks/useAutoCompact.ts diff --git a/src/App.tsx b/src/App.tsx index 1ac823033..227deae61 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -42,7 +42,6 @@ import { openclawKeys, useOpenClawHealth } from "@/hooks/useOpenClaw"; import { hermesKeys, useOpenHermesWebUI } from "@/hooks/useHermes"; import { hermesApi } from "@/lib/api/hermes"; import { useProxyStatus } from "@/hooks/useProxyStatus"; -import { useAutoCompact } from "@/hooks/useAutoCompact"; import { useUsageCacheBridge } from "@/hooks/useUsageCacheBridge"; import { useTauriEvent } from "@/hooks/useTauriEvent"; import { useLastValidValue } from "@/hooks/useLastValidValue"; @@ -247,9 +246,6 @@ function App() { const effectiveEditingProvider = useLastValidValue(editingProvider); const effectiveUsageProvider = useLastValidValue(usageProvider); - const toolbarRef = useRef(null); - const isToolbarCompact = useAutoCompact(toolbarRef); - useUsageCacheBridge(); const promptPanelRef = useRef(null); @@ -1277,10 +1273,7 @@ function App() { )} -
+
diff --git a/src/components/AppSwitcher.tsx b/src/components/AppSwitcher.tsx index 06bc5368b..bff3a2054 100644 --- a/src/components/AppSwitcher.tsx +++ b/src/components/AppSwitcher.tsx @@ -15,7 +15,6 @@ interface AppSwitcherProps { activeApp: AppId; onSwitch: (app: AppId) => void; visibleApps?: VisibleApps; - compact?: boolean; } const ALL_APPS: AppId[] = [ @@ -34,7 +33,6 @@ export function AppSwitcher({ activeApp, onSwitch, visibleApps, - compact, }: AppSwitcherProps) { const handleSwitch = (app: AppId) => { if (app === activeApp) return; @@ -80,6 +78,8 @@ export function AppSwitcher({ key={app} type="button" onClick={() => handleSwitch(app)} + title={appDisplayName[app]} + aria-label={appDisplayName[app]} className={cn( "group inline-flex items-center px-3 h-8 rounded-md text-sm font-medium transition-all duration-200", isActive @@ -115,16 +115,6 @@ export function AppSwitcher({ )} - - {appDisplayName[app]} - ); })} diff --git a/src/hooks/useAutoCompact.ts b/src/hooks/useAutoCompact.ts deleted file mode 100644 index b5b7bca39..000000000 --- a/src/hooks/useAutoCompact.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { useEffect, useRef, useState, type RefObject } from "react"; - -/** - * Detects whether the container's children overflow the available width - * and returns a `compact` flag for the AppSwitcher. - * - * Uses ResizeObserver on a flex-constrained container. The container - * must have `flex-1 min-w-0 overflow-hidden` so its width is determined - * by the parent layout, not its own content — avoiding the oscillation - * problem when toggling compact mode. - */ -export function useAutoCompact( - containerRef: RefObject, -): boolean { - const [compact, setCompact] = useState(false); - const normalWidthRef = useRef(0); - const lockUntilRef = useRef(0); - - useEffect(() => { - const el = containerRef.current; - if (!el) return; - - const ro = new ResizeObserver(() => { - // During expand animation, ignore resize events to prevent flicker - if (Date.now() < lockUntilRef.current) return; - - if (!compact) { - // Overflow detected → switch to compact - if (el.scrollWidth > el.clientWidth + 1) { - // Cache only at the overflow edge: when content fits, - // scrollWidth === clientWidth (DOM spec), so caching unconditionally - // would pollute normalWidthRef with the container width (e.g. after - // maximizing), making the expand threshold unreachable. - normalWidthRef.current = el.scrollWidth; - setCompact(true); - } - } else if (normalWidthRef.current > 0) { - // In compact mode: only recover to normal if - // available space >= what normal mode needed - if (el.clientWidth >= normalWidthRef.current) { - // Lock out resize events during the expand animation (200ms + 50ms margin) - lockUntilRef.current = Date.now() + 250; - setCompact(false); - } - } - }); - ro.observe(el); - return () => ro.disconnect(); - }, [compact]); - - return compact; -}