From 6b6844d8b2ad1fd7e6e22c6c5edbf08ad47fcb87 Mon Sep 17 00:00:00 2001 From: Jinjing <6427696+AmethystLiang@users.noreply.github.com> Date: Sat, 11 Jul 2026 14:30:55 -0700 Subject: [PATCH] Add settings deep-link to expand Appearance's Window accordion for one-s - Replaces the searchQuery-based redirect (fragile, flashed filter UI) with a dedicated appearanceAccordionDeepLink store field that force-opens the correct accordion and scrolls to the target row - Rebuilds the status-bar change notice as a plain elevated card instead of a Popover, since PopoverContent's glass/backdrop-filter defaults fought the opaque callout styling and needed heavy overrides - Simplifies the light/dark card CSS tokens accordingly --- src/renderer/src/assets/main.css | 106 +++++------ .../settings/AppearancePane.test.tsx | 4 +- .../components/settings/AppearancePane.tsx | 27 ++- .../src/components/settings/Settings.tsx | 9 + ...appearance-usage-percentage-search.test.ts | 16 ++ .../appearance-usage-percentage-search.ts | 16 ++ ...sagePercentageDisplayChangeNotice.test.tsx | 55 ++---- .../UsagePercentageDisplayChangeNotice.tsx | 167 +++++++++--------- src/renderer/src/store/slices/ui.ts | 13 ++ 9 files changed, 223 insertions(+), 190 deletions(-) create mode 100644 src/renderer/src/components/settings/appearance-usage-percentage-search.test.ts diff --git a/src/renderer/src/assets/main.css b/src/renderer/src/assets/main.css index 61612559626..758b44bb43a 100644 --- a/src/renderer/src/assets/main.css +++ b/src/renderer/src/assets/main.css @@ -1209,38 +1209,52 @@ background: color-mix(in srgb, var(--worktree-sidebar-foreground) 14%, var(--worktree-sidebar)); } -/* Why: status-bar usage-display change callout sits over same-luminance - terminal/titlebar chrome. Light and dark use separate opaque palettes (not - translucent glass) so the surface behind cannot bleed through. */ -.status-bar-change-notice-popover { +/* Why: one-shot status-bar callout sits over same-luminance titlebar/terminal + chrome. Opaque elevated surfaces (not glass) so the surface cannot bleed + through; light and dark use separate token mixes. */ +.status-bar-change-notice-card { position: relative; - overflow: visible; - z-index: 60; - /* Why: kill default PopoverContent glass; blur + alpha re-darkens the fill. */ - backdrop-filter: none !important; - -webkit-backdrop-filter: none !important; + border: 1px solid color-mix(in srgb, var(--foreground) 16%, var(--border)); + background: var(--card); + color: var(--card-foreground); + box-shadow: + 0 0 0 1px color-mix(in srgb, var(--foreground) 6%, transparent), + 0 14px 36px color-mix(in srgb, var(--foreground) 14%, transparent), + 0 2px 8px color-mix(in srgb, var(--foreground) 8%, transparent); } -/* ── Light mode ──────────────────────────────────────── - Paper card over light chrome (#fff / #f5f5f5). White top + soft cool gray - bottom so it lifts off the titlebar without looking like a hole. */ -:root:not(.dark) .status-bar-change-notice-popover, -:root:not(.dark) .status-bar-change-notice-popover[data-slot='popover-content'] { - background: linear-gradient(165deg, #ffffff 0%, #f7f7f8 52%, #eef0f3 100%) !important; - border-color: #c8cdd6 !important; - color: #0a0a0a !important; +/* Light: pure paper above light chrome — lift with a cool edge, not gray fill. */ +:root:not(.dark) .status-bar-change-notice-card { + background: #ffffff; + border-color: #c4c9d2; + color: #0a0a0a; box-shadow: 0 0 0 1px rgb(15 23 42 / 0.06), - 0 18px 40px rgb(15 23 42 / 0.14), - 0 4px 12px rgb(15 23 42 / 0.08), - inset 0 1px 0 #ffffff !important; + 0 16px 40px rgb(15 23 42 / 0.16), + 0 3px 10px rgb(15 23 42 / 0.08); } -:root:not(.dark) .status-bar-change-notice-popover .text-muted-foreground { - color: #52525b !important; +:root:not(.dark) .status-bar-change-notice-card .text-muted-foreground { + color: #52525b; } -:root:not(.dark) .status-bar-change-notice-popover::after { +/* Dark: lifted charcoal above #0a0a0a / #171717 — not translucent glass. */ +.dark .status-bar-change-notice-card { + background: #2a2a2e; + border-color: color-mix(in srgb, #ffffff 26%, transparent); + color: #fafafa; + box-shadow: + 0 0 0 1px color-mix(in srgb, #ffffff 12%, transparent), + 0 18px 44px rgb(0 0 0 / 0.62), + 0 3px 12px rgb(0 0 0 / 0.4); +} + +.dark .status-bar-change-notice-card .text-muted-foreground { + color: color-mix(in srgb, #fafafa 68%, transparent); +} + +/* Why: caret points down at the usage meters this notice explains. */ +.status-bar-change-notice-card::after { content: ''; position: absolute; left: 1.25rem; @@ -1248,47 +1262,17 @@ width: 12px; height: 12px; transform: rotate(45deg); - border-right: 1px solid #c8cdd6; - border-bottom: 1px solid #c8cdd6; - background: #eef0f3; + border-right: 1px solid #c4c9d2; + border-bottom: 1px solid #c4c9d2; + background: #ffffff; box-shadow: 2px 2px 5px rgb(15 23 42 / 0.1); } -/* ── Dark mode ───────────────────────────────────────── - Lifted charcoal over near-black terminal/titlebar (#0a0a0a / #171717). */ -.dark .status-bar-change-notice-popover, -.dark .status-bar-change-notice-popover[data-slot='popover-content'] { - background: linear-gradient( - 165deg, - color-mix(in srgb, #ffffff 18%, #2a2a2e) 0%, - #252528 48%, - #1a1a1d 100% - ) !important; - border-color: color-mix(in srgb, #ffffff 28%, transparent) !important; - color: #fafafa !important; - box-shadow: - 0 0 0 1px color-mix(in srgb, #ffffff 14%, transparent), - 0 20px 48px rgb(0 0 0 / 0.65), - 0 4px 14px rgb(0 0 0 / 0.45), - inset 0 1px 0 color-mix(in srgb, #ffffff 16%, transparent) !important; -} - -.dark .status-bar-change-notice-popover .text-muted-foreground { - color: color-mix(in srgb, #fafafa 68%, transparent) !important; -} - -.dark .status-bar-change-notice-popover::after { - content: ''; - position: absolute; - left: 1.25rem; - bottom: -6px; - width: 12px; - height: 12px; - transform: rotate(45deg); - border-right: 1px solid color-mix(in srgb, #ffffff 28%, transparent); - border-bottom: 1px solid color-mix(in srgb, #ffffff 28%, transparent); - background: #1a1a1d; - box-shadow: 2px 2px 8px rgb(0 0 0 / 0.5); +.dark .status-bar-change-notice-card::after { + border-right-color: color-mix(in srgb, #ffffff 26%, transparent); + border-bottom-color: color-mix(in srgb, #ffffff 26%, transparent); + background: #2a2a2e; + box-shadow: 2px 2px 8px rgb(0 0 0 / 0.45); } /* Why: the first-run Mobile Emulator intro sits directly under that menu row; diff --git a/src/renderer/src/components/settings/AppearancePane.test.tsx b/src/renderer/src/components/settings/AppearancePane.test.tsx index dc5a4abd1aa..ca68ff7e47c 100644 --- a/src/renderer/src/components/settings/AppearancePane.test.tsx +++ b/src/renderer/src/components/settings/AppearancePane.test.tsx @@ -23,7 +23,9 @@ const mocks = vi.hoisted(() => ({ usagePercentageDisplay: 'used' as 'used' | 'remaining', setUsagePercentageDisplay: vi.fn(), recordFeatureInteraction: vi.fn(), - setWorktreeCardMode: vi.fn() + setWorktreeCardMode: vi.fn(), + appearanceAccordionDeepLink: null as 'interface' | 'terminal' | 'window' | null, + clearAppearanceAccordionDeepLink: vi.fn() } })) diff --git a/src/renderer/src/components/settings/AppearancePane.tsx b/src/renderer/src/components/settings/AppearancePane.tsx index 92792621533..631059d6d3a 100644 --- a/src/renderer/src/components/settings/AppearancePane.tsx +++ b/src/renderer/src/components/settings/AppearancePane.tsx @@ -1,5 +1,5 @@ import type React from 'react' -import { useState } from 'react' +import { useLayoutEffect, useState } from 'react' import { AppWindow, PanelLeft, TerminalSquare } from 'lucide-react' import type { GlobalSettings } from '../../../../shared/types' @@ -10,6 +10,7 @@ import { AppearanceWindowSidebarSection } from './AppearanceWindowSidebarSection import { SearchableSetting } from './SearchableSetting' import { matchesSettingsSearch, normalizeSettingsSearchQuery } from './settings-search' import { useAppStore } from '../../store' +import { USAGE_PERCENTAGE_DISPLAY_SETTING_ID } from './appearance-usage-percentage-search' import { getAppIconEntries, getAppearancePaneSearchEntries, @@ -75,6 +76,10 @@ export function AppearancePane({ warpThemes }: AppearancePaneProps): React.JSX.Element { const searchQuery = useAppStore((state) => state.settingsSearchQuery) + const appearanceAccordionDeepLink = useAppStore((state) => state.appearanceAccordionDeepLink) + const clearAppearanceAccordionDeepLink = useAppStore( + (state) => state.clearAppearanceAccordionDeepLink + ) const isSearching = normalizeSettingsSearchQuery(searchQuery).length > 0 const isWebClient = isWebClientLocation() // Why: the system tray behavior is desktop-Electron Windows-only; a Windows @@ -84,6 +89,26 @@ export function AppearancePane({ const [manuallyOpenSection, setManuallyOpenSection] = useState( 'interface' ) + + // Why: nested deep links (e.g. Usage percentages) land under Window & Sidebar; + // expand that accordion before Settings scrolls so the row is actually visible. + useLayoutEffect(() => { + if (!appearanceAccordionDeepLink) { + return + } + setManuallyOpenSection(appearanceAccordionDeepLink) + clearAppearanceAccordionDeepLink() + // Why: accordion expand is layout-synchronous; scroll on the next frame so + // the target has non-zero height when Settings (or this fallback) scrolls. + const frameId = requestAnimationFrame(() => { + document + .getElementById(USAGE_PERCENTAGE_DISPLAY_SETTING_ID) + ?.scrollIntoView({ block: 'nearest' }) + }) + return () => { + cancelAnimationFrame(frameId) + } + }, [appearanceAccordionDeepLink, clearAppearanceAccordionDeepLink]) const interfaceTitle = translate( 'auto.components.settings.AppearancePane.interfaceTitle', 'Interface' diff --git a/src/renderer/src/components/settings/Settings.tsx b/src/renderer/src/components/settings/Settings.tsx index 50dee71a8f1..2d5a941226c 100644 --- a/src/renderer/src/components/settings/Settings.tsx +++ b/src/renderer/src/components/settings/Settings.tsx @@ -65,6 +65,7 @@ import { SettingsSidebar } from './SettingsSidebar' import { SettingsSetupGuidePane } from './SettingsSetupGuidePane' import { ActiveSettingsSectionProvider, SettingsSection } from './SettingsSection' import { getSettingsSectionSearchEntries, rankSettingsSearchItems } from './settings-search' +import { resolveAppearanceAccordionDeepLink } from './appearance-usage-percentage-search' import { cn } from '@/lib/utils' import { isIntentionalAppRestartInProgress } from '@/lib/updater-beforeunload' import { registerWindowCloseGuard } from '../window-close-request-coordinator' @@ -618,6 +619,14 @@ function Settings(): React.JSX.Element { ) pendingNavSectionRef.current = paneSectionId pendingScrollTargetRef.current = settingsNavigationTarget.sectionId ?? paneSectionId + // Why: Appearance nests status-bar controls under a collapsed accordion; + // force that accordion open before scrolling so the row is actually visible. + if (settingsNavigationTarget.pane === 'appearance') { + const accordion = resolveAppearanceAccordionDeepLink(settingsNavigationTarget.sectionId) + if (accordion) { + useAppStore.getState().setAppearanceAccordionDeepLink(accordion) + } + } if (settingsNavigationTarget.intent === 'add-quick-command') { setQuickCommandAddIntentSignal((signal) => signal + 1) } diff --git a/src/renderer/src/components/settings/appearance-usage-percentage-search.test.ts b/src/renderer/src/components/settings/appearance-usage-percentage-search.test.ts new file mode 100644 index 00000000000..fdb30dd8d80 --- /dev/null +++ b/src/renderer/src/components/settings/appearance-usage-percentage-search.test.ts @@ -0,0 +1,16 @@ +import { describe, expect, it } from 'vitest' +import { + resolveAppearanceAccordionDeepLink, + USAGE_PERCENTAGE_DISPLAY_SETTING_ID +} from './appearance-usage-percentage-search' + +describe('resolveAppearanceAccordionDeepLink', () => { + it('maps the usage percentage row to the Window accordion', () => { + expect(resolveAppearanceAccordionDeepLink(USAGE_PERCENTAGE_DISPLAY_SETTING_ID)).toBe('window') + }) + + it('returns null for unknown or missing section ids', () => { + expect(resolveAppearanceAccordionDeepLink(undefined)).toBeNull() + expect(resolveAppearanceAccordionDeepLink('something-else')).toBeNull() + }) +}) diff --git a/src/renderer/src/components/settings/appearance-usage-percentage-search.ts b/src/renderer/src/components/settings/appearance-usage-percentage-search.ts index e02e476bed6..b7bbc49218b 100644 --- a/src/renderer/src/components/settings/appearance-usage-percentage-search.ts +++ b/src/renderer/src/components/settings/appearance-usage-percentage-search.ts @@ -5,6 +5,22 @@ import { translateSearchKeyword } from './settings-search-keywords' /** Stable Settings deep-link / scroll target for the Used/Remaining control. */ export const USAGE_PERCENTAGE_DISPLAY_SETTING_ID = 'usage-percentage-display' +/** Appearance accordion keys that can be force-opened for nested deep links. */ +export type AppearanceAccordionSection = 'interface' | 'terminal' | 'window' + +/** + * Map a Settings subsection id to the Appearance accordion that must be open + * before the row is visible (collapsed accordion content is not user-visible). + */ +export function resolveAppearanceAccordionDeepLink( + sectionId: string | undefined +): AppearanceAccordionSection | null { + if (sectionId === USAGE_PERCENTAGE_DISPLAY_SETTING_ID) { + return 'window' + } + return null +} + export const getUsagePercentageDisplayEntry = createLocalizedCatalog(() => ({ title: translate( 'auto.components.settings.appearance.search.usagePercentageDisplayTitle', diff --git a/src/renderer/src/components/status-bar/UsagePercentageDisplayChangeNotice.test.tsx b/src/renderer/src/components/status-bar/UsagePercentageDisplayChangeNotice.test.tsx index 4fd218cb4e2..2ffd706cd35 100644 --- a/src/renderer/src/components/status-bar/UsagePercentageDisplayChangeNotice.test.tsx +++ b/src/renderer/src/components/status-bar/UsagePercentageDisplayChangeNotice.test.tsx @@ -1,9 +1,10 @@ // @vitest-environment happy-dom -import { act, createContext, useContext, type ReactNode } from 'react' +import { act } from 'react' import { createRoot, type Root } from 'react-dom/client' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import { UsagePercentageDisplayChangeNotice } from './UsagePercentageDisplayChangeNotice' +import { USAGE_PERCENTAGE_DISPLAY_SETTING_ID } from '../settings/appearance-usage-percentage-search' const storeState = { persistedUIReady: true, @@ -11,7 +12,6 @@ const storeState = { dismissUsagePercentageDisplayChangeNotice: vi.fn(), statusBarVisible: true, activeModal: 'none' as string, - setSettingsSearchQuery: vi.fn(), openSettingsTarget: vi.fn(), openSettingsPage: vi.fn() } @@ -25,29 +25,6 @@ vi.mock('@/store', () => ({ ) })) -vi.mock('@/components/ui/popover', () => { - const OpenContext = createContext(false) - return { - Popover: ({ open, children }: { open?: boolean; children: ReactNode }) => ( - -
- {children} -
-
- ), - PopoverAnchor: ({ children }: { children: ReactNode }) => ( -
{children}
- ), - PopoverContent: ({ children }: { children: ReactNode }) => { - const open = useContext(OpenContext) - if (!open) { - return null - } - return
{children}
- } - } -}) - describe('UsagePercentageDisplayChangeNotice', () => { let container: HTMLDivElement let root: Root @@ -59,6 +36,8 @@ describe('UsagePercentageDisplayChangeNotice', () => { storeState.statusBarVisible = true storeState.activeModal = 'none' storeState.dismissUsagePercentageDisplayChangeNotice = vi.fn() + storeState.openSettingsPage = vi.fn() + storeState.openSettingsTarget = vi.fn() container = document.createElement('div') document.body.appendChild(container) root = createRoot(container) @@ -72,7 +51,7 @@ describe('UsagePercentageDisplayChangeNotice', () => { vi.useRealTimers() }) - it('opens the callout next to usage meters after a short delay when eligible', () => { + it('shows the callout above usage meters after a short delay when eligible', () => { act(() => { root.render( @@ -81,11 +60,11 @@ describe('UsagePercentageDisplayChangeNotice', () => { ) }) - expect(container.querySelector('[data-open="true"]')).toBeNull() + expect(container.querySelector('.status-bar-change-notice-card')).toBeNull() act(() => { vi.advanceTimersByTime(1_800) }) - expect(container.querySelector('[data-open="true"]')).not.toBeNull() + expect(container.querySelector('.status-bar-change-notice-card')).not.toBeNull() expect(container.textContent).toContain('usage-meters') expect(container.textContent).toContain('Usage now shows % used') expect(container.textContent).toContain('Prefer remaining? Change it in Settings.') @@ -102,8 +81,7 @@ describe('UsagePercentageDisplayChangeNotice', () => { act(() => { vi.advanceTimersByTime(2_000) }) - expect(container.querySelector('[data-open="true"]')).toBeNull() - expect(container.textContent).not.toContain('Usage now shows % used') + expect(container.querySelector('.status-bar-change-notice-card')).toBeNull() }) it('does not open when the notice was already dismissed', () => { @@ -118,8 +96,7 @@ describe('UsagePercentageDisplayChangeNotice', () => { act(() => { vi.advanceTimersByTime(2_000) }) - expect(container.querySelector('[data-open="true"]')).toBeNull() - expect(container.textContent).not.toContain('Usage now shows % used') + expect(container.querySelector('.status-bar-change-notice-card')).toBeNull() }) it('does not open while another modal is open', () => { @@ -134,10 +111,10 @@ describe('UsagePercentageDisplayChangeNotice', () => { act(() => { vi.advanceTimersByTime(2_000) }) - expect(container.querySelector('[data-open="true"]')).toBeNull() + expect(container.querySelector('.status-bar-change-notice-card')).toBeNull() }) - it('opens Appearance with a Usage percentages filter after wiping prior search', () => { + it('deep-links to the Usage percentages setting without a search filter', () => { const callOrder: string[] = [] storeState.openSettingsPage = vi.fn(() => { callOrder.push('openSettingsPage') @@ -145,9 +122,6 @@ describe('UsagePercentageDisplayChangeNotice', () => { storeState.openSettingsTarget = vi.fn(() => { callOrder.push('openSettingsTarget') }) - storeState.setSettingsSearchQuery = vi.fn(() => { - callOrder.push('setSettingsSearchQuery') - }) storeState.dismissUsagePercentageDisplayChangeNotice = vi.fn() act(() => { @@ -169,13 +143,12 @@ describe('UsagePercentageDisplayChangeNotice', () => { openSettingsButton?.dispatchEvent(new MouseEvent('click', { bubbles: true })) }) - // Why: openSettingsPage clears search first; the filter must be applied after. - expect(callOrder).toEqual(['openSettingsPage', 'openSettingsTarget', 'setSettingsSearchQuery']) + expect(callOrder).toEqual(['openSettingsPage', 'openSettingsTarget']) expect(storeState.openSettingsTarget).toHaveBeenCalledWith({ pane: 'appearance', - repoId: null + repoId: null, + sectionId: USAGE_PERCENTAGE_DISPLAY_SETTING_ID }) - expect(storeState.setSettingsSearchQuery).toHaveBeenCalledWith('Usage percentages') expect(storeState.dismissUsagePercentageDisplayChangeNotice).toHaveBeenCalled() }) }) diff --git a/src/renderer/src/components/status-bar/UsagePercentageDisplayChangeNotice.tsx b/src/renderer/src/components/status-bar/UsagePercentageDisplayChangeNotice.tsx index 5023759f709..59413599b30 100644 --- a/src/renderer/src/components/status-bar/UsagePercentageDisplayChangeNotice.tsx +++ b/src/renderer/src/components/status-bar/UsagePercentageDisplayChangeNotice.tsx @@ -1,32 +1,29 @@ import { useEffect, useState, type ReactNode } from 'react' import { BarChart3, X } from 'lucide-react' import { Button } from '@/components/ui/button' -import { Popover, PopoverAnchor, PopoverContent } from '@/components/ui/popover' import { translate } from '@/i18n/i18n' import { useAppStore } from '@/store' import { shouldShowUsagePercentageDisplayChangeNotice } from '../../../../shared/usage-percentage-display-change-notice' +import { USAGE_PERCENTAGE_DISPLAY_SETTING_ID } from '../settings/appearance-usage-percentage-search' // Why: let startup modals settle before the status-bar callout competes for focus. const SHOW_DELAY_MS = 1_800 function openUsagePercentageSettings(): void { const store = useAppStore.getState() - // Why: openSettingsPage always wipes the search filter first — set the - // Appearance target and filter after that wipe. The search string force-opens - // Appearance → Window & Sidebar and surfaces the Usage percentages control. + // Why: openSettingsPage wipes any leftover search; do not re-apply a search + // filter — deep-link to the stable row id and let Appearance expand Window. store.openSettingsPage() - store.openSettingsTarget({ pane: 'appearance', repoId: null }) - store.setSettingsSearchQuery( - translate( - 'auto.components.settings.appearance.search.usagePercentageDisplayTitle', - 'Usage percentages' - ) - ) + store.openSettingsTarget({ + pane: 'appearance', + repoId: null, + sectionId: USAGE_PERCENTAGE_DISPLAY_SETTING_ID + }) } /** - * Anchors a one-time high-contrast callout above the status-bar usage meters - * after the default flipped from remaining → used. Permanent dismiss only. + * One-time elevated callout anchored above the status-bar usage meters after + * the default flipped from remaining → used. Permanent dismiss only. */ export function UsagePercentageDisplayChangeNotice({ children, @@ -67,89 +64,87 @@ export function UsagePercentageDisplayChangeNotice({ const open = eligible && delayElapsed + useEffect(() => { + if (!open) { + return + } + const onKeyDown = (event: KeyboardEvent): void => { + if (event.key === 'Escape') { + event.preventDefault() + dismiss() + } + } + window.addEventListener('keydown', onKeyDown) + return () => { + window.removeEventListener('keydown', onKeyDown) + } + }, [dismiss, open]) + const openSettings = (): void => { dismiss() openUsagePercentageSettings() } return ( - - -
{children}
-
- { - event.preventDefault() - }} - onOpenAutoFocus={(event) => { - // Why: keep focus in the terminal/workspace; this is a soft callout, - // not a modal that should steal keyboard context. - event.preventDefault() - }} - onEscapeKeyDown={(event) => { - event.preventDefault() - dismiss() - }} - // Why: reuse elevated glass popover chrome (border + blur + shadow) and - // layer status-bar-change-notice so the caret + contrast boost still apply. - className="status-bar-change-notice-popover w-[340px] max-w-[calc(100vw-24px)] overflow-visible p-3.5" - > -
-
-
- -
- {translate( - 'auto.components.status.bar.UsagePercentageDisplayChangeNotice.title', - 'Usage now shows % used' - )} +
+ {children} + {open ? ( +
+
+
+
+ +
+ {translate( + 'auto.components.status.bar.UsagePercentageDisplayChangeNotice.title', + 'Usage now shows % used' + )} +
+

+ {translate( + 'auto.components.status.bar.UsagePercentageDisplayChangeNotice.body', + 'Prefer remaining? Change it in Settings.' + )} +

-

- {translate( - 'auto.components.status.bar.UsagePercentageDisplayChangeNotice.body', - 'Prefer remaining? Change it in Settings.' + +

+
+ +
-
-
- - -
- - + ) : null} +
) } diff --git a/src/renderer/src/store/slices/ui.ts b/src/renderer/src/store/slices/ui.ts index d9f4496a4e7..bcc32db82f6 100644 --- a/src/renderer/src/store/slices/ui.ts +++ b/src/renderer/src/store/slices/ui.ts @@ -747,6 +747,16 @@ export type UISlice = { } | null openSettingsTarget: (target: NonNullable) => void clearSettingsTarget: () => void + /** + * One-shot Appearance accordion to expand for nested Settings deep links + * (e.g. Usage percentages lives under Window & Sidebar). Cleared when + * Appearance consumes it. + */ + appearanceAccordionDeepLink: 'interface' | 'terminal' | 'window' | null + setAppearanceAccordionDeepLink: ( + section: NonNullable + ) => void + clearAppearanceAccordionDeepLink: () => void activeModal: | 'none' | 'create-worktree' @@ -1499,6 +1509,9 @@ export const createUISlice: StateCreator = (set, get) settingsNavigationTarget: null, openSettingsTarget: (target) => set({ settingsNavigationTarget: target }), clearSettingsTarget: () => set({ settingsNavigationTarget: null }), + appearanceAccordionDeepLink: null, + setAppearanceAccordionDeepLink: (section) => set({ appearanceAccordionDeepLink: section }), + clearAppearanceAccordionDeepLink: () => set({ appearanceAccordionDeepLink: null }), activeModal: 'none', modalData: {},