diff --git a/src/vs/sessions/LAYOUT.md b/src/vs/sessions/LAYOUT.md index d8092909fb7..5c48d4391bb 100644 --- a/src/vs/sessions/LAYOUT.md +++ b/src/vs/sessions/LAYOUT.md @@ -645,6 +645,7 @@ interface IPartVisibilityState { |------|--------| | 2026-03-26 | Updated the sessions sidebar appear animation so only the body content (`.part.sidebar > .content`) slides/fades in during reveal while the sidebar title/header and footer remain fixed | | 2026-03-25 | Updated Sessions view documentation to reflect the refactored `SessionsView` implementation in `contrib/sessions/browser/views/sessionsView.ts` and documented the left-aligned "+ Session" sidebar action with its inline keybinding hint | +| 2026-03-24 | Updated the sessions new-chat empty state: removed the watermark, vertically centered the empty-state controls block, restyled the workspace picker as an inline `New session in {dropdown}` title row aligned to the chat input, and tuned empty-state dropdown icon/chevron and local-mode spacing for the final visual polish. | | 2026-03-02 | Fixed macOS sidebar traffic light spacer to only render with custom titlebar; added `!hasNativeTitlebar()` guard to `SidebarPart.createTitleArea()` so the 70px spacer is not created when using native titlebar (traffic lights are in the OS title bar, not overlapping the sidebar) | | 2026-02-20 | Replaced custom `EditorModal` with standard `ModalEditorPart` via `MODAL_GROUP`; main editor part created but hidden; changed `workbench.editor.useModal` from boolean to enum (`off`/`some`/`all`); sessions config uses `all`; removed `editorModal.ts` and editor modal CSS | | 2026-02-17 | Added `-webkit-app-region: drag` to sidebar title area so it can be used to drag the window; interactive children (actions, composite bar, labels) marked `no-drag`; CSS rules scoped to `.agent-sessions-workbench` in `parts/media/sidebarPart.css` | diff --git a/src/vs/sessions/contrib/chat/browser/media/chatWelcomePart.css b/src/vs/sessions/contrib/chat/browser/media/chatWelcomePart.css index 1a3017045c4..272eb3d02a8 100644 --- a/src/vs/sessions/contrib/chat/browser/media/chatWelcomePart.css +++ b/src/vs/sessions/contrib/chat/browser/media/chatWelcomePart.css @@ -12,48 +12,15 @@ height: 100%; box-sizing: border-box; overflow: hidden; - padding: 0 10px 48px 10px; + padding: 16px 16px 20px 16px; container-type: size; + position: relative; } .chat-full-welcome.revealed { justify-content: center; } -/* Header */ -.chat-full-welcome-header { - display: flex; - flex-direction: column; - align-items: center; - width: 100%; - max-width: 800px; - overflow: visible; -} - -/* Watermark letterpress */ -.chat-full-welcome-letterpress { - width: 100%; - max-width: 200px; - aspect-ratio: 1/1; - background-image: url('./letterpress-sessions-dark.svg'); - background-size: contain; - background-position: center; - background-repeat: no-repeat; - margin-top: 8px; - margin-bottom: 20px; -} - -.vs .chat-full-welcome-letterpress, -.hc-light .chat-full-welcome-letterpress { - background-image: url('./letterpress-sessions-light.svg'); -} - -@container (max-height: 350px) { - .chat-full-welcome-letterpress { - display: none; - } -} - /* Input slot */ .chat-full-welcome-inputSlot { width: 100%; @@ -73,7 +40,7 @@ display: none; width: 100%; max-width: 800px; - margin: 0 0 24px 0; + margin: 0 0 12px 0; padding: 0; box-sizing: border-box; } @@ -100,13 +67,19 @@ margin-bottom: 0; } +.chat-full-welcome-content { + width: 100%; + max-width: 800px; + display: flex; + flex-direction: column; + align-items: stretch; +} + /* Local mode picker (Workspace / Worktree) below input */ .chat-full-welcome-local-mode { width: 100%; max-width: 800px; margin-top: 8px; - padding-left: 7px; - padding-right: 7px; box-sizing: border-box; display: none; flex-direction: row; @@ -167,7 +140,8 @@ flex-direction: row; flex-wrap: nowrap; align-items: center; - justify-content: center; + justify-content: flex-start; + gap: 6px; width: 100%; box-sizing: border-box; padding: 0; @@ -177,34 +151,46 @@ display: none; } -/* Prominent project picker button */ +.chat-full-welcome-pickers-label { + font-size: 18px; + line-height: 1.25; + color: var(--vscode-descriptionForeground); + white-space: nowrap; +} + +/* Project picker in inline title row */ .sessions-chat-picker-slot.sessions-chat-workspace-picker .action-label { height: auto; - padding: 8px 20px; - font-size: 15px; - border: 1px solid var(--vscode-button-border); - background-color: var(--vscode-button-secondaryBackground); - color: var(--vscode-button-secondaryForeground); - border-radius: 6px; + padding: 4px; + font-size: 18px; + line-height: 1.25; + border: none; + background-color: transparent; + color: var(--vscode-foreground); + border-radius: 4px; } .sessions-chat-picker-slot.sessions-chat-workspace-picker .action-label:hover { - background-color: var(--vscode-button-secondaryHoverBackground); -} - -.sessions-chat-picker-slot.sessions-chat-workspace-picker .action-label .codicon { - font-size: 18px; - margin-right: 2px; -} - -.sessions-chat-picker-slot.sessions-chat-project-picker .action-label .codicon-chevron-down { - margin-right: 0; - position: relative; - top: 1px; + background-color: var(--vscode-toolbar-hoverBackground); + color: var(--vscode-foreground); } .sessions-chat-picker-slot.sessions-chat-workspace-picker .action-label .sessions-chat-dropdown-label { - font-size: 15px; + font-size: 18px; +} + +.sessions-chat-picker-slot.sessions-chat-workspace-picker .action-label > .codicon:not(.sessions-chat-dropdown-chevron) { + font-size: 16px; +} + +.sessions-chat-picker-slot.sessions-chat-workspace-picker .action-label .sessions-chat-dropdown-chevron { + display: inline-flex; + align-items: center; + justify-content: center; + font-size: 16px; + margin-left: 6px; + line-height: 1; + transform: translateY(1px); } .sessions-chat-dropdown-label { @@ -290,8 +276,13 @@ } .sessions-chat-picker-slot .action-label .codicon-chevron-down { + display: inline-flex; + align-items: center; + justify-content: center; font-size: 12px; margin-left: 6px; + line-height: 1; + transform: translateY(1px); } .sessions-chat-picker-slot .action-label .chat-session-option-label { diff --git a/src/vs/sessions/contrib/chat/browser/newChatViewPane.ts b/src/vs/sessions/contrib/chat/browser/newChatViewPane.ts index dd1d36bc813..f87522fd7fb 100644 --- a/src/vs/sessions/contrib/chat/browser/newChatViewPane.ts +++ b/src/vs/sessions/contrib/chat/browser/newChatViewPane.ts @@ -138,6 +138,9 @@ class NewChatWidget extends Disposable implements IHistoryNavigationWidget { this._sessionTypePicker = this._register(this.instantiationService.createInstance(SessionTypePicker)); // When a workspace is selected, create a new session + this._register(this._workspacePicker.onDidChangeSelection(() => { + this._renderOptionGroupPickers(); + })); this._register(this._workspacePicker.onDidSelectWorkspace(async (workspace) => { await this._onWorkspaceSelected(workspace); this._focusEditor(); @@ -168,15 +171,14 @@ class NewChatWidget extends Disposable implements IHistoryNavigationWidget { const welcomeElement = dom.append(wrapper, dom.$('.chat-full-welcome')); - // Watermark letterpress - const header = dom.append(welcomeElement, dom.$('.chat-full-welcome-header')); - dom.append(header, dom.$('.chat-full-welcome-letterpress')); + // Main empty-state content area (folder picker, input, local mode controls) + const welcomeContent = dom.append(welcomeElement, dom.$('.chat-full-welcome-content')); // Option group pickers (above the input) - this._pickersContainer = dom.append(welcomeElement, dom.$('.chat-full-welcome-pickers-container')); + this._pickersContainer = dom.append(welcomeContent, dom.$('.chat-full-welcome-pickers-container')); // Input slot - this._inputSlot = dom.append(welcomeElement, dom.$('.chat-full-welcome-inputSlot')); + this._inputSlot = dom.append(welcomeContent, dom.$('.chat-full-welcome-inputSlot')); // Input area inside the input slot const inputArea = dom.$('.sessions-chat-input-area'); @@ -193,7 +195,7 @@ class NewChatWidget extends Disposable implements IHistoryNavigationWidget { this._inputSlot.appendChild(inputArea); // Below-input row: session type picker, permission control, spacer, repository config (right) - const belowInputRow = dom.append(welcomeElement, dom.$('.chat-full-welcome-local-mode')); + const belowInputRow = dom.append(welcomeContent, dom.$('.chat-full-welcome-local-mode')); this._sessionTypePicker.render(belowInputRow); const controlContainer = dom.append(belowInputRow, dom.$('.sessions-chat-control-toolbar')); this._register(this.instantiationService.createInstance(MenuWorkbenchToolBar, controlContainer, Menus.NewSessionControl, { @@ -438,6 +440,10 @@ class NewChatWidget extends Disposable implements IHistoryNavigationWidget { dom.clearNode(this._pickersContainer); const pickersRow = dom.append(this._pickersContainer, dom.$('.chat-full-welcome-pickers')); + const pickersLabel = dom.append(pickersRow, dom.$('.chat-full-welcome-pickers-label')); + pickersLabel.textContent = this._workspacePicker.selectedProject + ? localize('newSessionIn', "New session in") + : localize('newSessionChooseWorkspace', "Start by picking"); // Project picker (unified folder + repo picker) this._workspacePicker.render(pickersRow); diff --git a/src/vs/sessions/contrib/chat/browser/sessionWorkspacePicker.ts b/src/vs/sessions/contrib/chat/browser/sessionWorkspacePicker.ts index c70e5fd750a..192120ac2ae 100644 --- a/src/vs/sessions/contrib/chat/browser/sessionWorkspacePicker.ts +++ b/src/vs/sessions/contrib/chat/browser/sessionWorkspacePicker.ts @@ -64,6 +64,8 @@ export class WorkspacePicker extends Disposable { private readonly _onDidSelectWorkspace = this._register(new Emitter()); readonly onDidSelectWorkspace: Event = this._onDidSelectWorkspace.event; + private readonly _onDidChangeSelection = this._register(new Emitter()); + readonly onDidChangeSelection: Event = this._onDidChangeSelection.event; private _selectedWorkspace: IWorkspaceSelection | undefined; @@ -105,6 +107,7 @@ export class WorkspacePicker extends Disposable { if (restored) { this._selectedWorkspace = restored; this._updateTriggerLabel(); + this._onDidChangeSelection.fire(); this._onDidSelectWorkspace.fire(restored); } } @@ -124,6 +127,8 @@ export class WorkspacePicker extends Disposable { const trigger = dom.append(slot, dom.$('a.action-label')); trigger.tabIndex = 0; trigger.role = 'button'; + trigger.setAttribute('aria-haspopup', 'listbox'); + trigger.setAttribute('aria-expanded', 'false'); this._triggerElement = trigger; this._updateTriggerLabel(); @@ -164,10 +169,16 @@ export class WorkspacePicker extends Disposable { this._selectProject(item.selection); } }, - onHide: () => { triggerElement.focus(); }, + onHide: () => { + triggerElement.setAttribute('aria-expanded', 'false'); + triggerElement.focus(); + }, }; - const listOptions = showFilter ? { showFilter: true, filterPlaceholder: localize('workspacePicker.filter', "Search Workspaces..."), reserveSubmenuSpace: false } : { reserveSubmenuSpace: false }; + const listOptions = showFilter + ? { showFilter: true, filterPlaceholder: localize('workspacePicker.filter', "Search Workspaces..."), reserveSubmenuSpace: false } + : { reserveSubmenuSpace: false }; + triggerElement.setAttribute('aria-expanded', 'true'); this.actionWidgetService.show( 'workspacePicker', @@ -197,12 +208,14 @@ export class WorkspacePicker extends Disposable { * Clears the selected project. */ clearSelection(): void { + this.actionWidgetService.hide(); this._selectedWorkspace = undefined; // Clear checked state from all recents const recents = this._getStoredRecentWorkspaces(); const updated = recents.map(p => ({ ...p, checked: false })); this.storageService.store(STORAGE_KEY_RECENT_WORKSPACES, JSON.stringify(updated), StorageScope.PROFILE, StorageTarget.MACHINE); this._updateTriggerLabel(); + this._onDidChangeSelection.fire(); } /** @@ -218,6 +231,7 @@ export class WorkspacePicker extends Disposable { this._selectedWorkspace = selection; this._persistSelectedWorkspace(selection); this._updateTriggerLabel(); + this._onDidChangeSelection.fire(); if (fireEvent) { this._onDidSelectWorkspace.fire(selection); } @@ -367,13 +381,13 @@ export class WorkspacePicker extends Disposable { dom.clearNode(this._triggerElement); const workspace = this._selectedWorkspace?.workspace; - const label = workspace ? workspace.label : localize('pickWorkspace', "Pick a Workspace"); + const label = workspace ? workspace.label : localize('pickWorkspace', "a workspace"); const icon = workspace ? workspace.icon : Codicon.project; dom.append(this._triggerElement, renderIcon(icon)); const labelSpan = dom.append(this._triggerElement, dom.$('span.sessions-chat-dropdown-label')); labelSpan.textContent = label; - dom.append(this._triggerElement, renderIcon(Codicon.chevronDown)); + dom.append(this._triggerElement, renderIcon(Codicon.chevronDown)).classList.add('sessions-chat-dropdown-chevron'); } private _isSelectedWorkspace(selection: IWorkspaceSelection): boolean { @@ -521,8 +535,10 @@ export class WorkspacePicker extends Disposable { // Clear current selection if it was the removed workspace if (this._isSelectedWorkspace(selection)) { + this.actionWidgetService.hide(); this._selectedWorkspace = undefined; this._updateTriggerLabel(); + this._onDidChangeSelection.fire(); } } diff --git a/src/vs/sessions/contrib/chat/browser/workspacePicker.ts b/src/vs/sessions/contrib/chat/browser/workspacePicker.ts deleted file mode 100644 index a4a092d8349..00000000000 --- a/src/vs/sessions/contrib/chat/browser/workspacePicker.ts +++ /dev/null @@ -1,4 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/