Skip to content

Commit 693c4e1

Browse files
sandy081Copilot
andauthored
sessions: gate titlebar new-session button behind experiment + add telemetry (#322689)
* Gate titlebar new-session button behind A/B experiment + add per-surface telemetry Gate the titlebar new-session button on the 'agentSessionsTitleBarNewSession' ExP treatment via the SessionsTitleBarNewSessionEnabledContext context key, so the affordance only shows for the treatment group and we can measure its impact on new-session metrics. Also tag the new-session interaction telemetry with the originating surface ('titleBar' vs 'sidebar') to count clicks from the titlebar. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * sessions: move titlebar new-session spacing into the left toolbar Let the left titlebar toolbar provide the gap between its actions via a 4px gap on the actions-container, instead of a per-item margin class on the new-session view item. Keeps spacing consistent across all left toolbar actions. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * sessions: shorten comments Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * sessions: derive new-session telemetry source from DOM instead of a constructor flag Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * sessions: pass telemetry source to new-session view item from its menu Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * sessions: always show titlebar new-session button in dev builds Show the affordance regardless of the experiment when running out of sources (IEnvironmentService.isBuilt === false) to ease development. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 57a6f50 commit 693c4e1

7 files changed

Lines changed: 55 additions & 34 deletions

File tree

src/vs/sessions/LAYOUT.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ The titlebar is a standalone implementation (`TitlebarPart`) — not extending `
8383

8484
| Section | Menu ID | Content |
8585
|---------|---------|---------|
86-
| Left | `Menus.TitleBarLeftLayout` | Toggle sidebar, new session (when sidebar hidden), agent host filter |
86+
| Left | `Menus.TitleBarLeftLayout` | Toggle sidebar, new session (when sidebar hidden, A/B experiment), agent host filter |
8787
| Center | `Menus.CommandCenter` | Session picker widget (plus `Menus.TitleBarSessionMenu` for active-session actions) |
8888
| Right | `Menus.TitleBarRightLayout` | Remote connections, run script (split button), Open Terminal/VS Code, toggle auxiliary bar, account widget |
8989

src/vs/sessions/browser/parts/media/titlebarpart.css

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,11 @@
170170
display: flex;
171171
}
172172

173+
/* Spacing between the left toolbar's actions. */
174+
.monaco-workbench .part.titlebar > .sessions-titlebar-container > .titlebar-left > .left-toolbar-container .monaco-action-bar .actions-container {
175+
gap: 4px;
176+
}
177+
173178
/* Remove the titlebar shadow in agent sessions */
174179
.agent-sessions-workbench.monaco-workbench .part.titlebar {
175180
box-shadow: none;

src/vs/sessions/common/contextkeys.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,12 @@ export const SessionsWelcomeVisibleContext = new RawContextKey<boolean>('session
5151

5252
//#endregion
5353

54+
//#region < --- Experiments --- >
55+
56+
export const SessionsTitleBarNewSessionEnabledContext = new RawContextKey<boolean>('sessionsTitleBarNewSessionEnabled', false, localize('sessionsTitleBarNewSessionEnabled', "Whether the new-session button is shown in the titlebar when the sessions list is hidden (A/B experiment)"));
57+
58+
//#endregion
59+
5460
//#region < --- Workspace Picker --- >
5561

5662
export const SessionWorkspacePickerGroupContext = new RawContextKey<string>('sessionWorkspacePickerGroup', '', localize('sessionWorkspacePickerGroup', "The currently active group tab in the session workspace picker"));

src/vs/sessions/common/sessionsTelemetry.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ export type SessionsInteractionButton =
1515
| 'openTerminal'
1616
| 'openInVSCode';
1717

18-
export type SessionsInteractionSource = 'menu' | 'actionWidget';
18+
export type SessionsInteractionSource = 'menu' | 'actionWidget' | 'titleBar' | 'sidebar';
1919

2020
type SessionsInteractionEvent = {
2121
button: string;
@@ -26,7 +26,7 @@ type SessionsInteractionClassification = {
2626
owner: 'osortega';
2727
comment: 'Tracks user interactions with buttons in the Agents window';
2828
button: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The identifier of the button that was clicked' };
29-
source?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The UI surface that triggered the interaction (menu or actionWidget)' };
29+
source?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The UI surface that triggered the interaction (menu, actionWidget, titleBar or sidebar)' };
3030
};
3131

3232
/**

src/vs/sessions/contrib/chat/browser/chat.contribution.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ import '../../sessions/browser/mobile/mobileOverlayContribution.js';
4040
import { Registry } from '../../../../platform/registry/common/platform.js';
4141
import { EditorAreaFocusContext, SideBarVisibleContext } from '../../../../workbench/common/contextkeys.js';
4242
import { NEW_SESSION_ACTION_ID } from '../common/constants.js';
43-
import { SessionsWelcomeVisibleContext } from '../../../common/contextkeys.js';
43+
import { SessionsTitleBarNewSessionEnabledContext, SessionsWelcomeVisibleContext } from '../../../common/contextkeys.js';
4444
import { Menus } from '../../../browser/menus.js';
4545

4646

@@ -77,7 +77,8 @@ class NewChatInSessionsWindowAction extends Action2 {
7777
id: Menus.TitleBarLeftLayout,
7878
group: 'navigation',
7979
order: 1,
80-
when: ContextKeyExpr.and(SideBarVisibleContext.toNegated(), SessionsWelcomeVisibleContext.toNegated())
80+
// Show in the titlebar only when the sidebar is hidden, gated behind an A/B experiment.
81+
when: ContextKeyExpr.and(SideBarVisibleContext.toNegated(), SessionsWelcomeVisibleContext.toNegated(), SessionsTitleBarNewSessionEnabledContext)
8182
}
8283
]
8384
});

src/vs/sessions/contrib/sessions/browser/media/newSessionActionViewItem.css

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,6 @@ unscoped so the widget renders identically wherever it is mounted. */
2929
cursor: pointer;
3030
}
3131

32-
/* When mounted in the titlebar, add breathing room from the adjacent
33-
sidebar toggle and the surrounding titlebar edges. */
34-
.sessions-titlebar-new-session-item {
35-
display: inline-flex;
36-
align-items: center;
37-
margin: 0 4px;
38-
}
39-
4032
.agent-sessions-compact-new-button.monaco-button.default-colors:hover {
4133
background-color: var(--vscode-agentsNewSessionButton-hoverBackground, var(--vscode-toolbar-hoverBackground));
4234
}

src/vs/sessions/contrib/sessions/browser/newSessionActionViewItem.ts

Lines changed: 38 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -15,30 +15,32 @@ import { OS } from '../../../../base/common/platform.js';
1515
import { localize } from '../../../../nls.js';
1616
import { IActionViewItemService } from '../../../../platform/actions/browser/actionViewItemService.js';
1717
import { MenuId, MenuItemAction } from '../../../../platform/actions/common/actions.js';
18-
import { IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js';
18+
import { IContextKey, IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js';
19+
import { IEnvironmentService } from '../../../../platform/environment/common/environment.js';
1920
import { IHoverService } from '../../../../platform/hover/browser/hover.js';
2021
import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js';
2122
import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js';
2223
import { asCssVariable } from '../../../../platform/theme/common/colorRegistry.js';
2324
import { defaultButtonStyles } from '../../../../platform/theme/browser/defaultStyles.js';
2425
import { IWorkbenchContribution } from '../../../../workbench/common/contributions.js';
2526
import { markOnboardingTarget } from '../../../../workbench/contrib/onboarding/browser/spotlight/onboardingTarget.js';
27+
import { IWorkbenchAssignmentService } from '../../../../workbench/services/assignment/common/assignmentService.js';
2628
import { Menus } from '../../../browser/menus.js';
29+
import { SessionsTitleBarNewSessionEnabledContext } from '../../../common/contextkeys.js';
2730
import { agentsNewSessionButtonBackground, agentsNewSessionButtonBorder, agentsNewSessionButtonForeground, agentsNewSessionButtonHoverBackground } from '../../../common/theme.js';
28-
import { logSessionsInteraction } from '../../../common/sessionsTelemetry.js';
31+
import { logSessionsInteraction, SessionsInteractionSource } from '../../../common/sessionsTelemetry.js';
2932
import { NEW_SESSION_ACTION_ID } from '../../chat/common/constants.js';
3033
import './media/newSessionActionViewItem.css';
3134

3235
/**
33-
* Renders the new-session action ({@link NEW_SESSION_ACTION_ID}) as the compact "New" pill
34-
* with an inline keybinding hint. Used wherever the action is contributed — the sessions
35-
* sidebar header and the titlebar — so both surfaces render the exact same affordance.
36+
* Renders the new-session action as the compact "New" pill, shared by the sessions sidebar
37+
* header and the titlebar.
3638
*/
3739
class NewSessionActionViewItem extends BaseActionViewItem {
3840

3941
constructor(
4042
action: IAction,
41-
private readonly inTitleBar: boolean,
43+
private readonly telemetrySource: SessionsInteractionSource,
4244
@IKeybindingService private readonly keybindingService: IKeybindingService,
4345
@IHoverService private readonly hoverService: IHoverService,
4446
@ITelemetryService private readonly telemetryService: ITelemetryService,
@@ -54,10 +56,6 @@ class NewSessionActionViewItem extends BaseActionViewItem {
5456
return;
5557
}
5658

57-
if (this.inTitleBar) {
58-
this.element.classList.add('sessions-titlebar-new-session-item');
59-
}
60-
6159
const newSessionButton = this._register(new Button(this.element, {
6260
...defaultButtonStyles,
6361
buttonSecondaryBackground: asCssVariable(agentsNewSessionButtonBackground),
@@ -70,15 +68,12 @@ class NewSessionActionViewItem extends BaseActionViewItem {
7068
newSessionButton.element.classList.add('agent-sessions-compact-new-button');
7169
this._register(markOnboardingTarget(newSessionButton.element, 'sessions.newSession.button'));
7270
this._register(newSessionButton.onDidClick(e => {
73-
// The inner button lives inside this view item's <li>, whose click
74-
// listener (installed by BaseActionViewItem) would also run the action.
75-
// Stop propagation so the command runs exactly once, and run through the
76-
// action runner so the action's enabled state is respected.
71+
// Stop propagation so the parent <li> click handler doesn't run the action twice.
7772
EventHelper.stop(e, true);
7873
if (!this.action.enabled) {
7974
return;
8075
}
81-
logSessionsInteraction(this.telemetryService, 'newSession');
76+
logSessionsInteraction(this.telemetryService, 'newSession', this.telemetrySource);
8277
this.actionRunner.run(this.action);
8378
}));
8479

@@ -143,31 +138,53 @@ class NewSessionActionViewItem extends BaseActionViewItem {
143138
}
144139

145140
/**
146-
* Registers {@link NewSessionActionViewItem} for the new-session action in every menu that
147-
* surfaces it (the sessions sidebar header and the titlebar's left toolbar). The factory is
148-
* announced once right after registration so a toolbar that was built before this contribution
149-
* ran re-renders and picks the widget up.
141+
* Registers {@link NewSessionActionViewItem} in the sessions sidebar header and the titlebar.
142+
* The titlebar entry is gated behind an A/B experiment via {@link SessionsTitleBarNewSessionEnabledContext}.
150143
*/
151144
export class NewSessionActionViewItemContribution extends Disposable implements IWorkbenchContribution {
152145

153146
static readonly ID = 'workbench.contrib.sessions.newSessionActionViewItem';
154147

148+
/** ExP treatment that shows the new-session button in the titlebar. */
149+
private static readonly NEW_SESSION_TITLEBAR_TREATMENT = 'agentSessionsTitleBarNewSession';
150+
151+
private readonly titleBarEnabledContext: IContextKey<boolean>;
152+
155153
constructor(
156154
@IActionViewItemService actionViewItemService: IActionViewItemService,
155+
@IContextKeyService contextKeyService: IContextKeyService,
156+
@IWorkbenchAssignmentService private readonly assignmentService: IWorkbenchAssignmentService,
157+
@IEnvironmentService private readonly environmentService: IEnvironmentService,
157158
) {
158159
super();
159160

161+
this.titleBarEnabledContext = SessionsTitleBarNewSessionEnabledContext.bindTo(contextKeyService);
162+
160163
const onDidRegister = this._register(new Emitter<void>());
161164
const menus: MenuId[] = [Menus.SidebarSessionsHeader, Menus.TitleBarLeftLayout];
162165
for (const menu of menus) {
163-
const inTitleBar = menu === Menus.TitleBarLeftLayout;
166+
const source: SessionsInteractionSource = menu === Menus.TitleBarLeftLayout ? 'titleBar' : 'sidebar';
164167
this._register(actionViewItemService.register(menu, NEW_SESSION_ACTION_ID, (action, _options, instantiationService) => {
165168
if (!(action instanceof MenuItemAction)) {
166169
return undefined;
167170
}
168-
return instantiationService.createInstance(NewSessionActionViewItem, action, inTitleBar);
171+
return instantiationService.createInstance(NewSessionActionViewItem, action, source);
169172
}, onDidRegister.event));
170173
}
171174
onDidRegister.fire();
175+
176+
// Resolve the titlebar experiment now and on refetch.
177+
this._register(this.assignmentService.onDidRefetchAssignments(() => this.updateTitleBarTreatment()));
178+
this.updateTitleBarTreatment();
179+
}
180+
181+
private async updateTitleBarTreatment(): Promise<void> {
182+
// Always show in dev builds (running from sources) to ease development, regardless of the experiment.
183+
if (!this.environmentService.isBuilt) {
184+
this.titleBarEnabledContext.set(true);
185+
return;
186+
}
187+
const enabled = await this.assignmentService.getTreatment<boolean>(NewSessionActionViewItemContribution.NEW_SESSION_TITLEBAR_TREATMENT);
188+
this.titleBarEnabledContext.set(enabled === true);
172189
}
173190
}

0 commit comments

Comments
 (0)