Skip to content

Commit abea192

Browse files
authored
Fix: Chat UI consistency and layout shifts (#5237)
1 parent e4b051b commit abea192

File tree

4 files changed

+83
-77
lines changed

4 files changed

+83
-77
lines changed

webview-ui/src/components/chat/AutoApproveMenu.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ const AutoApproveMenu = ({ style }: AutoApproveMenuProps) => {
142142
display: "flex",
143143
alignItems: "center",
144144
gap: "8px",
145-
padding: isExpanded ? "8px 0" : "8px 0 0 0",
145+
padding: isExpanded ? "8px 0" : "2px 0 0 0",
146146
cursor: "pointer",
147147
}}
148148
onClick={toggleExpanded}>

webview-ui/src/components/chat/ChatTextArea.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -787,10 +787,10 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
787787
"relative",
788788
"flex",
789789
"flex-col",
790-
"gap-2",
790+
"gap-1",
791791
"bg-editor-background",
792-
"m-2 mt-1",
793-
"p-1.5",
792+
"px-1.5",
793+
"pb-1",
794794
"outline-none",
795795
"border",
796796
"border-none",
@@ -998,7 +998,7 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
998998
/>
999999
)}
10001000

1001-
<div className={cn("flex", "justify-between", "items-center", "mt-auto", "pt-0.5")}>
1001+
<div className={cn("flex", "justify-between", "items-center", "mt-auto")}>
10021002
<div className={cn("flex", "items-center", "gap-1", "min-w-0")}>
10031003
<div className="shrink-0">
10041004
<ModeSelector

webview-ui/src/components/chat/ChatView.tsx

Lines changed: 77 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -1424,6 +1424,8 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
14241424
vscode.postMessage({ type: "condenseTaskContextRequest", text: taskId })
14251425
}
14261426

1427+
const areButtonsVisible = showScrollToBottom || primaryButtonText || secondaryButtonText || isStreaming
1428+
14271429
return (
14281430
<div className={isHidden ? "hidden" : "fixed top-0 left-0 right-0 bottom-0 flex flex-col overflow-hidden"}>
14291431
{(showAnnouncement || showAnnouncementModal) && (
@@ -1525,7 +1527,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
15251527
// but becomes scrollable when the viewport is too small
15261528
*/}
15271529
{!task && (
1528-
<div className="mb-[-2px] flex-initial min-h-0">
1530+
<div className="mb-1 flex-initial min-h-0">
15291531
<AutoApproveMenu />
15301532
</div>
15311533
)}
@@ -1536,7 +1538,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
15361538
<Virtuoso
15371539
ref={virtuosoRef}
15381540
key={task.ts} // trick to make sure virtuoso re-renders when task changes, and we use initialTopMostItemIndex to start at the bottom
1539-
className="scrollable grow overflow-y-scroll mb-[5px]"
1541+
className="scrollable grow overflow-y-scroll mb-1"
15401542
// increasing top by 3_000 to prevent jumping around when user collapses a row
15411543
increaseViewportBy={{ top: 3_000, bottom: Number.MAX_SAFE_INTEGER }} // hack to make sure the last message is always rendered to get truly perfect scroll to bottom animation when new messages are added (Number.MAX_SAFE_INTEGER is safe for arithmetic operations, which is all virtuoso uses this value for in src/sizeRangeSystem.ts)
15421544
data={groupedMessages} // messages is the raw format returned by extension, modifiedMessages is the manipulated structure that combines certain messages of related type, and visibleMessages is the filtered structure that removes messages that should not be rendered
@@ -1552,83 +1554,87 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
15521554
initialTopMostItemIndex={groupedMessages.length - 1}
15531555
/>
15541556
</div>
1555-
<AutoApproveMenu />
1556-
{showScrollToBottom ? (
1557-
<div className="flex px-[15px] pt-[10px]">
1558-
<StandardTooltip content={t("chat:scrollToBottom")}>
1559-
<div
1560-
className="bg-[color-mix(in_srgb,_var(--vscode-toolbar-hoverBackground)_55%,_transparent)] rounded-[3px] overflow-hidden cursor-pointer flex justify-center items-center flex-1 h-[25px] hover:bg-[color-mix(in_srgb,_var(--vscode-toolbar-hoverBackground)_90%,_transparent)] active:bg-[color-mix(in_srgb,_var(--vscode-toolbar-hoverBackground)_70%,_transparent)]"
1561-
onClick={() => {
1562-
scrollToBottomSmooth()
1563-
disableAutoScrollRef.current = false
1564-
}}>
1565-
<span className="codicon codicon-chevron-down text-[18px]"></span>
1566-
</div>
1567-
</StandardTooltip>
1568-
</div>
1569-
) : (
1557+
<div className={`flex-initial min-h-0 ${!areButtonsVisible ? "mb-1" : ""}`}>
1558+
<AutoApproveMenu />
1559+
</div>
1560+
{areButtonsVisible && (
15701561
<div
1571-
className={`flex ${
1572-
primaryButtonText || secondaryButtonText || isStreaming ? "px-[15px] pt-[10px]" : "p-0"
1573-
} ${
1574-
primaryButtonText || secondaryButtonText || isStreaming
1575-
? enableButtons || (isStreaming && !didClickCancel)
1562+
className={`flex h-9 items-center mb-1 px-[15px] ${
1563+
showScrollToBottom
1564+
? "opacity-100"
1565+
: enableButtons || (isStreaming && !didClickCancel)
15761566
? "opacity-100"
15771567
: "opacity-50"
1578-
: "opacity-0"
15791568
}`}>
1580-
{primaryButtonText && !isStreaming && (
1581-
<StandardTooltip
1582-
content={
1583-
primaryButtonText === t("chat:retry.title")
1584-
? t("chat:retry.tooltip")
1585-
: primaryButtonText === t("chat:save.title")
1586-
? t("chat:save.tooltip")
1587-
: primaryButtonText === t("chat:approve.title")
1588-
? t("chat:approve.tooltip")
1589-
: primaryButtonText === t("chat:runCommand.title")
1590-
? t("chat:runCommand.tooltip")
1591-
: primaryButtonText === t("chat:startNewTask.title")
1592-
? t("chat:startNewTask.tooltip")
1593-
: primaryButtonText === t("chat:resumeTask.title")
1594-
? t("chat:resumeTask.tooltip")
1595-
: primaryButtonText === t("chat:proceedAnyways.title")
1596-
? t("chat:proceedAnyways.tooltip")
1597-
: primaryButtonText ===
1598-
t("chat:proceedWhileRunning.title")
1599-
? t("chat:proceedWhileRunning.tooltip")
1600-
: undefined
1601-
}>
1602-
<VSCodeButton
1603-
appearance="primary"
1604-
disabled={!enableButtons}
1605-
className={secondaryButtonText ? "flex-1 mr-[6px]" : "flex-[2] mr-0"}
1606-
onClick={() => handlePrimaryButtonClick(inputValue, selectedImages)}>
1607-
{primaryButtonText}
1608-
</VSCodeButton>
1609-
</StandardTooltip>
1610-
)}
1611-
{(secondaryButtonText || isStreaming) && (
1612-
<StandardTooltip
1613-
content={
1614-
isStreaming
1615-
? t("chat:cancel.tooltip")
1616-
: secondaryButtonText === t("chat:startNewTask.title")
1617-
? t("chat:startNewTask.tooltip")
1618-
: secondaryButtonText === t("chat:reject.title")
1619-
? t("chat:reject.tooltip")
1620-
: secondaryButtonText === t("chat:terminate.title")
1621-
? t("chat:terminate.tooltip")
1622-
: undefined
1623-
}>
1569+
{showScrollToBottom ? (
1570+
<StandardTooltip content={t("chat:scrollToBottom")}>
16241571
<VSCodeButton
16251572
appearance="secondary"
1626-
disabled={!enableButtons && !(isStreaming && !didClickCancel)}
1627-
className={isStreaming ? "flex-[2] ml-0" : "flex-1 ml-[6px]"}
1628-
onClick={() => handleSecondaryButtonClick(inputValue, selectedImages)}>
1629-
{isStreaming ? t("chat:cancel.title") : secondaryButtonText}
1573+
className="flex-[2]"
1574+
onClick={() => {
1575+
scrollToBottomSmooth()
1576+
disableAutoScrollRef.current = false
1577+
}}>
1578+
<span className="codicon codicon-chevron-down"></span>
16301579
</VSCodeButton>
16311580
</StandardTooltip>
1581+
) : (
1582+
<>
1583+
{primaryButtonText && !isStreaming && (
1584+
<StandardTooltip
1585+
content={
1586+
primaryButtonText === t("chat:retry.title")
1587+
? t("chat:retry.tooltip")
1588+
: primaryButtonText === t("chat:save.title")
1589+
? t("chat:save.tooltip")
1590+
: primaryButtonText === t("chat:approve.title")
1591+
? t("chat:approve.tooltip")
1592+
: primaryButtonText === t("chat:runCommand.title")
1593+
? t("chat:runCommand.tooltip")
1594+
: primaryButtonText === t("chat:startNewTask.title")
1595+
? t("chat:startNewTask.tooltip")
1596+
: primaryButtonText === t("chat:resumeTask.title")
1597+
? t("chat:resumeTask.tooltip")
1598+
: primaryButtonText ===
1599+
t("chat:proceedAnyways.title")
1600+
? t("chat:proceedAnyways.tooltip")
1601+
: primaryButtonText ===
1602+
t("chat:proceedWhileRunning.title")
1603+
? t("chat:proceedWhileRunning.tooltip")
1604+
: undefined
1605+
}>
1606+
<VSCodeButton
1607+
appearance="primary"
1608+
disabled={!enableButtons}
1609+
className={secondaryButtonText ? "flex-1 mr-[6px]" : "flex-[2] mr-0"}
1610+
onClick={() => handlePrimaryButtonClick(inputValue, selectedImages)}>
1611+
{primaryButtonText}
1612+
</VSCodeButton>
1613+
</StandardTooltip>
1614+
)}
1615+
{(secondaryButtonText || isStreaming) && (
1616+
<StandardTooltip
1617+
content={
1618+
isStreaming
1619+
? t("chat:cancel.tooltip")
1620+
: secondaryButtonText === t("chat:startNewTask.title")
1621+
? t("chat:startNewTask.tooltip")
1622+
: secondaryButtonText === t("chat:reject.title")
1623+
? t("chat:reject.tooltip")
1624+
: secondaryButtonText === t("chat:terminate.title")
1625+
? t("chat:terminate.tooltip")
1626+
: undefined
1627+
}>
1628+
<VSCodeButton
1629+
appearance="secondary"
1630+
disabled={!enableButtons && !(isStreaming && !didClickCancel)}
1631+
className={isStreaming ? "flex-[2] ml-0" : "flex-1 ml-[6px]"}
1632+
onClick={() => handleSecondaryButtonClick(inputValue, selectedImages)}>
1633+
{isStreaming ? t("chat:cancel.title") : secondaryButtonText}
1634+
</VSCodeButton>
1635+
</StandardTooltip>
1636+
)}
1637+
</>
16321638
)}
16331639
</div>
16341640
)}

webview-ui/src/components/history/TaskItem.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ const TaskItem = ({
6868
</div>
6969
)}
7070

71-
<div className="flex-1">
71+
<div className="flex-1 min-w-0">
7272
{/* Header with metadata */}
7373
<TaskItemHeader item={item} isSelectionMode={isSelectionMode} onDelete={onDelete} />
7474

0 commit comments

Comments
 (0)