Improve border radius of input stack (#330852)

* Improve border radius of input stack

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
Logan Ramos
2026-08-14 15:47:59 +00:00
committed by GitHub
co-authored by Copilot Autofix powered by AI
parent d144109c7f
commit edf4ef0416
7 changed files with 161 additions and 77 deletions
@@ -1001,6 +1001,8 @@
"--prompt-timeline-gutter-dot-gap",
"--prompt-timeline-gutter-more-height",
"--chat-input-notice-severity",
"--chat-input-notice-radius",
"--chat-input-own-radius",
"--chat-input-notification-line-height",
"--chat-editing-last-edit-shift",
"--chat-voice-icon-glow-color",
@@ -1148,6 +1150,7 @@
"--chat-input-anim-angle",
"--chat-input-anim-duration",
"--chat-input-radius",
"--chat-input-stack-radius-outer",
"--chat-input-stack-radius-top",
"--chat-input-working-border-color1",
"--session-input-banner-anim-angle",
@@ -11,8 +11,9 @@
box-sizing: border-box;
border: 1px solid var(--vscode-input-border, transparent);
border-bottom: none;
border-top-left-radius: var(--chat-input-stack-radius-top, var(--vscode-cornerRadius-small));
border-top-right-radius: var(--chat-input-stack-radius-top, var(--vscode-cornerRadius-small));
/* Follows the run it docks into; the fallback is its own tier outside one. */
border-top-left-radius: var(--chat-input-stack-radius-top, var(--vscode-cornerRadius-large));
border-top-right-radius: var(--chat-input-stack-radius-top, var(--vscode-cornerRadius-large));
background-color: color-mix(in srgb, var(--vscode-focusBorder) 6%, var(--vscode-editorWidget-background));
color: var(--vscode-foreground);
font-size: var(--vscode-agents-fontSize-label1);
@@ -80,10 +80,12 @@
}
/* A card closes its own box and sits clear of the input, so it reports itself
standalone and the input below keeps its own frame. */
standalone and the input below keeps its own frame. Its bottom corners are its
own, so they take the run's radius directly rather than through the stack. */
.chat-input-notice.chat-input-notice-onboarding {
--chat-input-notice-radius: var(--chat-input-stack-radius-outer, var(--vscode-cornerRadius-large));
border-bottom: var(--vscode-strokeThickness) solid var(--vscode-input-border, var(--vscode-widget-border, transparent));
border-radius: var(--chat-input-stack-radius-top, var(--vscode-cornerRadius-large)) var(--chat-input-stack-radius-top, var(--vscode-cornerRadius-large)) var(--vscode-cornerRadius-large) var(--vscode-cornerRadius-large);
border-radius: var(--chat-input-stack-radius-top, var(--chat-input-notice-radius)) var(--chat-input-stack-radius-top, var(--chat-input-notice-radius)) var(--chat-input-notice-radius) var(--chat-input-notice-radius);
margin-bottom: var(--vscode-spacing-size40);
}
@@ -21,16 +21,36 @@
.chat-input-stack {
gap: 0;
}
/* Reset first, so a nested stack does not inherit its container's squaring. */
.chat-input-stack > * {
--chat-input-stack-radius-top: initial;
/*
* The outer radius of the run: how round the joined surface is on the
* corners it does not share with a neighbour.
*
* The input owns this value - a denser host rounds it tighter - but a custom
* property cannot travel from the input back up to the members docked above
* it, so the input part declares `--chat-input-radius` (see `chat.css`) and
* the stack republishes it here for the whole run to read. The fallback
* covers a stack used outside a chat input part.
*/
--chat-input-stack-radius-outer: var(--chat-input-radius, var(--vscode-cornerRadius-large));
}
/*
* Members read `--chat-input-stack-radius-top` for their top corners. It is
* unset outside a stack, so a notice used elsewhere keeps its own rounding.
* What the stack dictates for a member's top corners: the run's outer radius for
* the member that opens the run, and a squared edge for every member that
* continues one. Reset here so a nested stack does not inherit its container's
* squaring; the rule below re-squares the ones that continue.
*
* Members already read this with their own value as the fallback, so a member
* used outside a stack keeps its own rounding and one inside a run follows the
* run - no member has to know which input it is docked to.
*/
.chat-input-stack > * {
--chat-input-stack-radius-top: var(--chat-input-stack-radius-outer);
}
/*
* A member that continues the run meets the one above it, so it squares the
* shared edge.
*/
.chat-input-stack > .chat-input-stack-continues {
--chat-input-stack-radius-top: 0px;
@@ -1078,18 +1078,45 @@ have to be updated for changes to the rules above, or to support more deeply nes
margin-right: 3px;
}
/*
* How round the chat input run is.
*
* Declared on the part that wraps the whole stack rather than on the input
* itself. Everything docked above the input joins it into a single surface, so
* those members have to round their outer corners to the same value - and a
* custom property only reaches them from a shared ancestor. `chatInputStack.css`
* republishes this as the run's outer radius; members read it from there.
*
* The three rules below are ordered, not just specific: `.compact` and the
* quick input scope both match at the same specificity and a compact input
* inside the quick input widget must round like a compact one, so `.compact`
* comes last. Keep them adjacent and in this order.
*/
.interactive-session .interactive-input-part {
--chat-input-radius: var(--vscode-cornerRadius-large);
}
.quick-input-widget .interactive-session .interactive-input-part {
--chat-input-radius: var(--vscode-cornerRadius-xSmall);
}
.interactive-session .interactive-input-part.compact {
--chat-input-radius: var(--vscode-cornerRadius-small);
}
.monaco-workbench .interactive-session .chat-input-container {
box-sizing: border-box;
cursor: text;
background-color: var(--vscode-input-background);
border: 1px solid var(--vscode-input-border, transparent);
/* Top corners follow the stack, so a docked widget can square them. */
--chat-input-radius: var(--vscode-cornerRadius-large);
/* Top corners follow the stack, so a docked widget can square them. The
fallback keeps an input rendered outside a chat input part rounded. */
--chat-input-own-radius: var(--chat-input-radius, var(--vscode-cornerRadius-large));
border-radius:
var(--chat-input-stack-radius-top, var(--chat-input-radius))
var(--chat-input-stack-radius-top, var(--chat-input-radius))
var(--chat-input-radius)
var(--chat-input-radius);
var(--chat-input-stack-radius-top, var(--chat-input-own-radius))
var(--chat-input-stack-radius-top, var(--chat-input-own-radius))
var(--chat-input-own-radius)
var(--chat-input-own-radius);
padding: 0 6px 6px 6px;
/* top padding is inside the editor widget */
width: 100%;
@@ -1769,7 +1796,6 @@ have to be updated for changes to the rules above, or to support more deeply nes
padding-bottom: 0;
/* no scrollbar */
padding-right: 6px;
--chat-input-radius: var(--vscode-cornerRadius-small);
}
.interactive-session .interactive-input-and-side-toolbar {
@@ -2585,7 +2611,6 @@ have to be updated for changes to the rules above, or to support more deeply nes
.quick-input-widget .interactive-session .chat-input-container {
margin: 0;
--chat-input-radius: var(--vscode-cornerRadius-xSmall);
padding: 0 4px 0 6px;
}
@@ -135,7 +135,7 @@
}
.chat-input-stack > .chat-input-notification-container.chat-input-stack-docked ~ .interactive-input-and-side-toolbar {
--chat-input-stack-radius-top: var(--vscode-cornerRadius-large);
--chat-input-stack-radius-top: var(--chat-input-stack-radius-outer);
}
}
@@ -11,12 +11,52 @@ import { chatInputStackClass, chatInputStackSlotClass, ChatInputStackSlot, setCh
import { ComponentFixtureContext, defineComponentFixture, defineThemedFixtureGroup } from '../fixtureUtils.js';
/**
* The three notice variants side by side. Five producers share one frame, and
* the only way to see that they still agree - and that severity only tints what
* it is meant to - is to put the variants next to each other at one width.
* Mounts one notice above a stand-in chat input, inside a real stack.
*
* The notice is wrapped in a slot that reports its state, so the squared join
* between the two - and the rounding they share - is produced by the stack
* rather than hardcoded here, and this breaks if that mechanism does.
*/
function renderNotices(context: ComponentFixtureContext): void {
function addNoticeStack(context: ComponentFixtureContext, variant: ChatInputNoticeVariant, className: string, severity?: string): ChatInputNoticeWidget {
const { container, disposableStore } = context;
const stack = dom.append(container, dom.$(`.${chatInputStackClass}`));
stack.style.display = 'flex';
stack.style.flexDirection = 'column';
const slot = dom.append(stack, dom.$(`.${chatInputStackSlotClass}`));
const notice = disposableStore.add(new ChatInputNoticeWidget({
container: slot,
variant,
className,
ariaLabel: className,
}));
if (severity) {
notice.domNode.classList.add(severity);
}
const input = dom.append(stack, dom.$('div'));
input.textContent = 'Chat input';
// Match the real input's host radius and square its top corners when docked.
input.style.cssText = 'padding:10px;color:var(--vscode-descriptionForeground);'
+ 'background:var(--vscode-agentsChatInput-background, var(--vscode-input-background));'
+ 'border:var(--vscode-strokeThickness) solid var(--vscode-input-border);'
+ 'border-radius:'
+ 'var(--chat-input-stack-radius-top, var(--chat-input-radius, var(--vscode-cornerRadius-large)))'
+ ' var(--chat-input-stack-radius-top, var(--chat-input-radius, var(--vscode-cornerRadius-large)))'
+ ' var(--chat-input-radius, var(--vscode-cornerRadius-large))'
+ ' var(--chat-input-radius, var(--vscode-cornerRadius-large));';
// Reported last, once the input is in the stack: reporting a slot rescans the
// stack's children, so doing it before the input exists would leave the input
// unmarked and its top corners rounded under a docked notice.
setChatInputStackSlot(slot, variant === ChatInputNoticeVariant.Onboarding
? ChatInputStackSlot.Standalone
: ChatInputStackSlot.Docked);
return notice;
}
function prepareContainer(container: HTMLElement): void {
container.classList.add('monaco-workbench');
container.style.width = '320px';
container.style.padding = '24px';
@@ -24,65 +64,28 @@ function renderNotices(context: ComponentFixtureContext): void {
container.style.flexDirection = 'column';
container.style.gap = '16px';
container.style.background = 'var(--vscode-editor-background)';
}
const addNotice = (variant: ChatInputNoticeVariant, className: string, severity?: string) => {
// Each notice sits in a real stack above a stand-in for the chat input, and
// the notice is wrapped in a slot that reports its state. The squared top
// corner on the input below is therefore produced by the stack rather than
// hardcoded here, so this breaks if the mechanism does.
const stack = dom.append(container, dom.$(`.${chatInputStackClass}`));
stack.style.display = 'flex';
stack.style.flexDirection = 'column';
const slot = dom.append(stack, dom.$(`.${chatInputStackSlotClass}`));
const notice = disposableStore.add(new ChatInputNoticeWidget({
container: slot,
variant,
className,
ariaLabel: className,
}));
if (severity) {
notice.domNode.classList.add(severity);
}
setChatInputStackSlot(slot, variant === ChatInputNoticeVariant.Onboarding
? ChatInputStackSlot.Standalone
: ChatInputStackSlot.Docked);
const input = dom.append(stack, dom.$('div'));
input.textContent = 'Chat input';
// Reads the stack's radius the way a real input does: rounded on its own,
// squared on top while something is docked above it.
input.style.cssText = 'padding:10px;color:var(--vscode-descriptionForeground);'
+ 'background:var(--vscode-agentsChatInput-background, var(--vscode-input-background));'
+ 'border:var(--vscode-strokeThickness) solid var(--vscode-input-border);'
+ 'border-radius:'
+ 'var(--chat-input-stack-radius-top, var(--vscode-cornerRadius-large))'
+ ' var(--chat-input-stack-radius-top, var(--vscode-cornerRadius-large))'
+ ' var(--vscode-cornerRadius-large) var(--vscode-cornerRadius-large);';
return notice;
};
// A tip: one row of prose, an icon, and a dismiss that sits in the flow.
const tip = addNotice(ChatInputNoticeVariant.Tip, 'fixture-tip');
/** A tip: one row of prose, an icon, and a dismiss that sits in the flow. */
function addTip(context: ComponentFixtureContext): void {
const tip = addNoticeStack(context, ChatInputNoticeVariant.Tip, 'fixture-tip');
dom.append(tip.domNode, dom.$(ThemeIcon.asCSSSelector(Codicon.lightbulb)));
dom.append(tip.domNode, dom.$('span')).textContent = 'Start a parallel conversation to build on all the changes made in this session.';
tip.addDismissAction({ onActivate: () => { } });
}
// A notification, at each severity: same frame, only the tint changes.
for (const [severity, icon, message] of [
['severity-info', Codicon.info, 'You are approaching your monthly limit.'],
['severity-warning', Codicon.warning, 'This model is temporarily unavailable.'],
['severity-error', Codicon.error, 'Sign in to keep using chat.'],
] as const) {
const notification = addNotice(ChatInputNoticeVariant.Notification, 'chat-input-notification-widget', severity);
const header = dom.append(notification.domNode, dom.$('.chat-input-notification-header'));
dom.append(dom.append(header, dom.$('.chat-input-notification-icon')), dom.$(ThemeIcon.asCSSSelector(icon)));
dom.append(header, dom.$('.chat-input-notification-title')).textContent = message;
notification.addDismissAction({ parent: header, onActivate: () => { } });
}
/** A notification: the same frame at every severity, only the tint changes. */
function addNotification(context: ComponentFixtureContext, severity: string, icon: ThemeIcon, message: string): void {
const notification = addNoticeStack(context, ChatInputNoticeVariant.Notification, 'chat-input-notification-widget', severity);
const header = dom.append(notification.domNode, dom.$('.chat-input-notification-header'));
dom.append(dom.append(header, dom.$('.chat-input-notification-icon')), dom.$(ThemeIcon.asCSSSelector(icon)));
dom.append(header, dom.$('.chat-input-notification-title')).textContent = message;
notification.addDismissAction({ parent: header, onActivate: () => { } });
}
// An onboarding card: a stack, with its close pinned to the corner.
const card = addNotice(ChatInputNoticeVariant.Onboarding, 'fixture-card');
/** An onboarding card: a stack, with its close pinned to the corner. */
function addOnboardingCard(context: ComponentFixtureContext): void {
const card = addNoticeStack(context, ChatInputNoticeVariant.Onboarding, 'fixture-card');
const copy = dom.append(card.domNode, dom.$('div'));
copy.style.paddingRight = 'var(--vscode-spacing-size240)';
dom.append(copy, dom.$('div')).textContent = 'Welcome to Voice Mode';
@@ -90,6 +93,36 @@ function renderNotices(context: ComponentFixtureContext): void {
card.addDismissAction({ onActivate: () => { } });
}
/**
* The three notice variants side by side. Five producers share one frame, and
* the only way to see that they still agree - and that severity only tints what
* it is meant to - is to put the variants next to each other at one width.
*/
function renderNotices(context: ComponentFixtureContext): void {
prepareContainer(context.container);
addTip(context);
for (const [severity, icon, message] of [
['severity-info', Codicon.info, 'You are approaching your monthly limit.'],
['severity-warning', Codicon.warning, 'This model is temporarily unavailable.'],
['severity-error', Codicon.error, 'Sign in to keep using chat.'],
] as const) {
addNotification(context, severity, icon, message);
}
addOnboardingCard(context);
}
/** Renders notices at the compact input radius, declared inline because this fixture does not load `chat.css`. */
function renderNoticesInCompactInput(context: ComponentFixtureContext): void {
prepareContainer(context.container);
context.container.style.setProperty('--chat-input-radius', 'var(--vscode-cornerRadius-small)');
addTip(context);
addNotification(context, 'severity-info', Codicon.info, 'You are approaching your monthly limit.');
addOnboardingCard(context);
}
export default defineThemedFixtureGroup({ path: 'chat/input/' }, {
'Chat input notices': defineComponentFixture({ render: renderNotices }),
'Chat input notices in a compact input': defineComponentFixture({ render: renderNoticesInCompactInput }),
});