Skip to content
Merged
2 changes: 1 addition & 1 deletion .github/skills/sessions/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ Use the `agent-sessions-layout` skill for detailed guidance on the layout. Key p

### 4.3 Editor Modal

The main editor part is hidden (`display:none`). All editors open via `MODAL_GROUP` into the standard `ModalEditorPart` overlay (created on-demand by `EditorParts.createModalEditorPart`). The sessions configuration sets `workbench.editor.useModal` to `'on'`, which causes `findGroup()` to redirect all editor opens to the modal. Click backdrop or press Escape to dismiss.
The main editor part is hidden (`display:none`). All editors open via `MODAL_GROUP` into the standard `ModalEditorPart` overlay (created on-demand by `EditorParts.createModalEditorPart`). The sessions configuration sets `workbench.editor.useModal` to `'all'`, which causes `findGroup()` to redirect all editor opens to the modal. Click backdrop or press Escape to dismiss.

## 5. Chat Widget

Expand Down
18 changes: 13 additions & 5 deletions src/vs/base/browser/ui/tree/abstractTree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,7 @@ export enum RenderIndentGuides {

interface ITreeRendererOptions<T> {
readonly indent?: number;
readonly defaultIndent?: number;
readonly renderIndentGuides?: RenderIndentGuides;
// TODO@joao replace this with collapsible: boolean | 'ondemand'
readonly hideTwistiesOfChildlessElements?: boolean;
Expand Down Expand Up @@ -347,6 +348,7 @@ export class TreeRenderer<T, TFilterData, TRef, TTemplateData> implements IListR
private renderedElements = new Map<T, ITreeNode<T, TFilterData>>();
private renderedNodes = new Map<ITreeNode<T, TFilterData>, ITreeListTemplateData<TTemplateData>>();
private indent: number = TreeRenderer.DefaultIndent;
private defaultIndent: number = TreeRenderer.DefaultIndent;
private hideTwistiesOfChildlessElements: boolean = false;
private twistieAdditionalCssClass?: (element: T) => string | undefined;

Expand All @@ -372,14 +374,19 @@ export class TreeRenderer<T, TFilterData, TRef, TTemplateData> implements IListR
}

updateOptions(options: ITreeRendererOptions<T> = {}): void {
if (typeof options.indent !== 'undefined') {
const indent = clamp(options.indent, 0, 40);
if (typeof options.defaultIndent !== 'undefined') {
this.defaultIndent = options.defaultIndent;
}

if (typeof options.indent !== 'undefined' || typeof options.defaultIndent !== 'undefined') {
const indent = typeof options.indent !== 'undefined' ? clamp(options.indent, 0, 40) : this.indent;
const needsRerender = indent !== this.indent || typeof options.defaultIndent !== 'undefined';

if (indent !== this.indent) {
if (needsRerender) {
this.indent = indent;

for (const [node, templateData] of this.renderedNodes) {
templateData.indentSize = TreeRenderer.DefaultIndent + (node.depth - 1) * this.indent;
templateData.indentSize = this.defaultIndent + (node.depth - 1) * this.indent;
this.renderTreeElement(node, templateData);
}
}
Expand Down Expand Up @@ -427,7 +434,7 @@ export class TreeRenderer<T, TFilterData, TRef, TTemplateData> implements IListR
}

renderElement(node: ITreeNode<T, TFilterData>, index: number, templateData: ITreeListTemplateData<TTemplateData>, details?: IListElementRenderDetails): void {
templateData.indentSize = TreeRenderer.DefaultIndent + (node.depth - 1) * this.indent;
templateData.indentSize = this.defaultIndent + (node.depth - 1) * this.indent;

this.renderedNodes.set(node, templateData);
this.renderedElements.set(node.element, node);
Expand Down Expand Up @@ -2192,6 +2199,7 @@ function asTreeContextMenuEvent<T, TFilterData = void>(event: IListContextMenuEv
}

export interface IAbstractTreeOptionsUpdate<T> extends ITreeRendererOptions<T> {
readonly defaultIndent?: number; // Only recommended for compact layouts. Leave unchanged otherwise
readonly multipleSelectionSupport?: boolean;
readonly typeNavigationEnabled?: boolean;
readonly typeNavigationMode?: TypeNavigationMode;
Expand Down
10 changes: 5 additions & 5 deletions src/vs/sessions/LAYOUT.md
Original file line number Diff line number Diff line change
Expand Up @@ -178,9 +178,9 @@ The main editor part is created but hidden (`display:none`). It exists for futur

#### How It Works

The sessions configuration sets `workbench.editor.useModal` to `'on'` (in `contrib/configuration/browser/configuration.contribution.ts`). This causes `findGroup()` in `editorGroupFinder.ts` to redirect all editor opens (that do not specify an explicit preferred group) to `createModalEditorPart()`, which creates the standard workbench `ModalEditorPart` overlay on-demand.
The sessions configuration sets `workbench.editor.useModal` to `'all'` (in `contrib/configuration/browser/configuration.contribution.ts`). This causes `findGroup()` in `editorGroupFinder.ts` to redirect all editor opens (that do not specify an explicit preferred group) to `createModalEditorPart()`, which creates the standard workbench `ModalEditorPart` overlay on-demand.

When the setting is `'on'`:
When the setting is `'all'`:
- All editors without an explicit preferred group open in the modal editor part
- The modal is not auto-closed when editors open without explicit `MODAL_GROUP` as preferred group

Expand All @@ -197,8 +197,8 @@ When the setting is `'on'`:

The setting `workbench.editor.useModal` is an enum with three values:
- `'off'`: Editors never open in a modal overlay
- `'default'`: Certain editors (e.g. Settings, Keyboard Shortcuts) may open in a modal overlay when requested via `MODAL_GROUP`
- `'on'`: All editors open in a modal overlay (used by sessions window)
- `'some'`: Certain editors (e.g. Settings, Keyboard Shortcuts) may open in a modal overlay when requested via `MODAL_GROUP`
- `'all'`: All editors open in a modal overlay (used by sessions window)


---
Expand Down Expand Up @@ -640,7 +640,7 @@ interface IPartVisibilityState {

| Date | Change |
|------|--------|
| 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`/`default`/`on`); sessions config uses `on`; removed `editorModal.ts` and editor modal CSS |
| 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` |
| 2026-02-13 | Documentation sync: Updated all file names, class names, and references to match current implementation. `AgenticWorkbench` → `Workbench`, `AgenticSidebarPart` → `SidebarPart`, `AgenticAuxiliaryBarPart` → `AuxiliaryBarPart`, `AgenticPanelPart` → `PanelPart`, `agenticWorkbench.ts` → `workbench.ts`, `agenticWorkbenchMenus.ts` → `menus.ts`, `agenticLayoutActions.ts` → `layoutActions.ts`, `AgenticTitleBarWidget` → `SessionsTitleBarWidget`, `AgenticTitleBarContribution` → `SessionsTitleBarContribution`. Removed references to deleted files (`sidebarRevealButton.ts`, `floatingToolbar.ts`, `agentic.contributions.ts`, `agenticTitleBarWidget.ts`). Updated pane composite architecture from `SyncDescriptor`-based to `AgenticPaneCompositePartService`. Moved account widget docs from titlebar to sidebar footer. Added documentation for sidebar footer, project bar, traffic light spacer, card appearance styling, widget directory, and new contrib structure (`accountMenu/`, `chat/`, `configuration/`, `sessions/`). Updated titlebar actions to reflect Run Script split button and Open submenu. Removed Toggle Maximize panel action (no longer registered). Updated contributions section with all current contributions and their locations. |
| 2026-02-13 | Changed grid structure: sidebar now spans full window height at root level (HORIZONTAL root orientation); Titlebar moved inside right section; Grid is now `Sidebar \| [Titlebar / TopRight / Panel]` instead of `Titlebar / [Sidebar \| RightSection]`; Panel maximize now excludes both titlebar and sidebar; Floating toolbar positioning no longer depends on titlebar height |
Expand Down
28 changes: 23 additions & 5 deletions src/vs/sessions/browser/workbench.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1065,9 +1065,15 @@ export class Workbench extends Disposable implements IWorkbenchLayoutService {
!hidden,
);

// If sidebar becomes hidden, also hide the current active pane composite
if (hidden && this.paneCompositeService.getActivePaneComposite(ViewContainerLocation.Sidebar)) {
this.paneCompositeService.hideActivePaneComposite(ViewContainerLocation.Sidebar);
}

// If sidebar becomes visible, show last active Viewlet or default viewlet
if (!hidden && !this.paneCompositeService.getActivePaneComposite(ViewContainerLocation.Sidebar)) {
const viewletToOpen = this.paneCompositeService.getLastActivePaneCompositeId(ViewContainerLocation.Sidebar);
const viewletToOpen = this.paneCompositeService.getLastActivePaneCompositeId(ViewContainerLocation.Sidebar) ??
this.viewDescriptorService.getDefaultViewContainer(ViewContainerLocation.Sidebar)?.id;
if (viewletToOpen) {
this.paneCompositeService.openPaneComposite(viewletToOpen, ViewContainerLocation.Sidebar);
}
Expand All @@ -1088,9 +1094,15 @@ export class Workbench extends Disposable implements IWorkbenchLayoutService {
!hidden,
);

// If auxiliary bar becomes visible, show last active pane composite
// If auxiliary bar becomes hidden, also hide the current active pane composite
if (hidden && this.paneCompositeService.getActivePaneComposite(ViewContainerLocation.AuxiliaryBar)) {
this.paneCompositeService.hideActivePaneComposite(ViewContainerLocation.AuxiliaryBar);
}

// If auxiliary bar becomes visible, show last active pane composite or default
if (!hidden && !this.paneCompositeService.getActivePaneComposite(ViewContainerLocation.AuxiliaryBar)) {
const paneCompositeToOpen = this.paneCompositeService.getLastActivePaneCompositeId(ViewContainerLocation.AuxiliaryBar);
const paneCompositeToOpen = this.paneCompositeService.getLastActivePaneCompositeId(ViewContainerLocation.AuxiliaryBar) ??
this.viewDescriptorService.getDefaultViewContainer(ViewContainerLocation.AuxiliaryBar)?.id;
if (paneCompositeToOpen) {
this.paneCompositeService.openPaneComposite(paneCompositeToOpen, ViewContainerLocation.AuxiliaryBar);
}
Expand Down Expand Up @@ -1125,9 +1137,15 @@ export class Workbench extends Disposable implements IWorkbenchLayoutService {
!hidden,
);

// If panel becomes visible, show last active panel
// If panel becomes hidden, also hide the current active pane composite
if (hidden && this.paneCompositeService.getActivePaneComposite(ViewContainerLocation.Panel)) {
this.paneCompositeService.hideActivePaneComposite(ViewContainerLocation.Panel);
}

// If panel becomes visible, show last active panel or default
if (!hidden && !this.paneCompositeService.getActivePaneComposite(ViewContainerLocation.Panel)) {
const panelToOpen = this.paneCompositeService.getLastActivePaneCompositeId(ViewContainerLocation.Panel);
const panelToOpen = this.paneCompositeService.getLastActivePaneCompositeId(ViewContainerLocation.Panel) ??
this.viewDescriptorService.getDefaultViewContainer(ViewContainerLocation.Panel)?.id;
if (panelToOpen) {
this.paneCompositeService.openPaneComposite(panelToOpen, ViewContainerLocation.Panel);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@
*--------------------------------------------------------------------------------------------*/

import './agentFeedbackEditorInputContribution.js';
import './agentFeedbackEditorWidgetContribution.js';
import './agentFeedbackLineDecorationContribution.js';
import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js';
import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';
import { registerWorkbenchContribution2, WorkbenchPhase } from '../../../../workbench/common/contributions.js';
import { AgentFeedbackService, IAgentFeedbackService } from './agentFeedbackService.js';
import { AgentFeedbackAttachmentContribution } from './agentFeedbackAttachment.js';
Expand All @@ -25,8 +27,11 @@ registerSingleton(IAgentFeedbackService, AgentFeedbackService, InstantiationType
// Register the custom attachment widget for agentFeedback attachments
class AgentFeedbackAttachmentWidgetContribution {
static readonly ID = 'workbench.contrib.agentFeedbackAttachmentWidgetFactory';
constructor(@IChatAttachmentWidgetRegistry registry: IChatAttachmentWidgetRegistry) {
registry.registerFactory('agentFeedback', (instantiationService, attachment, options, container) => {
constructor(
@IChatAttachmentWidgetRegistry registry: IChatAttachmentWidgetRegistry,
@IInstantiationService instantiationService: IInstantiationService,
) {
registry.registerFactory('agentFeedback', (attachment, options, container) => {
return instantiationService.createInstance(AgentFeedbackAttachmentWidget, attachment as IAgentFeedbackVariableEntry, options, container);
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,10 @@ export class AgentFeedbackAttachmentWidget extends Disposable {
const label = dom.$('span.chat-attached-context-custom-text', {}, this._attachment.name);
this.element.appendChild(label);

const deletionCurrentlyNotSupported = true;

// Clear button
if (options.supportsDeletion) {
if (options.supportsDeletion && !deletionCurrentlyNotSupported) {
const clearBtn = dom.append(this.element, dom.$('.chat-attached-context-clear-button'));
const clearIcon = dom.$('span');
clearIcon.classList.add(...ThemeIcon.asClassNameArray(Codicon.close));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,6 @@ class NavigateFeedbackAction extends AgentFeedbackEditorAction {
editorService.openEditor({
resource: feedback.resourceUri,
options: {
selection: feedback.range,
preserveFocus: false,
revealIfVisible: true,
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,16 @@ import { localize } from '../../../../nls.js';
class AgentFeedbackInputWidget implements IOverlayWidget {

private static readonly _ID = 'agentFeedback.inputWidget';
private static readonly _MIN_WIDTH = 150;
private static readonly _MAX_WIDTH = 400;

readonly allowEditorOverflow = false;

private readonly _domNode: HTMLElement;
private readonly _inputElement: HTMLInputElement;
private readonly _inputElement: HTMLTextAreaElement;
private readonly _measureElement: HTMLElement;
private _position: IOverlayWidgetPosition | null = null;
private _lineHeight = 0;

constructor(
private readonly _editor: ICodeEditor,
Expand All @@ -36,12 +40,19 @@ class AgentFeedbackInputWidget implements IOverlayWidget {
this._domNode.classList.add('agent-feedback-input-widget');
this._domNode.style.display = 'none';

this._inputElement = document.createElement('input');
this._inputElement.type = 'text';
this._inputElement = document.createElement('textarea');
this._inputElement.rows = 1;
this._inputElement.placeholder = localize('agentFeedback.addFeedback', "Add Feedback");
this._domNode.appendChild(this._inputElement);

// Hidden element used to measure text width for auto-growing
this._measureElement = document.createElement('span');
this._measureElement.classList.add('agent-feedback-input-measure');
this._domNode.appendChild(this._measureElement);

this._editor.applyFontInfo(this._inputElement);
this._editor.applyFontInfo(this._measureElement);
this._lineHeight = this._editor.getOption(EditorOption.lineHeight);
}

getId(): string {
Expand All @@ -56,7 +67,7 @@ class AgentFeedbackInputWidget implements IOverlayWidget {
return this._position;
}

get inputElement(): HTMLInputElement {
get inputElement(): HTMLTextAreaElement {
return this._inputElement;
}

Expand All @@ -75,6 +86,28 @@ class AgentFeedbackInputWidget implements IOverlayWidget {

clearInput(): void {
this._inputElement.value = '';
this._autoSize();
}

autoSize(): void {
this._autoSize();
}

private _autoSize(): void {
const text = this._inputElement.value || this._inputElement.placeholder;

// Measure the text width using the hidden span
this._measureElement.textContent = text;
const textWidth = this._measureElement.scrollWidth;

// Clamp width between min and max
const width = Math.max(AgentFeedbackInputWidget._MIN_WIDTH, Math.min(textWidth + 10, AgentFeedbackInputWidget._MAX_WIDTH));
this._inputElement.style.width = `${width}px`;

// Reset height to auto then expand to fit all content, with a minimum of 1 line
this._inputElement.style.height = 'auto';
const newHeight = Math.max(this._inputElement.scrollHeight, this._lineHeight + 4 /* padding */);
this._inputElement.style.height = `${newHeight}px`;
}
}

Expand Down Expand Up @@ -110,8 +143,11 @@ export class AgentFeedbackEditorInputContribution extends Disposable implements
this._mouseDown = true;
this._hide();
}));
this._store.add(this._editor.onMouseUp(() => {
this._store.add(this._editor.onMouseUp((e) => {
this._mouseDown = false;
if (this._isWidgetTarget(e.event.target)) {
return;
}
this._onSelectionChanged();
}));
this._store.add(this._editor.onDidBlurEditorWidget(() => {
Expand Down Expand Up @@ -262,6 +298,12 @@ export class AgentFeedbackEditorInputContribution extends Disposable implements
e.stopPropagation();
}));

// Auto-size the textarea as the user types
this._widgetListeners.add(addStandardDisposableListener(widget.inputElement, 'input', () => {
widget.autoSize();
this._updatePosition();
}));

// Hide when input loses focus to something outside both editor and widget
this._widgetListeners.add(addStandardDisposableListener(widget.inputElement, 'blur', () => {
const win = getWindow(widget.inputElement);
Expand Down
Loading
Loading