Skip to content

Commit dcde63e

Browse files
sessions: drop the composer card, give each control its own surface (#334655)
* sessions: drop the composer card, give each control its own surface Over a chat background the new-session composer sat on a single frosted card, while a started session gives every control its own surface. This makes the composer match the started session. - Delete `.new-chat-widget-content`'s card styling: the padding, blur, border, radius and shadow. - Give the bottom-row action labels and the workspace pills an opaque `--session-view-background` fill, and put a 2px gap between the agent host config controls now that they no longer share one surface. - Keep the hover tint opaque. `--vscode-toolbar-hoverBackground` is translucent, so it goes on `background-image` as a flat gradient and composites over the opaque fill instead of replacing it. - Frame the bottom-row chips when a chat background is set, the same move the in-session secondary toolbar makes in `chatView.css`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * Address review: high-contrast chip frame, fixture layering The chip frame over a chat background used commandCenter.inactiveBorder, which — unlike its sibling border tokens — registers no hcDark/hcLight value, so it stayed a 25%-alpha wash in high contrast. Put contrastBorder first, the way the rest of the codebase does: high-contrast themes define it, others fall through. The chat-background fixture painted its opaque base on .session-view, which production makes transparent so the wallpaper can show. Move that base onto the part instead. Both screenshots are byte-identical, since the wallpaper is positioned and always painted above a static background either way — but the fixture now stacks its layers the way the real window does. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * Refresh the blocks-ci screenshot manifest Regenerated by CI. Most entries are this PR repainting the composer, and the NewSessionChatBackground pair is the fixture it adds. One entry is not from here: chatInput/ResponsiveModelResizeCycleMinimal/Dark. #334021 landed after the manifest was last regenerated and changed chatView.css without refreshing it, so this PR is the first regeneration to pick that up. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * Restore the resize-cycle hash the manifest refresh clobbered ResponsiveModelResizeCycleMinimal/Dark is not a fixture this branch touches, but the regenerated manifest I pasted carried a one-off hash for it. Put back the value main has held across ~20 regenerations, which is also what the latest run produced. That fixture is the only blocksCi one that sets virtualTime.enabled to false, and it drives 19 sequential resize widths on wall-clock time, so its final frame can land mid-settle on a loaded runner. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
1 parent cf01588 commit dcde63e

6 files changed

Lines changed: 110 additions & 47 deletions

File tree

src/vs/sessions/contrib/chat/browser/media/chatView.css

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -38,17 +38,6 @@
3838
overflow: hidden;
3939
}
4040

41-
.monaco-workbench.agent-sessions-workbench .part.sessionspart.has-chat-background .chat-view .sessions-chat-widget:not(.new-chat-in-session) .new-chat-widget-content {
42-
box-sizing: border-box;
43-
padding: var(--vscode-spacing-size120);
44-
background: color-mix(in srgb, var(--session-view-background) 86%, transparent);
45-
-webkit-backdrop-filter: blur(var(--vscode-spacing-size160)) saturate(1.08);
46-
backdrop-filter: blur(var(--vscode-spacing-size160)) saturate(1.08);
47-
border: var(--vscode-strokeThickness) solid color-mix(in srgb, var(--vscode-editorWidget-border, var(--vscode-widget-border)) 70%, transparent);
48-
border-radius: var(--vscode-cornerRadius-large);
49-
box-shadow: 0 var(--vscode-spacing-size80) var(--vscode-spacing-size240) color-mix(in srgb, var(--vscode-widget-shadow) 18%, transparent);
50-
}
51-
5241
.monaco-workbench.agent-sessions-workbench .part.sessionspart.has-chat-background .chat-view .interactive-session .chat-secondary-toolbar .action-label,
5342
.monaco-workbench.agent-sessions-workbench .part.sessionspart.has-chat-background .chat-view .interactive-session .chat-context-usage-widget {
5443
background-color: var(--vscode-chat-list-background, var(--vscode-button-secondaryBackground));

src/vs/sessions/contrib/chat/browser/media/chatWidget.css

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -204,12 +204,32 @@
204204

205205
/* Match VS Code chat-secondary-toolbar sizing for action items in the bottom row
206206
* (e.g. session control pickers and repo config pickers) so they align with the
207-
* model picker font in the input toolbar. */
207+
* model picker font in the input toolbar. Each item carries its own opaque
208+
* surface so a chat background cannot show through it. */
208209
.new-chat-widget-container .new-chat-bottom-container .action-label {
209210
height: 16px;
210211
padding: var(--vscode-spacing-size40) var(--vscode-spacing-size80);
211212
font-size: var(--vscode-fontSize-label2, 11px);
212213
color: var(--vscode-icon-foreground);
214+
background-color: var(--session-view-background);
215+
}
216+
217+
/* Over a chat background these chips sit straight on the wallpaper with nothing
218+
* to separate their opaque surface from it, so give them a frame — the same move
219+
* the in-session secondary toolbar makes in chatView.css. High-contrast themes
220+
* take `contrastBorder`, since `commandCenter.inactiveBorder` stays translucent
221+
* there. */
222+
.monaco-workbench.agent-sessions-workbench .part.sessionspart.has-chat-background .new-chat-widget-container .new-chat-bottom-container .action-label {
223+
border: var(--vscode-strokeThickness) solid var(--vscode-contrastBorder, var(--vscode-commandCenter-inactiveBorder, var(--vscode-widget-border)));
224+
border-radius: var(--vscode-cornerRadius-small);
225+
}
226+
227+
/* Outranks the shared action-bar hover in workbench style.css, which would
228+
* otherwise swap the opaque surface for a translucent one. The tint goes on the
229+
* background-image layer so it composites over that surface. */
230+
.monaco-workbench.agent-sessions-workbench .new-chat-widget-container .new-chat-bottom-container .action-label:not(.disabled):hover {
231+
background-color: var(--session-view-background);
232+
background-image: linear-gradient(var(--vscode-toolbar-hoverBackground), var(--vscode-toolbar-hoverBackground));
213233
}
214234

215235
.new-chat-widget-container .new-chat-bottom-container .action-label > .codicon {
@@ -279,6 +299,7 @@
279299
gap: var(--vscode-spacing-size80);
280300
min-height: var(--vscode-spacing-size240);
281301
padding: var(--vscode-spacing-size20) var(--vscode-spacing-size120);
302+
background-color: var(--session-view-background);
282303
border: var(--vscode-strokeThickness) solid var(--vscode-commandCenter-inactiveBorder, var(--vscode-widget-border));
283304
border-radius: var(--vscode-cornerRadius-circle);
284305
box-sizing: border-box;
@@ -287,9 +308,13 @@
287308
line-height: var(--vscode-spacing-size160);
288309
}
289310

311+
/* Re-assert the opaque surface, which the generic picker-slot hover below would
312+
* otherwise replace, and keep the tint on the background-image layer so it
313+
* composites over that surface. */
290314
.sessions-chat-picker-slot.sessions-workspace-category-picker-slot .action-label:hover,
291315
.sessions-chat-picker-slot.sessions-workspace-category-picker-slot .action-label[aria-expanded='true'] {
292-
background-color: var(--vscode-toolbar-hoverBackground);
316+
background-color: var(--session-view-background);
317+
background-image: linear-gradient(var(--vscode-toolbar-hoverBackground), var(--vscode-toolbar-hoverBackground));
293318
border-color: var(--vscode-commandCenter-activeBorder, var(--vscode-focusBorder));
294319
color: var(--vscode-foreground);
295320
}

src/vs/sessions/contrib/chat/test/browser/chatView.test.ts

Lines changed: 28 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -605,70 +605,82 @@ suite('Sessions - Chat View', () => {
605605
workbench.style.setProperty('--vscode-button-secondaryBackground', 'rgba(0, 0, 0, 0.08)');
606606
workbench.style.setProperty('--vscode-button-secondaryBorder', '#808080');
607607
workbench.style.setProperty('--vscode-button-secondaryForeground', '#202020');
608-
workbench.style.setProperty('--vscode-cornerRadius-large', '8px');
608+
workbench.style.setProperty('--vscode-commandCenter-inactiveBorder', '#606060');
609609
workbench.style.setProperty('--vscode-cornerRadius-small', '4px');
610-
workbench.style.setProperty('--vscode-spacing-size120', '12px');
611610
workbench.style.setProperty('--vscode-strokeThickness', '1px');
612611
const part = dom.append(workbench, dom.$('.part.sessionspart.has-chat-background'));
613612
const chatView = dom.append(part, dom.$('.chat-view'));
614613
const newChatWidget = dom.append(chatView, dom.$('.sessions-chat-widget'));
615614
const newChatContent = dom.append(newChatWidget, dom.$('.new-chat-widget-content'));
616-
const inSessionWidget = dom.append(chatView, dom.$('.sessions-chat-widget.new-chat-in-session'));
617-
const inSessionContent = dom.append(inSessionWidget, dom.$('.new-chat-widget-content'));
615+
const newChatContainer = dom.append(newChatWidget, dom.$('.new-chat-widget-container'));
616+
const bottomContainer = dom.append(newChatContainer, dom.$('.new-chat-bottom-container'));
617+
const bottomAction = dom.append(bottomContainer, dom.$('.action-label'));
618+
const workspacePickerSlot = dom.append(newChatContainer, dom.$('.sessions-chat-picker-slot.sessions-workspace-category-picker-slot'));
619+
const workspacePill = dom.append(workspacePickerSlot, dom.$('.action-label'));
618620
const session = dom.append(chatView, dom.$('.interactive-session'));
619621
const secondaryToolbar = dom.append(session, dom.$('.chat-secondary-toolbar'));
620622
const secondaryAction = dom.append(secondaryToolbar, dom.$('.action-label'));
621623
const contextUsage = dom.append(secondaryToolbar, dom.$('.chat-context-usage-widget'));
622624
const plainPart = dom.append(workbench, dom.$('.part.sessionspart'));
623625
const plainChatView = dom.append(plainPart, dom.$('.chat-view'));
624-
const plainNewChatWidget = dom.append(plainChatView, dom.$('.sessions-chat-widget'));
625-
const plainNewChatContent = dom.append(plainNewChatWidget, dom.$('.new-chat-widget-content'));
626626
const plainSession = dom.append(plainChatView, dom.$('.interactive-session'));
627627
const plainSecondaryToolbar = dom.append(plainSession, dom.$('.chat-secondary-toolbar'));
628628
const plainSecondaryAction = dom.append(plainSecondaryToolbar, dom.$('.action-label'));
629629
const plainContextUsage = dom.append(plainSecondaryToolbar, dom.$('.chat-context-usage-widget'));
630+
const plainNewChatWidget = dom.append(plainChatView, dom.$('.sessions-chat-widget'));
631+
const plainNewChatContainer = dom.append(plainNewChatWidget, dom.$('.new-chat-widget-container'));
632+
const plainBottomContainer = dom.append(plainNewChatContainer, dom.$('.new-chat-bottom-container'));
633+
const plainBottomAction = dom.append(plainBottomContainer, dom.$('.action-label'));
630634
dom.getWindow(workbench).document.body.appendChild(workbench);
631635
disposables.add(toDisposable(() => workbench.remove()));
632636

633637
const newChatStyle = dom.getWindow(newChatContent).getComputedStyle(newChatContent);
638+
const bottomActionStyle = dom.getWindow(bottomAction).getComputedStyle(bottomAction);
639+
const workspacePillStyle = dom.getWindow(workspacePill).getComputedStyle(workspacePill);
634640
const secondaryActionStyle = dom.getWindow(secondaryAction).getComputedStyle(secondaryAction);
635641
const contextUsageStyle = dom.getWindow(contextUsage).getComputedStyle(contextUsage);
636642
assert.deepStrictEqual({
637643
newChatBackgroundColor: newChatStyle.backgroundColor,
638-
newChatBorderRadius: newChatStyle.borderRadius,
639644
newChatPadding: newChatStyle.padding,
640-
inSessionBackgroundColor: dom.getWindow(inSessionContent).getComputedStyle(inSessionContent).backgroundColor,
645+
bottomActionBackgroundColor: bottomActionStyle.backgroundColor,
646+
bottomActionBorderColor: bottomActionStyle.borderColor,
647+
bottomActionBorderStyle: bottomActionStyle.borderStyle,
648+
bottomActionBorderRadius: bottomActionStyle.borderRadius,
649+
workspacePillBackgroundColor: workspacePillStyle.backgroundColor,
641650
secondaryActionBackgroundColor: secondaryActionStyle.backgroundColor,
642651
secondaryActionBackgroundImage: secondaryActionStyle.backgroundImage,
643652
secondaryActionBorderColor: secondaryActionStyle.borderColor,
644653
secondaryActionBorderStyle: secondaryActionStyle.borderStyle,
645654
contextUsageBackgroundColor: contextUsageStyle.backgroundColor,
646655
contextUsageBackgroundImage: contextUsageStyle.backgroundImage,
647656
contextUsageBorderRadius: contextUsageStyle.borderRadius,
648-
plainNewChatBackgroundColor: dom.getWindow(plainNewChatContent).getComputedStyle(plainNewChatContent).backgroundColor,
649-
plainNewChatPadding: dom.getWindow(plainNewChatContent).getComputedStyle(plainNewChatContent).padding,
650657
plainSecondaryActionBackgroundColor: dom.getWindow(plainSecondaryAction).getComputedStyle(plainSecondaryAction).backgroundColor,
651658
plainSecondaryActionBorderStyle: dom.getWindow(plainSecondaryAction).getComputedStyle(plainSecondaryAction).borderStyle,
652659
plainContextUsageBackgroundColor: dom.getWindow(plainContextUsage).getComputedStyle(plainContextUsage).backgroundColor,
653660
plainContextUsageBorderStyle: dom.getWindow(plainContextUsage).getComputedStyle(plainContextUsage).borderStyle,
661+
plainBottomActionBackgroundColor: dom.getWindow(plainBottomAction).getComputedStyle(plainBottomAction).backgroundColor,
662+
plainBottomActionBorderStyle: dom.getWindow(plainBottomAction).getComputedStyle(plainBottomAction).borderStyle,
654663
}, {
655-
newChatBackgroundColor: 'color(srgb 1 1 1 / 0.86)',
656-
newChatBorderRadius: '8px',
657-
newChatPadding: '12px',
658-
inSessionBackgroundColor: 'rgba(0, 0, 0, 0)',
664+
newChatBackgroundColor: 'rgba(0, 0, 0, 0)',
665+
newChatPadding: '0px',
666+
bottomActionBackgroundColor: 'rgb(255, 255, 255)',
667+
bottomActionBorderColor: 'rgb(96, 96, 96)',
668+
bottomActionBorderStyle: 'solid',
669+
bottomActionBorderRadius: '4px',
670+
workspacePillBackgroundColor: 'rgb(255, 255, 255)',
659671
secondaryActionBackgroundColor: 'rgb(255, 255, 255)',
660672
secondaryActionBackgroundImage: 'linear-gradient(rgba(0, 0, 0, 0.08), rgba(0, 0, 0, 0.08))',
661673
secondaryActionBorderColor: 'rgb(128, 128, 128)',
662674
secondaryActionBorderStyle: 'solid',
663675
contextUsageBackgroundColor: 'rgb(255, 255, 255)',
664676
contextUsageBackgroundImage: 'linear-gradient(rgba(0, 0, 0, 0.08), rgba(0, 0, 0, 0.08))',
665677
contextUsageBorderRadius: '4px',
666-
plainNewChatBackgroundColor: 'rgba(0, 0, 0, 0)',
667-
plainNewChatPadding: '0px',
668678
plainSecondaryActionBackgroundColor: 'rgba(0, 0, 0, 0)',
669679
plainSecondaryActionBorderStyle: 'none',
670680
plainContextUsageBackgroundColor: 'rgba(0, 0, 0, 0)',
671681
plainContextUsageBorderStyle: 'none',
682+
plainBottomActionBackgroundColor: 'rgb(255, 255, 255)',
683+
plainBottomActionBorderStyle: 'none',
672684
});
673685
});
674686

src/vs/sessions/contrib/chat/test/browser/newChatWidget.fixture.ts

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { assert } from '../../../../../base/common/assert.js';
88
import { Codicon } from '../../../../../base/common/codicons.js';
99
import { Event } from '../../../../../base/common/event.js';
1010
import { MarkdownString } from '../../../../../base/common/htmlContent.js';
11+
import { DisposableStore } from '../../../../../base/common/lifecycle.js';
1112
import { constObservable, observableValue } from '../../../../../base/common/observable.js';
1213
import { extUri } from '../../../../../base/common/resources.js';
1314
import { mock } from '../../../../../base/test/common/mock.js';
@@ -44,6 +45,7 @@ import { activeSessionViewBackground } from '../../../../common/theme.js';
4445
import { Menus } from '../../../../browser/menus.js';
4546
import { AgentHostFilterConnectionStatus, IAgentHostFilterService } from '../../../../services/agentHostFilter/common/agentHostFilter.js';
4647
import { ISessionsChatBackgroundService } from '../../../../services/chatBackground/browser/chatBackgroundService.js';
48+
import { SessionsChatBackgroundRenderer } from '../../../../services/chatBackground/browser/chatBackgroundRenderer.js';
4749
import { ISessionsProvidersService } from '../../../../services/sessions/browser/sessionsProvidersService.js';
4850
import { ISessionsRecentWorkspacesService } from '../../../../services/sessions/browser/sessionsRecentWorkspacesService.js';
4951
import { ISessionsService } from '../../../../services/sessions/browser/sessionsService.js';
@@ -85,6 +87,7 @@ interface INewChatWidgetFixtureOptions {
8587
readonly primaryToolbarWidth?: number;
8688
readonly phoneLayout?: boolean;
8789
readonly migrationCount?: number;
90+
readonly withChatBackground?: boolean;
8891
}
8992

9093
class AutoModelFixtureMenuService extends FixtureMenuService {
@@ -121,6 +124,24 @@ class AutoModelFixtureMenuService extends FixtureMenuService {
121124
}
122125
}
123126

127+
/**
128+
* Wraps the composer in the `.part.sessionspart` host the Agents window uses and
129+
* paints the real codicon wallpaper into it, so the fixture shows the composer
130+
* the way it reads once a chat background is set.
131+
*/
132+
function createChatBackgroundPart(container: HTMLElement, disposableStore: DisposableStore): HTMLElement {
133+
const part = dom.append(container, dom.$('.part.sessionspart'));
134+
part.style.position = 'relative';
135+
part.style.width = '100%';
136+
part.style.height = '100%';
137+
// The part carries the opaque base, as it does in the Agents window, so the
138+
// session view above it can stay transparent and let the wallpaper through.
139+
part.style.backgroundColor = asCssVariable(activeSessionViewBackground);
140+
const renderer = disposableStore.add(new SessionsChatBackgroundRenderer(part));
141+
renderer.setBackground({ kind: 'codicons' });
142+
return part;
143+
}
144+
124145
/**
125146
* Renders the whole new-session composer (`NewChatView` → `NewChatWidget`) inside
126147
* a `.session-view` so the draft-comments banner sits above the input the way it
@@ -151,6 +172,7 @@ async function renderNewChatWidget(context: ComponentFixtureContext, options: IN
151172
primaryToolbarWidth,
152173
phoneLayout = false,
153174
migrationCount = 0,
175+
withChatBackground = false,
154176
} = options;
155177
const feedbackItems: readonly IAgentFeedback[] = Array.from({ length: commentCount }, (_, index) => ({
156178
id: `feedback-${index}`,
@@ -330,10 +352,12 @@ async function renderNewChatWidget(context: ComponentFixtureContext, options: IN
330352
container.classList.add('monaco-workbench', 'agent-sessions-workbench');
331353
container.classList.toggle('phone-layout', phoneLayout);
332354

333-
const sessionView = dom.append(container, dom.$('.session-view.is-active'));
355+
const sessionView = dom.append(withChatBackground ? createChatBackgroundPart(container, disposableStore) : container, dom.$('.session-view.is-active'));
334356
sessionView.style.width = '100%';
335357
sessionView.style.height = '100%';
336-
sessionView.style.backgroundColor = asCssVariable(activeSessionViewBackground);
358+
if (!withChatBackground) {
359+
sessionView.style.backgroundColor = asCssVariable(activeSessionViewBackground);
360+
}
337361
sessionView.style.setProperty('--session-view-background', asCssVariable(activeSessionViewBackground));
338362
const sessionViewContent = dom.append(sessionView, dom.$('.session-view-content'));
339363
sessionViewContent.style.width = '100%';
@@ -411,6 +435,11 @@ export default defineThemedFixtureGroup({ path: 'sessions/chat/newWidget/' }, {
411435
expectedVisualDescriptions: ['The new-session composer shows Copilot, microsoft/vscode, and Issue/PR pills aligned to the left above the chat input. Customize is aligned separately to the right edge of the input, with a yellow migration indicator and no chevron.'],
412436
render: context => renderNewChatWidget(context, { withWorkspace: true, migrationCount: 3 }),
413437
}),
438+
NewSessionChatBackground: defineComponentFixture({
439+
labels: { kind: 'screenshot', blocksCi: true },
440+
expectedVisualDescriptions: ['The new-session composer sits directly on the codicon wallpaper with no card behind it. The workspace pills, the input area and the bottom-row controls each carry their own opaque surface and a thin border, and the wallpaper shows through the gaps between them.'],
441+
render: context => renderNewChatWidget(context, { withWorkspace: true, withAutoModel: true, withChatBackground: true }),
442+
}),
414443
NewSessionAutoModel: defineComponentFixture({
415444
labels: { kind: 'screenshot', blocksCi: true },
416445
expectedVisualDescriptions: ['The new-session input toolbar shows an Auto model picker whose background fits closely around the Copilot icon and Auto label without excessive empty horizontal space. The bottom row shows optically tuned compact rocket, warning, and connection status icons centered in matching controls, followed by the full Status text action without clipping.'],

src/vs/sessions/contrib/providers/agentHost/browser/media/agentHostSessionConfigPicker.css

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
.sessions-chat-agent-host-config {
77
display: flex;
88
align-items: center;
9+
/* Each config control carries its own surface, so they need a gap. */
10+
gap: var(--vscode-spacing-size20);
911
min-width: 0;
1012
}
1113

0 commit comments

Comments
 (0)