From e49897c3c4f27ede03dcabfc182fc6438adc061d Mon Sep 17 00:00:00 2001 From: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> Date: Thu, 13 Aug 2026 14:40:18 -0700 Subject: [PATCH] Clarify inherited worktree visibility settings --- .../sidebar/WorktreeVisibilityDialog.test.tsx | 15 ++++++ .../sidebar/WorktreeVisibilityDialog.tsx | 7 ++- .../WorktreeVisibilityGlobalSettingsLink.tsx | 24 ++++++++- ...ktree-visibility-source-provenance.test.ts | 49 +++++++++++++++++++ .../worktree-visibility-source-provenance.ts | 32 ++++++++++++ 5 files changed, 125 insertions(+), 2 deletions(-) create mode 100644 src/renderer/src/components/sidebar/worktree-visibility-source-provenance.test.ts diff --git a/src/renderer/src/components/sidebar/WorktreeVisibilityDialog.test.tsx b/src/renderer/src/components/sidebar/WorktreeVisibilityDialog.test.tsx index f94e9bc0053..c7edaefee8e 100644 --- a/src/renderer/src/components/sidebar/WorktreeVisibilityDialog.test.tsx +++ b/src/renderer/src/components/sidebar/WorktreeVisibilityDialog.test.tsx @@ -634,6 +634,21 @@ describe('WorktreeVisibilityDialog', () => { expect(sourceRow('GSD').textContent).toContain('Using global: Hidden') expect(sourceRow('Other locations').textContent).toContain('Using global: Hidden') expect(document.querySelector('button[aria-label^="Use global for"]')).toBeNull() + expect(document.body.textContent).toContain( + 'Sources enabled in Global Settings also apply to this project.' + ) + }) + + it('does not show the global impact notice when inherited sources are hidden', async () => { + mocks.state.repos = [makeRepo({ externalWorktreeVisibility: undefined })] + mocks.state.settings = { worktreeVisibilityDefaults: { external: 'hide' } } + + await renderDialog() + + expect(document.body.textContent).not.toContain( + 'Sources enabled in Global Settings also apply to this project.' + ) + expect(buttonWithText('Manage in Global Settings')).not.toBeNull() }) it('labels project overrides and resets a built-in source to global inheritance', async () => { diff --git a/src/renderer/src/components/sidebar/WorktreeVisibilityDialog.tsx b/src/renderer/src/components/sidebar/WorktreeVisibilityDialog.tsx index 0ad982b20eb..6f9110d9df2 100644 --- a/src/renderer/src/components/sidebar/WorktreeVisibilityDialog.tsx +++ b/src/renderer/src/components/sidebar/WorktreeVisibilityDialog.tsx @@ -57,6 +57,7 @@ import { import { WorktreeVisibilityGlobalSettingsLink } from './WorktreeVisibilityGlobalSettingsLink' import { WorktreeVisibilityScanStatus } from './WorktreeVisibilityScanStatus' import { createWorktreeVisibilityUseGlobalMutation } from './worktree-visibility-use-global' +import { hasGloballyShownWorktreeVisibilitySource } from './worktree-visibility-source-provenance' export default function WorktreeVisibilityDialog(): React.JSX.Element | null { const activeModal = useAppStore((s) => s.activeModal) @@ -91,6 +92,10 @@ export default function WorktreeVisibilityDialog(): React.JSX.Element | null { const effectivelyToggling = isToggling || activeMutation?.kind === 'toggle' const visibilityDefaults = useRepoOwnerVisibilityDefaults(repo) const removableSourceIds = useMemo(() => getRepoCustomWorktreeVisibilitySourceIds(repo), [repo]) + const hasGloballyShownSource = useMemo( + () => (repo ? hasGloballyShownWorktreeVisibilitySource(repo, visibilityDefaults) : false), + [repo, visibilityDefaults] + ) useLayoutEffect(() => { currentMutationScopeRef.current = mutationScope @@ -388,7 +393,7 @@ export default function WorktreeVisibilityDialog(): React.JSX.Element | null { onUseDefault={handleUseDefault} /> - + ) } + +export function WorktreeVisibilityGlobalSettingsLink({ + hasGloballyShownSource +}: { + hasGloballyShownSource: boolean +}): React.JSX.Element { + if (!hasGloballyShownSource) { + return + } + + return ( +
+

+ {translate( + 'auto.components.sidebar.WorktreeVisibilityDialog.globalSettingsApply', + 'Sources enabled in Global Settings also apply to this project.' + )} +

+ +
+ ) +} diff --git a/src/renderer/src/components/sidebar/worktree-visibility-source-provenance.test.ts b/src/renderer/src/components/sidebar/worktree-visibility-source-provenance.test.ts new file mode 100644 index 00000000000..498eb279b37 --- /dev/null +++ b/src/renderer/src/components/sidebar/worktree-visibility-source-provenance.test.ts @@ -0,0 +1,49 @@ +import { describe, expect, it } from 'vitest' +import type { Repo, WorktreeVisibilityDefaults } from '../../../../shared/types' +import { hasGloballyShownWorktreeVisibilitySource } from './worktree-visibility-source-provenance' + +function repo(overrides: Partial = {}): Repo { + return { + id: 'repo-1', + path: '/repo', + displayName: 'orca', + badgeColor: '#000000', + addedAt: 0, + externalWorktreeVisibility: undefined, + ...overrides + } +} + +describe('hasGloballyShownWorktreeVisibilitySource', () => { + const shownClaude: WorktreeVisibilityDefaults = { + external: 'hide', + sourcePreferences: { builtIn: { claude: 'show' } } + } + + it('detects an inherited source enabled in global settings', () => { + expect(hasGloballyShownWorktreeVisibilitySource(repo(), shownClaude)).toBe(true) + }) + + it('ignores a globally enabled source overridden by the project', () => { + expect( + hasGloballyShownWorktreeVisibilitySource( + repo({ worktreeVisibilitySourcePreferences: { builtIn: { claude: 'hide' } } }), + shownClaude + ) + ).toBe(false) + }) + + it('ignores global settings when all inherited sources are hidden', () => { + expect(hasGloballyShownWorktreeVisibilitySource(repo(), { external: 'hide' })).toBe(false) + }) + + it('detects an inherited custom source enabled in global settings', () => { + expect( + hasGloballyShownWorktreeVisibilitySource(repo(), { + external: 'hide', + customSources: [{ id: 'team', rootPath: '/srv/team' }], + sourcePreferences: { custom: { team: 'show' } } + }) + ).toBe(true) + }) +}) diff --git a/src/renderer/src/components/sidebar/worktree-visibility-source-provenance.ts b/src/renderer/src/components/sidebar/worktree-visibility-source-provenance.ts index 9c40a77236a..d57eeb7b693 100644 --- a/src/renderer/src/components/sidebar/worktree-visibility-source-provenance.ts +++ b/src/renderer/src/components/sidebar/worktree-visibility-source-provenance.ts @@ -5,6 +5,7 @@ import type { Repo, WorktreeVisibilityDefaults } from '../../../../shared/types' import { effectiveDefaultBuiltInWorktreeSourceVisibility, effectiveDefaultCustomWorktreeSourceVisibility, + normalizeCustomWorktreeVisibilitySources, normalizeWorktreeVisibilitySourcePreferences } from '../../../../shared/worktree-visibility-sources' import type { WorktreeVisibilitySourceRow } from './WorktreeVisibilitySourceList' @@ -14,6 +15,37 @@ type SourceProvenance = { globalEnabled?: boolean } +export function hasGloballyShownWorktreeVisibilitySource( + repo: Repo, + visibilityDefaults: WorktreeVisibilityDefaults | undefined +): boolean { + const defaults = visibilityDefaults ?? {} + const repoCustomSourceIds = new Set( + normalizeCustomWorktreeVisibilitySources(repo.customWorktreeVisibilitySources)?.map( + (source) => source.id + ) ?? [] + ) + const sources: WorktreeVisibilitySourceRow[] = [ + { kind: 'built-in', id: 'claude' }, + { kind: 'built-in', id: 'gsd' }, + ...(normalizeCustomWorktreeVisibilitySources(defaults.customSources) ?? []).map((source) => ({ + kind: 'custom' as const, + source + })), + { kind: 'other' } + ] + + return sources.some((source) => { + const provenance = getWorktreeVisibilitySourceProvenance( + repo, + source, + defaults, + repoCustomSourceIds + ) + return provenance?.kind === 'global' && provenance.globalEnabled === true + }) +} + export function getWorktreeVisibilitySourceProvenance( repo: Repo | undefined, source: WorktreeVisibilitySourceRow,