microsoft/vscode · #333603
sessions: Disable unavailable session type picker
src/vs/sessions/contrib/chat/browser/sessionTypePicker.ts12 + / 11 −
@@ -18,7 +18,6 @@ import { ISessionsProvidersService } from '../../../services/sessions/browser/se import { autorun, IObservable, observableValue } from '../../../../base/common/observable.js'; import { ISession, SessionStatus } from '../../../services/sessions/common/session.js'; import { Emitter } from '../../../../base/common/event.js';-import { isWeb } from '../../../../base/common/platform.js'; import { isEqual } from '../../../../base/common/resources.js'; import { URI } from '../../../../base/common/uri.js'; import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js';@@ -150,8 +149,7 @@ export class SessionTypePicker extends Disposable { protected _triggerElement: HTMLElement | undefined; /**- * Tracks whether the harness picker trigger is currently visible. Mirrors- * the `.hidden` state computed in {@link _updateTriggerLabel}, so the+ * Tracks whether the harness picker trigger is currently interactive, so the * new-session-view onboarding tour can skip the harness step when only a * single harness can serve the selected workspace. */@@ -637,18 +635,19 @@ export class SessionTypePicker extends Disposable { dom.clearNode(this._triggerElement); - // In web (vscode.dev/agents) the host filter already scopes the- // workbench to a single agent host, so when that host advertises only- // one harness there is nothing to pick — hide the trigger entirely.- const hideForSingleHarness = isWeb && this._folderSessionTypes.length <= 1 && this._pickServedByFolder(this._picked);- if (this._folderSessionTypes.length === 0 || hideForSingleHarness) {+ if (this._folderSessionTypes.length === 0) { this._triggerElement.classList.add('hidden');+ this._triggerElement.parentElement?.classList.remove('disabled'); this._visibleKey.set(false); return; } + const disabled = this._folderSessionTypes.length === 1 && this._pickServedByFolder(this._picked); this._triggerElement.classList.remove('hidden');- this._visibleKey.set(true);+ this._triggerElement.parentElement?.classList.toggle('disabled', disabled);+ this._triggerElement.tabIndex = disabled ? -1 : 0;+ this._triggerElement.setAttribute('aria-disabled', String(disabled));+ this._visibleKey.set(!disabled); const currentType = this._folderSessionTypes.find(t => t.providerId === this._picked?.providerId && t.sessionType.id === this._picked?.sessionTypeId)?.sessionType ?? this._folderSessionTypes.find(t => t.sessionType.id === this._picked?.sessionTypeId)?.sessionType;@@ -659,11 +658,13 @@ export class SessionTypePicker extends Disposable { const labelSpan = dom.append(this._triggerElement, dom.$('span.sessions-chat-dropdown-label')); labelSpan.textContent = modeLabel; - if (this._options?.showChevron !== false) {+ if (!disabled && this._options?.showChevron !== false) { const chevron = dom.append(this._triggerElement, renderIcon(Codicon.chevronDownCompact)); chevron.classList.add('sessions-chat-dropdown-chevron'); } - this._triggerElement.ariaLabel = localize('sessionTypePicker.triggerAriaLabel', "Pick Session Type, {0}", modeLabel);+ this._triggerElement.ariaLabel = disabled+ ? localize('sessionTypePicker.disabledTriggerAriaLabel', "Session Type, {0}", modeLabel)+ : localize('sessionTypePicker.triggerAriaLabel', "Pick Session Type, {0}", modeLabel); } }src/vs/sessions/contrib/chat/test/browser/sessionTypePicker.test.ts45 + / 0 −
@@ -264,6 +264,51 @@ suite('SessionTypePicker', () => { }); }); + test('disables the trigger when the selected workspace has only one session type', () => {+ management.setSessionTypes([+ sessionType('copilot', 'cloud', 'Cloud'),+ ]);+ const picker = createPicker(disposables, session, management, storage);+ session.set(createFakeSession('copilot', 'cloud', folder), undefined);+ const container = document.createElement('div');+ picker.render(container);+ const trigger = container.querySelector<HTMLElement>('.action-label');+ const singleType = {+ hidden: trigger?.classList.contains('hidden'),+ disabled: trigger?.getAttribute('aria-disabled'),+ tabIndex: trigger?.tabIndex,+ label: trigger?.getAttribute('aria-label'),+ };++ management.setSessionTypes([+ sessionType('copilot', 'cloud', 'Cloud'),+ sessionType('local-agent-host', 'local', 'Local'),+ ]);++ assert.deepStrictEqual({+ singleType,+ multipleTypes: {+ hidden: trigger?.classList.contains('hidden'),+ disabled: trigger?.getAttribute('aria-disabled'),+ tabIndex: trigger?.tabIndex,+ label: trigger?.getAttribute('aria-label'),+ },+ }, {+ singleType: {+ hidden: false,+ disabled: 'true',+ tabIndex: -1,+ label: 'Session Type, Cloud',+ },+ multipleTypes: {+ hidden: false,+ disabled: 'false',+ tabIndex: 0,+ label: 'Pick Session Type, Cloud',+ },+ });+ });+ test('re-selecting the default (first) session type clears the stored pick', () => { management.setSessionTypes([ sessionType('local-1', 'local', 'Local'),src/vs/sessions/contrib/onboardingTours/browser/tours/newSessionViewV2Tour.ts2 + / 0 −
@@ -7,6 +7,7 @@ import { IObservable } from '../../../../../base/common/observable.js'; import { localize } from '../../../../../nls.js'; import { ISpotlightPayload, SPOTLIGHT_PRESENTATION_KIND } from '../../../../../workbench/contrib/onboarding/browser/spotlight/spotlightTypes.js'; import { IOnboardingScenario } from '../../../../../workbench/contrib/onboarding/common/onboardingScenario.js';+import { SessionHarnessPickerVisibleContext } from '../../../../common/contextkeys.js'; import { NEW_SESSION_ONBOARDING_SEEN_KEY } from './newSessionTour.js'; import { createNewSessionViewRecentTourWhen, createNewSessionViewWorkspaceStep } from './newSessionViewTourShared.js'; @@ -30,6 +31,7 @@ const newSessionViewV2Payload: ISpotlightPayload = { placement: 'above', missingTarget: WAIT_FOR_PICKER, openTarget: false,+ when: SessionHarnessPickerVisibleContext, allowTargetInteraction: true, }, {src/vs/sessions/contrib/onboardingTours/test/browser/newSessionViewV2Tour.test.ts5 + / 1 −
@@ -6,7 +6,7 @@ import assert from 'assert'; import { observableValue } from '../../../../../base/common/observable.js'; import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js';-import { AgentHostSessionTypesAvailableContext, IsNewChatSessionContext, SessionHasWorkspaceContext } from '../../../../common/contextkeys.js';+import { AgentHostSessionTypesAvailableContext, IsNewChatSessionContext, SessionHarnessPickerVisibleContext, SessionHasWorkspaceContext } from '../../../../common/contextkeys.js'; import { createNewSessionViewV2Tour, NEW_SESSION_VIEW_V2_TOUR_ID } from '../../browser/tours/newSessionViewV2Tour.js'; import { createNewSessionViewV3Tour } from '../../browser/tours/newSessionViewV3Tour.js'; import { NEW_SESSION_ONBOARDING_SEEN_KEY } from '../../browser/tours/newSessionTour.js';@@ -34,6 +34,7 @@ suite('NewSessionViewV2Tour', () => { openTarget: step.openTarget, allowTargetInteraction: step.allowTargetInteraction, advanceWhenWorkspaceSelected: step.advanceWhen === SessionHasWorkspaceContext,+ requiresInteractiveHarnessPicker: step.when === SessionHarnessPickerVisibleContext, })), }, { id: NEW_SESSION_VIEW_V2_TOUR_ID,@@ -52,6 +53,7 @@ suite('NewSessionViewV2Tour', () => { openTarget: true, allowTargetInteraction: true, advanceWhenWorkspaceSelected: true,+ requiresInteractiveHarnessPicker: false, }, { id: 'harnessPicker',@@ -60,6 +62,7 @@ suite('NewSessionViewV2Tour', () => { openTarget: false, allowTargetInteraction: true, advanceWhenWorkspaceSelected: false,+ requiresInteractiveHarnessPicker: true, }, { id: 'modelPicker',@@ -68,6 +71,7 @@ suite('NewSessionViewV2Tour', () => { openTarget: true, allowTargetInteraction: true, advanceWhenWorkspaceSelected: false,+ requiresInteractiveHarnessPicker: false, }, ], });